0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

在STM32中为什么要引入链表?

GReq_mcu168 来源:知晓编程 作者:知晓编程 2021-05-03 10:30 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

1、为何引入链表

在程序中经常面临一个问题,我们需要保存一定数量的对象,但是对象数目是不确定的,或者说是随时增加或减少的。这时候最简单的方法是创建一个足够大的数组,用来存储这些对象。我最近开发一个项目就遇到类似的问题,下面我把问题简化一下。

需求:通过PC下发一些矩形的坐标和宽高信息,每个区域有个ID编号,并在这些矩形内填充一定的数据。

通常情况下,最简单易懂的做法是,限制最多5个区域,每个区域存储1K数据。因此设置了这样的一个结构体(类似于面向对象语言里说的成员属性)。

typedef struct Area_Inf{ uint8_t ID; uint8_t X; uint8_t Y; uint8_t Width; uint8_t Height; uint8_t data_len;}Area_Inf_Typedef;

然后定义结构体的实体。

#define Area_Num 5#define Area_cache 1024

Area_Inf_Typedef Area_Info[Area_Num];uint8_t Area_Data[Area_Num*Area_cache];//存储区域的数据

/*找到ID为5的区域,并将数据拷贝出去*/void main(){ uint8_t i; uint8_t data[1024]; for(i = 0;i 《 Area_Num;i++) { if(Area_Info[i].ID == 5) { memcpy(data,&Area_Data[i*Area_cache ],Area_Info[i].data_len); } }}

上面这种做法是最简单易懂的,但不灵活,比如有客户要求10个区域,但是每个区域存储的数据很少,根本用不到1K。虽然上面的程序已经使用了宏定义,只需要修改宏定义就能实现要求。但这意味着不同的客户,需要编译不同的固件。

#define Area_Num 10#define Area_cache 512

这样的程序存在的问题:

1、在内存资源很紧缺的单片机程序中,当区域数据很少时,这种程序的处理方法浪费了大量的内存空间。

2、数值固定,需要存储更多区域,即使还有内存,还是需要修改宏定义,重新编译固件,不灵活。

这时需要引入链表来解决这个问题。

2、链表实现

链表实际上是线性表的链式存储结构,与数组不同的是,它是用一组任意的存储单元来存储线性表中的数据,存储单元不一定是连续的,且链表的长度不是固定的,链表数据的这一特点使其可以非常的方便地实现节点的插入和删除操作。链表的每个元素称为一个节点,每个节点都可以存储在内存中的不同的位置,为了表示每个元素与后继元素的逻辑关系,以便构成“一个节点链着一个节点”的链式存储结构,除了存储元素本身的信息外,还要存储其直接后继信息,因此,每个节点都包含两个部分,第一部分称为链表的数据区域,用于存储元素本身的数据信息。

6ee7ead4-9e2c-11eb-8b86-12bb97331649.png

对于上面的问题,我们使用链表解决,需要配合内存管理才能实现。内存管理这一块,大家可以自己编写内存管理驱动,也可以使用C库的malloc和free函数。如何字节编写内存管理驱动不是本文的重点,下文将使用C库的malloc和free函数进行内存管理。

使用链表的方式,在原有的成员属性结构体的前提上,还要再封装多一层链表管理。以单向链表为例:

typedef struct Area_Inf{ uint8_t ID; uint8_t X; uint8_t Y; uint8_t Width; uint8_t Height; uint8_t data_len; uint8_t* Area_Data;}Area_Inf_Typedef;

