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
    +关注

    关注

    2313

    文章

    11191

    浏览量

    374580
  • 链表
    +关注

    关注

    0

    文章

    80

    浏览量

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

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    米尔MYD-YF13X引入STM32MP135F安全芯!系统、安全、功能三重升级

    MYD-YF135F 本次版本引入STM32MP135F 安全芯片 ,并推出基于该芯片的硬件平台 MYD-YF135F。该组合提供以下硬件级安全能力: 该设计可以有效提升设备工业、物联网及海外市场应用
    发表于 04-17 18:35

    引入STM32MP135F安全芯!米尔MYD-YF13X系统、安全、功能三重升级

    米尔电子MYD-YF13X平台资料及SDK迎来重要更新。本次发布的V2.0.0版本系统版本、安全能力以及功能支持方面进行了全面升级。本次发布引入安全芯片STM32MP135F,并推出搭载该芯片
    的头像 发表于 04-16 08:06 389次阅读
    <b class='flag-5'>引入</b><b class='flag-5'>STM32</b>MP135F安全芯!米尔MYD-YF13X系统、安全、功能三重升级

    如何在 STM32Cube CMake 工程添加源文件

    STM32CubeMX 能够很方便的导出 CMake 工程。例如,如下图所示 STM32CubeMX的 ToolChain/IDE 中选择 Cmake。CMake 工程可以很容易被
    发表于 04-15 16:07 0次下载

    STM32N6 PSRAM 上运行代码的调试

    开发其产品过程,使用了 STM32N657X0H3Q。客户的代码很大,不能放在SRAM 上运行,想放在 PSRAM 上去运行,并咨询如何在 STM32CubeIDE 上进行调试。2.2. 重现问题关于如何将用户代码放置
    发表于 04-15 16:02 0次下载

    LAT1621_STM32N6 PSRAM 上运行代码的调试

    STM32N6 项目中,用户代码可能会比较大,此时仅用内部 SRAM 并不能满足用代码运行需求。那么,将代码放置到 PSRAM 上运行也是个不错的选择。 客户开发其产品过程,使用了
    发表于 03-11 10:44 0次下载

    无数据域双向链表的代码

    ); return 0; } 在这个示例,我们定义了一个包含指向前一个节点和后一个节点的结构体 Node,以及一个包含整数数据和 Node 结构体的结构体 Data。然后实现了插入和打印链表的函数。在打
    发表于 12-11 06:56

    标准库下配置的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 496次阅读
    【留言有奖】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 3862次阅读

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

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

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

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

    STM32IDE如何设定代码到ITCM运行?

    近期使用STM32MUX生成STM32IDE的代码(MCU是STM32H743),目前希望可以将部分代码定位到ITCM运行,加快处理速度,关于代码
    发表于 06-24 06:45

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

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