STM32F1x固件库函数学习笔记(一)

news2024/11/23 17:25:43

文章目录

  • 一、基础知识
    • 1、什么是STM32
    • 2、STM32诞生背景
    • 3、STM32分类
    • 4、STM32F1X系列命名规则
    • 5、STM32F103C8T6最小系统
  • 二、STM32固件库
    • 1、初始固件库
      • (1)51单片机的寄存器
      • (2)STC8A通过库函数方式实现LED闪烁
      • (3)寄存器开发与库函数开发
      • (4)STM32F1固件库开发参考资料
      • (5)标准库和HAL库的区别
      • (6)STM32标准库说明及新建工程模板(略)
    • 2、GPIO输入输出与定时器延时函数
      • (1)STM32 GPIO的8种模式
      • (2)源码详解
    • 3、串口通信
      • (1)原理
      • (2)源码详解
    • 未完待续...

说明:本文所有例程使用STM32F103C8T6单片机运行无误

一、基础知识

1、什么是STM32

ST是意法半导体,M是Microelectronics(微控制器),32表示32位。合起来理解是:STM32就是ST公司开发的32位微控制器。

微控制器和微处理器区别:
最主要的区别在硬件结构上,微处理器就是一个单芯片的CPU,而微控制器则在集成了CPU和其他电路,构成了一个完整的微型计算机系统。微控制器除了CPU还包含RAM、ROM、串行接口、计时器、中断。

2、STM32诞生背景

51 是嵌入式学习中一款入门级的精典 MCU,因其结构简单,易于教学,且可以通过串口编程而不需要额外的仿真器,所以在教学时被大量采用,至今很多大学在嵌入式教学中用的还是 51。 51 诞生于 70 年代,属于传统的 8 位单片机,如今,久经岁月的洗礼,既有其辉煌又有其不足。现在的市场产品竞争越来越激烈,对成本极其敏感,相应地对 MCU的 性能要求也更苛刻: 更多功能, 更低功耗,易用界面和多任务。面对这些要求, 51 现有的 资源就显得得抓襟见肘。所以无论是高校教学还是市场需求,都急需一款新的 MCU 来为这个领域注入新的活力。

基于这样的市场需求, ARM 公司推出了其全新的基于 ARMv7 架构的 32 位 CortexM3 微控制器内核。紧随其后, ST(意法半导体)公司就推出了基于 Cortex-M3 内核的MCU—STM32。 STM32 凭借其产品线的多样化、极高的性价比、简单易用的库开发方 式,迅速在众多 Cortex-M3 MCU 中脱颖而出,成为最闪亮的一颗新星。 STM32 一上市就 迅速
占领了中低端 MCU 市场,受到了市场和工程师的无比青睐,颇有星火燎原之势。

作为一名合格的嵌入式工程师,面对新出现的技术,我们不是充耳不闻,而是要尽快吻合市场的需要,跟上技术的潮流。如今 STM32 的出现就是一种趋势,一种潮流,我们要做的就是搭上这趟快车,让自己的技术更有竞争力。

——参考野火原文《零死角玩转STM32—F429挑战者》

3、STM32分类

STM32 有很多系列,可以满足市场的各种需求,从内核上分有 Cortex-M0、 M3、 M4
和 M7 这几种在这里插入图片描述
——参考野火原文《零死角玩转STM32—F429挑战者》

4、STM32F1X系列命名规则

在这里插入图片描述

5、STM32F103C8T6最小系统

分享一个我之前学习STM32绘制的一个最小系统
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、STM32固件库

1、初始固件库

(1)51单片机的寄存器

之前写过51单片机的博客,对单片机的寄存器一些简要的说明,见下图
(图一)
回顾之前学习51单片机串口向PC机发送“0x01”的程序,如下:

#include <reg51.h>

/*向串口发送一字节数据*/
void SendOneByte(unsigned char c)
{
    SBUF = c;
    while(!TI);
    TI = 0;
}

void InitUART(void)
{
    TMOD = 0x20;//设置定时器1,工作方式2,8位自动重载
    SCON = 0x40;//设置串口工作方式1
    TH1 = 0xFD;//波特率设置为9600
    TL1 = TH1;
    PCON = 0x00;//波特率不加倍
    EA = 1;//开总中断
    ES = 1;//允许串联1中断
    TR1 = 1;//启动定时器T1
}

void main(void)
{
    InitUART();
	SendOneByte(0x01);
	while(1);
}

void UARTInterrupt(void) interrupt 4
{
    if(RI)
    {
        RI = 0;
        //add your code here!
    }
    else
        TI = 0;
}


通过这个程序我们可以理解到,CPU只有读和写这个操作,而我们要通过读和写寄存器变量的方式来实现来实现单片机控制外设的目的。
在程序中,我们通过给TMOD、SCON、TH1、TL1、PCON、EA、ES、TR1寄存器写数据来达到初始化串口的目的,而在串口中断中,我们又通过读RI寄存器来判断一帧数据发送完毕。

(2)STC8A通过库函数方式实现LED闪烁

学习过STC8单片机的应该知道,STC8是51单片机的一个升级版本,它完全兼容8051,可以使用直接操作寄存器的方式编程,也可以使用STC8A系列库函数进行开发,如下例程是使用“STC8A-SOFTWARE-LIB”实现LED闪烁的程序。

#include	"config.h"
#include	"GPIO.h"
#include	"delay.h"

