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

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

3天内不再提示

深度解析链表在STM32中的应用

GReq_mcu168 来源:开源博客 作者:Firefly_cjd 2021-05-02 10:33 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

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

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

选择Simulator调试

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

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

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

编辑:lyn

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

    关注

    2305

    文章

    11120

    浏览量

    371150
  • 链表
    +关注

    关注

    0

    文章

    80

    浏览量

    11000

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

文章出处:【微信号:mcu168,微信公众号:硬件攻城狮】欢迎添加关注!文章转载请注明出处。

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

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

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

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

    SMT与DIPPCBA加工的关键差异解析

    一站式PCBA加工厂家今天为大家讲讲PCBA加工如何选择更适合的工艺?SMT与DIPPCBA加工的差异解析PCBA(印制电路板组装)加工
    的头像 发表于 10-07 10:35 268次阅读

    边聊安全 | 安全芯片的守护神:BIST机制的深度解析

    BIST机制的深度解析写在前面:安全芯片的设计与验证过程,工程师常会遇到一个关键概念——BIST(Built-InSelf-Test,内置自检测)。初次接触这一术语时,许多人容易将
    的头像 发表于 09-05 16:17 29次阅读
    边聊安全 | 安全芯片的守护神:BIST机制的<b class='flag-5'>深度</b><b class='flag-5'>解析</b>

    PTC热敏电阻储能系统的应用及工作原理深度解析

    PTC热敏电阻储能系统的应用及工作原理深度解析 一、PTC热敏电阻核心特性 PTC(Positive Temperature Coefficient Thermistor)是一种典
    发表于 09-02 14:23

    微机消谐器是什么?技术原理深度解析

    6-35kV中性点不接地电网,**铁磁谐振**是引发电压异常(如PT爆炸、设备损坏)的主要元凶。微机消谐器正是专为解决这一顽疾而生的智能装置。 核心技术原理深度解析: 1.  精准
    的头像 发表于 08-15 09:07 635次阅读

    深度解析10BASE-T1S PLCA的多节点通信效率

    发送数据,避免出现碰撞。借助PLCA机制提升多节点通信效率是10BASE-T1S技术网络设计的重点课题之一。本篇文章将基于负载率、时延和抖动三个维度,深度解析1
    的头像 发表于 08-01 16:40 1475次阅读
    <b class='flag-5'>深度</b><b class='flag-5'>解析</b>10BASE-T1S PLCA的多节点通信效率

    GPU架构深度解析

    GPU架构深度解析从图形处理到通用计算的进化之路图形处理单元(GPU),作为现代计算机不可或缺的一部分,已经从最初的图形渲染专用处理器,发展成为强大的并行计算引擎,广泛应用于人工智能、科学计算
    的头像 发表于 05-30 10:36 1321次阅读
    GPU架构<b class='flag-5'>深度</b><b class='flag-5'>解析</b>

    Nginx核心功能深度解析

    Nginx核心功能深度解析
    的头像 发表于 05-09 10:50 694次阅读

    解锁未来汽车电子技术:软件定义车辆与区域架构深度解析

    解锁未来汽车电子技术:软件定义车辆与区域架构深度解析 ——立即下载白皮书,抢占智能汽车发展先机 *附件:解锁未来汽车电子技术:软件定义车辆与区域架构深度解析.pdf 为什么这份白皮书值
    的头像 发表于 04-27 11:58 1095次阅读

    风华电容命名方法深度解析

    电子元器件领域,风华电容凭借其清晰的命名体系、全面的技术参数和广泛的应用场景,成为国内外市场的标志性品牌。本文将从命名规则、技术参数、行业应用及市场优势四个维度,深度解析风华电容的技术特性
    的头像 发表于 04-11 11:58 1050次阅读

    深度解析Linux的DNS服务

    dns,Domain Name Server,它的作用是将域名解析为 IP 地址,或者将IP地址解析为域名。
    的头像 发表于 04-09 16:13 688次阅读

    吉时利数字源表2400半导体器件测试深度应用与关键技术解析

    技术原理、核心功能、典型应用场景、测试案例、自动化集成及未来趋势等方面,对吉时利2400半导体测试深度应用进行详细解析。 一、技术原理与核心功能 1. 高精度与宽动态范围 吉时利
    的头像 发表于 03-18 11:36 802次阅读
    吉时利数字源表2400<b class='flag-5'>在</b>半导体器件测试<b class='flag-5'>中</b>的<b class='flag-5'>深度</b>应用与关键技术<b class='flag-5'>解析</b>

    国产自研新标杆:龙芯GM9-3003主板深度解析

    国产自研新标杆:龙芯GM9-3003主板深度解析
    的头像 发表于 03-04 13:55 856次阅读

    玻璃通孔(TGV)技术深度解析

    玻璃通孔(TGV,Through-Glass Via)技术是一种玻璃基板上制造贯穿通孔的技术,它与先进封装的硅通孔(TSV)功能类似,被视为下一代三维集成的关键技术。TGV技术不仅提升了电子设备
    的头像 发表于 02-02 14:52 5961次阅读

    AI自动化生产:深度学习质量控制的应用

    生产效率、保证产品质量方面展现出非凡的能力。阿丘科技「AI干货补给站」推出《AI自动化生产:深度学习质量控制的应用》文章,探讨深度学习
    的头像 发表于 01-17 16:35 1210次阅读
    AI自动化生产:<b class='flag-5'>深度</b>学习<b class='flag-5'>在</b>质量控制<b class='flag-5'>中</b>的应用