FreeRTOS_空闲任务

news2024/11/24 16:34:36

目录

1. 空闲任务详解

1.1 空闲任务简介

1.2 空闲任务的创建

1.3 空闲任务函数

2. 空闲任务钩子函数详解

2.1 钩子函数

2.2 空闲任务钩子函数

3. 空闲任务钩子函数实验

3.1 main.c


        空闲任务是 FreeRTOS 必不可少的一个任务,其他 RTOS 类系统也有空闲任务,比如 UCOS。顾名思义,空闲任务就是处理器空闲的时候去运行的一个任务,当系统中没有其他就绪任务的时候空闲任务就会开始运行,空闲任务最重要的作用就是让处理器在无事可做的时候找点事做,防止处理器无聊,因此,空闲任务的优先级必然是最低的。当然是实际中,必然不会如此浪费宝贵的处理器资源,FreeRTOS 空闲任务中也会执行一些其他的处理。

1. 空闲任务详解

1.1 空闲任务简介

        当 FreeRTOS 的调度器启动以后就会自动的创建一个空闲任务,这样就可以确保至少有一个任务可以运行。但是这个空闲任务使用最低优先级,如果应用中有其他高优先级任务处于就绪态的话这个空闲任务就不会跟高优先级的任务抢占 CPU 资源。空闲任务还有另外一个重要的职责,如果某个任务要调用函数 vTaskDelete() 删除自身,那么这个任务的任务控制块 TCB 和任务堆栈等这些由 FreeRTOS 系统自动分配的内存需要在空闲任务中释放掉,如果删除的是别的任务那么相应的内存就会被直接释放掉,不需要在空闲任务中释放。因此,一定要给空闲任务执行的机会!除此以外空闲任务就没有什么特别重要的功能了,所以可以根据实际情况减少空闲任务使用 CPU 的时间(比如,当 CPU 运行空闲任务的时候使处理器进入低功耗模式)。

        用户可以创建与空闲任务优先级相同的应用任务,当宏 configIDLE_SHOULD_YIELD 为 1 的话应用任务就可以使用空闲任务的时间片,也就是说空闲任务会让出时间片给同优先级的应用程序。

1.2 空闲任务的创建

        当调用函数 vTaskStartScheduler() 启动任务调度器的时候此函数就会自动创建空闲任务,代码如下:

void vTaskStartScheduler( void ) 
{ 
    BaseType_t xReturn; 
 
    //创建空闲任务,使用最低优先级 
    #if( configSUPPORT_STATIC_ALLOCATION == 1 )                                 (1) 使用静态方法创建空闲任务
    { 
        StaticTask_t *pxIdleTaskTCBBuffer = NULL; 
        StackType_t *pxIdleTaskStackBuffer = NULL; 
        uint32_t ulIdleTaskStackSize; 
 
        vApplicationGetIdleTaskMemory( &pxIdleTaskTCBBuffer, &pxIdleTaskStackBuffer,
                                       &ulIdleTaskStackSize ); 
        xIdleTaskHandle = xTaskCreateStatic( prvIdleTask, 
                                             "IDLE", 
                                             ulIdleTaskStackSize, 
                                             ( void * ) NULL, 
                                             ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), 
                                             pxIdleTaskStackBuffer, 
                                             pxIdleTaskTCBBuffer ); 
 
        if( xIdleTaskHandle != NULL ) 
        { 
            xReturn = pdPASS; 
        } 
        else 
        { 
            xReturn = pdFAIL; 
        } 
    } 
    #else                                                                       (2) 使用动态方法创建空闲任务,空闲任务的任务函数为 prvIdleTask(),任务堆栈大小为 configMINIMAL_STACK_SIZE,任务堆栈大小可以在 FreeRTOSConfig.h 中修改。任务优先级为 tskIDLE_PRIORITY,宏 tskIDLE_PRIORITY 为 0,说明空闲任务优先级最低,用户不能随意修改空闲任务的优先级!
    { 
        xReturn = xTaskCreate( prvIdleTask, 
                               "IDLE", 
                               configMINIMAL_STACK_SIZE, 
                               ( void * ) NULL, 
                               ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), 
                               &xIdleTaskHandle ); 
    } 
    #endif             /* configSUPPORT_STATIC_ALLOCATION */ 
 