void	GPIO_config(void)
{
	GPIO_InitTypeDef	GPIO_InitStructure;		//结构定义
	GPIO_InitStructure.Pin  = GPIO_Pin_0 | GPIO_Pin_1;		//指定要初始化的IO,
	GPIO_InitStructure.Mode = GPIO_PullUp;		//指定IO的输入或输出方式,GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
	GPIO_Inilize(GPIO_P5,&GPIO_InitStructure);	//初始化
}

void main(void)
{
	GPIO_config();
	
	while(1)
	{
		P50 = ~P50;
		delay_ms(250);
		P51 = ~P51;
		delay_ms(250);
	}
}

通过这个程序,我们我们初始化STC8A的GPIO的时候,我们不需要直接写寄存器,而是先使用GPIO_InitTypeDef定义GPIO初始化结构体变量,然后调用GPIO_Inilize()函数,直接初始化GPIO。这便是一个经典的使用库函数编程。

那么,GPIO_InitTypeDef和GPIO_Inilize()函数里都有什么呢?我们看看

//GPIO_InitTypeDef
typedef struct
{
	u8	Mode;		//IO模式,  		GPIO_PullUp,GPIO_HighZ,GPIO_OUT_OD,GPIO_OUT_PP
	u8	Pin;		//要设置的端口	
} GPIO_InitTypeDef;
//========================================================================
// 函数: u8	GPIO_Inilize(u8 GPIO, GPIO_InitTypeDef *GPIOx)
// 描述: 初始化IO口.
// 参数: GPIOx: 结构参数,请参考timer.h里的定义.
// 返回: 成功返回0, 空操作返回1,错误返回2.
// 版本: V1.0, 2012-10-22
//========================================================================
u8	GPIO_Inilize(u8 GPIO, GPIO_InitTypeDef *GPIOx)
{
	if(GPIO > GPIO_P7)				return 1;	//空操作
	if(GPIOx->Mode > GPIO_OUT_PP)	return 2;	//错误
	if(GPIO == GPIO_P0)
	{
		if(GPIOx->Mode == GPIO_PullUp)		P0M1 &= ~GPIOx->Pin,	P0M0 &= ~GPIOx->Pin;	 //上拉准双向口
		if(GPIOx->Mode == GPIO_HighZ)		P0M1 |=  GPIOx->Pin,	P0M0 &= ~GPIOx->Pin;	 //浮空输入
		if(GPIOx->Mode == GPIO_OUT_OD)		P0M1 |=  GPIOx->Pin,	P0M0 |=  GPIOx->Pin;	 //开漏输出
		if(GPIOx->Mode == GPIO_OUT_PP)		P0M1 &= ~GPIOx->Pin,	P0M0 |=  GPIOx->Pin;	 //推挽输出
	}
	if(GPIO == GPIO_P1)
	{
		if(GPIOx->Mode == GPIO_PullUp)		P1M1 &= ~GPIOx->Pin,	P1M0 &= ~GPIOx->Pin;	 //上拉准双向口
		if(GPIOx->Mode == GPIO_HighZ)		P1M1 |=  GPIOx->Pin,	P1M0 &= ~GPIOx->Pin;	 //浮空输入
		if(GPIOx->Mode == GPIO_OUT_OD)		P1M1 |=  GPIOx->Pin,	P1M0 |=  GPIOx->Pin;	 //开漏输出
		if(GPIOx->Mode == GPIO_OUT_PP)		P1M1 &= ~GPIOx->Pin,	P1M0 |=  GPIOx->Pin;	 //推挽输出
	}
	if(GPIO == GPIO_P2)
	{
		if(GPIOx->Mode == GPIO_PullUp)		P2M1 &= ~GPIOx->Pin,	P2M0 &= ~GPIOx->Pin;	 //上拉准双向口
		if(GPIOx->Mode == GPIO_HighZ)		P2M1 |=  GPIOx->Pin,	P2M0 &= ~GPIOx->Pin;	 //浮空输入
		if(GPIOx->Mode == GPIO_OUT_OD)		P2M1 |=  GPIOx->Pin,	P2M0 |=  GPIOx->Pin;	 //开漏输出
		if(GPIOx->Mode == GPIO_OUT_PP)		P2M1 &= ~GPIOx->Pin,	P2M0 |=  GPIOx->Pin;	 //推挽输出
	}
	if(GPIO == GPIO_P3)
	{
		if(GPIOx->Mode == GPIO_PullUp)		P3M1 &= ~GPIOx->Pin,	P3M0 &= ~GPIOx->Pin;	 //上拉准双向口
		if(GPIOx->Mode == GPIO_HighZ)		P3M1 |=  GPIOx->Pin,	P3M0 &= ~GPIOx->Pin;	 //浮空输入
		if(GPIOx->Mode == GPIO_OUT_OD)		P3M1 |=  GPIOx->Pin,	P3M0 |=  GPIOx->Pin;	 //开漏输出
		if(GPIOx->Mode == GPIO_OUT_PP)		P3M1 &= ~GPIOx->Pin,	P3M0 |=  GPIOx->Pin;	 //推挽输出
	}
	if(GPIO == GPIO_P4)
	{
		if(GPIOx->Mode == GPIO_PullUp)		P4M1 &= ~GPIOx->Pin,	P4M0 &= ~GPIOx->Pin;	 //上拉准双向口
		if(GPIOx->Mode == GPIO_HighZ)		P4M1 |=  GPIOx->Pin,	P4M0 &= ~GPIOx->Pin;	 //浮空输入
		if(GPIOx->Mode == GPIO_OUT_OD)		P4M1 |=  GPIOx->Pin,	P4M0 |=  GPIOx->Pin;	 //开漏输出
		if(GPIOx->Mode == GPIO_OUT_PP)		P4M1 &= ~GPIOx->Pin,	P4M0 |=  GPIOx->Pin;	 //推挽输出
	}
	if(GPIO == GPIO_P5)
	{
		if(GPIOx->Mode == GPIO_PullUp)		P5M1 &= ~GPIOx->Pin,	P5M0 &= ~GPIOx->Pin;	 //上拉准双向口
		if(GPIOx->Mode == GPIO_HighZ)		P5M1 |=  GPIOx->Pin,	P5M0 &= ~GPIOx->Pin;	 //浮空输入
		if(GPIOx->Mode == GPIO_OUT_OD)		P5M1 |=  GPIOx->Pin,	P5M0 |=  GPIOx->Pin;	 //开漏输出
		if(GPIOx->Mode == GPIO_OUT_PP)		P5M1 &= ~GPIOx->Pin,	P5M0 |=  GPIOx->Pin;	 //推挽输出
	}
	if(GPIO == GPIO_P6)
	{
		if(GPIOx->Mode == GPIO_PullUp)		P6M1 &= ~GPIOx->Pin,	P6M0 &= ~GPIOx->Pin;	 //上拉准双向口
		if(GPIOx->Mode == GPIO_HighZ)		P6M1 |=  GPIOx->Pin,	P6M0 &= ~GPIOx->Pin;	 //浮空输入
		if(GPIOx->Mode == GPIO_OUT_OD)		P6M1 |=  GPIOx->Pin,	P6M0 |=  GPIOx->Pin;	 //开漏输出
		if(GPIOx->Mode == GPIO_OUT_PP)		P6M1 &= ~GPIOx->Pin,	P6M0 |=  GPIOx->Pin;	 //推挽输出
	}
	if(GPIO == GPIO_P7)
	{
		if(GPIOx->Mode == GPIO_PullUp)		P7M1 &= ~GPIOx->Pin,	P7M0 &= ~GPIOx->Pin;	 //上拉准双向口
		if(GPIOx->Mode == GPIO_HighZ)		P7M1 |=  GPIOx->Pin,	P7M0 &= ~GPIOx->Pin;	 //浮空输入
		if(GPIOx->Mode == GPIO_OUT_OD)		P7M1 |=  GPIOx->Pin,	P7M0 |=  GPIOx->Pin;	 //开漏输出
		if(GPIOx->Mode == GPIO_OUT_PP)		P7M1 &= ~GPIOx->Pin,	P7M0 |=  GPIOx->Pin;	 //推挽输出
	}
	return 0;	//成功
}

