A Brief Introduction to the Registration Mechanism in Embedded Software Development

Problem Description One of the most frustrating aspects of using timers is defining flags and hold times. Each time a definition is made, it leads to a proliferation of flags in the interrupt function, and time variables are scattered throughout the program. When attempting to port the code, one hesitates to delete anything. The program is in a state of high coupling, losing the significance of having a .c and a .h file.

How to Solve This Problem

Introduce a registration mechanism. To illustrate the registration mechanism, consider an example: when using the camera function on a mobile phone, there is an operation to send the captured photo. The easiest way to implement this process in code is as follows:

Add the following code to the camera’s sending module:

if (select_send) {     if(select_wechat_send)     {         get_sender();         select_sender();     }     else if(select_qq_send)     {         get_sender();         select_sender();     }     else if(select_weibo_send)     {         get_sender();         select_sender();     }    .    .    // omitted for brevity    }

This is the most straightforward implementation, similar to the timer implementation where a series of variables are defined wherever needed. Returning to the camera example, suppose one day a chat application even more popular than WeChat appears, and the user installs it and wants to send images. What should be done? Of course, one would have to add an else if (….) and its implementation in the camera’s sending module, which means that every time a new application that requires image functionality is updated, the camera module must be modified. Doesn’t this feel similar to our timer?The Essence of Registration Decoupling various modules. The principle of programming emphasizes high cohesion and low coupling. My current understanding of this statement is: high cohesion means that each functional module (c file, h file) does not call other modules, for example, the obstacle function should not have a state variable, nor should it have operations to restore zero landmarks. It should only do one thing: process IO port information and generate the corresponding obstacle state. Low coupling means that the coupling of the obstacle function with other modules is only for generating the obstacle state. Let’s delve deeper into the registration mechanism.What is Registration:

My current understanding is that when the camera needs to send an image, it faces multiple sending methods, each of which will certainly call different functions. Conversely, I have many applications that need to use the camera module (comparing it to the timer). Since this is the case, the camera module defines a registration function for other modules to call, allowing the camera to know which sending methods are permitted.


#define num_max 20                        // Maximum number of devices
typedef struct{  u8 num; // Current number of registered devices  u8 list_name[num_max];                // Used to save the registered device list  void (*click[num_max])(u8 * temp);  // Store the function addresses of different modules (WeChat, QQ) for sending} Equipment; Equipment COM;            
/**************************Registration Function****************************************/
void Photo_Register (void (*a)(u8 * temp), u8 list)  // Interface for external use{   if(COM.num < num_max)  {      COM.click[COM.num]=a;          // Save function address      COM.list_name[COM.num]=list;   // Save device name to list      COM.num++;  }  else  {     /**** Error if exceeding maximum number of devices******/  }}
/*Sending function in the camera*/
void Click(u8 temp)           // This function is called to implement image sending{   u8 i, NUM;   for(i=0; i <= COM.num; i++)   {    printf("Print list, showing registered devices");   }    NUM = Get(selected_send_method);   if(!NUM)   COM.click[NUM](temp);}/*******************Implemented in the camera************************************/

If WeChat wants to use it, during installation, it prompts to open camera permissions, which calls the above registration function. It passes the address of the self-integrated sending function of WeChat to the camera, and the camera only needs to check which devices are registered each time it sends, selecting the corresponding method. In this way, no matter how many new applications need to use the camera, they only need to register once. The camera and modules like WeChat, QQ, and Weibo are perfectly decoupled! Similarly, the decoupling of the timer can be handled this way.

Using the Registration Mechanism in Timers

First, to achieve decoupling, one must eliminate the randomly defined flags and time variables, allowing only one time variable. Therefore, define a 32-bit time variable without any conditional restrictions, allowing it to increment continuously. Referencing the method of timed processing in Arduino: define a function to get the current time, save the current time, run for a while, and then query the current time again. The difference between the two gives the running time. From the above, it is not difficult to see that the key points are: obtaining the current time function, storing the current time, and the time difference. The following is the implementation method:

time.h :

#include "stm32f10x.h"
#ifndef __TIME_H
#define __TIME_H
#define TimerID_max 20          // Maximum number of registered devices
#define RunOutOf_time(ID, ms)   (systime.now - systime.last[ID - 1] < ms)

typedef struct{     u8 ID;                     // Device ID     u32 now;                   // Current time     u32 last[TimerID_max];     // Store the captured time
     void (*timer_init)(u16 countdata, u16 freqData);   // Pointer to initialization function     u8 (*get_id)(void);                               // Pointer to get ID function     void (*refresh)(u8 ID);                           // Pointer to update time function
} SYSTIME;
extern SYSTIME systime;
#endif

time.c
#include "time.h"
/*********API for external use*******************/
void Timer_Init(u16 CountData, u16 FreqData);
unsigned char systime_get(void);
void Refresh(u8 ID);
/***********************************************/
SYSTIME systime =        // Define SYSTIME type variable and initialize function pointers{     .get_id = systime_get,     .refresh = Refresh,     .timer_init = Timer_Init};
/****************************************************///Function Name: Timer_init//Description: Initialize timer//Input: Related to interrupt time//Output: null/****************************************************/
void Timer_Init(u16 CountData, u16 FreqData){ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);          NVIC_InitTypeDef NVIC_InitStructure;                         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
 NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;              NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 4; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); 
 TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;             
 TIM_DeInit(TIM4); TIM_TimeBaseStructure.TIM_Prescaler = FreqData; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;  TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;   TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);  TIM_ClearFlag(TIM4, TIM_FLAG_Update);  TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);     TIM_Cmd(TIM4, ENABLE);             
}/****************************************************///Function Name: systime_get//Description: Get current time and generate a registration//Input: null//Output: null/****************************************************/unsigned char systime_get(){    if(systime.ID < TimerID_max)    {        systime.last[systime.ID] = systime.now; systime.ID++;        return systime.ID;    }    else  return 0;}/****************************************************///Function Name: Refresh//Description: Update current time//Input: Obtained ID//Output: null/****************************************************/void Refresh(u8 ID){    systime.last[ID - 1] = systime.now;}/****************************************************///Function Name: TIM4_IRQHandler //Description: 1ms timer//Input: null//Output: null/****************************************************/void TIM4_IRQHandler(void) {    if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)    { TIM_ClearITPendingBit(TIM4, TIM_IT_Update); systime.now++;              }

