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

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

3天内不再提示

非常实用,推荐一种面向对象思维的单片机程序框架

jf_pJlTbmA9 来源:小麦大叔 作者:小麦大叔 2023-10-24 18:03 次阅读

程序架构重要性

很多人尤其是初学者在写代码的时候往往都是想一点写一点,最开始没有一个整体的规划,导致后面代码越写越乱,bug不断。

最终代码跑起来看似没有问题(有可能也真的没有问题),但是系统的可扩展性很差,添加一个功能的时候会浪费大量的时间,甚至导致整个代码的崩溃。

所以,在一个项目开始的时候多花一些时间在代码的架构设计上是十分有必要的。代码架构确定好了之后你会发现敲代码的时候会特别快,并且在后期调试的时候也不会像无头苍蝇一样胡乱找问题。当然,调试也是一门技术。

在学习实时操作系统的过程中,发现实时操作系统框架与个人的业务代码之间的耦合性就非常低,都是只需要将业务代码通过一定的接口函数注册好后就交给操作系统托管了,十分方便。

但是操作系统的调度过于复杂,这里就使用操作系统的思维方式来重构这个时间片轮询框架。实现该框架的完全解耦,用户只需要包含头文件,并且在使用过程中不需要改动已经写好的库文件。

Demo

首先来个demo,该demo是使用电脑开两个线程:一个线程模拟单片机定时器中断产生时间片轮询个时钟,另一个线程则模拟主函数中一直运行的时间片轮询调度程序。

#include
#include
#include
#include"timeslice.h"

//创建5个任务对象
TimesilceTaskObjtask_1,task_2,task_3,task_4,task_5;

//具体的任务函数
voidtask1_hdl()
{
printf(">>task1isrunning...n");
}

voidtask2_hdl()
{
printf(">>task2isrunning...n");
}

voidtask3_hdl()
{
printf(">>task3isrunning...n");
}

voidtask4_hdl()
{
printf(">>task4isrunning...n");
}

voidtask5_hdl()
{
printf(">>task5isrunning...n");
}

//初始化任务对象,并且将任务添加到时间片轮询调度中
voidtask_init()
{
timeslice_task_init( task_1,task1_hdl,1,10);
timeslice_task_init( task_2,task2_hdl,2,20);
timeslice_task_init( task_3,task3_hdl,3,30);
timeslice_task_init( task_4,task4_hdl,4,40);
timeslice_task_init( task_5,task5_hdl,5,50);
timeslice_task_add( task_1);
timeslice_task_add( task_2);
timeslice_task_add( task_3);
timeslice_task_add( task_4);
timeslice_task_add( task_5);
}

//开两个线程模拟在单片机上的运行过程
voidtimeslice_exec_thread()
{
while(true)
{
timeslice_exec();
}
}

voidtimeslice_tick_thread()
{
while(true)
{
timeslice_tick();
Sleep(10);
}
}

intmain()
{
task_init();

printf(">>tasknum:%dn",timeslice_get_task_num());
printf(">>tasklen:%dn",timeslice_get_task_timeslice_len( task_3));

timeslice_task_del( task_2);
printf(">>delettask2n");
printf(">>task2isexist:%dn",timeslice_task_isexist( task_2));

printf(">>tasknum:%dn",timeslice_get_task_num());
timeslice_task_del( task_5);printf(">>delettask5n");
printf(">>tasknum:%dn",timeslice_get_task_num());
printf(">>task3isexist:%dn",timeslice_task_isexist( task_3));

timeslice_task_add( task_2);printf(">>addtask2n");
printf(">>task2isexist:%dn",timeslice_task_isexist( task_2));

timeslice_task_add( task_5);printf(">>addtask5n");
printf(">>tasknum:%dn",timeslice_get_task_num());

printf("nn========timeslicerunning===========n");

std::threadthread_1(timeslice_exec_thread);
std::threadthread_2(timeslice_tick_thread);

thread_1.join();
thread_2.join();

return0;
}

运行结果如下:

poYBAGGnJ7OAZ5XZAACMwYJDoAU530.png

由以上例子可见,这个框架使用十分方便,甚至可以完全不知道其原理,仅仅通过几个简单的接口就可以迅速创建任务并加入到时间片轮询的框架中,十分好用。

时间片轮询架构

其实该部分主要使用了面向对象的思维,使用结构体作为对象,并使用结构体指针作为参数传递,这样作可以节省资源,并且有着极高的运行效率。

其中最难的部分是侵入式链表的使用,这种链表在一些操作系统内核中使用十分广泛,这里是参考RT-Thread实时操作系统中的侵入式链表实现。

h文件:

#ifndef_TIMESLICE_H
#define_TIMESLICE_H

#include"./list.h"

typedefenum
{
TASK_STOP,
TASK_RUN
}IsTaskRun;