我们查看GPIO_InitTypeDef结构体时发现mode可以直接赋值GPIO_PullUp, GPIO_HighZ, GPIO_OUT_OD, GPIO_OUT_PP,那么这些又是什么呢?

我们查看GPIO.h文件可以得知如下:

#define	GPIO_PullUp		0	//上拉准双向口
#define	GPIO_HighZ		1	//浮空输入
#define	GPIO_OUT_OD		2	//开漏输出
#define	GPIO_OUT_PP		3	//推挽输出

我们还在GPIO.h程序中看到其他已经宏定义好的字符串

#define	GPIO_Pin_0		0x01	//IO引脚 Px.0
#define	GPIO_Pin_1		0x02	//IO引脚 Px.1
#define	GPIO_Pin_2		0x04	//IO引脚 Px.2
#define	GPIO_Pin_3		0x08	//IO引脚 Px.3
#define	GPIO_Pin_4		0x10	//IO引脚 Px.4
#define	GPIO_Pin_5		0x20	//IO引脚 Px.5
#define	GPIO_Pin_6		0x40	//IO引脚 Px.6
#define	GPIO_Pin_7		0x80	//IO引脚 Px.7
#define	GPIO_Pin_All	0xFF	//IO所有引脚
	
#define	GPIO_P0			0		
#define	GPIO_P1			1
#define	GPIO_P2			2
#define	GPIO_P3			3
#define	GPIO_P4			4
#define	GPIO_P5			5
#define	GPIO_P6			6
#define	GPIO_P7			7

(3)寄存器开发与库函数开发

我通过51单片机初始化串口和STC8A库函数方式实现LED灯闪烁,两个简单的例子,简单说明了,直接操作寄存器开发与调用库函数开发。

在51单片机开发过程中,因为51单片机相对简单,所以都是直接使用操作寄存器开发方式,这些工作很繁琐、机械。但是到了STM32开发时,外设资源非常丰富,必然寄存器和和复杂度增加了,直接配置寄存器方式就会造成开发速度慢、程序可读性差、维护复杂等缺点。而调用库函数开发正好可以弥补这些缺点。

但我们通过上面STC8A使用库函数方式使LED闪烁可以看到,只是一个简单的GPIO初始化源码代码量居然就这么大。这也是库函数开发的缺点,使用库函数开发会使代码量增大,库函数会把所有情况考虑到库函数里,势必会造成代码冗余。

所以直接操作寄存器开发不可否认还是具备程序运行占用资源少、代码简单明了、具体参数更直观的优点。

在摩尔定律预言中,每隔18个月,计算机的芯片集成度会提高一倍,功能提高一倍,而价格则下降为一半。现如今,STM32有足够的资源,权衡寄存器和库函数开发的优缺点,我们大多数的时候都会牺牲一些CPU资源来使用库函数开发。只有在一些对代码要求极为苛刻的地方,才会使用直接操作寄存器开发。调用库函数开发说得更加直接了当一点就是:我想偷点懒,然后就直接白嫖官方写好的程序,直接拿来用。而官方的程序帮我们把寄存器的工作全部都已经配置完成了,我不需要了解人家是怎么配置的,我知道的是白嫖过来后,程序下到板子里居然能跑

