A Simple And Easy-To-Use Embedded Software Framework

Hello everyone, today I am sharing an article about a microcontroller program framework.

 1. Importance of Program Framework

Many people, especially beginners, often write code without an overall plan, leading to increasingly messy code and constant bugs.
Eventually, the code seems to run fine (it might actually be fine), but when adding a feature, a lot of time is wasted, potentially leading to a complete code collapse.
Therefore, spending some time on code architecture design at the beginning of a project is very necessary. Once the code architecture is determined, you will find that coding becomes much faster, and debugging later won’t feel like searching for problems blindly. Of course, debugging is also a skill.
During the study of real-time operating systems, I found that the coupling between the real-time operating system framework and personal business code is very low; you only need to register the business code through certain interface functions, and then hand it over to the operating system for management, which is very convenient.
However, the scheduling of the operating system is too complex. Here, I will use the operating system’s thinking approach to reconstruct this time-slice polling framework. This framework achieves complete decoupling; users only need to include the header file and do not need to modify the already written library files during use.
 2. Program Example

First, let’s look at a demo. This demo uses two threads on a computer: one thread simulates the timer interrupt of the microcontroller generating time-slice polling clocks, while the other thread simulates the time-slice polling scheduling program that runs continuously in the main function.
#include <thread>#include <stdio.h>#include <windows.h>#include "timeslice.h"
// Create 5 task objectsTimesilceTaskObj task_1, task_2, task_3, task_4, task_5;
// Specific task functionsvoid 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");}
// Initialize task objects and add tasks to the time-slice polling schedulevoid 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);}
// Start two threads to simulate the running process on the microcontrollervoid 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(">> delete task 2\n");    printf(">> task 2 exists: %d\n", timeslice_task_isexist(&task_2));
    printf(">> task num: %d\n", timeslice_get_task_num());
    timeslice_task_del(&task_5);    printf(">> delete task 5\n");
    printf(">> task num: %d\n", timeslice_get_task_num());
    printf(">> task 3 exists: %d\n", timeslice_task_isexist(&task_3));    timeslice_task_add(&task_2);    printf(">> add task 2\n");    printf(">> task 2 exists: %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;}

The running result is as follows:

A Simple And Easy-To-Use Embedded Software Framework

From the above example, it can be seen that this framework is very convenient to use, and you can even create tasks and add them to the time-slice polling framework quickly without knowing its principles, just through a few simple interfaces, which is very useful.
 3. Time-Slice Polling Framework

This part mainly uses object-oriented thinking, using structures as objects and using structure pointers as parameters to save resources and achieve high operational efficiency.

The most challenging part is the use of intrusive linked lists, which are widely used in some operating system kernels. Here, I refer to the implementation of intrusive linked lists in the RT-Thread real-time operating system.
Header file:
#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 file:
#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;}
 4. Low-Level Intrusive Doubly Linked List

Header file:
#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 file:
#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;}

Thus, a brand new, completely decoupled, and very convenient time-slice polling framework is completed.











Click to read the original text and apply for free


Leave a Comment