【6】STM32·FreeRTOS·列表和列表项

目录

一、列表和列表项的简介

1.1、列表

1.2、列表项

1.3、迷你列表项

1.4、列表和列表项的关系

二、列表相关API函数介绍

2.1、初始化列表vListInitialise()

2.2、初始化列表项vListInitialiseItem()

2.3、列表插入列表项vListInsert()

2.4、列表末尾插入列表项vListInsertEnd()

2.5、列表移除列表项uxListRemove()

三、列表项的插入和删除实验

运行结果


一、列表和列表项的简介

列表:FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS 中的任务。

列表项:存放在列表中的项目

列表相当于链表,列表项相当于节点,FreeRTOS 中的列表是一个双向环形链表

列表的特点:列表项间的地址非连续,是人为的连接到一起的。列表项的数目是由后期添加的个数决定的,随时可以改变

数组的特点:数组成员地址是连续的,数组在最初确定了成员数量后期无法改变

在OS中任务的数量是不确定的,并且任务状态是会发生改变的,所以非常适用列表(链表)这种数据结构

1.1、列表

有关于列表的东西均在文件 list.c 和 list.h 中,下面是 list.h 中,列表相关结构体:

cpp 复制代码
typedef struct xLIST
{
    listFIRST_LIST_INTEGRITY_CHECK_VALUE      /* 校验值 */
    volatile UBaseType_t uxNumberOfItems;     /* 列表中的列表项数量 */
    ListItem_t * configLIST_VOLATILE pxIndex; /* 用于遍历列表项的指针 */
    MiniListItem_t xListEnd;                  /* 末尾列表项 */
    listSECOND_LIST_INTEGRITY_CHECK_VALUE     /* 校验值 */
} List_t;

列表结构示意图

1、在该结构体中,包含了两个宏,这两个宏是确定的已知常量,FreeRTOS 通过检查这两个常量的值,来判断列表的数据在程序运行过程中,是否遭到破坏,该功能一般用于调试,默认是不开启的。

2、成员 uxNumberOfItems,用于记录列表中列表项的个数(不包含 xListEnd)

3、成员 pxIndex 用于指向列表中的某个列表项,一般用于遍历列表中的所有列表项

4、成员变量 xListEnd 是一个迷你列表项,排在最末尾

1.2、列表项

列表项是列表中用于存放数据的地方,在 list.h 文件中,有列表项的相关结构体定义:

cpp 复制代码
struct xLIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE           /* 用于检测列表项的数据完整性 */
    configLIST_VOLATILE TickType_t xItemValue;          /* 列表项的值 */
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;     /* 下一个列表项 */
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /* 上一个列表项 */
    void * pvOwner;                                     /* 列表项的拥有者 */
    struct xLIST * configLIST_VOLATILE pxContainer;     /* 列表项所在列表 */
    listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE          /* 用于检测列表项的数据完整性 */
};
typedef struct xLIST_ITEM ListItem_t;

列表项结构示意图

1、成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序

2、成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项

3、成员变量 pxOwner 用于指向包含列表项的对象(通常是任务控制块)

4、成员变量 pxContainer 用于指向列表项所在列表

1.3、迷你列表项

迷你列表项也是列表项,但迷你列表项仅用于标记列表的末尾和挂载其他插入列表中的列表项

