数据通信的基本概念
串行/并行通信
按数据通信方式分类:串行通信、并行通信
串行通信数据按顺序逐位依次传输
并行通信数据各位通过多条线同时传输
单工/半双工/全双工通信
按数据传输方向分类:单工通信、半双工通信、全双工通信
单工通信数据只能单向传输
半双工通信数据可以沿两个方向传输,但需要分时进行
全双工通信数据可以在同一时间沿两个方向传输
同步/异步通信
按数据同步方式分类:同步通信、异步通信
同步通信发送方和接收方共用同一时钟信号
异步通信不需要时钟信号,随时发送随时读取
比特率和波特率
比特率:每秒钟传输的比特数,单位bit/s
波特率:每秒钟传输的码元数,单位Baud。码元指的是数据经过调试后进行的编码。
比特率=波特率*log2 M , M表示每个码元承载的信息量
比如一个上升沿编码为1,一个下降沿编码为0,则如果一个码元为10,则承载的信息量可以为00,01,10,11,则一个码元的信息量=4
信息量也可以理解成 进制,比如二进制、四进制、十进制
二进制系统中,波特率数值上等于比特率,因为一个二进制数(码元)的信息量为2,那么log以2为底数,以M为指数,波特率=比特率*1
常见的串行通信接口
UART、1-wire、IIC、SPI都是常见的串行通信接口
串口(RS-232)
什么是串口
串口 是 串行通信接口的简称,指的是按位发送和接收数据的接口。如RS-232/422/485等
RS-232被称为标准串口。
RS-232接口
RS-232接口有2根数据信号线:
TXD:串口数据输出
RXD:串口数据输入
和5根握手信号线
RTS:请求发送
CTS:发送清除
DSR:数据发送就绪
DCD:数据载波检测
DTR:数据终端就绪
还有地线GND和振铃指示线RI
串口异步通信的话只需要用到数据输入线、数据输出线和地线
RS232电平与COMS/TTL电平对比
STM32用的COMS电平,51单片机用的TTL电平
通过电平对比可以看出RS-232电平直接连接到STM32上会烧掉芯片,也就是COMS/TTL电平不能与RS-232电平直接交换信息,需要一个电平转换芯片,阿波罗开发板选择的是 TP3232(也可以用 SP3232)来做电平转接
设备间的RS-232通信示意图
现在电脑上一般都没有RS-232(DB9)接口了,而是用的USB接口进行串口通信
STM32串口与电脑USB口通信示意图
注意USB转串口电路使用CH340C时,电脑上还需要安装CH340 USB虚拟串口驱动
RS-232异步通信协议
异步通信不受时钟线影响,因此图上的时钟线可以不看
异步通信协议的时序图中,
一帧数据包含 1 个位长 的启动位,
5~9个位长的有效数据位,
1 个位长的可选奇偶校验位,
0.5~2个位长的停止位
这里的位长指1位数据的脉冲周期 ,LSB指最低有效位,MSB指最高有效位
STM32的USART
STM32的USART简介
USART,Universal synchronous asynchrounous receiver transmitter,通用同步异步收发器
UART,Universal asynchrounous receiver transmitter,通用异步收发器
USART/UART都可以与外部设备进行全双工异步通信
USART我们常用的也是异步通信
STM32的USART主要特征
1、全双工异步通信
2、单线半双工通信
3、单独的发送器和接收器使能位
4、可配置使用DMA的多缓冲通信
5、多个带标志的中断源
如何快速查看STM32某个外设的数量及其对应的引脚
参考:ST MCU最新选型手册.pdf 原理图 数据手册
STMF1/F4/F7的USART框图
看USART的框图首先看输入输出引脚
TX、RX、SW_RX、nRTS、nCTS和SCLK
TX和RX对应发送和接收引脚
SW_RX是智能卡上的,是芯片内部的引脚
RTS和CTS是硬件流控制相关的引脚,使用同步控制用到;SCLK是同步时钟,也是同步控制用到
最顶上是总线,可以通过CPU或者DMA访问。比如要发送一个串口数据,就需要由CPU或者DMA对USART进行写操作,灰色框中的寄存器用户是无法访问的,由芯片内部工作自行处理。用户能操作的寄存器是DR(Data Register,数据寄存器)
要让串口发送数据,就需要向DR中写入数据,之后寄存器的值会在灰色框内各个寄存器自动传递。当发送移位寄存器为空时,数据会从发送数据寄存器TDR自动传递进去,之后发送移位寄存器会一位一位串行向编解码模块发送数据,由TX端口发送出去
接收数据时则由RX端口接收信号,经过编解码模块,传递到接收移位寄存器,再进入接收数据寄存器RDR,最后进入数据寄存器DR,由用户通过CPU或DMA读取
发送是CPU主动发送,而接收是用户设置后被动接收,所以USART内部发送器控制不需要唤醒单元,接收器控制则需要唤醒单元
发送器控制有发送器时钟,接收器控制有接收器时钟
发送器时钟和接收器时钟都由波特率发生器(Boud Rate Register,BRR)形成
TE是BRR的发送使能位,RE是BRR的接收使能位
TE使能则波特率发生器产生的时钟会走到发送器控制器,
RE使能则波特率发生器产生的时钟会走到接收器控制器
波特率发生器(Boud Rate Register,BRR)的低16位存放的是波特率设置的值,该值由/USARTDIV决定
fPCLKx(x=1,2)是PB1或者PB2的总线时钟。fPCLKx(x=1,2)/USARTDIV/16之后才能发送给发送器控制器和接收器控制器
over8指的是CR1寄存器中的over8这个bit位,可以设置为0或1
需要了解的点:
1、发送/接收数据的流程
2、相关寄存器的作用
3、波特率的设置
STM32F1/F4/F7/H7的USART框图简化版
设置USART波特率(F4)
fck是USART挂载的总线时钟频率,over8指的是CR1寄存器中的over8这个bit位
如果CR1中的OVER8位=0,那么baud=fck/(16*USARTDIV)
如果CR1中的OVER8位=1,那么baud=fck/(8*USARTDIV)
USART_CR1的over8位其实是F4波特率的过采样设置,OVER8=0就是16倍过采样,OVER8=1就是8倍过采样
公式中总线频率fck、设置的波特率baud,和OVER8都是可知的,这样USARTDIV也可知
DIV_Mantissa是整数部分,DIV_Fraction是小数部分,如此都可知了
USARTDIV代表波特率的除数,该数要写入USART_BRR寄存器,低4位写小数,高12位写整数
USART寄存器介绍(F1)
控制寄存器1(CR1)
该寄存器需要完成的配置:
位13 ,UE:使能USART
0禁止 USART 预分频器和输出
1使能 USART
位12 ,M:该位决定了字长。通常配置为8个数据位
0:1 起始位,8 数据位,n 停止位
1:1 起始位,9 数据位,n 停止位
位10,PCE:奇偶校验控制使能。通常禁止检验控制。Parity control enable
位5,RXNE中断使能:使能接收缓冲区非空中断 (Receive Buffer Not Empty,RXNE)
位3,TE:使能发送
位2,RE:使能接收
控制寄存器2(CR2)
USART_CR2只需用到2Bit位来设置停止位的位数
控制寄存器3(CR3)
USART_CR3只需要用到1Bit来配置是否选择半双工。通常不选择半双工模式。
数据寄存器DR
USART_DR只有低八位有效,用来保存接收或发送的数据。有发送数据寄存器TDR和接收数据寄存器RDR两种。TDR和RDR一样
状态寄存器SR
位6,TC:表示发送是否完成 指的是是数据从发送数据寄存器TDR到发送移位寄存器后,TDR和发送移位寄存器都为空
位5,RXNE:表示读取寄存器是否非空 位7,TXE表示发送数据寄存器TDR是否为空
用户判断TC=1后可以继续发送数据,判断RXNE=1后可以读取数据
需要配置的时序总结
HAL库外设初始化MSP回调机制
HAL库外设初始化MSP回调机制介绍
MSP,Main Stack Pointer
ST官方库提供的stm32f4xx_hal_msp.c里面的HAL_PPP_Init和HAL_PPP_MspInit等函数是空的,本意是把所有外设初始化都放到一个函数里面,正点原子每个外设用一个独立函数
用户调用 HAL_PPP_Init()去初始化外设的工作参数,该函数会自动调动MSP回调函数
HAL_PPP_MspInit()是被__weak修饰的空函数,用户可重定义,用来配置PPP外设所用到的硬件
当多个PPP外设,比如USART1、USART2、USART3都被使用,则HAL_USART_MspInit()会被调用3次,需要依靠外设寄存器的基地址来区分具体外设,再配置PPP外设用到的硬件。
此时由于难以描述HAL_PPP_MspInit()放在USART1.c、USART2.c、USART2.c哪个文件中合适,且一个函数中涉及多个外设设置,因此不建议使用该函数,推荐直接把硬件的初始化写在用户自定义函数中,自定义GPIO、外设、NVIC的配置和初始化函数,然后IRQHadnler里面写逻辑。比如HAL_USART1_IRQHandler写在USART1.c文件里,详见本文中断回调例程
HAL库外设初始化MSP回调机制-USART为例
HAL_PPP_MspInit(PPP_HandTypeDef *ppp)函数的形参PPP_HandTypeDef *ppp是外设的句柄。句柄是指特殊类型的标识符或对象引用。
例子中huart->Instance == USART1就是外设的基地址
HAL库中断回调机制
HAL库中断回调机制介绍
当中断信号到达GPIO,首先由SYSCFG进行GPIO与EXIT线的映射,之后经过边沿检测线路(上升/下降/双边沿触发选择器)判定是否触发中断,接着到达软硬件触发选择线路,然后到达中断屏蔽/清除线路(挂起)线路,最后到达NVIC中断控制器或脉冲发生器
经过NVIC中断控制器后根据中断优先级会调用中断服务函数HAL_PPP_IRQHandler()
中断服务函数HAL_PPP_IRQHandler()会调用公共中断处理函数HAL_PPP_EXTI_IRQHandler()
公共中断处理函数会调用数据处理回调函数HAL_PPP_XXXCallback()
xxx是根据回调函数的作用来命名,比如GPIO外部中断就是HAL_GPIO_EXTICallback()
当多个PPP外设同时使用中断回调函数,同样是通过判断外设寄存器基地址区分哪个外设进入中断,并执行相应的处理。同样会存在一个函数包含多个外设文件难以管理的问题。
HAL库中断回调机制-USART为例(F1)
用户在中断服务函数USARTx_IRQHandler()中调用HAL库中断共用处理函数HAL_USART_IRQHandler()或者HAL_UART_IRQHandler()
HAL库自己调用中断回调函数HAL_UART_xxxCallback()
#define USART_UX_IRQHandler USART1_IRQHandler //宏处理.s文件中断服务函数名
UART_HandleTypeDef g_uart1_handle; /* UART句柄 */
/**
* @brief 串口X中断服务函数
* @param 无
* @retval 无
*/
void USART_UX_IRQHandler(void)
{
HAL_UART_IRQHandler(&g_uart1_handle); /* 调用HAL库中断处理公用函数 */
}
#*************在Hal库中断处理共用函数中 调用 了数据处理回调函数**************#
#else
/*Call legacy weak Tx complete callback*/
HAL_UART_TxCpltCallback(huart);
ST官方的UART公共处理函数HAL_UART_IRQHandler()设计思路
USART/UART异步通信配置步骤
USART/UART异步通信配置步骤介绍
串口数据无论是发送还是接收用到的都是USART_DR寄存器
USART/UART异步通信的HAL库相关函数介绍
1,HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
2,HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
作用:以中断的方式接收指定字节的数据
形参 1 : UART_HandleTypeDef 结构体类型指针变量
形参 2 : 指向接收数据缓冲区
形参 3 : 要接收的数据大小,以字节为单位
3,HAL_StatusTypeDef HAL_UART_Transmit (UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
作用:以阻塞的方式发送指定字节的数据
形参 1 :UART_HandleTypeDef 结构体类型指针变量
形参 2:指向要发送的数据地址
形参 3:要发送的数据大小,以字节为单位
形参 4:设置的超时时间,以ms单位
/*定义UART的状态参数*/
typedef enum
{
HAL_OK = 0x00U,//初始化完成
HAL_ERROR = 0x01U,//初始化错误
HAL_BUSY = 0x02U,//串口在忙,初始化错误
HAL_TIMEOUT = 0x03U //超时,初始化错误
} HAL_StatusTypeDef;
/*定义UART的初始化参数*/
typedef struct
{
uint32_t BaudRate; //波特率
uint32_t WordLength; //字长(数据位长度)
uint32_t StopBits; //停止位
uint32_t Parity; //奇偶校验位的设置(如无校验、偶校验、奇校验等)
uint32_t Mode; //UART工作模式
uint32_t HwFlowCtl; //用于设置硬件流控制
uint32_t OverSampling;//用于设置过采样率,如8倍过采样、16倍过采样
} UART_InitTypeDef;
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;
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;
DMA_HandleTypeDef *hdmatx;
DMA_HandleTypeDef *hdmarx;
HAL_LockTypeDef Lock;
__IO HAL_UART_StateTypeDef gState;
__IO HAL_UART_StateTypeDef RxState;
__IO uint32_t ErrorCode;
} USART_HandleTypeDef;
IO引脚复用功能
何为复用
首先理解通用的概念,IO端口的输入或输出是由GPIO外设控制,我们称之为通用
比如置位或复位引脚由BSRR寄存器控制吗,输出状态由ODR寄存器控制,输入状态由IDR寄存器控制,这些都是属于GPIO外设的寄存器
Bit Set Reset Register,BSRR
Output Data Register,ODR
Input Data Register,IDR
复用是指IO端口的输入或输出是由其他非GPIO外设控制,我们称之为复用
比如USART的输入输出由DR寄存器控制,还有像TIM、ADC、DAC等其他外设
STM32FX的IO引脚复用
1,各IO支持什么复用功能
可查数据手册引脚定义
2,IO复用功能冲突问题
同一时间IO只能用作一种复用功能,否则会发生冲突
比如前面引脚功能图的PA1,可以复用为ADC1、ADC2、ADC3的IN1也就是输入通道1,也可以复用为TIM5的CH2也就是通道2
如果多个复用功能都被配置且使能,就会出现IO复用冲突,导致冲突的功能都不能正常工作
由于PA1没有重映射功能,碰到这种情况就没办法了,只能自行避免
而PA6是有重映射功能的,可以重映射为TIM1_BKIN,如果其他引脚的TIM1_BKIN功能出现冲突,则可以将PA6重映射到该功能来解决
BKIN,Break In,断路输入
3,遇到 IO复用功能冲突
可考虑重映射功能
STM32F4/F7/H7的IO引脚复用
为了解决F1系列存在的IO复用功能冲突问题,F4往后的系列都加入了复用器
复用器特点:
1、每个 IO 引脚都有一个复用器。
2、复用器采用 16 路复用功能输入(AF0 到 AF15)
3、复用器一次仅允许一个外设的复用功能 (AF) 连接到 IO 引脚
4、通过GPIOx_AFRL和GPIOx_AFRH寄存器进行配置
复位完成后,所有的 IO 都会连接到系统的复用功能0 (AF0),也就是IO口默认连接到AF0
例如PAx每个x都有一个复用器,该复用器有AF0~AF15共16路复用功能输入,复用器一次只允许一个外设的复用功能(AF)与IO引脚连接,也就是16选1
可以看到AFRL和AFRH都是32位,AFRL负责将AF0~15复用到GPIO引脚0~7,AFRH负责将AF0~15复用到GPIO引脚8~15,也就是每个引脚由AFRL或AFRH的4个Bit位进行配置
AFRLx对应的就是引脚x
可以看出GPIOX的高8位对应32位的AFRH,GPIOX的低8位对应32位的AFRL
通过串口接收或者发送字符串
CH340 USB转串口原理图中TYPE-C的CH340 D+和CH340 D-就是USB信号,USB信号会连接到CH340的对应引脚,由CH340将TYPE-C电平转换成C-MOS电平,输出到TXD和RXD引脚,之后经过端子连线到STM32引脚。注意电脑到控制芯片是交叉连接
typedef struct __UART_HandleTypeDef
{
USART_TypeDef *Instance; /*!< UART 寄存器基地址,在stm32fxx.h中定义*/
UART_InitTypeDef Init; /*!< UART 通信参数*/
……
}
UART_HandleTypeDef g_uart1_handle; /* UART句柄 */
/**
* @brief 串口X中断服务函数
* @param 无
* @retval 无
*/
void USART_UX_IRQHandler(void)
{
HAL_UART_IRQHandler(&g_uart1_handle); /* 调用HAL库中断处理公用函数 */
}
首先是串口外设初始化,
UART_HandleTypeDef 是HAL库提供的UART句柄,
包括 USART_TypeDef 对象用来设置USART的寄存器,USART_InitTypeDef 对象用来设置USART的工作参数
HAL_UART_Init 将 UART_HandlerTypeDef作为参数,会完成UART的参数设置、标志位清除和UART使能工作,同时会调用HAL_UART_MspInit完成GPIO和串口的时钟使能、引脚配置、中断配置
/**
* @brief 串口X初始化函数
* @param baudrate: 波特率, 根据自己需要设置波特率值
* @retval 无
*/
void usart_init(uint32_t baudrate)
{
g_uart1_handle.Instance = USART_UX; /* USART1 */
g_uart1_handle.Init.BaudRate = baudrate; /* 波特率 */
g_uart1_handle.Init.WordLength = UART_WORDLENGTH_8B; /* 字长为8位数据格式 */
g_uart1_handle.Init.StopBits = UART_STOPBITS_1; /* 一个停止位 */
g_uart1_handle.Init.Parity = UART_PARITY_NONE; /* 无奇偶校验位 */
g_uart1_handle.Init.HwFlowCtl = UART_HWCONTROL_NONE; /* 无硬件流控 */
g_uart1_handle.Init.Mode = UART_MODE_TX_RX; /* 收发模式 */
HAL_UART_Init(&g_uart1_handle); /* HAL_UART_Init()会使能UART1 */
/* 该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量 */
HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
}
然后是相应的回调函数重写,本例程中为 Rx传输回调函数的重写
USART_UX_IRQHandler HAL_UART_IRQHandler HAL_UART_RxCpltCallback
中断服务函数 会调用 中断处理公用函数
中断处理公用函数 会调用 数据处理回调函数
中断处理公用函数HAL_UART_IRQHandler 会判断UART是发送模式还是接收模式,根据发送/接收缓冲区的状态标志位调用UART_Receive_IT,在该函数里清空相关中断标志位、清中断使能并调用数据处理函数HAL_UART_RxCpltCallback,接收完成回调函数
因此在回调函数里一般手动调用HAL_UART_Receive_IT,该函数会开启接收中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
/**
* @brief 串口X中断服务函数
* @param 无
* @retval 无
*/
void USART_UX_IRQHandler(void)
{
HAL_UART_IRQHandler(&g_uart1_handle); /* 调用HAL库中断处理公用函数 */
}
/**
* @brief Rx接收完成回调函数
* @param huart: UART句柄类型指针
* @retval 无
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART_UX) /* 如果是串口1 */
{
if ((g_usart_rx_sta & 0x8000) == 0) /* 接收未完成 */
{
if (g_usart_rx_sta & 0x4000) /* 接收到了0x0d */
{
if (g_rx_buffer[0] != 0x0a)
{
g_usart_rx_sta = 0; /* 接收错误,重新开始 */
}
else
{
g_usart_rx_sta |= 0x8000; /* 接收完成了 */
}
}
else /* 还没收到0X0D */
{
if (g_rx_buffer[0] == 0x0d)
{
g_usart_rx_sta |= 0x4000;
}
else
{
g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0] ;
g_usart_rx_sta++;
if (g_usart_rx_sta > (USART_REC_LEN - 1))
{
g_usart_rx_sta = 0; /* 接收数据错误,重新开始接收 */
}
}
}
}
HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
}
}
在UART_Receive_IT函数内部,首先检查了当前 UART 接收状态是否忙线。然后根据 UART 初始化的参数(数据位长度和奇偶校验位)来处理接收到的数据。
如果接收的数据位长度为 9 位且无奇偶校验位,则将接收到的数据存储在 `pdata16bits` 中;否则存储在 `pdata8bits` 中。
接着,根据接收的数据位长度递增接收缓冲区指针,并递减接收剩余字节数 `RxXferCount`。当所有数据接收完成后,禁用接收中断并将 UART 接收状态设置为 `HAL_UART_STATE_READY`。根据接收方式不同,调用不同的回调函数,可能是 Rx Event 回调或 Rx Complete 回调。
最后,根据条件返回不同的状态,如果接收忙则返回 `HAL_BUSY`,接收完成则返回 `HAL_OK`。
总的来说,UART_Receive_IT通过串口句柄的缓冲区当前指针从DR寄存器读取数据,并使当前指针指向下一个缓冲区位置,用户最终可以通过缓冲区指针获得读取到的数据
注意串口发送字符中文字符占俩字节,英文一字节
static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
uint8_t *pdata8bits;
uint16_t *pdata16bits;
/* 检查了当前 UART 接收状态是否为 `HAL_UART_STATE_BUSY_RX` */
if (huart->RxState == HAL_UART_STATE_BUSY_RX)
{
/* 根据 UART 初始化的参数(数据位长度和奇偶校验位)来处理接收到的数据
如果接收的数据位长度为 9 位且无奇偶校验位,
则将接收到的数据存储在 `pdata16bits` 中;否则存储在 `pdata8bits` 中 */
if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
{
pdata8bits = NULL; //在这种情况下不需要使用 8 位指针
//将`pdata16bits`指针设置为指向接收缓冲区当前位置的指针,
//这表明接收到的数据将以16位形式存储。
pdata16bits = (uint16_t *) huart->pRxBuffPtr;
//从UART数据寄存器`DR`中读取数据,并通过位掩码操作保留低9位数据,
//然后将其存储到`pdata16bits`指向的位置
*pdata16bits = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
//将接收缓冲区指针向前移动2个位置(2Byte=16位),以便为下一次接收做准备。
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;
}
/*根据接收的数据位长度递增接收缓冲区指针,
并递减接收剩余字节数 `RxXferCount`
当所有数据接收完成后,
禁用接收中断并将 UART 接收状态设置为 `HAL_UART_STATE_READY`
根据接收方式不同,调用不同的回调函数,
可能是 Rx Event 回调或 Rx Complete 回调*/
if (--huart->RxXferCount == 0U)
{
/* 失能RXNE中断 */
__HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);
/* 失能PE中断 parity求校验 Error*/
__HAL_UART_DISABLE_IT(huart, UART_IT_PE);
/* 失能ERR中断 Error */
__HAL_UART_DISABLE_IT(huart, UART_IT_ERR);
/* 接收过程已完成,将 huart->RxState 恢复为 Ready */
huart->RxState = HAL_UART_STATE_READY;
/* 检查当前接收模式:
如果已选择接收直到空闲事件 */
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))
{
/* 在ISR寄存器中清除空闲标志。 */
__HAL_UART_CLEAR_IDLEFLAG(huart);
}
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx Event callback*/
huart->RxEventCallback(huart, huart->RxXferSize);
#else
/*调用传统的弱Rx事件回调*/
HAL_UARTEx_RxEventCallback(huart, huart->RxXferSize);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
else
{
/* Standard reception API called */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
/*Call registered Rx complete callback*/
huart->RxCpltCallback(huart);
#else
/*调用传统的弱Rx完成回调*/
HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}
return HAL_OK;
}
return HAL_OK;
}
else
{
return HAL_BUSY;
}
}
解读例程源码:串口实验(接收发送字符串)
中断处理公用函数HAL_UART_IRQHandler 会判断UART是发送模式还是接收模式,根据发送/接收缓冲区的状态标志位调用UART_Receive_IT,在该函数里清空相关中断标志位并i调用数据处理函数HAL_UART_RxCpltCallback
UART_Receive_IT通过串口句柄UART_HandlerTypeDef的缓冲区当前指针pRxBuffPtr从DR寄存器读取数据,并使当前指针指向下一个缓冲区位置,用户最终可以通过缓冲区指针获得读取到的数据
/* 接收缓冲, 最大USART_REC_LEN个字节. */
uint8_t g_usart_rx_buf[USART_REC_LEN];
/* 接收状态
* bit15, 接收完成标志
* bit14, 接收到0x0d
* bit13~0, 接收到的有效字节数目,最大接收2^14个字节,16KB
*/
uint16_t g_usart_rx_sta = 0;
uint8_t g_rx_buffer[RXBUFFERSIZE]; /* HAL库使用的串口接收缓冲 */
UART_HandleTypeDef g_uart1_handle; /* UART句柄 */
/**
* @brief 正点原子串口通信历程中重写的的Rx传输完成回调函数
* @param huart: UART句柄类型指针
* @retval 无
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART_UX) /* 如果是串口1 */
{
if ((g_usart_rx_sta & 0x8000) == 0) /* 接收未完成 */
{
if (g_usart_rx_sta & 0x4000) /* 接收到了0x0d回车符*/
{
if (g_rx_buffer[0] != 0x0a) //如果接收到的不是0x0a换行符
{
g_usart_rx_sta = 0; /* 接收错误,重新开始 */
}
else
{
g_usart_rx_sta |= 0x8000; /* 接收完成了 */
}
}
else /* 还没收到0X0D */
{
if (g_rx_buffer[0] == 0x0d)
{
g_usart_rx_sta |= 0x4000;
}
else
{
g_usart_rx_buf[g_usart_rx_sta & 0X3FFF] = g_rx_buffer[0] ;
g_usart_rx_sta++;
//如果接收还没完成 有效数据已经超过缓冲区大小
if (g_usart_rx_sta > (USART_REC_LEN - 1))
{
g_usart_rx_sta = 0; /* 接收数据错误,重新开始接收 */
}
}
}
}
HAL_UART_Receive_IT(&g_uart1_handle, (uint8_t *)g_rx_buffer, RXBUFFERSIZE);
}
}
缓冲区g_rx_buffer[1]每次收到一个字节都会把数据存放到g_usart_rx_buf[200]中
g_usart_rx_buf[200]可存放200字节的数据
正点原子例程中g_usart_rx_sta的Bit15为1,也就是接收到换行/n,代表接收完成
就会获取读取到的数据长度
调用HAL_UART_Transmit()发送数据,发送完SR寄存器的TC位会置1 (Tranmit Complete)
比特率 = 波特率 * Log2 M , M为码元承载的信息量,
二进制下比特率=波特率,代表每秒传输的Bit数
因此波特率=115200,可以理解为每秒传输115200个Bit
以一个串口数据帧包含 1 Bit 起始位,8 Bit 数据位,1 Bit 停止位 为例,
一个数据帧就是10个 Bit,1个字节是8位,一帧数据中有意义的是这1个字节
因此115200/10=11.25KB有意义的数据
因此Printf函数只建议在测试代码中使用