typedefstructtimesilce
{
unsignedintid;
void(*task_hdl)(void);
IsTaskRunis_run;
unsignedinttimer;
unsignedinttimeslice_len;
ListObjtimeslice_task_list;
}TimesilceTaskObj;

voidtimeslice_exec(void);
voidtimeslice_tick(void);
voidtimeslice_task_init(TimesilceTaskObj*obj,void(*task_hdl)(void),unsignedintid,unsignedinttimeslice_len);
voidtimeslice_task_add(TimesilceTaskObj*obj);
voidtimeslice_task_del(TimesilceTaskObj*obj);
unsignedinttimeslice_get_task_timeslice_len(TimesilceTaskObj*obj);
unsignedinttimeslice_get_task_num(void);
unsignedchartimeslice_task_isexist(TimesilceTaskObj*obj);

#endifc文件:
#include"./timeslice.h"

staticLIST_HEAD(timeslice_task_list);

voidtimeslice_exec()
{
ListObj*node;
TimesilceTaskObj*task;

list_for_each(node, timeslice_task_list)
{

task=list_entry(node,TimesilceTaskObj,timeslice_task_list);
if(task->is_run==TASK_RUN)
{
task->task_hdl();
task->is_run=TASK_STOP;
}
}
}

voidtimeslice_tick()
{
ListObj*node;
TimesilceTaskObj*task;

list_for_each(node, timeslice_task_list)
{
task=list_entry(node,TimesilceTaskObj,timeslice_task_list);
if(task->timer!=0)
{
task->timer--;
if(task->timer==0)
{
task->is_run=TASK_RUN;
task->timer=task->timeslice_len;
}
}
}
}

unsignedinttimeslice_get_task_num()
{
returnlist_len( timeslice_task_list);
}

voidtimeslice_task_init(TimesilceTaskObj*obj,void(*task_hdl)(void),unsignedintid,unsignedinttimeslice_len)
{
obj->id=id;
obj->is_run=TASK_STOP;
obj->task_hdl=task_hdl;
obj->timer=timeslice_len;
obj->timeslice_len=timeslice_len;
}

voidtimeslice_task_add(TimesilceTaskObj*obj)
{
list_insert_before( timeslice_task_list, obj->timeslice_task_list);
}

voidtimeslice_task_del(TimesilceTaskObj*obj)
{
if(timeslice_task_isexist(obj))
list_remove( obj->timeslice_task_list);
else
return;
}

unsignedchartimeslice_task_isexist(TimesilceTaskObj*obj)
{
unsignedcharisexist=0;
ListObj*node;
TimesilceTaskObj*task;

list_for_each(node, timeslice_task_list)
{
task=list_entry(node,TimesilceTaskObj,timeslice_task_list);
if(obj->id==task->id)
isexist=1;
}

returnisexist;
}

unsignedinttimeslice_get_task_timeslice_len(TimesilceTaskObj*obj)
{
returnobj->timeslice_len;
}

底层侵入式双向链表

该链表是linux内核中使用十分广泛,也十分经典,其原理具体可以参考文章:

https://www.cnblogs.com/skywang12345/p/3562146.html

h文件:

#ifndef_LIST_H
#define_LIST_H
#defineoffset_of(type,member)(unsignedlong) ((type*)0)->member
#definecontainer_of(ptr,type,member)((type*)((char*)(ptr)-offset_of(type,member)))

typedefstructlist_structure
{
structlist_structure*next;
structlist_structure*prev;
}ListObj;

#defineLIST_HEAD_INIT(name){ (name), (name)}
#defineLIST_HEAD(name)ListObjname=LIST_HEAD_INIT(name)

voidlist_init(ListObj*list);
voidlist_insert_after(ListObj*list,ListObj*node);
voidlist_insert_before(ListObj*list,ListObj*node);
voidlist_remove(ListObj*node);
intlist_isempty(constListObj*list);
unsignedintlist_len(constListObj*list);

#definelist_entry(node,type,member)
container_of(node,type,member)

#definelist_for_each(pos,head)
for(pos=(head)->next;pos!=(head);pos=pos->next)

#definelist_for_each_safe(pos,n,head)
for(pos=(head)->next,n=pos->next;pos!=(head);
pos=n,n=pos->next)

#endif

c文件:

#include"list.h"

voidlist_init(ListObj*list)
{
list->next=list->prev=list;
}

voidlist_insert_after(ListObj*list,ListObj*node)
{
list->next->prev=node;
node->next=list->next;

list->next=node;
node->prev=list;
}

voidlist_insert_before(ListObj*list,ListObj*node)
{
list->prev->next=node;
node->prev=list->prev;

list->prev=node;
node->next=list;
}

voidlist_remove(ListObj*node)
{
node->next->prev=node->prev;
node->prev->next=node->next;

node->next=node->prev=node;
}

intlist_isempty(constListObj*list)
{
returnlist->next==list;
}

unsignedintlist_len(constListObj*list)
{
unsignedintlen=0;
constListObj*p=list;
while(p->next!=list)
{
p=p->next;
len++;
}

returnlen;
}

