通讯协议学习之路(实践部分):UART开发实践

通讯协议之路主要分为两部分,第一部分从理论上面讲解各类协议的通讯原理以及通讯格式,第二部分从具体运用上讲解各类通讯协议的具体应用方法。

后续文章会同时发表在个人博客(jason1016.club)、CSDN;视频会发布在bilibili(UID:399951374)

本文前缀:

通讯协议专栏:通讯协议_JASON丶LI的博客-CSDN博客

UART理论部分:通讯协议学习之路:UART协议理论-CSDN博客

具体实践方案选择:

1、轮询模式

程序必须轮询状态位以检查是否已收到新字符并以足够快的速度读取它以获得所有字节

优点

很容易实现,但在真正项目中的应用很少

缺点

在突发数据中很容易错过接收到的字符

仅适用于低波特率

应用程序必须非常快速地检查是否收到新的数据

2、中断模式

UART触发中断,CPU跳转到服务程序处理数据接收

优点

目前程序中最常用的方法

在低速率下工作良好,115200 波特

缺点

为每个接收到的字符执行中断服务程序

可能会在具有许多中断的高性能 MCU 中停止其他任务

一次接收突发数据时可能会停止操作系统

3、DMA模式

🍀🍀🍀注意这里的DMA模式接收不定长数据时是检测IDLE空闲中断标志位来判断DMA接收是否完成的,但是本人在使用proteus仿真中,IDLE一直都不会挂起导致仿真的DMA接收无法实现,后续会想方案解决

DMA 用于在硬件级别将数据从 USART RX 数据寄存器传输到用户存储器。 除了在必要时由应用程序处理接收到的数据,此时不需要应用程序交互

优点

i.从 USART 外设到内存的传输是在硬件完成的,无需 CPU干涉

ii.可以很容易地与操作系统一起工作

iii.针对最高波特率 > 1Mbps 和低功耗应用进行了优化

vi.在大量数据突发的情况下,增加数据缓冲区大小可以改进功能

缺点

i.DMA 硬件必须事先知道要传输的字节数

ii.如果通信失败,DMA 可能不会通知应用程序所有传输的字节

本文仅关注接收未知数据长度的 DMA 模式。

开发实践

对于usart的开发实践,其实并没有学习理论时预想的那么负责,因为目前市面上绝大部分单片机芯片内核都已经配备了完整的U(S)ART固件,相当于厂家已经配置好了对应的协议传输方案,我们要做的就是简单地配置一下其已存在的固件以及对应数据传输的规则即可。

本文以STM32F103C8为例,分两种方式进行usart协议通讯的配置,分别给标准库用户和HAL库用户详细的配置解决方案(ps:寄存器开发没有,作者寄存器开发不太熟练...)

一、标准库

中断模式:

单片机知识巩固

流控:【STM32学习笔记】USART 硬件流控 - 知乎 (zhihu.com)

usart协议的使用核心在与配置与数据的收发处理,对于标准库而言,USART的配置核心在与IO口的配置,而数据的收发核心在于数据的发送格式和接收缓存标注位,接收数据筛选。

1、UART的配置

USART协议硬件通道,在单片机的配置,本人有自己的一点想法。之前在理论中提到了单片机拟人化的概念,在这章我将继续延续这个概念进行介绍。先让我们回忆一下:之前提到晶振就是单片机的心跳信号,每实现一次晶振的跳变信号,单片机就执行一次指令周期;而各类的总线就是血管和供血,各类的IO口和IO协议就是单片机感知世界与世界交流的感官。

不知道大家有没有听过捕食者效应,那就是当你饥饿的时候,你会发现你的感官会变得更加灵敏(比如我考试之前一般都不吃饭的),这是什么原因呢?原因就是当我们空腹时,我们的消化系统就不需要工作,血液的占用就少了,但是血液的总量是不变的,因此心脏每一次泵血,血液就可以流向人身体内更需要他的地方。

回归单片机,正常单片机这么多IO口,当他每一个都开启工作的时候,是不是就得持续给他们供电以及随时随地管理信号呀,这会导致单片机的能耗变得非常高,那怎么解决呢?因此单片机所有的外设默认都是失能的,什么意思呢,就像刚刚提到的空腹状态,就是单片机不需要消化这个外设通讯,因此我们就可以失能消化系统这类的外设时钟,就是不给他供血了。那当我们发现我们需要消化食物了,那我们就重新开启这个外设的时钟就行了。这种按需开启,有利于单片机大大节省能耗以及降低CPU占用率,通常我们都是用啥就开啥,其他的就不管了。

而单片机GPIO、USART、SPI、NVIC等这类型外设的配置,以及各类的配置选项,就需要大家各自去掌握了,这里附上STM32F103的库函数编程文档供参考。

配置阶段分为:

  1. 使能GPIO时钟
  2. 使能USART时钟
  3. 配置GPIO口
  4. 配置USART口
  5. 开启USART中断模式
  6. 配置NVIC中断模式
  7. 使能usart

具体配置方案以及配置原因参考下列代码。

