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

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

3天内不再提示

单片机程序时间轮片法框架

strongerHuang 来源:ERYUESANHI 作者:ERYUESANHI 2021-08-26 11:07 次阅读

来源 | ERYUESANHI

编排 | strongerHuang

今天分享一篇单片机程序框架的文章。

程序架构重要性

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

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

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

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

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

Demo

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

#include 《thread》#include 《stdio.h》#include 《windows.h》#include “timeslice.h”

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

// 具体的任务函数void task1_hdl(){ printf(“》》 task 1 is running 。..

”);}

void task2_hdl(){ printf(“》》 task 2 is running 。..

”);}

void task3_hdl(){ printf(“》》 task 3 is running 。..

”);}

void task4_hdl(){ printf(“》》 task 4 is running 。..

”);}

void task5_hdl(){ printf(“》》 task 5 is running 。..

”);}

// 初始化任务对象,并且将任务添加到时间片轮询调度中void task_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);}

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

void timeslice_tick_thread(){ while (true) { timeslice_tick(); Sleep(10); }}

int main(){ task_init();

printf(“》》 task num: %d

”, timeslice_get_task_num()); printf(“》》 task len: %d

”, timeslice_get_task_timeslice_len(&task_3));

timeslice_task_del(&task_2); printf(“》》 delet task 2

”); printf(“》》 task 2 is exist: %d

”, timeslice_task_isexist(&task_2));

printf(“》》 task num: %d

”, timeslice_get_task_num());

timeslice_task_del(&task_5); printf(“》》 delet task 5

”);

printf(“》》 task num: %d

”, timeslice_get_task_num());

printf(“》》 task 3 is exist: %d

”, timeslice_task_isexist(&task_3)); timeslice_task_add(&task_2); printf(“》》 add task 2

”); printf(“》》 task 2 is exist: %d

”, timeslice_task_isexist(&task_2));

timeslice_task_add(&task_5); printf(“》》 add task 5

”);

printf(“》》 task num: %d

”, timeslice_get_task_num());

printf(“

========timeslice running===========

”);

std::thread thread_1(timeslice_exec_thread); std::thread thread_2(timeslice_tick_thread);

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

return 0;}

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

时间片轮询架构

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

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

h文件:

#ifndef _TIMESLICE_H#define _TIMESLICE_H

#include “。/list.h”

typedef enum { TASK_STOP, TASK_RUN} IsTaskRun;

typedef struct timesilce{ unsigned int id; void (*task_hdl)(void); IsTaskRun is_run; unsigned int timer; unsigned int timeslice_len; ListObj timeslice_task_list;} TimesilceTaskObj;

void timeslice_exec(void);void timeslice_tick(void);void timeslice_task_init(TimesilceTaskObj* obj, void (*task_hdl)(void), unsigned int id, unsigned int timeslice_len);void timeslice_task_add(TimesilceTaskObj* obj);void timeslice_task_del(TimesilceTaskObj* obj);unsigned int timeslice_get_task_timeslice_len(TimesilceTaskObj* obj);unsigned int timeslice_get_task_num(void);unsigned char timeslice_task_isexist(TimesilceTaskObj* obj);

#endif

c文件:

#include “。/timeslice.h”

static LIST_HEAD(timeslice_task_list);

void timeslice_exec(){ ListObj* node; TimesilceTaskObj* task;

list_for_each(node, ×lice_task_list) { task = list_entry(node, TimesilceTaskObj, timeslice_task_list); if (task-》is_run == TASK_RUN) { task-》task_hdl(); task-》is_run = TASK_STOP; } }}

void timeslice_tick(){ ListObj* node; TimesilceTaskObj* task;

list_for_each(node, ×lice_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; } } }}

unsigned int timeslice_get_task_num(){ return list_len(×lice_task_list);}

void timeslice_task_init(TimesilceTaskObj* obj, void (*task_hdl)(void), unsigned int id, unsigned int timeslice_len){ obj-》id = id; obj-》is_run = TASK_STOP; obj-》task_hdl = task_hdl; obj-》timer = timeslice_len; obj-》timeslice_len = timeslice_len;}

void timeslice_task_add(TimesilceTaskObj* obj){ list_insert_before(×lice_task_list, &obj-》timeslice_task_list);}

void timeslice_task_del(TimesilceTaskObj* obj){ if (timeslice_task_isexist(obj)) list_remove(&obj-》timeslice_task_list); else return;}

unsigned char timeslice_task_isexist(TimesilceTaskObj* obj){ unsigned char isexist = 0; ListObj* node; TimesilceTaskObj* task;

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

return isexist;}

unsigned int timeslice_get_task_timeslice_len(TimesilceTaskObj* obj){ return obj-》timeslice_len;}

底层侵入式双向链表

该链表是linux内核中使用十分广泛,也十分经典,其原理具体可以参考文章:https://www.cnblogs.com/skywang12345/p/3562146.html

h文件:

#ifndef _LIST_H#define _LIST_H

#define offset_of(type, member) (unsigned long) &((type*)0)-》member#define container_of(ptr, type, member) ((type *)((char *)(ptr) - offset_of(type, member)))

typedef struct list_structure{ struct list_structure* next; struct list_structure* prev;} ListObj;