cpp 复制代码
struct xMINI_LIST_ITEM
{
    listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE             /* 用于检测数据完整性 */
    configLIST_VOLATILE TickType_t xItemValue;            /* 列表项的值 */
    struct xLIST_ITEM * configLIST_VOLATILE pxNext;       /* 下一个列表项 */
    struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;   /* 上一个列表项 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

迷你列表项结构示意图

1、成员变量 xItemValue 为列表项的值,这个值多用于按升序对列表中的列表项进行排序

2、成员变量 pxNext 和 pxPrevious 分别用于指向列表中列表项的下一个列表项和上一个列表项

3、迷你列表项只用于标记列表的末尾和挂载其他插入列表中的列表项,因此不需要成员变量 pxOwner 和 pxContainer,以节省内存开销

1.4、列表和列表项的关系

列表初始状态,以及即将插入的两个列表项如下:

二、列表相关API函数介绍

|-----------------------|-----------|
| 函数 | 描述 |
| vListInitialise() | 初始化列表 |
| vListInitialiseItem() | 初始化列表项 |
| vListInsert() | 列表插入列表项 |
| vListInsertEnd() | 列表末尾插入列表项 |
| uxListRemove() | 列表移除列表项 |

2.1、初始化列表vListInitialise()

|--------|--------|
| 形参 | 描述 |
| pxList | 待初始化列表 |

cpp 复制代码
void vListInitialise( List_t * const pxList )
{
    /* 初始化时,列表中只有 xListEnd,因此 pxIndex 指向 xListEnd */
    pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); 
    /* xListEnd 的值初始化为最大值,用于列表项升序排序时,排在最后 */
    pxList->xListEnd.xItemValue = portMAX_DELAY;

    /* 初始化时,列表中只有 xListEnd,因此上一个和下一个列表项都为 xListEnd 本身 */
    pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
    pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );

    /* 初始化时,列表中的列表项数量为 0(不包含 xListEnd) */
    pxList->uxNumberOfItems = ( UBaseType_t ) 0U;

    /* 初始化用于检测列表数据完整性的校验值 */
    listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
    listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}

初始化后列表结构

2.2、初始化列表项vListInitialiseItem()

|--------|---------|
| 形参 | 描述 |
| pxItem | 待初始化列表项 |

cpp 复制代码
void vListInitialiseItem( ListItem_t * const pxItem )
{
    /* 初始化时,列表项所在列表设为空 */
    pxItem->pxContainer = NULL;

    /* 初始化用于检测列表项数据完整性的校验值 */
    listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
    listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

初始化后列表项结构

2.3、列表插入列表项vListInsert()

此函数用于将待插入列表的列表项按照列表项值升序进行排序,有序地插入到列表中

|---------------|--------|
| 形参 | 描述 |
| pxList | 列表 |
| pxNewListItem | 待插入列表项 |

cpp 复制代码
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    ListItem_t * pxIterator;

    /* 获取列表项的数值依据数值升序排列 */
    const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;

    /* 检查参数是否正确 */
    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

    /* 如果待插入列表项的值为最大值 */
    if( xValueOfInsertion == portMAX_DELAY )
    {
        /* 插入的位置为列表 xListEnd 前面 */
        pxIterator = pxList->xListEnd.pxPrevious;
    }
    else
    {
        /* 遍历列表中的列表项,找到插入的位置 */
        for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
             pxIterator->pxNext->xItemValue <= xValueOfInsertion;
             pxIterator = pxIterator->pxNext ){}
    }

    /* 将待插入的列表项插入指定位置 */
    pxNewListItem->pxNext = pxIterator->pxNext;
    pxNewListItem->pxNext->pxPrevious = pxNewListItem;
    pxNewListItem->pxPrevious = pxIterator;
    pxIterator->pxNext = pxNewListItem;

    /* 更新待插入列表项所在列表 */
    pxNewListItem->pxContainer = pxList;

    /* 更新列表中列表项的数量 */
    ( pxList->uxNumberOfItems )++;
}

2.4、列表末尾插入列表项vListInsertEnd()

此函数用于将待插入列表的列表项插入到列表 pxIndex 指针指向的列表项前面,是一种无序的插入方法

|---------------|--------|
| 形参 | 描述 |
| pxList | 列表 |
| pxNewListItem | 待插入列表项 |