void USART1_Init(u32 bound)                //USART1初始化函数
{
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);    //使能USART1(ck=PB8,TX=PA9,RX=PA10)
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);    //使能GPIOA
    
    
    GPIO_InitTypeDef GPIO_Initstructure;                    //定义GPIO结构体
    GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF_PP;            //设置GPIO为复用推挽输出
    GPIO_Initstructure.GPIO_Pin = GPIO_Pin_9;                //设置为Pin9
    GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;        //设置为50MHZ速度
        GPIO_Init(GPIOA, &GPIO_Initstructure);                    //按照上述结构体配置初始化GPIOA

    GPIO_Initstructure.GPIO_Mode = GPIO_Mode_IPU;            //设置GPIO为浮空输入
    GPIO_Initstructure.GPIO_Pin = GPIO_Pin_10;                //设置为Pin9
    GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;        //设置为50MHZ速度
        GPIO_Init(GPIOA, &GPIO_Initstructure);                    //按照上述结构体配置初始化GPIOA
    
    
    USART_InitTypeDef USART_Initstructure;                                            //定义UASRT结构体
    USART_Initstructure.USART_BaudRate = bound;                                        //配置波特率
    USART_Initstructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;    //配置流控
    USART_Initstructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;                    //配置通讯方式,一般选择双控
    USART_Initstructure.USART_Parity = USART_Parity_No;                                //配置是否需要校验位(需要对应下面的数据长度WordLength)
    USART_Initstructure.USART_StopBits = USART_StopBits_1;                                            //配置停止位
    USART_Initstructure.USART_WordLength = USART_WordLength_8b;                        //配置数据长度(可选8位和9位,若设置校验位则配置9位,否则配置8位即可)
    
        USART_Init(USART1, &USART_Initstructure);                                        //按照上述结构体配置初始化USART1
    
    
        USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);            //使能USART中断
    
    NVIC_InitTypeDef NVIC_Initstructure;                    //定义NVIC结构体
    NVIC_Initstructure.NVIC_IRQChannel = USART1_IRQn;        //设置NVIC管理USART1中断
    NVIC_Initstructure.NVIC_IRQChannelCmd = ENABLE;            //确定使能
    NVIC_Initstructure.NVIC_IRQChannelPreemptionPriority = 1;    //设置抢占优先级为0
    NVIC_Initstructure.NVIC_IRQChannelSubPriority = 1;            //设置等候优先级为0
        NVIC_Init(&NVIC_Initstructure);                            //按照上述结构体配置初始化NVIC
    
        USART_Cmd(USART1, ENABLE);                                //使能UASRT1
}

库函数配置关键点:

  • @param USART_FLAG:指定要检查的标志。
  • @arg USART_FLAG_CTS: CTS更改标志(不适用于UART4和UART5)
  • @arg USART_FLAG_LBD: LIN中断检测标志
  • @arg USART_FLAG_TXE:传输数据寄存器空标志
  • @arg USART_FLAG_TC:传输完成标志
  • @arg USART_FLAG_RXNE:接收数据寄存器不空标志
  • @arg USART_FLAG_IDLE:空闲线检测标志
  • @arg USART_FLAG_ORE:超限错误标志
  • @arg USART_FLAG_NE:噪声错误标志
  • @arg USART_FLAG_FE:帧错误标志
  • @arg USART_FLAG_PE:奇偶校验错误标志

  • @param USART_IT:指定USART中断源要启用或禁用。
  • @arg USART_IT_CTS: CTS更改中断(不适用于UART4和UART5)
  • @arg USART_IT_LBD: LIN中断检测中断
  • @arg USART_IT_TXE:传输数据寄存器空中断
  • @arg USART_IT_TC:传输完成中断
  • @arg USART_IT_RXNE:接收数据寄存器不空中断
  • @arg USART_IT_IDLE:空闲线检测中断
  • @arg USART_IT_PE:奇偶校验错误中断
  • @arg USART_IT_ERR:中断错误(帧错误,噪声错误,溢出错误)
2、UART的发送

USART发送发本质就是调用USART_SendData()函数发送信息,各类的信息发送都是基于该函数变形而得的。

void Serial_SendByte(uint8_t Byte)    //编写发送函数
{
    USART_SendData(USART1, Byte);     //发送字节函数
    while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);    //检验发送是否完成
}


//发送一个数组
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
    uint16_t i;
    for (i = 0; i < Length; i ++)
    {
        Serial_SendByte(Array[i]);
    }
}


//发送一个字符串
void Serial_SendString(char *String)
{
    uint8_t i;
    for (i = 0; String[i] != 0; i++)
    {
        Serial_SendByte(String[i]);
    }
}


//取X的Y次方(用于下列运算)
uint32_t Serial_Pow(uint32_t X,uint32_t Y)
{
    uint32_t Result =1;
    while (Y--)
    {
        Result *=X;
    }
    return Result;
}


//发送一串数字(数字需要针对每一位数字进行对应的除法和求余)
void Serial_SendNumber(uint32_t Number,uint8_t Length)
{
    uint8_t i;
    for (i = 0; i < Length; i ++)
    {
        Serial_SendByte(Number/Serial_Pow(10, Length - i - 1) % 10 + '0');
    }
}


//重写fputc函数
int fputc(int ch, FILE *f)
{
    Serial_SendByte(ch);
    return ch;
}

//封装printf可变参数格式
void Serial_Printf(char *format, ...)
{
    char String[100];
    va_list arg;
    va_start(arg, format);
    vsprintf(String, format, arg);
    va_end(arg);
    Serial_SendString(String);
}
3、UART的接收

UART数据接收的本质

当已使能的UART信道接收到信息的时候触发usart中断,

然后在中断事件中调用USART_GetITStatus()函数检查接收数据寄存器是否为空

若检查到数据则将数据转移到Serial_RxData中进行数据的存储,同时挂起标志位Serial_RxFlag,方便后续要轮询模式中进行数据的打印或调用操作.

最后调用USART_ClearITPendingBit()函数进行中断挂起状态的清除,退出中断并方便下次再次进入中断.

uint8_t Serial_RxData;                     //定义UASRT1_RX数据缓存    
uint8_t Serial_RxFlag;                    //定义USART1_RX接收标志位,用于后续TX特定数据的接收
char Serial_RxPacket[100];

//调用标志位和重置标志位
uint8_t Serial_GetFlag(void)
{
    if (Serial_RxFlag == 1)
    {
        Serial_RxFlag = 0;
        return 1;
    }
    return 0;
}
    

uint8_t Serial_GetRxData(void)
{
        return Serial_RxData;
}

//无限制接收
 //void USART1_IRQHandler(void)
//{
//    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
//    {
//        Serial_RxData = USART_ReceiveData(USART1);
//        Serial_RxFlag = 1;
//        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
//    }
//}

 //接收筛选,接收数据包
