初探STM32F4(1)--GPIO(1)


本文从两个方面剖析GPIO:

  • 结合《STM32F4xx中文参考手册》,从硬件和寄存器的角度剖析GPIO
  • 结合库例程,从软件角度剖析GPIO如何配置,同时剖析嵌入式代码规范

阅读完本文,要能回答以下问题:

  1. 简述GPIO口的硬件架构与配置寄存器
  2. 配置GPIO口寄存器的代码流程
  3. 然后就是结合配置GPIO的代码回答一些嵌入式代码规范的问题:
    1. 寄存器前面的类型修饰符的含义,例如__IO uint32_t ODR
    2. 为啥要用volatile类型修饰符
    3. 使用unsigned int与int声明后续配置有什么区别?
    4. #define与typedef有什么区别?
    5. 为什么嵌入式代码中有这么多宏定义?
    6. GPIO定义的结构体中的这些配置寄存器变量是怎么映射到实际地址的?
    7. 嵌入式代码中常常需要进行位操作,结合配置GPIO的代码,谈谈如何进行位操作?
    8. 嵌入式代码中常常需要对多位进行位操作,结合配置GPIO的代码,谈谈如何进行位的遍历?
    9. M3M4内核通过位带映射支持直接对位进行操作,谈谈你对位带操作的认识?
    10. 嵌入式代码进入带参数的函数调用中去时,常常会先使用assert_param()函数,结合代码谈谈==assert_param()==的作用

文章最后,列出了一些值得进一步研究的内容,例如GPIO如何做输入使用,GPIO口中断的相关概念,详情阅读文章《初探STM32F4(4)–GPIO(2)》

GPIO概述

每组GPIO口包括4个32位的配置寄存器、2个32位的数据寄存器、1个32位的置位/复位寄存器、1个32位锁定寄存器和2个32位复用功能选择寄存器,一共10个寄存器。研究清楚这些寄存器有什么用、以及如何配置,也就搞懂了GPIO。

GPIO硬件架构与寄存器

GPIO硬件架构

在这里插入图片描述

GPIO工作模式

GPIO有八种工作模式,四种输入工作模式:
1.== 输入浮空模式==。上拉/下拉电阻不起作用,外部电平直接输入到芯片内部,适合电流要求小的场合。信号送至输入数据寄存器
2.== 输入上拉模式==。上拉电阻起作用。低电平输入时对芯片外部有灌电流。信号送至输入数据寄存器
3. 输入下拉模式。下拉电阻起作用。高电平输入时对芯片外部有拉电流。信号送至输入数据寄存器
4. 模拟模式。上拉/下拉电阻不起作用,信号沿模拟输入线输入到芯片内部ADC

四种输出工作模式:

  1. 开漏输出模式。CPU通过置位/复位寄存器或直接写操作,设置好输出数据寄存器。输出驱动器只有一个MOS管工作,当输出1时,MOS管截止,通过外接上拉电阻实现输出高电平。当输出0时,MOS管导通,输出低电平。
  2. 开漏复用输出模式。输出的数据来自CPU的片上复用外设。
  3. 推挽式输出。输出驱动器的NMOS与PMOS管均工作,当输出1时,PMOS导通,输出高电平,当输出0时,NMOS导通,输出低电平。
  4. 推挽式复用输出模式。输出的数据来自CPU的片上复用外设。

GPIO上电复位后,默认工作在输入浮空状态。

GPIO相关寄存器

注意,每组GPIO口通过10个相关寄存器管理好,一组包括16个GPIO口,那么,如果某个寄存器需要2位配置一个口,那刚好均分位,如果某个寄存器只需要1位配置一个口,那一般高16位保留。

4个配置寄存器

1、端口模式寄存器(GPIOx_MODER)

  • 每两个位控制一个IO口:00—输入浮空模式(复位状态);01—通用输出模式;10—复用功能模式;11—模拟模式。不用记忆、会查DATASHEET看懂就行

2、端口输出类型寄存器(GPIOx_OTYPER)

  • 配置成推挽或开漏输出,每1位就能控制一个IO口,所以高16位保留

3、端口输出速度寄存器(GPIOx_OSPEEDR)

  • 有四种输出速度,每两个位控制一个IO口

4、端口上拉下拉寄存器(GPIOx_PUPDR)

  • 有三种情况(无上拉下拉、上拉、下拉),每两个位控制一个IO口

