当前位置: 首页 > article >正文

HAL库:串口 不定长数据接收 + 循环收发缓冲区 + 空闲中断 收发数据

目录

HAL库:不定长数据接收 + 循环收发缓冲区 + 空闲中断 收发数据

串口:多指针定位+收发循环使用缓冲区设计

文件架构:

程序部分大体思路

Uart.h

Uart.c

stm32fxx_It.c

main.c


HAL库:不定长数据接收 + 循环收发缓冲区 + 空闲中断 收发数据

串口:多指针定位+收发循环使用缓冲区设计

STM32C8T6单片机一共有20K的RAM

设计介绍:

  • 定义接收、发送缓冲区(2K 2048字节大小) 用于在接收时不频繁进入中断。

  • 定义结构体,成员为 :Start End 指针 用于在一次串口接收完毕,程序进入空闲中断时。使用Start、End 标记这一段字节的起始和节数地址。 用于读取

  • 定义结构体数组 可以存10段上述的一段字节的起始地址。[0:9]

  • 定义结构体指针 In Out 初始化时,In、Out都指向结构体数组的第一个元素。当接收到一段字节后。In跳到结构体数组的第二个元素。准备一下次接收。 当Out和In的地址不同时。代表接收到数据,此时Out可通过第一个元素的Start 和 End 读出这段 字节,再跳到下一个元素,等待下次不相同时读出。

  • 定义结构体指针 END END指针固定指向结构体数组的最后一个元素 [9] 当Out跳到最后一个成员,即Out 或 In = END时、 在写入、读取后回滚到结构体数组的第一个元素。

  • 接收回卷: 约定最大发送值为300。当2048字节的接收缓冲区余量小于最大发送值300时,回卷。重新从起始地址开始缓存

  • 发送回卷: 当发送字节数量 小于当前发送缓冲余量时,进行回卷。最大值为2048。

文件架构:

  • Uart.h :定义了与串口通信相关的数据结构和函数声明,用于管理串口通信中的接收和发送缓冲区,以及串口的初始化操作。
  • Uart.c :主要针对串口 1 进行配置为空闲中断 ,包括初始化参数、设置缓冲区指针、配置 IO 口和使能空闲中断等。 实现了数据收发、中断处理及缓冲区管理,确保串口 1 高效稳定地进行数据通信。
  • stm32fxx_It.c : 主要是 void USART1_IRQHandler(void) 的中断函数:该函数是串口 1 的中断服务函数。首先调用 HAL 库的中断处理函数,后续 检测到串口 1 进入空闲状态时,清除空闲中断标志位,计算接收字节数量并累加,然后终止接收,触发终止回调函数。终止函数在Uart.c中
  • main.c :在主循环中,通过判断接收和发送缓冲区的指针状态,实现数据的接收和发送,并在指针到达末尾时进行回卷操作。当接收缓冲区有数据时,将其拷贝到发送缓冲区并移动输出指针;当发送缓冲区有数据且处于空闲状态时,发送数据并移动输出指针。

程序部分大体思路

当接收到没有超过最大接收量256 - 1 时,会触发空闲中断。 (如果等于256 会同时触发接收完成中断和空闲中断。)

在接受到后, 会进入串口中断中

在中断判断空闲中断标志位,记录空闲前接收了多少的字节

然后终止当前的接收(否则会继续等到完成接收)。

进入终止接收回调函数。

在函数中会对每次传入的 一段 字节 的起始位和结束位做标记。并且会根据指针的位置进行回卷等操作。

在主函数中。会不断循环检查是否接收到一段字节。即Rx_In_ArrPtr 结构体指针的位置是否与Rx_Out_ArrPtr 的地址相同,如果不同就代表已经记录了一个接收到的一段字节的起始位置。就可以把他移动到 发送缓冲区。

在主函数中调用这个函数, 在往发送缓冲区移动的时候仍然会对这段字节 的起始位和结束位做标记。并且会根据指针的位置进行回卷等操作。