/*********************************************************************/ 
/**************************省略其他代码*******************************/ 
/*********************************************************************/ 
}

1.3 空闲任务函数

        空闲任务的任务函数为 prvIdleTask(),但是实际上是找不到这个函数的,因为它是通过宏定义来实现的,在文件 portmacro.h 中有如下宏定义:

#define portTASK_FUNCTION(vFunction,pvParameters) void vFunction(void *pvParameters)

        其中,portTASK_FUNCTION() 在文件 tasks.c 中有定义,它就是空闲任务的任务函数,源码如下:

static portTASK_FUNCTION( prvIdleTask, pvParameters )                             (1) 将此行展开就是 static void prvIdleTask(void *pvParameters),创建空闲任务的时候任务函数名就是 prvIdleTask()
{ 
    ( void ) pvParameters; //防止报错 
 
    //本函数为 FreeRTOS 的空闲任务任务函数,当任务调度器启动以后空闲任务会自动创建 
 
    for( ;; ) 
    { 
        //检查是否有任务要删除自己,如果有的话就释放这些任务的任务控制块 TCB 和 
        //任务堆栈的内存 
 
        prvCheckTasksWaitingTermination();                                        (2) 调用函数 prvCheckTasksWaitingTermination 检查是否有需要释放内存的被删除任务,当有任务调用函数 vTaskDelete() 删除自身的话,此任务就会添加到列表 xTasksWaitingTermination 是否为空,如果不为空的话就依次将列表中所有任务对应的内存释放掉(任务控制块 TCB 和任务堆栈的内存)
 
        #if ( configUSE_PREEMPTION == 0 ) 
        { 
 
            //如果没有使用抢占式内核的话就强制进行一次任务切换查看是否有其他 
            //任务有效,如果有使用抢占式内核的话就不需要这一步,因为只要有任 
            //何任务有效(就绪)之后都会自动的抢夺 CPU 使用权 
 
            taskYIELD(); 
        } 
        #endif /* configUSE_PREEMPTION */ 
 
        #if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) (3) 使用抢占式内核并且 configIDLE_SHOULD_YIELD 为 1,说明空闲任务需要让出时间片给同优先级的其他就绪任务
        { 
            //如果使用抢占式内核并且使能时间片调度的话,当有任务和空闲任务共享 
            //一个优先级的时候,并且此任务处于就绪态的话空闲任务就应该放弃本时 
            //间片,将本时间片剩余的时间让给这个就绪任务。如果在空闲任务优先级 
            //下的就绪列表中有多个用户任务的话就执行这些任务。 
 
            if( listCURRENT_LIST_LENGTH(                                          (4) 检查优先级为 tskIDLE_PRIORITY(空闲任务优先级)的就绪任务列表是否为空,如果不为空的话就调用函数 taskYIELD() 进行一次任务切换。
                    &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) )> ( UBaseType_t ) 1 ) 
            { 
                taskYIELD(); 
            } 
            else 
            { 
                mtCOVERAGE_TEST_MARKER(); 
            } 
        } 
        #endif 
 
        #if ( configUSE_IDLE_HOOK == 1) 
        { 
            extern void vApplicationIdleHook( void ); 
 
            //执行用户定义的空闲任务钩子函数,注意!钩子函数里面不能使用任何 
            //可以引起阻塞空闲任务的 API 函数。
            vApplicationIdleHook();                                              (5) 如果使能了空闲任务钩子函数的话就执行这个钩子函数,空闲任务钩子函数的函数名为 vApplicationIdleHook(),这个函数需要用户自行编写!在编写这个钩子函数的时候一定不能调用任何可以阻塞空闲任务的 API 函数。
        } 
        #endif /* configUSE_IDLE_HOOK */ 
 
        //如果使能了 Tickless 模式的话就执行相关的处理代码 
        #if ( configUSE_TICKLESS_IDLE != 0 )                                     (6) configUSE_TICKLESS_IDLE 不为 0,说明使能了 FreeRTOS 的低功耗 Tickless 模式。
        { 
            TickType_t xExpectedIdleTime; 
 
            xExpectedIdleTime = prvGetExpectedIdleTime();                        (7) 调用函数 prvGetExpectedIdleTime() 获取处理器进入低功耗模式的时长,此值保存在变量 xExpectedIdleTime 中,单位为时钟节拍数
            if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP )     (8) xExpectedIdleTime 的值要大于 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 才有效
            { 
                vTaskSuspendAll();                                               (9) 处理 Tickless 模式,挂起任务调度器,其实就是起到临界段代码保护功能
                { 
 
                    //调度器已经被挂起,重新采集一次时间值,这次的时间值可以 
                    //使用 
 
                    configASSERT( xNextTaskUnblockTime >= xTickCount ); 
                    xExpectedIdleTime = prvGetExpectedIdleTime();                (10) 重新获取一次时间值,这次的时间值是直接用于 portSUPPRESS_TICKS_AND_SLEEP() 的
 
                    if( xExpectedIdleTime >=
                        configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) 
                    { 
                        traceLOW_POWER_IDLE_BEGIN(); 
                        portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime );       (11) 调用 portSUPPRESS_TICKS_AND_SLEEP() 进入低功耗 Tickless 模式
                        traceLOW_POWER_IDLE_END(); 
                    } 
                    else 
                    { 
                        mtCOVERAGE_TEST_MARKER(); 
                    } 
                } 
                ( void ) xTaskResumeAll();                                       (12) 恢复任务调度器
            } 
            else 
            { 
                mtCOVERAGE_TEST_MARKER(); 
            } 
        } 
        #endif /* configUSE_TICKLESS_IDLE */ 
    } 
} 