cpp 复制代码
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
    /* 获取列表 pxIndex 指向的列表项 */
    ListItem_t * const pxIndex = pxList->pxIndex;

    /* 检查参数是否正确 */
    listTEST_LIST_INTEGRITY( pxList );
    listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

    /* 更新待插入列表项的指针成员变量 */
    pxNewListItem->pxNext = pxIndex;
    pxNewListItem->pxPrevious = pxIndex->pxPrevious;

    /* Only used during decision coverage testing.(仅在决策覆盖测试中使用) */
    mtCOVERAGE_TEST_DELAY();

    /* 更新列表中原本列表项的指针成员变量 */
    pxIndex->pxPrevious->pxNext = pxNewListItem;
    pxIndex->pxPrevious = pxNewListItem;

    /* 更新待插入列表项的所在列表成员变量 */
    pxNewListItem->pxContainer = pxList;

    /* 更新列表中列表项的数量 */
    ( pxList->uxNumberOfItems )++;
}

2.5、列表移除列表项uxListRemove()

此函数用于将列表项从列表项所在列表中移除

|----------------|--------|
| 形参 | 描述 |
| pxItemToRemove | 待删除列表项 |

|---------|------------------------|
| 返回值 | 描述 |
| 整数 | 待移除列表项移除后,所在列表剩余列表项的数量 |

cpp 复制代码
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
    /* 获取列表项所属列表 */
    List_t * const pxList = pxItemToRemove->pxContainer;

    /* 从列表中移除列表项 */
    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

    /* Only used during decision coverage testing. */
    mtCOVERAGE_TEST_DELAY();

    /* 如果 pxIndex 正指向待移除的列表项 */
    if( pxList->pxIndex == pxItemToRemove )
    {
        /* pxIndex 指向上一个列表项 */
        pxList->pxIndex = pxItemToRemove->pxPrevious;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    /* 将待移除的列表项的所在列表指针清空 */
    pxItemToRemove->pxContainer = NULL;

    /* 更新列表中列表项的数量 */
    ( pxList->uxNumberOfItems )--;

    /* 返回移除后的列表中列表项的数量 */
    return pxList->uxNumberOfItems;
}

三、列表项的插入和删除实验

将设计三个任务:start_task、task1、task2

三个任务的功能如下

start_task:用来创建其他的 2 个任务

task1:实现 LED0 每 500ms 闪烁一次,用来提示系统正在运行

task2:调用列表和列表项相关 API 函数,并且通过串口输出相应的信息,进行观察

main.c

cpp 复制代码
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "freertos_demo.h"

int main(void)
{
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟,168Mhz */
    delay_init(168);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    lcd_init();                         /* 初始化LCD */
    key_init();                         /* 初始化按键 */

    freertos_demo();
}

freertos_demo.c

cpp 复制代码
#include "freertos_demo.h"

/******************************************************************************************************/
/*FreeRTOS配置*/

/* START_TASK 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define START_TASK_PRIO 1            /* 任务优先级 */
#define START_STK_SIZE 128           /* 任务堆栈大小 */
TaskHandle_t StartTask_Handler;      /* 任务句柄 */
void start_task(void *pvParameters); /* 任务函数 */

/* TASK1 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK1_PRIO 2            /* 任务优先级 */
#define TASK1_STK_SIZE 128      /* 任务堆栈大小 */
TaskHandle_t Task1Task_Handler; /* 任务句柄 */
void task1(void *pvParameters); /* 任务函数 */

/* TASK2 任务 配置
 * 包括: 任务句柄 任务优先级 堆栈大小 创建任务
 */
#define TASK2_PRIO 3            /* 任务优先级 */
#define TASK2_STK_SIZE 128      /* 任务堆栈大小 */
TaskHandle_t Task2Task_Handler; /* 任务句柄 */
void task2(void *pvParameters); /* 任务函数 */

List_t TestList;      /* 定义测试列表 */
ListItem_t ListItem1; /* 定义测试列表项1 */
ListItem_t ListItem2; /* 定义测试列表项2 */
ListItem_t ListItem3; /* 定义测试列表项3 */

/******************************************************************************************************/