2个数据寄存器

5、端口输入数据寄存器(GPIOx_IDR)

  • 每个GPIO的输入有两种情况,用一位来记录,所以高16位保留,这些位是只读模式的。

6、端口输出数据寄存器(GPIOx_ODR)

  • 每个GPIO的输出有两种情况,用一位来记录,所以高16位保留,这些位是可读可写的。

1个置位/复位寄存器

7、端口置位/复位寄存器(GPIOx_BSRR)

  • 高16位为复位位、低16位为置位位。这些位为只写,0无影响,1有作用。
  • ==已经有数据寄存器了,为啥还要这个寄存器?==如果只有数据寄存器,每次设置某一个IO口的取值时,需要先进行读操作,保证其他IO口不受影响,再设置这个IO口。有了置位/复位寄存器,无需进行读操作了,把不用操作的IO口写0就不会影响到那些IO口了。

1个锁存寄存器

8、端口锁存寄存器(GPIOx_LCKR)

  • 用的不多,当IO口被锁住时,无法执行CPU的写操作了。

2个复用功能寄存器

9、复用功能低位寄存器(GPIOx_AFRL)

每4位控制一个IO口的复用功能,低位寄存器控制低8位的8个GPIO口的复用功能。一个GPIO口可以复用16个复用功能

10、复用功能高位寄存器(GPIOx_AFRH)

每4位控制一个IO口的复用功能,高位寄存器控制高8位的8个GPIO口的复用功能。

GPIO库文件架构与代码剖析

两个层次,首先是寄存器版本例程,学习如何操作相应寄存器实现操控GPIO口。

寄存器版本

GPIO结构体定义

根据上文所讲的,每组GPIO口对应的10个相关寄存器,定义了以下结构体:

typedef struct
{
   
  __IO uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
  __IO uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
  __IO uint32_t OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
  __IO uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  __IO uint32_t IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
  __IO uint32_t ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
  __IO uint32_t BSRR;     /*!< GPIO port bit set/reset register,      Address offset: 0x18      */
  __IO uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
  __IO uint32_t AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;

看了上面的结构体定义,我产生了几个疑问:

(1)数据类型_IO uint32_t是什么声明类型?

#define     __IO    volatile             /*!< Defines 'read / write' permissions */
typedef unsigned  int uint32_t;

_IO是volatile类型修饰符,uint32_t是unsigned int。

(2)为啥要用volatile类型修饰符?
volatile的意思是告诉编译器,在编译源代码时,对这个变量不要使用优化。具体看这篇文章.

比如写这个io端口的时候,如果没有这个volatile,很可能由于编译器的优化,会先把值先写到一个缓冲区,到一定时候
再写到io端口,这样就不能使数据及时的写到io端口,有了volatile说明以后,就不会再经过cache,write buffer这种,而是直接写到io端口,从而避免了读写io端口的延时

(3)使用unsigned int与int声明的区别?
unsigned int占四个字节,int是默认有符号的即signed int也占4个字节。有符号无符号在使用上有区别,例如我要把寄存器ODR的32位全置1,十进制下,按照无符号类型我要赋值232-1,有符号类型我要赋值-231

(4)#define与typedef有什么区别?

  • 在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。
  • #define 宏名 字符串 。其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。
  • 关键字typedef在编译阶段有效,由于是在编译阶段,因此typedef有类型检查的功能。
    define则是宏定义,发生在预处理阶段,也就是编译之前,它只进行简单而机械的字符串替换,而不进行任何检查。具体看这篇文章.

(5)结构体中的这些变量是怎么跟实际地址映射到一起的呢?

GPIO地址映射

使用上述声明的结构体定义了11组GPIO

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)
#define GPIOE               ((GPIO_TypeDef *) GPIOE_BASE)
#define GPIOF               ((GPIO_TypeDef *) GPIOF_BASE)
#define GPIOG               ((GPIO_TypeDef *) GPIOG_BASE)
#define GPIOH               ((GPIO_TypeDef *) GPIOH_BASE)
#define GPIOI               ((GPIO_TypeDef *) GPIOI_BASE)
#define GPIOJ               ((GPIO_TypeDef *) GPIOJ_BASE)
#define GPIOK               ((GPIO_TypeDef *) GPIOK_BASE)

这时候又有疑问了,GPIO的地址映射是什么样的?

每组GPIO的地址是用外设

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值