在检查到发送缓冲区的In和Out不同后,有会去判断并发送这一段的 字节。

这只是一个大概的顺序, 具体的 内容 去看 文件吧。里边的注释挺详细的

Uart.h

#ifndef __UART_H
#define __UART_H

#include "string.h" //memry操作用的头文件

#define U1_RX_SIZE 2048 //接收、发送数组缓冲区长度 宏定义
#define U1_TX_SIZE 2048
#define U1_RX_MAX  256  //约定发送方单次发送字节 最大值 - 1。
                        //否则当接收满了,就会产生完成回调和空闲回调两个回调。

typedef struct
{
    //用于标记接收到的一段字节的起始和结束位置
    uint8_t* Start;
    uint8_t* End;
}LCB;    //location Ctrl Block 位置单元组

typedef struct
{
    /*接收缓冲区配置*/
    uint32_t    Rx_Counter;          //接收缓冲区 累计收到的字节
    LCB         Rx_LocationArr[10];  //接收缓冲区 字节段 位置单元组 数组
    LCB        *Rx_In_ArrPtr;           //用于保存 一段字节的 位置
    LCB        *Rx_Out_ArrPtr;          //用于根据位置 读出缓冲区中的值
    LCB        *Rx_End_ArrPtr;          //用于定义位置单元组 数组的终点位置,防止指针越界
    /*发送缓冲区配置*/
    uint32_t    Tx_Counter;          //发送缓冲区 累计发送的字节。用于计算余量
    LCB         Tx_LocationArr[10];  //发送缓冲区 字节段 位置单元组 数组
    LCB        *Tx_In_ArrPtr;        //用于保存 一段字节的 位置
    LCB        *Tx_Out_ArrPtr;       //用于根据位置 读出缓冲区中的值
    LCB        *Tx_End_ArrPtr;       //用于定义位置单元组 数组的终点位置,防止指针越界
    /*串口处理结构体配置*/
    UART_HandleTypeDef Uart;         //方便一次配置完成。
    
    /*发送忙状态*/
    uint8_t     Tx_State;
}UCB;    //UART Ctrl Block 串口缓冲区单元组

//串口1 初始化
void Uart1_Init(uint32_t Bandrate);
//串口1 接收缓冲区 规则  指针位置 初始化
void Uart1_Ptr_Init(void);
//串口1 发送数据
void Uart1_Tx_Data(uint8_t* data, uint8_t len);

//UART2初始化
void Uart2_Init(uint32_t Bandrate);
//UART3初始化
void Uart3_Init(uint32_t Bandrate);

//外部声明的总控结构体
extern UCB Uart1;
extern UART_HandleTypeDef Uart2;
extern UART_HandleTypeDef Uart3;

#endif

Uart.c

#include "stm32f1xx_hal.h"
#include "Uart.h"

//串口1 配置结构体
UCB Uart1;
UART_HandleTypeDef Uart2;
UART_HandleTypeDef Uart3;

//定义接收和发送的缓冲区
uint8_t U1_Rx_Buff[U1_RX_SIZE];
uint8_t U1_Tx_Buff[U1_TX_SIZE];

UCB uarb1;
//初始化串口1
void Uart1_Init(uint32_t Bandrate)
{
    Uart1.Uart.Instance = USART1;//选择串口1
    Uart1.Uart.Init.BaudRate = Bandrate;//波特率
    Uart1.Uart.Init.WordLength = UART_WORDLENGTH_8B;//数据长度
    Uart1.Uart.Init.StopBits = UART_STOPBITS_1;//停止位
    Uart1.Uart.Init.Parity = UART_PARITY_NONE;//奇偶校验
    Uart1.Uart.Init.Mode = UART_MODE_TX_RX;//收发模式
    Uart1.Uart.Init.HwFlowCtl = UART_HWCONTROL_NONE;//流控
    
    HAL_UART_Init(&Uart1.Uart);
    
    /*初始化串口1 缓冲区 规则  指针位置*/
    Uart1_Ptr_Init();
    /*使能空闲中断*/
    __HAL_UART_ENABLE_IT(&Uart1.Uart,UART_IT_IDLE);
    HAL_UART_Receive_IT(&Uart1.Uart, Uart1.Rx_In_ArrPtr->Start, U1_RX_MAX);
    //开启接收中断。缓冲区位置为刚才记录的Start。接受的数量为U1_RX_MAX
}
//初始化串口1 缓冲区 规则  指针位置函数
void Uart1_Ptr_Init(void)
{
    Uart1.Rx_In_ArrPtr  = &Uart1.Rx_LocationArr[0];     //输入 位置数组对 指向 第一个元素
    Uart1.Rx_Out_ArrPtr = &Uart1.Rx_LocationArr[0];     //输出 位置数组对 指向 第一个元素
    Uart1.Rx_End_ArrPtr = &Uart1.Rx_LocationArr[9];     //结束 位置指向 最后一个元素
    Uart1.Rx_Counter = 0;   //用于累加收到的字节数量
    Uart1.Rx_In_ArrPtr->Start = U1_Rx_Buff;             //记录接收缓冲区的起始地址
    
    Uart1.Tx_In_ArrPtr  = &Uart1.Tx_LocationArr[0];     //输入 位置数组对 指向 第一个元素
    Uart1.Tx_Out_ArrPtr = &Uart1.Tx_LocationArr[0];     //输出 位置数组对 指向 第一个元素
    Uart1.Tx_End_ArrPtr = &Uart1.Tx_LocationArr[9];     //结束 位置指向 最后一个元素
    Uart1.Tx_Counter = 0;   //用于累加收到的字节数量
    Uart1.Tx_In_ArrPtr->Start = U1_Tx_Buff;             //记录接收缓冲区的起始地址
}

//串口1 发送数据
void Uart1_Tx_Data(uint8_t* data, uint8_t len)//参数为数据的起始位置,和发送多少
{
    //判断这次要发送的数据量是不是 小于 剩余缓冲区的大小.是:还能放
    if( len < (U1_TX_SIZE - Uart1.Tx_Counter) )
    {
        Uart1.Tx_In_ArrPtr->Start = &U1_Tx_Buff[Uart1.Tx_Counter]; //记录下一次 开始记录的位置
    }
    else    //不够了。回卷
    {
        Uart1.Tx_Counter = 0;   //重置计数
        Uart1.Tx_In_ArrPtr->Start = U1_Tx_Buff; //标记起始地址为缓冲区起始位置
    }
    
    
    memcpy(Uart1.Tx_In_ArrPtr->Start, data, len);   //拷贝源地址到标记的Start位置
    Uart1.Tx_Counter += len;    //累加,为下次拷贝做准备
    Uart1.Tx_In_ArrPtr->End = &U1_Tx_Buff[Uart1.Tx_Counter - 1];//标记End
    
    Uart1.Tx_In_ArrPtr++;//准备记录下一次的 起始位置
    
    //判断是否指向最后一个元素
    if(Uart1.Tx_In_ArrPtr == Uart1.Tx_End_ArrPtr)
    {
        Uart1.Tx_In_ArrPtr = &Uart1.Tx_LocationArr[0];   //回到0号位置
    }
}

//初始化串口2
void Uart2_Init(uint32_t Bandrate)
{
    Uart2.Instance = USART2;//选择串口2
    Uart2.Init.BaudRate = Bandrate;//波特率
    Uart2.Init.WordLength = UART_WORDLENGTH_8B;//数据长度
    Uart2.Init.StopBits = UART_STOPBITS_1;//停止位
    Uart2.Init.Parity = UART_PARITY_NONE;//奇偶校验
    Uart2.Init.Mode = UART_MODE_TX_RX;//收发模式
    Uart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;//流控
    
    HAL_UART_Init(&Uart2);
}

//初始化串口3
void Uart3_Init(uint32_t Bandrate)
{
    Uart3.Instance = USART3;//选择串口3
    Uart3.Init.BaudRate = Bandrate;//波特率
    Uart3.Init.WordLength = UART_WORDLENGTH_8B;//数据长度
    Uart3.Init.StopBits = UART_STOPBITS_1;//停止位
    Uart3.Init.Parity = UART_PARITY_NONE;//奇偶校验
    Uart3.Init.Mode = UART_MODE_TX_RX;//收发模式
    Uart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;//流控
    
    HAL_UART_Init(&Uart3);
}

//定义 强声明 的IO口和时钟打开的 回调函数
void HAL_UART_MspInit(UART_HandleTypeDef *huart)
{
    //判断传入的结构体中第一个成员  来判断是串口1 、 2、 3
    if(huart->Instance == USART1)
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();   //打开GPIOA时钟
        __HAL_RCC_USART1_CLK_ENABLE();  //打开串口1 的时钟
        
        GPIO_InitTypeDef GPIO_InitStructure;
        //TX初始化 PA9
        GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;//输出
        GPIO_InitStructure.Pin = GPIO_PIN_9;
        GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_MEDIUM;//速度中等为10M
        HAL_GPIO_Init(GPIOA,&GPIO_InitStructure);
        
        //RX初始化 PA10
        GPIO_InitStructure.Mode = GPIO_MODE_AF_INPUT;//输入
        GPIO_InitStructure.Pin = GPIO_PIN_10;
        GPIO_InitStructure.Pull = GPIO_NOPULL;//浮空输入
        HAL_GPIO_Init(GPIOA,&GPIO_InitStructure);
        
        HAL_NVIC_SetPriority(USART1_IRQn,3,0);
        HAL_NVIC_EnableIRQ(USART1_IRQn);    //配置优先级和使能中断
    }
    else if(huart->Instance == USART2)//如果是串口2
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();//打开GPIOA(PA2 PA3)时钟
        __HAL_RCC_USART2_CLK_ENABLE();//打开串口2 的时钟
    
        GPIO_InitTypeDef GPIO_InitStructure;
        //TX初始化 PA2
        GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;//输出
        GPIO_InitStructure.Pin = GPIO_PIN_2;
        GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_MEDIUM;//速度中等为10M
        HAL_GPIO_Init(GPIOA,&GPIO_InitStructure);
        
        //RX初始化 PA3
        GPIO_InitStructure.Mode = GPIO_MODE_AF_INPUT;//输入
        GPIO_InitStructure.Pin = GPIO_PIN_3;
        GPIO_InitStructure.Pull = GPIO_NOPULL;//浮空输入
        HAL_GPIO_Init(GPIOA,&GPIO_InitStructure);
    }
    else if(huart->Instance == USART3)//如果是串口3
    {
        __HAL_RCC_GPIOB_CLK_ENABLE();//打开GPIOB(PB10 PB11)时钟
        __HAL_RCC_USART3_CLK_ENABLE();//打开串口3 的时钟
    
        GPIO_InitTypeDef GPIO_InitStructure;
        //TX初始化 PB10
        GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;//输出
        GPIO_InitStructure.Pin = GPIO_PIN_10;
        GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_MEDIUM;//速度中等为10M
        HAL_GPIO_Init(GPIOB,&GPIO_InitStructure);
        
        //RX初始化 PB11
        GPIO_InitStructure.Mode = GPIO_MODE_AF_INPUT;//输入
        GPIO_InitStructure.Pin = GPIO_PIN_11;
        GPIO_InitStructure.Pull = GPIO_NOPULL;//浮空输入
        HAL_GPIO_Init(GPIOB,&GPIO_InitStructure);
    }
}

//定义 强声明的 UART 接收完成回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    //判断
    if(huart->Instance == USART1)
    {
        
    }
}

//定义 强声明的 UART中断错误回调函数
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
    //判断串口
    if(huart->Instance == USART1)
    {
        
    }
}

//定义 强声明的 UART发送完成回调函数
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)
    {
        Uart1.Tx_State = 0; //退出忙状态
    }
}

//强定义的 串口终止 回调函数
void HAL_UART_AbortReceiveCpltCallback(UART_HandleTypeDef *huart)
{
    //判断串口
    if(huart->Instance == USART1)
    {
        // 标记这次字节段的结束的地址
        Uart1.Rx_In_ArrPtr->End = &U1_Rx_Buff[Uart1.Rx_Counter - 1]; //从0开始存,所以要减1
        
        Uart1.Rx_In_ArrPtr++;   //In指向下一个 位置对结构体 数组的下一个元素
        //判断是否指向最后一个元素
        if(Uart1.Rx_In_ArrPtr == Uart1.Rx_End_ArrPtr)
        {
            Uart1.Rx_In_ArrPtr = &Uart1.Rx_LocationArr[0];   //回到0号位置
        }
        
        //判断缓冲区余量是否小于U1_RX_MAX
        if( (U1_RX_SIZE - Uart1.Rx_Counter) < U1_RX_MAX )
        {   
            //如果小于就回滚
            Uart1.Rx_Counter = 0;   //计数清零
            Uart1.Rx_In_ArrPtr->Start = U1_Rx_Buff; //开始指针指向缓冲区起始地址
        }
        else//余量还够
        {
            //从第一个字节段末尾的下一个地址开始存
            Uart1.Rx_In_ArrPtr->Start = &U1_Rx_Buff[Uart1.Rx_Counter];   
            //重新打开中断接收
            HAL_UART_Receive_IT(&Uart1.Uart, Uart1.Rx_In_ArrPtr->Start, U1_RX_MAX);
        }
    }

}

stm32fxx_It.c

/*-------------------------------------------------*/
/*                                                 */
/*          实现各种中断服务函数的源文件           */
/*                                                 */
/*-------------------------------------------------*/

#include "stm32f1xx_hal.h"   
#include "stm32f1xx_it.h"

#include "Uart.h"

void EXTI15_10_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10);
}
void EXTI0_IRQHandler(void)
{
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
}

void USART1_IRQHandler(void)
{
    HAL_UART_IRQHandler(&Uart1.Uart);
    
    if( __HAL_UART_GET_FLAG(&Uart1.Uart,UART_FLAG_IDLE) )//如果进入空闲状态
    {
        __HAL_UART_CLEAR_IDLEFLAG(&Uart1.Uart); //清除空闲中断标志位
        Uart1.Rx_Counter += (U1_RX_MAX - Uart1.Uart.RxXferCount);   //提前读取Count,计算此次字节数量并累加
        HAL_UART_AbortReceive_IT(&Uart1.Uart);  //终止接收(会清除记录的接收量:Count。有终止回调函数)
    }
}

/*-------------------------------------------------*/
/*函数名:不可屏蔽中断处理函数                     */
/*参  数:无                                       */
/*返回值:无                                       */
/*-------------------------------------------------*/
void NMI_Handler(void)
{

}

/*-------------------------------------------------*/
/*函数名:硬件出错后进入的中断处理函数             */
/*参  数:无                                       */
/*返回值:无                                       */
/*-------------------------------------------------*/
void HardFault_Handler(void)
{

}
/*-------------------------------------------------*/
/*函数名:软中断,SWI 指令调用的处理函数           */
/*参  数:无                                       */
/*返回值:无                                       */
/*-------------------------------------------------*/
void SVC_Handler(void)
{
    
}
/*-------------------------------------------------*/
/*函数名:可挂起的系统服务处理函数                 */
/*参  数:无                                       */
/*返回值:无                                       */
/*-------------------------------------------------*/
void PendSV_Handler(void)
{
    
}
/*-------------------------------------------------*/
/*函数名:SysTic系统嘀嗒定时器处理函数             */
/*参  数:无                                       */
/*返回值:无                                       */
/*-------------------------------------------------*/
void SysTick_Handler(void)
{  
    HAL_IncTick();  
}

