STM32官方固件库代码解读--GPIO
来源:互联网 发布:windows未能启动怎么办 编辑:程序博客网 时间:2024/06/06 10:58
最近闲得无聊,又把 stm32 拿了出来。之前学的时候是看的库函数版本,现在和寄存器版本的一起看感觉比一开始接触的时候看得顺畅多了,详细了解了底层寄存器的功能。之前用 stm32 只是调用函数,看了寄存器版本后对那些实现具体细节的库函数代码产生了兴趣,所以稍微看了看库函数的代码,同时把一些难理解的地方也记录下来。
我用的是 ALIENTEK 的战舰版,看的也是他们的开发指南,这里我就写对库函数代码的理解,寄存器种类和功能等指南里面有的我基本不写了。
一开始是 GPIO,学任何单片机一开始都是流水灯。stm32 的每组 GPIO 有 7 个寄存器,功能这里就不介绍了,网上很多。官方库里关于 GPIO 有 18 个函数,看了几个常用的,如下
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
这些函数除了第一个初始化函数以外其他都很简单,了解一下寄存器的功能就能看明白代码了,这里只写一下初始化函数。
代码如下:
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; /* Check the parameters */ assert_param(IS_GPIO_ALL_PERIPH(GPIOx)); assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode)); assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin)); /*---------------------------- GPIO Mode Configuration -----------------------*/ currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F); if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00) { /* Check the parameters */ assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed)); /* Output mode */ currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed; }/*---------------------------- GPIO CRL Configuration ------------------------*/ /* Configure the eight low port pins */ if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00) { tmpreg = GPIOx->CRL; for (pinpos = 0x00; pinpos < 0x08; pinpos++) { pos = ((uint32_t)0x01) << pinpos; /* Get the port pins position */ currentpin = (GPIO_InitStruct->GPIO_Pin) & pos; if (currentpin == pos) { pos = pinpos << 2; /* Clear the corresponding low control register bits */ pinmask = ((uint32_t)0x0F) << pos; tmpreg &= ~pinmask; /* Write the mode configuration in the corresponding bits */ tmpreg |= (currentmode << pos); /* Reset the corresponding ODR bit */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) { GPIOx->BRR = (((uint32_t)0x01) << pinpos); } else { /* Set the corresponding ODR bit */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) { GPIOx->BSRR = (((uint32_t)0x01) << pinpos); } } } } GPIOx->CRL = tmpreg; }/*---------------------------- GPIO CRH Configuration ------------------------*/ /* Configure the eight high port pins */ if (GPIO_InitStruct->GPIO_Pin > 0x00FF) { tmpreg = GPIOx->CRH; for (pinpos = 0x00; pinpos < 0x08; pinpos++) { pos = (((uint32_t)0x01) << (pinpos + 0x08)); /* Get the port pins position */ currentpin = ((GPIO_InitStruct->GPIO_Pin) & pos); if (currentpin == pos) { pos = pinpos << 2; /* Clear the corresponding high control register bits */ pinmask = ((uint32_t)0x0F) << pos; tmpreg &= ~pinmask; /* Write the mode configuration in the corresponding bits */ tmpreg |= (currentmode << pos); /* Reset the corresponding ODR bit */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) { GPIOx->BRR = (((uint32_t)0x01) << (pinpos + 0x08)); } /* Set the corresponding ODR bit */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) { GPIOx->BSRR = (((uint32_t)0x01) << (pinpos + 0x08)); } } } GPIOx->CRH = tmpreg; }}
每个 GPIO 端口有四个配置为,分别是 CNF1,CNF0,MODE1,MODE0,初始化主要就是配置这四位,如下表
可以看到输入模式中上拉输入与下拉输入 CNF1 和 CNF0 是一样的,那这两种模式在配置时是怎么区分的呢?其实这是通过配置输出数据寄存器 ODR 来区分的,对应位置 1 为上拉,对应位置 0 为下拉。那这在库函数里又是怎么实现的呢?
先介绍固件库 GPIO 头文件里的两个枚举类型,如下
typedef enum{ GPIO_Speed_10MHz = 1, GPIO_Speed_2MHz, GPIO_Speed_50MHz}GPIOSpeed_TypeDef;typedef enum{ GPIO_Mode_AIN = 0x0, GPIO_Mode_IN_FLOATING = 0x04, GPIO_Mode_IPD = 0x28, GPIO_Mode_IPU = 0x48, GPIO_Mode_Out_OD = 0x14, GPIO_Mode_Out_PP = 0x10, GPIO_Mode_AF_OD = 0x1C, GPIO_Mode_AF_PP = 0x18}GPIOMode_TypeDef;
这两个是初始化函数的第二个结构体参数里的两个参数,用于速率和输入输出模式的初始化。第一个速率还好理解,01、10、11 分别对应上表中代表不同的输出速率。第二个就不太看得懂了,模式配置明明只要 4 位数据,怎么有 8 位,这个 8 位数据又到底是什么涵义?我也在网上看到有人问这个问题,他说对着寄存器的配置怎么也看不懂,其实对着寄存器配置是肯定看不懂的,因为这个要对着库函数的具体实现方法来看。
初始化函数里先定义了几个参数,如下
uint32_t currentmode = 0x00, currentpin = 0x00, pinpos = 0x00, pos = 0x00;uint32_t tmpreg = 0x00, pinmask = 0x00;
看变量名就知道大概是用来干嘛的,就不专门解释了,讲具体用法再说。
下面一段用来模式配置
currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F);if ((((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00){ /* Check the parameters */ assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed)); /* Output mode */ currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed;}
这里用到了之前看不懂的那个枚举量,现在就可以看它的值到底是什么意思了。先把这个 8 位数保留低 4 位,高四位置 0 ,保存到 currentmode 这个变量,然后判断这个 8 位数的第 4 位是不是 1,如果是 1,把 currentmode 和速率按位或之后保存到 currentmode 这个变量中。乍一看这段代码根本不知道是什么意思,只知道现在 currentmode 大概保存的是要配置的模式参数。其实这要和之前那个枚举量结合起来看。
既然检测了第四位是否为 1,那看之前的枚举类型中的数值,四种输出模式对应的值为 0x14、0x10、0x1C、0x18,四种输入模式对应的值为 0x0、0x04、0x28、0x48。可以发现输出模式的第四位都为 1,输入模式的第四位都不为 1,所以只有输出模式会进入 if 语句,因为只有输出模式要配置速率,所以再将 currentmode 和速率按位或就得到了输出模式的配置参数。以 0x14 为例,高四位 0001 是用来检测是否为输出模式,第 2 位和第 3 位是输出模式参数,第 0 位和第一位为 0,在 if 语句里和速率参数拼成一个完整的参数。至于输入模式的参数,它不用进入 if 语句,它的低四位就是它的模式参数,同时下拉和上拉输入枚举值高四位的不同也起到区分的作用,因为之前说过它们的配置参数是一样的。
下面是 CRL 寄存器的配置,对应 GPIO_x的 0~7,每个口四位参数。代码如下:
if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00) { tmpreg = GPIOx->CRL; for (pinpos = 0x00; pinpos < 0x08; pinpos++) { pos = ((uint32_t)0x01) << pinpos; /* Get the port pins position */ currentpin = (GPIO_InitStruct->GPIO_Pin) & pos; if (currentpin == pos) { pos = pinpos << 2; /* Clear the corresponding low control register bits */ pinmask = ((uint32_t)0x0F) << pos; tmpreg &= ~pinmask; /* Write the mode configuration in the corresponding bits */ tmpreg |= (currentmode << pos); /* Reset the corresponding ODR bit */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) { GPIOx->BRR = (((uint32_t)0x01) << pinpos); } else { /* Set the corresponding ODR bit */ if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) { GPIOx->BSRR = (((uint32_t)0x01) << pinpos); } } } } GPIOx->CRL = tmpreg; }
先判断是不是低 8 位的端口,若是就进入继续判断。pinpos 控制判断次数,因为有 8 个口,所以要循环 8 次。pos 的值表示当前判断的是哪一个端口,只有 1 位为 1。接着 pos 和输入参数 GPIO_Pin 按位与赋值给 currentpin,如果 currentpin 的值和 pos 相等,说明要配置当前这个端口,进入 if 语句进行配置。下面这句pos = pinpos << 2,就是把 pinpos 乘上 4 赋给 pos,pos 之后控制移位次数,因为一个口的配置参数有 4 位,所以计数参数要乘 4。下面两句pinmask =((uint32_t)0x0F) << pos;tmpreg &= ~pinmask,是把要配置的这个口对应的四位清零。然后一句tmpreg |= (currentmode << pos),大功告成,要配置的那个端口对应的四位参数设置好了。下面还有两个判断语句是用来判断是否为上拉或下拉输入模式,若是则设置相应的 ODR 位。
下面还有一段是设置 CRH 寄存器的,和设置 CRL 差不多,只是它是端口 8~15。
OK,对 GPIO 的库函数先写这么多吧。
- STM32官方固件库代码解读--GPIO
- 【STM32】获取STM32官方固件库
- STM32库函数之GPIO初始化代码分析
- 对STM32官方库封装一:GPIO库
- STM32官方固件库简介
- STM32 GPIO
- STM32 GPIO
- STM32 GPIO
- STM32:GPIO
- stm32---gpio
- stm32--GPIO
- STM32--GPIO
- 解读STM32单片机:代码实现 PCROP清除
- EmBitz STM32 HAL 固件库之 GPIO
- Google官方MVP Sample代码解读
- STM32之GPIO笔记
- STM32之GPIO
- stm32 gpio 笔记!
- [知了堂学习笔记] 二叉树建立及其遍历的思路和实现
- 构建二叉树及遍历
- 上机一 D 水水的Horner Rule
- 从“信息增益”到KL Divergence 和 Cross Entropy
- Android沉浸式(透明)状态栏适配
- STM32官方固件库代码解读--GPIO
- Object Detection -- 论文FPN(Feature Pyramid Networks for Object Detection)解读
- SSH整合
- 实现订单支付方法
- Java4Android笔记之Java中的面向对象基础(二)
- 错误集锦
- linux 系统中的cache 和 buffer 的区别
- 【入门计数类问题/数位DP模板】来自于唐老师rgnoH
- linux 审计--audit