/* FreeRTOS例程入口函数 */
void freertos_demo(void)
{
    lcd_show_string(10, 10, 220, 32, 32, "STM32", RED);
    lcd_show_string(10, 47, 220, 24, 24, "List & ListItem", RED);
    lcd_show_string(10, 76, 220, 16, 16, "ATOM@ALIENTEK", RED);

    xTaskCreate((TaskFunction_t)start_task,          /* 任务函数 */
                (const char *)"start_task",          /* 任务名称 */
                (uint16_t)START_STK_SIZE,            /* 任务堆栈大小 */
                (void *)NULL,                        /* 传入给任务函数的参数 */
                (UBaseType_t)START_TASK_PRIO,        /* 任务优先级 */
                (TaskHandle_t *)&StartTask_Handler); /* 任务句柄 */
    vTaskStartScheduler();
}

/* start_task */
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL(); /* 进入临界区 */
    /* 创建任务1 */
    xTaskCreate((TaskFunction_t)task1,               /* 任务函数 */
                (const char *)"task1",               /* 任务名称 */
                (uint16_t)TASK1_STK_SIZE,            /* 任务堆栈大小 */
                (void *)NULL,                        /* 传入给任务函数的参数 */
                (UBaseType_t)TASK1_PRIO,             /* 任务优先级 */
                (TaskHandle_t *)&Task1Task_Handler); /* 任务句柄 */
    /* 创建任务2 */
    xTaskCreate((TaskFunction_t)task2,               /* 任务函数 */
                (const char *)"task2",               /* 任务名称 */
                (uint16_t)TASK2_STK_SIZE,            /* 任务堆栈大小 */
                (void *)NULL,                        /* 传入给任务函数的参数 */
                (UBaseType_t)TASK2_PRIO,             /* 任务优先级 */
                (TaskHandle_t *)&Task2Task_Handler); /* 任务句柄 */

    vTaskDelete(StartTask_Handler); /* 删除开始任务 */
    taskEXIT_CRITICAL();            /* 退出临界区 */
}

/* task1 */
void task1(void *pvParameters)
{
    while (1)
    {
        LED0_TOGGLE();
        vTaskDelay(500);
    }
}