2. 空闲任务钩子函数详解

2.1 钩子函数

        FreeRTOS 中有多个钩子函数,钩子函数类似于回调函数,当某个功能(函数)执行的时候就会调用钩子函数,至于钩子函数的具体内容就由用户来编写。如果不需要使用钩子函数的话就什么也不用管,钩子函数是一个可选功能,可以通过宏定义来选择使用哪个钩子函数。

宏定义:                                                                                                        描述:

configUSE_IDLE_HOOK                                                 空闲任务钩子函数,空闲任务会调用此钩子函数                              

configUSE_TICK_HOOK                                                 时间片钩子函数,xTaskIncrementTick()会调用此钩子函数。此钩子函数最终会被节拍中断服务函数用,对于 STM32 来说就是

                                                                                           滴答定时器中断服务函数

configUSE_MALLOC_FAILED_HOOK                            内存申请失败钩子函数,当使用函数 pvPortMalloc() 申请内存失败的时候就会调用此钩子函数

configUSE_DAEMON_TASK_STARTUP_HOOK            守护(Daemon)任务启动钩子函数,守护任务也就是定时器服务任务

        钩子函数的使用方法基本相同,用户使能相应的钩子函数,然后自行根据实际需求编写钩子函数的内容。

2.2 空闲任务钩子函数

        在每个空闲任务运行周期都会调用空闲任务钩子函数,如果想在空闲任务优先级下处理某个任务有两种选择

1. 在空闲任务钩子函数中处理任务

        不管什么时候都要保证系统中至少有一个任务可以运行,因此绝对不能在空闲任务钩子函数中调用任何可以阻塞空闲任务的 API 函数,比如 vTaskDelay(),或者其他带有阻塞时间的信号量或队列操作函数。

2. 创建一个与空闲任务优先级相同的任务

        创建一个任务是最好的解决方法,但是这种方法也消耗更多的 RAM。

        要使用空闲任务钩子函数首先要在 FreeRTOSConfig.h 中将宏 configUSE_IDLE_HOOK 改为 1,然后编写空闲任务钩子函数 vApplicationIdleHook()。通常在空闲任务钩子函数中将处理器设置为低功耗模式来节省电能,为了与 FreeRTOS 自带的 Tickless 模式做区别,这里将这种低功耗的实现方法称之为通用低功耗模式。(因为几乎所有的 RTOS 系统都可以使用这种方法实现低功耗)。

        上图中总共有三个任务,它们分别是一个空闲任务(Idle),两个用户任务(Task1 和 Task2),其中空闲任务一共运行了三次,分别为(1)、(2)和(3),其中 T1 到 T12 是 12 个时刻,下面我们分别从这两种低功耗的实现方法去分析一下整个过程。

1. 通用低功耗模式

        如果使用通用低功耗模式的话每个滴答定时器中断都会将处理器从低功耗模式中唤醒,以(1)为例,在 T2 时刻处理器从低功耗模式中唤醒,但是接下来由于没有就绪的其他任务所以处理器又再一次进入低功耗模式。T2、T3 和 T4 这三个时刻都一样,反复的进入低功耗、退出低功耗,最理想的情况应该是从 T1 时刻就进入低功耗,然后在 T5 时刻退出。

        在(2)中空闲任务只工作了两个时钟节拍,但是也执行了低功耗模式的进入和退出,显然这个意义不大,因为进出低功耗也是需要时间的。

        (3)中空闲任务在 T12 时刻被某个外部中断唤醒,中断的具体处理过程在任务 2 (使用信号量实现中断与任务之间的同步)。

2. 低功耗 Tickless 模式

        在(1)中的 T1 时刻处理器进入低功耗模式,在 T5 时刻退出低功耗模式。相比通用低功耗模式少了 3 次进出低功耗模式的操作。

        在(2)中由于空闲任务只运行了两个时钟节拍,所以就没必要进入低功耗模式。说明在 Tickless 模式中只有空闲任务要运行时间超过某个最小阈值的时候才会进入低功耗模式,此阈值通过 configEXPECTED_IDLE_TIME_BEFORE_SLEEP 来设置。

        (3)中的情况和通用低功耗模式一样。

        可以看出相对于通用低功耗模式,FreeRTOS 自带的 Tickless 模式更加合理有效,所以如果有低功耗设计需求的话大家尽量使用 FreeRTOS 自带的 Tickless 模式。

3. 空闲任务钩子函数实验

        本实验学习如何在 FreeRTOS 空闲任务钩子函数中实现低功耗。在空闲任务钩子函数中使用 WFI 指令使处理器进入睡眠模式。

3.1 main.c

#include "stm32f4xx.h"  
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h"     //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "timer.h"
#include "queue.h"
#include "semphr.h"


//任务优先级
#define START_TASK_PRIO     1     //用于创建其他两个任务
//任务堆栈大小
#define START_STK_SIZE      256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define TASK1_TASK_PRIO     2   //控制 LED0 闪烁,提示系统正在运行
//任务堆栈大小
#define TASK1_STK_SIZE      256
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);

//任务优先级
#define DATAPROCESS_TASK_PRIO 3  //指令处理函数
//任务堆栈大小	
#define DATAPROCESS_STK_SIZE  256 
//任务句柄
TaskHandle_t DataProcess_Handler;
//任务函数
void DataProcess_task(void *pvParameters);

//二值信号量句柄
SemaphoreHandle_t BinarySemaphore;    //二值信号量句柄

//用于命令解析用的命令值
#define LED1ON  1
#define LED1OFF 2
#define BEEPON  3
#define BEEPOFF 4
#define COMMANDERR  0xFF