void USART1_IRQHandler(void)        //编写USART1中断函数处理接收事件
{
        //static为静态变量,只需定义一次(只能再规定函数中执行的全局变量)
    static uint8_t RxStare = 0;
    static uint8_t pRxPacket = 0;
        if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
        {
            uint8_t RxData = USART_ReceiveData(USART1);
            
            if (RxStare == 0) //状态1
            {
                if (RxData == '@' && Serial_RxFlag == 0)
                {
                    RxStare = 1;
                    pRxPacket = 0;
                }
                
            }
            else if (RxStare == 1) //状态2
            {
                if (RxData == '\r')
                {
                    RxStare =2;
                }
                else
                {
                    Serial_RxPacket[pRxPacket] = RxData;
                    pRxPacket ++;
                }
                
            }
            else if (RxStare == 2) //状态3
            {
                if (RxData == '\n')
                {
                    RxStare = 0;
                    Serial_RxPacket[pRxPacket] = '\0';
                    Serial_RxFlag = 1;
                    
                }
            }
            USART_ClearITPendingBit(USART1, USART_FLAG_RXNE);
        }
}

DMA模式:

参考文档:

STM32 DMA串口发送模式配置及使用简单分享 - 知乎 (zhihu.com)

STM32 | 串口DMA很难?其实就是如此简单!(超详细、附代码)-CSDN博客

对于DMA转运,核心就在于DMA通道的配置与选取,发送不需要用到cpu,接收需要进入cpu的中断模式进行数据的处理

对于DMA的发送,核心在于调用DMA_Cmd(DMA1_Channel7, ENABLE);函数,将原先设定好的CNDTR(数据长度)的CMAR(数据地址)数据发送出去

对于DMA的接收,分为定长与不定长两种接收方式

定长方式采用DMA1_Channel6_IRQHandler中断方式,当接收数据缓存满了之后就会发生中断,将数据缓存内的所有数据读取。

不定长方式采用USART2_IRQHandler(IDLE方式)的中断方式,接收串口空闲标志位,当数据接收完存入数据缓存之后就会触发IDLE中断,这是我们将读取数据缓存内的数据,同时检查数据缓存内的剩余容量,这样数据长度就等于数据缓存总容量-数据缓存剩余容量,当得知数据长度与数据地址后就可以实现不定长数据的接收了。

配置:
#include "usart2.h"

//USART2_MAX_TX_LEN和USART2_MAX_RX_LEN在头文件进行了宏定义,分别指USART2最大发送长度和最大接收长度
u8 USART2_TX_BUF[USART2_MAX_TX_LEN];     //发送缓冲,最大USART2_MAX_TX_LEN字节
u8 u1rxbuf[USART2_MAX_RX_LEN];                //发送数据缓冲区1
u8 u2rxbuf[USART2_MAX_RX_LEN];                //发送数据缓冲区2
u8 witchbuf=0;                              //标记当前使用的是哪个缓冲区,0:使用u1rxbuf;1:使用u2rxbuf
u8 USART2_TX_FLAG=0;                                    //USART2发送标志,启动发送时置1
u8 USART2_RX_FLAG=0;                                    //USART2接收标志,启动接收时置1

void Initial_UART2(unsigned long baudrate)
{
    //GPIO端口设置
     GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure; 
    
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2 | RCC_APB2Periph_GPIOA, ENABLE);        //使能USART2,GPIOA时钟
    
    //USART2_TX   GPIOA.2初始化
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;                                                                                //PA.2
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                                                                    //复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                                                                //GPIO速率50MHz
    GPIO_Init(GPIOA, &GPIO_InitStructure);                                                                                    //初始化GPIOA.2
    
    //USART2_RX      GPIOA.3初始化
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;                                                                                //PA.3
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;                                                        //浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);                                                                                    //初始化GPIOA.3
     
    //USART 初始化设置
    USART_InitStructure.USART_BaudRate = baudrate;                                                                    //串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;                                            //字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;                                                    //一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No ;                                                        //无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;    //无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                                    //收发模式
    USART_Init(USART2, &USART_InitStructure);                                                                             //初始化串口2
    
    //中断开启设置
    USART_ITConfig(USART2, USART_IT_IDLE, ENABLE);                                                                    //开启检测串口空闲状态中断
    USART_ClearFlag(USART2,USART_FLAG_TC);                                                                                    //清除USART2标志位
        
    USART_Cmd(USART2, ENABLE);                                                                                                            //使能串口2
    
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;                                                                //NVIC通道设置
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 8;                                                //抢占优先级8
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;                                                            //响应优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                                    //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);                                                                                                    //根据指定的参数初始化NVIC寄存器
    
    DMA1_USART2_Init();                                                                                                                            //DMA1_USART2初始化
}

