历史上的今天
返回首页

历史上的今天

今天是:2024年12月24日(星期二)

正在发生

2020年12月24日 | 链表在STM32中的应用

2020-12-24 来源:eefocus

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、链表实现

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

图片

对于上面的问题,我们使用链表解决,需要配合内存管理才能实现。内存管理这一块,大家可以自己编写内存管理驱动,也可以使用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_Datarn");

  

  while(p->next_Area_Inf!=NULL)

  {

    p = p->next_Area_Inf;

    printf(" %d     %d    %d   %d    %d      %d      %srn",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-----------------------rn");

}

3、测试函数

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


/**

* @brief 链表测试函数

*/

void list_main()

{

  int i,j;

  Area_Inf_Typedef temp;

  Area_Inf_Typedef **data_p;

  data_p = NULL;

  printf("------------------List test---------------------rn"); 

  if(!Area_List_Init( ))

  {

    printf("Memory fail..rn");

  }

  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.Area_Data[j] = j+0x30;

    }

    temp.Area_Data[j] = 0;

    if(!Add_Area_ToList(&temp))

    {

      printf("Add Area %d Area_Info failrn",i);

    }

  }

  

  Printf_Area_Inf();

  printf("rn-------------Delete ID of Area is 3-------------rn");

  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.Area_Data[j] = j+0x30;

  }

  temp.Area_Data[j] = 0;

  if(!Add_Area_ToList(&temp))

  {

    printf("Add Area %d info failrn",temp.ID);

  }

  printf("rn--------------Add ID of Area is 9---------------rn");

  Printf_Area_Inf();

  

  Find_Area_According_ID(data_p,2);

  temp.ID = (*data_p)->ID;

  

  Delete_All_Area();

  printf("rn--------------Delete All Area-------------------rn");

  Printf_Area_Inf();

  

  while(1);

}

测试结果

图片

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

https://github.com/strongercjd/STM32_Linklist

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

选择Simulator调试

图片

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

图片


推荐阅读

史海拾趣

FINDER公司的发展小趣事

在电子行业中,FINDER公司(即Finder芬德)的发展历程充满了创新与进取的故事。以下是五个关于FINDER公司发展起来的相关故事,每个故事均基于事实进行描述:

1. 创立与初步发展

故事背景:FINDER公司由Piero Giordanino于1954年创立,当时他凭借对电子技术的深刻理解和远见卓识,决定投身于继电器制造领域。在成立初期,FINDER专注于研发和生产高质量的继电器产品,以满足当时市场对电子元器件日益增长的需求。

关键事实:Giordanino在1949年就已经获得了第一步继电器的专利,这为他后来创立FINDER公司奠定了坚实的基础。随着技术的不断积累和市场的逐步开拓,FINDER逐渐在继电器领域崭露头角。

2. 产品多样化与技术创新

故事背景:在成立后的几十年里,FINDER公司不断推出新产品,实现了产品线的多样化。从最初的单一继电器产品,逐步扩展到包括步进继电器、光敏继电器、工业继电器、微型和超薄继电器、功率继电器、定时器继电器插座和配件等多个领域。

关键事实:截至当前,FINDER已生产超过14,500种不同类型的机电和电子设备,产品广泛应用于工业自动化、建筑、家庭和办公室等多个领域。同时,FINDER还致力于技术创新,不断研发出具有更高性能、更可靠性的新产品。

3. 全球化布局与市场拓展

故事背景:随着公司规模的扩大和产品线的丰富,FINDER开始将目光投向全球市场。通过设立分支机构、建立销售网络以及与国际知名企业的合作,FINDER逐步实现了全球化布局。

关键事实:目前,FINDER已经在全球范围内设立了多个分支机构和销售网络,覆盖欧洲、美洲、亚洲和非洲等地区。同时,FINDER还与德国SIEMENS、日本OMRON等世界知名电子企业并列为全球三大继电器制造商之一,在欧洲市场享有极高的盛誉。

4. 质量控制与环保生产

故事背景:在快速发展的同时,FINDER始终将产品质量放在首位。通过严格的质量控制体系和环保生产标准,FINDER确保了产品的卓越品质和可持续发展。

关键事实:FINDER的每个产品在出厂前都经过两个彻底的测试周期以确保最高水平的质量。此外,FINDER还尊重国家和欧盟的环保规范,确保其产品符合现行立法要求。在生产过程中采用环保材料和技术减少对环境的影响。

5. 行业认可与荣誉

故事背景:由于卓越的产品质量和持续的技术创新,FINDER在电子行业中获得了广泛的认可和赞誉。公司不仅赢得了众多客户的信赖和支持还获得了多项行业荣誉和认证。

关键事实:FINDER的产品已经通过了三十多个国家四十多个产品安全认证这充分证明了其在产品质量和安全方面的卓越表现。同时FINDER还被评为全球拥有最多质量认证的继电器制造商之一这进一步巩固了其在行业中的领先地位。

这些故事共同展现了FINDER公司在电子行业中的发展历程和成就。从创立初期的艰难起步到如今的全球知名企业FINDER凭借其卓越的产品质量、持续的技术创新以及全球化布局赢得了广泛的认可和赞誉。

E Connector Solutions公司的发展小趣事

