完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980
第65章 STM32H7的低功耗串口LPUART基础知识和HAL库API
本章节为大家讲解LPUART(Low power universal asynchronous receiver transmitter,低功耗通用异步收发器)的基础知识和对应的HAL库API。相比第29章的通用串口,增加了低功耗特性。
65.1 初学者重要提示
65.2 低功耗串口基础知识
65.3 低功耗串口的HAL库用法
65.4 源文件stm32h7xx_hal_uart.c
65.5 总结
65.1 初学者重要提示
- 特别注意,LPUART没有自己的HAL库驱动文件,是跟通用串口公用的驱动文件。
- 学习串口外设推荐从硬件框图开始了解基本的功能特性,然后逐步深入了解各种特性,这种方式方便记忆和以后查阅。而串口的通信学习,推荐看时序图。
65.2 低功耗串口基础知识
LPUART的全称是Low power universal synchronous asynchronous receiver transmitter,中文意思是低功耗通用异步收发器,简称LPUART。
65.2.1 低功耗串口的硬件框图
认识一个外设,最好的方式就是看它的框图,方便我们快速的了解串口的基本功能,然后再看手册了解细节。
通过这个框图,我们可以得到如下信息:
- IRQ Interface中断接口
用于实现中断方式的串口唤醒lpusart_wkup和串口的相关中断lpusart_it。
- DMA Interface DMA接口
实现串口发送lpuart_tx_dma和接收lpuart_rx_dma的DMA方式。
- COM Contronller串口控制器
串口相关的寄存器基本都在这部分。
- TxFIFO和RxFIFO
串口的发送和接收都支持了硬件FIFO功能。
- TX和RX引脚的互换功能
发送偏移寄存器(TX Shift Reg)和接收偏移寄存器(RX Shift Reg)与TX引脚,RX引脚之间弄了个交叉连接,这里的意思是支持了引脚互换功能,这样大家在设计PCB的时候就可以比较随性了,接反了也没有关系。
- 发送过程经过的寄存器
依次是LPUART_TDR -> TxFIFO ->Tx Shift Reg偏移寄存器 –> TX或者RX引脚。
- 接收经过的寄存器
依次是TX或者RX引脚-> Rx Shift Reg偏移寄存器->RxFIFO –>LPUART_RDR。
- 两个时钟lpuart_pclk和lpuart_ker_ck
这两个时钟是独立的,作用如下:
-
- lpuart_pclk
用于为外设总线提供时钟。
-
- lpuart_ker_ck
串口外设的时钟源。
65.2.2 低功耗串口的基本功能
STM32的串口功能很强大,支持太多的模式。我们只需关心我们最常用的特性即可。我们的串口驱动使用的串口中断+FIFO结构,没有使用DMA。因此我们只讨论和串口中断、串口常规参数有关的知识。
STM32串口的优越特性:(只列了举常用的)
- 各种波特率。硬件采用分数波特率发生器系统,可以设置各种需要的波特率
- 可编程数据字长度,支持7bit,8bit和9bit。
- 可配置的停止位。支持1或2个停止位。
- 发送器和接收器可以单独使能。比如GPS应用只需要串口接收,那么发送的GPIO就可以节省出来用作其他功能。
- 检测标志和中断:
- 接收缓冲器满,可产生中断。串口中断服务程序据此判断是否接收到数据。
- 发送缓冲器空,可产生中断。串口中断服务程序据此启动发送下一个数据。
- 传输结束标志,可产生中断。用于RS485通信,等最后一个字节发送完毕后,需要控制RS485收发器芯片切换为接收模式。
其它中断不常用,包括:CTS改变、LIN断开符检测、检测到总线为空闲(在DMA不定长接收方式会用到)、溢出错误、帧错误、噪音错误、校验错误。
65.2.3 低功耗串口的高级特性
H7系列的串口支持了一些高级特性,比如:
- 数据逻辑电平翻转。
- 低功耗特性。
- RX和TX引脚交换。
- MSB位先发送。
- 外接485的PHY芯片时,硬件支持收发切换,无需用户手动控制DE引脚。
相比第29章的通用串口,低功耗串口不支持超时接收和自适应波特率。
65.2.4 低功耗串口的数据帧格式
串口支持的帧格式如下(M和PCE都是LPUART_CR1寄存器的位,其中M位用于控制帧长度,PCE用于使能奇偶校验位):
这里特别注意奇偶校验位,用户在配置的时候可以选择奇校验和偶校验,校验位是占据的最高位。比如选择M=00,PCE=1,即7bit的数据位。
- 串口发送数据:
如果发送的7bit数据是111 0011,这个里面有奇数个1,那么选择偶校验的情况下,校验位 = 1,凑够偶数个1,而选择奇校验的情况下,校验位 = 0,因为已经是奇数个1。校验位不需要用户去计算,是硬件自动生成的。
- 串口接收数据:
根据用户设置的奇校验或者偶校验类型,串口硬件会对接收到的数据做校验,如果失败,LPUART_ISR寄存器的PE位会被置1。如果使能了对应的中断PEIE,那么失败的时候还会产生中断。
了解到帧格式后,再来看一下实际数据发送时,数据位的先后顺序:
65.2.5 低功耗串口的支持的时钟和波特率
低功耗定时器支持如下几种时钟:
这里我们重点关注PCLK3(D3PCLK1),HSI和LSE。
- LPUART时钟选择LSE(32768Hz)
最高速度是10922bps,最低8bps(计算方法3x < 32768 < 4096x,x表示波特率)。
- LPUART时钟选择HSI(64MHz)
最高值是21MHz,最小值15625bps(计算方法3x < 64MHz < 4096x,x表示波特率)。
- LPUART时钟选择D3PCLK1(100MHz)
最大值33Mbps,最小值24414bps(计算方法3x < 100MHz < 4096x,x表示波特率)。
65.2.6 低功耗串口的支持唤醒方式
低功耗串口的唤醒主要是通过接收数据来唤醒,具体唤醒的方如下:
- 检测到起始位唤醒。
- 检测到RXNE标志唤醒,即接收到数据。
- 检测到匹配地址时唤醒。
匹配地址支持7bit和4bit匹配两种方式,比如我们采用7bit匹配,设置地址是0x19,那么用户唤醒的时候要将最高bit设置为1,即发送地址0x99(0b1001 1001)才可以唤醒。
唤醒成功时的时序效果如下,特别注意唤醒信号位置:
唤醒失败时的时序效果:
65.2.7 低功耗串口发送时序图
这个时序图非常具有代表性,可以帮助大家很好的理解TC发送完成中断和TXE空中断。
65.2.8 单工,半双工和全双工通讯
单工:在一个单工的串行通讯系统中,一般至少有两根线(信号线和地线),数据传送只有一个方向,例如可以使用单工数据传送将数据从一个简单的数据监测系统传送到PC上。
半双工:在半双工串行通信系统中,一般同样要求至少有两根线。这里的数据传送是双向的。然而,同一个时刻只能为一个方向。在上面的数据监测的例子中做了一些变化,可以使用半双工通讯机制发送信息到嵌入式模块(来设置参数,比如采样率)。此外,在其他时候,可以使用这个种连接将嵌入式装置上的数据下载到PC中。
全双工:在一个全双工的串行通信系统中,一般要求至少有三根线(信号线A,信号线B和地线)。信号线A将传输一个方向上的数据,同时信号线B传送另一个方向上的数据。
65.3 低功耗串口的HAL库用法
串口的HAL库用法其实就是几个结构体变量成员的配置和使用,然后配置GPIO、时钟,并根据需要配置NVIC、中断和DMA。下面我们逐一展开为大家做个说明。
65.3.1 低功耗串口寄存器结构体USART_TypeDef
USART相关的寄存器是通过HAL库中的结构体USART_TypeDef定义的,在stm32h743xx.h中可以找到这个类型定义:
typedef struct { __IO uint32_t CR1; /*!< USART Control register 1, Address offset: 0x00 */ __IO uint32_t CR2; /*!< USART Control register 2, Address offset: 0x04 */ __IO uint32_t CR3; /*!< USART Control register 3, Address offset: 0x08 */ __IO uint32_t BRR; /*!< USART Baud rate register, Address offset: 0x0C */ __IO uint16_t GTPR; /*!< USART Guard time and prescaler register, Address offset: 0x10 */ uint16_t RESERVED2; /*!< Reserved, 0x12 */ __IO uint32_t RTOR; /*!< USART Receiver Time Out register, Address offset: 0x14 */ __IO uint16_t RQR; /*!< USART Request register, Address offset: 0x18 */ uint16_t RESERVED3; /*!< Reserved, 0x1A */ __IO uint32_t ISR; /*!< USART Interrupt and status register, Address offset: 0x1C */ __IO uint32_t ICR; /*!< USART Interrupt flag Clear register, Address offset: 0x20 */ __IO uint16_t RDR; /*!< USART Receive Data register, Address offset: 0x24 */ uint16_t RESERVED4; /*!< Reserved, 0x26 */ __IO uint16_t TDR; /*!< USART Transmit Data register, Address offset: 0x28 */ uint16_t RESERVED5; /*!< Reserved, 0x2A */ __IO uint32_t PRESC; /*!< USART clock Prescaler register, Address offset: 0x2C */ } USART_TypeDef;
这个结构体的成员名称和排列次序和CPU的USART寄存器是一 一对应的。
__IO表示volatile, 这是标准C语言中的一个修饰字,表示这个变量是非易失性的,编译器不要将其优化掉。core_m7.h 文件定义了这个宏:
#define __O volatile /*!< Defines ‘write only‘ permissions */ #define __IO volatile /*!< Defines ‘read / write‘ permissions */
下面我们看下LPUART的定义,在stm32h743xx.h文件。
#define PERIPH_BASE (0x40000000UL) #define D3_APB1PERIPH_BASE (PERIPH_BASE + 0x18000000UL) #define LPUART1_BASE (D3_APB1PERIPH_BASE + 0x0C00UL) #define LPUART1 ((USART_TypeDef *) LPUART1_BASE) <----- 展开这个宏,(USART_TypeDef *) 0x58000C00
我们访问LPUART1的CR1寄存器可以采用这种形式:LPUART1->CR1 = 0。
65.3.2 低功耗串口句柄结构体UART_HandleTypeDef
HAL库在USART_TypeDef的基础上封装了一个结构体UART_HandleTypeDef,定义如下:
typedef struct __UART_HandleTypeDef { USART_TypeDef *Instance; UART_InitTypeDef Init; UART_AdvFeatureInitTypeDef AdvancedInit; uint8_t *pTxBuffPtr; uint16_t TxXferSize; __IO uint16_t TxXferCount; uint8_t *pRxBuffPtr; uint16_t RxXferSize; __IO uint16_t RxXferCount; uint16_t Mask; uint32_t FifoMode; uint16_t NbRxDataToProcess; uint16_t NbTxDataToProcess; void (*RxISR)(struct __UART_HandleTypeDef *huart); void (*TxISR)(struct __UART_HandleTypeDef *huart); 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 (* RxFifoFullCallback)(struct __UART_HandleTypeDef *huart); void (* TxFifoEmptyCallback)(struct __UART_HandleTypeDef *huart); void (* MspInitCallback)(struct __UART_HandleTypeDef *huart); void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart); #endif } UART_HandleTypeDef;
注意事项:
条件编译USE_HAL_HRTIM_REGISTER_CALLBACKS用来设置使用自定义回调还是使用默认回调,此定义一般放在stm32h7xx_hal_conf.h文件里面设置:
#define USE_HAL_UAR_REGISTER_CALLBACKS 1
通过函数HAL_UART_RegisterCallback注册回调,取消注册使用函数HAL_UART_UnRegisterCallback。
这里重点介绍前三个参数,其它参数主要是HAL库内部使用和自定义回调函数。
- USART_TypeDef *Instance
这个参数是寄存器的例化,方便操作寄存器,比如使能串口的发送空中断。
SET_BIT(huart->Instance->CR1, USART_CR1_TXEIE)。
- UART_InitTypeDef Init
这个参数是用户接触最多的,用于配置串口的基本参数,像波特率、奇偶校验、停止位等。UART_InitTypeDef结构体的定义如下:
typedef struct { uint32_t BaudRate; /* 波特率 */ uint32_t WordLength; /* 数据位长度 */ uint32_t StopBits; /* 停止位 */ uint32_t Parity; /* 奇偶校验位 */ uint32_t Mode; /* 发送模式和接收模式使能 */ uint32_t HwFlowCtl; /* 硬件流控制 */ uint32_t OverSampling; /* 过采样,可以选择8倍和16倍过采样 */ uint32_t Prescaler; /* 串口分频 */ uint32_t FIFOMode; /* 串口FIFO使能 */ uint32_t TXFIFOThreshold; /* 发送FIFO的阀值 */ uint32_t RXFIFOThreshold; /* 接收FIFO的阀值 */ }UART_InitTypeDef;
- UART_AdvFeatureInitTypeDef AdvancedInit
这个参数用于配置串口的高级特性。具体支持的功能参数如下:
typedef struct { uint32_t AdvFeatureInit; /* 初始化的高级特性类别 */ uint32_t TxPinLevelInvert; /* Tx引脚电平翻转 */ uint32_t RxPinLevelInvert; /* Rx引脚电平翻转 */ uint32_t DataInvert; /* 数据逻辑电平翻转 */ uint32_t Swap; /* Tx和Rx引脚交换 */ uint32_t OverrunDisable; /* 接收超时检测禁止 */ uint32_t DMADisableonRxError; /* 接收出错,禁止DMA */ uint32_t AutoBaudRateEnable; /* 自适应波特率使能 */ uint32_t AutoBaudRateMode; /* 自适应波特率的四种检测模式选择 */ uint32_t MSBFirst; /* 发送或者接收数据时,高位在前 */ } UART_AdvFeatureInitTypeDef;
配置串口参数,其实就是配置结构体UART_HandleTypeDef的成员。比如下面配置为波特率115200,8个数据位,无奇偶校验,1个停止位。
UART_HandleTypeDef UartHandle; /* 配置如下: - 数据位 = 8 Bits - 停止位 = 1 bit - 奇偶校验位 = 无 - 波特率 = 115200bsp - 硬件流控制 (RTS 和 CTS 信号) */ UartHandle.Instance = LPUART1; UartHandle.Init.BaudRate = 115200; UartHandle.Init.WordLength = UART_WORDLENGTH_8B; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.OverSampling = UART_OVERSAMPLING_16; UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if(HAL_UART_Init(&UartHandle) != HAL_OK) { Error_Handler(); }
65.3.3 低功耗串口的底层配置(GPIO、时钟、中断等)
串口外设的基本参数配置完毕后还不能使用,还需要配置GPIO、时钟、中断等参数,比如下面配置使用引脚PA9和PA10。
/* LPUART1的GPIO PA9, PA10 */ #define LPUART1_CLK_ENABLE() __HAL_RCC_LPUART1_CLK_ENABLE() #define LPUART1_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define LPUART1_TX_GPIO_PORT GPIOA #define LPUART1_TX_PIN GPIO_PIN_9 #define LPUART1_TX_AF GPIO_AF3_LPUART #define LPUART1_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE() #define LPUART1_RX_GPIO_PORT GPIOA #define LPUART1_RX_PIN GPIO_PIN_10 #define LPUART1_RX_AF GPIO_AF3_LPUART /* ********************************************************************************************************* * 函 数 名: InitHardUart * 功能说明: 配置串口的硬件参数和底层 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void InitHardUart(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_PeriphCLKInitTypeDef RCC_PeriphClkInit; #if LPUART1_FIFO_EN == 1 /* 使能 GPIO TX/RX 时钟 */ LPUART1_TX_GPIO_CLK_ENABLE(); LPUART1_RX_GPIO_CLK_ENABLE(); /* 使能 USARTx 时钟 */ LPUART1_CLK_ENABLE(); /* 配置TX引脚 */ GPIO_InitStruct.Pin = LPUART1_TX_PIN; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = LPUART1_TX_AF; HAL_GPIO_Init(LPUART1_TX_GPIO_PORT, &GPIO_InitStruct); /* 配置RX引脚 */ GPIO_InitStruct.Pin = LPUART1_RX_PIN; GPIO_InitStruct.Alternate = LPUART1_RX_AF; HAL_GPIO_Init(LPUART1_RX_GPIO_PORT, &GPIO_InitStruct); /* 配置NVIC the NVIC for UART */ HAL_NVIC_SetPriority(LPUART1_IRQn, 0, 1); HAL_NVIC_EnableIRQ(LPUART1_IRQn); /* 配置波特率、奇偶校验 */ bsp_SetLPUartParam(LPUART1, LPUART1_BAUD, UART_PARITY_NONE, UART_MODE_TX_RX); SET_BIT(LPUART1->ICR, USART_ICR_TCCF); /* 清除TC发送完成标志 */ SET_BIT(LPUART1->RQR, USART_RQR_RXFRQ); /* 清除RXNE接收标志 */ SET_BIT(LPUART1->CR1, USART_CR1_RXNEIE); /* 使能PE. RX接受中断 */ #endif }
总结下来就是以下几点:
- 配置GPIO引脚时钟。
- 配置LPUART时钟。
- 配置LPUART的发送和接收引脚。
- 通过NVIC配置中断。
- 配置波特率,奇偶校验等,在上一小节有讲。
- 清除TC和RXNE标志,使能接收中断。
关于这个底层配置有以下几点要着重说明下:
- 串口发送和接收引脚的复用模式选择已经被HAL库定义好,放在了stm32h7xx_hal_gpio_ex.h文件里面。比如串口1有两个复用
#define GPIO_AF3_LPUART ((uint8_t)0x03) /* LPUART Alternate Function mapping */ #define GPIO_AF8_LPUART ((uint8_t)0x08) /* LPUART Alternate Function mapping */
具体使用那个,要看数据手册,比如我们这里使用引脚PA9和PA10,对应的复用如下:
那么使用GPIO_AF3_LPUART1即可。
- 根据情况要清除TC发送完成标志和RXNE接收数据标志,因为这两个标志位在使能了串口后就已经置位,所以当用户使用了TC或者RX中断后,就会进入一次中断服务程序,这点要特别注意。
- HAL库有个自己的底层初始化回调函数HAL_UART_MspInit,是弱定义的,用户可以在其它的C文件里面实现,并将相对的底层初始化在里面实现。当用户调用HAL_UART_Init后,会在此函数里面调用HAL_UART_MspInit,对应的底层复位函数HAL_UART_MspDeInit是在函数HAL_UART_DeInit里面被调用的。
当然,用户也可以自己初始化,不限制必须在两个函数里面实现。
- 上面举的例子里面没有用到DMA,如果用到了DMA,也是要初始化的。
65.3.4 低功耗串口的状态标志清除问题
注,早前使用F1和F4时候,经常会有网友咨询为什么串口中断服务程序里面没有做清除标志。
下面我们介绍__HAL_USART_GET_FLAG函数。这个函数用来检查LPUART/USART标志位是否被设置。
/** @brief Check whether the specified USART flag is set or not. * @param __HANDLE__: specifies the USART Handle * @param __FLAG__: specifies the flag to check. * This parameter can be one of the following values: * @arg USART_FLAG_TXFT: TXFIFO threshold flag * @arg USART_FLAG_RXFT: RXFIFO threshold flag * @arg USART_FLAG_RXFF: RXFIFO Full flag * @arg USART_FLAG_TXFE: TXFIFO Empty flag * @arg USART_FLAG_REACK: Receive enable ackowledge flag * @arg USART_FLAG_TEACK: Transmit enable ackowledge flag * @arg USART_FLAG_BUSY: Busy flag * @arg USART_FLAG_TXE: Transmit data register empty flag * @arg USART_FLAG_TC: Transmission Complete flag * @arg USART_FLAG_RXNE: Receive data register not empty flag * @arg USART_FLAG_IDLE: Idle Line detection flag * @arg USART_FLAG_ORE: OverRun Error flag * @arg USART_FLAG_UDR: UnderRun Error flag * @arg USART_FLAG_NE: Noise Error flag * @arg USART_FLAG_FE: Framing Error flag * @arg USART_FLAG_PE: Parity Error flag * @retval The new state of __FLAG__ (TRUE or FALSE). */ #define __HAL_USART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->ISR & (__FLAG__)) == (__FLAG__))
USART_FLAG有如下几种取值:
请大家重点关注上表中红字部分,LPUART/USART标志是需要软件主动清零的。清零有两种方式:一种是调用__HAL_USART_CLEAR_FLAG函数,另一种是操作相关寄存器后自动清零。
/** @brief Clear the specified USART pending flag. * @param __HANDLE__: specifies the USART Handle. * @param __FLAG__: specifies the flag to check. * This parameter can be any combination of the following values: * @arg USART_FLAG_TXFT: TXFIFO threshold flag * @arg USART_FLAG_RXFT: RXFIFO threshold flag * @arg USART_FLAG_RXFF: RXFIFO Full flag * @arg USART_FLAG_TXFE: TXFIFO Empty flag * @arg USART_FLAG_REACK: Receive enable ackowledge flag * @arg USART_FLAG_TEACK: Transmit enable ackowledge flag * @arg USART_FLAG_WUF: Wake up from stop mode flag * @arg USART_FLAG_RWU: Receiver wake up flag (is the USART in mute mode) * @arg USART_FLAG_SBKF: Send Break flag * @arg USART_FLAG_CMF: Character match flag * @arg USART_FLAG_BUSY: Busy flag * @arg USART_FLAG_ABRF: Auto Baud rate detection flag * @arg USART_FLAG_ABRE: Auto Baud rate detection error flag * @arg USART_FLAG_RTOF: Receiver timeout flag * @arg USART_FLAG_LBD: LIN Break detection flag * @arg USART_FLAG_TXE: Transmit data register empty flag * @arg USART_FLAG_TC: Transmission Complete flag * @arg USART_FLAG_RXNE: Receive data register not empty flag * @arg USART_FLAG_IDLE: Idle Line detection flag * @arg USART_FLAG_ORE: OverRun Error flag * @arg USART_FLAG_NE: Noise Error flag * @arg USART_FLAG_FE: Framing Error flag * @arg USART_FLAG_PE: Parity Error flag * @retval The new state of __FLAG__ (TRUE or FALSE). */ #define __HAL_USART_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->Instance->ICR = (__FLAG__))
上面介绍的USART标志大部分能够设置为产生中断,也就是有对应的USART中断标志。我们只介绍几个串口驱动要用到的中断标志:
USART_IT_TXE:TXE:发送数据寄存器空(此时数据可能正在发送)。
USART_IT_TC:发送完成 。
USART_IT_RXNE:接收数据寄存器非空。
中断缺省都是关闭的,通过__HAL_USART_ENABLE_IT函数可以使能相应的中断标志。函数定义如下:
/** @brief Enable the specified USART interrupt. * @param __HANDLE__: specifies the USART Handle. * @param __INTERRUPT__: specifies the USART interrupt source to enable. * This parameter can be one of the following values: * @arg USART_IT_RXFF: RXFIFO Full interrupt * @arg USART_IT_TXFE: TXFIFO Empty interrupt * @arg USART_IT_RXFT: RXFIFO threshold interrupt * @arg USART_IT_TXFT: TXFIFO threshold interrupt * @arg USART_IT_TXE : Transmit Data Register empty interrupt * @arg USART_IT_TC : Transmission complete interrupt * @arg USART_IT_RXNE: Receive Data register not empty interrupt * @arg USART_IT_IDLE: Idle line detection interrupt * @arg USART_IT_PE : Parity Error interrupt * @arg USART_IT_ERR : Error interrupt(Frame error, noise error, overrun error) * @retval None */ #define __HAL_USART_ENABLE_IT(__HANDLE__, __INTERRUPT__) (((((uint8_t)(__INTERRUPT__)) >> 5U) == 1)? ((__HANDLE__)->Instance->CR1 |= (1U << ((__INTERRUPT__) & USART_IT_MASK))): \ ((((uint8_t)(__INTERRUPT__)) >> 5U) == 2)? ((__HANDLE__)->Instance->CR2 |= (1U << ((__INTERRUPT__) & USART_IT_MASK))): ((__HANDLE__)->Instance->CR3 |= (1U << ((__INTERRUPT__) & USART_IT_MASK))))
STM32一个串口的中断服务程序入口地址只有一个,进入中断服务程序后,我们需要判断是什么原因进入的中断,因此需要调用一个函数来检测中断标志。函数原型如下:
#define __HAL_USART_GET_IT(__HANDLE__, __IT__) ((__HANDLE__)->Instance->ISR & ((uint32_t)1 << ((__IT__)>> 0x08)))
中断处理完毕后,必须软件清除中断标志,否则中断返回后,会重入中断。清中断标志位的函数为:
#define __HAL_USART_CLEAR_IT(__HANDLE__, __IT_CLEAR__) ((__HANDLE__)->Instance->ICR = (uint32_t)(__IT_CLEAR__))
正如前面介绍的,不是所有的标志都需要用这个函数清零。
注意:操作串口的寄存器不限制必须要用HAL库提供的API,比如要操作寄存器CR1,直接调用LPUART1->CR1操作即可。
65.3.5 低功耗串口初始化流程总结
使用方法由HAL库提供:
第1步:定义UART_HandleTypeDef类型串口结构体变量,比如UART_HandleTypeDef huart。
第2步:使用函数HAL_UART_MspInit初始化串口底层,不限制一定要用此函数里面初始化,用户也可以自己实现。
- 使能串口时钟。
- 引脚配置。
a、使能串口所使用的GPIO时钟。
b、配置GPIO的复用模式。
- 如果使用中断方式函数HAL_UART_Transmit_IT和HAL_UART_Receive_IT需要做如下配置。
a、配置串口中断优先级。
b、使能串口中断。
- 串口中断的开关是通过函数__HAL_UART_ENABLE_IT() 和 __HAL_UART_DISABLE_IT()来实现,这两个函数被嵌套到串口的发送和接收函数中调用。
- 如果使用DMA方式函数HAL_UART_Transmit_DMA和HAL_UART_Receive_DMA需要做如下配置。
a、声明串口的发送和接收DMA结构体变量,注意发送和接收是独立的,如果都使用,那就都需要配置。
b、使能DMA接口时钟。
c、配置串口的发送和接收DMA结构体变量。
d、配置DMA发送和接收通道。
e、关联DMA和串口的句柄。
f、配置发送DMA和接收DMA的传输完成中断和中断优先级。
第3步:配置串口的波特率,位长,停止位,奇偶校验位,流控制和发送接收模式。
第4步:如果需要,可以编程高级特性,比如TX/RX交换引脚,自动波特率检测。通过第1步串口结构体变量huart的结构体成员AdvancedInit来设置。
第5步:串口初始化调用的函数HAL_UART_Init初始化。
第6步:根据需要可以做动态注册回调。
首先使能宏定义USE_HAL_UART_REGISTER_CALLBACKS。
然后调用函数HAL_UART_RegisterCallback() 就可以注册如下回调函数:
(+) TxHalfCpltCallback
(+) TxCpltCallback
(+) RxHalfCpltCallback
(+) RxCpltCallback
(+) ErrorCallback
(+) AbortCpltCallback
(+) AbortTransmitCpltCallback
(+) AbortReceiveCpltCallback
(+) WakeupCallback
(+) RxFifoFullCallback
(+) TxFifoEmptyCallback
(+) MspInitCallback
(+) MspDeInitCallback
函数HAL_UART_UnRegisterCallback允许取消注册的回调函数如下:
(+) TxHalfCpltCallback
(+) TxCpltCallback
(+) RxHalfCpltCallback
(+) RxCpltCallback
(+) ErrorCallback
(+) AbortCpltCallback
(+) AbortTransmitCpltCallback
(+) AbortReceiveCpltCallback
(+) WakeupCallback
(+) RxFifoFullCallback
(+) TxFifoEmptyCallback
(+) MspInitCallback
(+) MspDeInitCallback
关于动态注册回调函数注意以下几点:
- 默认情况下,HAL_UART_Init调用后将使用默认的弱定义回调,如果用户注册了回调,将使用用户设置的。
- 回调函数只能在HAL_UART_STATE_READY状态下才可以注册/注销。
- 回调函数MspInit和MspDeInit除外,这两个函数可以在HAL_HRTIM_STATE_READY 或 HAL_HRTIM_STATE_RESET状态下注册,这样的话,用户调用函数HAL_HRTIM_DeInit()或者HAL_HRTIM_Init()时,就可以在其函数内运行MspInit/DeInit。
- 用户可以在调用HAL_HRTIM_DeInit()或者HAL_HRTIM_Init()之前调用HAL_HRTIM_RegisterCallback()为MspInit/MspDeInit注册回调。
65.4 低功耗源文件stm32h7xx_hal_uart.c
此文件涉及到的函数较多,这里把几个常用的函数做个说明:
- HAL_UART_Init
- HAL_UART_Transmit
- HAL_UART_Receive
- HAL_UART_Transmit_IT
- HAL_UART_Receive_IT
- HAL_UART_Transmit_DMA
- HAL_UART_Receive_DMA
其实V7开发板设计的低功耗串口FIFO驱动文件bsp_lpuart_fifo.c仅用到了函数HAL_UART_Init,其它函数都没有用到,不过这里也为大家做个说明。
65.4.1 函数HAL_UART_Init
函数原型:
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart) { /* 省略 */ if(huart->gState == HAL_UART_STATE_RESET) { huart->Lock = HAL_UNLOCKED; /* 初始化硬件: GPIO, CLOCK */ HAL_UART_MspInit(huart); } huart->gState = HAL_UART_STATE_BUSY; /* 禁止串口 */ __HAL_UART_DISABLE(huart); /* 配置串口参数 */ if (UART_SetConfig(huart) == HAL_ERROR) { return HAL_ERROR; } /* 配置串口高级特性 */ if (huart->AdvancedInit.AdvFeatureInit != UART_ADVFEATURE_NO_INIT) { UART_AdvFeatureConfig(huart); } /* 清寄存器的一些标志位 */ CLEAR_BIT(huart->Instance->CR2, (USART_CR2_LINEN | USART_CR2_CLKEN)); CLEAR_BIT(huart->Instance->CR3, (USART_CR3_SCEN | USART_CR3_HDSEL | USART_CR3_IREN)); /* 使能串口 */ __HAL_UART_ENABLE(huart); return (UART_CheckIdleState(huart)); }
函数描述:
此函数用于初始化串口的基础特性和高级特性。
函数参数:
- 第1个参数是UART_HandleTypeDef类型结构体指针变量,用于配置要初始化的参数。
- 返回值,返回HAL_TIMEOUT表示超时,HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示串口忙,正在使用中。
注意事项:
1、函数HAL_UART_MspInit用于初始化USART的底层时钟、引脚等功能。需要用户自己在此函数里面实现具体的功能。由于这个函数是弱定义的,允许用户在工程其它源文件里面重新实现此函数。当然,不限制一定要在此函数里面实现,也可以像早期的标准库那样,用户自己初始化即可,更灵活些。
2、如果形参huart的结构体成员gState没有做初始状态,这个地方就是个坑。特别是用户搞了一个局部变量UART_HandleTypeDef UartHandle。
对于局部变量来说,这个参数就是一个随机值,如果是全局变量还好,一般MDK和IAR都会将全部变量初始化为0,而恰好这个 HAL_UART_STATE_RESET = 0x00U。
解决办法有三
方法1:用户自己初始串口和涉及到的GPIO等。
方法2:定义UART_HandleTypeDef UartHandle为全局变量。
方法3:下面的方法
if(HAL_UART_DeInit(&UartHandle) != HAL_OK) { Error_Handler(); } if(HAL_UART_Init(&UartHandle) != HAL_OK) { Error_Handler(); }
3、注意串口的中断状态寄存器USART_ISR复位后,TC发送完成状态和RXNE接收状态都被置1,如果用户使能这两个中断前,最好优先清除中断标志。
使用举例:
UART_HandleTypeDef UartHandle; /* USART3工作在UART模式 */ /* 配置如下: - 数据位 = 8 Bits - 停止位 = 1 bit - 奇偶校验位 = 无 - 波特率 = 115200bsp - 硬件流控制 (RTS 和 CTS 信号) */ UartHandle.Instance = USART3; UartHandle.Init.BaudRate = 115200; UartHandle.Init.WordLength = UART_WORDLENGTH_8B; UartHandle.Init.StopBits = UART_STOPBITS_1; UartHandle.Init.Parity = UART_PARITY_NONE; UartHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; UartHandle.Init.Mode = UART_MODE_TX_RX; UartHandle.Init.OverSampling = UART_OVERSAMPLING_16; UartHandle.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if(HAL_UART_Init(&UartHandle) != HAL_OK) { Error_Handler(); }
65.4.2 函数HAL_UART_Transmit
函数原型:
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) { /* 省略 */ if(huart->gState == HAL_UART_STATE_READY) { /* 省略 */ while(huart->TxXferCount > 0U) { huart->TxXferCount--; /* 等待发送空中断标志 */ if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } } /* 等待发送完成中断 */ if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } /* 省略 */ return HAL_OK; } else { return HAL_BUSY; } }
函数描述:
此函数以查询的方式发送指定字节。看源码的话,程序里面最重要的就是上面代码中置红的两个标志,发送空标志和发送完成标志。发送空标志表示发送数据寄存器为空,数据还在移位寄存器里面,而发送完成标志表示数据已经从移位寄存器发送出去。
函数参数:
- 第1个参数是UART_HandleTypeDef类型结构体指针变量。
- 第2个参数是要发送的数据地址。
- 第3个参数是要发送的数据大小,单位字节。
- 第4个参数是溢出时间,单位ms。
- 返回值,返回HAL_TIMEOUT表示超时,HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示串口忙,正在使用中。
使用举例:
/* ********************************************************************************************************* * 函 数 名: fputc * 功能说明: 重定义putc函数,这样可以使用printf函数从串口1打印输出 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int fputc(int ch, FILE *f) { HAL_UART_Transmit(&UartHandle, (uint8_t *)&ch, 1, HAL_MAX_DELAY); return ch; }
65.4.3 函数HAL_UART_Receive
函数原型:
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout) { /* 省略 */ if(huart->RxState == HAL_UART_STATE_READY) { /* 省略 */ while(huart->RxXferCount > 0U) { huart->RxXferCount--; if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_RXNE, RESET, tickstart, Timeout) != HAL_OK) { return HAL_TIMEOUT; } } /* 省略 */ return HAL_OK; } else { /* 省略 */ return HAL_BUSY; } }
函数描述:
此函数以查询的方式接收指定字节。这个函数相对比较好理解,就是等待上面程序中的RXNE标志,置位了表示接收数据寄存器已经存入数据。
函数参数:
第1个参数是UART_HandleTypeDef类型结构体指针变量。
第2个参数是要接收的数据地址。
第3个参数是要接收的数据大小,单位字节。
第4个参数是溢出时间,单位ms。
返回值,返回HAL_TIMEOUT表示超时,HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示串口忙,正在使用中。
使用举例:
/* ********************************************************************************************************* * 函 数 名: fgetc * 功能说明: 重定义getc函数,这样可以使用scanff函数从串口1输入数据 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int fgetc(FILE *f) { int ret; HAL_UART_Receive(&UartHandle, (uint8_t *)&ret, 1, HAL_MAX_DELAY); return ret; }
65.4.4 函数HAL_UART_Transmit_IT
函数原型:
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { if(huart->gState == HAL_UART_STATE_READY) { if((pData == NULL ) || (Size == 0U)) { return HAL_ERROR; } __HAL_LOCK(huart); huart->pTxBuffPtr = pData; huart->TxXferSize = Size; huart->TxXferCount = Size; huart->ErrorCode = HAL_UART_ERROR_NONE; huart->gState = HAL_UART_STATE_BUSY_TX; __HAL_UNLOCK(huart); if (READ_BIT(huart->Instance->CR1, USART_CR1_FIFOEN) != RESET) { /* 使能FIFO发送中断 */ SET_BIT(huart->Instance->CR3, USART_CR3_TXFTIE); } else { /* 使能发空中断 */ SET_BIT(huart->Instance->CR1, USART_CR1_TXEIE); } return HAL_OK; } else { return HAL_BUSY; } }
函数描述:
此函数以中断的方式发送指定字节,可以选择使能FIFO中断方式或者发送空中断方式。具体数据的发送是在中断处理函数HAL_UART_IRQHandler里面实现。
函数参数:
- 第1个参数是UART_HandleTypeDef类型结构体指针变量。
- 第2个参数是要发送的数据地址。
- 第3个参数是要发送的数据大小,单位字节。
- 返回值,返回HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示串口忙,正在使用中。
使用举例:
使用中断方式要使能串口中断,此贴有完整例子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86245 。
UART_HandleTypeDef UartHandle; uint8_t s_ucBuf[5]; /* 数据发送 */ HAL_UART_Transmit_IT(&UartHandle, s_ucBuf, 1); HAL_UART_Transmit_IT(&UartHandle, (uint8_t*)"KEY_DOWN_K1\r\n", 13);
65.4.5 函数HAL_UART_Receive_IT
函数原型:
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { if(huart->RxState == HAL_UART_STATE_READY) { if((pData == NULL ) || (Size == 0U)) { return HAL_ERROR; } __HAL_LOCK(huart); huart->pRxBuffPtr = pData; huart->RxXferSize = Size; huart->RxXferCount = Size; UART_MASK_COMPUTATION(huart); huart->ErrorCode = HAL_UART_ERROR_NONE; huart->RxState = HAL_UART_STATE_BUSY_RX; __HAL_UNLOCK(huart); /* 使能错误中断: (Frame error, noise error, overrun error) */ SET_BIT(huart->Instance->CR3, USART_CR3_EIE); if (READ_BIT(huart->Instance->CR1, USART_CR1_FIFOEN) != RESET) { /* 使能奇偶校验失败中断 */ SET_BIT(huart->Instance->CR1, USART_CR1_PEIE); /* 使能FIFO接收中断 */ SET_BIT(huart->Instance->CR3, USART_CR3_RXFTIE); } else { /* 使能奇偶校验失败中断和接收中断 */ SET_BIT(huart->Instance->CR1, USART_CR1_PEIE | USART_CR1_RXNEIE); } return HAL_OK; } else { return HAL_BUSY; } }
函数描述:
此函数以中断的方式接收指定字节,可以选择使能FIFO中断方式或者普通中断方式,两种方式使能了奇偶校验中断失败和错误中断。具体数据的接收是在中断处理函数HAL_UART_IRQHandler里面实现。
函数参数:
- 第1个参数是UART_HandleTypeDef类型结构体指针变量。
- 第2个参数是要接收的数据地址。
- 第3个参数是要接收的数据大小,单位字节。
- 返回值,返回HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示串口忙,正在使用中。
使用举例:
使用中断方式要使能串口中断,此贴有完整例子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86245 。
UART_HandleTypeDef UartHandle; uint8_t s_ucBuf[5]; /* 数据接收*/ HAL_UART_Receive_IT(&UartHandle, s_ucBuf, 1);
65.4.6 函数HAL_UART_Transmit_DMA
函数原型:
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { if(huart->gState == HAL_UART_STATE_READY) { if((pData == NULL ) || (Size == 0U)) { return HAL_ERROR; } __HAL_LOCK(huart); huart->pTxBuffPtr = pData; huart->TxXferSize = Size; huart->TxXferCount = Size; huart->ErrorCode = HAL_UART_ERROR_NONE; huart->gState = HAL_UART_STATE_BUSY_TX; /* 注册各种DMA回调函数 */ huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt; huart->hdmatx->XferHalfCpltCallback = UART_DMATxHalfCplt; huart->hdmatx->XferErrorCallback = UART_DMAError; huart->hdmatx->XferAbortCallback = NULL; /* 使能串口发送DMA通道 */ HAL_DMA_Start_IT(huart->hdmatx, (uint32_t)huart->pTxBuffPtr, (uint32_t)&huart->Instance->TDR, Size); /* 清除传输TC完成标志 */ __HAL_UART_CLEAR_FLAG(huart, UART_CLEAR_TCF); __HAL_UNLOCK(huart); /* 使能串口发送DMA传输 */ SET_BIT(huart->Instance->CR3, USART_CR3_DMAT); return HAL_OK; } else { return HAL_BUSY; } }
函数描述:
此函数以DMA的方式发送指定字节。这里是用的DMA中断方式HAL_DMA_Start_IT进行的发送。所以使用此函数的话,不要忘了写DMA中断服务程序。而且DMA的配置也是需要用户实现的,可以直接在函数HAL_UART_MspInit里面实现,也可以放在其它位置。
函数参数:
- 第1个参数是UART_HandleTypeDef类型结构体指针变量。
- 第2个参数是要发送的数据地址。
- 第3个参数是要发送的数据大小,单位字节。
- 返回值,返回HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示串口忙,正在使用中。
使用举例:
使用DMA方式,此贴有完整例子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86271 。
65.4.7 函数HAL_UART_Receive_DMA
函数原型:
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) { if(huart->RxState == HAL_UART_STATE_READY) { if((pData == NULL ) || (Size == 0U)) { return HAL_ERROR; } __HAL_LOCK(huart); huart->pRxBuffPtr = pData; huart->RxXferSize = Size; huart->ErrorCode = HAL_UART_ERROR_NONE; huart->RxState = HAL_UART_STATE_BUSY_RX; /* 注册各种DMA回调函数 */ huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt; huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt; huart->hdmarx->XferErrorCallback = UART_DMAError; huart->hdmarx->XferAbortCallback = NULL; /* 使能串口接收DMA通道 */ HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->RDR, (uint32_t)huart->pRxBuffPtr, Size); __HAL_UNLOCK(huart); /* 使能串口校验错误中断 */ SET_BIT(huart->Instance->CR1, USART_CR1_PEIE); /* 使能串口错误中断:(Frame error, noise error, overrun error) */ SET_BIT(huart->Instance->CR3, USART_CR3_EIE); /* 使能串口接收DMA传输 */ SET_BIT(huart->Instance->CR3, USART_CR3_DMAR); return HAL_OK; } else { return HAL_BUSY; } }
函数描述:
此函数以DMA的方式接收指定字节。这里是用的DMA中断方式HAL_DMA_Start_IT进行的接收。所以使用此函数的话,不要忘了写DMA中断服务程序。而且DMA的配置也是需要用户实现的,可以直接在函数HAL_UART_MspInit里面实现,也可以放在其它位置。
函数参数:
- 第1个参数是UART_HandleTypeDef类型结构体指针变量。
- 第2个参数是要接收的数据地址。
- 第3个参数是要接收的数据大小,单位字节。
- 返回值,返回HAL_ERROR表示参数错误,HAL_OK表示发送成功,HAL_BUSY表示串口忙,正在使用中。
使用举例:
使用DMA方式,此贴有完整例子:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=86271 。
65.5 总结
本章节就为大家讲解这么多,涉及到的知识点和API函数比较多,需要花点时间消化,后面用到的多了,就可以熟练掌握了。