void DMA1_USART2_Init(void)
{
    DMA_InitTypeDef DMA1_Init;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);                                //使能DMA1时钟

    //DMA_USART2_RX  USART2->RAM的数据传输
    DMA_DeInit(DMA1_Channel6);                                                                            //将DMA的通道6寄存器重设为缺省值 
    DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR);                    //启动传输前装入实际RAM地址
    DMA1_Init.DMA_MemoryBaseAddr = (u32)u1rxbuf;                            //设置接收缓冲区首地址
    DMA1_Init.DMA_DIR = DMA_DIR_PeripheralSRC;                                            //数据传输方向,从外设读取到内存
    DMA1_Init.DMA_BufferSize = USART2_MAX_RX_LEN;                                        //DMA通道的DMA缓存的大小
    DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                //外设地址寄存器不变
    DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable;                                    //内存地址寄存器递增
    DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;    //数据宽度为8位
    DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;                    //数据宽度为8位
    DMA1_Init.DMA_Mode = DMA_Mode_Normal;                                                        //工作在正常模式
    DMA1_Init.DMA_Priority = DMA_Priority_High;                                         //DMA通道 x拥有高优先级 
    DMA1_Init.DMA_M2M = DMA_M2M_Disable;                                                        //DMA通道x没有设置为内存到内存传输
     
    DMA_Init(DMA1_Channel6,&DMA1_Init);                                                         //对DMA通道6进行初始化
    
    //DMA_USART2_TX  RAM->USART2的数据传输
    DMA_DeInit(DMA1_Channel7);                                                                            //将DMA的通道7寄存器重设为缺省值 
    DMA1_Init.DMA_PeripheralBaseAddr = (u32)(&USART2->DR);                    //启动传输前装入实际RAM地址
    DMA1_Init.DMA_MemoryBaseAddr = (u32)USART2_TX_BUF;              //设置发送缓冲区首地址
    DMA1_Init.DMA_DIR = DMA_DIR_PeripheralDST;                                             //数据传输方向,从内存发送到外设
    DMA1_Init.DMA_BufferSize = USART2_MAX_TX_LEN;                                        //DMA通道的DMA缓存的大小
    DMA1_Init.DMA_PeripheralInc = DMA_PeripheralInc_Disable;                //外设地址寄存器不变
    DMA1_Init.DMA_MemoryInc = DMA_MemoryInc_Enable;                                    //内存地址寄存器递增
    DMA1_Init.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;    //数据宽度为8位
    DMA1_Init.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;                    //数据宽度为8位
    DMA1_Init.DMA_Mode = DMA_Mode_Normal;                                                        //工作在正常模式
    DMA1_Init.DMA_Priority = DMA_Priority_High;                                         //DMA通道 x拥有高优先级 
    DMA1_Init.DMA_M2M = DMA_M2M_Disable;                                                        //DMA通道x没有设置为内存到内存传输

    DMA_Init(DMA1_Channel7,&DMA1_Init);                                                         //对DMA通道7进行初始化
    
    //DMA1通道6 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;                //NVIC通道设置
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ;            //抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;                            //子优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                    //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);                                                                    //根据指定的参数初始化NVIC寄存器
 
    //DMA1通道7 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn;                //NVIC通道设置
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ;            //抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;                            //子优先级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                    //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);                                                                    //根据指定的参数初始化NVIC寄存器

    DMA_ITConfig(DMA1_Channel6,DMA_IT_TC,ENABLE);                                        //开USART2 Rx DMA中断
    DMA_ITConfig(DMA1_Channel7,DMA_IT_TC,ENABLE);                                        //开USART2 Tx DMA中断

    DMA_Cmd(DMA1_Channel6,ENABLE);                                                           //使DMA通道6停止工作
    DMA_Cmd(DMA1_Channel7,DISABLE);                                                       //使DMA通道7停止工作
     
    USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);                            //开启串口DMA发送
    USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE);                            //开启串口DMA接收
}
发送函数
//DMA 发送应用源码
void DMA_USART2_Tx_Data(u8 *buffer, u32 size)
{
    while(USART2_TX_FLAG);                                        //等待上一次发送完成(USART2_TX_FLAG为1即还在发送数据)
    USART2_TX_FLAG=1;                                                    //USART2发送标志(启动发送)
    DMA1_Channel7->CMAR  = (uint32_t)buffer;    //设置要发送的数据地址
    DMA1_Channel7->CNDTR = size;                        //设置要发送的字节数目
    DMA_Cmd(DMA1_Channel7, ENABLE);                        //开始DMA发送
}

void USART2_printf(char *format, ...)
{
    //VA_LIST 是在C语言中解决变参问题的一组宏,所在头文件:#include <stdarg.h>,用于获取不确定个数的参数。
    va_list arg_ptr;                                                                                                                //实例化可变长参数列表
    
    while(USART2_TX_FLAG);                                                                                                    //等待上一次发送完成(USART2_TX_FLAG为1即还在发送数据)
    
    va_start(arg_ptr, format);                                                                                             //初始化可变参数列表,设置format为可变长列表的起始点(第一个元素)
    
    // USART2_MAX_TX_LEN+1可接受的最大字符数(非字节数,UNICODE一个字符两个字节), 防止产生数组越界
    vsnprintf((char*)USART2_TX_BUF, USART2_MAX_TX_LEN+1, format, arg_ptr);    //从USART2_TX_BUF的首地址开始拼合,拼合format内容;USART2_MAX_TX_LEN+1限制长度,防止产生数组越界
    
    va_end(arg_ptr);                                                                                                                //注意必须关闭

    DMA_USART2_Tx_Data(USART2_TX_BUF,strlen((const char*)USART2_TX_BUF));        //发送USART2_TX_BUF内容
}
接收函数
//处理DMA1 通道6的接收完成中断
void DMA1_Channel6_IRQHandler(void)
{
    u8 *p;
    if(DMA_GetITStatus(DMA1_IT_TC6)!= RESET)        //DMA接收完成标志
    {
        DMA_ClearITPendingBit(DMA1_IT_TC6);             //清除中断标志 
        USART_ClearFlag(USART2,USART_FLAG_TC);        //清除USART2标志位
        DMA_Cmd(DMA1_Channel6, DISABLE );               //关闭USART2 TX DMA1 所指示的通道
        if(witchbuf)                                    //之前用的u2rxbuf,切换为u1rxbuf
        {
            p=u2rxbuf;                                                            //先保存前一次数据地址再切换缓冲区
            DMA1_Channel6->CMAR=(u32)u1rxbuf;                //切换为u1rxbuf缓冲区地址
            witchbuf=0;                                     //下一次切换为u2rxbuf
        }else                                           //之前用的u1rxbuf,切换为u2rxbuf
        {
            p=u1rxbuf;                                                            //先保存前一次数据地址再切换缓冲区
            DMA1_Channel6->CMAR=(u32)u2rxbuf;                //切换为u2rxbuf缓冲区地址
            witchbuf=1;                                     //下一次切换为u1rxbuf
        }
        DMA1_Channel6->CNDTR = USART2_MAX_RX_LEN;    //DMA通道的DMA缓存的大小
        DMA_Cmd(DMA1_Channel6, ENABLE);                 //使能USART2 TX DMA1 所指示的通道
        
        //******************↓↓↓↓↓这里作数据处理↓↓↓↓↓******************//
        
        DMA_USART2_Tx_Data(p,USART2_MAX_RX_LEN);
        
        //******************↑↑↑↑↑这里作数据处理↑↑↑↑↑******************//
        
    }
}

//DMA1通道7中断
void DMA1_Channel7_IRQHandler(void)
{
    if(DMA_GetITStatus(DMA1_IT_TC7)!= RESET)    //DMA接收完成标志
    {
        DMA_ClearITPendingBit(DMA1_IT_TC7);         //清除中断标志 
        USART_ClearFlag(USART2,USART_FLAG_TC);    //清除串口2的标志位
        DMA_Cmd(DMA1_Channel7, DISABLE );           //关闭USART2 TX DMA1 所指示的通道
        USART2_TX_FLAG=0;                                                //USART2发送标志(关闭)
    }
}

