STM32HAL库开发-串口篇

数据通信的基本概念

串行/并行通信

按数据通信方式分类:串行通信、并行通信

串行通信数据按顺序逐位依次传输

并行通信数据各位通过多条线同时传输

单工/半双工/全双工通信

按数据传输方向分类:单工通信、半双工通信、全双工通信

单工通信数据只能单向传输

半双工通信数据可以沿两个方向传输,但需要分时进行

全双工通信数据可以在同一时间沿两个方向传输

同步/异步通信

按数据同步方式分类:同步通信、异步通信

同步通信发送方和接收方共用同一时钟信号

异步通信不需要时钟信号,随时发送随时读取

比特率和波特率

比特率:每秒钟传输的比特数,单位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框图简化版

设置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)

                                                                  0:禁止中断
                                                                  1:当 USART_SR 寄存器中 ORE=1 或 RXNE=1 时,
                                                                        生成 USART 中断

位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转串口原理图

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函数只建议在测试代码中使用

  • 28
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值