(4)STM32F1固件库开发参考资料

1、STM32F1X固件函数库:用于查阅STM32F1标准库,有英文的帮助文档,也要翻译好的中文pdf文档。
2、STM32F103X数据手册:介绍芯片的一些基本参数、功能一栏、电气特性、封装参数等。
3、STM32F10X参考手册:此手册包含各个模块的内部结构、所有可能的功能描述、各个工作模式的使用寄存器配置等详细信息,值得注意的是参考手册不包含有关产品的计数特征说明,这些在芯片手册中
4、Cortex-M3权威指南:本书详细概述了ARM Cortex-M3内核的架构和特性。

(5)标准库和HAL库的区别

标准库是直接把寄存器封装好的库,而HAL库可以直接使用ST的CUBEMX软件生成,相比于HAL库可移植性更强。在推行时间上,HAL库是最近才推出的库,STM32官方也主推,而标准库是STM32最早推出的库,应用非常广泛,在市面上的模块都能找得到例程,但是比较新的F7和H7等系列已经不支持了。

如果在时间紧迫的情况下,只需要掌握其中一种库就行了,没必要两种都掌握。

如果以后时间充裕的话,我写一份HAL库的博客…

(6)STM32标准库说明及新建工程模板(略)

这段内容网上有太多的资料,此处直接略过。
关于STM32标准库的说明和新建一个STM32工程模板可以多到B站上看看视频。可以到B站找一下野火的视频,不是打广告,野火也没有给我任何好处,也不是我的什么亲戚,我自己学习STM32的时候也是看野火的视频,讲得还不错。看完视频,然后再找一本参考书《STM32库开发指南(基于STM32F103)》,把视频的知识点和书中的结合,多做笔记,多动手实践。

最后,如果之前学习51单片机安装了keil c51的话,现在安装keil arm是可以安装在一起的,多去网上找找资料,或者安装两个不同的软件,分别安装在不同的目录。

2、GPIO输入输出与定时器延时函数

(1)STM32 GPIO的8种模式

通过《STM32F10X-参考手册中文版》可以得知,每个GPIO管脚都可以由软件设置成输出(推挽或开漏)、输入(带或者不带上下拉)或其他外设功能接口,多数GPIO管脚数字或模拟的外设共用,具体GPIO模式如下8种:

  • 输入浮空
  • 输入上拉
  • 输入下拉
  • 模拟输入
  • 开漏输出
  • 推挽输出
  • 推挽复用功能
  • 开漏复用功能

(2)源码详解

1)首先,我们将系统时钟设置成72MHZ

//将系统时钟配置为72MHZ
void RCC_Configuration(void)
{   
  SystemInit();
}

2)封装GPIO推挽式输出、按键输入、按键检测函数

/**
  ******************************************************************************
  * @file    gpio.c
  * @author  小途
  * @version V1.0
  * @date    2022-2-21
  * @brief   STM32F1的GPIO初始化设置
  ******************************************************************************
  * @attention
  *
  *
  ******************************************************************************
  */

#include "gpio.h"

/***************************************************
 *函数名称:void gpio_OutputConfig(uint32_t RCC_APB2Periph, uint16_t pin, GPIO_TypeDef* GPIOx)
 *函数输入:RCC_APB2Periph:		初始化GPIO总线的外设时钟
 *					pin:								引脚数
 *					GPIOx: 							where x can be (A..G) to select the GPIO peripheral.			
 *函数返回:无
 *函数说明:GPIO推挽式输出初始化
 ***************************************************/
void gpio_OutputConfig(uint32_t RCC_APB2Periph, uint16_t pin, GPIO_TypeDef* GPIOx)
{
	//定义一个GPIO_InitTypeDef类型结构体
	GPIO_InitTypeDef GPIO_InitStructure;
	
	//开启要初始化GPIO总线的外设时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph, ENABLE);
	
	//选择要控制的GPIO引脚
	GPIO_InitStructure.GPIO_Pin = pin;
	
	//设置引脚模式为通用推挽输出
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	
	//设置引脚速率为50MHZ
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	
	//初始化GPIO
	GPIO_Init(GPIOx, &GPIO_InitStructure);
}

/**
  * @brief  配置按键用到的I/O口
  * @param  无
  * @retval 无
  */
void Key_GPIO_Config(uint32_t RCC_APB2Periph, uint16_t pin, GPIO_TypeDef* GPIOx)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/*开启按键端口时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph, ENABLE);
	
	//选择按键的引脚
	GPIO_InitStructure.GPIO_Pin = pin;
	
	// 设置按键的引脚为浮空输入
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
	
	//使用结构体初始化按键
	GPIO_Init(GPIOx, &GPIO_InitStructure);
	
}

 /*
 * 函数名:Key_Scan
 * 描述  :检测是否有按键按下
 * 输入  :GPIOx:x 可以是 A,B,C,D或者 E
 *		     GPIO_Pin:待读取的端口位 	
 * 输出  :KEY_OFF(没按下按键)、KEY_ON(按下按键)
 */
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{			
	/*检测是否有按键按下 */
	if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON )  
	{	 
		/*等待按键释放 */
		while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);   
		return 	KEY_ON;	 
	}
	else
		return KEY_OFF;
}


//gpio.h
#ifndef __GPIO_H
#define __GPIO_H

