1、怎么看待外设:
从总线连接的角度看,外设和Core、DMA通过总线交换数据,正所谓要想富先修路。要注意,这些总线中的每一个都连接到不同的时钟源,这些时钟源决定了连接到该总线的外设操作的最大速度。
从内存分配的角度看,外设位于0x4000 0000~0x5FFF FFFF。该区域进一步分为几个子区域,每个子区域对应于一个特定的外围设备。按下图,GPIOA 外设(管理连接到 PORT-A 的所有引脚)从 0x4800 0000 映射到 0x4800 03FF。通过修改和读取这些 Map 区域的每个 register 来控制外设。
例如,GPIOA 外设的示例,要使能 PA5 引脚作为输出引脚,必须配置 MODER 寄存器,以便将位 [11:10] 配置为 01(对应于通用输出模式),如图所示。
接下来,要将引脚拉高,我们必须在输出数据寄存器 (ODR) 中设置相应的位[5],根据下表,该位映射到 GPIOA + 0x14 内存位置,即 0x4800 0000 + 0x14。
综合上述所讲,使用指针访问上述过程中GPIOA 外设映射内存,可写为:
int main(void) {
volatile uint32_t *GPIOA_MODER = 0x0, *GPIOA_ODR = 0x0;
GPIOA_MODER = (uint32_t*)0x48000000; // Address of the GPIOA->MODER register
GPIOA_ODR = (uint32_t*)(0x48000000 + 0x14); // Address of the GPIOA->ODR register
//This ensures that the peripheral is enabled and connected to the AHB1 bus
__HAL_RCC_GPIOA_CLK_ENABLE();
*GPIOA_MODER = *GPIOA_MODER | 0x400; // Sets MODER[11:10] = 0x1
*GPIOA_ODR = *GPIOA_ODR | 0x20; // Sets ODR[5] = 0x1, that is pulls PA5 high
while(1);
}
2、HAL handler
HAL要做的就是把上面所说的外设映射抽象出来。通过为每个外设定义多个处理程序(handler)来完成。处理程序handler只不过是一个 C 结构体,这个结构体引用指向真正的外围地址。
例如,将 GPIOA 定义为 GPIO_TypeDef 类型的指针,定义方式如下:
typedef struct {
volatile uint32_t MODER;
volatile uint32_t OTYPER;
volatile uint32_t OSPEEDR;
volatile uint32_t PUPDR;
volatile uint32_t IDR;
volatile uint32_t ODR;
volatile uint32_t BSRR;
volatile uint32_t LCKR;
volatile uint32_t AFR[2];
volatile uint32_t BRR;
} GPIO_TypeDef;
GPIO_TypeDef *GPIOA = 0x48000000;
GPIOA->MODER |= 0x400;
GPIOA->ODR |= 0x20;
什么意思?就是在地址0x48000000开始的内存,建立了一个结构体,并以0x48000000基地址操作MODER、OTYPER等变量。
在HAL中,已经定义了GPIOA,并将用户通过GPIO_InitStruct结构体设置的参数,使用HAL_GPIO_Init函数写入上面所说的GPIO_TypeDef结构体(在地址0x48000000开始的内存)。
/*Configure GPIO pin : PA5 */
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
3、GPIO配置
要配置 GPIO,我们使用 HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init) 函数。GPIO_InitTypeDef 是用于配置 GPIO 的 C 结构体,其定义方式如下:
typedef struct {
uint32_t Pin;
uint32_t Mode;
uint32_t Pull;
uint32_t Speed;
uint32_t Alternate;
} GPIO_InitTypeDef;
设置所需的替代功能。例如,设置 PA3 可以用作USART2_RX(即它可以用作 USART/UART2 外设的 RX 引脚)。CubeMX 会自动为我们生成正确的初始化代码,如下所示:
/* Configure GPIO pins : PA2 PA3 */
GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_LOW;
GPIO_InitStruct.Alternate = GPIO_AF1_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
4、驱动 GPIO
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin);