STM32串口通信 (采用链表接收不定长数据帧)

2023-05-16

STM32串口通信 链表接收不定长数据帧

  • 数据帧说明
    • 不太恰当的比方
  • 数据缓冲链表结构
  • 效果展示
  • 工程文件

数据帧说明

STM32数据寄存器为USARTx->DR寄存器
在这里插入图片描述
可以看到DR寄存器只有[8:0]位可以使用,第8位用于奇偶校验,也就是DR寄存器一次只能接受8bit既1字节的数据。

不太恰当的比方

打个比方就是一个篮子 (DR寄存器) 只能装8 (bit) 个物品,
我们用这个篮子把水果放到我们的仓库 (MCU) 中,
别人把物品一个一个放入篮子里,装满8个我们就把这篮子东西放到仓库里。
但是我们觉得这样放太乱了,就在仓库里面划了一片地,在上面贴了一个苹果标签,以后接收到的东西就放这里。

但是仓库里又不只是放苹果,而且我们也不能确定装进我篮子里的是不是苹果,也就是说我们单次接受数据的时候基本没有办法判断数据的可靠性,而且数据也是单一的。

那这样吧,我们定个规矩,你往我篮子里放了3个苹果5个橘子就代表你要开始发送有用数据了,

第二篮子你就放苹果,

第三篮子你就放橘子,

第四篮子如果放的是3个橘子5个苹果,那这次接收就结束了,
然后我也不用检查哪一个篮子是苹果哪一个是橘子,
直接就可以把第二篮放到苹果的位置,第三篮放到橘子的位置。

打的比方有些不太恰当,但基本就是这么个意思,这样我们就可以一帧接收多样的数据,通过确定开始和结束的协议也提高数据的可靠性。

但是问题又来了,我们只有一个篮子,一下子接收不了那么多篮子的数据,

那怎么办,我们在仓库立划个缓存区用于存储别人发送的数据,也就是我们把这次的数据接收完再做处理,但是这个缓冲区一般都是使用数组定义,也就是要事先规定好,你发给我8个篮子的东西,我就划8个篮子的地方,一旦开始接收数据,我事先划的地放大小就不能改了,因为数组定义的时候要事先给定长度。

呐有没有可以边接收边划分空间的方法呢,最近也在看链表的相关知识于是便想到将链表用于数据缓冲区,这样就可以边接受数据边开辟空间了。

数据缓冲链表结构

struct Frame
{
    u8 data;            //数据域
	struct Frame *next; //指针域指向下一个节点
 };

我使用的链表比较简单,一是用于节省空间,另一方面自己会的也不多。

数据域就用于存储串口传来的8bit数据,

指针域就用于指向下一个节点,把两个节点联系起来,最后一个节点指向NULL代表链表结尾。

基本结构就是这样:
在这里插入图片描述
头结点的作用是用于指向下一个数据,data存储一帧的节点数量。

比如一帧的数据为 :A5 34 56 5A

那么:Head->data=4,也就是除去头结点一共接收了4个节点

一定要注意 !!不用的指针一定要指向NULL,防止产生野指针造成内存泄漏

那么我们定义一个全局的头指针就可以在程序了任何地方使用了

struct Frame *Head=NULL; //头指针
extern struct Frame *Head;//声明全局变量

这个头指针现在是没有存储空间的因为它现在只是个地址信息

/**
**********************************
* 函数名:Head_Init
* 描述  :初始化头结点
* 输入  :无
* 输出  : 无
* 注    :
**********************************
*/
void Head_Init(void)
{
    Head=(struct Frame *)malloc(sizeof(struct Frame));
    if(Head==NULL)
        exit(1);

    Head->data=0;
    Head->next=NULL;
}

我们给头指针分配完内存,头指针就是头结点了,
头节点的data就可以存储节点长度了。

然后我们就可以在串口中断里写我们规定的协议了


#define Frame_Head 0xA5                  //帧头
#define Frame_END  0x5A                  //帧尾

/**********帧头帧尾标志位**********/
bool Frame_Head_sta=0;
bool Frame_End_sta=0;