#include "stm32f10x.h"

 /** 按键按下标置宏
	*  按键按下为高电平,设置 KEY_ON=1, KEY_OFF=0
	*  若按键按下为低电平,把宏设置成KEY_ON=0 ,KEY_OFF=1 即可
	*/
#define KEY_ON	1
#define KEY_OFF	0

void gpio_OutputConfig(uint32_t RCC_APB2Periph, uint16_t pin, GPIO_TypeDef* GPIOx);
void Key_GPIO_Config(uint32_t RCC_APB2Periph, uint16_t pin, GPIO_TypeDef* GPIOx);
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);

#endif

3)配置延时函数定时器
51单片机开发中,常使用软件执行空语句来达到延时一段时间的目的。这种延时会导致CPU资源被占用无法执行其他程序,导致系统效率降低。在STM32开发中,可以使用定时器来达到延时效果。

/**
  ******************************************************************************
  * @file    delay.c
  * @author  小途
  * @version V1.0
  * @date    2022-2-21
  * @brief   STM32F1的延时函数
  ******************************************************************************
  * @attention
  *
	* 使用定时器4(通用定时器),备用2个基本定时器TIM6、TIM7
  *
  ******************************************************************************
  */

#include "delay.h"

//延时函数定时器初始,初始化定时器4
void Delay_Timer_Init(void)
{
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
  
	TIM_TimeBaseStructInit(&TIM_TimeBaseInitStruct);
	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Down;
	TIM_TimeBaseInitStruct.TIM_Period = 100-1;
	TIM_TimeBaseInitStruct.TIM_Prescaler = (84-1);
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStruct);
	
	while((TIM4->SR & TIM_FLAG_Update)!=SET);
	TIM4->SR = (uint16_t)~TIM_FLAG_Update;
}

void Delay_us(uint32_t us_cnt)
{
	TIM4->CNT = us_cnt-1;
	TIM4->CR1 |= TIM_CR1_CEN;    
	while((TIM4->SR & TIM_FLAG_Update)!=SET);
	TIM4->SR = (uint16_t)~TIM_FLAG_Update;
	TIM4->CR1 &= ~TIM_CR1_CEN;
}

void Delay_ms(uint32_t ms_cnt)
{
	int i;
	for(i=0; i<ms_cnt; i++)
	{
		Delay_us(1000);
	}
}
#ifndef __DELAY_H
#define __DELAY_H

#include "stm32f10x.h"


/********************基本定时器TIM参数定义,只限TIM6、7************/
#define BASIC_TIM6 // 如果使用TIM7,注释掉这个宏即可

#ifdef  BASIC_TIM6 // 使用基本定时器TIM6
#define            BASIC_TIM                   TIM6
#define            BASIC_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            BASIC_TIM_CLK               RCC_APB1Periph_TIM6
#define            BASIC_TIM_Period            (1000-1)
#define            BASIC_TIM_Prescaler         71
#define            BASIC_TIM_IRQ               TIM6_IRQn
#define            BASIC_TIM_IRQHandler        TIM6_IRQHandler

#else  // 使用基本定时器TIM7
#define            BASIC_TIM                   TIM7
#define            BASIC_TIM_APBxClock_FUN     RCC_APB1PeriphClockCmd
#define            BASIC_TIM_CLK               RCC_APB1Periph_TIM7
#define            BASIC_TIM_Period            1000-1
#define            BASIC_TIM_Prescaler         71
#define            BASIC_TIM_IRQ               TIM7_IRQn
#define            BASIC_TIM_IRQHandler        TIM7_IRQHandler

#endif
/**************************函数声明********************************/
void Delay_Timer_Init(void);
void Delay_us(uint32_t us_cnt);
void Delay_ms(uint32_t ms_cnt);

#endif

4)对GPIO输出端口进行宏定义
在编写程序过程中,还应该考虑硬件修改的情况。例如输出端口改变,这时我们更希望修改一小部分代码就可以在新的硬件上运行。我们可以把硬件的相关部分使用宏来进行封装,若更改了硬件环境,只需要修改相关硬件的宏就可以了。

#define LED5_ON 				GPIO_SetBits(GPIOC, GPIO_Pin_13)
#define LED5_OFF 				GPIO_ResetBits(GPIOC, GPIO_Pin_13)

5)主函数

int main(void)
{
	RCC_Configuration();   				
	Delay_Timer_Init();	
	gpio_OutputConfig(RCC_APB2Periph_GPIOC, GPIO_Pin_13, GPIOC);
	while(1)
	{
		LED5_ON;
		Delay_ms(500);
		LED5_OFF;
		Delay_ms(500);
	}
}

3、串口通信

(1)原理

这个段落主要讲解标准库编程源码,关于串口通信的基础,可以查看这一篇文章
【通信基础】TTL、RS232、RS485

通过查看《STM32F103数据手册中文版》STM32F103C8T6共有3个串口,各个默认串口引脚分布如下:
串口1:TX——PA9,RX——PA10
串口2:TX——PA2,RX——PA3
串口3:TX——PB10,TX——PB11

继续查看发现STM32F103C8T6光串口一就有5个功能管脚,如下图所示:
在这里插入图片描述
那么,USARTx_CK、USARTx_CTS、USARTx_RTS分别是什么呢?
首先,USARTx_CK很好理解,CK表示的是SCLK,中文意思是时钟。该管脚是一个时钟输出引脚,用于同步模式下使用。而关于CTS与RTS接口,我们是否还记得之前学习232通信的DB9接口,如下图所示:
在这里插入图片描述
我们看到DB9接口8和7管脚正好是CTS和RTS接口,这两个管脚的功能也如上图中所示,分别是清除发生和发生数据请求。
我们玩232通信都接触过DB9接口,但是我们使用最多的也就是TX、RX、GND这三个接口。但是,在STM32已经具备硬件流控的功能。这里只做了解,我平时开发的时候很少会用到。

