stm32的寄存器映射方法
0赞小目标是对几个io口延时翻转,即实现闪灯。
最终控制:GPIOA端口的ODR寄存器,对应基地址0x4001 0800 - 0x4001 0BFF,偏移地址0x0C。
如何把类似的几百个寄存器映射成方便操作的指针或变量?
下面从顶部向下逐步分解。
直接用cube产生代码如下,调用HAL_GPIO_TogglePin函数
/* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ HAL_GPIO_TogglePin(GPIOA,user_led_1_Pin); HAL_GPIO_TogglePin(GPIOA,user_led_2_Pin); HAL_GPIO_TogglePin(GPIOA,user_led_3_Pin); HAL_GPIO_TogglePin(GPIOA,user_led_4_Pin); HAL_Delay(1000); } /* USER CODE END 3 */ |
Toggle函数把指定bit逐次异或(bit=0与1异或=1,bit=1与1异或=0,即实现翻转)
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) { /* Check the parameters */ assert_param(IS_GPIO_PIN(GPIO_Pin)); GPIOx->ODR ^= GPIO_Pin; } |
参数user_led_1_Pin,根据如下宏定义,把io对应位标为1
#define user_led_1_Pin GPIO_PIN_0 #define user_led_1_GPIO_Port GPIOA #define user_led_2_Pin GPIO_PIN_1 #define user_led_2_GPIO_Port GPIOA #define user_led_3_Pin GPIO_PIN_2 #define user_led_3_GPIO_Port GPIOA #define user_led_4_Pin GPIO_PIN_3 #define user_led_4_GPIO_Port GPIOA |
#define GPIO_PIN_0 ((uint16_t)0x0001) #define GPIO_PIN_1 ((uint16_t)0x0002) #define GPIO_PIN_2 ((uint16_t)0x0004) #define GPIO_PIN_3 ((uint16_t)0x0008) #define GPIO_PIN_4 ((uint16_t)0x0010) #define GPIO_PIN_5 ((uint16_t)0x0020) #define GPIO_PIN_6 ((uint16_t)0x0040) #define GPIO_PIN_7 ((uint16_t)0x0080) #define GPIO_PIN_8 ((uint16_t)0x0100) #define GPIO_PIN_9 ((uint16_t)0x0200) #define GPIO_PIN_10 ((uint16_t)0x0400) #define GPIO_PIN_11 ((uint16_t)0x0800) #define GPIO_PIN_12 ((uint16_t)0x1000) #define GPIO_PIN_13 ((uint16_t)0x2000) #define GPIO_PIN_14 ((uint16_t)0x4000) #define GPIO_PIN_15 ((uint16_t)0x8000) #define GPIO_PIN_All ((uint16_t)0xFFFF) |
参数GPIOA,是个struct型指针,指向GPIOA_BASE表示的地址。
#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) |
GPIOA_BASE的地址如下表示(查手册 RM0008 Reference manual)
#define GPIOA_BASE (APB2PERIPH_BASE + 0x0800) #define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00) #define GPIOC_BASE (APB2PERIPH_BASE + 0x1000) #define GPIOD_BASE (APB2PERIPH_BASE + 0x1400) #define GPIOE_BASE (APB2PERIPH_BASE + 0x1800) |
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000) |
#define PERIPH_BASE ((uint32_t)0x40000000) |
st的工程师合理运用结构体的特点,将寄存器的偏移地址与结构体变量对应。
Port configuration register low (GPIOx_CRL) (x=A..G) |
Address offset: 0x00 |
Port configuration register high (GPIOx_CRH) (x=A..G) |
Address offset: 0x04 |
Port input data register (GPIOx_IDR) (x=A..G) |
Address offset: 0x08h |
Port output data register (GPIOx_ODR) (x=A..G) |
Address offset: 0x0C |
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; |
定义一个struct就会对应一个存储地址,内部的变量依次占用存储空间。
如果#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE),则定义的GPIOA是一个结构体指针,指向基地址。
内部的变量依次对应有其偏移地址,uint32_t 占32bit即4Byte,那么CRL偏移0x00,CRH偏移0x04,IDR偏移0x08,ODR偏移0x0C 。(结构体有默认的对齐方式,分配空间一定是最大变量的整数倍)
用struct的方式很好地将端口以及其寄存器匹配起来,层次分明。
对REG内单个bit位的处理
1、将bit7位置0,其他位不变。
GPIOx->CRL &= ~ (uint32_t)(1<<7);
分析: (1<<7)即0000_0000_0000_0000_0000_0000_1000_0000,
取反后变成1111_1111_1111_1111_1111_1111_0111_1111,跟原reg值与,即实现置0
2、将bit7位置1,其他位不变。
GPIOx->CRL |= (uint32_t)(1<<7);
3、将bit7位取反,其他位不变。
GPIOx->CRL ^= (uint32_t)(1<<7);
继续把移位值define成常量,#define CNF1_1 7,这样每个bit位都实现了定义。
\Drivers\CMSIS\Device\ST\STM32F1xx\Include\stm32f103xb.h
可以参考这个文件里面position和mask的做法。
/******************* Bit definition for GPIO_CRL register *******************/
#define GPIO_CRL_MODE_Pos (0U)
#define GPIO_CRL_MODE_Msk (0x33333333U << GPIO_CRL_MODE_Pos)
#define GPIO_CRL_MODE GPIO_CRL_MODE_Msk