//进入低功耗模式前需要处理的事件
//ulExpectedIdleTime:低功耗模式运行时间
void PreSleepProcessing(void)   //因为二值信号量实验用到了串口,所以对GPIOB~H时钟不使能!
{
    //关闭某些低功耗模式下不使用的外设时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,DISABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,DISABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,DISABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,DISABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,DISABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,DISABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH,DISABLE);
}

//退出低功耗模式以后需要处理的事情
//ulExpectedIdleTime:低功耗模式运行时间
void PostSleepProcessing(void)
{
    //退出低功耗模式以后打开那些被关闭的外设时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOH,ENABLE);
}

//空闲任务钩子函数
void vApplicationIdleHook(void)
{
    __disable_irq();
    __dsb(portSY_FULL_READ_WRITE );
	__isb(portSY_FULL_READ_WRITE );
	
	PreSleepProcessing();		//进入睡眠模式之前需要处理的事情
	__wfi();				    //进入睡眠模式
	PostSleepProcessing();		//退出睡眠模式之后需要处理的事情
	
	__dsb(portSY_FULL_READ_WRITE );
	__isb(portSY_FULL_READ_WRITE );
	__enable_irq();
}

//函数 LowerToCap 用于将串口发送过来的命令中的小写字母统一转换成大写字母,
//这样就可以在发送命令的时候不用区分大小写,因为开发板会统一转换成大写。
//将字符串中的小写字母转换为大写
//str:要转换的字符串
//len:字符串长度
void LowerToCap(u8 *str,u8 len)
{
    u8 i;
    for(i=0;i<len;i++)
    {
        //判断字符串的ASCII码是否位于96到123之间
        if((96<str[i])&&(str[i]<123))  //小写字母
        {
            //ASCII码是一种用于表示字符的编码系统。在ASCII码中,每个字符都被赋予一个唯一的整数值。
            //大写字母的ASCII码值是65到90
            //小写字母的ASCII码值是97到122   所以一旦确定ASCII码值位于小写字母的范畴内,只需要将ASCII码值减去32即可转换为大写
            str[i] = str[i] - 32;  //转换为大写
        }
    }
}

//函数 CommandProcess 用于将接收到的命令字符串转换成命令值,比如说命令“LED1ON”转换成命令值就是 0(宏LED1ON为 0)
//命令处理函数,将字符串命令转换成命令值
//str:命令
//返回值:0xFF,命令错误;其他值,命令值
u8 CommandProcess(u8 *str)
{
    u8 CommandValue = COMMANDERR;
    if(strcmp((char*)str,"LED1ON")==0) //strcmp 字符串比较函数
        //这个函数会比较两个参数;比较时,会以字符的ASCII值进行比较
        //如果str1的ASCII码值小于str2,返回一个负数;反之,返回一个正数;
        //如果str1的ASCII码值等于str2,返回 0,此时,if判断语句成立
        CommandValue = LED1ON; //设置的LED1ON的宏为1,也就是在串口输入1,if判断语句成立
    else if(strcmp((char*)str,"LED1OFF")==0)
        CommandValue = LED1OFF; //在串口输入2,if判断语句成立
    else if(strcmp((char*)str,"BEEPON")==0)
        CommandValue = BEEPON; //在串口输入3,if判断语句成立
    else if(strcmp((char*)str,"BEEPOFF")==0)
        CommandValue = BEEPOFF; //在串口输入4,if判断语句成立
    return CommandValue;
}