这时我们还是编程第一步,编写usart.c和usart.h文件

(2)源码详解

/**
  ******************************************************************************
  * @file    usart.c
  * @author  小途
  * @version V1.0
  * @date    2022-2-21
  * @brief   STM32F1的串口初始化设置
  ******************************************************************************
  * @attention
  *
  *
  ******************************************************************************
  */

#include "usart.h"


 /**
  * @brief  配置嵌套向量中断控制器NVIC
  * @param  无
  * @retval 无
  */
static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* 嵌套向量中断控制器组选择 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  
  /* 配置USART为中断源 */
  NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
  /* 抢断优先级*/
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  /* 子优先级 */
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  /* 使能中断 */
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  /* 初始化配置NVIC */
  NVIC_Init(&NVIC_InitStructure);
}

 /**
  * @brief  USART GPIO 配置,工作参数配置
  * @param  无
  * @retval 无
  */
void USART_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	// 打开串口GPIO的时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	
	// 打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);

	// 将USART Tx的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  // 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
	
	// 配置串口的工作参数
	// 配置波特率
	USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
	// 配置 针数据字长
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	// 配置停止位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	// 配置校验位
	USART_InitStructure.USART_Parity = USART_Parity_No ;
	// 配置硬件流控制
	USART_InitStructure.USART_HardwareFlowControl = 
	USART_HardwareFlowControl_None;
	// 配置工作模式,收发一起
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	// 完成串口的初始化配置
	USART_Init(DEBUG_USARTx, &USART_InitStructure);
	
	// 串口中断优先级配置
	NVIC_Configuration();
	
	// 使能串口接收中断
	USART_ITConfig(DEBUG_USARTx, USART_IT_RXNE, ENABLE);	
	
	// 使能串口
	USART_Cmd(DEBUG_USARTx, ENABLE);	    
}

 /**
  * @brief  USART2 GPIO 配置,工作参数配置
  * @param  无
  * @retval 无
  */
void USART2_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	// 打开串口GPIO的时钟
	DEBUG_USART2_GPIO_APBxClkCmd(DEBUG_USART2_GPIO_CLK, ENABLE);
	
	// 打开串口外设的时钟
	DEBUG_USART2_APBxClkCmd(DEBUG_USART2_CLK, ENABLE);

	// 将USART Tx的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART2_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART2_TX_GPIO_PORT, &GPIO_InitStructure);

  // 将USART Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin = DEBUG_USART2_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART2_RX_GPIO_PORT, &GPIO_InitStructure);
	
	// 配置串口的工作参数
	// 配置波特率
	USART_InitStructure.USART_BaudRate = DEBUG_USART2_BAUDRATE;
	// 配置 针数据字长
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	// 配置停止位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;
	// 配置校验位
	USART_InitStructure.USART_Parity = USART_Parity_No ;
	// 配置硬件流控制
	USART_InitStructure.USART_HardwareFlowControl = 
	USART_HardwareFlowControl_None;
	// 配置工作模式,收发一起
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	// 完成串口的初始化配置
	USART_Init(DEBUG_USART2, &USART_InitStructure);	
	
	// 使能串口
	USART_Cmd(DEBUG_USART2, ENABLE);	    
}

/*****************  发送一个字符 **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
	/* 发送一个字节数据到USART */
	USART_SendData(pUSARTx,ch);
		
	/* 等待发送数据寄存器为空 */
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

/*****************  发送字符串 **********************/
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
	unsigned int k=0;
  do 
  {
      Usart_SendByte( pUSARTx, *(str + k) );
      k++;
  } while(*(str + k)!='\0');
  
  /* 等待发送完成 */
  while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);
}

//重定向c库函数printf到串口,重定向后可使用printf函数,默认使用串口1
int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到串口 */
		USART_SendData(DEBUG_USART1, (uint8_t) ch);
		
		/* 等待发送完毕 */
		while (USART_GetFlagStatus(DEBUG_USART1, USART_FLAG_TXE) == RESET);		
	
		return (ch);
}

//重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数,默认使用串口1
int fgetc(FILE *f)
{
		/* 等待串口输入数据 */
		while (USART_GetFlagStatus(DEBUG_USART1, USART_FLAG_RXNE) == RESET);

		return (int)USART_ReceiveData(DEBUG_USART1);
}

#ifndef __USART_H
#define __USART_H

#include "stm32f10x.h"
#include <stdio.h>

/*
 * 串口宏定义,不同的串口挂载的总线不一样,移植时需要修改这几个宏
 */
 
 
 // 串口1-USART1
#define  DEBUG_USARTx                   USART1
#define  DEBUG_USART_CLK                RCC_APB2Periph_USART1
#define  DEBUG_USART_APBxClkCmd         RCC_APB2PeriphClockCmd
#define  DEBUG_USART_BAUDRATE           115200

// USART GPIO 引脚宏定义
#define  DEBUG_USART_GPIO_CLK           (RCC_APB2Periph_GPIOA)
#define  DEBUG_USART_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd
    
#define  DEBUG_USART_TX_GPIO_PORT       GPIOA   
#define  DEBUG_USART_TX_GPIO_PIN        GPIO_Pin_9
#define  DEBUG_USART_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_10

#define  DEBUG_USART_IRQ                USART1_IRQn
#define  DEBUG_USART_IRQHandler         USART1_IRQHandler
 
 
 
 
 
 //------------------------------------------------------------------------

