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

news2024/12/28 5:57:25

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

后续文章会同时发表在个人博客(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    
        }
    }
    
    
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

破解制造业痛点,数据化方案助力高效生产!

随着科技的飞速发展&#xff0c;我们已进入数据化时代。在这个时代&#xff0c;制造业面临着前所未有的挑战。本文将探讨制造业在当今数据化时代遇到的问题以及相应的解决方案。 一、背景 制造业作为全球经济发展的重要支柱&#xff0c;正面临着数据化带来的巨大变革。在这个以…

DP4301-M无线模块一款SUB-1G无线收发模块

DP4301-M无线模块是一款低成本高效率工作于1GHz以内的收发模块&#xff0c;支持中国智能电无线 集抄标准470MHz~ 510MHz&#xff0c;兼容433MHz ISM/SRD频段均可使用。 此模块且前已经超大量应用于国标智能无线抄表及物联网自组网等双向数据传输系统方案&#xff0c;模 块具备的…

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

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

基于springboot实现生鲜超市管理的设计与实现系统【项目源码】

基于springboot实现生鲜超市管理的设计与实现系统演示 Java技术 Java是由Sun公司推出的一门跨平台的面向对象的程序设计语言。因为Java 技术具有卓越的通用性、高效性、健壮的安全性和平台移植性的特点&#xff0c;而且Java是开源的&#xff0c;拥有全世界最大的开发者专业社群…

SAP ABAP选择屏幕程序语法及实例

选择屏幕有单个栏位检索&#xff08;PARAMETERS&#xff09;和范围筛选&#xff08;SELECT-OPTION&#xff09;两种。 1、单个栏位检索&#xff08;PARAMETERS&#xff09; 语法&#xff1a; parameters[default ][lower case][obligatory] [as checkbox][radiobutton group…

2.jvm类加载系统

目录 概述类加载器执行顺序加载时机与过程类加载的四个时机一个类的一生 类加载途径 自定义类加载器工作准备编写自定义加载器结果 结束 概述 类加载器 jvm 的类加载是通过 ClassLoader 及其子类来完成的。 有以下类加载器 注意&#xff1a; bootstrap 引导程序 根据上图总结…

vue-组件注册及使用

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Vue篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容-组件注册及使用 目录 1、组件的注册及使用 2、组件常用属性 2.1、directive 2.2、computed 2.…

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

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

一个车厢号码识别算法(2005年的老程序----ccc)

一个车厢号码识别算法&#xff08;2005年的老程序----ccc&#xff09; 2023-09-18 ccc 程序的识别效果 对图中的车厢号码部分用上下两条线限定分为&#xff0c;然后进行识别。 从上面的识别效果可以看出&#xff0c;识别算法具有一定的鲁棒性&#xff0c;能够适应车厢号码的各…

如何改变偏执的性格?

偏执不等同于固执和顽固&#xff0c;固执和顽固更多是好面子的因素&#xff0c;是自尊心的一种表现&#xff0c;而偏执更多是表示处于“不知”的心理状态。当然这里仅仅是讨论性格上的偏执&#xff0c;从性格的角度来分析&#xff0c;如果从精神角度来分析偏执那就比较严重了&a…

在以BUF,字节存储区中,存放有n个带符号整数。试编写统计其中负偶数个数(假设≤9)并且显示。

;默认认采用ML6.11汇编程序 DATAS SEGMENT;此处输入数据段代码BUF DB -2,2,3,4,-4N$-BUF DATAS ENDS STACKS SEGMENT;此处处输入堆栈段代码 STACKS ENDS CODES SEGMENTASSUME CS:CODES,DS: DATAS, SS:STACKS START:MOV AX, DATASMOV DS,AXMOV BX,0MOV CX,0 LOP: mov AX,[BX] RO…

2023.11-9 hive数据仓库,概念,架构,元数据管理模式

目录 0.数据仓库和数据库 数据仓库和数据库的区别 数据仓库基础三层架构 一.HDFS、HBase、Hive的区别 二.大数据相关软件 三. Hive 的优缺点 1&#xff09;优点 2&#xff09;缺点 四. Hive 和数据库比较 1&#xff09;查询语言 2&#xff09;数据更新 3&#xff09;…

Misc | 相当于签到 第二届“奇安信”杯网络安全技能竞赛

题目描述&#xff1a; 图片似乎经过了什么处理&#xff0c;你能否将其复原呢&#xff1f; 密文&#xff1a; 下载附件&#xff0c;解压得到一张.jpg图片。 解题思路&#xff1a; 1、一张图片&#xff0c;典型的图片隐写。放到Kali中&#xff0c;使用binwalk检测&#xff0c;确…

探索人工智能领域——每日30个名词详解【day2】

目录 前言 正文 总结 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很高兴与大家相识&#xff0c;希望我的博客能对你有所帮助。 &#x1f4a1;本文由Filotimo__✍️原创&#xff0c;首发于CSDN&#x1f4da;。 &#x1f4e3;如需转载&#xff0c;请事先与我联系以…

JL-03小型气象站气象环境在线监测设备自动上传并保存数据

JL-03小型气象站产品概述 小型气象站用于对风速、风向、雨量、空气温度、空气湿度、太阳辐射、光照强度、土壤温度、土壤湿度、蒸发量、大气压力等气象要素进行现场监测。既可以通过无线通讯将数据传送至云平台&#xff0c;又可以通过配套的数据采集通讯线与计算机进行连接&am…

智能PDU在现代智慧医院机房末端配电系统中的应用分析

随着智慧医院的高速建设和发展&#xff0c;电子计算机及各类通讯设备在医院中广泛应用&#xff0c;医院信息化程度在不断提高&#xff0c;对医院内网、外网、无线网及设备网的稳定运行提出了更高的要求。信息机房作为医院所有网络数据存储交换的中心&#xff0c;它的7x24小时安…

操作系统——内存管理(一文搞懂操作系统的内存管理)

VIRT(虚拟内存)、RES(常驻内存)和SHR(共享内存) VIRT&#xff08;虚拟内存&#xff09; 进程“需要的”虚拟内存大小&#xff0c;包括进程使用的库、代码、数据&#xff0c;以及malloc、new分配的堆空间和分配的栈空间等&#xff1b;假如进程新申请10MB的内存&#xff0c;但实…

软考中级-软件设计师知识点

软考中级-软件设计师知识点 1. cpu、总线、io控制方式、寄存器2.cpu和dma3.海明码4.中断向量、中断响应时间、保存现场5.补码6.指令流水线计算公式7.存储器分类8.总线9.补码、移码10.漏洞扫描11.木马和病毒12.编译与反编译13.死锁判断14.数据库三大范式15.数据库连结和映射16.最…

Node-RED系列教程-28修改UI默认背景图片

主要使用到如下节点: 实现过程如下: 步骤1:编写背景图片请求服务. http in节点配置: 读文件节点配置: http out节点配置: 到此,背景图片请求服务搭建完成。

制作一本3D仿真翻页电子画册,精致又美观

3D仿真翻页电子画册&#xff0c;可能很多小伙伴都不知道是什么样子的&#xff0c;我用一个图例告诉大家&#xff0c;大家看完就知道3D仿真翻页电子画册到底是什么样子啦&#xff01; 随着人们生活水平的提高&#xff0c;审美也在发生着变化。很显然&#xff0c;之前那种简单的文…