目录
一些C知识点
枚举enum
enum枚举用于创建符号常量,通常用来替代const创建符号常量的方式。对枚举类型的变量赋值时,规定只能用枚举量。对于这个结构体变量来说,赋值只能用枚举的三个类型,只不过,也可以通过强制转换实现赋值。
typedef enum //速度枚举定义
{
GPIO_Speed_10MHZ = 1,
GPIO_Speed_2MHZ,
GPIO_Speed_50MHZ
}GPIOSpeed_TypeDef;
一、GPIO初始化函数
1、GPIO初始化结构体及成员定义
在配置端口时,需要对此端口的引脚、输入输出模式、速率进行初始化,前面的方法都是查询寄存器表,根据需求去对寄存器进行位操作以进行初始化。那么,可以像上一篇端口置位函数那样,编写GPIO初始化函数。
GPIO初始化包含引脚选择、输入输出模式配置以及速度配置三部分,这三者作为结构体成员,建立GPIO_InitTypeDef结构体变量。
typedef struct //GPIO初始化结构体
{
uint16_t GPIO_Pin;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;
其中,引脚为短整型16位,速度与模式因为都是已知类型,可以使用枚举类型定义。
#define GPIO_Pin_0 ((uint16_t)0x0001) /*!< 选择Pin0 */ //(00000000 00000001)b
#define GPIO_Pin_1 ((uint16_t)0x0002) /*!< 选择Pin1 */ //(00000000 00000010)b
#define GPIO_Pin_2 ((uint16_t)0x0004) /*!< 选择Pin2 */ //(00000000 00000100)b
#define GPIO_Pin_3 ((uint16_t)0x0008) /*!< 选择Pin3 */ //(00000000 00001000)b
#define GPIO_Pin_4 ((uint16_t)0x0010) /*!< 选择Pin4 */ //(00000000 00010000)b
#define GPIO_Pin_5 ((uint16_t)0x0020) /*!< 选择Pin5 */ //(00000000 00100000)b
#define GPIO_Pin_6 ((uint16_t)0x0040) /*!< 选择Pin6 */ //(00000000 01000000)b
#define GPIO_Pin_7 ((uint16_t)0x0080) /*!< 选择Pin7 */ //(00000000 10000000)b
#define GPIO_Pin_8 ((uint16_t)0x0100) /*!< 选择Pin8 */ //(00000001 00000000)b
#define GPIO_Pin_9 ((uint16_t)0x0200) /*!< 选择Pin9 */ //(00000010 00000000)b
#define GPIO_Pin_10 ((uint16_t)0x0400) /*!< 选择Pin10 */ //(00000100 00000000)b
#define GPIO_Pin_11 ((uint16_t)0x0800) /*!< 选择Pin11 */ //(00001000 00000000)b
#define GPIO_Pin_12 ((uint16_t)0x1000) /*!< 选择Pin12 */ //(00010000 00000000)b
#define GPIO_Pin_13 ((uint16_t)0x2000) /*!< 选择Pin13 */ //(00100000 00000000)b
#define GPIO_Pin_14 ((uint16_t)0x4000) /*!< 选择Pin14 */ //(01000000 00000000)b
#define GPIO_Pin_15 ((uint16_t)0x8000) /*!< 选择Pin15 */ //(10000000 00000000)b
#define GPIO_Pin_All ((uint16_t)0xFFFF) /*!< 选择全部引脚*/ //(11111111 11111111)b
typedef enum //速度枚举定义
{
GPIO_Speed_10MHZ = 1,
GPIO_Speed_2MHZ,
GPIO_Speed_50MHZ
}GPIOSpeed_TypeDef;
typedef enum //输入输出模式枚举定义
{
GPIO_Mode_AIN = 0x0, // 模拟输入 (0000 0000)b
GPIO_Mode_IN_FLOATING = 0x04, // 浮空输入 (0000 0100)b
GPIO_Mode_IPD = 0x28, // 下拉输入 (0010 1000)b
GPIO_Mode_IPU = 0x48, // 上拉输入 (0100 1000)b
GPIO_Mode_Out_OD = 0x14, // 开漏输出 (0001 0100)b
GPIO_Mode_Out_PP = 0x10, // 推挽输出 (0001 0000)b
GPIO_Mode_AF_OD = 0x1C, // 复用开漏输出 (0001 1100)b
GPIO_Mode_AF_PP = 0x18 // 复用推挽输出 (0001 1000)b
}GPIOMode_TypeDef;
2、GPIO初始化函数
上图为枚举变量GPIOMode_TypeDef对应的输入输出模式,主要根据bit2到6来进行判断,以对CRL、CRH、BSR、BSRR寄存器赋值,从而实现配置。
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
{
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;
uint32_t tmpreg = 0x00, pinmask = 0x00;
/*---------------------- GPIO 模式配置 --------------------------*/
// 把输入参数GPIO_Mode的低四位暂存在currentmode
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);
// bit4是1表示输出,bit4是0则是输入
// 判断bit4是1还是0,即首选判断是输入还是输出模式
if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00)
{
// 输出模式则要设置输出速度
currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;
}
/*-------------GPIO CRL 寄存器配置 CRL寄存器控制着低8位IO- -------*/
// 配置端口低8位,即Pin0~Pin7
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00)
{
// 先备份CRL寄存器的值
tmpreg = GPIOx->CRL;
// 循环,从Pin0开始配对,找出具体的Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
// pos的值为1左移pinpos位
pos = ((uint32_t)0x01) << pinpos;
// 令pos与输入参数GPIO_PIN作位与运算,为下面的判断作准备
currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;
//若currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
// pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << pinpos);
}
else
{
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
}
}
}
}
// 把前面处理后的暂存值写入到CRL寄存器之中
GPIOx->CRL = tmpreg;
}
/*-------------GPIO CRH 寄存器配置 CRH寄存器控制着高8位IO- -----------*/
// 配置端口高8位,即Pin8~Pin15
if (GPIO_InitStruct->GPIO_Pin > 0x00FF)
{
// // 先备份CRH寄存器的值
tmpreg = GPIOx->CRH;
// 循环,从Pin8开始配对,找出具体的Pin
for (pinpos = 0x00; pinpos < 0x08; pinpos++)
{
pos = (((uint32_t)0x01) << (pinpos + 0x08));
// pos与输入参数GPIO_PIN作位与运算
currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos);
//若currentpin=pos,则找到使用的引脚
if (currentpin == pos)
{
//pinpos的值左移两位(乘以4),因为寄存器中4个寄存器位配置一个引脚
pos = pinpos << 2;
//把控制这个引脚的4个寄存器位清零,其它寄存器位不变
pinmask = ((uint32_t)0x0F) << pos;
tmpreg &= ~pinmask;
// 向寄存器写入将要配置的引脚的模式
tmpreg |= (currentmode << pos);
// 判断是否为下拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)
{
// 下拉输入模式,引脚默认置0,对BRR寄存器写1可对引脚置0
GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
// 判断是否为上拉输入模式
if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)
{
// 上拉输入模式,引脚默认值为1,对BSRR寄存器写1可对引脚置1
GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08));
}
}
}
// 把前面处理后的暂存值写入到CRH寄存器之中
GPIOx->CRH = tmpreg;
}
}
二、程序可移植性
总结下来:宏定义+函数;
最开始,查寄存器表,对地址进行强制转换指针,再位操作去赋值此指针指向的值;
后来,使用宏定义,把指向寄存器地址的指针,用寄存器名称宏定义;
结合32位寄存器特点,对GPIO寄存器用结构体定义,各寄存器作为32位无符号整型成员,通过结构体变量下的各成员去赋值;
对于位操作,使用置位、复位函数,提升程序可读性。
对于可移植性,将变量在主函数开头宏定义,修改定义过的目标端口即可。
*( unsigned int * )0x40010C0C &= ~(1<<0);
GPIOB_ODR &= ~(1<<0);
GPIOB->ODR &= ~(1<<0);
GPIO_ResetBits( GPIOB,GPIO_Pin_0 );
三、固件库文件分析
1、汇编启动文件
startup_stm32f10x_hd.s:设置堆栈指针、设置PC指针、初始化中断向量表、配置时钟系统、调用C库函数__main从汇编环境跳到C环境。
2、时钟配置文件
system_stm32f10x.c:把外部时钟HSE=8MHz,经过PLL倍频到72MHz。
3、外设相关
stm32f10x.h:实现寄存器映射,寄存器结构体定义。
stm32f10x_xxx.c:外设的驱动函数的库文件。
stm32f10x_xxx.h:外设的初始化结构体定义;外设初始化结构体成员的参数列表,即枚举;函数声明。(xxx可为GPIO、USART、I2C、SPI、FSMC等外设)
4、内核相关:
CMSIS - Cortex微控制器软件接口标准
core_cm3.h:实现内核里外设的寄存器映射;
core_cm3.c:实现内核里外设的寄存器映射;
NVIC - 嵌套向量中断控制器;SysTick - 系统滴答定时器
misc.h和misc.c
5、头文件的配置文件:
stm32f10x_conf.h:头文件的头文件,包含GPIO、USART、I2C、SPI、FSMC的头文件,这样在主程序中仅包含此头文件即可。
6、专门存放中断服务函数的文件
stm32f10x_it.c、stm32f10x_it.h(中断函数可以随意放在其他地方,不一定要放在此文件)
参考
[野火®]STM32库开发实战指南——基于野火指南者开发板 — [野火]STM32库开发实战指南——基于野火指南者开发板 文档