Design and construction of software virtual timer based on one-way linked list structure
author | date | edition | explain |
---|---|---|---|
Dog Tao | 2022.07.30 | V1.0 | Start document creation |
One way linked list design
One way linked list design reference Linked list (implemented in c language) One article.
One way linked list header file
The name of the one-way linked list header file is aw_slist.h.
#pragma once #include <stdio.h> #include "FreeRTOS.h" /** * Data type construction of headless one-way acyclic linked list * * be careful: * 1. The headless one-way acyclic linked list can only use the node address of the chain header to represent the whole linked list, otherwise the node in front of the node currently pointed to by the linked list will not be found. * 2. Create a new list, that is, define a SListNode type pointer variable (for example, SListNode *listTimer = NULL), and then use operations such as head increase and tail increase to add nodes. * 3. The position of the listTimer in the list will be automatically adjusted by these operation functions. You cannot manually modify the value of the listTimer pointer! * 4. This linked list supports any type of data (void *), but it requires forced type conversion before constructing the node and after obtaining the data to restore it to its original type. */ #define LIST_MALLOC pvPortMalloc // #define LIST_MALLOC malloc #define LIST_FREE vPortFree // #define LIST_FREE free // Callback function type definition when traversing the linked list typedef void (*SList_TraversalCB)(void *node_data, size_t data_size); // Node definition of one-way headless acyclic linked list typedef struct _SListNode { void *data; // void * pointer can point to any type of data struct _SListNode* next; // Point to next data size_t data_len; // Data size (bytes) } SListNode; /** * @brief Single linked list application node * You can use any type of data to construct a node, but you need to pass in the correct data size, and the incoming data will be stored in the node. * data Member points to the starting address of incoming data, data_ The len member stores the length of the incoming data. * @param node_data Starting address of incoming data * @param data_size Length of incoming data * @return SListNode* Requested linked list node (NULL returned if the application fails) */ SListNode *SList_NodeCreate(void *node_data, size_t data_size); /** * @brief Single linked list traversal printing * Output the contents of each node in the linked list in turn (print the data pointed to by the data member). * @param plist Linked list pointer (first address) * @param traversalHandler When traversing the linked list nodes, the callback function is executed after obtaining the node data in turn * @return int Operation return value * @arg 0 Operation successful * @arg -1 operation failed */ int SList_Traversal(SListNode *plist, SList_TraversalCB traversalHandler); /** * @brief Move the linked list pointer to the end node of the linked list (use with caution) * The headless one-way acyclic linked list can only use the node address of the chain header to represent the whole linked list, otherwise the node in front of the node currently pointed to by the linked list will not be found. * @param pplist Linked list pointer (first address) * Using the secondary pointer (the pointer to the linked list) is convenient to directly modify the address of the linked list itself, * If the first level pointer is passed in, the temporary variable can only achieve the effect of "reading" but not "writing". * @return int Operation return value * @arg 0 Operation successful * @arg -1 operation failed */ int SList_MoveToTail(SListNode **pplist); /** * @brief Single chain tail insertion * Insert a node from the end of the linked list. If the incoming linked list is empty, the application node will be used as the starting node of the linked list. * @param pplist Linked list pointer (first address) * Using the secondary pointer (the pointer to the linked list) is convenient to directly modify the address of the linked list itself, * If the first level pointer is passed in, the temporary variable can only achieve the effect of "reading" but not "writing". * @param node_data Starting address of incoming data * @param data_size Length of incoming data * @return Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_PushTail(SListNode **pplist, void *node_data, size_t data_size); /** * @brief Tail deletion of single linked list * Delete a node from the end of the linked list (delete the last node). * @param pplist Linked list pointer (first address) * Using the secondary pointer (the pointer to the linked list) is convenient to directly modify the address of the linked list itself, * If the first level pointer is passed in, the temporary variable can only achieve the effect of "reading" but not "writing". * @return Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_PopTail(SListNode **pplist); /** * @brief Header insertion of single linked list * Insert a node from the head of the linked list. * @param pplist Linked list pointer (first address) * Using the secondary pointer (the pointer to the linked list) is convenient to directly modify the address of the linked list itself, * If the first level pointer is passed in, the temporary variable can only achieve the effect of "reading" but not "writing". * @param node_data Starting address of incoming data * @param data_size Length of incoming data * @return Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_PushHead(SListNode **pplist, void *node_data, size_t data_size); /** * @brief Header deletion of single linked list * Delete a node from the head of the linked list (delete the first node). * @param pplist Linked list pointer (first address) * Using the secondary pointer (the pointer to the linked list) is convenient to directly modify the address of the linked list itself, * If the first level pointer is passed in, the temporary variable can only achieve the effect of "reading" but not "writing". * @return Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_PopHead(SListNode **pplist); /** * @brief Search of single linked list (use with caution) * The node search is realized by successively comparing whether the data of each node is consistent with the incoming data. * The execution efficiency of this function is low, so use it with caution. * @param plist Linked list pointer (first address) * @param node_data Starting address for comparing data * @param data_size Length used to compare data * @return SListNode* If the found linked list node is not found, NULL will be returned */ SListNode *SList_FindNode(SListNode *plist, void *node_data, size_t data_size); /** * @brief Single linked list remove node * Find the node with the same data, then delete the node and splice the linked list again. * @param pplist Linked list pointer (first address) * Using the secondary pointer (the pointer to the linked list) is convenient to directly modify the address of the linked list itself, * If the first level pointer is passed in, the temporary variable can only achieve the effect of "reading" but not "writing". * @param node_data Starting address of incoming data * @param data_size Length of incoming data * @return int Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_RemoveNode(SListNode **pplist, void *node_data, size_t data_size); /** * @brief The single linked list inserts a new node after the pos position * @param pos Location of nodes * @param node_data * @param data_size * @return Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_InsertAfter(SListNode *pos, void *node_data, size_t data_size); /** * @brief The single linked list deletes a node after the pos position * @param pos Location of nodes * @return Operation results * @arg 0 Operation successful * @arg -1 operation failed */ // Value after pos position is deleted in single linked list int SList_RemoveAfter(SListNode *pos);
One way linked list source file
The name of the one-way linked list header file is aw_slist.c.
#include "aw_slist.h" /** * @brief Single linked list application node * You can use any type of data to construct nodes, but you need to pass in the correct data size. * data Member points to the starting address of incoming data, data_ The len member stores the length of the incoming data. * @param node_data Starting address of incoming data * @param data_size Length of incoming data * @return SListNode* Requested linked list node (NULL returned if the application fails) */ SListNode *SList_NodeCreate(void *node_data, size_t data_size) { // SListNode *tmp = (SListNode *)LIST_MALLOC(sizeof(SListNode) + data_size); SListNode *tmp = (SListNode *)LIST_MALLOC(sizeof(SListNode)); if (tmp == NULL) { return NULL; } else { // tmp->data = tmp + sizeof(SListNode); // The data pointer points to the extra allocated data space // memcpy(tmp->data, node_data, data_size); // copy the incoming data into the extra allocated data space tmp->data = node_data; // The data pointer points to the incoming data pointer tmp->data_len = data_size; // Store the length of incoming data tmp->next = NULL; // The next node is initialized to NULL return tmp; } } /** * @brief Single linked list traversal printing * Output the contents of each node in the linked list in turn (print the data pointed to by the data member). * @param plist Linked list pointer (first address) * @param traversalHandler When traversing the linked list nodes, the callback function is executed after obtaining the node data in turn * @return int Operation return value * @arg 0 Operation successful * @arg -1 operation failed */ int SList_Traversal(SListNode *plist, SList_TraversalCB traversalHandler) { SListNode *head = plist; while (head != NULL) { if (traversalHandler != NULL) { traversalHandler(head->data, head->data_len); } head = head->next; } return 0; } /** * @brief Move the linked list pointer to the end node of the linked list (use with caution) * The headless one-way acyclic linked list can only use the node address of the chain header to represent the whole linked list, otherwise the node in front of the node currently pointed to by the linked list will not be found. * @param pplist Linked list pointer (first address) * Using the secondary pointer (the pointer to the linked list) is convenient to directly modify the address of the linked list itself, * If the first level pointer is passed in, the temporary variable can only achieve the effect of "reading" but not "writing". * @return int Operation return value * @arg 0 Operation successful * @arg -1 operation failed */ int SList_MoveToTail(SListNode **pplist) { // The linked list is empty if (*pplist == NULL) { return -1; } SListNode *tail = *pplist; // Look up the last node in the order of the linked list nodes (the next member points to NULL) while (tail->next != NULL) { tail = tail->next; } *pplist = tail; return 0; } /** * @brief Single chain tail insertion * Insert a node from the end of the linked list. If the incoming linked list is empty, the application node will be used as the starting node of the linked list. * @param pplist Linked list pointer (first address) * Using the secondary pointer (the pointer to the linked list) is convenient to directly modify the address of the linked list itself, * If the first level pointer is passed in, the temporary variable can only achieve the effect of "reading" but not "writing". * @param node_data Starting address of incoming data * @param data_size Length of incoming data * @return Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_PushTail(SListNode **pplist, void *node_data, size_t data_size) { // Apply for single chain node SListNode *newnode = SList_NodeCreate(node_data, data_size); if(newnode == NULL) { return -1; } // If the incoming linked list is empty, the requested node will be set as the first node if (*pplist == NULL) { *pplist = newnode; } // If the incoming linked list is not empty else { // Get the first node of the linked list SListNode *tail = *pplist; // Look up the last node in the order of the linked list nodes (the next member points to NULL) while (tail->next != NULL) { tail = tail->next; } // Append the newly applied node to the end of the linked list tail->next = newnode; } return 0; } /** * @brief Tail deletion of single linked list * Delete a node from the end of the linked list (delete the last node). * @param pplist Linked list pointer (first address) * Using the secondary pointer (the pointer to the linked list) is convenient to directly modify the address of the linked list itself, * If the first level pointer is passed in, the temporary variable can only achieve the effect of "reading" but not "writing". * @return Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_PopTail(SListNode **pplist) { // Check whether the passed in linked list pointer is null if (*pplist == NULL) { return -1; } SListNode *cur = *pplist; SListNode *prev = NULL; // If the current node is already the tail node (the linked list has only one node), release the node (delete the entire linked list) if (cur->next == NULL) { LIST_FREE(cur); *pplist = NULL; } else { // Until the tail node is reached (the next node is empty) while (cur->next != NULL) { // Record the current node as the previous node prev = cur; // Record the next node as the current node cur = cur->next; } // Delete tail node LIST_FREE(cur); // Mark the previous node as the tail node prev->next = NULL; } return 0; } /** * @brief Header insertion of single linked list * Insert a node from the head of the linked list. * @param pplist Linked list pointer (first address) * Using the secondary pointer (the pointer to the linked list) is convenient to directly modify the address of the linked list itself, * If the first level pointer is passed in, the temporary variable can only achieve the effect of "reading" but not "writing". * @param node_data Starting address of incoming data * @param data_size Length of incoming data * @return Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_PushHead(SListNode **pplist, void *node_data, size_t data_size) { SListNode *newnode = SList_NodeCreate(node_data, data_size); if(newnode == NULL) { return -1; } if (*pplist == NULL) { *pplist = newnode; } else { // Head insert newnode->next = *pplist; *pplist = newnode; } return 0; } /** * @brief Header deletion of single linked list * Delete a node from the head of the linked list (delete the first node). * @param pplist Linked list pointer (first address) * Using the secondary pointer (the pointer to the linked list) is convenient to directly modify the address of the linked list itself, * If the first level pointer is passed in, the temporary variable can only achieve the effect of "reading" but not "writing". * @return Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_PopHead(SListNode **pplist) { // The linked list is empty if (*pplist == NULL) { return -1; } SListNode *cur = *pplist; if ((*pplist)->next == NULL) { free(*pplist); *pplist = NULL; } else { cur = cur->next; free(*pplist); *pplist = cur; } return 0; } /** * @brief Search of single linked list (use with caution) * The node search is realized by successively comparing whether the data of each node is consistent with the incoming data. * The execution efficiency of this function is low, so use it with caution. * @param plist Linked list pointer (first address) * @param node_data Starting address for comparing data * @param data_size Length used to compare data * @return SListNode* If the found linked list node is not found, NULL will be returned */ SListNode *SList_FindNode(SListNode *plist, void *node_data, size_t data_size) { // The linked list is empty if (plist == NULL) { return NULL; } while (plist != NULL) { // The data length is inconsistent, and NULL is returned directly if (plist->data_len != data_size) { return NULL; } // Compare node data uint8_t check = 1; for (int i = 0; i < data_size; i++) { // If the data is different, exit the comparison directly and mark the results uint8_t data1 = *(uint8_t *)(plist->data + i); uint8_t data2 = *(uint8_t *)(node_data + i); if (data1 != data2) { check = 0; break; } } // If the comparison result is the same, the pointer of the current node is returned if (check != 0) { return plist; } // Next node plist = plist->next; } return NULL; } /** * @brief Single linked list remove node * Find the node with the same data, then delete the node and splice the linked list again. * @param pplist Linked list pointer (first address) * Using the secondary pointer (the pointer to the linked list) is convenient to directly modify the address of the linked list itself, * If the first level pointer is passed in, the temporary variable can only achieve the effect of "reading" but not "writing". * @param node_data Starting address of incoming data * @param data_size Length of incoming data * @return int Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_RemoveNode(SListNode **pplist, void *node_data, size_t data_size) { // The linked list is empty if (*pplist == NULL) { return -1; } // Record previous node SListNode * preNode = NULL; SListNode * currNode = *pplist; while (currNode != NULL) { // The data length is inconsistent, and NULL is returned directly if (currNode->data_len != data_size) { return -1; } // Compare node data uint8_t check = 1; for (int i = 0; i < data_size; i++) { // If the data is different, exit the comparison directly and mark the results uint8_t data1 = *(uint8_t *)(currNode->data + i); uint8_t data2 = *(uint8_t *)(node_data + i); if (data1 != data2) { check = 0; break; } } // If the comparison results are the same, delete the current node if (check != 0) { // Delete the first node, and set the next node as the starting node if(preNode == NULL) { *pplist = currNode->next; } else { preNode->next = currNode->next; free(currNode); } return 0; } // Update the previous node and the current node preNode = currNode; currNode = currNode->next; } // Node not found return -1; } /** * @brief The single linked list inserts a new node after the pos position * @param pos Location of nodes * @param node_data * @param data_size * @return Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_InsertAfter(SListNode *pos, void *node_data, size_t data_size) { if (pos == NULL) { return -1; } SListNode *newnode = SList_NodeCreate(node_data, data_size); if(newnode == NULL) { return -1; } newnode->next = pos->next; pos->next = newnode; return 0; } /** * @brief The single linked list deletes a node after the pos position * @param pos Location of nodes * @return Operation results * @arg 0 Operation successful * @arg -1 operation failed */ int SList_RemoveAfter(SListNode *pos) { if (pos == NULL) { return -1; } // No data after pos position if (pos->next == NULL) { return -1; } SListNode *prev = pos; SListNode *cur = pos->next; prev->next = cur->next; free(cur); return 0; }
Design of software timer
The software timer library manages timers through the built-in one-way linked list (static linked list). SoftwareTimer_ The typedef structure defines the type of software timer. Via SoftwareTimer_Register method, register the defined software timer to take effect (add it to the linked list to participate in polling the update status). Via SoftwareTimer_Remove method to remove the registered software timer (no longer participate in polling update status).
Software timer header file
The name of the software timer header file is virtual_timer.h.
#pragma once #include "platform.h" #include "aw_slist.h" typedef void (*SoftwareTimer_TimeoutCB)(uint8_t timer_id); typedef struct { uint8_t id; uint32_t count; uint32_t expire; SoftwareTimer_TimeoutCB callback; uint8_t isEnable; // 0, disable 1,enable int16_t repeat; // -1, forever } SoftwareTimer_TypeDef; /** * @brief Register a software timer * Registration is achieved by adding a timer node to the one-way linked list that manages virtual timers. * Note that the structure defined by the timer type cannot be a temporary variable, because the registration process only records the position of the timer pointer, and does not copy the value. * @param virtual_timer Virtual timer structure pointer defined * @return int Registration results * @arg 0 login was successful * @arg -1 login has failed */ int SoftwareTimer_Register(SoftwareTimer_TypeDef *virtual_timer); /** * @brief Remove a software timer * @param virtual_timer Registered software timer structure pointer * @return int Remove results * @arg 0 Removed successfully * @arg -1 Removal failed */ int SoftwareTimer_Remove(SoftwareTimer_TypeDef *virtual_timer); /** * @brief Generally placed in the interrupt handler of the hardware timer * and called periodically. * @param none * @retval none */ void SoftwareTimer_Tick(void); /** * @brief Initialization of software timer. * @param virtual_timer Define of the virtual timer. * @param id: ID of the timer, which need to be initialized. * @param expire: The period of time to execute callback function (trigger timeout event). * @param callback: The callback function of timer. When the timer is expired, this function will be called. * @param repeat: Repeat times of the timer. * @arg -1: repeat forever * @arg 0: invalid * @arg more than 0: repeat times * @retval none */ void SoftwareTimer_Init(SoftwareTimer_TypeDef *virtual_timer, uint8_t id, uint32_t expire, SoftwareTimer_TimeoutCB callback, int16_t repeat); /** * @brief Enable the timer, which specified by ID. * @param virtual_timer: Define of the virtual timer. * @retval none */ void SoftwareTimer_Enable(SoftwareTimer_TypeDef *virtual_timer); /** * @brief Disable the timer, which specified by ID. * @param virtual_timer: Define of the virtual timer. * @retval none */ void SoftwareTimer_Disable(SoftwareTimer_TypeDef *virtual_timer);
Software timer source file
The name of the software timer header file is virtual_timer.c.
#include "virtual_timer.h" // One way linked list for managing virtual timers static SListNode *virtual_timer_list = NULL; /** * @brief The traversal callback of the virtual timer one-way linked list realizes a status update of the timer * @param node_data Node data pointer * @param data_size Node data size */ static void timerList_Handler(void *node_data, size_t data_size) { // Parse node data into virtual timers SoftwareTimer_TypeDef *timer = (SoftwareTimer_TypeDef *)node_data; if (timer->isEnable == 0 || timer->expire == 0) { return; } timer->count++; if (timer->count >= timer->expire) { timer->count = 0; if (timer->repeat > 0) { timer->repeat--; if (timer->callback != NULL) { timer->callback(timer->id); } } else if (timer->repeat == 0) { timer->isEnable = 0; // memset(timer, 0, sizeof(SoftwareTimerStruct)); } else // repeat < 0, repeat = 0 { if (timer->callback != NULL) { timer->callback(timer->id); } } } } /** * @brief Register a software timer * Registration is achieved by adding a timer node to the one-way linked list that manages virtual timers. * Note that the structure defined by the timer type cannot be a temporary variable, because the registration process only records the position of the timer pointer, and does not copy the value. * @param virtual_timer Virtual timer structure pointer defined * @return int Registration results * @arg 0 login was successful * @arg -1 login has failed */ int SoftwareTimer_Register(SoftwareTimer_TypeDef *virtual_timer) { return SList_PushTail(&virtual_timer_list, virtual_timer, sizeof(SoftwareTimer_TypeDef)); } /** * @brief Remove a software timer * @param virtual_timer Registered software timer structure pointer * @return int Remove results * @arg 0 Removed successfully * @arg -1 Removal failed */ int SoftwareTimer_Remove(SoftwareTimer_TypeDef *virtual_timer) { return SList_RemoveNode(&virtual_timer_list, virtual_timer, sizeof(SoftwareTimer_TypeDef)); } /** * @brief Generally placed in the interrupt handler of the hardware timer * and called periodically. * @param none * @retval none */ void SoftwareTimer_Tick(void) { // Traverse the list and execute callback SList_Traversal(virtual_timer_list, timerList_Handler); } /** * @brief Initialization of software timer. * @param virtual_timer Define of the virtual timer. * @param id: ID of the timer, which need to be initialized. * @param expire: The period of time to execute callback function (trigger timeout event). * @param callback: The callback function of timer. When the timer is expired, this function will be called. * @param repeat: Repeat times of the timer. * @arg -1: repeat forever * @arg 0: invalid * @arg more than 0: repeat times * @retval none */ void SoftwareTimer_Init(SoftwareTimer_TypeDef *virtual_timer, uint8_t id, uint32_t expire, SoftwareTimer_TimeoutCB callback, int16_t repeat) { virtual_timer->id = id; virtual_timer->expire = expire; virtual_timer->callback = callback; virtual_timer->repeat = repeat; virtual_timer->count = 0; virtual_timer->isEnable = 0; } /** * @brief Enable the timer, which specified by ID. * @param virtual_timer: Define of the virtual timer. * @retval none */ void SoftwareTimer_Enable(SoftwareTimer_TypeDef *virtual_timer) { virtual_timer->isEnable = 1; } /** * @brief Disable the timer, which specified by ID. * @param virtual_timer: Define of the virtual timer. * @retval none */ void SoftwareTimer_Disable(SoftwareTimer_TypeDef *virtual_timer) { virtual_timer->isEnable = 0; }
Use examples
Use of one-way linked list
The linked list based on SListNode node constructed in this paper belongs to headless one-way acyclic linked list. The linked list appears in the form of the first node pointer, and the node pointing position is adjusted by the built-in operation functions (such as chain header insertion, tail insertion, head deletion, tail deletion, etc.). Never manually adjust the pointing position of the linked list at will, otherwise unknown errors may occur.
When using, first create a linked list, that is, define a SListNode type pointer variable (for example, SListNode *listTimer = NULL), and then use operations such as head increase and tail increase to add nodes. This linked list supports any type of data (void *), but it requires forced type conversion before constructing the node and after obtaining the data to restore it to its original type.
The nodes of the linked list only record the corresponding data pointers without allocating memory and copying a copy of the data. Therefore, the data pointed to by the linked list node (void *node_data, size_t data_size) needs to be a global variable or a static variable. Do not use temporary variables to construct linked list nodes! For example, if a data variable is defined in a function and a node is generated and added to the linked list, the data variable pointed to by the node will be automatically recycled by the system after the function is executed, resulting in pointing errors.
The software virtual timer designed in this paper is based on a one-way linked list for management. Through slist_ The traversal method can traverse the nodes of the linked list and execute the registered callback function. In the callback function, a series of processing such as data type restoration, timer status update and so on can be realized. Through SList_PushTail method realizes the node addition of the linked list (register a new timer) through slist_ The removenode method implements the node deletion of the linked list (removing the registered timer). The specific implementation can refer to the source code of the software timer.
Virtual timer usage
- Define the global virtual timer structure variable. If you need multiple timers, you can directly define a virtual timer array.
SoftwareTimer_TypeDef timer[10];
- Define the timeout callback function and initialize the virtual timer structure variable. You can use SoftwareTimer_Init function performs initialization operation.
Define the timeout callback function through the parameter timer_id determines the trigger source. If timer is used_ ID identifies the trigger source, and a callback function can be used to initialize all virtual timers.
void timeout_handler(uint8_t timer_id) { // Do something. }
- Register and enable the timer for initialization completion. Using SoftwareTimer_Register completes the registration of timer and uses SoftwareTimer_Enable completes the enabling of the timer.
for (int i = 0; i < 10; i++) { SoftwareTimer_Init(timer+i, i, 10, timeout_handler, -1); // initialization SoftwareTimer_Register(timer+i); // register SoftwareTimer_Enable(timer+i); // Enable (start) }
- Call softwaretimer periodically where appropriate_ Tick method, refresh the status of the timer.
void led0_task(void *pvParameters) { TickType_t ticks = xTaskGetTickCount(); while(1) { MCU_LED = ~MCU_LED; SoftwareTimer_Tick(); xEventGroupSetBits(xCreatedEventGroup, TASK_BIT_1); vTaskDelayUntil(&ticks, 1000); } }
- After the timer countdown is completed, the callback function of timeout will be automatically executed, and whether to stop the timer or restart counting (timing) will be decided according to the set working mode.