• FreeRTOS学习笔记(二)


    一、时间片调度

    1、同等优先级任务轮流地享有相同的 CPU 时间(可设置), 叫时间片,在FreeRTOS中,一个时间片就等于SysTick 中断周期

    /* 任务一,实现LED0每500ms翻转一次 */
    void task1( void * pvParameters )
    {
        uint32_t task1_num = 0;
        while(1)
        {
            taskENTER_CRITICAL();               /* 进入临界区 */
            printf("task1运行次数:%d\r\n",++task1_num);
            taskEXIT_CRITICAL();                /* 退出临界区 */
            delay_ms(10);
        }
    }
    
    /* 任务二,列表项的插入和删除实验 */
    void task2( void * pvParameters )
    {
        uint32_t task2_num = 0;
        while(1)
        {
            taskENTER_CRITICAL();               /* 进入临界区 */
            printf("task2运行次数:%d\r\n",++task2_num);
            taskEXIT_CRITICAL();                /* 退出临界区 */
            delay_ms(10);
        }
    }
    
    • 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

    二、FreeRTOS任务相关API函数介绍

    在这里插入图片描述
    获取任务运行时间函数代码

    char task_buff[500];
    /* 任务二,实现任务运行时间统计API函数的使用 */
    void task2( void * pvParameters )
    {
        uint8_t key = 0;
        while(1)
        {
            key = key_scan(0);
            if(key == KEY0_PRES)
            {
                vTaskGetRunTimeStats(task_buff);
                printf("%s\r\n",task_buff);
            }
            vTaskDelay(10);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    任务查询函数使用例程

    char task_buff[500];
    /* 任务二,实现任务状态查询API函数使用 */
    void task2( void * pvParameters )
    {
        UBaseType_t priority_num = 0;
        UBaseType_t task_num = 0;
        UBaseType_t task_num2 = 0;
        TaskStatus_t * status_array = 0;
        TaskStatus_t * status_array2 = 0;
        TaskHandle_t task_handle = 0;
        UBaseType_t task_stack_min = 0;
        eTaskState state = 0;
        uint8_t i = 0;
        
        vTaskPrioritySet( task2_handler,4 );
        priority_num = uxTaskPriorityGet( NULL );
        printf("task2任务优先级为%ld\r\n",priority_num);
        
        task_num = uxTaskGetNumberOfTasks();
        printf("任务数量:%ld\r\n",task_num);
        
        status_array = mymalloc(SRAMIN,(sizeof(TaskStatus_t) * task_num));
        task_num2 = uxTaskGetSystemState( status_array,task_num,NULL);
        printf("任务名\t\t任务优先级\t任务编号\r\n");
        for(i = 0; i < task_num2; i++)
        {
            printf("%s\t\t%ld\t%ld\r\n",
                    status_array[i].pcTaskName,
                    status_array[i].uxCurrentPriority,
                    status_array[i].xTaskNumber);
        }
        
        status_array2 = mymalloc(SRAMIN,sizeof(TaskStatus_t));
        vTaskGetInfo( task2_handler,status_array2,pdTRUE,eInvalid);
        printf("任务名:%s\r\n",status_array2->pcTaskName);
        printf("任务优先级:%ld\r\n",status_array2->uxCurrentPriority);
        printf("任务编号:%ld\r\n",status_array2->xTaskNumber);
        printf("任务状态:%d\r\n",status_array2->eCurrentState);
        
        task_handle = xTaskGetHandle( "task1" );
        printf("任务句柄:%#x\r\n",(int)task_handle);
        printf("task1的任务句柄:%#x\r\n",(int)task1_handler);
        
        state = eTaskGetState( task2_handler );
        printf("当前task2的任务状态为:%d\r\n",state);
        
        vTaskList( task_buff );
        printf("%s\r\n",task_buff);
        while(1)
        {
    //        task_stack_min = uxTaskGetStackHighWaterMark( task2_handler );
    //        printf("task2历史剩余最小堆栈为%ld\r\n",task_stack_min);
            vTaskDelay(1000);
        }
    }
    
    
    • 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

    三、FreeRTOS时间管理

    在这里插入图片描述
    相对延时:指每次延时都是从执行函数vTaskDelay()开始,直到延时指定的时间结束
    绝对延时:指将整个任务的运行周期看成一个整体,适用于需要按照一定频率运行的任务

    /* 任务一,演示相对延时函数 */
    void task1( void * pvParameters )
    {
        while(1)
        {
            LED0_TOGGLE();      /* PB1 */
            delay_ms(20);
            vTaskDelay(500);
        }
    }
    
    /* 任务二,演示绝对延时函数 */
    void task2( void * pvParameters )
    {
        TickType_t xLastWakeTime;
        xLastWakeTime = xTaskGetTickCount();
        while(1)
        {
            LED1_TOGGLE();      /* PB0 */
            delay_ms(20);
            vTaskDelayUntil(&xLastWakeTime,500);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    四、FreeRTOS消息队列

    1、队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)
    类似全局变量?假设有一个全局变量a = 0,现有两个任务都在写这个变量a
    在这里插入图片描述
    全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损

    问题:
    当多个任务写入消息给一个“满队列”时,这些任务都会进入阻塞状态,也就是说有多个任务 在等待同一 个队列的空间。那当队列中有空间时,哪个任务会进入就绪态?
    答:
    1、优先级最高的任务
    2、如果大家的优先级相同,那等待时间最久的任务会进入就绪态

    队列相关API函数介绍
    在这里插入图片描述
    在这里插入图片描述

    #include "freertos_demo.h"
    #include "./SYSTEM/usart/usart.h"
    #include "./BSP/LED/led.h"
    #include "./BSP/LCD/lcd.h"
    #include "./BSP/KEY/key.h"
    #include "./SYSTEM/delay/delay.h"
    #include "./MALLOC/malloc.h"
    #include "FreeRTOS.h"
    #include "task.h"
    #include "queue.h"
    
    #define START_TASK_PRIO         1
    #define START_TASK_STACK_SIZE   128
    TaskHandle_t    start_task_handler;
    void start_task( void * pvParameters );
    
    #define TASK1_PRIO         2
    #define TASK1_STACK_SIZE   128
    TaskHandle_t    task1_handler;
    void task1( void * pvParameters );
    
    #define TASK2_PRIO         3
    #define TASK2_STACK_SIZE   128
    TaskHandle_t    task2_handler;
    void task2( void * pvParameters );
    
    #define TASK3_PRIO         4
    #define TASK3_STACK_SIZE   128
    TaskHandle_t    task3_handler;
    void task3( void * pvParameters );
    
    QueueHandle_t key_queue;        /* 小数据句柄 */
    QueueHandle_t big_date_queue;   /* 大数据句柄 */
    char buff[100] = {"我是一个大数组,大大的数组 124214 uhsidhaksjhdklsadhsaklj"};
    
    void freertos_demo(void)
    {    
        /* 队列的创建 */
        key_queue = xQueueCreate( 2, sizeof(uint8_t) );
        if(key_queue != NULL)
        {
            printf("key_queue队列创建成功!!\r\n");
        }else printf("key_queue队列创建失败!!\r\n");
        
        big_date_queue = xQueueCreate( 1, sizeof(char *) );
        if(big_date_queue != NULL)
        {
            printf("big_date_queue队列创建成功!!\r\n");
        }else printf("big_date_queue队列创建失败!!\r\n");
        
        xTaskCreate((TaskFunction_t         )   start_task,
                    (char *                 )   "start_task",
                    (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   START_TASK_PRIO,
                    (TaskHandle_t *         )   &start_task_handler );
        vTaskStartScheduler();
    }
    
    void start_task( void * pvParameters )
    {
        taskENTER_CRITICAL();               /* 进入临界区 */
        xTaskCreate((TaskFunction_t         )   task1,
                    (char *                 )   "task1",
                    (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK1_PRIO,
                    (TaskHandle_t *         )   &task1_handler );
                    
        xTaskCreate((TaskFunction_t         )   task2,
                    (char *                 )   "task2",
                    (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK2_PRIO,
                    (TaskHandle_t *         )   &task2_handler );
                   
        xTaskCreate((TaskFunction_t         )   task3,
                    (char *                 )   "task3",
                    (configSTACK_DEPTH_TYPE )   TASK3_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK3_PRIO,
                    (TaskHandle_t *         )   &task3_handler );                
        vTaskDelete(NULL);
        taskEXIT_CRITICAL();                /* 退出临界区 */
    }
    
    /* 任务一,实现入队 */
    void task1( void * pvParameters )
    {
        uint8_t key = 0;
        char * buf;
        BaseType_t   err = 0;
        buf = &buff[0]; /* buf = &buff[0] */
        while(1) 
        {
            key = key_scan(0);
            if(key == KEY0_PRES || key == KEY1_PRES)
            {
                err = xQueueSend( key_queue, &key, portMAX_DELAY );
                if(err != pdTRUE)
                    printf("key_queue队列发送失败\r\n");
            }else if(key == WKUP_PRES)
            {
                err = xQueueSend( big_date_queue, &buf, portMAX_DELAY );
                if(err != pdTRUE)
                    printf("key_queue队列发送失败\r\n");
            }
            vTaskDelay(10);
        }
    }
    
    /* 任务二,小数据出队 */
    void task2( void * pvParameters )
    {
        uint8_t key = 0;
        BaseType_t err = 0;
        while(1)
        {
            err = xQueueReceive( key_queue,&key,portMAX_DELAY);
            if(err != pdTRUE)
                printf("key_queue队列读取失败\r\n");
            else 
                printf("key_queue读取队列成功,数据:%d\r\n",key);
        }
    }
    
    /* 任务三,大数据出队 */
    void task3( void * pvParameters )
    {
        char * buf;
        BaseType_t err = 0;
        while(1)
        {
            err = xQueueReceive( big_date_queue,&buf,portMAX_DELAY);
            if(err != pdTRUE)
            {
                printf("big_date_queue队列读取失败\r\n");
            }else 
            {
                printf("数据:%s\r\n",buf);
            }
        }
    }
    
    • 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

    五、信号量

    队列与信号量的对比
    在这里插入图片描述

    5.1、二值信号量

    二值信号量通常用于互斥访问或任务同步, 与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题 ,所以二值信号量更适合用于同步!
    
    • 1

    二值信号量相关API函数
    在这里插入图片描述

    二值信号量实验

    QueueHandle_t semphore_handle;
    
    void freertos_demo(void)
    {    
        semphore_handle = xSemaphoreCreateBinary();
        if(semphore_handle != NULL)
        {
            printf("二值信号量创建成功!!!\r\n");
        }
        xTaskCreate((TaskFunction_t         )   start_task,
                    (char *                 )   "start_task",
                    (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   START_TASK_PRIO,
                    (TaskHandle_t *         )   &start_task_handler );
        vTaskStartScheduler();
    }
    
    void start_task( void * pvParameters )
    {
        taskENTER_CRITICAL();               /* 进入临界区 */
        xTaskCreate((TaskFunction_t         )   task1,
                    (char *                 )   "task1",
                    (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK1_PRIO,
                    (TaskHandle_t *         )   &task1_handler );
                    
        xTaskCreate((TaskFunction_t         )   task2,
                    (char *                 )   "task2",
                    (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK2_PRIO,
                    (TaskHandle_t *         )   &task2_handler );
                             
        vTaskDelete(NULL);
        taskEXIT_CRITICAL();                /* 退出临界区 */
    }
    
    /* 任务一,释放二值信号量 */
    void task1( void * pvParameters )
    {
        uint8_t key = 0;
        BaseType_t err;
        while(1) 
        {
            key = key_scan(0);
            if(key == KEY0_PRES)
            {
                if(semphore_handle != NULL)
                {
                    err = xSemaphoreGive(semphore_handle);
                    if(err == pdPASS)
                    {
                        printf("信号量释放成功!!\r\n");
                    }else printf("信号量释放失败!!\r\n");
                }
            }
            vTaskDelay(10);
        }
    }
    
    /* 任务二,获取二值信号量 */
    void task2( void * pvParameters )
    {
        uint32_t i = 0;
        BaseType_t err;
        while(1)
        {
            err = xSemaphoreTake(semphore_handle,portMAX_DELAY); /* 获取信号量并死等 */
            if(err == pdTRUE)
            {
                printf("获取信号量成功\r\n");
            }else printf("已超时%d\r\n",++i);
        }
    }
    
    • 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

    5.2、计数型信号量

    计数型信号量相关API函数
    在这里插入图片描述

    QueueHandle_t count_semphore_handle;
    
    void freertos_demo(void)
    {    
        count_semphore_handle = xSemaphoreCreateCounting(100 , 0);  /* 创建计数型信号量 */
        if(count_semphore_handle != NULL)
        {
            printf("计数型信号量创建成功!!!\r\n");
        }
        xTaskCreate((TaskFunction_t         )   start_task,
                    (char *                 )   "start_task",
                    (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   START_TASK_PRIO,
                    (TaskHandle_t *         )   &start_task_handler );
        vTaskStartScheduler();
    }
    
    
    void start_task( void * pvParameters )
    {
        taskENTER_CRITICAL();               /* 进入临界区 */
        xTaskCreate((TaskFunction_t         )   task1,
                    (char *                 )   "task1",
                    (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK1_PRIO,
                    (TaskHandle_t *         )   &task1_handler );
                    
        xTaskCreate((TaskFunction_t         )   task2,
                    (char *                 )   "task2",
                    (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK2_PRIO,
                    (TaskHandle_t *         )   &task2_handler );
                                 
        vTaskDelete(NULL);
        taskEXIT_CRITICAL();                /* 退出临界区 */
    }
    
    /* 任务一,释放计数型信号量 */
    void task1( void * pvParameters )
    {
        uint8_t key = 0;
        while(1) 
        {
            key = key_scan(0);
            if(key == KEY0_PRES)
            {
                if(count_semphore_handle != NULL)
                {
                    xSemaphoreGive(count_semphore_handle);      /* 释放信号量 */
                }
            }
            vTaskDelay(10);
        }
    }
    
    /* 任务二,获取计数型信号量 */
    void task2( void * pvParameters )
    {
        BaseType_t err = 0;
        while(1)
        {
            err = xSemaphoreTake(count_semphore_handle,portMAX_DELAY); /* 获取信号量并死等 */
            if(err == pdTRUE)
            {
                printf("信号量的计数值为:%d\r\n",(int)uxSemaphoreGetCount(count_semphore_handle));
            }
            vTaskDelay(1000);
        }
    }
    
    • 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

    优先级反转问题
    在这里插入图片描述
    高优先级任务被低优先级任务阻塞,导致高优先级任务迟迟得不到调度。但其他中等优先级的任务却能抢到CPU资源。从现象上看,就像是中优先级的任务比高优先级任务具有更高的优先权(即优先级翻转)

    优先级反转示例代码

    /* 任务一,低优先级任务 */
    void low_task( void * pvParameters )
    {
        while(1) 
        {
            printf("low_task获取信号量\r\n");
            xSemaphoreTake(semphore_handle,portMAX_DELAY);
            printf("low_task正在运行!!!\r\n");
            delay_ms(3000);
            printf("low_task释放信号量\r\n");
            xSemaphoreGive(semphore_handle); 
            vTaskDelay(1000);
        }
    }
    
    /* 任务二,中优先级任务 */
    void middle_task( void * pvParameters )
    {
        while(1)
        {
            printf("middle_task正在运行!!!\r\n");
            vTaskDelay(1000);
        }
    }
    
    /* 任务三,高优先级任务 */
    void high_task( void * pvParameters )
    {
        while(1)
        {
            printf("high_task获取信号量\r\n");
            xSemaphoreTake(semphore_handle,portMAX_DELAY);
            printf("high_task正在运行!!!\r\n");
            delay_ms(1000);
            printf("high_task释放信号量\r\n");
            xSemaphoreGive(semphore_handle); 
            vTaskDelay(1000);
        }
    }
    
    
    • 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

    5.3、互斥信号量

    互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中!
    优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会
    被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
    
    • 1
    • 2
    • 3

    优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响
    注意:互斥信号量不能用于中断服务函数中,原因如下:
    (1) 互斥信号量有任务优先级继承的机制, 但是中断不是任务,没有任务优先级, 所以互斥信号量只能用与任务中,不能用于中断服务函数。
    (2) 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。

    /* 任务一,低优先级任务 */
    void low_task( void * pvParameters )
    {
        while(1) 
        {
            printf("low_task获取信号量\r\n");
            xSemaphoreTake(mutex_semphore_handle,portMAX_DELAY);
            printf("low_task正在运行!!!\r\n");
            delay_ms(3000);
            printf("low_task释放信号量\r\n");
            xSemaphoreGive(mutex_semphore_handle); 
            vTaskDelay(1000);
        }
    }
    
    /* 任务二,中优先级任务 */
    void middle_task( void * pvParameters )
    {
        while(1)
        {
            printf("middle_task正在运行!!!\r\n");
            vTaskDelay(1000);
        }
    }
    
    /* 任务三,高优先级任务 */
    void high_task( void * pvParameters )
    {
        while(1)
        {
            printf("high_task获取信号量\r\n");
            xSemaphoreTake(mutex_semphore_handle,portMAX_DELAY);
            printf("high_task正在运行!!!\r\n");
            delay_ms(1000);
            printf("high_task释放信号量\r\n");
            xSemaphoreGive(mutex_semphore_handle); 
            vTaskDelay(1000);
        }
    }
    
    • 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

    六、队列集

    一个队列只允许任务间传递的消息为同一种数据类型,如果需要在任务间传递不同数据类型的消息时,那么就可以使用队列集 !
    作用:用于对多个队列或信号量进行“监听”,其中不管哪一个消息到来,都可让任务退出阻塞状态。

    队列集相关API函数介绍
    在这里插入图片描述

    void start_task( void * pvParameters )
    {
        taskENTER_CRITICAL();                               /* 进入临界区 */
        queueset_handle = xQueueCreateSet( 2 );             /* 创建队列集,可以存放2个队列 */
        if(queueset_handle != NULL)
        {
            printf("队列集创建成功!!\r\n");
        }
        
        queue_handle = xQueueCreate( 1, sizeof(uint8_t) );  /* 创建队列 */ 
        semphr_handle = xSemaphoreCreateBinary();           /* 创建二值信号量 */
        
        xQueueAddToSet( queue_handle,queueset_handle);
        xQueueAddToSet( semphr_handle,queueset_handle);
        
        xTaskCreate((TaskFunction_t         )   task1,
                    (char *                 )   "task1",
                    (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK1_PRIO,
                    (TaskHandle_t *         )   &task1_handler );
                    
        xTaskCreate((TaskFunction_t         )   task2,
                    (char *                 )   "task2",
                    (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK2_PRIO,
                    (TaskHandle_t *         )   &task2_handler );
                                 
        vTaskDelete(NULL);
        taskEXIT_CRITICAL();                /* 退出临界区 */
    }
    
    /* 任务一,实现队列发送以及信号量释放 */
    void task1( void * pvParameters )
    {
        uint8_t key = 0;
        BaseType_t err = 0;
        while(1) 
        {
            key = key_scan(0);
            if(key == KEY0_PRES)
            {
                err = xQueueSend( queue_handle, &key, portMAX_DELAY );
                if(err == pdPASS)
                {
                    printf("往队列queue_handle写入数据成功!!\r\n");
                }
            }else if(key == KEY1_PRES)
            {
                err = xSemaphoreGive(semphr_handle);
                if(err == pdPASS)
                {
                    printf("释放信号量成功!!\r\n");
                }
            }
            vTaskDelay(10);
        }
    }
    
    /* 任务二,获取队列集的消息 */
    void task2( void * pvParameters )
    {
        QueueSetMemberHandle_t member_handle;
        uint8_t key;
        while(1)
        {
            member_handle = xQueueSelectFromSet( queueset_handle,portMAX_DELAY);
            if(member_handle == queue_handle)
            {
                xQueueReceive( member_handle,&key,portMAX_DELAY);
                printf("获取到的队列数据为:%d\r\n",key);
            }else if(member_handle == semphr_handle)
            {
                xSemaphoreTake( member_handle, portMAX_DELAY );
                printf("获取信号量成功!!\r\n");
            }
        }
    }
    
    • 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

    队列集使用流程
    在这里插入图片描述

    七、事件标志组

    它的每一个位表示一个事件(高8位不算)
    每一位事件的含义,由用户自己决定,如:bit0表示按键是否按下,bit1表示是否接受到消息 …
    这些位的值为1:表示事件发生了;值为0:表示事件未发生
    任意任务或中断都可以读写这些位
    可以等待某一位成立,或者等待多位同时成立

    虽然使用了 32 位无符号的数据类型变量来存储事件标志, 但其中的高8位用作存储事件标志组的控制信息,低24位用作存储事件标志 ,所以说一个事件组最多可以存储 24 个事件标志!
    在这里插入图片描述
    事件标志组与队列、信号量的区别?
    在这里插入图片描述
    事件标志组相关API函数介绍
    在这里插入图片描述
    事件标志组使用代码:

    void start_task( void * pvParameters )
    {
        taskENTER_CRITICAL();               /* 进入临界区 */
        eventgroup_handle = xEventGroupCreate();
        if(eventgroup_handle != NULL)
        {
            printf("事件标志组创建成功!!\r\n");
        }
        
        xTaskCreate((TaskFunction_t         )   task1,
                    (char *                 )   "task1",
                    (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK1_PRIO,
                    (TaskHandle_t *         )   &task1_handler );
                    
        xTaskCreate((TaskFunction_t         )   task2,
                    (char *                 )   "task2",
                    (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK2_PRIO,
                    (TaskHandle_t *         )   &task2_handler );
                                 
        vTaskDelete(NULL);
        taskEXIT_CRITICAL();                /* 退出临界区 */
    }
    
    /* 任务一,释放二值信号量 */
    void task1( void * pvParameters )
    {
        uint8_t key = 0;
        while(1) 
        {
            key = key_scan(0);
            if(key == KEY0_PRES)
            {
                xEventGroupSetBits( eventgroup_handle, EVENTBIT_0); /* 将事件标志组的bit0位置1 */
            }else if(key == KEY1_PRES)
            {
                xEventGroupSetBits( eventgroup_handle, EVENTBIT_1); /* 将事件标志组的bit1位置1 */
            }
            vTaskDelay(10);
        }
    }
    
    /* 任务二,获取二值信号量 */
    void task2( void * pvParameters )
    {
        EventBits_t event_bit = 0;
        while(1)
        {
            event_bit = xEventGroupWaitBits( eventgroup_handle,         /* 事件标志组句柄 */
                                             EVENTBIT_0 | EVENTBIT_1,   /* 等待事件标志组的bit0和bit1位 */
                                             pdTRUE,                    /* 成功等待到事件标志位后,清除事件标志组中的bit0和bit1*/
                                             pdTRUE,                    /* 等待事件标志组的bit0和bit1位都置1,就成立 */
                                             portMAX_DELAY );           /* 死等 */
            printf("等待到的事件标志位值为:%#x\r\n",event_bit);
        }
    }
    
    • 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

    八、任务通知

    在这里插入图片描述
    只要合理,灵活的利用任务通知的特点,可以在一些场合中替代队列、信号量、事件标志组!
    在这里插入图片描述
    在这里插入图片描述

    8.1、任务通知模拟信号量实验

    FreeRTOS任务通知模拟计数型信号量实验

    void start_task( void * pvParameters )
    {
        taskENTER_CRITICAL();               /* 进入临界区 */
        
        xTaskCreate((TaskFunction_t         )   task1,
                    (char *                 )   "task1",
                    (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK1_PRIO,
                    (TaskHandle_t *         )   &task1_handler );
                    
        xTaskCreate((TaskFunction_t         )   task2,
                    (char *                 )   "task2",
                    (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK2_PRIO,
                    (TaskHandle_t *         )   &task2_handler );
                                 
        vTaskDelete(NULL);
        taskEXIT_CRITICAL();                /* 退出临界区 */
    }
    
    /* 任务一,发送任务通知值 */
    void task1( void * pvParameters )
    {
        uint8_t key = 0;
        
        while(1) 
        {
            key = key_scan(0);
            if(key == KEY0_PRES)
            {
                printf("任务通知模拟计数型信号量释放!\r\n");
                xTaskNotifyGive(task2_handler);
            }
            vTaskDelay(10);
        }
    }
    
    /* 任务二,接收任务通知值 */
    void task2( void * pvParameters )
    {
        uint32_t rev = 0;
        while(1)
        {
            rev = ulTaskNotifyTake(pdFALSE , portMAX_DELAY);
            if(rev != 0)
            {
                printf("rev:%d\r\n",rev);
            }
            vTaskDelay(1000);
        }
    }
    
    
    • 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

    FreeRTOS任务通知模拟二值信号量实验

    /* 任务一,发送任务通知值 */
    void task1( void * pvParameters )
    {
        uint8_t key = 0;
        
        while(1) 
        {
            key = key_scan(0);
            if(key == KEY0_PRES)
            {
                printf("任务通知模拟二值信号量释放!\r\n");
                xTaskNotifyGive(task2_handler);
            }
            vTaskDelay(10);
        }
    }
    
    /* 任务二,接收任务通知值 */
    void task2( void * pvParameters )
    {
        uint32_t rev = 0;
        while(1)
        {
            rev = ulTaskNotifyTake(pdTRUE , portMAX_DELAY);
            if(rev != 0)
            {
                printf("接收任务通知成功,模拟获取二值信号量!\r\n");
            }
        }
    }
    
    • 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

    FreeRTOS任务通知模拟消息邮箱实验

    /* 任务一,发送任务通知值 */
    void task1( void * pvParameters )
    {
        uint8_t key = 0;
        
        while(1) 
        {
            key = key_scan(0);
            if((key != 0) && (task2_handler != NULL))
            {
                printf("任务通知模拟消息邮箱发送,发送的键值为:%d\r\n",key);
                xTaskNotify( task2_handler, key, eSetValueWithOverwrite );
            }
            vTaskDelay(10);
        }
    }
    
    /* 任务二,接收任务通知值 */
    void task2( void * pvParameters )
    {
        uint32_t noyify_val = 0;
        while(1)
        {
            xTaskNotifyWait( 0, 0xFFFFFFFF, &noyify_val, portMAX_DELAY );
            switch(noyify_val)
            {
                case KEY0_PRES:
                {
                    printf("接收到的通知值为:%d\r\n",noyify_val);
                    LED0_TOGGLE();
                    break;
                }
                case KEY1_PRES:
                {
                    printf("接收到的通知值为:%d\r\n",noyify_val);
                    LED1_TOGGLE();
                    break;
                }
                default : break;
            }
        }
    }
    
    • 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

    FreeRTOS任务通知模拟事件标志组实验

    /* 任务一,发送任务通知值*/
    void task1( void * pvParameters )
    {
        uint8_t key = 0;
        while(1) 
        {
            key = key_scan(0);
            if(key == KEY0_PRES)
            {
                printf("将bit0位置1\r\n");
                xTaskNotify( task2_handler, EVENTBIT_0, eSetBits );
            }else if(key == KEY1_PRES)
            {
                printf("将bit1位置1\r\n");
                xTaskNotify( task2_handler, EVENTBIT_1, eSetBits );
            }
            vTaskDelay(10);
        }
    }
    
    /* 任务二,接收任务通知值 */
    void task2( void * pvParameters )
    {
        uint32_t notify_val = 0,event_bit = 0;
        while(1)
        {
            xTaskNotifyWait( 0, 0xFFFFFFFF, &notify_val, portMAX_DELAY );
            if(notify_val & EVENTBIT_0)
            {
                event_bit |= EVENTBIT_0;
            }
            if(notify_val & EVENTBIT_1)
            {
                event_bit |= EVENTBIT_1;
            }
            if(event_bit == (EVENTBIT_0|EVENTBIT_1))
            {
                printf("任务通知模拟事件标志组接收成功!!\r\n");
                event_bit = 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

    九、软件定时器

    软件定时器的相关配置
    在这里插入图片描述
    在这里插入图片描述
    FreeRTOS软件定时器实验

    TimerHandle_t timer1_handle = 0;    /* 单次定时器 */
    TimerHandle_t timer2_handle = 0;    /* 周期定时器 */
    
    void start_task( void * pvParameters )
    {
        taskENTER_CRITICAL();               /* 进入临界区 */
        
        /* 单次定时器 */
        timer1_handle = xTimerCreate( "timer1", 
                                        500,
                                        pdFALSE,
                                        (void *)1,
                                        timer1_callback );
                                
        /* 周期定时器 */
        timer2_handle = xTimerCreate( "timer2", 
                                        2000,
                                        pdTRUE,
                                        (void *)2,
                                        timer2_callback );
                                        
        xTaskCreate((TaskFunction_t         )   task1,
                    (char *                 )   "task1",
                    (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK1_PRIO,
                    (TaskHandle_t *         )   &task1_handler );
                                                 
        vTaskDelete(NULL);
        taskEXIT_CRITICAL();                /* 退出临界区 */
    }
    
    /* 任务一,按键扫描并控制软件定时器 */
    void task1( void * pvParameters )
    {
        uint8_t key = 0;
        while(1) 
        {
            key = key_scan(0);
            if(key == KEY0_PRES)
            {
                xTimerStart(timer1_handle,portMAX_DELAY);
                xTimerStart(timer2_handle,portMAX_DELAY);
            }else if(key == KEY1_PRES)
            {
                xTimerStop(timer1_handle,portMAX_DELAY);
                xTimerStop(timer2_handle,portMAX_DELAY);
            }
            vTaskDelay(10);
        }
    }
    
    /* timer1的超时回调函数 */
    void timer1_callback( TimerHandle_t pxTimer )
    {
        static uint32_t timer = 0;
        printf("timer1的运行次数:%d\r\n",++timer);
    }
    
    /* timer2的超时回调函数 */
    void timer2_callback( TimerHandle_t pxTimer )
    {
        static uint32_t timer = 0;
        printf("timer2的运行次数:%d\r\n",++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

    十、低功耗模式

    在这里插入图片描述
    FreeRTOS低功耗Tickless模式实验

    QueueHandle_t semphore_handle;
    /* 进入低功耗前所需要执行的操作 */
    void PRE_SLEEP_PROCESSING(void)
    {
        __HAL_RCC_GPIOA_CLK_DISABLE();
        __HAL_RCC_GPIOB_CLK_DISABLE();
        __HAL_RCC_GPIOC_CLK_DISABLE();
        __HAL_RCC_GPIOD_CLK_DISABLE();
        __HAL_RCC_GPIOE_CLK_DISABLE();
        __HAL_RCC_GPIOF_CLK_DISABLE();
        __HAL_RCC_GPIOG_CLK_DISABLE();
    }
    /* 退出低功耗后所需要执行的操作 */
    void POST_SLEEP_PROCESSING(void)
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();
        __HAL_RCC_GPIOB_CLK_ENABLE();
        __HAL_RCC_GPIOC_CLK_ENABLE();
        __HAL_RCC_GPIOD_CLK_ENABLE();
        __HAL_RCC_GPIOE_CLK_ENABLE();
        __HAL_RCC_GPIOF_CLK_ENABLE();
        __HAL_RCC_GPIOG_CLK_ENABLE();
    }
    /**
     * @brief       FreeRTOS例程入口函数
     * @param       无
     * @retval      无
     */
    void freertos_demo(void)
    {    
        semphore_handle = xSemaphoreCreateBinary();
        if(semphore_handle != NULL)
        {
            printf("二值信号量创建成功!!!\r\n");
        }
        xTaskCreate((TaskFunction_t         )   start_task,
                    (char *                 )   "start_task",
                    (configSTACK_DEPTH_TYPE )   START_TASK_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   START_TASK_PRIO,
                    (TaskHandle_t *         )   &start_task_handler );
        vTaskStartScheduler();
    }
    
    
    void start_task( void * pvParameters )
    {
        taskENTER_CRITICAL();               /* 进入临界区 */
        xTaskCreate((TaskFunction_t         )   task1,
                    (char *                 )   "task1",
                    (configSTACK_DEPTH_TYPE )   TASK1_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK1_PRIO,
                    (TaskHandle_t *         )   &task1_handler );
                    
        xTaskCreate((TaskFunction_t         )   task2,
                    (char *                 )   "task2",
                    (configSTACK_DEPTH_TYPE )   TASK2_STACK_SIZE,
                    (void *                 )   NULL,
                    (UBaseType_t            )   TASK2_PRIO,
                    (TaskHandle_t *         )   &task2_handler );
                                 
        vTaskDelete(NULL);
        taskEXIT_CRITICAL();                /* 退出临界区 */
    }
    
    /* 任务一,释放二值信号量 */
    void task1( void * pvParameters )
    {
        uint8_t key = 0;
        BaseType_t err;
        while(1) 
        {
            key = key_scan(0);
            if(key == KEY0_PRES)
            {
                if(semphore_handle != NULL)
                {
                    err = xSemaphoreGive(semphore_handle);
                    if(err == pdPASS)
                    {
                        printf("信号量释放成功!!\r\n");
                    }else printf("信号量释放失败!!\r\n");
                }
                
            }
            vTaskDelay(10);
        }
    }
    
    /* 任务二,获取二值信号量 */
    void task2( void * pvParameters )
    {
        uint32_t i = 0;
        BaseType_t err;
        while(1)
        {
            err = xSemaphoreTake(semphore_handle,portMAX_DELAY); /* 获取信号量并死等 */
            if(err == pdTRUE)
            {
                printf("获取信号量成功\r\n");
            }else printf("已超时%d\r\n",++i);
            
        }
    }
    
    • 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

    十一、内存管理

    /* 任务一,申请内存以及释放内存,并显示空闲内存大小 */
    void task1( void * pvParameters )
    {
        uint8_t key = 0, t = 0;
        uint8_t * buf = NULL;
        while(1) 
        {
            key = key_scan(0);
            if(key == KEY0_PRES)
            {
                buf = pvPortMalloc(30);                 /* 申请内存 */
                if(buf != NULL)
                {
                    printf("申请内存成功!\r\n");
                }else printf("申请内存失败\r\n");
            }else if(key == KEY1_PRES)
            {
                if(buf != NULL)
                {
                    vPortFree(buf);                     /* 释放内存 */
                    printf("释放内存!!\r\n");
                }                
            }
            if(t++ > 50)
            {
                t = 0;
                printf("剩余的空闲内存大小为:%d\r\n",xPortGetFreeHeapSize());
            }
            vTaskDelay(10);
        }
    }
    
    
    • 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
  • 相关阅读:
    Ubuntu22.0.4安装svn服务
    手把手教你CSP系列之object-src
    python云端开发入门
    计算机操作系统第二章 进程的描述与控制
    IDA Pro与x64dbg联动调试记录
    如何保持注意力,高效学习
    openssl创建CA证书教程
    前后端分离的低代码快速开发框架
    100天精通Python(数据分析篇)——第52天:numpy完结
    母胎级教学,工业路由器远程维护PLC详细操作指南
  • 原文地址:https://blog.csdn.net/afddasfa/article/details/134296661