完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
一、UART简介 1.1 串口通信简介 一般情况下,设备之间的通信方式可以分成并行通信和串行通信两种,它们的区别是: [tr]通信方式并行通信串行通信[/tr]
[tr]串行通信分类特点[/tr]
常见的串行通信接口汇总如下: [tr]通信协议引脚说明工作模式同步方式[/tr]
USART全称为Universal Synchronous Asynchronous Receiver and Transmitter,通用同步-异步接收发射器,怎么既同步又异步呢?实际上USART既支持同步通信也支持异步通信,如果作为异步通信就是UART(Universal Asynchronous Receiver and Transmitter),作为同步通信相比UART多了一根时钟同步信号线,USART相当于UART的增强版,二者的关系如下: 由上图可以看出USART以同步方式通信需要时钟同步信号,但不需要额外的起始、停止位,可以实现更快的传输速度。但USART的支持范围更有限,后面我们主要介绍其作为异步通信也即UART通信。 串口通讯的数据包由发送设备通过自身的TXD接口传输到接收设备的RXD接口,通讯双方的数据包格式要规约一致才能正常收发数据。UART串口通信的数据包以帧为单位,常用的帧结构为:1位起始位+8位数据位+1位奇偶校验位(可选)+1位停止位,如下图所示: 奇偶校验位分为奇校验和偶校验两种,是一种简单的数据误码校验方法。奇校验是指每帧数据中,包括数据位和奇偶校验位的全部9个位中1的个数必须为奇数;偶校验是指每帧数据中,包括数据位和奇偶校验位的全部9个位中1的个数必须为偶数。 STM32L475中USART结构框图如下图所示: 这个框图分成上、中、下三个部分。本文大概讲述一下各个部分的内容,具体的可以看《SMT32L475VE Reference manual》中的描述。 框图的上部分,数据从RX进入到接收移位寄存器,后进入到接收数据寄存器,最终供CPU或者DMA来进行读取;数据从CPU或者DMA传递过来,进入发送数据寄存器,后进入发送移位寄存器,最终通过TX发送出去(感觉上面框图箭头有误)。 框图的中间部分,UART的发送移位寄存器和接收移位寄存器分别由发送控制器和接收控制器来进行控制,而发送控制器和接收控制器主要受硬件流控信号和时钟信号控制。硬件流控nRTS / nCTS (Request To Send / Clear To Send,n表示低电平有效)可以控制数据发送、接收的速率,防止因数据缓冲区溢出而导致部分数据丢失的情况出现,在很多情况下为了减少引脚数量,都省去了硬件流控引脚而采用软件流控方式以达到同样的目的。针对USART的同步通信方式还有一个发送器时钟输出引脚CK,仅适用于同步模式。 框图的下部分,接收控制器、发送控制器都有一个进入的箭头,分别连接到接收器时钟、发送器时钟。也就是说,异步通信尽管没有时钟同步信号,但是在串口内部,是提供了时钟信号来进行控制的。接收器时钟和发送器时钟又被连接到同一个控制单元,也就是说它们共用一个波特率发生器。 上面提到了波特率,讲波特率之前首先了解一下通讯速率,通讯速率通常是以比特率来表示,即每秒钟传输的二进制位数,单位为比特每秒(bit/s)。容易和比特率混淆的概念是“波特率”,它表示每秒传输了多少码元。码元是通讯信号调制的概念,时间间隔相同的符号来表示一个二进制数字,这样的信号就称为码元。如常见的通讯传输中,用0V表示数字0,5V表示数字1,那么一个码元可以表示两种状态0和1,所以一个码元等于一个二进制比特位,此时波特率的大小与比特率一致;若传输中,有0V、2V、4V和6V分别表示00、01、10、11,那么每个码元可以表示四种状态,两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率的一半。因为很多常见的通讯中一个码元都是表示两种状态,人们常常直接以波特率来表示比特率,其实二者是有区别的。 异步通讯由于没有时钟信号,所以两个通讯设备需要规约好波特率,即每个码元的长度,以便对信号进行解码,常见的波特率为4800,9600,115200。 我们的STM32L475开发板支持的串口模式及特征如下: 1.3 USART配置 前面介绍了UART数据帧的格式和波特率的概念,UART的初始化主要是确定数据帧格式和双方通信的波特率,下面先介绍UART初始化结构体的定义: // DriversSTM32L4xx_HAL_DriverIncstm32l4xx_hal_uart.h /** * @brief UART Init Structure definition */ typedef struct { uint32_t BaudRate; /*!< This member configures the UART communication baud rate. The baud rate register is computed using the following formula: LPUART: ======= Baud Rate Register = ((256 * lpuart_ker_ckpres) / ((huart->Init.BaudRate))) where lpuart_ker_ck_pres is the UART input clock (divided by a prescaler if applicable) UART: ===== - If oversampling is 16 or in LIN mode, Baud Rate Register = ((uart_ker_ckpres) / ((huart->Init.BaudRate))) - If oversampling is 8, Baud Rate Register[15:4] = ((2 * uart_ker_ckpres) / ((huart->Init.BaudRate)))[15:4] Baud Rate Register[3] = 0 Baud Rate Register[2:0] = (((2 * uart_ker_ckpres) / ((huart->Init.BaudRate)))[3:0]) >> 1 where uart_ker_ck_pres is the UART input clock (divided by a prescaler if applicable) */ uint32_t WordLength; /*!< Specifies the number of data bits transmitted or received in a frame. This parameter can be a value of @ref UARTEx_Word_Length. */ uint32_t StopBits; /*!< Specifies the number of stop bits transmitted. This parameter can be a value of @ref UART_Stop_Bits. */ uint32_t Parity; /*!< Specifies the parity mode. This parameter can be a value of @ref UART_Parity @note When parity is enabled, the computed parity is inserted at the MSB position of the transmitted data (9th bit when the word length is set to 9 data bits; 8th bit when the word length is set to 8 data bits). */ uint32_t Mode; /*!< Specifies whether the Receive or Transmit mode is enabled or disabled. This parameter can be a value of @ref UART_Mode. */ uint32_t HwFlowCtl; /*!< Specifies whether the hardware flow control mode is enabled or disabled. This parameter can be a value of @ref UART_Hardware_Flow_Control. */ uint32_t OverSampling; /*!< Specifies whether the Over sampling 8 is enabled or disabled, to achieve higher speed (up to f_PCLK/8). This parameter can be a value of @ref UART_Over_Sampling. */ uint32_t OneBitSampling; /*!< Specifies whether a single sample or three samples' majority vote is selected. Selecting the single sample method increases the receiver tolerance to clock deviations. This parameter can be a value of @ref UART_OneBit_Sampling. */ #if defined(USART_PRESC_PRESCALER) uint32_t ClockPrescaler; /*!< Specifies the prescaler value used to divide the UART clock source. This parameter can be a value of @ref UART_ClockPrescaler. */ #endif /* USART_PRESC_PRESCALER */ } UART_InitTypeDef; /** @defgroup UART_Stop_Bits UART Number of Stop Bits */ #define UART_STOPBITS_0_5 USART_CR2_STOP_0 /*!< UART frame with 0.5 stop bit */ #define UART_STOPBITS_1 0x00000000U /*!< UART frame with 1 stop bit */ #define UART_STOPBITS_1_5 (USART_CR2_STOP_0 | USART_CR2_STOP_1) /*!< UART frame with 1.5 stop bits */ #define UART_STOPBITS_2 USART_CR2_STOP_1 /*!< UART frame with 2 stop bits */ /** @defgroup UART_Parity UART Parity */ #define UART_PARITY_NONE 0x00000000U /*!< No parity */ #define UART_PARITY_EVEN USART_CR1_PCE /*!< Even parity */ #define UART_PARITY_ODD (USART_CR1_PCE | USART_CR1_PS) /*!< Odd parity /** @defgroup UART_Mode UART Transfer Mode */ #define UART_MODE_RX USART_CR1_RE /*!< RX mode */ #define UART_MODE_TX USART_CR1_TE /*!< TX mode */ #define UART_MODE_TX_RX (USART_CR1_TE |USART_CR1_RE) /*!< RX and TX mode */ /** @defgroup UART_Hardware_Flow_Control UART Hardware Flow Control */ #define UART_HWCONTROL_NONE 0x00000000U /*!< No hardware control */ #define UART_HWCONTROL_RTS USART_CR3_RTSE /*!< Request To Send */ #define UART_HWCONTROL_CTS USART_CR3_CTSE /*!< Clear To Send */ #define UART_HWCONTROL_RTS_CTS (USART_CR3_RTSE | USART_CR3_CTSE) /*!< Request and Clear To Send */ /** @defgroup UART_Over_Sampling UART Over Sampling */ #define UART_OVERSAMPLING_16 0x00000000U /*!< Oversampling by 16 */ #define UART_OVERSAMPLING_8 USART_CR1_OVER8 /*!< Oversampling by 8 */ /** @defgroup UART_OneBit_Sampling UART One Bit Sampling Method */ #define UART_ONE_BIT_SAMPLE_DISABLE 0x00000000U /*!< One-bit sampling disable */ #define UART_ONE_BIT_SAMPLE_ENABLE USART_CR3_ONEBIT /*!< One-bit sampling enable */ UART初始化结构体UART_InitTypeDef中的成员配置在CubeMX配置中会涉及,但多数可以使用默认配置,如果默认配置满足不了需求,则需要了解结构体成员有哪些配置选项,各配置选项有何意义。 前面介绍HAL库提供了更高的抽象,将每类外设抽象为一个句柄,对实例化句柄的操作相当于对该外设的操作,这就类似于C++中面向对象编程的思想。前面介绍GPIO更偏底层,只提供了初始化结构体,不需要句柄结构体,所以并没有介绍句柄结构体是怎样实现的?下面以UART为例介绍其句柄结构体定义如下: // DriversSTM32L4xx_HAL_DriverIncstm32l4xx_hal_uart.h /** * @brief UART handle Structure definition */ typedef struct __UART_HandleTypeDef { USART_TypeDef *Instance; /*!< UART registers base address */ UART_InitTypeDef Init; /*!< UART communication parameters */ UART_AdvFeatureInitTypeDef AdvancedInit; /*!< UART Advanced Features initialization parameters */ uint8_t *pTxBuffPtr; /*!< Pointer to UART Tx transfer Buffer */ uint16_t TxXferSize; /*!< UART Tx Transfer size */ __IO uint16_t TxXferCount; /*!< UART Tx Transfer Counter */ uint8_t *pRxBuffPtr; /*!< Pointer to UART Rx transfer Buffer */ uint16_t RxXferSize; /*!< UART Rx Transfer size */ __IO uint16_t RxXferCount; /*!< UART Rx Transfer Counter */ uint16_t Mask; /*!< UART Rx RDR register mask */ #if defined(USART_CR1_FIFOEN) uint32_t FifoMode; /*!< Specifies if the FIFO mode is being used. This parameter can be a value of @ref UARTEx_FIFO_mode. */ uint16_t NbRxDataToProcess; /*!< Number of data to process during RX ISR execution */ uint16_t NbTxDataToProcess; /*!< Number of data to process during TX ISR execution */ #endif /*USART_CR1_FIFOEN */ 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_HandleTypeDef *hdmatx; /*!< UART Tx DMA Handle parameters */ DMA_HandleTypeDef *hdmarx; /*!< UART Rx DMA Handle parameters */ HAL_LockTypeDef Lock; /*!< Locking object */ __IO HAL_UART_StateTypeDef gState; /*!< UART state information related to global Handle management and also related to Tx operations. This parameter can be a value of @ref HAL_UART_StateTypeDef */ __IO HAL_UART_StateTypeDef RxState; /*!< UART state information related to Rx operations. This parameter can be a value of @ref HAL_UART_StateTypeDef */ __IO uint32_t ErrorCode; /*!< UART Error code */ #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) void (* TxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Tx Half Complete Callback */ void (* TxCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Tx Complete Callback */ void (* RxHalfCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Rx Half Complete Callback */ void (* RxCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Rx Complete Callback */ void (* ErrorCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Error Callback */ void (* AbortCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Complete Callback */ void (* AbortTransmitCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Transmit Complete Callback */ void (* AbortReceiveCpltCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Abort Receive Complete Callback */ void (* WakeupCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Wakeup Callback */ #if defined(USART_CR1_FIFOEN) void (* RxFifoFullCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Rx Fifo Full Callback */ void (* TxFifoEmptyCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Tx Fifo Empty Callback */ #endif /* USART_CR1_FIFOEN */ void (* MspInitCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Msp Init callback */ void (* MspDeInitCallback)(struct __UART_HandleTypeDef *huart); /*!< UART Msp DeInit callback */ #endif /* USE_HAL_UART_REGISTER_CALLBACKS */ } UART_HandleTypeDef; /** @addtogroup UART_Exported_Functions_Group1 Initialization and de-initialization functions */ /* Initialization and de-initialization functions ****************************/ HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_HalfDuplex_Init(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_LIN_Init(UART_HandleTypeDef *huart, uint32_t BreakDetectLength); HAL_StatusTypeDef HAL_MultiProcessor_Init(UART_HandleTypeDef *huart, uint8_t Address, uint32_t WakeUpMethod); HAL_StatusTypeDef HAL_UART_DeInit(UART_HandleTypeDef *huart); void HAL_UART_MspInit(UART_HandleTypeDef *huart); void HAL_UART_MspDeInit(UART_HandleTypeDef *huart); /* Callbacks Register/UnRegister functions ***********************************/ #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) HAL_StatusTypeDef HAL_UART_RegisterCallback(UART_HandleTypeDef *huart, HAL_UART_CallbackIDTypeDef CallbackID, pUART_CallbackTypeDef pCallback); HAL_StatusTypeDef HAL_UART_UnRegisterCallback(UART_HandleTypeDef *huart, HAL_UART_CallbackIDTypeDef CallbackID); #endif /* USE_HAL_UART_REGISTER_CALLBACKS */ /** @addtogroup UART_Exported_Functions_Group2 IO operation functions */ /* IO operation functions *****************************************************/ HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout); HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_UART_DMAPause(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_UART_DMAResume(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart); /* Transfer Abort functions */ HAL_StatusTypeDef HAL_UART_Abort(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_UART_AbortTransmit(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_UART_AbortReceive(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_UART_Abort_IT(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_UART_AbortTransmit_IT(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_UART_AbortReceive_IT(UART_HandleTypeDef *huart); void HAL_UART_IRQHandler(UART_HandleTypeDef *huart); void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart); void HAL_UART_AbortCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_AbortTransmitCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart); /** @addtogroup UART_Exported_Functions_Group3 Peripheral Control functions */ /* Peripheral Control functions ************************************************/ HAL_StatusTypeDef HAL_LIN_SendBreak(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_MultiProcessor_EnableMuteMode(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_MultiProcessor_DisableMuteMode(UART_HandleTypeDef *huart); void HAL_MultiProcessor_EnterMuteMode(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_HalfDuplex_EnableTransmitter(UART_HandleTypeDef *huart); HAL_StatusTypeDef HAL_HalfDuplex_EnableReceiver(UART_HandleTypeDef *huart); /** @addtogroup UART_Exported_Functions_Group4 Peripheral State and Error functions */ /* Peripheral State and Errors functions **************************************************/ HAL_UART_StateTypeDef HAL_UART_GetState(UART_HandleTypeDef *huart); uint32_t HAL_UART_GetError(UART_HandleTypeDef *huart); /* Private functions -----------------------------------------------------------*/ /** @addtogroup UART_Private_Functions UART Private Functions */ #if (USE_HAL_UART_REGISTER_CALLBACKS == 1) void UART_InitCallbacksToDefault(UART_HandleTypeDef *huart); #endif /* USE_HAL_UART_REGISTER_CALLBACKS */ HAL_StatusTypeDef UART_SetConfig(UART_HandleTypeDef *huart); HAL_StatusTypeDef UART_CheckIdleState(UART_HandleTypeDef *huart); HAL_StatusTypeDef UART_WaitOnFlagUntilTimeout(UART_HandleTypeDef *huart, uint32_t Flag, FlagStatus Status, uint32_t Tickstart, uint32_t Timeout); void UART_AdvFeatureConfig(UART_HandleTypeDef *huart); UART句柄结构体UART_HandleTypeDef中的成员比较全面,既包括寄存器基地址结构体USART_TypeDef、UART初始化结构体UART_InitTypeDef,又包括Tx / Rx缓冲区地址及大小、中断处理函数指针、DMA句柄指针、回调函数指针等,基本可以通过UART句柄访问与UART有关的所有资源,因此HAL库为UART提供的API函数基本都以UART句柄作为参数传入。 正如前一篇HAL库详解中介绍的,UART的API函数主要可分为如下几类: [tr]API分类API函数[/tr]
前面介绍了UART的结构、原理,了解了UART常用的API,可以开始UART程序开发了。UART包括其它外设,通常有三种I / O方式,分别为轮询、中断和DMA,下面先以最常见的中断为例展示UART程序开发的一般步骤。 2.1 CubeMX配置UART1 我们在前一篇展示的RGB_LED工程的基础上新增UART1配置,USART1的GPIO引脚配置(GPIO参数按默认配置即可)如下: USART1初始化参数配置如下(默认配置即可): USART1的NVIC中断配置(包括中断优先级分组、抢占优先级、子优先级、使能中断等)如下: 由于我们的USART1只使用中断,并没有使用DMA,所以DMA不需要配置。到这里USART1的配置完成,生成代码后到KEIL MDK V5工程中继续新增用户控制逻辑和回调函数。 2.2 完成用户控制逻辑 前面已经介绍过USART HAL库的API函数,其中初始化与反初始化函数已经由CubeMX生成了,我们在用户控制逻辑中主要实现的是I / O函数与处理完成回调函数,由于这里是采用中断方式接收/发送,所以我们主要用到的HAL API如下: // DriversSTM32L4xx_HAL_DriverIncstm32l4xx_hal_uart.h HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size); void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); 由上面的函数声明可知,UART接收/发送函数需要指定数据长度,当达到指定的数据长度后触发中断,但我们往往不知道将要接收到的数据长度是多少?所以我们不能依赖HAL_UART_Receive_IT直接接收一段数据,常需要在接受完成回调函数中设置接收完成标志位(常把收到回车换行符作为该段数据结束的标志),在用户控制逻辑中只有通过该标志位确定该段数据接收完成后才对其进行处理。 要发送/接收数据,一般需要相应的缓冲区暂存数据,同时需要知道数据长度,接收/发送完成标志位等元素,我们可以把这些变量组织成一个结构体变量统一管理,我们定义的数据结构如下: // CoreIncmain.h /* USER CODE BEGIN Private defines */ #define USART1_TX_LEN 256 #define USART1_RX_LEN 256 typedef struct{ uint8_t TxBuff[USART1_TX_LEN]; uint16_t TxSize; uint8_t Tx_end_flag; uint8_t RxBuff[USART1_RX_LEN]; uint8_t aRxBuff; uint16_t RxSize; uint8_t Rx_end_flag; }USART_BuffTypeDef; extern USART_BuffTypeDef usart1_buf; /* USER CODE END Private defines */ // main.c /* Private variables ---------------------------------------------------------*/ /* USER CODE BEGIN PV */ USART_BuffTypeDef usart1_buf; /* USER CODE END PV */ USART_BuffTypeDef结构定义在main.h中,初始化结构体变量usart1_buf在main.c中,usart1_buf作为全局变量,在系统启动时默认初始化为0,所以这里可以省略赋初值。 接下来完成接收/发送回调函数的编写,在接收回调函数中把回车换行符/r或/n作为一段数据的结束标志符,检测到结束标识符则结束标志置位,否则将该字符存放到接收数据缓冲区内;在发送回调函数中返回已发送数据段的字符总数,最后清空发送缓冲区和标志位,两个回调函数的实现代码如下: // CoreSrcusart.c /* USER CODE BEGIN 1 */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(huart->Instance == USART1) { if(usart1_buf.aRxBuff == 'n' || usart1_buf.aRxBuff == 'r'){ usart1_buf.Rx_end_flag = 1; usart1_buf.RxBuff[usart1_buf.RxSize] = ' |