锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

【STM32】HAL库-串口USART

时间:2023-03-05 21:30:00 cr1mt交流功率固态继电器cr1u系列特殊型固态继电器

USART简介

通用同步异步收发器(USART)工业标准的使用提供了灵活的方法NRZ外部设备之间的异步串行数据格式进行全双工数据交换。

USART利用分数波特率发生器提供广泛的波特率选择。
波特率寄存器(USART_BRR),12位整数和4位小数

任何USART双向通信至少需要两只脚:
接收数据输入(RX)并发送数据输出(TX)

当发送器被激活而不发送数据时,TX引脚处于高电平
一开始,TX脚处于低电平,停止时处于高电平。

当发送器和接收器的使能位分别位置时,由共用波特率发生器驱动发送和接收。

在这里插入图片描述

空闲符号和端口符号

  • 空闲符号是一帧,全部由1组成。空闲帧后面跟着下一个数据帧的起始位置。(空闲帧包括停止位)

空闲符号被视为完全由1组成的完整数据帧,其次是包含数据的下一帧的开始位(1位数也包含停止位数)。

  • 断开符号是一帧,全部由0组成,断开帧后面有一个停止位。

断开符号被视为在帧周期内收到0(包括停止期间,也是0)。断开帧结束时,发送器插入1或2个停止位(‘1’)来响应起始位。


起始位为1,数据位为8,停止位为1:

  • 空闲帧包括停止位。

  • 断开帧为10位低电平,后跟停止。

发送(TX)器

配置步骤

  • 空发送数据寄存器(TXE)
  • 发送完成(TE)

串口数据发送过程:

将数据写入DR(字节大小),DR将数据复制到TDR中,TDR将数据复制到发送移位寄存器中,从LSB(最低有效位)一个发送到TX在引脚上,实现数据发送。
其中:
每次发送一个字节(发送数据寄存器)TDR为空)TXE标志将被定位。
发送完所有数据后TC标志将被定位。

接收器

USART可以根据USART_CR1的M位接收8位或9位的数据字
在USART在接收过程中,数据的最低有效性首先从RX脚移入接收数据寄存器。

空闲符号
当检测到一个空闲帧时,它的处理步骤与接收到的普通数据帧相同,但如果IDLEIE位被设置会产生中断。

配置步骤

  • 接收到的数据已准备好读取(RXNE)

串口数据接收流程:
接收移位寄存器从RX引脚接收数据,从最低有效数据(因为数据是先发送最低有效数据)开始接收数据,当接收到字节数据时,复制数据RDR寄存器和DR此时在寄存器中RXNE标志被置位,通过读取DR存储器获取接收到的字节数据。

分数波特率

波特率寄存器(USART_BRR),12位整数和4位小数
整数部分用12位二进制数表示,小数部分用4位表示。

波特率计算公式

设置波特率115200,fck = 36MHz,则USARTDIV = 19.5
整数部分为19 << 4 = 304
小数部分为0.5*16 = 8
USART_BRR寄存器值304 8=312=0x138

中断

注意:USART连接到同一中断向量的各种中断事件

demo

串口异步通信-阻塞发送-模仿printf发送

采用STM32F103C8T6单片机,KeilMDK5.32版本

串口异步通信,只打开启动方向,阻塞数据(模仿printf发送)。
PC13控制LED灯,
LED照明指示程序正常运行。

串口仿printf发送函数

#define USART1_SENDBUFF_MAX_BYTES 100U //串口1发送缓冲区 单位字节  /** * @brief UART 仿printf发送 * @param format 输出字符串 * @retval 返回字符总数 */ int USART1Printf(const char* format, ...) { 
          static char sendBuff[USART1_SENDBUFF_MAX_BYTES] = { 
         0 };//发送缓冲区  int bytes = 0;  va_list list;   va_start(list, format);  bytes = vsprintf(sendBuff, format, list
       
        )
        ;
        //格式化输入 
        va_end
        (list
        )
        ; 
        /* 发送之前清除标志位 */ 
        CLEAR_BIT
        (huart1
        .Instance
        ->SR
        , USART_SR_TC_Msk
        )
        ;
        //往TC位写入0来清除TC位 
        HAL_UART_Transmit
        (
        &huart1
        , 
        (
        void
        *
        )sendBuff
        , bytes
        , INFINITE
        )
        ;
        //阻塞式发送数据,发送等待时间为最大等待时间 
        return bytes
        ; 
        } 
       