// 串口1-USART1
#define  DEBUG_USART1                    USART1
#define  DEBUG_USART1_CLK                RCC_APB2Periph_USART1
#define  DEBUG_USART1_APBxClkCmd         RCC_APB2PeriphClockCmd
#define  DEBUG_USART1_BAUDRATE           115200

// USART GPIO 引脚宏定义
#define  DEBUG_USART1_GPIO_CLK           (RCC_APB2Periph_GPIOA)
#define  DEBUG_USART1_GPIO_APBxClkCmd    RCC_APB2PeriphClockCmd
    
#define  DEBUG_USART1_TX_GPIO_PORT       GPIOA   
#define  DEBUG_USART1_TX_GPIO_PIN        GPIO_Pin_9
#define  DEBUG_USART1_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART1_RX_GPIO_PIN        GPIO_Pin_10

#define  DEBUG_USART1_IRQ                USART1_IRQn
#define  DEBUG_USART1_IRQHandler         USART1_IRQHandler

//------------------------------------------------------------------------

// 串口2-USART2
#define  DEBUG_USART2                   	USART2
#define  DEBUG_USART2_CLK                	RCC_APB1Periph_USART2
#define  DEBUG_USART2_APBxClkCmd         	RCC_APB1PeriphClockCmd
#define  DEBUG_USART2_BAUDRATE           	115200

// USART GPIO 引脚宏定义
#define  DEBUG_USART2_GPIO_CLK           	(RCC_APB2Periph_GPIOA)
#define  DEBUG_USART2_GPIO_APBxClkCmd    	RCC_APB2PeriphClockCmd
   
#define  DEBUG_USART2_TX_GPIO_PORT        GPIOA   
#define  DEBUG_USART2_TX_GPIO_PIN         GPIO_Pin_2
#define  DEBUG_USART2_RX_GPIO_PORT       	GPIOA
#define  DEBUG_USART2_RX_GPIO_PIN        	GPIO_Pin_3

#define  DEBUG_USART2_IRQ                	USART2_IRQn
#define  DEBUG_USART2_IRQHandler         	USART2_IRQHandler

void NVIC_Configuration(void);
void USART_Config(void);
//void USART1_Config(void);
void USART2_Config(void);
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch);
void Usart_SendString( USART_TypeDef * pUSARTx, char *str);


#endif


并在stm32f10x_it.c里编写串口中断服务函数

#include "stm32f10x_it.h"
#include "usart.h"
#include "delay.h"

// 串口中断服务函数
void DEBUG_USART_IRQHandler(void)
{
  uint8_t ucTemp;
	if(USART_GetITStatus(DEBUG_USART1,USART_IT_RXNE)!=RESET)
	{		
		ucTemp = USART_ReceiveData(DEBUG_USART1);
    USART_SendData(DEBUG_USART1,ucTemp);
	}	 
}

最后就是主函数调用

int main(void)
{
	RCC_Configuration();   				
	Delay_Timer_Init();
	
	USART_Config();
	while(1)
	{
		Delay_ms(1000);
		printf("HelloWorld!\n");
	}
}

烧录验证效果
在这里插入图片描述

未完待续…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/652611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spark入门(二)

2.3 Standalone模式 Standalone模式是Spark自带的资源调度引擎&#xff0c;构建一个由Master Worker构成的Spark集群&#xff0c;Spark运行在集群中。 这个Standalone区别于Hadoop的。这里的Standalone是指只用Spark来搭建一个集群&#xff0c;不需要借助其他框架。 2.3.1集…

充能书单|618,买什么都不如买知识!

前言 “IT有得聊”是机械工业出版社旗下IT专业资讯和服务平台&#xff0c;致力于帮助读者在广义的IT领域里&#xff0c;掌握更专业、更实用的知识与技能&#xff0c;快速提升职场竞争力。 点击蓝色微信名可快速关注我们。 一年一度的618又到啦&#xff01;今年的618就不要乱买…

【Linux】MySQL数据库 (二)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 MySQL数据库 数据表高级操作克隆表&#xff0c;将数据表的数据记录生成到新的表中清空表&#xff0c;删除表内的所有数据创建临时表创建外键约束&#xff0c;保证数据的完整性…

【计算机网络】第一章 概述(下)

文章目录 第一章 概述1.5 计算机网络的性能指标1.5.1 速率1.5.2 带宽1.5.3 吞吐量1.5.4 时延 1.6 计算机网络体系结构1.6.1 常见的体系结构1.6.2 分层的必要性1.6.4 体系结构中的专用术语 1.8 习题 第一章 概述 1.5 计算机网络的性能指标 常用的 计算机网络 的性能指标有以下 …

ECC算法学习(一)算法公式

ECC 一、ECC简介优缺点运用 二、算法理论基础1. 椭圆曲线的加法2. 椭圆曲线的二倍运算3. 同余运算4. 有限域5. 乘法逆元 三、算法公式1、有限域的负元2、有限域的加法&#xff0c; P Q P Q PQ3. 斜率计算&#xff08;PQ即要计算P点切线&#xff0c;需要求导&#xff09;4. 椭…

chatgpt赋能python:PYTHON如何进行累乘操作?

PYTHON如何进行累乘操作&#xff1f; 在PYTHON编程中&#xff0c;累乘操作是指不断地将一个给定数字序列中的数字相乘的过程。这个操作在数学中也被称为阶乘&#xff0c;通常用符号“!”来表示。 在PYTHON中&#xff0c;进行累乘操作的方法主要有两种&#xff1a;使用循环实现…