int main(void)
{
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); //设置系统中断优先级
    delay_init(168);
    uart_init(115200);
    LED_Init();
    KEY_Init();
    BEEP_Init();
    LCD_Init();
    my_mem_init(SRAMIN);    //初始化内部内存池
    
    POINT_COLOR=RED;
    LCD_ShowString(10,10,200,16,16,"ATK STM32F407");
    LCD_ShowString(10,30,200,16,16,"FreeRTOS Example");
    LCD_ShowString(10,50,200,16,16,"Binary Semaphore");
    LCD_ShowString(10,70,200,16,16,"Command Data:");
    
    //创建开始任务
    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();          //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();    //进入临界区
    
    //创建二值信号量,也就是创建一个长度为1的队列
    BinarySemaphore = xSemaphoreCreateBinary();   //xSemaphoreCreateBinary函数为动态创建二值信号量函数
    //返回 NULL,二值信号量创建失败;返回其他值,表示创建成功的二值信号量的句柄;
    //所以BinarySemaphore表示创建成功的二值信号量的句柄;
    
    //创建Task1任务
    xTaskCreate((TaskFunction_t )task1_task,            //任务函数
                (const char*    )"task1_task",          //任务名称
                (uint16_t       )TASK1_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )TASK1_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&Task1Task_Handler);   //任务句柄  
    //创建Task2任务
    xTaskCreate((TaskFunction_t )DataProcess_task,            //任务函数
                (const char*    )"DataProcess_task",          //任务名称
                (uint16_t       )DATAPROCESS_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )DATAPROCESS_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&DataProcess_Handler);   //任务句柄  
                                  
    vTaskDelete(StartTask_Handler);  //删除开始任务
    taskEXIT_CRITICAL();          //退出临界区
}

//Task1任务
//控制 LED0 闪烁,提示系统正在运行
void task1_task(void *pvParameters)
{
    while(1)
    {
        LED0=!LED0;
        vTaskDelay(500);        //延时500ms,也就是500个时钟节拍
    }
}