HAL_UART_Transmit()是HAL库串口阻塞式发送函数,这个函数没有用到TXE与TC标志位,发送结束后也没有清除标志。

主程序中的代码

while (1)
  { 
        
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	HAL_Delay(1000);//延时1000毫秒
	USART1Printf("Hello World! %hhu\r\n", times);
	times++;
	PCout(13) = !PCin(13);

  }

STM32CubeMX配置

工程文件下载链接

串口异步通信-非阻塞式发送-仿printf发送

采用STM32F103C8T6单片机,KeilMDK5.32版本

串口异步通信,,仅开启发方向,非阻塞式发送数据(仿printf发送)。
PC13控制LED灯,LED灯的亮灭指示程序正常运行。

串口仿printf发送函数

#define USART_SENDBUFF_MAX_BYTES 100U //串口1发送缓冲区大小 单位字节
/** * @brief UART 仿printf发送 * @param huart 指向串口结构体的指针 * @param format 输出的字符串 * @retval 返回写入的字符总数 */
int USARTPrintf(UART_HandleTypeDef *huart, const char* format, ...)
{ 
        
	static char sendBuff[USART_SENDBUFF_MAX_BYTES] = { 
         0 };
	int bytes = 0;
	va_list list;

	va_start(list, format);
	bytes = vsprintf(sendBuff, format, list);
	va_end(list);

	/* 发送之前清除标志位 */
	CLEAR_BIT(huart->Instance->SR, USART_SR_TC_Msk);//往TC位写入0来清除TC位
	HAL_UART_Transmit_IT(huart, (void*)sendBuff, bytes);//非阻塞式发送数据,开启TXE中断,再全部数据都写入DR寄存器并发送后将关闭TXE中断,开启TC中断

	return bytes;
}

HAL_UART_Transmit_IT()函数开启了TXE中断,并在最后一个字节发送结束之后开启TC中断。如下图

HAL_UART_IRQHandler()函数

这里有2个函数需要注意

  • UART_Transmit_IT()是每发送完一个字节,则进入该函数里面,往DR寄存器写入下一个字节,如果是最后一个字节则关闭TXE中断开启TC中断。

  • UART_EndTransmit_IT()全部数据发送完毕,关闭TC中断,进入发送完毕回调函数

    STM32CubeMX配置

发送完成回调函数HAL_UART_TxCpltCallback()
在进入该函数前,TXE与TC中断已关闭。

在发生串口回调函数(TXE/TC)时,并且全部数据发送完毕,HAL_UART_IRQHandler()调用UART_EndTransmit_IT(),UART_EndTransmit_IT()在关闭TC中断后进入HAL_UART_TxCpltCallback()。

/** * @brief Tx Transfer completed callbacks. * @param huart Pointer to a UART_HandleTypeDef structure that contains * the configuration information for the specified UART module. * @retval None */
__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{ 
        
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function should not be modified, when the callback is needed, the HAL_UART_TxCpltCallback could be implemented in the user file */
}

工程文件下载链接

串口异步通信-非阻塞式接收数据

HAL库的阻塞式接收数据函数HAL_UART_Receive()
使用示例HAL_UART_Receive(&huart1, (void*)receiveBuff, sizeof(receiveBuff), INFINITE);

/** * @brief Receives an amount of data in blocking mode. * @note When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01), * the received data is handled as a set of u16. In this case, Size must indicate the number * of u16 available through pData. * @param huart Pointer to a UART_HandleTypeDef structure that contains * the configuration information for the specified UART module. * @param pData Pointer to data buffer (u8 or u16 data elements). * @param Size Amount of data elements (u8 or u16) to be received. * @param Timeout Timeout duration * @retval HAL status */
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);

非阻塞式接收数据函数HAL_UART_Receive_IT()
该函数会开启以下中断

  • 奇偶检验错(PE)
  • 帧错误、噪声错误、溢出错误(NE或ORT或FE)
  • UART数据寄存器非空中断(RXNE)

并且会将huart的接收类型设置为HAL_UART_RECEPTION_STANDARD
接收类型值有

  • HAL_UART_RECEPTION_STANDARD标准接收
  • HAL_UART_RECEPTION_TOIDLE接收至完成或闲置事件
