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

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

程序架构重要性

很多人尤其是初学者在写代码的时候往往都是想一点写一点,最开始没有一个整体的规划,导致后面代码越写越乱,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 ...\n");}
void task2_hdl(){    printf(">> task 2 is running ...\n");}
void task3_hdl(){    printf(">> task 3 is running ...\n");}
void task4_hdl(){    printf(">> task 4 is running ...\n");}
void task5_hdl(){    printf(">> task 5 is running ...\n");}
// 初始化任务对象,并且将任务添加到时间片轮询调度中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\n", timeslice_get_task_num());    printf(">> task len: %d\n", timeslice_get_task_timeslice_len(&task_3));
    timeslice_task_del(&task_2);    printf(">> delet task 2\n");    printf(">> task 2 is exist: %d\n", timeslice_task_isexist(&task_2));
    printf(">> task num: %d\n", timeslice_get_task_num());
    timeslice_task_del(&task_5);    printf(">> delet task 5\n");
    printf(">> task num: %d\n", timeslice_get_task_num());
    printf(">> task 3 is exist: %d\n", timeslice_task_isexist(&task_3));    timeslice_task_add(&task_2);    printf(">> add task 2\n");    printf(">> task 2 is exist: %d\n", timeslice_task_isexist(&task_2));
    timeslice_task_add(&task_5);    printf(">> add task 5\n");
    printf(">> task num: %d\n", timeslice_get_task_num());
    printf("\n\n========timeslice running===========\n");
    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, &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;        }    }}
void timeslice_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;            }        }    }}
unsigned int timeslice_get_task_num(){    return list_len(&timeslice_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(&timeslice_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, &timeslice_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;}到此,一个全新的,完全解耦的,十分方便易用时间片轮询框架完成
相关推荐
智商偏低6 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen7 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森9 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白9 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D9 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术12 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt13 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘13 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang13 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n16 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件