//串口2中断函数
void USART2_IRQHandler(void)                    
{
    u8 *p;
    u8 USART2_RX_LEN = 0;                                                                                //接收数据长度
    if(USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)                //串口2空闲中断
    {
        USART_ReceiveData(USART2);                                                                 //清除串口2空闲中断IDLE标志位
        USART_ClearFlag(USART2,USART_FLAG_TC);                                        //清除USART2标志位
        DMA_Cmd(DMA1_Channel6, DISABLE );                                               //关闭USART2 TX DMA1 所指示的通道
        USART2_RX_LEN = USART2_MAX_RX_LEN - DMA1_Channel6->CNDTR;    //获得接收到的字节数
        if(witchbuf)                                                                    //之前用的u2rxbuf,切换为u1rxbuf
        {
            p=u2rxbuf;                                                                                            //先保存前一次数据地址再切换缓冲区
            DMA1_Channel6->CMAR=(u32)u1rxbuf;                                                //切换为u1rxbuf缓冲区地址
            witchbuf=0;                                                                     //下一次切换为u2rxbuf
        }else                                                                           //之前用的u1rxbuf,切换为u2rxbuf
        {
            p=u1rxbuf;                                                                                            //先保存前一次数据地址再切换缓冲区
            DMA1_Channel6->CMAR=(u32)u2rxbuf;                                                //切换为u2rxbuf缓冲区地址
            witchbuf=1;                                                                     //下一次切换为u1rxbuf
        }
        DMA1_Channel6->CNDTR = USART2_MAX_RX_LEN;                                    //DMA通道的DMA缓存的大小
        DMA_Cmd(DMA1_Channel6, ENABLE);                                                 //使能USART2 TX DMA1 所指示的通道
        
        //******************↓↓↓↓↓这里作数据处理↓↓↓↓↓******************//
        
        DMA_USART2_Tx_Data(p,USART2_RX_LEN);
        
        //******************↑↑↑↑↑这里作数据处理↑↑↑↑↑******************//
        
  }
    USART_ClearITPendingBit(USART2,USART_IT_ORE);                                //清除USART2_ORE标志位
}

二、HAL库

这里先放上HAL库的串口句柄

typedef struct __UART_HandleTypeDef
{
  USART_TypeDef                 *Instance;        /*!< UART registers base address        */
 
  UART_InitTypeDef              Init;             /*!< UART communication 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           */
 
  __IO HAL_UART_RxTypeTypeDef ReceptionType;      /*!< Type of ongoing reception          */
 
  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                  */
  void (* RxEventCallback)(struct __UART_HandleTypeDef *huart, uint16_t Pos); /*!< UART Reception Event Callback     */
 
  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;

1、串口发送/接收函数

HAL_UART_Transmit():串口发送数据,使用超时管理机制
HAL_UART_Receive():串口接收数据,使用超时管理机制
HAL_UART_Transmit_IT():串口中断模式发送
HAL_UART_Receive_IT():串口中断模式接收
HAL_UART_Transmit_DMA():串口DMA模式发送
HAL_UART_Transmit_DMA():串口DMA模式接收
串口发送数据 HAL_UART_Transmit
HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)

功能:串口发送指定长度的数据。如果超时没发送完成,则不再发送,返回超时标志(HAL_TIMEOUT)。

参数:

  • UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1
  • *pData 需要发送的数据
  • Size 发送的字节数
  • Timeout 最大发送时间,发送数据超过该时间退出发送
举例:   HAL_UART_Transmit(&huart1, (uint8_t *)ZZX, 3, 0xffff);   //串口发送三个字节数据,最大传输时间0xffff
中断接收数据 HAL_UART_Receive_IT
HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)

功能:串口中断接收,以中断方式接收指定长度数据。

大致过程是,设置数据存放位置,接收数据长度,然后使能串口接收中断。接收到数据时,会触发串口中断。

再然后,串口中断函数处理,直到接收到指定长度数据,而后关闭中断,进入中断接收回调函数,不再触发接收中断。(只触发一次中断)

参数:

UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1

*pData 接收到的数据存放地址

Size 接收的字节数

举例:    HAL_UART_Receive_IT(&huart1,(uint8_t *)&value,1);   //中断接收一个字符,存储到value中

2、串口中断函数

HAL_UART_IRQHandler(UART_HandleTypeDef *huart);  //串口中断处理函数
HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);  //串口发送中断回调函数
HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);  //串口发送一半中断回调函数(用的较少)
HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);  //串口接收中断回调函数
HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);//串口接收一半回调函数(用的较少)
HAL_UART_ErrorCallback();串口接收错误函数

a.串口中断服务函数 USART1_IRQHandler(void)【不需要配置】

USART1_IRQHandler(void);

功能:当我们使能了中断并且中断发生时就会执行这里的中断服务函数。

这个函数在MX配置后会自行进行下面所说的一系列判断,不需要额外配置,因此HAL库不同于标准库,标准库是在这个函数里面进行响应的中断处理事件的配置,而标准库则在这里面进行接收和发送两个事件类型的判断,再跳转到对应的函数进行对应的处理。

b.串口中断处理函数 HAL_UART_IRQHandler(UART_HandleTypeDef *huart)【不需要配置】

HAL_UART_IRQHandler(UART_HandleTypeDef *huart);

功能:对接收到的数据进行判断和处理 判断是发送中断还是接收中断,然后进行数据的发送和接收,在中断服务函数中使用

如果接收数据,则会进行接收中断处理函数

/* UART in mode Receiver ---------------------------------------------------*/
if((tmp_flag != RESET) && (tmp_it_source != RESET))
{
UART_Receive_IT(huart);
}

如果发送数据,则会进行发送中断处理函数

/* UART in mode Transmitter ------------------------------------------------*/
if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
{
UART_Transmit_IT(huart);
return;
}
c.❤️串口接收中断回调函数 HAL_UART_RxCpltCallback(huart)【需要配置】

HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);

功能:HAL库的中断进行完之后,并不会直接退出,而是会进入中断回调函数中,用户可以在其中设置代码,串口中断接收完成之后,会进入该函数,该函数为空函数,用户需自行修改,