/** * @brief Receives an amount of data in non blocking mode. * @note When UART parity is not enabled (PCE = 0), and Word Length is configured to 9 bits (M1-M0 = 01), * the received data is handled as a set of u16. In this case, Size must indicate the number * of u16 available through pData. * @param huart Pointer to a UART_HandleTypeDef structure that contains * the configuration information for the specified UART module. * @param pData Pointer to data buffer (u8 or u16 data elements). * @param Size Amount of data elements (u8 or u16) to be received. * @retval HAL status */
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

HAL_UART_IRQHandler()函数会调用UART_Receive_IT()进行串口接收中断处理。UART_Receive_IT()会将DR寄存器中的数据复制到串口接收缓存区中,如果接收数据的字节数目满足指定的数目,则会关闭中断(RXNE,NE或ORT或FE,PE),根据接收类型进入相应的接收完成回调函数中。

下面示例串口非阻塞式接收数据
采用STM32F103C8T6单片机,KeilMDK5.32版本

串口异步通信,开启收发方向,阻塞式发送(仿printf发送);非阻塞式接收数据。
PC13控制LED灯,LED灯的亮灭指示接收到数据。

程序初始化完成之后,开启接收中断。
在接收完成回调函数中,重新开启接收中断(因为在进入接收回调函数前,所有与接收相关的中断已经关闭)

STM32CubeMX配置


接收完成回调函数HAL_UART_RxCpltCallback()

在进入该函数前,以下中断已关闭。

  • 奇偶检验错(PE)
  • 帧错误、噪声错误、溢出错误(NE或ORT或FE)
  • UART数据寄存器非空中断(RXNE)
extern char receiveBuff[15];
/** * @brief Rx Transfer completed callbacks. * @param huart Pointer to a UART_HandleTypeDef structure that contains * the configuration information for the specified UART module. * @retval None */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{ 
        
	if(huart == &huart1)
	{ 
        
		PCout(13) = !PCin(13);//;LED亮灭翻转 指示接收到数据
		HAL_UART_Receive_IT(&huart1, (void*)receiveBuff, sizeof(receiveBuff));
	}
}

注意一下:主要当全部数据都接收完毕(比如,想要收到15个字节的数据,当收到第15个字节的数据的时候)才会进入接收完成回调函数(在该函数中重新开启串口空闲接收)HAL_UART_RxCpltCallback()

工程文件下载链接

串口异步通信-串口空闲中断接收,未使用DMA

采用STM32F103C8T6单片机,KeilMDK5.32版本

串口异步通信,开启收发方向,阻塞式发送(仿printf发送);非阻塞式接收数据。
PC13控制LED灯,LED灯的亮灭指示接收到数据。

程序初始化完成之后,开启接收空闲中断。
在接收空闲回调函数中,重新开启接收空闲中断(因为在进入接收回调函数前,所有与接收相关的中断已经关闭)

串口空闲中断:检测到有数据被接收后,当总线上在一个字节的时间内没有再接收到数据的时候,触发串口空闲中断。

调用HAL_UARTEx_ReceiveToIdle_IT()来使用串口空闲中断,该函数会将串口接收类型设置为HAL_UART_RECEPTION_TOIDLE,开启RXNE,NE或ORT或FE,PE和串口空闲中断(IDLE)

在RXNE中断中,将数据(一个字节)复制到串口接收缓冲区中

串口空闲回调函数
在该函数中将LED亮灭取反,并重新开启串口空闲接收

extern char receiveBuff[15];
/** * @brief Reception Event Callback (Rx event notification called after use of advanced reception service). * @param huart UART handle * @param Size Number of data available in application reception buffer (indicates a position in * reception buffer until which, data are available) * @retval None */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{ 
        
	if(huart == &huart1)
	{ 
        
		PCout(13) = !PCin(13);//;LED亮灭翻转 指示接收到数据
		HAL_UARTEx_ReceiveToIdle_IT(&huart1, (void*)receiveBuff, sizeof(receiveBuff));//重新开启串口空闲接收
	}
}

HAL_UART_IRQHandler()函数中有2处可以进入串口空闲回调函数

一处是UART_Receive_IT()
当数据全部接收完毕时(比如,想要收到15个字节的数据,实际也收到15个字节的数据),则进入串口空闲回调函数,在进入该函数前会将接收类型设置为HAL_UART_RECEPTION_STANDARD

另外一处是数据没有接收完毕(比如,想要收到15个字节的数据,实际收到少于15个字节的数据),也会进入串口空闲中断。

