Object-Oriented Thinking in MCU Architecture

Follow+Star Public Account Number, don’t miss out on exciting contentSource | ERYUESANHIEditor | strongerHuangToday, I will share an article about the microcontroller program framework using time-slicing method combined with source code.

Importance of Program Architecture

Many people, especially beginners, often write code without an overall plan, leading to increasingly messy code and constant bugs.Ultimately, the code may seem to run without issues(it might actually be fine), but adding a new feature can waste a lot of time and even cause the entire code to crash.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 will not 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 hand it over to the operating system for management, which is very convenient.However, the scheduling of the operating system is overly complex, so I will use the operating system’s way of thinking to reconstruct this time-slice polling framework. This will achieve complete decoupling of the framework, allowing users to simply include the header file without needing to modify the already written library files during use.

Demo

First, here’s a demo that uses two threads on a computer: one thread simulates the microcontroller’s timer interrupt generating time-slice polling for the clock, while the other thread simulates the time-slice polling scheduler 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:Object-Oriented Thinking in MCU ArchitectureFrom 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 through a few simple interfaces without needing to know its principles, which is very useful.

Time-Slice Polling Architecture

This part mainly uses object-oriented thinking, using structures as objects and using structure pointers as parameters to pass, which saves resources and has 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 referenced 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

Source 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;}

Low-Level Intrusive Doubly Linked List

This linked list is widely used in the Linux kernel and is very classic. The specific principles can be referenced in the article:https://www.cnblogs.com/skywang12345/p/3562146.htmlHeader 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

Source 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 user-friendly time-slice polling framework is completed.Disclaimer: The materials in this article are sourced from the internet, and the copyright belongs to the original author. If there are any copyright issues, please contact me for removal.———— END ————Object-Oriented Thinking in MCU Architecture● Column “Embedded Tools”● Column “Embedded Development”● Column “Keil Tutorials”● Selected Tutorials on EmbeddedFollow the public accountReply “Add Group” to join the technical exchange group according to the rules, reply “1024” to see more content.Click “Read the original text” to see more shares.

Leave a Comment