重新审视STM32(1)——GPIO
第一次学习stm32时什么都不会,51也不懂,C语言模模糊糊不成体统连什么是硬件什么单片机的问题都回答不上来,“51已为神,32掌控万物”是对我那是鄙陋的想法,
后赶鸭子上架,匆匆忙忙,有幸学习了正点原子的教程,但仅限于应用,恍恍惚惚缺乏细节的理解。再往后慢慢用起来FPGA,了解时序,也用上其他的硬件。。。。。对许多问题有了自己的思考和想法,不再像以前一样用黑科技的眼光看待库函数,看待那一串串的0011
由于是纯野生的小白,很难有科班的理解,仍有欠妥当之处,望海涵。
如果真要说起stm32从gpio并不合适,绕开M3内核和指令集讲述文题终究浮于表面,在此一说希望看客有兴趣的话去看一看M3的权威指南,本人汇编水平和对ARM架构的理解不足深入探讨这些问题。故从应用下手——第一篇GPIO
硬件设备:正点原子战舰V3 STM32F103zet6
GPIO
(讲解顺序
1 库函数
2寄存器
3位带操作
)
1,库函数
GPIO_Init()
万物离不开init
来看
1 void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct) 2 /*其中第一个参数为那组引脚,每组拥有16个引脚,每组都具有不同的寄存器配置地址,第二个参数是一个数据结构,也就是将基本配置信息放在这个数据结构里面,再将这个结构传入函数进行配置*/ 3 //其中数据机构可以表示为如下 4 typedef struct 5 { 6 uint16_t GPIO_Pin; //引脚号 7 GPIOSpeed_TypeDef GPIO_Speed; //配置速度 8 GPIOMode_TypeDef GPIO_Mode; //工作模式 9 }GPIO_InitTypeDef; 10 11 //其中配置模式和工作模式为GPIOSpeed_TypeDef和GPIOMode_TypeDef的枚举变量 12 //其次是模式定义 13 typedef enum 14 { GPIO_Mode_AIN = 0x0, //模拟输入 15 GPIO_Mode_IN_FLOATING = 0x04, //浮空输入模式, 默认 16 GPIO_Mode_IPD = 0x28, //上拉/下拉输入模式 17 GPIO_Mode_IPU = 0x48, //保留 18 GPIO_Mode_Out_OD = 0x14, //通用开漏输出 19 GPIO_Mode_Out_PP = 0x10, //通用推挽输出 20 GPIO_Mode_AF_OD = 0x1C, //复用(开漏)输出 21 GPIO_Mode_AF_PP = 0x18 //复用(推挽)输出 22 }GPIOMode_TypeDef; 23 //最后是速度定义
24 typedef enum 25 { 26 GPIO_Speed_10MHz = 1, 27 GPIO_Speed_2MHz, 28 GPIO_Speed_50MHz 29 }GPIOSpeed_TypeDef;
三大定义支撑起stm32IO口的半壁江山啊,可这如何实现的呢?
我们先看参数信息,第一个
GPIO_TypeDef* GPIOx
typedef struct { __IO uint32_t CRL; __IO uint32_t CRH; __IO uint32_t IDR; __IO uint32_t ODR; __IO uint32_t BSRR; __IO uint32_t BRR; __IO uint32_t LCKR; } GPIO_TypeDef;
显而易见,可以理解为这是在往函数里加载寄存器
下一个
GPIO_InitStruct
typedef struct { uint16_t GPIO_Pin; /*!< Specifies the GPIO pins to be configured. This parameter can be any value of @ref GPIO_pins_define */ GPIOSpeed_TypeDef GPIO_Speed; /*!< Specifies the speed for the selected pins. This parameter can be a value of @ref GPIOSpeed_TypeDef */ GPIOMode_TypeDef GPIO_Mode; /*!< Specifies the operating mode for the selected pins. This parameter can be a value of @ref GPIOMode_TypeDef */ }GPIO_InitTypeDef;
三大定义 引脚 速度 工作方式,三个定义引出三段宏定义
IS_GPIO_ALL_PERIPH(GPIOx);
#define IS_GPIO_ALL_PERIPH(PERIPH) (((PERIPH) == GPIOA) || \
((PERIPH) == GPIOB) || \
((PERIPH) == GPIOC) || \
((PERIPH) == GPIOD) || \
((PERIPH) == GPIOE) || \
((PERIPH) == GPIOF) || \
((PERIPH) == GPIOG))
这个宏定义的作用就是检查参数PERIPH(外围),判断参数PERIPH是否为GPIOX(A...G)基址中的一个,只要有一个为真则其值为真,否则为假
IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode);
#define IS_GPIO_MODE(MODE) (((MODE) == GPIO_Mode_AIN) || ((MODE) == GPIO_Mode_IN_FLOATING) || \
((MODE) == GPIO_Mode_IPD) || ((MODE) == GPIO_Mode_IPU) || \
((MODE) == GPIO_Mode_Out_OD) || ((MODE) == GPIO_Mode_Out_PP) || \
((MODE) == GPIO_Mode_AF_OD) || ((MODE) == GPIO_Mode_AF_PP))
与第一个同理八种工作模式只要有一个为真则为真
IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin);
#define IS_GPIO_PIN(PIN) ((((PIN) & (uint16_t)0x00) == 0x00) && ((PIN) != (uint16_t)0x00))
(PIN) & (uint16_t)0x00) == 0x00 如果PIN与0x00为0,则成立;这个等式恒成立,不管PIN为何值其结果都为0x00。
(PIN) !=(uint16_t)0x00) 如果当PIN=0x00就不成立,否则都成立;
&& 表示两者都成立为真 否则为假;
总而言之,第一句恒成立第二句不为0时成立,想要该式成立的条件就是PIN的值不能为0x00;
通过寄存器我们发现每4位控制 io速度和工作模式(以下需要)
assert_param()这个宏定义在上面(我省了)在下面都需要,我们看看它是什么
#define assert_param(expr) ((void)0)
仅仅定义了一个null????其实不然,这是一种对于应用这并没有什么用的断言机制,我列出它的完整代码
#ifdef USE_FULL_ASSERT #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__)) /* Exported functions ------------------------------------------------------- */ void assert_failed(uint8_t* file, uint32_t line); #else #define assert_param(expr) ((void)0)
这是和有意思的调试程序,用于设计者debug的东西。如果宏为1那么该函数如果返回1则该函数无效,程序继续执行,如果返回为0,则这里执行出了问题返回所在位置(看见那个三目运算符就明白了)
那么这就意味着带 assert_param()的函数无效执行。
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; //currentmode 用于存放临时的LCIR //currentpin 用于存放配置的引脚位 //pinpos 用于存放当前操作的引脚号 //pos 存放当前操作的引脚位 //tmreg 当前的CIR //pinmask //判断参数 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)); /*---------------------------- 模式配置 -----------------------*/ //取出配置信息里面的模式信息并且取它的低4位 currentmode = ((uint32_t)GPIO_InitStruct->GPIO_Mode) & ((uint32_t)0x0F); if ((((uint32_t)GPIO_OInitStruct->GPIO_Mode) & ((uint32_t)0x10)) != 0x00) //输出模式 { //判断参数 assert_param(IS_GPIO_SPEED(GPIO_InitStruct->GPIO_Speed)); //将速度信息放入currentmode低二位 currentmode |= (uint32_t)GPIO_InitStruct->GPIO_Speed; } if (((uint32_t)GPIO_InitStruct->GPIO_Pin & ((uint32_t)0x00FF)) != 0x00) //引脚有定义 { //当前的CRL保存 tmpreg = GPIOx->CRL; //循环低八位引脚 for (pinpos = 0x00; pinpos < 0x08; pinpos++) { //当前是那个引脚,那个位置1 pos = ((uint32_t)0x01) << pinpos; //读取引脚信息里面的当前引脚 currentpin = (GPIO_InitStruct->GPIO_Pin) & pos; if (currentpin == pos) //如果当前引脚在配置信息里存在 { pos = pinpos << 2; //pos=引脚号x2 pinmask = ((uint32_t)0x0F) << pos; //1111<<引脚号x2,根据 CRL 的结构很容易理解 tmpreg &= ~pinmask; //当前应该操作的CRL位清0 tmpreg |= (currentmode << pos); //设置当前操作的 CRL 位 if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD) // 端口置为高电平 { GPIOx->BRR = (((uint32_t)0x01) << pinpos); } else { if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU) // 端口清0 { GPIOx->BSRR = (((uint32_t)0x01) << pinpos); } } } } GPIOx->CRL = tmpreg; }
若是高八位,则处理方式相同,注意BSRR和BRR加八位
GPIO_DeInit()的处理方式也是非常的简单粗暴 寻找GPIOX 调用函数 ,而函数本质就是时钟复位
每组IO口含下面7个寄存器。也就是7个寄存器,
一共可以控制一组GPIO的16个IO口
两个32位配置寄存器(GPIOx_CRL,GPIOx_CRH),
两个32位数据寄存器(GPIOx_IDR和GPIOx_ODR),
一个32位置位/复位寄存器(GPIOx_BSRR),
一个16位复位寄存器(GPIOx_BRR)
一个32位锁定寄存器(GPIOx_LCKR)。
模式有
─ 输入浮空 ─ 输入上拉 ─ 输入下拉 ─ 模拟输入 ─ 开漏输出 ─ 推挽式输出 ─ 推挽式复用功能
我们所有和io口有关的寄存器全部挂载在APB2总线
位带操作
emm,。。。。本篇未完待续
腾出时间尽量做到周更
打算做一篇stm32(0)讲一下32内部的事