STM32CubeMX配置与非阻塞式接收数据demo一样。

工程文件下载链接

DMA式收发数据

利用DMA发送数据


注意:根据需要来确定是不是要DMA控制寄存器中开启存储器地址自增的位

TXE事件后,DMA传输指定地址上的数据到DR中和从DR中传输数据到TX引脚上是同时进行。

利用DMA接收数据


RXNE事件后,从RX引脚读出数据到DR寄存器中和DR寄存器中的数据复制的指定的地址中式同时进行的

串口异步通信-DMA式收发数据-仿printf发送

采用STM32F103C8T6单片机,KeilMDK5.32版本

串口异步通信,开启收发方向,DMA式收发数据(仿printf发送)。
收发的DMA不在循环模式下(单次)。
PC13控制LED灯,LED灯的亮灭指示接收到数据。
在STM32CubeMX中需要同时开启DMA与串口全局中断

DMA发送

调用HAL_UART_Transmit_DMA()来使用DMA发送数据,会开启DMA传输错误(TE),传输完成(TC)和传输完成一半(HT)中断。注意并没有开启串口的TC中断

一般用不到传输完成一半中断,如果不需要这个中断,可以将HAL_UART_Transmit_DMA()函数中 huart->hdmatx->XferHalfCpltCallback = UART_DMA_TxHalfCplt;
改为huart->hdmatx->XferHalfCpltCallback = NULL;即可

在DMA传输完成之后,开启串口的TC中断(UART_DMATransmitCplt()中开中断)

UART_DMATransmitCplt()函数如下

通过观察UART_DMATransmitCplt()函数源码,在DMA单次模式下,需要开启串口全局中断,在TC中断发生后,进入串口发送完成回调函数。

串口发送完成回调函数

/** * @brief Tx Transfer completed callbacks. * @param huart Pointer to a UART_HandleTypeDef structure that contains * the configuration information for the specified UART module. * @retval None */
__weak void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{ 
        
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function should not be modified, when the callback is needed, the HAL_UART_TxCpltCallback could be implemented in the user file */
}

DMA接收

调用HAL_UART_Receive_DMA()函数来使能串口DMA接收。
该函数会使能NE或ORT或FE,PE
并且也会开启DMA传输错误(TE),传输完成(TC)和传输完成一半(HT)中断
注意:并没有开启RXNE中断

在DMA传输完成之后进入中断服务函数后,
UART_DMAReceiveCplt()函数调用HAL_UART_RxCpltCallback()串口接收完成回调函数

与DMA发送类似,如果不需要DMA传输完成一半的中断可以更改源码UART_Start_Receive_DMA()

huart->hdmarx->XferHalfCpltCallback = NULL;		以前是UART_DMARxHalfCplt;

STM32CubeMX配置



接收回调函数中重新开启DMA接收(因为DMA处于单次模式下)

工程文件下载链接

串口异步通信-DMA式收发数据-仿printf发送-接收的DMA循环

采用STM32F103C8T6单片机,KeilMDK5.32版本

串口异步通信,开启收发方向,DMA式收发数据(仿printf发送)。
发的DMA不在循环模式下(单次);接收的DMA在循环模式下。
PC13控制LED灯,LED灯的亮灭指示接收到数据。
在STM32CubeMX中需要同时开启DMA与串口全局中断

在每次DMA传输完成之后,DMA中断服务函数中,检查DMA是否处于循环模式,如果是则不关闭传输完成TC和传输完成一半HT中断

中断服务函数调用UART_DMAReceiveCplt()函数,再次判断串口接收类型,根据类型进入相应的传输完成回调函数。

调用HAL_UART_Receive_DMA()函数,该函数不会开启RXNE中断。

因为DMA是循环模式,所有不需要重新开启接收

STM32CubeMX配置

工程文件下载链接

串口异步通信-DMA式收发数据-仿printf发送-串口空闲接收

采用STM32F103C8T6单片机,KeilMDK5.32版本

串口异步通信,开启收发方向,DMA式收发数据(仿printf发送)。
发的DMA不在循环模式下(单次);接收的DMA在单次模式下。开启串口接收空闲中断
PC13控制LED灯,LED灯的亮灭指示接收到数据。
在STM32CubeMX中需要同时开启DMA与串口全局中断

