• 基于单向链表结构的软件虚拟定时器的设计与构建


    基于单向链表结构的软件虚拟定时器的设计与构建

    作者日期版本说明
    Dog Tao2022.07.30V1.0开始建立文档

    单向链表设计

    单向链表的设计参考了《链表(c语言实现)》一文。

    单向链表头文件

    单向链表头文件的名称为aw_slist.h

    #pragma once
    
    #include 
    #include "FreeRTOS.h"
    
    /**
     * 无头单向非循环链表数据类型构造
     *
     * 注意:
     * 1. 无头单向非循环链表只能使用链表头节点地址表示整个链表,否则链表当前指向节点前的节点将无法找到。
     * 2. 新建一个列表即定义一个SListNode类型的指针变量(例如SListNode *listTimer = NULL),然后使用头增、尾增等操作增加节点。
     * 3. 列表listTimer的位置将由这些操作函数自动调整,不可手动修改listTimer指针的值!
     * 4. 此链表支持任意类型数据(void *),但需要在构造节点前与获取数据后进行强制类型转换,将其恢复为原来的类型。
     */
    #define LIST_MALLOC  pvPortMalloc
    // #define LIST_MALLOC  malloc
    
    #define LIST_FREE  vPortFree
    // #define LIST_FREE  free
    
    // 遍历链表时的回调函数类型定义
    typedef void (*SList_TraversalCB)(void *node_data, size_t data_size);
    
    // 单向无头非循环链表的节点定义
    typedef struct _SListNode
    {
        void *data;                 // void *指针可以指向任意类型数据
        struct _SListNode* next;     // 指向下一个数据
        size_t data_len;            // 数据大小(字节数)
    } SListNode;
    
    /**
     * @brief 单链表申请节点
     *      可以使用任意类型的数据构造节点,但需要传入正确的数据大小,传入的数据会存储在节点中。
     *      data成员指向传入数据的起始地址,data_len成员存储了传入数据的长度。
     * @param node_data 传入数据的起始地址
     * @param data_size 传入数据的长度
     * @return SListNode* 申请的链表节点(申请失败返回NULL)
     */
    SListNode *SList_NodeCreate(void *node_data, size_t data_size);
    
    /**
     * @brief 单链表遍历打印
     *      依次输出链表中的各个节点内容(打印data成员指向的数据)。
     * @param plist 链表指针(首地址)
     * @param traversalHandler 遍历链表节点时,依次获取节点数据后执行的回调函数
     * @return int 操作返回值
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_Traversal(SListNode *plist, SList_TraversalCB traversalHandler);
    
    /**
     * @brief 将链表指针移动到链表尾部节点(慎用)
     *      无头单向非循环链表只能使用链表头节点地址表示整个链表,否则链表当前指向节点前的节点将无法找到。
     * @param pplist 链表指针(首地址)
     *      使用二级指针(指向链表的指针)是方便直接修改链表本身的地址,
     *      如果传入一级指针,则临时变量只能实现类似“读”而不能“写”的效果。
     * @return int 操作返回值
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_MoveToTail(SListNode **pplist);
    
    /**
     * @brief 单链表尾插
     *      从链表尾部插入一个节点。如果传入的链表为空,则将申请节点作为链表的起始节点。
     * @param pplist 链表指针(首地址)
     *      使用二级指针(指向链表的指针)是方便直接修改链表本身的地址,
     *      如果传入一级指针,则临时变量只能实现类似“读”而不能“写”的效果。
     * @param node_data 传入数据的起始地址
     * @param data_size 传入数据的长度
     * @return 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_PushTail(SListNode **pplist, void *node_data, size_t data_size);
    
    /**
     * @brief 单链表的尾删
     *      从链表尾部删除一个节点(删除最后一个节点)。
     * @param pplist 链表指针(首地址)
     *      使用二级指针(指向链表的指针)是方便直接修改链表本身的地址,
     *      如果传入一级指针,则临时变量只能实现类似“读”而不能“写”的效果。
     * @return 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_PopTail(SListNode **pplist);
    
    /**
     * @brief 单链表的头插
     *      从链表的头部插入一个节点。
     * @param pplist 链表指针(首地址)
     *      使用二级指针(指向链表的指针)是方便直接修改链表本身的地址,
     *      如果传入一级指针,则临时变量只能实现类似“读”而不能“写”的效果。
     * @param node_data 传入数据的起始地址
     * @param data_size 传入数据的长度
     * @return 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_PushHead(SListNode **pplist, void *node_data, size_t data_size);
    
    /**
     * @brief 单链表的头删
     *      从链表的头部删除一个节点(删除第一个节点)。
     * @param pplist 链表指针(首地址)
     *      使用二级指针(指向链表的指针)是方便直接修改链表本身的地址,
     *      如果传入一级指针,则临时变量只能实现类似“读”而不能“写”的效果。
     * @return 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_PopHead(SListNode **pplist);
    
    /**
     * @brief 单链表的查找(慎用)
     *      采用了依次比较各个节点的数据是否与传入数据一致的方式实现节点查找。
     *      此函数的执行效率较低,慎用。
     * @param plist 链表指针(首地址)
     * @param node_data 用于比较数据的起始地址
     * @param data_size 用于比较数据的长度
     * @return SListNode* 查找到的链表节点,如果未找到则返回NULL
     */
    SListNode *SList_FindNode(SListNode *plist, void *node_data, size_t data_size);
    
    /**
     * @brief 单链表移除节点
     *      查找相同数据的节点,然后删除该节点,并重新拼接链表。
     * @param pplist 链表指针(首地址)
     *      使用二级指针(指向链表的指针)是方便直接修改链表本身的地址,
     *      如果传入一级指针,则临时变量只能实现类似“读”而不能“写”的效果。
     * @param node_data 传入数据的起始地址
     * @param data_size 传入数据的长度
     * @return int 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_RemoveNode(SListNode **pplist, void *node_data, size_t data_size);
    
    /**
     * @brief 单链表在pos位置之后插入一个新的节点
     * @param pos 节点的位置
     * @param node_data
     * @param data_size
     * @return 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_InsertAfter(SListNode *pos, void *node_data, size_t data_size);
    
    /**
     * @brief 单链表删除pos位置之后的一个节点
     * @param pos 节点的位置
     * @return 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    // 单链表删除pos位置之后的值
    int SList_RemoveAfter(SListNode *pos);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161

    单向链表源文件

    单向链表头文件的名称为aw_slist.c

    #include "aw_slist.h"
    
    /**
     * @brief 单链表申请节点
     *      可以使用任意类型的数据构造节点,但需要传入正确的数据大小。
     *      data成员指向传入数据的起始地址,data_len成员存储了传入数据的长度。
     * @param node_data 传入数据的起始地址
     * @param data_size 传入数据的长度
     * @return SListNode* 申请的链表节点(申请失败返回NULL)
     */
    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);     // data指针 指向额外分配的数据空间
            // memcpy(tmp->data, node_data, data_size); // 将传入的数据copy到额外分配的数据空间中
            tmp->data = node_data;                      // data指针 指向传入的数据指针
            tmp->data_len = data_size;                  // 存储传入数据的长度
            tmp->next = NULL;                           // 下一个节点初始化为NULL
            return tmp;
        }
    }
    
    /**
     * @brief 单链表遍历打印
     *      依次输出链表中的各个节点内容(打印data成员指向的数据)。
     * @param plist 链表指针(首地址)
     * @param traversalHandler 遍历链表节点时,依次获取节点数据后执行的回调函数
     * @return int 操作返回值
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    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 将链表指针移动到链表尾部节点(慎用)
     *      无头单向非循环链表只能使用链表头节点地址表示整个链表,否则链表当前指向节点前的节点将无法找到。
     * @param pplist 链表指针(首地址)
     *      使用二级指针(指向链表的指针)是方便直接修改链表本身的地址,
     *      如果传入一级指针,则临时变量只能实现类似“读”而不能“写”的效果。
     * @return int 操作返回值
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_MoveToTail(SListNode **pplist)
    {
        // 链表为空
        if (*pplist == NULL)
        {
            return -1;
        }
        SListNode *tail = *pplist;
        // 沿着链表节点顺序查到到最后一个节点(next成员指向NULL)
        while (tail->next != NULL)
        {
            tail = tail->next;
        }
        *pplist = tail;
        return 0;
    }
    
    /**
     * @brief 单链表尾插
     *      从链表尾部插入一个节点。如果传入的链表为空,则将申请节点作为链表的起始节点。
     * @param pplist 链表指针(首地址)
     *      使用二级指针(指向链表的指针)是方便直接修改链表本身的地址,
     *      如果传入一级指针,则临时变量只能实现类似“读”而不能“写”的效果。
     * @param node_data 传入数据的起始地址
     * @param data_size 传入数据的长度
     * @return 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_PushTail(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
        {
            // 获取链表的首个节点
            SListNode *tail = *pplist;
            // 沿着链表节点顺序查到到最后一个节点(next成员指向NULL)
            while (tail->next != NULL)
            {
                tail = tail->next;
            }
            // 将新申请的节点追加到链表的尾部
            tail->next = newnode;
        }
        return 0;
    }
    
    /**
     * @brief 单链表的尾删
     *      从链表尾部删除一个节点(删除最后一个节点)。
     * @param pplist 链表指针(首地址)
     *      使用二级指针(指向链表的指针)是方便直接修改链表本身的地址,
     *      如果传入一级指针,则临时变量只能实现类似“读”而不能“写”的效果。
     * @return 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_PopTail(SListNode **pplist)
    {
        // 检查传入的链表指针是否为空
        if (*pplist == NULL)
        {
            return -1;
        }
        SListNode *cur = *pplist;
        SListNode *prev = NULL;
        // 如果当前节点已是尾节点(链表仅一个节点),则释放该节点(删除整个链表)
        if (cur->next == NULL)
        {
            LIST_FREE(cur);
            *pplist = NULL;
        }
        else
        {
            // 直到抵达尾节点(下一个节点为空)
            while (cur->next != NULL)
            {
                // 记录当前节点为前一个节点
                prev = cur;
                // 记录下一个节点为当前节点
                cur = cur->next;
            }
            // 删除尾节点
            LIST_FREE(cur);
            // 将前一个节点标记为尾节点
            prev->next = NULL;
        }
        return 0;
    }
    
    /**
     * @brief 单链表的头插
     *      从链表的头部插入一个节点。
     * @param pplist 链表指针(首地址)
     *      使用二级指针(指向链表的指针)是方便直接修改链表本身的地址,
     *      如果传入一级指针,则临时变量只能实现类似“读”而不能“写”的效果。
     * @param node_data 传入数据的起始地址
     * @param data_size 传入数据的长度
     * @return 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    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
        {
            // 头插
            newnode->next = *pplist;
            *pplist = newnode;
        }
        return 0;
    }
    
    /**
     * @brief 单链表的头删
     *      从链表的头部删除一个节点(删除第一个节点)。
     * @param pplist 链表指针(首地址)
     *      使用二级指针(指向链表的指针)是方便直接修改链表本身的地址,
     *      如果传入一级指针,则临时变量只能实现类似“读”而不能“写”的效果。
     * @return 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_PopHead(SListNode **pplist)
    {
        // 链表为空
        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 单链表的查找(慎用)
     *      采用了依次比较各个节点的数据是否与传入数据一致的方式实现节点查找。
     *      此函数的执行效率较低,慎用。
     * @param plist 链表指针(首地址)
     * @param node_data 用于比较数据的起始地址
     * @param data_size 用于比较数据的长度
     * @return SListNode* 查找到的链表节点,如果未找到则返回NULL
     */
    SListNode *SList_FindNode(SListNode *plist, void *node_data, size_t data_size)
    {
        // 链表为空
        if (plist == NULL)
        {
            return NULL;
        }
        while (plist != NULL)
        {
            // 数据长度不一致,直接返回NULL
            if (plist->data_len != data_size)
            {
                return NULL;
            }
            // 比较节点数据
            uint8_t check = 1;
            for (int i = 0; i < data_size; i++)
            {
                // 数据不同直接退出比较,并标记结果
                uint8_t data1 = *(uint8_t *)(plist->data + i);
                uint8_t data2 = *(uint8_t *)(node_data + i);
                if (data1 != data2)
                {
                    check = 0;
                    break;
                }
            }
            // 如果比较结果相同,则返回当前节点的指针
            if (check != 0)
            {
                return plist;
            }
            // 下一个节点
            plist = plist->next;
        }
        return NULL;
    }
    
    /**
     * @brief 单链表移除节点
     *      查找相同数据的节点,然后删除该节点,并重新拼接链表。
     * @param pplist 链表指针(首地址)
     *      使用二级指针(指向链表的指针)是方便直接修改链表本身的地址,
     *      如果传入一级指针,则临时变量只能实现类似“读”而不能“写”的效果。
     * @param node_data 传入数据的起始地址
     * @param data_size 传入数据的长度
     * @return int 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_RemoveNode(SListNode **pplist, void *node_data, size_t data_size)
    {
        // 链表为空
        if (*pplist == NULL)
        {
            return -1;
        }
        // 记录前一个节点
        SListNode * preNode = NULL;
        SListNode * currNode = *pplist;
        while (currNode != NULL)
        {
            // 数据长度不一致,直接返回NULL
            if (currNode->data_len != data_size)
            {
                return -1;
            }
            // 比较节点数据
            uint8_t check = 1;
            for (int i = 0; i < data_size; i++)
            {
                // 数据不同直接退出比较,并标记结果
                uint8_t data1 = *(uint8_t *)(currNode->data + i);
                uint8_t data2 = *(uint8_t *)(node_data + i);
                if (data1 != data2)
                {
                    check = 0;
                    break;
                }
            }
            // 如果比较结果相同,则删除当前的节点
            if (check != 0)
            {
                // 删除第一个节点,则将下一个节点设置为起始节点
                if(preNode == NULL)
                {
                    *pplist = currNode->next;
                }
                else
                {
                    preNode->next = currNode->next;
                    free(currNode);
                }
                return 0;
            }
            // 更新上一个节点与当前节点
            preNode = currNode;
            currNode = currNode->next;
        }
        // 未找到节点
        return -1;
    }
    
    /**
     * @brief 单链表在pos位置之后插入一个新的节点
     * @param pos 节点的位置
     * @param node_data
     * @param data_size
     * @return 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    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 单链表删除pos位置之后的一个节点
     * @param pos 节点的位置
     * @return 操作结果
     *  @arg 0 操作成功
     *  @arg -1 操作失败
     */
    int SList_RemoveAfter(SListNode *pos)
    {
        if (pos == NULL)
        {
            return -1;
        }
        // pos位置之后无数据
        if (pos->next == NULL)
        {
            return -1;
        }
        SListNode *prev = pos;
        SListNode *cur = pos->next;
        prev->next = cur->next;
        free(cur);
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389

    软件定时器设计

    软件定时器库通过内置的单向链表(静态链表)管理定时器SoftwareTimer_TypeDef结构体定义了软件定时器的类型。通过SoftwareTimer_Register方法,将定义的软件定时器注册生效(添加到链表中,参与轮询更新状态)。通过SoftwareTimer_Remove方法,将已经注册的软件定时器移除(不再参与轮询更新状态)。

    软件定时器头文件

    软件定时器头文件的名称为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 注册一个软件定时器
     *      通过向管理虚拟定时器的单向链表中添加一个定时器节点实现注册。
     *      注意定时器类型定义的结构体不能为临时变量,因为注册过程仅记录定时器指针位置,而不会复制值。
     * @param virtual_timer 定义的虚拟定时器结构体指针
     * @return int 注册结果
     *  @arg 0 注册成功
     *  @arg -1 注册失败
     */
    int SoftwareTimer_Register(SoftwareTimer_TypeDef *virtual_timer);
    
    /**
     * @brief 移除一个软件定时器
     * @param virtual_timer 已注册的软件定时器结构体指针
     * @return int 移除结果
     *  @arg 0 移除成功
     *  @arg -1 移除失败
     */
    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);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    软件定时器源文件

    软件定时器头文件的名称为virtual_timer.c

    #include "virtual_timer.h"
    
    // 用于管理虚拟定时器的单向链表
     static SListNode *virtual_timer_list = NULL;
     
    /**
     * @brief 虚拟定时器单向链表的遍历回调,实现定时器的一次状态更新
     * @param node_data 节点数据指针
     * @param data_size 节点数据大小
     */
    static void timerList_Handler(void *node_data, size_t data_size)
    {
        // 将节点数据解析为虚拟定时器
        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 注册一个软件定时器
     *      通过向管理虚拟定时器的单向链表中添加一个定时器节点实现注册。
     *      注意定时器类型定义的结构体不能为临时变量,因为注册过程仅记录定时器指针位置,而不会复制值。
     * @param virtual_timer 定义的虚拟定时器结构体指针
     * @return int 注册结果
     *  @arg 0 注册成功
     *  @arg -1 注册失败
     */
    int SoftwareTimer_Register(SoftwareTimer_TypeDef *virtual_timer)
    {
        return SList_PushTail(&virtual_timer_list, virtual_timer, sizeof(SoftwareTimer_TypeDef));
    }
    
    /**
     * @brief 移除一个软件定时器
     * @param virtual_timer 已注册的软件定时器结构体指针
     * @return int 移除结果
     *  @arg 0 移除成功
     *  @arg -1 移除失败
     */
    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)
    {
        // 遍历列表,执行回调
        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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124

    使用示例

    单向链表使用

    本文构造的基于SListNode节点的链表属于无头单向非循环链表。链表以第一个的节点指针的形式出现,并由内置的操作函数(例如链表头插、尾插、头删、尾删等)调整节点指向位置。切不可随意手动调整链表的指向位置,否则可能出现未知的错误。

    使用时首先新建一个链表,即定义一个SListNode类型的指针变量(例如SListNode *listTimer = NULL),然后使用头增、尾增等操作增加节点。此链表支持任意类型数据(void *),但需要在构造节点前与获取数据后进行强制类型转换,将其恢复为原来的类型。

    链表的节点只记录对应的数据指针而不会分配内存并复制一份数据的副本。因此,链表节点指向的数据( void *node_data, size_t data_size)需要为全局变量或者为静态变量切勿使用临时变量构造链表节点!例如在一个函数中定义一个数据变量并生成节点添加到链表中,则函数执行完毕后,节点指向的数据变量会被系统自动回收,导致指向错误。

    本文设计的软件虚拟定时器就是基于一个单向链表进行管理的。通过SList_Traversal方法可以遍历链表的节点并执行注册的回调函数。在回调函数中可以实现数据的类型还原、定时器状态更新等一系列的处理。通过SList_PushTail方法实现链表的节点添加(注册新的定时器),通过SList_RemoveNode方法实现链表的节点删除(移除注册的定时器)。具体的实现可以参考软件定时器的源码。

    虚拟定时器使用

    • 定义全局虚拟定时器结构体变量,如果需要多个定时器,可以直接定义一个虚拟定时器数组。
    SoftwareTimer_TypeDef timer[10];
    
    • 1
    • 定义timeout回调函数,并初始化虚拟定时器结构体变量。可以使用SoftwareTimer_Init函数进行初始化操作。
      定义timeout回调函数,可以通过参数timer_id确定触发源。如果使用timer_id识别触发源,则可以使用一个回调函数初始化全部的虚拟定时器。
    void timeout_handler(uint8_t timer_id)
    {
        // Do something.
    }
    
    • 1
    • 2
    • 3
    • 4
    • 注册并使能初始化完成的定时器。使用SoftwareTimer_Register完成定时器的注册,使用SoftwareTimer_Enable完成定时器的使能。
        for (int i = 0; i < 10; i++)
        {
            SoftwareTimer_Init(timer+i, i, 10, timeout_handler, -1);        // 初始化
            SoftwareTimer_Register(timer+i);                                    // 注册
            SoftwareTimer_Enable(timer+i);                                      // 使能(启动)
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 在合适的地方周期性调用SoftwareTimer_Tick方法,刷新定时器的状态。
    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • timer倒计时完成后,会自动执行timeout的回调函数,并根据设定的工作模式决定是停止定时器工作还是重新开始计数(记时)。
  • 相关阅读:
    设计模式之外观模式
    【记录】服务器本地tensorboard可视化events.out.tfevents.***文件
    SQL-Labs46关order by注入姿势
    U81206:链式前向星模板题
    命令行下使用百度网盘(bypy)
    Android常用的延迟执行任务及轮询定时任务的几种方式
    ffmpeg 图片合成视频
    python 编写 phpstudy_2016 EXP 漏洞利用脚本之RCE
    【SQL】以mysql为例系统学习DQL理论知识
    【算法】【递归与动态规划模块】数组字符串转字母的所有种数
  • 原文地址:https://blog.csdn.net/u013441358/article/details/126073970