GPIO控制
GPIO口寄存器的封装和总线时钟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| typedef struct { __IO uint32_t MODER; __IO uint32_t OTYPER; __IO uint32_t OSPEEDR; __IO uint32_t PUPDR; __IO uint32_t IDR; __IO uint32_t ODR; __IO uint32_t BSRR; __IO uint32_t LCKR; __IO uint32_t AFR[2]; } GPIO_TypeDef;
typedef struct { uint32_t Pin; uint32_t Mode; uint32_t Pull; uint32_t Speed; uint32_t Alternate; }GPIO_InitTypeDef;
__HAL_RCC_GPIOx_CLK_ENABLE();
#define __HAL_RCC_GPIOA_CLK_ENABLE() do { \ __IO uint32_t tmpreg = 0x00U; \ SET_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\ \ tmpreg = READ_BIT(RCC->AHB1ENR, RCC_AHB1ENR_GPIOAEN);\ UNUSED(tmpreg); \ } while(0U)
|
GPIO的初始化
1
| void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init)
|
其中GPIOx可以直接传入GPIOA, GPIOB等等,这些已经被预编译成了以引脚初始寄存器位置开始的GPIO_TypeDef 类型的结构体,上面一节有介绍。GPIO_InitTypeDef用于控制引脚的各个功能和状态配置。
GPIO_InitTypeDef成员的值定义如下:
Pin:
Pin控制要对那一个引脚操控, 可传GPIO_PIN_x,x可以为0-15,ALL,MASK三种。
GPIO_PIN_0 如果将x设置为0-15,则传入的是GPIO的寄存器位置(例如,GPIO3对应的寄存器地址0B0000,0100)。多个引脚时,可以用或运算同时使能,如GPIO_PIN_0 | GPIO_PIN_1。
GPIO_PIN_ALL 对应的是0xFFFF,也就是将16个PIN全部操作
GPIO_PIN_MASK是给断言机制判断传入参数是否合法使用的,不作为用户调用参数传入。断言机制代码如下:
1 2
| #define IS_GPIO_PIN(__PIN__) ((((__PIN__) & GPIO_PIN_MASK) != (uint32_t)0x00) &&\ (((__PIN__) & ~GPIO_PIN_MASK) == (uint32_t)0x00))
|
这样IS_GPIO_PIN 宏定义就会在输入非GPIO合法值时返回0,触发断言机制,程序报错并定位。
Mode:
Mode控制GPIO的输入、输出模式,有如下选择:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #define GPIO_MODE_INPUT #define GPIO_MODE_OUTPUT_PP #define GPIO_MODE_OUTPUT_OD
#define GPIO_MODE_AF_PP #define GPIO_MODE_AF_OD #define GPIO_MODE_ANALOG #define GPIO_MODE_IT_RISING #define GPIO_MODE_IT_FALLING #define GPIO_MODE_IT_RISING_FALLING
#define GPIO_MODE_EVT_RISING #define GPIO_MODE_EVT_FALLING #define GPIO_MODE_EVT_RISING_FALLING
|
Pull:
PULL控制上拉、下拉电阻使能情况:
1 2 3
| #define GPIO_NOPULL ((uint32_t)0x00000000) #define GPIO_PULLUP ((uint32_t)0x00000001) #define GPIO_PULLDOWN ((uint32_t)0x00000002)
|
Speed
控制GPIO时钟的速度,对应也就是GPIO的响应速度.ST公司给了四种不同的相对速度(相对MCU极限速度而言)可供选择。对于不同的MCU,这里define的值是不一样的,对应的频率也是不一样的,需要参考芯片手册。对于F4系列,其定义如下
1 2 3 4
| #define GPIO_SPEED_FREQ_LOW #define GPIO_SPEED_FREQ_MEDIUM #define GPIO_SPEED_FREQ_HIGH #define GPIO_SPEED_FREQ_VERY_HIGH
|
Alternate
控制引脚的复用情况,不同的MCU引脚可复用功能不同,需要参看stm32f4xx_hal_gpio_ex.h 头文件内的定义。例如F407系列,AF8引脚就可以配置成GPIO_AF8_UART4、GPIO_AF8_UART5、GPIO_AF8_USART6三种。详情需要参考不同芯片的手册。
GPIO的去初始化
1
| void HAL_GPIO_DeInit(GPIO_TypeDef *GPIOx, uint32_t GPIO_Pin)
|
这个函数用于将已初始化的GPIO恢复至默认值
- GPIOx 同init。
- GPIO_Pin传入要初始化第几个即可,多个Pin使用或运算合并,同Init。
读取引脚电平
1
| GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
|
用于读取GPIO的电平,读取的是IDR寄存器内对应的值。其返回值类型为GPIO_PinState,定义如下:
1 2 3 4 5
| typedef enum { GPIO_PIN_RESET = 0, GPIO_PIN_SET }GPIO_PinState;
|
也就是RESET=0,SET=1。
写引脚电平
1
| void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState)
|
用于写入GPIO某一引脚的电平。GPIOx, GPIO_Pin同上,PinState有 GPIO_PIN_RESET 和 GPIO_PIN_SET 两种
反转引脚电平
1
| void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
|
这个函数的作用是讲某个引脚输出的值反转
这个函数的实现原理是使用BSRR寄存器,如果直接操作ODR寄存器,有被中断等(IRQ)打断的风险,而通过BSRR和BRR去改变管脚状态是不会被中断打断的,即不需要关闭中断。
BSRR的低 16bits 是set操作,而高16bits是 reset 操作。这个函数的实现原理如下
1 2
| GPIOx->BSRR = ((odr & GPIO_Pin) << GPIO_NUMBER) | (~odr & GPIO_Pin);
|
这个语句首先将要操作PIN的ODR值放进Reset里面,然后再将这个值取反放进Set里面,如果此时这个Pin是1,那么对应寄存器的Reset就会是1,Set是0,反之亦然。这样就达到了对某一个PIN反相的效果。
锁定端口电平
1
| HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin)
|
这个函数通过对LCKR寄存器进行配置,对某一个引脚输出的值进行锁定,一般用得比较少。其返回值是锁定是否成功,成功返回HAL_OK (0),失败返回HAL_ERROR (1)
GPIO触发的中断处理函数
1 2 3 4 5 6 7 8 9 10 11
| void HAL_GPIO_EXTI_IRQHandler(uint16_t GPIO_Pin) { if(__HAL_GPIO_EXTI_GET_IT(GPIO_Pin) != RESET) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin); HAL_GPIO_EXTI_Callback(GPIO_Pin); } } __weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {UNUSED(GPIO_Pin);}
|
这个函数在中断入口的位置被自动调用。函数首先会清除中断Flag,然后调用HAL_GPIO_EXTI_Callback函数,这个回调函数就是中断要干什么。在HAL库内,有一个HAL_GPIO_EXTI_Callback已经被定义,但是使用__weak修饰,且函数只是返回一个void值,这里的意思是当用户自己定义这个中断处理函数时HAL库内这个会被disable,这个函数存在在这个只是为了防止gcc报错,且用户不可直接修改HAL库内这个callback,需要自己重新定义一个新的。
也就是说,我们需要自定义一个void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) 的函数,来写入中断操作的内容。
NVIC、EXTI和中断
代码在stm32f4xx_hal_cortex.c中
设置中断分组(NVIC)
1
| void HAL_NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
|
其中参数如下:
- NVIC_PRIORITYGROUP_0
- NVIC_PRIORITYGROUP_1
- NVIC_PRIORITYGROUP_2
- NVIC_PRIORITYGROUP_3
- NVIC_PRIORITYGROUP_4