#define LIST_HEAD_INIT(name) {&(name), &(name)}#define LIST_HEAD(name) ListObj name = LIST_HEAD_INIT(name)

void list_init(ListObj* list);void list_insert_after(ListObj* list, ListObj* node);void list_insert_before(ListObj* list, ListObj* node);void list_remove(ListObj* node);int list_isempty(const ListObj* list);unsigned int list_len(const ListObj* list);

#define list_entry(node, type, member) container_of(node, type, member)

#define list_for_each(pos, head) for (pos = (head)-》next; pos != (head); pos = pos-》next)

#define list_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”

void list_init(ListObj* list){ list-》next = list-》prev = list;}

void list_insert_after(ListObj* list, ListObj* node){ list-》next-》prev = node; node-》next = list-》next;

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

void list_insert_before(ListObj* list, ListObj* node){ list-》prev-》next = node; node-》prev = list-》prev;

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

void list_remove(ListObj* node){ node-》next-》prev = node-》prev; node-》prev-》next = node-》next;

node-》next = node-》prev = node;}

int list_isempty(const ListObj* list){ return list-》next == list;}

unsigned int list_len(const ListObj* list){ unsigned int len = 0; const ListObj* p = list; while (p-》next != list) { p = p-》next; len++; }

return len;}

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

责任编辑:haq

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

    关注

    6029

    文章

    44480

    浏览量

    631609
  • 框架
    +关注

    关注

    0

    文章

    397

    浏览量

    17388
  • 程序
    +关注

    关注

    115

    文章

    3753

    浏览量

    80710

原文标题:单片机面向对象思维的架构:时间轮片法

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

收藏 人收藏

    评论

    相关推荐

    单片机怎么写入程序

    程序通常涉及以下几个步骤: 选择单片机和开发环境 : 确定项目需求,选择合适的单片机型号。 安装相应的开发环境,如Keil、IAR、MPLAB等。 硬件连接 : 将单片机连接到开发板或
    的头像 发表于 10-21 11:21 177次阅读

    keil可以读出单片机程序

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

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

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

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

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

    单片机烧录程序时为什么一直在检测

    单片机烧录程序时,如果软件一直在显示“正在检测单片机”,可能是由多种原因导致的。以下是一些常见的原因及其解决方法: 1. 连接问题 USB转串口接触不良 :检查单片机与电脑的连接是否稳
    的头像 发表于 09-02 09:57 2255次阅读

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

    单片机烧录原理 单片机烧录是指将编写好的程序代码通过一定的方式传输到单片机的存储器中,使其能够按照程序的指令运行。这个过程通常需要使用烧录器
    的头像 发表于 09-02 09:54 331次阅读

    单片机烧录程序的线叫什么

    单片机烧录程序时所使用的线,根据不同的烧录方式和接口标准,可能会有所不同。以下是一些常见的单片机烧录接口和对应的线: JTAG接口 : JTAG(Joint Test Action Group
    的头像 发表于 09-02 09:52 613次阅读

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

    单片机烧录程序单片机开发过程中非常重要的一步,它涉及到将编写好的程序代码通过一定的方式传输到单片机内部的存储器中,使
    的头像 发表于 09-02 09:47 587次阅读

    上系统与单片机的区别

    上系统(SoC)与单片机在多个方面存在明显的区别。
    的头像 发表于 03-28 14:39 1097次阅读

    RENESAS-RA家族32位单片机命名

    电子发烧友网站提供《RENESAS-RA家族32位单片机命名.pdf》资料免费下载
    发表于 02-19 14:10 0次下载
    RENESAS-RA家族32位<b class='flag-5'>单片机</b>命名<b class='flag-5'>法</b>

    有些汽车ECU会使用2单片机,请问这2单片机是如何分配任务的?

    来自一位用户的咨询,麻烦帮忙解答。越详细越好,有图有真相。如有相关的视频、文档也可以辅助说明,谢谢。 有些汽车ECU会使用2单片机,请问这2单片机是如何分配任务的? 下图是某F1动
    发表于 02-02 08:13

    软件架构之时间

    首先来个demo,该demo是使用电脑开两个线程:一个线程模拟单片机的定时器中断产生时间轮询个时钟,另一个线程则模拟主函数中一直运行的时间
    的头像 发表于 01-18 09:29 485次阅读
    软件架构之<b class='flag-5'>时间</b><b class='flag-5'>轮</b><b class='flag-5'>片</b><b class='flag-5'>法</b>

    keil5mdk怎么编写51单片机程序

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

    一个线程模拟单片机程序框架分享

    首先来个demo,该demo是使用电脑开两个线程:一个线程模拟单片机的定时器中断产生时间轮询个时钟,另一个线程则模拟主函数中一直运行的时间
    发表于 11-19 10:39 2276次阅读
    一个线程模拟<b class='flag-5'>单片机</b><b class='flag-5'>程序</b><b class='flag-5'>框架</b>分享

    多级时间实现框架

    一. 多级时间实现框架 上图是5个时间级联的效果图。中间的大是工作
    的头像 发表于 11-11 15:49 640次阅读
    多级<b class='flag-5'>时间</b><b class='flag-5'>轮</b>实现<b class='flag-5'>框架</b>