//DataProcess_task函数
//指令处理任务,根据接收到的指令来控制不同的外设
void DataProcess_task(void *pvParameters)
{
    u8 len=0;
    u8 CommandValue=COMMANDERR;
    BaseType_t err=pdFALSE;
    
    u8 *CommandStr;
    
    while(1)
    {
        err=xSemaphoreTake(BinarySemaphore,portMAX_DELAY); //获取信号量函数;返回值pdTURE,获取信号量成功;pdFALSE,获取信号量失败;
        //第一个参数,要获取的信号量句柄
        //第二个参数,阻塞时间,这里设置为portMAX_DEALY,译为无限等待,直至获得信号量
        if(err==pdTRUE) //获取信号量成功
        {
            len=USART_RX_STA&0x3fff;  //得到此次接收到的数据长度
            //接收状态
            //bit15,	接收完成标志
            //bit14,	接收到0x0d
            //bit13~0,	接收到的有效字节数目
            CommandStr=mymalloc(SRAMIN,len+1);  //申请内存 指针指向申请内存的首地址
            sprintf((char*)CommandStr,"%s",USART_RX_BUF);  //打印接收缓存区,把接收缓存区的数据保存到CommandStr中
            CommandStr[len]='\0';   //加上字符串结尾符号
            //CommandStr 是个指针,长度为len,数组是从下角标 0 开始的,所以len就表示数组的最后一个
            LowerToCap(CommandStr,len);  //将字符串转换成大写
            CommandValue=CommandProcess(CommandStr);  //命令解析,也就是获取上面定义的宏 1 2 3 4
            if(CommandValue!=COMMANDERR)//if判断语句成立,表示CommandValue不等于0xFF,那也就是 LED1ON、LED1OFF、BEEPON、BEEPOFF 其中一个指令
            {
                printf("命令为:%s\r\n",CommandStr);   
                switch(CommandValue)
                {
                    case LED1ON:
                        LED1=0;
                        break;
                    case LED1OFF:
                        LED1=1;
                        break;
                    case BEEPON:
                        BEEP=1;
                        break;
                    case BEEPOFF:
                        BEEP=0;
                        break;
                }
            }
            else
            {//当命令错误的时候开发板会向串口调试助手发送命令错误的提示信息
                //比如我们发送 LED1_off 这个命令,串口助手会显示:无效的命令,请重新输入!!
                printf("无效的命令,请重新输入!!\r\n");
            }
            USART_RX_STA = 0;
            memset(USART_RX_BUF,0,USART_REC_LEN);  //串口接收缓冲区清零
            myfree(SRAMIN,CommandStr);             //释放内存
        }
    }
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1185647.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android MotionLayout

MotionLayout exends ConstraintLayout(动画框架 过渡) View动画 API1 属性动画API11 过渡动画API18 root.width RootViewWidth TransitionManager.beginDelayedTransition(view) 过渡动画 可以改变其大小和流畅性 Fade 可以改变透明度 通过TrasitinManager管理 Go:动态替…

adb and 软件架构笔记

Native Service&#xff0c;这是Android系统里的一种特色&#xff0c;就是通过C或是C代码写出来的&#xff0c;供Java进行远程调用的Remote Service&#xff0c;因为C/C代码生成的是Native代码&#xff08;机器代码&#xff09;&#xff0c;于是叫Native Service。 native服务…

Linux C语言进阶-D14指针函数

指针函数&#xff1a;指一个函数的返回值为地址量的函数 <数据类型>* <函数名称>&#xff08;<参数说明>&#xff09; { 语句序列; } 返回值&#xff1a;全部变量的地址、静态变量的地址、字符串常量的地址、堆上的地址 注意&#xff1a;不可返回局部变量…

Redis Java 开发简单示例

文章目录 一、概述二、Jedis 开发示例2.1 导入 maven 依赖2.2 使用连接池读写2.3 使用集群读写2.4 完整示例代码2.5 测试集群的搭建 三、Lettuce 开发示例3.1 导入 maven 依赖3.2 读写数据 四、Spring Boot Redis 开发示例4.1 导入 maven 依赖4.2 配置Redis服务地址4.3 基于 Re…

52基于MATLAB的希尔伯特Hilbert变换求包络谱

基于MATLAB的希尔伯特Hilbert变换求包络谱&#xff0c;对原始信号进行初步滤波&#xff0c;之后进行包络谱分析。可替换自己的数据进行优化。程序已调通&#xff0c;可直接运行。 52的尔伯特Hilbert变换包络谱 (xiaohongshu.com)

混沌系统在图像加密中的应用(基于哈密顿能量函数的混沌系统构造1.1)

混沌系统在图像加密中的应用&#xff08;基于哈密顿能量函数的混沌系统构造1.1&#xff09; 前言一、基于广义哈密顿系统的一类混沌系统构造1.基本动力学特性分析2.数值分析 待续 前言 本文的主题是“基于哈密顿能量函数的混沌系统构造”&#xff0c;哈密顿能量函数是是全文研…

【Git】快速入门安装及使用git与svn的区别常用命令

一、导言 1、什么是svn&#xff1f; SVN是Subversion的简称&#xff0c;是一个集中式版本控制系统。与Git不同&#xff0c;SVN没有分布式的特性。在SVN中&#xff0c;项目的代码仓库位于服务器上&#xff0c;团队成员通过向服务器提交和获取代码来实现版本控制。SVN记录了每个文…

Go 接口:nil接口为什么不等于nil?

本文主要内容:深入了解接口类型的运行时表示层。 文章目录 一、Go 接口的地位二、接口的静态特性与动态特性2.1 接口的静态特性与动态特性介绍2.2 “动静皆备”的特性的好处 三、nil error 值 ! nil四、接口类型变量的内部表示第一种&#xff1a;nil 接口变量第二种&#xff1a…

JDBC(二)

第4章 操作BLOB类型字段 4.1 MySQL BLOB类型 MySQL中&#xff0c;BLOB是一个二进制大型对象&#xff0c;是一个可以存储大量数据的容器&#xff0c;它能容纳不同大小的数据。 插入BLOB类型的数据必须使用PreparedStatement&#xff0c;因为BLOB类型的数据无法使用字符串拼接写…

Hundred Finance 攻击事件分析

背景知识 Hundred Finance 是 fork Compound 的一个借贷项目&#xff0c;在2023/04/15遭受了黑客攻击。攻击者在发起攻击交易之前执行了两笔准备交易占据了池子&#xff0c;因为发起攻击的前提是池子处于 empty 的状态&#xff08;发行的 hToken 数量为 0&#xff09;。 准备交…

​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​

软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】 课本里章节里所有蓝色字体的思维导图

CAN 协议常见面试题总结

0.讲一下CAN通讯的过程 第一段&#xff1a;需要发送的通讯设备&#xff0c;先发送一个显性电平0&#xff0c;告诉其他通讯设备&#xff0c;需要开始通讯。 第二段&#xff1a;就是发送仲裁段&#xff0c;其中包括ID帧和数据帧类型&#xff0c;告诉其他通讯设备&#xff0c;需…

记录一次校园CTF--wp

一.第一题简单nc 这题直接nc 地址端口即可得到flags没有套路 二.第二题pwn:ezstack 这是一题栈溢出题目&#xff0c;查看保护&#xff1a; 没有开启PIE&#xff0c;运行下查看效果&#xff1a; 题目是一个文字购物游戏。 接着扔进IDA中分析&#xff1a; 在主函数中我们找到…

C++ string赋值和添加值

在MFC中使用C的string&#xff0c;要先#include <string>&#xff0c;然后&#xff0c;std::string s2("") 这样就可以了&#xff1b; void CStrnewView::OnDraw(CDC* pDC) {CStrnewDoc* pDoc GetDocument();ASSERT_VALID(pDoc);// TODO: add draw code for n…

【Redis】list常用命令内部编码使用场景

文章目录 前置知识列表类型的特点 命令LPUSHLPUSHXRPUSHRPUSHXLRANGELPOPRPOPLINDEXLREMLINSERTLTRIMLSETLLEN 阻塞版本命令BLPOPBRPOP 命令总结内部编码测试内部编码 使用场景消息队列分频道的消息队列 模拟栈和队列 前置知识 列表类型是⽤来存储多个有序的字符串&#xff0c…

项目管理之如何出道(上)

前言 终于有时间更新了&#xff0c;大家是不是等不及了&#xff1f;那么书接上文&#xff0c;言归正传。 各位盆友&#xff0c;时间之轮已划入夜晚&#xff0c;尝试静下心来&#xff0c;思考一番。 Q1&#xff1a;是否曾经期待自己做一名项目经理&#xff0c;干了几年的coder甚…

MinGW32丢失dll文件

问题现象 执行Makefile的时候&#xff0c;突然出现这个提示&#xff0c;还有好几个类似的&#xff0c;提示我找不到dll文件&#xff0c;建议重装。 问题分析 重装软件 最直接的办法肯定是按照建议来重装&#xff0c;但是发现重装了好几次&#xff0c;不是缺这个就是缺那个&a…

每天一道算法题:17. 电话号码的字母组合

难度 中等 题目 给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。 给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。 示例 1&#xff1a; 输入&#xff1a;digits “23” …

换服还是掀桌?哪条才是程序员的出路?

站在时代的风口浪尖&#xff0c;猪都能起飞。 大数据互联网正是时代的宠儿&#xff0c;IT行业的发展也正如火如荼。 人人都眼红程序员的高薪资&#xff0c;认为他们吃着时代的红利。 但是三百六十行&#xff0c;行行出社畜。”996“也好&#xff0c;甚至"007"也罢…

龙迅LT6911GXC,HDMI 2.1转4 PORT MIPI/LVDS支持分辨率高达8K30HZ

描述&#xff1a; LT6911GXC 是一款面向 VR / 显示应用的高性能 HDMI2.1 至 MIPI 或 LVDS 芯片。 高清遥控器RX作为高清电脑中继器的上游&#xff0c;可与其他芯片的高清电脑TX合作&#xff0c;实现直译台功能。 对于 HDMI2.1 输入&#xff0c;LT6911GXC 可配置为 3/4 通道。 …