设置中断优先级(NVIC)
1
| void HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)
|
其中IRQn表示要设置的中断,PreemptPriority表示抢占优先级,SubPriority表示响应优先级
IRQn(中断号)
中断号用于指明控制哪一个中断,代码定义和对应中断如下:
它存储的是偏移地址,即相对于外部中断第一个中断寄存器WWDG_IRQn(0x0000,0040)地址的偏移量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| typedef enum { NonMaskableInt_IRQn = -14, MemoryManagement_IRQn = -12, BusFault_IRQn = -11, UsageFault_IRQn = -10, SVCall_IRQn = -5, DebugMonitor_IRQn = -4, PendSV_IRQn = -2, SysTick_IRQn = -1, WWDG_IRQn = 0, PVD_IRQn = 1, TAMP_STAMP_IRQn = 2, RTC_WKUP_IRQn = 3, FLASH_IRQn = 4, RCC_IRQn = 5, EXTI0_IRQn = 6, EXTI1_IRQn = 7, EXTI2_IRQn = 8, EXTI3_IRQn = 9, EXTI4_IRQn = 10, DMA1_Stream0_IRQn = 11, DMA1_Stream1_IRQn = 12, DMA1_Stream2_IRQn = 13, DMA1_Stream3_IRQn = 14, DMA1_Stream4_IRQn = 15, DMA1_Stream5_IRQn = 16, DMA1_Stream6_IRQn = 17, ADC_IRQn = 18, CAN1_TX_IRQn = 19, CAN1_RX0_IRQn = 20, CAN1_RX1_IRQn = 21, CAN1_SCE_IRQn = 22, EXTI9_5_IRQn = 23, TIM1_BRK_TIM9_IRQn = 24, TIM1_UP_TIM10_IRQn = 25, TIM1_TRG_COM_TIM11_IRQn = 26, TIM1_CC_IRQn = 27, TIM2_IRQn = 28, TIM3_IRQn = 29, TIM4_IRQn = 30, I2C1_EV_IRQn = 31, I2C1_ER_IRQn = 32, I2C2_EV_IRQn = 33, I2C2_ER_IRQn = 34, SPI1_IRQn = 35, SPI2_IRQn = 36, USART1_IRQn = 37, USART2_IRQn = 38, USART3_IRQn = 39, EXTI15_10_IRQn = 40, RTC_Alarm_IRQn = 41, OTG_FS_WKUP_IRQn = 42, TIM8_BRK_TIM12_IRQn = 43, TIM8_UP_TIM13_IRQn = 44, TIM8_TRG_COM_TIM14_IRQn = 45, TIM8_CC_IRQn = 46, DMA1_Stream7_IRQn = 47, FSMC_IRQn = 48, SDIO_IRQn = 49, TIM5_IRQn = 50, SPI3_IRQn = 51, UART4_IRQn = 52, UART5_IRQn = 53, TIM6_DAC_IRQn = 54, TIM7_IRQn = 55, DMA2_Stream0_IRQn = 56, DMA2_Stream1_IRQn = 57, DMA2_Stream2_IRQn = 58, DMA2_Stream3_IRQn = 59, DMA2_Stream4_IRQn = 60, ETH_IRQn = 61, ETH_WKUP_IRQn = 62, CAN2_TX_IRQn = 63, CAN2_RX0_IRQn = 64, CAN2_RX1_IRQn = 65, CAN2_SCE_IRQn = 66, OTG_FS_IRQn = 67, DMA2_Stream5_IRQn = 68, DMA2_Stream6_IRQn = 69, USART6_IRQn = 71, I2C3_EV_IRQn = 72, I2C3_ER_IRQn = 73, OTG_HS_EP1_OUT_IRQn = 74, OTG_HS_EP1_IN_IRQn = 75, OTG_HS_WKUP_IRQn = 76, OTG_HS_IRQn = 77, DCMI_IRQn = 78, RNG_IRQn = 80, FPU_IRQn = 81 } IRQn_Type;
|
PreemptPriority(抢占优先级)和SubPriority (响应优先级)
可输入0-15(十进制),对应抢占优先级/响应优先级的大小,数字越小优先级越高。朝IPR寄存器内写入,优先级的输入范围随Group设置范围变化。
使能/失能中断(NVIC)
1 2
| void HAL_NVIC_EnableIRQ(IRQn_Type IRQn) void HAL_NVIC_DisableIRQ(IRQn_Type IRQn)
|
其输入参数IRQn(中断号) 同上。用于指明控制哪一个中断。
配置EXTI模式和映射IO
在HAL库中,配置EXTI寄存器上沿/下沿触发,映射IO,都通过在引脚初始化时,设置GPIO_InitTypeDef.mode实现。可设置参数如下,详见GPIO章节。
1 2 3 4 5 6 7
| #define GPIO_MODE_IT_RISING #define GPIO_MODE_IT_FALLING #define GPIO_MODE_IT_RISING_FALLING
#define GPIO_MODE_EVT_RISING #define GPIO_MODE_EVT_FALLING #define GPIO_MODE_EVT_RISING_FALLING
|
清除中断flag
HAL库中中断flag会在调用回调函数前自动通过宏清除,宏定义如下:
1
| #define __HAL_GPIO_EXTI_CLEAR_IT(__EXTI_LINE__) (EXTI->PR = (__EXTI_LINE__))
|
这个过程无需人为干预
中断Handler
在进入中断时,我们需要编写中断执行的函数,STM32不同于大部分单片机中断函数都叫interrupt,它不同的中断会调用各自不同的函数。这些中断函数在startup文件中被定义,称之为“中断向量表”。以下是定义代码和对应的名称。在使用CubeMX初始化代码时,这些Handler会被定义在stm32fxxx_it.c文件中,可以直接在it.c文件中对中断进行编辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler DCD MemManage_Handler ; MPU Fault Handler DCD BusFault_Handler ; Bus Fault Handler DCD UsageFault_Handler ; Usage Fault Handler DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD 0 ; Reserved DCD SVC_Handler ; SVCall Handler DCD DebugMon_Handler ; Debug Monitor Handler DCD 0 ; Reserved DCD PendSV_Handler ; PendSV Handler DCD SysTick_Handler ; SysTick Handler
; External Interrupts DCD WWDG_IRQHandler ; Window WatchDog DCD PVD_IRQHandler ; PVD through EXTI Line detection DCD TAMP_STAMP_IRQHandler ; Tamper and TimeStamps through the EXTI line DCD RTC_WKUP_IRQHandler ; RTC Wakeup through the EXTI line DCD FLASH_IRQHandler ; FLASH DCD RCC_IRQHandler ; RCC DCD EXTI0_IRQHandler ; EXTI Line0 DCD EXTI1_IRQHandler ; EXTI Line1 DCD EXTI2_IRQHandler ; EXTI Line2 DCD EXTI3_IRQHandler ; EXTI Line3 DCD EXTI4_IRQHandler ; EXTI Line4 DCD DMA1_Stream0_IRQHandler ; DMA1 Stream 0 DCD DMA1_Stream1_IRQHandler ; DMA1 Stream 1 DCD DMA1_Stream2_IRQHandler ; DMA1 Stream 2 DCD DMA1_Stream3_IRQHandler ; DMA1 Stream 3 DCD DMA1_Stream4_IRQHandler ; DMA1 Stream 4 DCD DMA1_Stream5_IRQHandler ; DMA1 Stream 5 DCD DMA1_Stream6_IRQHandler ; DMA1 Stream 6 DCD ADC_IRQHandler ; ADC1, ADC2 and ADC3s DCD CAN1_TX_IRQHandler ; CAN1 TX DCD CAN1_RX0_IRQHandler ; CAN1 RX0 DCD CAN1_RX1_IRQHandler ; CAN1 RX1 DCD CAN1_SCE_IRQHandler ; CAN1 SCE DCD EXTI9_5_IRQHandler ; External Line[9:5]s DCD TIM1_BRK_TIM9_IRQHandler ; TIM1 Break and TIM9 DCD TIM1_UP_TIM10_IRQHandler ; TIM1 Update and TIM10 DCD TIM1_TRG_COM_TIM11_IRQHandler ; TIM1 Trigger and Commutation and TIM11 DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare DCD TIM2_IRQHandler ; TIM2 DCD TIM3_IRQHandler ; TIM3 DCD TIM4_IRQHandler ; TIM4 DCD I2C1_EV_IRQHandler ; I2C1 Event DCD I2C1_ER_IRQHandler ; I2C1 Error DCD I2C2_EV_IRQHandler ; I2C2 Event DCD I2C2_ER_IRQHandler ; I2C2 Error DCD SPI1_IRQHandler ; SPI1 DCD SPI2_IRQHandler ; SPI2 DCD USART1_IRQHandler ; USART1 DCD USART2_IRQHandler ; USART2 DCD USART3_IRQHandler ; USART3 DCD EXTI15_10_IRQHandler ; External Line[15:10]s DCD RTC_Alarm_IRQHandler ; RTC Alarm (A and B) through EXTI Line DCD OTG_FS_WKUP_IRQHandler ; USB OTG FS Wakeup through EXTI line DCD TIM8_BRK_TIM12_IRQHandler ; TIM8 Break and TIM12 DCD TIM8_UP_TIM13_IRQHandler ; TIM8 Update and TIM13 DCD TIM8_TRG_COM_TIM14_IRQHandler ; TIM8 Trigger and Commutation and TIM14 DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare DCD DMA1_Stream7_IRQHandler ; DMA1 Stream7 DCD FMC_IRQHandler ; FMC DCD SDIO_IRQHandler ; SDIO DCD TIM5_IRQHandler ; TIM5 DCD SPI3_IRQHandler ; SPI3 DCD UART4_IRQHandler ; UART4 DCD UART5_IRQHandler ; UART5 DCD TIM6_DAC_IRQHandler ; TIM6 and DAC1&2 underrun errors DCD TIM7_IRQHandler ; TIM7 DCD DMA2_Stream0_IRQHandler ; DMA2 Stream 0 DCD DMA2_Stream1_IRQHandler ; DMA2 Stream 1 DCD DMA2_Stream2_IRQHandler ; DMA2 Stream 2 DCD DMA2_Stream3_IRQHandler ; DMA2 Stream 3 DCD DMA2_Stream4_IRQHandler ; DMA2 Stream 4 DCD ETH_IRQHandler ; Ethernet DCD ETH_WKUP_IRQHandler ; Ethernet Wakeup through EXTI line DCD CAN2_TX_IRQHandler ; CAN2 TX DCD CAN2_RX0_IRQHandler ; CAN2 RX0 DCD CAN2_RX1_IRQHandler ; CAN2 RX1 DCD CAN2_SCE_IRQHandler ; CAN2 SCE DCD OTG_FS_IRQHandler ; USB OTG FS DCD DMA2_Stream5_IRQHandler ; DMA2 Stream 5 DCD DMA2_Stream6_IRQHandler ; DMA2 Stream 6 DCD DMA2_Stream7_IRQHandler ; DMA2 Stream 7 DCD USART6_IRQHandler ; USART6 DCD I2C3_EV_IRQHandler ; I2C3 event DCD I2C3_ER_IRQHandler ; I2C3 error DCD OTG_HS_EP1_OUT_IRQHandler ; USB OTG HS End Point 1 Out DCD OTG_HS_EP1_IN_IRQHandler ; USB OTG HS End Point 1 In DCD OTG_HS_WKUP_IRQHandler ; USB OTG HS Wakeup through EXTI DCD OTG_HS_IRQHandler ; USB OTG HS DCD DCMI_IRQHandler ; DCMI DCD 0 ; Reserved DCD HASH_RNG_IRQHandler ; Hash and Rng DCD FPU_IRQHandler ; FPU __Vectors_End
|
HAL库的中断处理流程
CubeMX+HAL工程中断初始化的流程
如果我们采用寄存器编程,以初始化映射至IO的EXTI中断为例,那么需要经历以下步骤:
- 使能GPIO时钟和NVIC、EXTI总线时钟
- 设置GPIO模式(输入、是否上拉等)
- 设置EXTI和GPIO的映射关系(设置APIO或STYSCFG寄存器)
- 设置边沿触发模式(RTSR,FTSR)、enbale中断(IMR/EMR)
- 设置NVIC分组模式
- 设置NVIC优先级模式
- 使能NVIC对应中断
在使用CubeMX生成的工程中,1,2,3,4都是在MX_GPIO_Init()函数内完成。如果是映射至IO的EXTI的中断,那么MX_GPIO_Init()函数内还会完成相应中断的优先级设置和使能,也就是6,7;如果是其他中断,那么在相应外设的init函数中也会完成优先级设置和使能。
鉴于NVIC的分组模式一般不会更改,因此NVIC的分组在一开始就被设置,CubeMX+HAL库生成的工程会在HAL_Init()函数中完成。
main函数中,会依次调用这三个函数来完成整个系统的初始化,其中便囊括了中断的初始化:
1 2 3
| HAL_Init(); SystemClock_Config(); MX_GPIO_Init();
|
中断调用函数的流程
在中断被触发时,STM32首先会调用Handler,也就是下方 中断Handler 小节内介绍的这些。这些Handler的内容存储在stm32fxxx_it.c。Hander函数内首先调用公共中断函数,来实现清除中断标志等,然后调用回调函数执行处理的内容。这里面用户可直接编辑Handler函数的内容,来决定其调用哪些公共函数,也可自行重新定义Callback函数的内容,来编写中断处理程序。
这个流程的函数伪代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void xxx_IRQHandler(){ xxx_public_INT_Func(specific parameters); } void xxx_public_INT_Func(parameters){ public_funcs(parameters); Callback_func(parameters); } void Callback_func(parameters){ if (parameters == xx){ } if (parameters == yy){ } }
|
一般来说,callback函数是弱定义的,例如EXTI的回调函数定义如下:
1
| __weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
|
因此,在HAL库中使用中断时,用户只需要自定重新定义回调函数部分,找到不同中断Handler调用的回调函数,在回调函数部分写入中断处理的代码即可。通常来说,用户只需要自己重新定义Callback fucntion,并根据传入的参数不同,使用if编写不同中断源的处理程序即可。但是再多个外设共用一个回调函数时,这些if可能需要写非常多,这样不便于管理,因此直接把处理函数写在其自己的驱动文件内,然后在xxx_IRQHandler() 内调用即可。
UART串口
huart句柄
这是一个调用和uart相关HAL库接口时,需要传入的参数,用于说明数据从哪个串口进来,串口的基本设置参数。这个参数被称为”handel”,也就是huart中h的来源,可以理解为握着uart1的handel,就可以控制和操作uart1的意思。其源代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| typedef struct __UART_HandleTypeDef { USART_TypeDef *Instance; UART_InitTypeDef Init; const uint8_t *pTxBuffPtr; uint16_t TxXferSize; __IO uint16_t TxXferCount; uint8_t *pRxBuffPtr; uint16_t RxXferSize; __IO uint16_t RxXferCount; __IO HAL_UART_RxTypeTypeDef ReceptionType; __IO HAL_UART_RxEventTypeTypeDef RxEventType; DMA_HandleTypeDef *hdmatx; DMA_HandleTypeDef *hdmarx; HAL_LockTypeDef Lock; __IO HAL_UART_StateTypeDef gState;
__IO HAL_UART_StateTypeDef RxState;
__IO uint32_t ErrorCode; #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); void (* TxCpltCallback)(struct __UART_HandleTypeDef *huart); void (* RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); void (* RxCpltCallback)(struct __UART_HandleTypeDef *huart); void (* ErrorCallback)(struct __UART_HandleTypeDef *huart); void (* AbortCpltCallback)(struct __UART_HandleTypeDef *huart); void (* AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart); void (* AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart); void (* WakeupCallback)(struct __UART_HandleTypeDef *huart); void (* RxEventCallback)(struct __UART_HandleTypeDef *huart, uint16_t Pos);
void (* MspInitCallback)(struct __UART_HandleTypeDef *huart); void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart); #endif
} UART_HandleTypeDef;
|
USART_TypeDef
该结构体用于参数UASRT硬件的寄存器基地址,结构体定义如下:
在创建 $USART_TypeDef\ *$ 类型,以某一个串口配置寄存器开始的结构体指针后,便可以访问该串口的配置寄存器。
1 2 3 4 5 6 7 8 9 10
| typedef struct { __IO uint32_t SR; __IO uint32_t DR; __IO uint32_t BRR; __IO uint32_t CR1; __IO uint32_t CR2; __IO uint32_t CR3; __IO uint32_t GTPR; } USART_TypeDef;
|
该结构体的基地址变量,由总线基地址+偏移地址宏定义,例如F407定义的USART1和2
1 2 3 4
| #define USART1_BASE (APB2PERIPH_BASE + 0x1000UL) #define USART2_BASE (APB1PERIPH_BASE + 0x4400UL) #define USART1 ((USART_TypeDef *) USART1_BASE) #define USART2 ((USART_TypeDef *) USART2_BASE)
|
对于F4系列,有6个串口:USART1-3,UART4-5,USART6,他们都通过如上代码宏定义了基地址,因此给结构体成员Instance传入这些地址即可。
UART_InitTypeDef
该结构体用于存放UART通信中的一些基本参数,代码如下:
1 2 3 4 5 6 7 8 9 10
| typedef struct { uint32_t BaudRate; uint32_t WordLength; uint32_t StopBits; uint32_t Parity; uint32_t Mode; uint32_t HwFlowCtl; uint32_t OverSampling; } UART_InitTypeDef;
|
串口初始化
轮询方式收发的初始化
使用轮询模式时,只需要先配置huart中Init结构体的参数,然后再调用HAL_UART_Init函数进行寄存器配置即可。下面是一个例子
1 2 3 4 5 6 7 8 9 10 11 12
| huart1.Instance = USART3; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart1.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart1) != HAL_OK) { Error_Handler(); }
|
在HAL_UART_Init()函数中,会依次执行如下操作:
- 若检测为轮询模式的初始化,则会调用HAL_UART_MspInit()函数对其所用的总线时钟、GPIO等关联设备进行初始化
- UART_SetConfig() 函数来对控制寄存器写入
- 对句柄结构体中所有状态指示器写入OK的状态
其中HAL_UART_MspInit()函数一个例子如下,它会对所有关联的设备进行初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle) {
GPIO_InitTypeDef GPIO_InitStruct = {0}; if(uartHandle->Instance==USART1) { __HAL_RCC_USART1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; GPIO_InitStruct.Alternate = GPIO_AF7_USART1; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); } }
|
在使用CubeMX配置工程时,会生成usart.c文件来存放各项init函数,例如总初始化函数MX_USART1_UART_Init()、关联设备初始化函数HAL_UART_MspIni(),以备HAL库文件stm32fxxx_hal_uart.c调用
轮询(阻塞)数据收发
在轮询数据模式下,MCU专注于发送数据或接受数据,在Timeout规定的时间内收发完成则return 成功,否则return超时。在这个期间,MCU一直除以等数据接受或者看着输出发送的状态,效率较低。
轮询串口接收
1
| HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
|
huart句柄已在上方介绍,剩余输入参数分别是:
- pData:存放数据的buffer首地址
- Size:接受数据的字符串长度
- Timeout:轮询时长,超时则返回
这个函数会返回的类型”HAL_StatusTypeDef”有如下状态:
1 2 3 4 5 6 7
| typedef enum { HAL_OK = 0x00U, HAL_ERROR = 0x01U, HAL_BUSY = 0x02U, HAL_TIMEOUT = 0x03U } HAL_StatusTypeDef;
|
轮询串口发送
1
| HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, const uint8_t *pData, uint16_t Size, uint32_t Timeout)
|
其输入参数、返回参数定义均同轮询接收函数一样,这里不重复介绍。
中断数据收发
使用中断模式进行数据收发时,MCU进会在有数据进来的时候让CPU去处理数据。其余时候可以干其他事情。这样效率较高
中断串口接收
1
| HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
|
参数如下:
- huart:uart handle地址
- pData:存放数据的buffer首地址
- Size:接受数据的字符串长度
这里面输入的Size参数会被赋值进handle内的RxXferSize成员内。pData会被赋值进handle内的pRxBuffPtr成员内。在USART中断函数
HAL_UART_IRQHandler()中,会首先判断有没有错误出现,如果没有错误,则调用UART_Receive_IT()函数进行接收。这两个函数都被定义在stm32xxx_hal_uart.c内。
UART_Receive_IT函数中,每接收一个数据,就会对pRxBuffPtr的指针地址进行位移,来指向下一个内存单元的地址。同时对RxXferCount自减。当RxXferCount自减至0时,会关闭USART接受相关的中断使能。这一段代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart) { uint8_t *pdata8bits; uint16_t *pdata16bits; if (huart->RxState == HAL_UART_STATE_BUSY_RX) { if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE)) { pdata8bits = NULL; pdata16bits = (uint16_t *) huart->pRxBuffPtr; *pdata16bits = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF); huart->pRxBuffPtr += 2U; } else { pdata8bits = (uint8_t *) huart->pRxBuffPtr; pdata16bits = NULL;
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) || ((huart->Init.WordLength == UART_WORDLENGTH_8B) && (huart->Init.Parity == UART_PARITY_NONE))) { *pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF); } else { *pdata8bits = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F); } huart->pRxBuffPtr += 1U; } if (--huart->RxXferCount == 0U) { __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE); __HAL_UART_DISABLE_IT(huart, UART_IT_PE); __HAL_UART_DISABLE_IT(huart, UART_IT_ERR); huart->RxState = HAL_UART_STATE_READY; huart->RxEventType = HAL_UART_RXEVENT_TC; if (huart->ReceptionType == HAL_UART_RECEPTION_TOIDLE) { huart->ReceptionType = HAL_UART_RECEPTION_STANDARD; ATOMIC_CLEAR_BIT(huart->Instance->CR1, USART_CR1_IDLEIE); if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE)) __HAL_UART_CLEAR_IDLEFLAG(huart); }
|
因此,调用HAL_UART_Receive_IT()后便会自动使能UART接收相关串口中断,同时准备好接受。在当接受的字符>=Size所指定的字符数之后,便会自动失能相关中断
在UART_Receive_IT()中,如果指定的字符数已经接收完成(RxXferCount == 0),则会调用HAL_UART_RxCpltCallback()这一公共回调函数。因为HAL库的库文件需要把硬件抽象成接口,因此直接改库文件内的代码不利于移植,用户的自定义中断回到函数写在HAL_UART_RxCpltCallback()内即可。此时UART已经将接收到的字符存在了pData指定的地址内,在HAL_UART_RxCpltCallback()内对pData内的数据进行处理即可。如果处理完成还需继续接收下一个数据,则再次调用HAL_UART_Receive_IT()来启用中断准备接收。
举个例子,假设我使用的通信协议规定每8byte数据为以通信帧,一数据帧传送数据位1byte,那么就可以把Size设置为8,然后每8byte数据接收完成之后就会调用用户自定义的回调函数HAL_UART_RxCpltCallback()函数来让用户处理数据。
下面是一个例程,实现使用中断接受UART数据,然后在收到换行符时把接收的数据发回去(省去了系统初始化部分)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| uint8_t ch; uint8_t count=0; uint8_t buffer[13]= {0}; int main(void){ HAL_UART_Receive_IT(&huart1,&ch,1); while(1); }
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){
buffer[count] = ch; count++; if(buffer[count-1] == '\n'){ HAL_UART_Transmit(huart, buffer, count , 100); memset(buffer, '\0', sizeof(buffer)); count = 0; } HAL_UART_Receive_IT(&huart1,&ch,1); }
|
SPI 通信