到此,一个全新的,完全解耦的,十分方便易用时间片轮询框架完成。

来源:小麦大叔

免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理

审核编辑 黄宇

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

    关注

    6035

    文章

    44554

    浏览量

    634653
  • 框架
    +关注

    关注

    0

    文章

    403

    浏览量

    17475
收藏 人收藏

    评论

    相关推荐

    一种面向飞行试验的数据融合框架

    天地气动数据致性,针对某外形飞行试验数据开展了典型对象的天地气动数据融合方法研究。结合数据挖掘的随机森林方法,本文提出了一种面向飞行试验的数据融合
    的头像 发表于 11-27 11:34 217次阅读
    <b class='flag-5'>一种</b><b class='flag-5'>面向</b>飞行试验的数据融合<b class='flag-5'>框架</b>

    单片机编程语言有哪些选择

    些常用的单片机编程语言,以及它们的特点和应用场景: 1. C语言 特点 :C语言是一种通用的编程语言,以其高效性和灵活性而闻名。它提供了丰富的库函数和直接的硬件访问能力,非常适合用于单片机
    的头像 发表于 11-01 14:13 500次阅读

    单片机怎么写入程序

    单片机(Microcontroller Unit,MCU)是一种集成电路芯片,它将计算机的CPU、存储器、输入/输出接口等功能集成在个芯片上。单片机广泛应用于嵌入式系统和物联网设备中
    的头像 发表于 10-21 11:21 428次阅读

    单片机有哪些中断类型

    单片机中断是指在单片机执行程序的过程中,当外部设备或内部条件发生某个特定事件时,能够暂停当前正在执行的程序,转而去执行个特定的服务
    的头像 发表于 10-17 18:12 637次阅读

    单片机的中断机制

    单片机的中断机制是一种重要的处理方式,它允许单片机在执行主程序的过程中,能够暂停当前任务,转而处理外部或内部紧急事件。这种机制极大地提高了系统的响应速度和处理能力,使得
    的头像 发表于 10-17 18:03 620次阅读

    keil可以读出单片机程序

    Keil是款广泛应用于单片机程序开发的软件,它提供了包括C编译器、宏汇编、连接器、库管理和个功能强大的仿真调试器等在内的完整开发方案。然而,关于Keil是否能直接“读出”
    的头像 发表于 09-02 10:32 912次阅读

    单片机烧录程序用什么软件

    单片机烧录程序单片机开发过程中的个重要环节,涉及到将编写好的程序代码通过烧录器写入单片机的R
    的头像 发表于 09-02 10:05 1156次阅读

    单片机烧录程序可以重新烧吗

    单片机(Microcontroller Unit, MCU)是一种集成电路芯片,它将计算机的CPU、存储器、输入/输出接口等集成在块芯片上,用于控制各种电子设备。单片机烧录
    的头像 发表于 09-02 10:04 1180次阅读

    单片机烧录程序的线比单片机上的少还能烧录吗

    单片机烧录原理 单片机烧录是指将编写好的程序代码通过定的方式传输到单片机的存储器中,使其能够按照程序
    的头像 发表于 09-02 09:54 482次阅读

    单片机烧录程序的基本步骤是什么

    单片机烧录程序单片机开发过程中非常重要的步,它涉及到将编写好的程序代码通过
    的头像 发表于 09-02 09:47 998次阅读

    文读懂什么单片机:组成结构与应用

    欢迎来到单片机的世界,这是一种微小(但功能强大)的设备,改变了嵌入式系统的面貌。在本文中,您将了解单片机些基本知识、单片机的结构以及微处
    的头像 发表于 08-09 11:49 1673次阅读
    <b class='flag-5'>一</b>文读懂什么<b class='flag-5'>单片机</b>:组成结构与应用

    数字电路仿真软件单片机怎么用

    数字电路仿真软件是一种用于模拟和测试数字电路设计的工具。其中,单片机仿真软件是一种专门针对单片机进行仿真的工具。这种软件能够提供个类似真实
    的头像 发表于 04-21 10:28 1046次阅读

    单片机中断功能及其应用

    单片机中断功能及其应用  单片机中断是指在程序执行过程中,根据特定的条件或事件自动暂时中断当前程序的执行,转而执行特定的中断服务程序。中断是
    的头像 发表于 01-30 14:45 5273次阅读

    单片机芯片怎么写入程序

    单片机芯片的程序写入是通过将程序代码写入单片机芯片的非易失性存储器(如Flash)中实现的。 在计算机科学和电子工程领域,单片机
    的头像 发表于 01-05 14:06 8272次阅读

    keil5mdk怎么编写51单片机程序

    编写51单片机程序需要以下步骤: 编写程序框架:首先,你需要定义程序所需要的变量和常量,以及设置中断向量和寄存器的初始状态。这些信息可以根据
    的头像 发表于 12-25 14:54 1544次阅读