通过调用函数HAL_UARTEx_ReceiveToIdle_DMA来使能DMA接收。
该函数会开启DMA相应的中断传输完成,传输完成一半,传输错误
和串口空闲中断,NE或ORT或FE,PE
注意没有开启RXNE中断

接收回调函数为HAL_UARTEx_RxEventCallback()(在该函数中重新开启串口DMA空闲接收)
分2种情况

  • 接收到指定数目的数据(如,想要15个字节数据,实际也是15个字节)
    HAL_DMA_IRQHandler()调用UART_DMAReceiveCplt()进入接收回调函数
    HAL_DMA_IRQHandler()

    UART_DMAReceiveCplt()

  • 接收的数据数目少于指定数目
    在串口服务函数HAL_UART_IRQHandler()中进入接收回调函数

工程文件下载链接

串口异步通信-DMA式收发数据-仿printf发送-接收DMA循环-串口空闲接收

采用STM32F103C8T6单片机,KeilMDK5.32版本

串口异步通信,开启收发方向,DMA式收发数据(仿printf发送)。
发的DMA不在循环模式下(单次);接收的DMA在循环模式下。开启串口接收空闲中断
PC13控制LED灯,LED灯的亮灭指示接收到数据。
在STM32CubeMX中需要同时开启DMA与串口全局中断

调用HAL_UARTEx_ReceiveToIdle_DMA()开启串口DMA空闲接收,
该函数会开启DMA相应的中断传输完成,传输完成一半,传输错误
和串口空闲中断,NE或ORT或FE,PE
注意没有开启RXNE中断

接收回调函数为HAL_UARTEx_RxEventCallback()(不需要在该函数中重新开启串口DMA空闲接收)

同样的,

  • 接收的数据数目少于指定数目
    通过串口空闲中断进入接收回调函数中断函数
  • 接收到指定数目的数据
    通过DMA中断服务函数进入接收回调函数中断函数

在DMA传输完成一半中断关闭的条件下。按空闲中断的定义来说,接收到指定数据的数据也会触发串口空闲中断,从而在退出DMA中断后进入串口空闲中断,也就是触发2次中断(一次是DMA的中断传输完成中断,一次是串口空闲中断)。
但是在实验调试中,只触发一次中断DMA传输完成中断。在进入HAL_DMA_IRQHandler()函数前,调试发现SR寄存器被置位,在进入函数后,串口SR寄存器的IDLE位被自动复位,与手册的需要软件序列来复位说法不同,


猜想读串口DR寄存器也会清除IDLE标志位。
设计实验,开启串口空闲中断,发送数据,在串口中断服务函数中,先读DR寄存器,发现IDLE标志位被复位。

查看手册 串口DMA接收时序图
在DMA的传输完成标志位被置位后,进入DMA中断服务函数,此时DMA正在读串口的DR寄存器,从而将IDLE标志位复位。

但是,为了安全起见,还是在串口接收完成回调函数中添加软件系列复位IDLE标志的代码,如下

另外一个注意点
在接收的数据数目少于指定数目时候(DMA把串口DR寄存器的数据复制到串口接收缓冲区数组array中),比如指定接收15个字节数据,但是实际接收到7个字节数据,此时触发串口空闲中断,进入串口回调函数中处理这7个字节数据,存放下一个待接收字节的地址是array + 7,而不是array(从数组第0个字节开始存放接收到的数据)。

可通过如下方法修改
先失能DMA通道,修改修改DMA数据传输数量,再使能DMA通道

/** * @brief Reception Event Callback (Rx event notification called after use of advanced reception service). * @param huart UART handle * @param Size Number of data available in application reception buffer (indicates a position in * reception buffer until which, data are available) * @retval None */
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{ 
        
	if(huart == &huart1)
	{ 
        
		__HAL_UART_CLEAR_IDLEFLAG(huart);//清除串口空闲标志位
		__HAL_DMA_DISABLE(huart->hdmarx);//失能DMA
		WRITE_REG(huart->hdmarx->Instance->CNDTR, sizeof(receiveBuff));//修改DMA数据传输数量
		__HAL_DMA_ENABLE(huart->hdmarx);//使能DMA
		PCout(13) = !PCin(13);//;LED亮灭翻转 指示接收到数据
	}
}

工程文件下载链接

STM32CubeMX使用DMA的注意点

使用STM32CubeMX配置得工程文件中,任何使用DMA的外设初始化函数必须得再DMA初始化后再进行外设初始化。否则DMA无效

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章