参数:

  • UART_HandleTypeDef *huart UATR的别名 如 : UART_HandleTypeDef huart1; 别名就是huart1

举例: HAL_UART_RxCpltCallback(&huart1){ //用户设定的代码 }

3、串口查询函数

 HAL_UART_GetState();  判断UART的接收是否结束,或者发送数据是否忙碌

  举例:     

while(HAL_UART_GetState(&huart4) == HAL_UART_STATE_BUSY_TX) //检测UART发送结束

三、 HAL库具体实现过程

1、发送函数(重定向)

引入printf重定向代码块

代码最适合加在CubeMX自动生成后的usart.c文件的 / * USER CODE BEGIN 0 * / 和 / * USER CODE END 0 * / 中间

/* USER CODE BEGIN 0 */
#include <stdio.h>

 #ifdef __GNUC__
     #define PUTCHAR_PROTOTYPE int _io_putchar(int ch)
 #else
     #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
 #endif /* __GNUC__*/
 
 /******************************************************************
     *@brief  Retargets the C library printf  function to the USART.
     *@param  None
     *@retval None
 ******************************************************************/
 PUTCHAR_PROTOTYPE
 {
     HAL_UART_Transmit(&huart1, (uint8_t *)&ch,1,0xFFFF);
     return ch;
 }
/* USER CODE END 0 */
添加#include<stdio.h>

比较全局的办法就是将#include直接加入main.h中,因为Cube生成文件大部分都是包含了main.h的,所以除了自建文件几乎都可以全局包含到stdio.h,而且自建文件也可以直接包含main.h,我的习惯是把工程用的共性的概率高的头文件都放在main.h里面,具体位置如下:

//main.h

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include<stdio.h>
/* USER CODE END Includes */

在自建文件使用printf函数时记得#include

main.c内测试的代码:

  while (1)
  {
    /* USER CODE END WHILE */
            printf("串口打印测试\n");
            HAL_Delay(1000);
    /* USER CODE BEGIN 3 */
  }

注意!!!使用此代码时还要在魔术棒那个选项中打勾“UseMicroLIB”,否则stdio.h是编译不了的,但它又不会报错。

参考文章:STM32-HAL库-printf函数重定向(USART应用实例)_hal库printf重定向_Calvin Haynes的博客-CSDN博客

2、⭐接收函数(中断)

因为中断接收函数只能触发一次接收中断,所以我们需要在中断回调函数中再调用一次中断接收函数

具体流程:

1、初始化串口

2、在main中第一次调用接收中断函数

3、进入接收中断,接收完数据 进入中断回调函数

4、修改HAL_UART_RxCpltCallback中断回调函数,处理接收的数据,

5 、回调函数中要调用一次HAL_UART_Receive_IT函数,使得程序可以重新触发接收中断

函数流程图:

HAL_UART_Receive_IT(中断接收函数) -> USART2_IRQHandler(void)(中断服务函数) -> HAL_UART_IRQHandler(UART_HandleTypeDef *huart)(中断处理函数) -> UART_Receive_IT(UART_HandleTypeDef *huart) (接收函数) -> HAL_UART_RxCpltCallback(huart);(中断回调函数)

HAL_UART_RxCpltCallback函数就是用户要重写在main.c里的回调函数。

代码实现:

在main.c中添加下列定义:

#include <string.h>
 
#define RXBUFFERSIZE  256     //最大接收字节数
char RxBuffer[RXBUFFERSIZE];   //接收数据
uint8_t aRxBuffer;            //接收中断缓冲
uint8_t Uart1_Rx_Cnt = 0;        //接收缓冲计数

在main()主函数中,调用一次接收中断函数

/* USER CODE BEGIN 2 */
    HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
/* USER CODE END 2 */

在main.c下方添加中断回调函数

/* USER CODE BEGIN 4 */
 
void HAL_UART_RxCpltCallback(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
   */
 
    if(Uart1_Rx_Cnt >= 255)  //溢出判断
    {
        Uart1_Rx_Cnt = 0;
        memset(RxBuffer,0x00,sizeof(RxBuffer));
        HAL_UART_Transmit(&huart1, (uint8_t *)"数据溢出", 10,0xFFFF);     
        
    }
    else
    {
        RxBuffer[Uart1_Rx_Cnt++] = aRxBuffer;   //接收数据转存
    
        if((RxBuffer[Uart1_Rx_Cnt-1] == 0x0A)&&(RxBuffer[Uart1_Rx_Cnt-2] == 0x0D)) //判断结束位
        {
            HAL_UART_Transmit(&huart1, (uint8_t *)&RxBuffer, Uart1_Rx_Cnt,0xFFFF); //将收到的信息发送出去
            while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX);//检测UART发送结束
            Uart1_Rx_Cnt = 0;
            memset(RxBuffer,0x00,sizeof(RxBuffer)); //清空数组
        }
    }
    
    HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);   //再开启接收中断
}
/* USER CODE END 4 */

参考文章:【STM32】HAL库 STM32CubeMX教程四---UART串口通信详解_hal_uart_transmit-CSDN博客

3、发送函数(DMA转运)

UART DMA函数库

HAL_UART_Transmit();串口发送数据,使用超时管理机制
HAL_UART_Receive();串口接收数据,使用超时管理机制
HAL_UART_Transmit_IT();串口中断模式发送
HAL_UART_Receive_IT();串口中断模式接收
HAL_UART_Transmit_DMA();串口DMA模式发送
HAL_UART_Transmit_DMA();串口DMA模式接收
HAL_UART_DMAPause() 暂停串口DMA
HAL_UART_DMAResume(); 恢复串口DMA
HAL_UART_DMAStop(); 结束串口DMA

在main.C中添加:

/* USER CODE BEGIN Init /
uint8_t Senbuff[] = "\r\n*** Serial Output Message by DMA **\r\n   UART DMA Test \r\n   Zxiaoxuan";  //定义数据发送数组
/ USER CODE END Init */

while循环:

while (1)
{
/* USER CODE END WHILE */
HAL_UART_Transmit_DMA(&huart1, (uint8_t )Senbuff, sizeof(Senbuff));HAL_Delay(1000);/ USER CODE BEGIN 3 */
}