/**
**********************************
* 函数名:USART1_IRQHandler
* 描述  :接受数据格式为:数据长度->帧头->数据->数据...->帧尾
*                         例:4 A5 12 34 56 5A
* 输入  :无
* 输出  : 无
* 注    :
**********************************
*/
void USART1_IRQHandler(void)               
{
    u8 Res;
    if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) 
    {
        Res =USART_ReceiveData(USART1);	//读取接收到的数据
        //判断是否接受到帧头
        if(Res==Frame_Head)
        {
            Frame_Head_sta=1;
        }//判断是否接受到帧尾->若还没有接受到帧头不接受帧尾
        else if(Res==Frame_END)
        {
            Frame_End_sta=1;
        }

        //已经接受到帧头或者帧尾
        if((Frame_Head_sta==1)||(Frame_End_sta==1))
        {
            //数据长度加一
            Head->data+=1;
            //添加数据进入链表->尾插法
            Head=Add_Data(&Head,Res);
        }
    }
}

协议定的比较简单,看一下注释基本就明白了

链表的插入方法采用的尾插法,其实头插法更快一些,不需要轮训数据,但是我感觉这样处理数据不太舒服,于是便写成了尾插法。

/**
**********************************
* 函数名:Add_Data
* 描述  :尾插法插入节点
* 输入  :(指向头结点的指针   需要插入的数据)
* 输出  : 无
* 注    :
**********************************
*/
struct Frame* Add_Data(struct Frame **head,u8 Res)
{
    struct Frame *data,*temp;
    //分配内存
    data=(struct Frame *)malloc(sizeof(struct Frame));
    //数据域存储串口数据
    data->data=Res;       
    //指针域指向NULL
    data->next=NULL;
    //指向头指针指向的位置不是NULL   ->头结点后面已经连接了其他节点
    if(*head!=NULL)
    {
        //指向头节点的指针传给temp   ->保证*head不发生变化
        temp=*head;
        //一个节点一个节点往后查询直到temp->next=NULL,也就是最后一个节点的位置
        while(temp->next)
        {
            temp=temp->next;
        }
        //把最后一个节点指向的位置改成data也就是在最后一个节点插入新的节点
        temp->next=data;
    }
    else//头节点后还未插入节点
    {
        *head=data;
    }
    return *head;
}

一帧数据接受完之后一定要及时处理释放内存空间,不然会造成内存溢出,程序跑飞。

写到这里我又遇到一个问题,我接受的数据是不定长的,那么我存储起来也要是不定长的

但是我当时还没有好的思路,于是写了下面这坨代码

/**
/**
**********************************
* 函数名:Frame_Manage
* 描述  :链表数据处理
* 输入  :无
* 输出  : 无
* 注    :
**********************************
*/
void Frame_Manage(  u8 *data0,u8 *data1,u8 *data2,
                    u8 *data3,u8 *data4,u8 *data5,
                    u8 *data6,u8 *data7,u8 *data8)
{
    u8 i;
    //接受到帧头和帧尾
    if(Frame_Head_sta&&Frame_End_sta)
    {
        //标志位清零
        Frame_Head_sta=0;
        Frame_End_sta=0;
        //遍历链表并存储数据
        while(Head!=NULL)
        {   
            switch(i)
            {
                case 0: *data0=Head->data;break;
                case 1: *data1=Head->data;break;
                case 2: *data2=Head->data;break;
                case 3: *data3=Head->data;break;
                case 4: *data4=Head->data;break;
                case 5: *data5=Head->data;break;
                case 6: *data6=Head->data;break;
                case 7: *data7=Head->data;break;
                case 8: *data8=Head->data;break;
            }
            //释放节点内存
            free(Head);
            //地址往下走
            Head=Head->next;
            i++;
        }
        //再次分配内存给头结点 因为已经释放了投机点的内存
        Head_Init();
    }
}

这个代码真是相当难受,明明是不定长的接受,后来又变成固定长度的存储

后来我才想到头结点里存放的数据长度,
可以直接利用这个数据长度,使用malloc开辟一个相同长度的空间存储帧数据,

使用u8类型的指针指向开辟的内存空间,用于存放一帧的数据,

记录好首地址的位置,经过 Frame_Manage() 函数后,

我们的帧数据就存储在 *Frame_data 所指向的空间

下次帧数据来了以后再释放空间就可以了,重新更新数据长度就可以了

修改后的代码如下:


u8 *Frame_data=NULL;     //帧数据缓冲区
u8 *Frame_data_Head=NULL;//只用于存放缓冲区首地址

extern u8 *Frame_data;     //帧数据存储
extern u8 *Frame_data_Head;//只用于存放缓冲区首地址

