本文从两个方面剖析GPIO:
阅读完本文,要能回答以下问题: 每组GPIO口包括4个32位的配置寄存器、2个32位的数据寄存器、1个32位的置位/复位寄存器、1个32位锁定寄存器和2个32位复用功能选择寄存器,一共10个寄存器。研究清楚这些寄存器有什么用、以及如何配置,也就搞懂了GPIO。 GPIO有八种工作模式,四种输入工作模式: 四种输出工作模式: GPIO上电复位后,默认工作在输入浮空状态。 注意,每组GPIO口通过10个相关寄存器管理好,一组包括16个GPIO口,那么,如果某个寄存器需要2位配置一个口,那刚好均分位,如果某个寄存器只需要1位配置一个口,那一般高16位保留。 1、端口模式寄存器(GPIOx_MODER) 2、端口输出类型寄存器(GPIOx_OTYPER) 3、端口输出速度寄存器(GPIOx_OSPEEDR) 4、端口上拉下拉寄存器(GPIOx_PUPDR) 5、端口输入数据寄存器(GPIOx_IDR) 6、端口输出数据寄存器(GPIOx_ODR) 7、端口置位/复位寄存器(GPIOx_BSRR) 8、端口锁存寄存器(GPIOx_LCKR) 9、复用功能低位寄存器(GPIOx_AFRL) 每4位控制一个IO口的复用功能,低位寄存器控制低8位的8个GPIO口的复用功能。一个GPIO口可以复用16个复用功能 10、复用功能高位寄存器(GPIOx_AFRH) 每4位控制一个IO口的复用功能,高位寄存器控制高8位的8个GPIO口的复用功能。 两个层次,首先是寄存器版本例程,学习如何操作相应寄存器实现操控GPIO口。 根据上文所讲的,每组GPIO口对应的10个相关寄存器,定义了以下结构体: 看了上面的结构体定义,我产生了几个疑问: (1)数据类型_IO uint32_t是什么声明类型? _IO是volatile类型修饰符,uint32_t是unsigned int。 (2)为啥要用volatile类型修饰符? 比如写这个io端口的时候,如果没有这个volatile,很可能由于编译器的优化,会先把值先写到一个缓冲区,到一定时候 (3)使用unsigned int与int声明的区别? (4)#define与typedef有什么区别? (5)结构体中的这些变量是怎么跟实际地址映射到一起的呢? 使用上述声明的结构体定义了11组GPIO 这时候又有疑问了,GPIO的地址映射是什么样的? 每组GPIO的地址是用外设总线基地址+偏移量定义好的 外设总线基地址是用外设基地址+偏移量定义好的 外设基地址是绝对地址了。 下面这些是sys.h里面的代码块,也就是正点原子自己写的,但位操作的思想值得学习一番。 GPIO初始化分为三步:使能IO口时钟、配置IO口寄存器、配置初始值。 (1)位或操作|=的作用:进行位操作,让指定的位置1。 上面定义的函数配置了IO口所需的4个配置寄存器,配置IO口的规范操作是进行位操作。 定义好GPIO口的引脚编号标志符,需要配置哪些位就把哪些位的标志符进行位或操作。 遍历的实现,是定义一个可变索引pos=1<<pinpos,当标志符curpin=BITx&pos的值与索引pos相同时,锁定到了要操作的位。这里也可以发现&操作是尽可能让位置0的。 对ODR、IDR的操作进行宏定义 其中位地址映射BIT_ADDR()的宏定义如下: 使用GPIOA的结构体基地址+偏移量,定位到了ODR寄存器 同样产生了一些疑问: 对于F1、F4(7系列没有),内核架构支持位地址映射(位带操作),可以把每一个比特位膨胀(映射)为一个字(32位、4字节),访问这个字地址就达到访问位的目的。 映射关系是人为规定的,但能够映射的位带区地址范围是有约束的,SRAM区最低1MB(0X20000000~0X200FFFFF)与片外外设区最低1MB范围是位带区地址。 此代码定义的映射关系为(addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2),把GPIOA_ODR的地址中的指定位bitnum通过基地址+偏移量的方式映射到SRAM区0X20000000中去。 (2)为什么要有位带操作? (3)取址符号*的使用把人搞糊涂了,怎么理解清楚? (4)为啥有这么多宏定义? 首先比较下使用寄存器(位带操作)与使用KAL库的代码结构有什么不同 下面学习HAL库怎么封装寄存器操作的。 跟寄存器形式相比较,似乎少了一些定义,然后发现定义的Mode变量既可以设置端口模式,还可以设置端口输出类型。 怎么实现一个32位的变量去设置两个32位的寄存器的呢?那就要看后续GPIO初始化的程序。 首先是对GPIO_InitTypeDef的成员变量赋值,然后进入HAL_GPIO_Init()函数 HAL库版本的GPIO初始化代码有100多行,比寄存器版本的代码多了10倍,为什么?我们接着研究 1、首先,HAL_GPIO_Init()接收两个入口参数,第一个变量是寄存器版本定义的GPIO_TypeDef变量类型,由此我们知道,GPIO原始的寄存器地址声明在这个变量里面,实际上还是要操作这些配置寄存器。第二个变量是GPIO_InitTypeDef类型,是我们新定义的类型 2、进入函数内部的第一步,就是进行入口参数的有效性判断。几乎是带参数的函数前面都有调用assert_param() assert_param()可以对表达式进行判断,如果表达式为真,什么都不执行,如果表达式为假,进入错误日志函数。这个函数常用于调试阶段检查是否有参数输入错误。 3、有效性判断后,开始进行遍历寻找需要设置的IO口,当IO口被索引到之后,开始正式配置寄存器。 4、配置寄存器的操作同上,按照先清零(=取反再位与)再设置(位或)的先后顺序来配置 同样,第一个入口参数是寄存器版本定义的GPIO_TypeDef变量类型,由此我们知道,实际上还是操作这些配置寄存器。函数内部就是设置BSRR寄存器,很容易看明白。通用I/O
GPIO概述
GPIO硬件架构与寄存器
GPIO硬件架构
GPIO工作模式
1.== 输入浮空模式==。上拉/下拉电阻不起作用,外部电平直接输入到芯片内部,适合电流要求小的场合。信号送至输入数据寄存器
2.== 输入上拉模式==。上拉电阻起作用。低电平输入时对芯片外部有灌电流。信号送至输入数据寄存器
3. 输入下拉模式。下拉电阻起作用。高电平输入时对芯片外部有拉电流。信号送至输入数据寄存器
4. 模拟模式。上拉/下拉电阻不起作用,信号沿模拟输入线输入到芯片内部ADC
GPIO相关寄存器
4个配置寄存器
2个数据寄存器
1个置位/复位寄存器
1个锁存寄存器
2个复用功能寄存器
GPIO库文件架构与代码剖析
寄存器版本
GPIO结构体定义
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;
#define __IO volatile /*!< Defines 'read / write' permissions */ typedef unsigned int uint32_t;
volatile的意思是告诉编译器,在编译源代码时,对这个变量不要使用优化。具体看这篇文章.
再写到io端口,这样就不能使数据及时的写到io端口,有了volatile说明以后,就不会再经过cache,write buffer这种,而是直接写到io端口,从而避免了读写io端口的延时
unsigned int占四个字节,int是默认有符号的即signed int也占4个字节。有符号无符号在使用上有区别,例如我要把寄存器ODR的32位全置1,十进制下,按照无符号类型我要赋值232-1,有符号类型我要赋值-231
define则是宏定义,发生在预处理阶段,也就是编译之前,它只进行简单而机械的字符串替换,而不进行任何检查。具体看这篇文章.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)
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000) #define GPIOB_BASE (AHB1PERIPH_BASE + 0x0400) #define GPIOC_BASE (AHB1PERIPH_BASE + 0x0800) #define GPIOD_BASE (AHB1PERIPH_BASE + 0x0C00) #define GPIOE_BASE (AHB1PERIPH_BASE + 0x1000) #define GPIOF_BASE (AHB1PERIPH_BASE + 0x1400) #define GPIOG_BASE (AHB1PERIPH_BASE + 0x1800) #define GPIOH_BASE (AHB1PERIPH_BASE + 0x1C00) #define GPIOI_BASE (AHB1PERIPH_BASE + 0x2000) #define GPIOJ_BASE (AHB1PERIPH_BASE + 0x2400) #define GPIOK_BASE (AHB1PERIPH_BASE + 0x2800)
/*!< Peripheral memory map */ #define APB1PERIPH_BASE PERIPH_BASE #define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000) #define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000) #define AHB2PERIPH_BASE (PERIPH_BASE + 0x10000000)
#define SRAM2_BASE ((uint32_t)0x2001C000) /*!< SRAM2(16 KB) base address in the alias region */ #define SRAM3_BASE ((uint32_t)0x20020000) /*!< SRAM3(64 KB) base address in the alias region */ #define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region
GPIO初始化(寄存器配置)
void LED_Init(void) { RCC->AHB1ENR|=1<<1;//使能PORTB时钟 GPIO_Set(GPIOB,PIN0|PIN1,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_100M,GPIO_PUPD_PU); //PB0,PB1ÉèÖÃ LED0=1;//LED0关闭 LED1=1;//LED1关闭 }
(2)左移<<优先级比位或|=高,因此这里是将AHB1ENR的第二位置1
void GPIO_Set(GPIO_TypeDef* GPIOx,u32 BITx,u32 MODE,u32 OTYPE,u32 OSPEED,u32 PUPD) { u32 pinpos=0,pos=0,curpin=0; for(pinpos=0;pinpos<16;pinpos++) { pos=1<<pinpos; //一个个引脚遍历 curpin=BITx&pos;//检查该位是否需要设置 if(curpin==pos) //如果需要设置,开始设置 { GPIOx->MODER&=~(3<<(pinpos*2)); //先清除原本的设置 GPIOx->MODER|=MODE<<(pinpos*2); //设置新的模式 if((MODE==0X01)||(MODE==0X02)) //如果是输出模式/外设输出模式 { //设置输出模式/外设输出模式下需要配置的寄存器 GPIOx->OSPEEDR&=~(3<<(pinpos*2)); //清零 GPIOx->OSPEEDR|=(OSPEED<<(pinpos*2));//置位 GPIOx->OTYPER&=~(1<<pinpos) ; //清零 GPIOx->OTYPER|=OTYPE<<pinpos; //置位 } GPIOx->PUPDR&=~(3<<(pinpos*2)); //清零 GPIOx->PUPDR|=PUPD<<(pinpos*2); //置位 } } } GPIO_Set(GPIOB,PIN0|PIN1,GPIO_MODE_OUT,GPIO_OTYPE_PP,GPIO_SPEED_100M,GPIO_PUPD_PU);//配置两个GPIO口
//GPIO口的引脚编号定义 #define PIN0 1<<0 #define PIN1 1<<1 #define PIN2 1<<2 #define PIN3 1<<3 #define PIN4 1<<4 #define PIN5 1<<5 #define PIN6 1<<6 #define PIN7 1<<7 #define PIN8 1<<8 #define PIN9 1<<9 #define PIN10 1<<10 #define PIN11 1<<11 #define PIN12 1<<12 #define PIN13 1<<13 #define PIN14 1<<14 #define PIN15 1<<15
以GPIOx->MODER&=~(3<<(pinpos*2))为例,由于MODER是两位定义一个IO口的功能,所以操作的基本单元是二进制11,将11左移到需要设置的IO口的那些位<<(pinpos * 2)。然后取反再位与,保证需要设置的位清零,其它位保持不变。GPIO赋值操作
//IO口操作,只对单一IO口 //确保n小于16 #define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出 #define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入 #define LED0 PAout(1) // DS0 #define LED1 PAout(0) // DS1 LED0=1; LED1=1;//给ODR寄存器相应位映射的地址写1赋值。
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) #define MEM_ADDR(addr) *((volatile unsigned long *)(addr)) #define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOA_ODR_Addr (GPIOA_BASE+20) //0x40020014
(1)内存都是字节地址,这为什么能定位到字节中的某一位,是我理解出问题了?
HAL库版本
//使用HAL库 int main(void) { HAL_Init(); //初始化HAL Stm32_Clock_Init(360,25,2,8); //设置时钟,180MHZ delay_init(180); //初始化延时函数 LED_Init(); //设置GPIO口 while(1) { HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET); delay_ms(1000); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET); delay_ms(1000); } } //操作寄存器 int main(void) { Stm32_Clock_Init(360,25,2,8); //ÉèÖÃʱÖÓ,180Mhz delay_init(180); //³õʼ»¯ÑÓʱº¯Êý LED_Init(); //³õʼ»¯LED while(1) { LED0=0; //LED0ÁÁ LED1=1; //LED1Ãð delay_ms(500); LED0=1; //LED0Ãð LED1=0; //LED1ÁÁ delay_ms(500); } }
GPIO结构体定义
typedef struct { uint32_t Pin; /*!< Specifies the GPIO pins to be configured. This parameter can be any value of @ref GPIO_pins_define */ uint32_t Mode; /*!< Specifies the operating mode for the selected pins. This parameter can be a value of @ref GPIO_mode_define */ uint32_t Pull; /*!< Specifies the Pull-up or Pull-Down activation for the selected pins. This parameter can be a value of @ref GPIO_pull_define */ uint32_t Speed; /*!< Specifies the speed for the selected pins. This parameter can be a value of @ref GPIO_speed_define */ uint32_t Alternate; /*!< Peripheral to be connected to the selected pins. This parameter can be a value of @ref GPIO_Alternate_function_selection */ }GPIO_InitTypeDef;
#define GPIO_MODE_INPUT ((uint32_t)0x00000000) /*!< Input Floating Mode */ #define GPIO_MODE_OUTPUT_PP ((uint32_t)0x00000001) /*!< Output Push Pull Mode */ #define GPIO_MODE_OUTPUT_OD ((uint32_t)0x00000011) /*!< Output Open Drain Mode */ #define GPIO_MODE_AF_PP ((uint32_t)0x00000002) /*!< Alternate Function Push Pull Mode */ #define GPIO_MODE_AF_OD ((uint32_t)0x00000012) /*!< Alternate Function Open Drain Mode */
GPIO初始化设置
void LED_Init(void) { GPIO_InitTypeDef GPIO_Initure; __HAL_RCC_GPIOB_CLK_ENABLE(); //使能IO时钟 GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1; //PB1,0 GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推挽输出 GPIO_Initure.Pull=GPIO_PULLUP; //上拉 GPIO_Initure.Speed=GPIO_SPEED_HIGH; //¸高速 HAL_GPIO_Init(GPIOB,&GPIO_Initure); HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET); //PB0置1 HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET); //PB1置1 }
HAL_GPIO_Init(GPIOB,&GPIO_Initure);
/* Check the parameters */ assert_param(IS_GPIO_ALL_INSTANCE(GPIOx)); assert_param(IS_GPIO_PIN(GPIO_Init->Pin)); assert_param(IS_GPIO_MODE(GPIO_Init->Mode)); assert_param(IS_GPIO_PULL(GPIO_Init->Pull));
#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__)) #define IS_GPIO_SPEED(SPEED) (((SPEED) == GPIO_SPEED_FREQ_LOW) || ((SPEED) == GPIO_SPEED_FREQ_MEDIUM) || ((SPEED) == GPIO_SPEED_FREQ_HIGH) || ((SPEED) == GPIO_SPEED_FREQ_VERY_HIGH))
for(position = 0; position < GPIO_NUMBER; position++) { /* Get the IO position */ ioposition = ((uint32_t)0x01) << position; /* Get the current IO position */ iocurrent = (uint32_t)(GPIO_Init->Pin) & ioposition; if(iocurrent == ioposition) { //开始配置寄存器 }
assert_param(IS_GPIO_SPEED(GPIO_Init->Speed)); /* Configure the IO Speed */ temp = GPIOx->OSPEEDR; temp &= ~(GPIO_OSPEEDER_OSPEEDR0 << (position * 2)); temp |= (GPIO_Init->Speed << (position * 2)); GPIOx->OSPEEDR = temp;
GPIO赋值操作
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET); //PB0置1
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState) { /* Check the parameters */ assert_param(IS_GPIO_PIN(GPIO_Pin)); assert_param(IS_GPIO_PIN_ACTION(PinState)); if(PinState != GPIO_PIN_RESET) { GPIOx->BSRR = GPIO_Pin; } else { GPIOx->BSRR = (uint32_t)GPIO_Pin << 16; } }
GPIO外部中断
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算