参考文章:【STM32】HAL库 STM32CubeMX教程十一---DMA (串口DMA发送接收)_hal库dma串口接收-CSDN博客

4、接收函数(DMA)

STM32的IDLE的中断产生条件:在串口无数据接收的情况下,不会产生,当清除IDLE标志位后,必须有接收到第一个数据后,才开始触发,一但接收的数据断流,没有接收到数据,即产生IDLE中断

使用DMA+串口接受空闲中断 实现将接收的数据完整发送到上位机的功能

uart.c

volatile uint8_t rx_len = 0;  //接收一帧数据的长度
volatile uint8_t recv_end_flag = 0; //一帧数据接收完成标志
uint8_t rx_buffer[100]={0};  //接收数据缓存数组
void MX_USART1_UART_Init(void)
{

  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
//下方为自己添加的代码
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); //使能IDLE中断

//DMA接收函数,此句一定要加,不加接收不到第一次传进来的实数据,是空的,且此时接收到的数据长度为缓存器的数据长度
    HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);
    
}

uart.h

extern UART_HandleTypeDef huart1;
extern DMA_HandleTypeDef hdma_usart1_rx;
extern DMA_HandleTypeDef hdma_usart1_tx;
/* USER CODE BEGIN Private defines */
 
 
#define BUFFER_SIZE  100  
extern  volatile uint8_t rx_len ;  //接收一帧数据的长度
extern volatile uint8_t recv_end_flag; //一帧数据接收完成标志
extern uint8_t rx_buffer[100];  //接收数据缓存数组

main.c

/*
*********************************************************************************************************
* 函 数 名: DMA_Usart_Send
* 功能说明: 串口发送功能函数
* 形  参: buf,len
* 返 回 值: 无
*********************************************************************************************************
*/
void DMA_Usart_Send(uint8_t *buf,uint8_t len)//串口发送封装
{
 if(HAL_UART_Transmit_DMA(&huart1, buf,len)!= HAL_OK) //判断是否发送正常,如果出现异常则进入异常中断函数
  {
   Error_Handler();
  }

}




/*
*********************************************************************************************************
* 函 数 名: DMA_Usart1_Read
* 功能说明: 串口接收功能函数
* 形  参: Data,len
* 返 回 值: 无
*********************************************************************************************************
*/
void DMA_Usart1_Read(uint8_t *Data,uint8_t len)//串口接收封装
{
    HAL_UART_Receive_DMA(&huart1,Data,len);//重新打开DMA接收
}

while循环

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

    /* USER CODE BEGIN 3 */
         if(recv_end_flag == 1)  //接收完成标志
        {
            
            
            DMA_Usart_Send(rx_buffer, rx_len);
            rx_len = 0;//清除计数
            recv_end_flag = 0;//清除接收结束标志位
//            for(uint8_t i=0;i<rx_len;i++)
//                {
//                    rx_buffer[i]=0;//清接收缓存
//                }
                memset(rx_buffer,0,rx_len);
  }
        HAL_UART_Receive_DMA(&huart1,rx_buffer,BUFFER_SIZE);//重新打开DMA接收
}

main.c中的 HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

void HAL_UART_RxCpltCallback(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
   */
 
    if(huart == &huart2)//目前用不了IDLE标志位在Proteus中不知道为什么无法检测
    {
        uint32_t tmp_flag = 0;
        uint32_t temp;
        tmp_flag =__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE); //获取IDLE标志位
        if((tmp_flag != RESET))//idle标志被置位
        { 
            LED_turn(GPIO_PIN_2);
            __HAL_UART_CLEAR_IDLEFLAG(&huart2);//清除标志位
            //temp = huart1.Instance->SR;  //清除状态寄存器SR,读取SR寄存器可以实现清除SR寄存器的功能
            //temp = huart1.Instance->DR; //读取数据寄存器中的数据
            //这两句和上面那句等效
            HAL_UART_DMAStop(&huart2); //  停止DMA传输,防止
            temp  =  __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);// 获取DMA中未传输的数据个数   
            //temp  = hdma_usart1_rx.Instance->NDTR;// 读取NDTR寄存器,获取DMA中未传输的数据个数,
            rx_len =  BUFFER_SIZE - temp; //总计数减去未传输的数据个数,得到已经接收的数据个数
            recv_end_flag = 1;    // 接受完成标志位置1    
        }
    }
    
    
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/138258.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Javascript享元模式

Javascript享元模式 1 什么是享元模式2 内部状态与外部状态3 享元模式的通用结构4 文件上传4.1 对象爆炸4.2 享元模式重构 5 没有内部状态的享元模式6 对象池7 通用对象池实现 1 什么是享元模式 享元&#xff08;flyweight&#xff09;模式是一种用于性能优化的模式&#xff0…

数据恢复工具推荐,高效恢复,这4款很实用!

很多电脑用户都会选择将文件直接保存在电脑上&#xff0c;但是在实际的操作过程中&#xff0c;数据丢失的情况难免会出现。而实用的数据恢复工具或许能有效帮助我们找回丢失的数据。电脑上有哪些使用效果比较好的数据恢复工具呢&#xff1f; 今天小编总结了几款好用的工具&…

leetcode:21. 合并两个有序链表

一、题目 函数原型&#xff1a; struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) 二、思路 合并两个有序链表为一个新的升序链表&#xff0c;只需要遍历两个有序链表并比较结点值大小&#xff0c;依次将较小的结点尾插到新链表即可。 三、代码…

C#中.NET Framework 4.8控制台应用通过EF访问已建数据库

目录 一、创建.NET Framework 4.8控制台应用 二、建立数据库 1. 在SSMS中建立数据库Blogging 2.在VS上新建数据库连接 三、安装EF程序包 四、自动生成EF模型和上下文 1.Blog.cs类的模型 2.Post.cs类的模型 3.BloggingContext.cs数据库上下文 五、编写应用程序吧 我们…

Vatee万腾数字化引领未来,vatee创新思维

