串口收发单元主要利用:数据寄存器DR
、发送引脚TX、接收引脚RX,以及状态寄存器SR
的数据寄存器为空TXE
标志、数据传输完成TC
标志、接收寄存器非空RXNE
标志。
typedef uint32_t HAL_UART_RxTypeTypeDef;
typedef struct __UART_HandleTypeDef
{
USART_TypeDef *Instance; /* 串口寄存器的基地址定义 */
UART_InitTypeDef Init; /* 串口初始化的通信参数(波特率/停止位/奇偶校验等) */
UART_AdvFeatureInitTypeDef AdvancedInit; /* 串口高级特性初始化参数 */
// 串口的I/O缓冲区
uint8_t *pTxBuffPtr; /* 串口发送缓冲区首地址 */
uint16_t TxXferSize; /* 串口待发送数据个数 */
__IO uint16_t TxXferCount; /* 串口发送数据计数器 */
uint8_t *pRxBuffPtr; /* 串口接收缓冲区首地址 */
uint16_t RxXferSize; /* 串口待接收数据的个数 */
__IO uint16_t RxXferCount; /* 串口接收数据计数器 */
uint16_t Mask; /* 串口RDR寄存器掩码 */
__IO HAL_UART_RxTypeTypeDef ReceptionType; /* 持续接收的类型 */
void (*RxISR)(struct __UART_HandleTypeDef *huart); /*!< Function pointer on Rx IRQ handler */
void (*TxISR)(struct __UART_HandleTypeDef *huart); /*!< Function pointer on Tx IRQ handler */
// 串口发送和接收的DMA通道句柄
DMA_HandleTypeDef *hdmatx; /* 串口发送的DMA通道句柄定义 */
DMA_HandleTypeDef *hdmarx; /* 串口接收的DMA通道句柄定义 */
// 串口工作状态
HAL_LockTypeDef Lock; /* 保护锁类型定义 */
__IO HAL_UART_StateTypeDef gState; /* 串口全局状态和发送状态信息 */
__IO HAL_UART_StateTypeDef RxState; /* 串口接收状态信息 */
__IO uint32_t ErrorCode; /* 串口错误代码 */
} UART_HandleTypeDef;
typedef struct
{
uint32_t BaudRate; /* 设置通信波特率 */
uint32_t WordLength; /* 设置通信数据的位数 */
uint32_t StopBits; /* 设置停止位 */
uint32_t Parity; /* 奇偶校验位(当奇偶校验启用时,计算的奇偶校验被插入到传输数据的MSB位置(当字长度设置为9位时,为第9位;当字长设置为8位时,为第8位) */
uint32_t Mode; /* 设置接收和发送模式是否使能 */
uint32_t HwFlowCtl; /* 设置硬件控制流是否使能 */
uint32_t OverSampling; /* 设置采样频率和信号传输频率的比例(最大f_PCLK/8) */
uint32_t OneBitSampling; /* 选择采样位方法:三采样位法/单采样位法 */
} UART_InitTypeDef;
#define UART_STOPBITS_0_5 USART_CR2_STOP_0 /* 0.5 stop bit */
#define UART_STOPBITS_1 0x00000000U /* 1 stop bit */
#define UART_STOPBITS_1_5 (USART_CR2_STOP_0 | USART_CR2_STOP_1) /* 1.5 stop bits */
#define UART_STOPBITS_2 USART_CR2_STOP_1 /* 2 stop bits */
硬件流控可以控制数据传输的进程,防止数据丢失,该功能主要在收发双方传输速度不匹配的时候使用。
#define UART_ONE_BIT_SAMPLE_DISABLE 0x00000000U /*!< One-bit sampling disable */
#define UART_ONE_BIT_SAMPLE_ENABLE USART_CR3_ONEBIT /*!< One-bit sampling enable */
void MX_USART2_UART_Init(void)
{
huart2.Instance = USART2; // 配置串口2
huart2.Init.BaudRate = 115200; // 波特率115200
huart2.Init.WordLength = UART_WORDLENGTH_8B; // 数据位为8位
huart2.Init.StopBits = UART_STOPBITS_1; // 停止位为1位
huart2.Init.Parity = UART_PARITY_NONE; // 无奇偶校验
huart2.Init.Mode = UART_MODE_TX_RX; // 使能接收和发送模式
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; // 不使能硬件控制流
huart2.Init.OverSampling = UART_OVERSAMPLING_16; // 16倍过采样
huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; // One-bit sampling disable
huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; // 不使用高级特性
if (HAL_UART_Init(&huart2) != HAL_OK) // 执行串口初始化操作
{
Error_Handler();
}
}
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(uartHandle->Instance==USART2)
{
__HAL_RCC_USART2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
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_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF4_USART2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
/**
* @brief 在轮询方式下发送一定数量的数据
* @note 1. 该函数连续发送数据,发送过程中通过判断TXE标志来发送下一个数据,通过判断TC标志来结束数据的发送。
* 2. 如果在等待时间内没有完成发送,则不再发送,返回超时标志
* @param huart UART handle.
* @param pData 指向发送数据缓冲区的指针 (u8 or u16 data elements).
* @param Size 待发送数据的个数(u8 or u16)
* @param Timeout 超时等待时间
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
返回值:
/**
* @brief 在轮询方式下接收一定数量的数据
* @note 1. 该函数连续接收数据,在接收过程中通过判断RXNE标志来接收新的数据
* 2.如果在等待时间内没有完成接收,则不再接收,返回超时标志
* @param huart UART handle.
* @param pData 指向接收数据缓冲区的指针(u8 or u16 data elements).
* @param Size 待接收数据的个数 (u8 or u16)
* @param Timeout 超时等待时间
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
返回值:
uint8_t rec_buf[10];
void usart_polling(void)
{
if (HAL_UART_Receive(&huart2, rec_buf, 5, 1000) == HAL_OK)
HAL_UART_Transmit(&huart2, rec_buf, 5, 1000);
}
int fputc(int ch, FILE* f)
{
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
int fgetc(FILE *f)
{
uint8_t ch;
HAL_UART_Receive(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
测试:
char data;
while (1) {
scanf("%c", &data);
if (data == 'y')
printf("receive y!\r\n");
else
printf("receive others!\r\n");
}
DMA
方式。/**
* @brief 在中断方式下发送一定数量的数据
* @param huart UART handle.
* @param pData 指向发送数据缓冲区的指针 (u8 or u16 data elements).
* @param Size 待发送数据的个数(u8 or u16)
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
注意:
TXEIE
和TCIE
(USART->CR1
),即使能发送数据寄存器空中断和发送完成中断。完成数据发送后,将会关闭发送中断,即清零TXEIE
和TCIE
,因此采用中断方式连续发送数据时,需要重复调用该函数,以便重新开启中断。HAL_UART_TxCpltCallback
进行后续处理即每发送一次数据进入一次中断,在中断中根据发送数据的个数判断数据是否发送完成。
/**
* @brief 在中断方式下接收一定数量的数据
* @param huart UART handle.
* @param pData 指向接收数据缓冲区的指针 (u8 or u16 data elements).
* @param Size 待接收数据的个数(u8 or u16)
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
注意:
RXNEIE
,即使能接收数据寄存器非空中断RXNEIE
。完成数据接收后,将会关闭接收中断,即清零RXNEIE
,因此采用中断方式连续发送数据时,需要重复调用该函数,以便重新开启中断。HAL_UART_RxCpltCallback
进行后续处理即每接收一次数据进入一次中断,在中断中根据接收数据的个数判断数据是否接收完成。
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
作为所有串口中断发生后的通用函数,会调用包括上述两回调函数的各种callback函数。
注意:
函数内部先判断中断类型,并清除对应的中断标志,最后调用回调函数完成对应的中断处理。
此函数有cubemx自动生成。
__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
用于处理所有串口的发送中断。
注意:
HAL_UART_IRQHandler
中调用,完成所有发生中断处理__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
用于处理所有串口的接收中断。
注意:
HAL_UART_IRQHandler
中调用,完成所有接收中断处理/** @brief Enable the specified UART interrupt.
* @param __HANDLE__ specifies the UART Handle.
* @param __INTERRUPT__ specifies the UART interrupt source to enable.
* This parameter can be one of the following values:
* @arg @ref UART_IT_WUF Wakeup from stop mode interrupt
* @arg @ref UART_IT_CM Character match interrupt
* @arg @ref UART_IT_CTS CTS change interrupt
* @arg @ref UART_IT_LBD LIN Break detection interrupt
* @arg @ref UART_IT_TXE Transmit Data Register empty interrupt
* @arg @ref UART_IT_TC Transmission complete interrupt
* @arg @ref UART_IT_RXNE Receive Data register not empty interrupt
* @arg @ref UART_IT_RTO Receive Timeout interrupt
* @arg @ref UART_IT_IDLE Idle line detection interrupt
* @arg @ref UART_IT_PE Parity Error interrupt
#define __HAL_UART_ENABLE_IT(__HANDLE__, __INTERRUPT__)
__HANDLE__
:串口句柄地址__INTERRUPT__
:串口中断类型失能宏:
__HAL_UART_DISABLE_IT(__HANDLE__, __INTERRUPT__)
/** @brief Check whether the specified UART flag is set or not.
* @param __HANDLE__ specifies the UART Handle.
* @param __FLAG__ specifies the flag to check.
* This parameter can be one of the following values:
* @arg @ref UART_FLAG_REACK Receive enable acknowledge flag
* @arg @ref UART_FLAG_TEACK Transmit enable acknowledge flag
* @arg @ref UART_FLAG_WUF Wake up from stop mode flag
* @arg @ref UART_FLAG_RWU Receiver wake up flag (if the UART in mute mode)
* @arg @ref UART_FLAG_SBKF Send Break flag
* @arg @ref UART_FLAG_CMF Character match flag
* @arg @ref UART_FLAG_BUSY Busy flag
* @arg @ref UART_FLAG_ABRF Auto Baud rate detection flag
* @arg @ref UART_FLAG_ABRE Auto Baud rate detection error flag
* @arg @ref UART_FLAG_CTS CTS Change flag
* @arg @ref UART_FLAG_LBDF LIN Break detection flag
* @arg @ref UART_FLAG_TXE Transmit data register empty flag
* @arg @ref UART_FLAG_TC Transmission Complete flag
* @arg @ref UART_FLAG_RXNE Receive data register not empty flag
* @arg @ref UART_FLAG_RTOF Receiver Timeout flag
* @arg @ref UART_FLAG_IDLE Idle Line detection flag
* @arg @ref UART_FLAG_ORE Overrun Error flag
* @arg @ref UART_FLAG_NE Noise Error flag
* @arg @ref UART_FLAG_FE Framing Error flag
* @arg @ref UART_FLAG_PE Parity Error flag
* @retval The new state of __FLAG__ (TRUE or FALSE).
*/
#define __HAL_UART_GET_FLAG(__HANDLE__, __FLAG__) (((__HANDLE__)->Instance->ISR & (__FLAG__)) == (__FLAG__))
__HANDLE__
:串口句柄地址__FLAG__
:串口标志#define __HAL_UART_CLEAR_IDLEFLAG(__HANDLE__) __HAL_UART_CLEAR_FLAG((__HANDLE__), UART_CLEAR_IDLEF)
其中__HAL_UART_CLEAR_FLAG
宏可以清除相应的标准:
/** @brief Clear the specified UART pending flag.
* @param __HANDLE__ specifies the UART Handle.
* @param __FLAG__ specifies the flag to check.
* This parameter can be any combination of the following values:
* @arg @ref UART_CLEAR_PEF Parity Error Clear Flag
* @arg @ref UART_CLEAR_FEF Framing Error Clear Flag
* @arg @ref UART_CLEAR_NEF Noise detected Clear Flag
* @arg @ref UART_CLEAR_OREF Overrun Error Clear Flag
* @arg @ref UART_CLEAR_IDLEF IDLE line detected Clear Flag
* @arg @ref UART_CLEAR_TCF Transmission Complete Clear Flag
* @arg @ref UART_CLEAR_RTOF Receiver Timeout clear flag
* @arg @ref UART_CLEAR_LBDF LIN Break Detection Clear Flag
* @arg @ref UART_CLEAR_CTSF CTS Interrupt Clear Flag
* @arg @ref UART_CLEAR_CMF Character Match Clear Flag
* @arg @ref UART_CLEAR_WUF Wake Up from stop mode Clear Flag
* @retval None
*/
#define __HAL_UART_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->Instance->ICR = (__FLAG__))
使能串口中断:
uint8_t Rxbuf[10];
uint8_t RxFlag = 0;
int main(void)
{
HAL_UART_Receive_IT(&huart2, (uint8_t *)&Rxbuf, 10); // 使能串口接收中断
while(1){
if (RxFlag){
RxFlag = 0;
printf("receive success!\r\n");
HAL_UART_Transmit_IT(&huart2, (uint8_t *)&Rxbuf, 10);
}
}
}
串口接收中断回调函数:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2)
{
RxFlag = 1;
HAL_UART_Receive_IT(&huart2, (uint8_t *)&Rxbuf, 10); // 使能串口接收中断
// UART_Start_Receive_IT(&huart2, (uint8_t *)&Rxbuf, 10);
}
}
HAL_UART_IRQHandler
中调用HAL_UART_RxCpltCallback
函数,其中HAL_UART_IRQHandler
函数会在数据接收完成后清除相应中断标志位,因此需要在回调函数中调用HAL_UART_Receive_IT
使能接收中断RXNEIE
,该函数最终会调用UART_Start_Receive_IT
。
处理流程:
UART_Receive_IT
函数是STM32F1/4调用方式,STM32L0采用函数指针huart->RxISR(huart)
调用。
uint8_t Rxbuf[4];
uint8_t RxFlag = 0;
uint8_t ErrFlag = 0;
int main(void)
{
HAL_UART_Receive_IT(&huart2, (uint8_t *)&Rxbuf, 10); // 使能串口接收中断
while(1){
if (RxFlag){
RxFlag = 0;
if (Rxbuf[0] == 0xaa && Rxbuf[3] == 0x55)
{
if (Rxbuf[1] == 0x01)
{
if (Rxbuf[2] == 0x00)
{
BOARD_LED_OFF;
printf("LED OFF!\r\n");
}
else if (Rxbuf[2] == 0x01)
{
BOARD_LED_ON;
printf("LED ON!\r\n");
}
else
ErrFlag = 1;
}
else
ErrFlag = 1;
}
else
ErrFlag = 1;
if (ErrFlag){
ErrFlag = 0;
printf("error!\r\n");
}
memset(Rxbuf, 0, 4);
}
}
}
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART2)
{
RxFlag = 1;
UART_Start_Receive_IT(&huart2, (uint8_t *)&Rxbuf, 4); // 使能串口接收中断
}
}
DMA数据传输的4个要素:
65535
,数据宽度通过DMA->CCR MSIZE[1:0]
和PSIZE[1:0]
配置)中断标志:(硬件置位)
优先级:
软件阶段:通过DMA_CRRX: PL[1:0
]配置
硬件阶段:通道编号小的优先级大;DMA1的优先级高于DMA2的优先级
数据流是数据传输的通路,可以完成以下传输:
DMA_CCRx
寄存器DIR位为0)DMA_CCRx
寄存器DIR位为1)指针增量:设置DMA_CCRx
寄存器中的PINC
和MINC
标志位,外设和存储器的指针每次传输后,下次传输的地址是前一个地址加上增量值,增量值取决于数据宽度(1/2/4)。(串口buff接收数据必须开启存储器增量模式,否则不能自增接收/发送)
DMA数据传输方式:
DMA_CNDTRx
寄存器中重新写入传输数目)ADC
扫描模式)。当激活循环模式后,每轮传输结束时,要传输的数据数量将自动用设置的初始值进行加载,并继续响应DMA请求。注意:存储器到存储器模式不能与循环模式同时使用。
DMA寄存器配置流程:
在DMA_CPARx
寄存器中设置外设寄存器的地址。发生外设数据传输请求时,这个地址将 是数据传输的源或目标。
在DMA_CMARx
寄存器中设置数据存储器的地址。发生外设数据传输请求时,传输的数 据将从这个地址读出或写入这个地址。
在DMA_CNDTRx
寄存器中设置要传输的数据量。在每个数据传输后,这个数值递减(只能在通道不工作(DMA_CCRx的EN=0
)时写入)
在DMA_CCRx
寄存器的PL[1:0]位中设置通道的优先级
在DMA_CCRx
寄存器中设置数据传输的方向、循环模式、外设和存储器的增量模式、外设和存储器的数据宽度、传输一半产生中断或传输完成产生中断。
设置DMA_CCRx
寄存器的ENABLE位,启动该通道。
DMA_ISR
和DMA_IFCR
寄存器分别读取和清除传输标志
寄存器的x = 1~7,共7个通道。
注意:对于DMA_CNDTRx
寄存器,当寄存器内容为0时,无论通道是否开启,都不会发生任何数据传输。数据传输结束后,寄存器的内容或者变为0,因此单次传输结束后,非循环模式需要重新向该寄存器写入传输数据的数量。
/**
* @brief DMA方式发送一定数量的数据
* @param huart UART handle.
* @param pData 指向发送数据缓冲区的指针 (u8 or u16 data elements).
* @param Size 待发送数据的个数(u8 or u16)
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
注意:数据发送完成后,可触发DMA中断,在中断中调用串口送发中断回调函数HAL_UART_TxCpltCallback
进行后续处理
/**
* @brief 在DMA方式下接收一定数量的数据
* @param huart UART handle.
* @param pData 指向接收数据缓冲区的指针 (u8 or u16 data elements).
* @param Size 待接收数据的个数(u8 or u16)
* @retval HAL status
*/
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
注意:数据发送完成后,可触发DMA中断,在中断中调用串口接收中断回调函数HAL_UART_RxCpltCallback
进行后续处理
在stm32xx_hal_dma.h
中
#define __HAL_DMA_GET_COUNTER(__HANDLE__) ((__HANDLE__)->Instance->CNDTR)
IDLE
将由硬件置1。如果串口控制寄存器CR1中的IDLEIE
位为1,将会触发空闲中断(IDLE中断);假设一帧数据由2个字符构成,分别是0xaa、0x55,传输的时序图如下:
0xaa和0x55数据帧传输过程中没有出现idle状态,传输结束后才出现。
【设计思路】
USART2_IRQHandler
中添加对IDLE中断的判断,该函数位于stm32f4xx_it.c
文件;IDLE
状态,将触发IDLE中断,调用IDLE中断回调函数,设置数据接收完成标志;HAL_UART_RxCpltCallback
,在回调函数中重新启动DMA传输。优先级为LOW、模式为普通模式Normal、数据宽度为字节Byte
DMA中断由CubeMX自动勾选。
USART2_RX的DMA1_CH5_CCR
寄存器位配置情况:
USART2_TX的DMA1_CH4_CCR
寄存器位配置情况:
MINC表示存储器增量模式,必须开启。
hdma_usart2_rx.Instance = DMA1_Channel5; // DMA1 通道5
hdma_usart2_rx.Init.Request = DMA_REQUEST_4; // 请求数字
hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设到存储器
hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; // 禁用外设指针增量
hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE; // 使用存储器指针增量
hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 外设字节宽度1
hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; // 存储器字节宽度1
hdma_usart2_rx.Init.Mode = DMA_NORMAL; // 普通模式
hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW; // 低优先级
if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
{
Error_Handler();
}
TX基本相同,不同之处为:
hdma_usart2_tx.Instance = DMA1_Channel4; // DMA1 通道4
hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; // 存储器到外设
STM32L0系列有个请求数字:
uint8_t Rxbuf[100];
uint8_t Rec_cnt = 0;
uint8_t RxFlag = 0;
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_DMA_Init();
MX_USART2_UART_Init();
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE); // 使能IDLE中断
HAL_UART_Receive_DMA(&huart2, (uint8_t *)&Rxbuf, 100); // 启动DMA接收
while (1)
{
if (RxFlag)
{
RxFlag = 0;
HAL_UART_Transmit_DMA(&huart2, (uint8_t *)&Rxbuf, Rec_cnt);
// 重新启动下一次DMA接收
HAL_UART_Receive_DMA(&huart2, (uint8_t *)&Rxbuf, 100);
}
}
}
串口中断服务例程空闲中断标志处理
void USART2_IRQHandler(void)
{
HAL_UART_IRQHandler(&huart2);
if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&huart2); // 清除串口空闲中断标志IDLEF
// 关闭所有的接收和发送DMA,清空CCR寄存器EN位 否则无法修改hdma_usart2_rx.Instance->CNDTR
// 但DMA串口发送和接收函数内部都会先清除CCR_EN,修改CNDTR后,再置位。实际上不加此函数会导致将上次接收的数据一起传输
HAL_UART_DMAStop(&huart2);
Rec_cnt = 100 - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
RxFlag = 1;
}
}
END