main.c

#include "stm32f1xx_hal.h"

#include "rcc.h"
#include "led.h"
#include "key.h"

#include "Uart.h"
/*函 数 名:HAL库:中断方式。实现UART串口数据收发。 定长  20字节 */
 

int main (void)
{
    HAL_Init();//初始化HAL库
    
    RccClock_Init();//配置时钟树72M

    LED_Init();
    
    Uart1_Init(921600);//初始化串口1
    
    while(1)
    {   
        //如果接收缓冲区 In指针和Out指针不是一个地址。那么就说明接收缓冲区有数据了
        if(Uart1.Rx_Out_ArrPtr != Uart1.Rx_In_ArrPtr)
        {
            //拷贝这次接收到的字节 到发送缓冲区 .注意是Out 的地址,不是in
            Uart1_Tx_Data( Uart1.Rx_Out_ArrPtr->Start, (Uart1.Rx_Out_ArrPtr->End - Uart1.Rx_Out_ArrPtr->Start +1));
            Uart1.Rx_Out_ArrPtr++;  //向后挪
            //如果等于END  则回卷 
            if( Uart1.Rx_Out_ArrPtr == Uart1.Rx_End_ArrPtr )
            {
                Uart1.Rx_Out_ArrPtr = &Uart1.Rx_LocationArr[0]; //记录下一次 开始记录的位置
            }
        }
        
        //如果发送缓冲区 In指针和Out指针不是一个地址。那么就说明发送缓冲区有数据了。并且此时处于空闲状态
        if( (Uart1.Tx_Out_ArrPtr != Uart1.Tx_In_ArrPtr) && (Uart1.Tx_State == 0) )//0为空闲。1 为忙
        {
            Uart1.Tx_State = 1; //进入忙状态
            HAL_UART_Transmit_IT( &Uart1.Uart, Uart1.Tx_Out_ArrPtr->Start, (Uart1.Tx_Out_ArrPtr->End - Uart1.Tx_Out_ArrPtr->Start + 1) );
            Uart1.Tx_Out_ArrPtr++;  //向后挪
            //如果等于END  则回卷 
            if( Uart1.Tx_Out_ArrPtr == Uart1.Tx_End_ArrPtr )
            {
                Uart1.Tx_Out_ArrPtr = &Uart1.Tx_LocationArr[0]; //记录下一次 开始记录的位置
            }
        }
    }
}


http://www.kler.cn/a/282171.html

相关文章:

  • 【数据结构与算法】查找
  • 矩阵的对角化特征值分解
  • 魔方和群论
  • python怎么加锁
  • HMI FUXA测试
  • 本地部署Apache Answer搭建高效的知识型社区并一键发布到公网流程
  • SpringMVC框架学习
  • 使用ftl文件导出时,多层嵌套循环
  • 如何开Stand Up Meeting
  • 深度学习基础—Softmax回归
  • 虚拟化云管服务奥创的优化升级以及多集群下VPC网络实现
  • XML CSS:结构和样式的完美结合
  • python(9) : docker方式运行python程序(自启动,守护)
  • 恒电流间歇滴定法 (GITT) 测试教程
  • 国产游戏行业的技术突破与未来展望:挑战与机遇并存
  • macos MacPort 包管理工具安装和使用
  • TCP的连接建立及报文段首部格式
  • P1516 青蛙的约会(exgcd)
  • 操作系统
  • 最火视频素材去哪里找?热门的视频素材网站库分享给你
  • 工业软件架构1:(QT和C++实现)
  • LLama3技术报告笔记(垂直能力)
  • js逆向——异步栈分析(上)
  • Faiss入门心得---向量数据库Faiss的搭建与使用
  • C#/WinForm实现炸弹人游戏
  • PaddleNLP 3.0 支持大语言模型开发