随着数字化时代的全面来临&#xff0c;Vatee万腾正以其独特的创新思维&#xff0c;为未来描绘出令人瞩目的数字化画卷。在这个充满变革和机遇的时代&#xff0c;Vatee万腾所展现的数字化引领力和创新思维&#xff0c;成为业界的翘楚。 Vatee万腾的创新思维贯穿于其数字化战略的…

数据结构 | 队列的实现

数据结构 | 队列的实现 文章目录 数据结构 | 队列的实现队列的概念及结构队列的实现队列的实现头文件&#xff0c;需要实现的接口 Queue.h初始化队列队尾入队列【重点】队头出队列【重点】获取队列头部元素获取队列队尾元素获取队列中有效元素个数检测队列是否为空销毁队列 Que…

更新:扶风解析计费系统V1.8.2源码/免授权优化版+附教程/修正完整版

源码简介&#xff1a; 最新的扶风解析计费系统V1.8.2源码&#xff0c;它是修正完整版&#xff0c;免授权优化版附带了教程。是更新优化版最新 V1.8 版本免授权版本。 之前分享过1.7.1版本的扶风计费系统&#xff0c;该版本已经存在相当长的时间&#xff0c;并且一直没有进行更…

一文读懂:什么是RISC-V?为啥它是国产芯崛起的关键?

各位ICT的小伙伴们大家好呀。 提到CPU&#xff0c; 大家首先就会想到"卡脖子"事件。 X86和ARM的IP授权虽然方便&#xff0c;但是不自主和不可控&#xff0c; 一被限制就可能导致国内一夜间"无芯"可用。 今天我们就来聊聊一个解决芯片卡脖子的有效方式-…

多路复用IO:select、poll、epoll

文章目录 一、常见的IO模型二、什么是多路IO复用&#xff1f;三、select、poll、epollselectpollepoll 四、总结 一、常见的IO模型 概念优点       缺点适用场景阻塞IOBlocking IO当应用程序执行IO操作时&#xff0c;会被阻塞&#xff0c;直到数据准备好或者IO操作完成才…

项目管理工具:提高团队协作效率,确保项目按时完成

项目管理对于企业的成功至关重要&#xff0c;一个好的项目管理工具可以提高团队协作效率&#xff0c;确保项目按时完成&#xff0c;并保持项目进度的高效跟踪。 近年来&#xff0c;一款名为“进度猫”的项目管理工具逐渐崭露头角&#xff0c;它以其独特的功能和优势&#xff…

删除快一年的数据,能够恢复吗?

在数字化时代&#xff0c;数据已经成为了企业和个人生活中不可或缺的一部分。然而&#xff0c;由于各种原因&#xff0c;我们有时会需要删除某些数据&#xff0c;比如过期的文件、无用的照片或者账号下的旧信息等。但是&#xff0c;当我们删除这些数据后&#xff0c;是否真的能…

提高生产效率和质量,这个方式很有效

在当今竞争激烈的市场环境下&#xff0c;企业需要不断提高生产效率和质量水平以保持竞争优势。而精益生产正是一种能够帮助企业实现这一目标的方法。其中&#xff0c;持续改善是精益生产的核心理念之一。它是指通过不断地寻找和消除浪费&#xff0c;改善流程和提高效率来实现质…

PHP中$_SERVER全局变量

在PHP中&#xff0c;$_SERVER 是一个全局数组变量&#xff0c;它包含了有关服务器和当前脚本的信息。$_SERVER 数组中的每个元素都是服务器环境的一个参数&#xff0c;如请求的方法、请求的 URI、客户端 IP 地址等。 PATH 系统环境变量的值&#xff0c;包含了多个目录的路径…

Xmind 24 for Mac思维导图软件

XMind是一款流行的思维导图软件&#xff0c;可以帮助用户创建各种类型的思维导图和概念图。 以下是XMind的主要特点&#xff1a; - 多样化的导图类型&#xff1a;XMind提供了多种类型的导图&#xff0c;如鱼骨图、树形图、机构图等&#xff0c;可以满足不同用户的需求。 - 强大…

通过 Kaptcha 插件生成字符验证码

Kaptcha 是 Google 的⼀个⾼度可配置的实⽤验证码⽣成⼯具&#xff0c;我们选择的是⼀个适配SpringBoot的 开源项⽬ 生成的验证码效果如下&#xff1a; 原理 验证码可以客户端生成,也可以服务器生成. 对于普通的字符验证码, 后端通常分两部分&#xff1a; ⼀&#xff1a;⽣成验…

能跟“猫主子”聊天了!生成式AI最快5年内破译第一种动物语言

image.png ChatGPT用它自己的方式来理解世界&#xff0c;类似的技术是否也能用来学习动物的语言&#xff1f; 所罗门能够与动物交流并不是因为他拥有魔法物品&#xff0c;而是因为他有观察的天赋。 ——康拉德・劳伦兹《所罗门王的指环》 在《狮子王》、《疯狂动物城》等以动…

“颠覆·挑战·极致”华瑞指数云ExponTech WDS新一代产品重新定义企业存储和数据架构

数字经济发展&#xff0c;离不开数据这一信息时代的“新能源”。当数据爆发式增长&#xff0c;企业何处寻得一款在性能和成本上皆具备良好表现的“储能仓”&#xff1f;国内数据存储领域领先厂商华瑞指数云ExponTech自主研发的高性能、高可靠的分布式存储产品ExponTech WDS成为…

SCADA系统在化工行业应用解决方案和注意事项

SCADA系统在化工行业的数字化工厂中具有广泛的应用解决方案。SCADA系统通过实时监控和远程控制&#xff0c;帮助化工企业实现生产过程的自动化和数字化管理。以下是化工行业的SCADA系统行业应用中可以解决的客户痛点以及相关的详细设计说明&#xff1a; 远程监测和控制&#xf…

【VECTOR】:CAN OE Alyzer使用

CAN OE Alyzer使用 工程搭建新建工程DBC文件导入插入IG模块Trace查看录制Logger回放Trace 实际应用将需要回放报文的导出需要报文添加导出的报文&#xff0c;回放 工程搭建 新建工程 配置硬件1&#xff1a;通道数量选择&#xff08;根据使用情况而定&#xff09; 硬件配置2&am…

从0到0.01入门React | 006.精选 React 面试题

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…