在电子连接器行业,产品质量直接关系到企业的生存和发展。E Connector Solutions公司始终坚持品质至上的原则,从原材料采购到生产流程控制,再到产品检测,都严格遵循国际标准。公司还建立了完善的质量管理体系,确保每一款产品都符合客户的要求。正是凭借过硬的产品质量,E Connector Solutions公司赢得了市场的认可,逐渐在行业中树立了良好的品牌形象。

D3 Semiconductor公司的发展小趣事

在全球化的大背景下,D3 Semiconductor积极拓展国际市场。除了与贸泽电子的合作外,公司还与多家国际知名的电子企业建立了合作关系,将产品推向全球各地。同时,D3 Semiconductor还加强了在国际市场的营销和品牌建设,提高了公司的知名度和美誉度。这一系列的努力使D3 Semiconductor在国际市场上取得了显著的成绩,也为公司的未来发展奠定了坚实的基础。

FTCAP Fischer & Tausche Capacitor Group公司的发展小趣事

在全球化的大背景下,D3 Semiconductor积极拓展国际市场。除了与贸泽电子的合作外,公司还与多家国际知名的电子企业建立了合作关系,将产品推向全球各地。同时,D3 Semiconductor还加强了在国际市场的营销和品牌建设,提高了公司的知名度和美誉度。这一系列的努力使D3 Semiconductor在国际市场上取得了显著的成绩,也为公司的未来发展奠定了坚实的基础。

台湾町洋(dinkle)公司的发展小趣事

町洋公司创立于1983年,由一群志同道合的电子工程师创立。创业初期,町洋专注于接线端子及相关产品的研发与生产,凭借其独特的设计理念和精湛的生产工艺,逐渐在台湾市场上崭露头角。公司创始人深知产品质量的重要性,因此始终坚持严格的品质控制,赢得了客户的信赖。

Diconex公司的发展小趣事

在电子行业的激烈竞争中,Diconex公司凭借其卓越的技术创新能力脱颖而出。公司成立之初,便专注于研发高性能、低能耗的半导体芯片。通过不断的研发投入和团队努力,Diconex成功推出了一系列具有行业领先水平的产品,赢得了客户的广泛认可。随着技术的不断升级和市场需求的不断增长,Diconex逐渐在电子行业占据了一席之地。

问答坊 | AI 解惑

嵌入式

嵌入式系统的好资料…

查看全部问答>

三角波参数测试仪——09年四川

本帖最后由 paulhyde 于 2014-9-15 04:14 编辑 [local]2[/local]“TI”杯时我们三人的作品。 三角波参数测试仪。  …

查看全部问答>

【藏书阁】电路分析 (面向21世纪课程教材)

目录: 第一章 线性电路的复数解法 1.1 电源与元件的概念 1.2 常参量线性电路的复数解法 1.3 一阶RC滤波器 1.4 二级LC滤波器 思考题 习题 第二章 线性电路的s域解法 2.1 拉普拉斯变换 2.2 线性电路的s域解法 2.3 卷积 附录2 拉氏变换 ...…

查看全部问答>

关于可视对讲,一直疑惑的问题

做了这么多年的门禁和可视对讲,做过了好多的产品,现在市场上的产品真的是五花八门, 国内到底谁家, 才是工程商的不二选择......…

查看全部问答>

cycloneIII_3c25的原装开发板到手了-发资料

整快板子上的外设不是很多,但是存储器资源丰富。并预留了一个HSMC接口,可以用来扩展。有三张光盘,一张是介绍开发板的使用,例程,入门指导,另两张是Quartus2的软件,常用库,NIOS2开发资料。先简单介绍到这里,把开发板的资料传上来共享(全部 ...…

查看全部问答>

DSP2407在转速测定中的应用

DSP2407在转速测定中的应用…

查看全部问答>

通过MR16谈谈自己对LED行业的想法--antsin

本人一直在一家电源IC原厂华东区做FAE,07年公司从linear挖了一个团队进入LED驱动行业,我随之也开始进入这个行业。在勤勤勉勉的耕耘3年中,我从学习到与客户分享我的学习经验,很多客户都会跟我讲,王工,从你身上我学到了很多。但这时我告诫自己 ...…

查看全部问答>

如何设置vxworks(主机)的IP地址,和网关?如果设置成功,我能否在windows下 用ping 命令对运行中的vxworks进行网络连接??能ping通吗

如何设置vxworks(主机)的IP地址,和网关????????? 如果设置成功,我能否在windows下 用ping 命令对运行中的vxworks进行网络连接??能ping通吗?? …

查看全部问答>

【MSP430共享】MSP430 电容单触式传感器设计指南

MSP430 电容单触式传感器设计指南,这是官网应用手册,很权威的。 [ 本帖最后由 鑫海宝贝 于 2011-10-12 09:35 编辑 ]…

查看全部问答>

LCD液晶显示

LCD1604驱动模块 /*备注一:LCD1604只含有ASCII码表内字符的字库,无汉字字库,不能自行打点,不便画曲线*/ /*本模板功能一:在任意位置显示阿拉伯数字 */ /*本模板功能二:在任意位置显示字符及字符串 */ #include #include \"lcd1604.h\" /* ...…

查看全部问答>