/**
**********************************
* 函数名:Frame_Manage
* 描述  :链表数据处理
* 输入  :无
* 输出  : 无
* 注    :
**********************************
*/
void Frame_Manage(void)
{
    //如果接收到帧头 帧尾
    if(Frame_Head_sta&&Frame_End_sta)
    {       
        //释放上次缓冲区空间->放入Frame_data_Head
        free(Frame_data_Head);
        //指向NULL->防止乱指
        Frame_data=NULL;
        //开辟一帧的大小(可变)
        Frame_data=(u8 *)malloc(sizeof(u8)*(Head->data));
        //存放空间首地址->用于free和读取数据
        Frame_data_Head=Frame_data;
        //清除标志位
        Frame_Head_sta=0;
        Frame_End_sta=0;
        while(Head!=NULL)
        {   
            //存放帧数据
            *Frame_data=(Head->data);
            //地址->后移
            Frame_data++;
            free(Head);
            Head=Head->next;
        }
        Head_Init();
        //归还空间首地址
        Frame_data=Frame_data_Head;
    }
}

这片空间的用法和指向数组的指针的用法相同,

通过下面这个代码就可以读取数据帧任意一点的数据了

/**
**********************************
* 函数名:Find_Frame
* 描述  :读取固定位置数据
* 输入  :位置,起始地址
* 输出  : u8 数据
* 帧格式    :4 A5 34 56 5A
* 位置      : 0  1  2  3  4
**********************************
*/
u8 Find_Frame(u8 team,u8 *head)
{
    head+=team;
    return *head;
}

很显然8bit无符号型明显是不够用的
就写了俩u8融合成u16的,其他类型的融合思路基本相似

/**
**********************************
* 函数名:Fusion
* 描述  :u8融合u16
* 输入  :team0 高8位位置 team1 低8位位置
* 输出  : 无
* 注    :
**********************************
*/
u16 Fusion(u8 team0,u8 team1)
{
    team0=Find_Frame(team0,Frame_data);
    team1=Find_Frame(team1,Frame_data);
    return ((team0<<8)+team1);
}

效果展示

我们直接用串口发送16进制且包含帧头 (A5) 帧尾 (5A) 的数据帧STM32就可以接受到数据帧并打印出来。
在这里插入图片描述
可以看出我们发送数据帧长度变化的时候,STM32同样可以接收该长度的数据帧,完全不用修改程序,非常的方便,而且也是用多少拿多少,非常的人性化。

文件放在下面了,芯片类型为F103VCT6

工程文件