After implementing the above in .c and .h, only three functions are exposed to the outside:

/*********API for external use*******************/
void Timer_Init(u16 CountData, u16 FreqData);
unsigned char systime_get(void);
void Refresh(u8 ID);
/***********************************************/

How to Use the Timer

1
/*Initialization*/
2
  /**********Task 1 implementation: blinking, frequency 1s **********/    void task1(){        static u8 Task1_ID; if(!Task1_ID)    Task1_ID = systime.get_id();        if(RunOutOf_time(Task1_ID, 1000))    RUN_LED() = 1;  else if(RunOutOf_time(Task1_ID, 2000))    RUN_LED() = 0;  else if(RunOutOf_time(Task1_ID, 3000))    RUN_LED() = 1;  else if(RunOutOf_time(Task1_ID, 4000))    RUN_LED() = 0;  else if(RunOutOf_time(Task1_ID, 5000))            RUN_LED() = 1;  else    systime.refresh(Task1_ID);   }
/*******************Task 2 implementation: blinking, frequency 100ms**********************/  void task2(){ static u8 Task1_ID; if(!Task1_ID)   Task1_ID = systime.get_id(); if(RunOutOf_time(Task1_ID, 100))   RUN_LED() = 1;  else if(RunOutOf_time(Task1_ID, 200))          RUN_LED() = 0;  else if(RunOutOf_time(Task1_ID, 300))   RUN_LED() = 1;  else if(RunOutOf_time(Task1_ID, 400))   RUN_LED() = 0;         else if(RunOutOf_time(Task1_ID, 500))        RUN_LED() = 1;  else   systime.refresh(Task1_ID);}
/***************Main function implementation: Task 1 runs for 10s, Task 2 runs for 10s****************/int main(void){        static u8 main_ID;        System_Init();        while(1)        {       if(!main_ID)       main_ID = systime.get_id();    if(RunOutOf_time(main_ID, 10000))       task1();    else if(RunOutOf_time(main_ID, 20000))       task2();    else             systime.refresh(main_ID);         }

In the above, any function that wants to use the timer only needs to set up an ID storage variable to store the ID allocated during registration, and it can call the timer, which can be easily ported to any platform, requiring only hardware initialization modifications. This program cannot achieve tasks that need to be executed at precise intervals, such as a task that needs to execute every 100ms; it can only be used for execution within time periods. The reason is that the main loop of the program takes time, causing polling to miss the precise 100ms mark. To achieve this effect, further improvements or a complete change in approach is needed, such as capturing in an interrupt and using the main loop to query the 100ms enable bit. 【Paid】 STM32 Embedded Resource Package

References & Citations

[1] Sun Wukong, the Monkey King. (2018, June 24). Reusing the registration mechanism in STM32 timers. https://blog.csdn.net/qq_36969440/article/details/84455176?spm=1001.2014.3001.5501Related Articles:Using Callback Functions to Reduce Program CouplingHow to Optimize Microcontroller Program Code?

Leave a Comment