/* task2 */
void task2(void *pvParameters)
{
    /* 第一步:初始化列表和列表项 */
    vListInitialise(&TestList);      /* 初始化列表 */
    vListInitialiseItem(&ListItem1); /* 初始化列表项1 */
    vListInitialiseItem(&ListItem2); /* 初始化列表项2 */
    vListInitialiseItem(&ListItem3); /* 初始化列表项3 */
    ListItem1.xItemValue = 40;
    ListItem2.xItemValue = 60;
    ListItem3.xItemValue = 50;

    /* 第二步:打印列表和其他列表项的地址 */
    printf("/**************第二步:打印列表和列表项的地址**************/\r\n");
    printf("项目\t\t\t地址\r\n");
    printf("TestList\t\t0x%p\t\r\n", &TestList);
    printf("TestList->pxIndex\t0x%p\t\r\n", TestList.pxIndex);
    printf("TestList->xListEnd\t0x%p\t\r\n", (&TestList.xListEnd));
    printf("ListItem1\t\t0x%p\t\r\n", &ListItem1);
    printf("ListItem2\t\t0x%p\t\r\n", &ListItem2);
    printf("ListItem3\t\t0x%p\t\r\n", &ListItem3);
    printf("/**************************结束***************************/\r\n");
    printf("按下KEY0键继续!\r\n\r\n\r\n");
    while (key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

    /* 第三步:列表项1插入列表 */
    printf("/*****************第三步:列表项1插入列表******************/\r\n");
    vListInsert((List_t *)&TestList,       /* 列表 */
                (ListItem_t *)&ListItem1); /* 列表项 */
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("/**************************结束***************************/\r\n");
    printf("按下KEY0键继续!\r\n\r\n\r\n");
    while (key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

    /* 第四步:列表项2插入列表 */
    printf("/*****************第四步:列表项2插入列表******************/\r\n");
    vListInsert((List_t *)&TestList,       /* 列表 */
                (ListItem_t *)&ListItem2); /* 列表项 */
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));
    printf("/**************************结束***************************/\r\n");
    printf("按下KEY0键继续!\r\n\r\n\r\n");
    while (key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

    /* 第五步:列表项3插入列表 */
    printf("/*****************第五步:列表项3插入列表******************/\r\n");
    vListInsert((List_t *)&TestList,       /* 列表 */
                (ListItem_t *)&ListItem3); /* 列表项 */
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));
    printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));
    printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));
    printf("/**************************结束***************************/\r\n");
    printf("按下KEY0键继续!\r\n\r\n\r\n");
    while (key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

    /* 第六步:移除列表项2 */
    printf("/*******************第六步:移除列表项2********************/\r\n");
    uxListRemove((ListItem_t *)&ListItem2); /* 移除列表项 */
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));
    printf("/**************************结束***************************/\r\n");
    printf("按下KEY0键继续!\r\n\r\n\r\n");
    while (key_scan(0) != KEY0_PRES)
    {
        vTaskDelay(10);
    }

    /* 第七步:列表末尾添加列表项2 */
    printf("/****************第七步:列表末尾添加列表项2****************/\r\n");
    vListInsertEnd((List_t *)&TestList,       /* 列表 */
                   (ListItem_t *)&ListItem2); /* 列表项 */
    printf("项目\t\t\t\t地址\r\n");
    printf("TestList->pxIndex\t\t0x%p\r\n", TestList.pxIndex);
    printf("TestList->xListEnd->pxNext\t0x%p\r\n", (TestList.xListEnd.pxNext));
    printf("ListItem1->pxNext\t\t0x%p\r\n", (ListItem1.pxNext));
    printf("ListItem2->pxNext\t\t0x%p\r\n", (ListItem2.pxNext));
    printf("ListItem3->pxNext\t\t0x%p\r\n", (ListItem3.pxNext));
    printf("TestList->xListEnd->pxPrevious\t0x%p\r\n", (TestList.xListEnd.pxPrevious));
    printf("ListItem1->pxPrevious\t\t0x%p\r\n", (ListItem1.pxPrevious));
    printf("ListItem2->pxPrevious\t\t0x%p\r\n", (ListItem2.pxPrevious));
    printf("ListItem3->pxPrevious\t\t0x%p\r\n", (ListItem3.pxPrevious));
    printf("/************************实验结束***************************/\r\n");

    while (1)
    {
        vTaskDelay(10);
    }
}

freertos_demo.h

cpp 复制代码
#ifndef __FREERTOS_DEMO_H
#define __FREERTOS_DEMO_H

#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/TIMER/btim.h"
#include "FreeRTOS.h"
#include "task.h"

void freertos_demo(void);

#endif

运行结果

相关推荐
嵌入式科普41 分钟前
嵌入式科普(24)从SPI和CAN通信重新理解“全双工”
c语言·stm32·can·spi·全双工·ra6m5
重生之我是数学王子1 小时前
点亮核心板小灯 STM32U575
stm32·单片机·嵌入式硬件
end_SJ1 小时前
初学stm32 --- 定时器中断
stm32·单片机·嵌入式硬件
南城花随雪。1 小时前
单片机:实现数码管动态显示(0~99999999)74hc138驱动(附带源码)
单片机·嵌入式硬件
南城花随雪。4 小时前
单片机:实现信号发生器(附带源码)
单片机·嵌入式硬件
灵槐梦5 小时前
【速成51单片机】2.点亮LED
c语言·开发语言·经验分享·笔记·单片机·51单片机
三月七(爱看动漫的程序员)6 小时前
HiQA: A Hierarchical Contextual Augmentation RAG for Multi-Documents QA---附录
人工智能·单片机·嵌入式硬件·物联网·机器学习·语言模型·自然语言处理
新晨单片机设计6 小时前
【087】基于51单片机智能宠物喂食器【Proteus仿真+Keil程序+报告+原理图】
嵌入式硬件·51单片机·proteus·宠物·ad原理图
大风起兮127 小时前
STM32HAL库中RTC闹钟设置时分秒,年月日
stm32·嵌入式硬件
超能力MAX8 小时前
IIC驱动EEPROM
单片机·嵌入式硬件·fpga开发