提取码:qqy7

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32串口通信 (采用链表接收不定长数据帧) 的相关文章

  • 处理器指令周期执行时间

    我的猜测是 no operation 内在 ARM 指令应花费 1 168 MHz 来执行 前提是每个NOP在一个时钟周期内执行 我想通过文档验证这一点 有关处理器指令周期执行时间的信息是否有标准位置 我试图确定 STM32f407IGh6
  • c项目makefile多重定义错误

    这个问题是一个对应于创建的repexthis问题 在我的嵌入式 C 项目中 我有两个独立的板 我想为每个板创建两个 c 文件 master c 和 Slave c 其中包含自己的特定main 功能 我使用 stm32cumbemx 生成带有
  • 在 MCU 内部 FLASH 中从一个固件跳转到另一个固件

    我目前正在开发针对 STM32F030C8 的引导加载程序固件应用程序 我在分散文件中指定引导加载程序应用程序将占用主内存位置 0x08000000 到 0x08002FFF 扇区 0 到扇区 2 我还编写了一个主固件应用程序 存储在0x0
  • 133-基于stm32单片机停车场车位管理系统Proteus仿真+源程序

    资料编号 133 一 功能介绍 1 采用stm32单片机 4位数码管 独立按键 制作一个基于stm32单片机停车场车位管理系统Proteus仿真 2 通过按键进行模拟车辆进出 并且通过程序计算出当前的剩余车位数量 3 将剩余的车位数量显示到
  • 136-基于stm32单片机家庭温湿度防漏水系统设计Proteus仿真+源程序

    资料编号 136 一 功能介绍 1 采用stm32单片机 LCD1602显示屏 独立按键 DHT11传感器 蜂鸣器 制作一个基于stm32单片机家庭温湿度防漏水系统设计Proteus仿真 2 通过DHT11传感器检测当前温湿度 并且显示到L
  • 匹配 STM32F0 和 zlib 中的 CRC32

    我正在研究运行 Linux 的计算机和 STM32F0 之间的通信链路 我想对我的数据包使用某种错误检测 并且由于 STM32F0 有 CRC32 硬件 并且我在 Linux 上有带有 CRC32 的 zlib 所以我认为在我的项目中使用
  • 物联网网关

    物联网网关是 连接物联网设备和互联网的重要桥梁 它负责将物联网设备采集到的数据进行处理 存储和转发 使其能够与云端或其它设备进行通信 物联网网关的作用是实现物联网设备与云端的无缝连接和数据交互 物联网网关功能 数据采集 物联网网关可以从物联
  • STM32F103概要

    The STM32F103x4 STM32F103x6 STM32F103xC STM32F103xD and STM32F103xE are a drop in replacement for STM32F103x8 B medium d
  • VS Code 有没有办法导入 Makefile 项目?

    正如标题所说 我可以从现有的 Makefile 自动填充 c cpp properties json 吗 Edit 对于其他尝试导入 makefile 的人 我找到了一组脚本 它们完全可以实现我想要实现的目标 即通过 VS Code 管理
  • 在 Atollic TrueStudio、STM32CubeMX 中导入 C 库

    我目前正在开发 STM32F767ZI Nucleo 板和一个小安全芯片 microchip atecc508a 通过 i2c 连接进行连接 该芯片有一个可用的库加密验证库 https github com MicrochipTech cr
  • 串口通讯第一次发送数据多了一字节

    先初始化IO再初始化串口 导致第一次发送时 多出一个字节数据 优化方案 先初始化串口再初始化IO 即可正常通讯
  • STM32F207 I2C 测试失败

    我正在使用 STM32F207 微控制器在 STM3220G EVAL 板上学习嵌入式开发 我尝试通过连接同一芯片上的两个 I2C2 和 I2C3 模块并发送 接收字符来测试 I2C 接口 这是我当前编写的代码 使用 mdk arm 5 i
  • 毕设开题分享 单片机智能教室系统(智能照明+人数统计)

    1 简介 Hi 大家好 今天向大家介绍一个学长做的单片机项目 单片机智能教室系统 智能照明 人数统计 大家可用于 课程设计 或 毕业设计 项目分享 https gitee com feifei1122 simulation project
  • Freertos低功耗管理

    空闲任务中的低功耗Tickless处理 在整个系统运行得过程中 其中大部分时间都是在执行空闲任务的 空闲任务之所以执行 因为在系统中的其他任务处于阻塞或者被挂起时才会执行 因此可以将空闲任务的执行时间转换成低功耗模式 在其他任务解除阻塞而准
  • Arm:objcopy 如何知道 elf 中的哪些部分要包含在二进制或 ihex 中?

    我正在开发一个项目 其中涉及解析arm elf 文件并从中提取部分 显然 elf 文件中有很多部分没有加载到闪存中 但我想知道 objcopy 到底如何知道要在二进制文件中包含哪些部分以直接闪存到闪存中 以arm elf文件的以下reade
  • STM32内部时钟

    我对 STM32F7 设备 意法半导体的 Cortex M7 微控制器 上的时钟系统感到困惑 参考手册没有充分阐明这些时钟之间的差异 SYSCLK HCLK FCLK 参考手册中阅读章节 gt RCC 为 Cortex 系统定时器 SysT
  • 使用 STM32 USB 设备库将闪存作为大容量存储设备

    我的板上有这个闪存IC 它连接到我的STM32F04 ARM处理器 处理器的USB端口可供用户使用 我希望我的闪存在通过 USB 连接到 PC 时被检测为存储设备 作为第一步 我在程序中将 USB 类定义为 MSC 效果很好 因为当我将主板
  • 当端点和 PMA 地址均更改时,CubeMX 生成的 USB HID 设备发送错误数据

    我正在调试我正在创建的复合设备的问题 并在新生成的仅 CubeMX 代码中重新创建了该问题 以使其更容易解决 我添加了少量代码main 让我发送 USB HID 鼠标点击 并在按下蓝色按钮时使 LED 闪烁 uint8 t click re
  • STM32 传输结束时,循环 DMA 外设到存储器的行为如何?

    我想问一下 在以下情况下 STM32 中的 DMA SPI rx 会如何表现 我有一个指定的 例如 96 字节数组 名为 A 用于存储从 SPI 接收到的数据 我打开循环 SPI DMA 它对每个字节进行操作 配置为 96 字节 是否有可能
  • stm32l0: 执行MI命令失败。使用 vFlashErase 数据包擦除闪存时出错

    我正在使用 Nucleo STM32L031 和 AC6 STM32 工作台 eclipse 我编写应用程序并进入调试模式 一切正常 直到我在应用程序中添加另一个功能 我注意到当我删除 评论 新函数 软件可以再次进入调试模式 但是当我添加

随机推荐