typedef struct Area_List_Inf{ Area_Inf_Typedef *Area_Inf; struct Area_List_Inf *next_Area_Inf; //用于指向下一个}Area_List_Inf_Typedef;

Area_List_Inf_Typedef *Head_Area_List; //链表的头指针

由于在定义的时候,只定义了一个头指针,那么它也只是个指向了Area_List_Inf_Typedef也就是链表结构体的指针,同样没有内存空间,在没有创建新增链表之前,它是一个野指针。

所以,在具体应用之前,需要先执行一个初始化操作,也就是申请空间给链表管理结构体,然后头指针指向这个空间。

/*** @brief 动态区链表初始化* @return int */int Area_List_Init(void){ //申请链表类型大小的空间,并让头指针指向它 Head_Area_List = (Area_List_Inf_Typedef*)malloc(sizeof(Area_List_Inf_Typedef)); if(Head_Area_List == NULL) return false; //同时要标记下一个信息为空 Head_Area_List-》next_Area_Inf = NULL; return true;}

通过PC下发一个新的区域信息后,增加新区域到链表末尾。

/*** @brief 在链表末尾增加一个区域参数* @param Area_Inf 增加的区域区参数指针* @return int */int Add_Area_ToList(Area_Inf_Typedef *Area_Inf){ Area_List_Inf_Typedef *p = Head_Area_List; while(p-》next_Area_Inf!=NULL) { p = p-》next_Area_Inf; } //先申请链表结构体的空间,因为后续还要继续增加 p-》next_Area_Inf = (Area_List_Inf_Typedef*)malloc(sizeof(Area_List_Inf_Typedef)); if(p-》next_Area_Inf == NULL) return false;//申请不到内存,返回失败 //指向刚刚申请的空间,并为需要存放的动态区信息申请对应的内存 p = p-》next_Area_Inf; p-》Area_Inf = (Area_Inf_Typedef*)malloc(sizeof(Area_Inf_Typedef)); if(p-》Area_Inf == NULL) { free(p);//由于申请失败,先前申请的链表空间也要释放 return false; } memcpy(p-》Area_Inf,Area_Inf,sizeof(Area_Inf_Typedef)); /*拷贝数据*/ p-》Area_Inf-》Area_Data = (uint8_t*)malloc(Area_Inf-》data_len); if(p-》Area_Inf-》Area_Data == NULL) { free(p-》Area_Inf); free(p); return false; } memcpy(p-》Area_Inf-》Area_Data,Area_Inf-》Area_Data,Area_Inf-》data_len); //标记这个链表的尾部 p-》next_Area_Inf=NULL; //添加成功 return true;}

通过PC下发一个删除指定ID的区域命令。

/*** @brief 根据区域ID删除动态区* @param num 区域ID* @return int */int Delete_Area_Accordingn_ID(int num){ int res = false; Area_List_Inf_Typedef *p = Head_Area_List; while(p-》next_Area_Inf!=NULL) { Area_List_Inf_Typedef *temp = p; p = p-》next_Area_Inf; if(p-》Area_Inf-》ID == num)//匹配到对应的值 { temp-》next_Area_Inf = p-》next_Area_Inf; //释放内存空间 free(p-》Area_Inf-》Area_Data); free(p-》Area_Inf); free(p); p=temp; res = true; } } return res;}

看了上面的驱动函数,相信大家已经明白,大家可以自行编写一些驱动,下面我实现的三个简单函数。

/*** @brief 根据区域ID找到链表* @param data_p 链表指针* @param num 区域ID编号* @return int */int Find_Area_According_ID(Area_Inf_Typedef **data_p,int num){ Area_List_Inf_Typedef *p = Head_Area_List; while(p-》next_Area_Inf!=NULL) { p = p-》next_Area_Inf; if(p-》Area_Inf-》ID == num)//匹配到对应的值 { *data_p = p-》Area_Inf; return true; } } return false;}/*** @brief 删除所有区域* */int Delete_All_Area(void){ int res = false; Area_List_Inf_Typedef *p = Head_Area_List; while(p-》next_Area_Inf!=NULL) { Area_List_Inf_Typedef *temp = p; p = p-》next_Area_Inf; temp-》next_Area_Inf = p-》next_Area_Inf; //释放内存空间 free(p-》Area_Inf-》Area_Data); free(p-》Area_Inf); free(p); p=temp; res = true; } return res;}/*** @brief 打印链表信息* */void Printf_Area_Inf(void){ int i=0; Area_List_Inf_Typedef *p = Head_Area_List; printf(“list ID X Y Width Height Area_Data

”); while(p-》next_Area_Inf!=NULL) { p = p-》next_Area_Inf; printf(“ %d %d %d %d %d %d %s

”,i,p-》Area_Inf-》ID,p-》Area_Inf-》X,p-》Area_Inf-》Y,p-》Area_Inf-》Width,p-》Area_Inf-》Height,p-》Area_Inf-》Area_Data); i++; } printf(“----------------------end-----------------------

”);}

3、测试函数

下面编写一个测试函数,可以测试,链表的初始化,增加一个区域,删除指定区域,根据ID返回区域信息,删除所有区域接口

/*** @brief 链表测试函数* */void list_main(){ int i,j; Area_Inf_Typedef temp; Area_Inf_Typedef **data_p; data_p = NULL; printf(“------------------List test---------------------

”); if(!Area_List_Init( )) { printf(“Memory fail.。

”); } for(i=0;i《5;i++) { temp.ID = i; temp.X = 5+i; temp.Y = i; temp.Width = 10+i; temp.Height = 10+i; temp.data_len = i+1; temp.Area_Data = (uint8_t*)malloc(temp.data_len+1); for(j=0;j《temp.data_len;j++) { temp.Area_Data[j] = j+0x30; } temp.Area_Data[j] = 0; if(!Add_Area_ToList(&temp)) { printf(“Add Area %d Area_Info fail

”,i); } } Printf_Area_Inf(); printf(“

-------------Delete ID of Area is 3-------------

”); Delete_Area_Accordingn_ID(3); Printf_Area_Inf(); temp.ID = 9; temp.data_len = 10; temp.Area_Data = (uint8_t*)malloc(temp.data_len+1); for(j=0;j《temp.data_len;j++) { temp.Area_Data[j] = j+0x30; } temp.Area_Data[j] = 0; if(!Add_Area_ToList(&temp)) { printf(“Add Area %d info fail

”,temp.ID); } printf(“

--------------Add ID of Area is 9---------------

”); Printf_Area_Inf(); Find_Area_According_ID(data_p,2); temp.ID = (*data_p)-》ID; Delete_All_Area(); printf(“

--------------Delete All Area-------------------

”); Printf_Area_Inf(); while(1);}

测试结果

6ef62022-9e2c-11eb-8b86-12bb97331649.png

IAR和keil工程代码开源地址:

https://github.com/strongercjd/STM32_Linklist

(提示:公众号不支持外链接,请复制链接到浏览器下载)

如果大家手中有板子可以调试,可以看《一文了解串口打印》文章,使用串口打印。如果临时没有板子可以debug,可以模拟测试,IAR设置如下:

选择Simulator调试

6eff06a6-9e2c-11eb-8b86-12bb97331649.png

打开View-》TerminalI/O,就可以看到打印信息

6f0adc92-9e2c-11eb-8b86-12bb97331649.png

原文标题:链表在STM32中的应用

文章出处:【微信公众号:玩转单片机】欢迎添加关注!文章转载请注明出处。

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • STM32
    +关注

    关注

    2305

    文章

    11120

    浏览量

    371094
  • 链表
    +关注

    关注

    0

    文章

    80

    浏览量

    10999
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    标准库下配置的stm32f103x的spi引入rtthread后收发的消息就错了,为什么?

    标准库下配置的stm32f103x的spi引入rtthread后收发的消息就错了,引入之前没问题,请问这是啥情况
    发表于 10-13 08:07

    rt_object_get_information获取到的链表为空怎么解决?

    rtt启动过程,初始化堆的时候,进入rt_object_init,调用rt_object_get_information获取到的链表为空,导致系统起不来。
    发表于 10-11 11:44

    rt-thread系统,使用stm32,怎样设置接收中断字节数?

    rt-thread系统,使用stm32,怎样设置接收中断字节数
    发表于 09-15 06:17

    【留言有奖】2025 STM32研讨会,RT-Thread现场期待与你共会!| 活动预告

    2025年9月11日及9月17日,STM32研讨会将走进北京和上海,为大家深入解读STM32的中国战略,并围绕STM32不同领域的最新产品布局和生态展开主题演讲,包括边缘人工智能、电
    的头像 发表于 09-07 11:10 333次阅读
    【留言有奖】2025 <b class='flag-5'>STM32</b>研讨会,RT-Thread<b class='flag-5'>在</b>现场期待与你共会!| 活动预告

    意法半导体:STM32CubeMX将不再集成到STM32CubeIDE

    从2025年11月发布的版本开始,我们将仅以 独立 版本的形式提供 STM32CubeIDE 和 STM32CubeMX 。 STM32CubeMX将不再集成到STM32CubeIDE
    的头像 发表于 09-01 17:18 2620次阅读

    德国莫尔利用全新的在线配置器改进了电缆引入装置设计流程

    MurrplastikSystemtechnik GmbH(德国莫尔系统技术有限公司)总部位于巴登符腾堡州奥本韦勒市,全球范围内开发、制造和销售电缆管理系统、电缆标签及组件。从机器人到食品行业
    发表于 08-20 14:29

    对于STM32CubeMx使用FMC的NAND FLASH里面配置这些时间有什么说明吗?

    对于STM32CubeMx使用FMC的NAND FLASH里面配置这些时间有什么说明吗?
    发表于 07-21 07:07

    LTspice里压敏电阻MOV怎么引入?

    LTspice里压敏电阻MOV怎么引入
    发表于 04-28 08:26

    0STM32的SysTick是个啥?咋用?(可下载)

    的使能位清除,就永不停息。SysTickSTM32xx中文参考手册》里面基本没有介绍,其详细介绍,内核编程手册工程
    发表于 04-01 14:52 0次下载

    ST-LINKSTM32CubeProgrammer无法连接到器件怎么解决?

    ST-LINKSTM32CubeProgrammer无法连接到器件,对比发现serial number有些差别,这个ST-LINK的序列号比较奇怪,有哪位高手可以修改这个序列号么
    发表于 03-14 14:38

    STM32微控制器实现数据加密的方法

    STM32微控制器实现数据加密,可以通过多种方法和技术来确保数据的安全性。以下是一些常见的方法和步骤: · 使用内置加密库: · · STM32提供了专门的加密库
    发表于 03-07 07:30

    电压跟随器测量系统的应用

    通常用作电路的缓冲级,以隔离前后级电路之间的相互影响。 测量系统,传感器输出的信号往往较弱,且易受干扰。通过引入电压跟随器,可以有效地隔离传感器电路与后续测量电路之间的相互影响,
    的头像 发表于 02-18 16:08 944次阅读

    微软寻求365 Copilot引入非OpenAI模型

    担心企业用户使用365 Copilot时面临的成本和速度问题,因此正在寻求更多样化的技术来源。尽管微软与OpenAI之间有着紧密的合作关系,并允许微软定制OpenAI的模型,但微软仍在努力寻找其他选择。 据称,微软不仅在训练自己的小型模型,包括最新的Phi-4,还在努力定制其他
    的头像 发表于 12-25 10:41 833次阅读

    AMC1200BDUBR工作时引入了112MHz和132MHz的EMC噪声,怎么消除?

    请问前辈,我用的TI芯片:AMC1200BDUBR 工作时引入了112MHz和132MHz的EMC 噪声。请问电路怎么改进才能消除这个噪声?望前辈能帮忙推荐解决方法 不胜感激!
    发表于 12-23 07:31

    ADS1299DAISY-CHAIN模式下只能配置第一个AD吗,那后面几个都是怎么配置寄存器,都和第一个一样吗?

    大家: 1ADS1299DAISY-CHAIN 模式下只能配置第一个AD么,那后面几个都是怎么配置寄存器,都和第一个一样么? 2 手册上写了有关时钟配置的问题,ADS1299
    发表于 12-20 06:47