Java实现TestNg+ExtentReport实现接口测试,并生成测试报告

一 在pom.xml文件中引入TestNg以及ExtentReport包 <dependencies> <!--testNg引入--> <dependency> <groupId>org.testng</groupId> <artifactId>testng</artifactId> <version>6.9.10</version> </de…

学习css样式的第二章

1.CSS 布局 - display 属性 display 属性是用于控制布局的最重要的 CSS 属性。 display 属性 display 属性规定是否/如何显示元素。 每个 HTML 元素都有一个默认的 display 值&#xff0c;具体取决于它的元素类型。大多数元素的默认 display 值为 block 或 inline 块级元素…

chatgpt赋能python:Python编程:如何粘贴代码

Python编程&#xff1a;如何粘贴代码 在Python编程过程中&#xff0c;粘贴代码是一个非常普遍的操作。不幸的是&#xff0c;许多初学者并不知道如何正确地粘贴代码&#xff0c;这可能会导致一些常见的错误和问题。本文将介绍如何正确地粘贴代码以及一些常见的问题和解决方案。…

Wise 的平台工程 KPI 探索之旅

作者&#xff5c;Lambros Charissis 翻译&#xff5c;Seal软件 链接&#xff5c;https://medium.com/wise-engineering/platform-engineering-kpis-6a3215f0ee14 平台即产品&#xff08;PaaP&#xff09;已经成为软件企业构建内部平台的一种流行方式。在众多软件公司争夺市场份…

地球物理专业毕业生毕业后能干高性能计算工程师吗?

很多高校都开设有地球物理专业&#xff0c;但是很多身为地球物理专业的毕业生&#xff0c;很多同学却不清楚以后能做什么工作&#xff0c;做什么工作有前景&#xff0c;十分迷茫。在这里&#xff0c;我们有很多从事高性能计算领域的前地球物理专业学长现身说法——地球物理专业…

Qt弱加密漏洞分析

0x00 漏洞背景 Qt是一个跨平台的C应用程序开发框架&#xff0c;用于创建图形用户界面&#xff08;GUI&#xff09;应用程序、命令行工具、嵌入式系统和网络应用等各种类型的应用。 Qt框架包含的Qt Network&#xff08;网络模块&#xff09;&#xff0c;提供了QNetworkAccessM…

Vue中如何进行游戏开发与游戏引擎集成?

Vue中如何进行游戏开发与游戏引擎集成&#xff1f; Vue.js是一款流行的JavaScript框架&#xff0c;它的MVVM模式和组件化开发思想非常适合构建Web应用程序。但是&#xff0c;如果我们想要开发Web游戏&#xff0c;Vue.js并不是最合适的选择。在本文中&#xff0c;我们将介绍如何…

mycat分库分表中间件介绍,有案例

目录 MyCat分库分表概述水平拆分和垂直拆分安装JDK安装MyCat安装 MyCat案例1、创建数据库2、分片配置&#xff08;schema.xml&#xff09;3、分片配置&#xff08;server.xml&#xff09;4、启动服务5、查看日志&#xff0c;看是否启动成功6、登录MyCat7、查看数据库和表8、创建…

SpringBoot自动配置原理简单分析

说明&#xff1a;在SpringBoot项目中&#xff0c;我们添加了许许多多的注解&#xff0c;这些注解提高了开发效率。这是因为SpringBoot在项目启动时&#xff0c;帮我们自动装配了大量的Bean对象&#xff0c;可以通过分析源码查看自动装配的大致原理。 第一步&#xff1a;Spring…

python:并发编程(十)

前言 本文将和大家一起探讨python的多协程并发编程&#xff08;上篇&#xff09;&#xff0c;使用内置基本库asyncio来实现并发&#xff0c;先通过官方来简单使用这个模块。先打好基础&#xff0c;能够有个基本的用法与认知&#xff0c;后续文章&#xff0c;我们再进行详细使用…

【程序人生-Hello‘s P2P】哈尔滨工业大学深入理解计算机系统大作业

计算机系统 大作业 题 目 程序人生-Hello’s P2P 专 业 xxxx 学 号 2021xxxx 班 级 210xxxx 学 生 xx 指 导 教 师 xxx 计算机科学与技术学院 2023年5月 摘 要 HelloWorld是每个程序员接触的第一个程序&#xff0c;表面上平平无奇的它背后却是由操作系统许多设计精巧的机制支撑…

直击面试!阿里技术官手码12W字面试小册在Github上爆火

Java面试 临近金三银四&#xff0c;想必有不少老哥已经在为面试做准备了。大家想必也知道现在面试就是看项目经验基本技术个人潜力(也就是值不值得培养)。总之就是每一次面试都是对我们能力的检验&#xff08;无论是软实力还是硬实力&#xff09;。软实力其实就是简历包装&…

高性能计算专业发展及就业前景

高性能计算专业是指一种针对计算领域最高端、最具挑战性且最具应用价值的领域专业。本文从专业背景、就业前景、发展趋势、技能需求和职业发展路径等5个角度进行较为详细的论证&#xff0c;旨在为高性能计算专业的学生提供一个全面的了解&#xff0c;以及为需要参考的人员提供参…

Java中常用的工具类

有时间就该多学&#xff0c;我还年轻&#xff0c;吃苦趁现在&#xff01; 文章目录 ❗前言1️⃣Object类①、使用步骤equals方法hashCode方法toString方法❗equals方法和运算符的区别 ②、重要方法总结 2️⃣包装类基本类型和包装类相互转换字符串类型转换成基本类型 3️⃣Date…