FreeRTOS专题之常见错误总结与项目反思

 大纲:

常见错误总结

项目经历的思考与改进

 

 

 

常见错误总结

         1.中断优先级

             与硬件平台有关。有些数字优先级高的代表中断优先级高,有些则是相反;有些中断有预抢占优先级和子优先级,设置需注意;有些中断不能使用FreeRTOS的API,有些则可以;有些中断优先级设置考虑移位操作。

                  

         2.stackOverflow

        检测方法有:

  •          非实时方法:

       

  uxTaskGetStackHighWaterMark()

         检查栈剩余最少的量。

 

  •          实时检测方法有两种:

         先设置好钩子函数:

        

 void vApplicationStackOverflowHook( TaskHandle_t *pxTask, signed char *pcTaskName );

         其中,pxTask是任务的句柄;pcTaskName是任务的名字

 

         当configCHECK_FOR_STACK_OVERFLOW宏设为1,启用第一种方法:

         在上下文被保存后内核会检查栈指针指向剩余有效的空间。

         如果栈指针发现超出了有效的范围,栈溢出的钩子函数会被调用。

        

        

          当configCHECK_FOR_STACK_OVERFLOW宏设为2,启用第二种方法:

         当一个任务被创建,他的栈被一个已知的模式填充。方法二测试任务栈空间的最后20个有效字节检查这个模式有没被覆盖。如果20个字节中任意一个被修改他们原来的值,那栈溢出钩子函数将被调用。

 

         3.不恰当地使用printf或者sprintf

         例如:

                   1.在中断中使用printf,影响中断实时性。

                   2.有些嵌入式系统提供了printf或sprintf的实现,但有些没有则需要使用标准库函数。此时可能会涉及到使用malloc函数,或者使用很大的栈空间。

 

                   可以考虑使用Printf-stdarg.c第三方库替代标准库,从而在使用时节省栈空间。注意Printf-stdarg.c并

                   不包含在FreeRTOS中,需要导入。

                   3.大量使用printf

                   在调试完毕后没有把用到的printf函数删去。很多时候为了省事,其实会引入更多的麻烦问题:

                   如,串口打印的log太多,影响观察;调用太多的printf函数,影响系统的运行效率,甚至引入未知

                   的bug.

                   可以考虑以下的做法来减少printf的影响:

                   1.在调用printf语句的前后加调试宏,通过改变宏的值调用或者屏蔽printf语句。

                   2.自定义日志级别,重命名printf

                   在Java中日志框架都有以下几个级别,从低到高TRACE,DEBUG,INFO,WARN,ERROR,FATAL.

                   在默认情况下,定义日志打印级别INFO,它会把大于等于INFO级别的日志信息打印出来。

                   在C语言中,并没有现成的日志级别设置机制,但可以通过设置宏条件编译的形式将打印信息分级管理。

                   如:

//新建一个.h文件

                   enum DEBUG_LEVEL{

                            D_FATAL= 1,

                            D_ERROR= 2,

                            D_WARN  = 3,

                            D_INFO  = 4,

                            D_DEBUG= 5,

                            D_TRACE= 6,

                   };

 

                   unsigned char debugLevel = 6;//通过改变数值,从0-6,改变打印等级

 

                   #define DLOGCOM( level, x, y... ) \

                   do{

                            if(level <= debugLevel ) printf( x"\r\n", ##y ); \

                   }while(0 )

 

                   #define DLOGF( x, y... )  DLOGCOM( D_FATAL, x,##y )

                   #define DLOGE( x, y... )  DLOGCOM( D_ERROR, x,##y )

                   #define DLOGW( x, y... )  DLOGCOM( D_WARN, x, ##y)

                   #define DLOGI( x, y... )  DLOGCOM( D_INFO, x, ##y)

                   #define DLOGD( x, y... )  DLOGCOM( D_DEBUG, x,##y )

                   #define DLOGT( x, y... )  DLOGCOM( D_TRACE, x,##y )

                   在调试时,调用 DLOGT等函数替代printf,通过改变debugLevel的值,

                   即可改变日志的等级。

 

         4.向范例添加简单的任务内容导致系统崩溃

         如:向空闲任务或者守护任务中添加一些代码。

         由于在调用vTaskStartScheduler()时系统自动创建空闲任务和守护任务,且他们预留的栈空间不多。因此,再向其中添加代码,就容易导致这些任务遭到破坏。

 

         5.在中断中错误使用API导致系统崩溃

         如:在中断中使用不带‘...FromISR()’的FreeRTOS函数,或者在使用使用中断安全宏的情况下在中断设置临界区;在大于configMAX_SYSCALL_INTERRUPT_PRIORITY.优先级的中断中使用FreeRTOS API.

 

         6.在开启任务调度器时系统崩溃

         检查是否有一些优先的事项必须在开启任务调度器前做好的,如C Startup 代码等。

 

         7.在开启任务调度器前使用FreeRTOS的API函数

         导致在第一个任务开始执行前中断失效,因为中断为了保护防止在开启任务调度器之前在中断调用FreeRTOS的API函数。

 

         8.使用中断嵌套的时候没有调用临界保护

         taskENTER_CRITICAL()and taskEXIT_CRITICAL()这两个函数记录了中断的嵌套层数。

 

         9.在开启任务调度器之前系统崩溃了

         在任务调度器开启之前,中断发送或接收FreeRTOS的对象,如:队列、信号量等。这会导致系统崩溃。

         因此,最保险的方法是在任务调度器开启之后再创建这些对象,如任务、队列、信号量等。

 

         10.在任务调度器挂起时调用调用FreeRTOS的API或者尝试进入临界区

         任务调度器在调用vTaskSuspendAll()时挂起,在调用xTaskResumeAll()时恢复;

         进入临界区时调用taskENTER_CRITICAL(),,退出临界区时调用taskEXIT_CRITICAL().

 

项目经历的思考与改进

1.

         问题:

         在程序运行时突然打印栈溢出。系统崩溃。

 

         原因:

         创建太多的任务,消耗太多的资源,在嵌入式小系统中RAM资源有限,而

         创建任务是需要从RAM中获取栈的。

 

         风险:

         严重bug.

        

         改进措施:

         其实,FreeRTOS并不是多进程的系统,所有的任务都是共用一块内存,所以,任何一个任务崩溃,都会导致整个系统挂掉。(但在支持多进程的Windows、安卓上则不会因为一个应用程序出问题导致系统崩溃)

         所以可以考虑把并不需要并行的功能模块合并是顺序执行的程序,也就是一个任务就搞定了,类似裸机程序单while循环结构。对于一些需要并行执行的或者对内存使用要求比较大的功能模块,可以另外放到一个任务中执行。

 

         监控措施:

         由于FreeRTOS的任务的栈分配需要人为预估,且如果预估大了,就会导致内存浪费,如果预估小了,就会容易内存溢出。因此,可以参考“常见错误”栈溢出中介绍的方法去预估以及检测栈的使用情况。

         也可以调用xPortGetFreeHeapSize()获取系统堆整体剩余情况监控。

 printf("-->free %f K", xPortGetFreeHeapSize()/1024.0); 

 

 

2.

         问题:

         莫名的不断执行串口发送函数。影响系统的正常响应。

 

         原因:

         在定时器中断中调用了串口发送函数。

         原本想着在定时器中断中放串口发送函数就可以定时发送,符合项目需要。但没有考虑到这个定时器在某个时刻是会改变定时器周期的。而且这个做法很不好,其一,在中断中执行的代码应该尽可能少,保证中断实时性。其二,如果在中断中调用了跟中断相关的东西,这就是中断嵌套了。没有一定的功底,使用中断嵌套麻烦很大。

 

         风险:

         严重bug。

 

         改进措施:

         避免将比较庞大的,运行时间比较长的代码防止到中断中。

 

3.

         问题:

         系统自定义的两个任务中其中一个感觉被阻塞了。

 

         原因:

         在一个任务中调用了mqtt的推送函数(里面有等待的过程),导致该任务的正常功能被阻塞了。

         调试方法:

         通过printf定位,看到底是哪个地方阻塞了。

 

         风险;

         严重bug

 

         改进措施:

         可以提前注册好回调函数。将与mqtt相关的函数全部放置到mqtt的任务中。

 

4.

         问题:

         系统运行时突然卡住

 

         原因:

         在写flash时任务切换导致系统异常。

 

         风险:

         严重bug

 

         改进措施:

         对flash操作前既要对flash进行上锁处理,也要禁止任务调度vTaskSuspendAll().

         在完成flash操作后再对flash解锁,并且恢复任务调度xTaskResumeAll().

 

 

5.

         问题:

         对于某些业务超时需要做出反应的,如何设置定时?

         措施:

         善用FreeRTOS提供的超时器。

 

        

        //设置超时器:

        vTaskSetTimeOutState( &tvTimer );.

    tv_timeout   = 120000;//120s超时器

 

         //检查是否超时

         if(xTaskCheckForTimeOut(&tvTimer, &tv_timeout ) == pdTRUE )

                     break;

        

6.

         问题:

         全局变量作为状态机扩散到多个任务,多个文件中。

 

         原因:

         当时不懂得任务之间通信的方式,没有使用事件组一样可以起到状态机作用的FreeRTOS的对象。全局变量虽然使用方便,但是不好维护。

 

         使用状态机变量的好处是减少变量的使用,一个变量的每一位都可以作为一个状态。

         在FreeRTOS中事件组也有状态机的性质,使用事件组可以在中断或者任务中通信,可以替代全局变量。

 

         下面介绍一个事件组的使用方法:

         1.创建事件组:

                  

 函数原型:

                   EventGroupHandle_t xEventGroupCreate( void );

                   返回值:

                   Anon-NULL value being returned indicates that the event group has been

                   createdsuccessfully. The returned value should be stored as the handle

                   tothe created event group.

 

         2.设置事件标志位:

                  

 函数原型:

                   EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,

                   constEventBits_t uxBitsToSet );

                   参数:

                   xEventGroup

                   Thehandle of the event group in which bits are being set. The event

                   grouphandle will have been returned from the call to

                   xEventGroupCreate()used to create the event group.

 

                   uxBitsToSet

                   Abit mask that specifies the event bit, or event bits, to set to 1 in the event

                   group.The value of the event group is updated by bitwise ORing the

                   eventgroup’s existing value with the value passed in uxBitsToSet.

                   Asan example, setting uxBitsToSet to 0x04 (binary 0100) will result in

                   eventbit 3 in the event group becoming set (if it was not already set),

                   whileleaving all the other event bits in the event group unchanged.

                   返回值:

                   ReturnedValue

                   Thevalue of the event group at the time the call to xEventGroupSetBits()

                   returned.Note that the value returned will not necessarily have the bits

                   specifiedby uxBitsToSet set, because the bits may have been cleared

                   againby a different task.

 

         3.读取事件标志位:

                

   函数原型:

                   EventBits_t xEventGroupWaitBits( const EventGroupHandle_t xEventGroup,

                   constEventBits_t uxBitsToWaitFor,

                   constBaseType_t xClearOnExit,

                   constBaseType_t xWaitForAllBits,

                   TickType_txTicksToWait );

 

                   参数:

                   xEventGroup

                   Thehandle of the event group that contains the event bits being read.

                   Theevent group handle will have been returned from the call to

                   xEventGroupCreate()used to create the event group.

                   uxBitsToWaitForA bit mask that specifies the event bit, or event bits, to test in the event

                   group.

                   Forexample, if the calling task wants to wait for event bit 0 and/or event

                   bit2 to become set in the event group, then set uxBitsToWaitFor to 0x05

                   (binary0101). Refer to Table 45 for further examples.

                  

                   xClearOnExit

                   Ifthe calling task’s unblock condition has been met, and xClearOnExit is

                   setto pdTRUE, then the event bits specified by uxBitsToWaitFor will be

                   clearedback to 0 in the event group before the calling task exits the

                   xEventGroupWaitBits()API function.

                   IfxClearOnExit is set to pdFALSE, then the state of the event bits in the

                   eventgroup are not modified by the xEventGroupWaitBits() API function.

 

                   xWaitForAllBits

                   TheuxBitsToWaitFor parameter specifies the event bits to test in the

                   eventgroup. xWaitForAllBits specifies if the calling task should be

                   removedfrom the Blocked state when one or more of the events bits

                   specifiedby the uxBitsToWaitFor parameter are set, or only when all of

                   theevent bits specified by the uxBitsToWaitFor parameter are set.

                   IfxWaitForAllBits is set to pdFALSE, then a task that entered the Blocked

                   stateto wait for its unblock condition to be met will leave the Blocked state

                   whenany of the bits specified by uxBitsToWaitFor become set (or the time

                   outspecified by the xTicksToWait parameter expires).

                   IfxWaitForAllBits is set to pdTRUE, then a task that entered the Blocked

                   stateto wait for its unblock condition to be met will only leave the Blocked

                   statewhen all of the bits specified by uxBitsToWaitFor are set (or the time

                   outspecified by the xTicksToWait parameter expires).

                   Referto Table 45 for examples.

 

                   xTicksToWait

                   Themaximum amount of time the task should remain in the Blocked state

                   towait for its unblock condition to be met.

                   xEventGroupWaitBits()will return immediately if xTicksToWait is zero, or

                   theunblock condition is met at the time xEventGroupWaitBits() is called.

                   Theblock time is specified in tick periods, so the absolute time it

                   representsis dependent on the tick frequency. The macro

                   pdMS_TO_TICKS()can be used to convert a time specified in

                   millisecondsinto a time specified in ticks.

                   SettingxTicksToWait to portMAX_DELAY will cause the task to wait

                   indefinitely(without timing out), provided INCLUDE_vTaskSuspend is set

                   to1 in FreeRTOSConfig.h.

 

 

         sample:

     

    //定义事件组事件位

         #define MQTT_INIT        0X01

         #define MQTT_NEEDCONNECT 0X02

         #define MQTT_CONNECTED   0X04

         #define MQTT_DISCONNECT  0X08

        

         //-------------------------------------------------------------------------------------------

         //main.c

         //global

         EventGroupHandle_t xEventGroupMqtt;

 

         int main( void ){

                   /*Before an event group can be used it must first be created. */

                   xEventGroupMqtt= xEventGroupCreate();

 

                   /*Create the task that mainloop_task use for wait forevent bits in the eventgroup. */

                   xTaskCreate(mainloopTask, "mainloop", 1000, NULL, 2, NULL );

                   /*Create the task that mqtt task use for set event bits to get set in the eventgroup. */

                   xTaskCreate(mqttTask, "mqttTask", 1000, NULL, 1, NULL );

                   return0;

         }

 

         //mainloopTask:

         static void mainloopTask( void *pvParameters ){

                   EventBits_tx EventGroupMqttValue;

                   constEventBits_t xBitsToWaitForMqtt = MQTT_INIT | MQTT_NEEDCONNECT | MQTT_CONNECTED| MQTT_DISCONNECT;

 

                   while(1 ){

                            /*Block to wait for event bits to become set within the event group. */

                            xEventGroupMqttValue= xEventGroupWaitBits( /* The event group to read. */

                                                                                                                                   xEventGroupMqtt,

                                                                                                                                   /*Bits to test. */

                                                                                                                                   xBitsToWaitForMqtt,

                                                                                                                                   /*Clear bits on exit if the

                                                                                                                                   unblockcondition is met. */

                                                                                                                                   pdFALSE,

                                                                                                                                   /*Don't wait for all bits. This

                                                                                                                                   parameteris set to pdTRUE for the

                                                                                                                                   second execution. */

                                                                                                                                   pdFALSE,

                                                                                                                                   /*Don't time out. */

                                                                                                                                   0

                            );

                            if(xEventGroupMqttValue & MQTT_INIT ){

                                     DLOGT("mqttInit success");

                            }

 

                            if(xEventGroupMqttValue & MQTT_CONNECTED ){

                                     DLOGT("mqtt connected");

                            }

                   }

         }

 
     //-------------------------------------------------------------------------------------------

         mqtt.c

         //global

         externEventGroupHandle_t xEventGroupMqtt;

        

         //mqttTask

         static void mqttTask( void *pvParameters ){

                   u8 tv_mqttStatus = 0;

                   int tv_ret = -1;

                   while(1 ){

                            if(tv_mqttStatus & MQTT_NEEDCONNECT ){

                                     if((tvret = mqtt_connect()) == 0 ){

                                               tv_mqttStatus|= MQTT_CONNECTED;

                                               //setevent bit 1.of mqtt connected.

                                               xEventGroupSetBits(xEventGroupMqtt, MQTT_CONNECTED );

                                     }

                                    

                            }

                  

                   }

         }

 

         对于中断中使用设置事件组调用xEventGroupSetBitsFromISR()函数即可。具体参考FreeRTOS手册

         说明:如果对于多个任务和中断有读有写的操作,最好用xEventGroupSync()函数,具体参考FreeRTOS手册。

 

7.

         问题:

         串口接收采用全局数组接收数据,传到另一个文件处理串口中断。

         缺点:

         1.全局的数组扩展到多个文件,不利于模块化。

         2.在串口接收相对较慢的情况下,容易丢失接收数据。

 

         解决方法:

         使用消息队列,建一个缓冲区,保证串口接收不丢失。同时减少全局变量的使用。

 

         消息队列的使用:

         1.创建消息队列:

                   

函数原型:

                   QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );

                   参数:

                   uxQueueLength

                   Themaximum number of items that the queue being created can hold

                   atany one time.

 

                   uxItemSize

                   Thesize in bytes of each data item that can be stored in the queue.

 

                   返回值:

                   ReturnValue

                   IfNULL is returned, then the queue cannot be created because there

                   isinsufficient heap memory available for FreeRTOS to allocate the

                   queuedata structures and storage area.

                   Anon-NULL value being returned indicates that the queue has been

                   createdsuccessfully. The returned value should be stored as the

                   handleto the created queue.

 

         2.往消息队列写数据

     

 函数原型:

                   BaseType_t xQueueSendToBack( QueueHandle_t xQueue,

                   constvoid * pvItemToQueue,

                   TickType_txTicksToWait );

                  

                   参数:

                   xQueue

                   Thehandle of the queue to which the data is being sent (written). The

                   queuehandle will have been returned from the call to xQueueCreate()

                   usedto create the queue.

 

                   pvItemToQueue

                   Apointer to the data to be copied into the queue.

                   Thesize of each item that the queue can hold is set when the queue is

                   created,so this many bytes will be copied from pvItemToQueue into

                   thequeue storage area.

                  

                   xTicksToWait

                   Themaximum amount of time the task should remain in the Blocked

                   stateto wait for space to become available on the queue, should the

                   queuealready be full.

                   BothxQueueSendToFront() and xQueueSendToBack() will return

                   immediatelyif xTicksToWait is zero and the queue is already full.

                   Theblock time is specified in tick periods, so the absolute time it

                   representsis dependent on the tick frequency. The macro

                   pdMS_TO_TICKS()can be used to convert a time specified in

                   millisecondsinto a time specified in ticks.

                   SettingxTicksToWait to portMAX_DELAY will cause the task to wait

                   indefinitely(without timing out), provided INCLUDE_vTaskSuspend is

                   setto 1 in FreeRTOSConfig.h.

 

                   返回值:

                   Returnedvalue

                   Description

                   Thereare two possible return values:

                   1.pdPASS

                   pdPASSwill be returned only if data was successfully sent to the

                   queue.

                   Ifa block time was specified (xTicksToWait was not zero), then it is

                   possiblethe calling task was placed into the Blocked state, to wait

                   forspace to become available in the queue, before the function

                   returned,but data was successfully written to the queue before the

                   blocktime expired.

                   2.errQUEUE_FULL

                   errQUEUE_FULLwill be returned if data could not be written to the

                   queuebecause the queue was already full.

                   Ifa block time was specified (xTicksToWait was not zero) then the

                   callingtask will have been placed into the Blocked state to wait for

                   anothertask or interrupt to make space in the queue, but the

                   specifiedblock time expired before that happened.

 3.从消息队列读数据

                 

  函数原型:


                   BaseType_t xQueueReceive( QueueHandle_t xQueue,

                   void* const pvBuffer,

                   TickType_txTicksToWait );

 

                   参数:

                   xQueue

                   Thehandle of the queue from which the data is being received (read).

                   Thequeue handle will have been returned from the call to

                   xQueueCreate()used to create the queue.

 

                   pvBuffer

                   Apointer to the memory into which the received data will be copied.

                   Thesize of each data item that the queue holds is set when the queue

                   iscreated. The memory pointed to by pvBuffer must be at least large

                   enoughto hold that many bytes.

 

                   xTicksToWait

                   Themaximum amount of time the task should remain in the Blocked

                   stateto wait for data to become available on the queue, should the

                   queuealready be empty.

                   IfxTicksToWait is zero, then xQueueReceive() will return immediately if

                   thequeue is already empty.

                   Theblock time is specified in tick periods, so the absolute time it

                   representsis dependent on the tick frequency. The macro

                   pdMS_TO_TICKS()can be used to convert a time specified in

                   millisecondsinto a time specified in ticks.

                   SettingxTicksToWait to portMAX_DELAY will cause the task to wait

                   indefinitely(without timing out) provided INCLUDE_vTaskSuspend is set

                   to1 in FreeRTOSConfig.h.

 

                   Returnedvalue

                   Thereare two possible return values:

                   1.pdPASS

                   pdPASSwill be returned only if data was successfully read from the

                   queue.

                   If a block time was specified (xTicksToWait was not zero), then it is

                   possiblethe calling task was placed into the Blocked state, to wait for

                   datato become available on the queue, but data was successfully

                   readfrom the queue before the block time expired.

                   2.errQUEUE_EMPTY

                   errQUEUE_EMPTYwill be returned if data cannot be read from the

                   queuebecause the queue is already empty.

                   Ifa block time was specified (xTicksToWait was not zero,) then the

                   callingtask will have been placed into the Blocked state to wait for

                   anothertask or interrupt to send data to the queue, but the block time

                   expiredbefore that happened.

         sample:

       

  //uart.c

         static u8 recv_buf[20]={0};     

         //uartinterrupt recv data

         void uartRecvInterrupt(){

 

                   BaseType_t xHigherPriorityTaskWoken;

                   u8 rc = 0;

                   int tv_len = 0;

                   rc= serial_getc( tp_sobj );

                   recv_buf[tv_len ] = rc;

                   tv_len++;

                   if(tv_len > 20)

                    || (rc == '\r') ){

                           //sendthe uart recv data to the queue.

                            xQueueSendToBackFromISR(xUartQueue,

                            recv_buf,

                            &xHigherPriorityTaskWoken);

                            tc_len= 0;

                   }

 

         }

 

        

   //main.c

         //mainloopTask:

 

         //global

         QueueHandle_t xUartQueue;

 

         static void mainloopTask( void *pvParameters ){

                   u8 receiveBuf[20]={0};

                   //createa queue for uart

                   xUartQueue= xQueueCreate( 3, sizeof( receiveBuf ) );

                   assert(xUartQueue != NULL );

                   while(1 ){

                   ..//recv the  uart recv data from queue.

                            if(xQueueReceive( xUartQueue,

                                             receivedBuf,

                                             0) != errQUEUE_EMPTY{

                                     //handlethe receive data

                                     if(receivedBuf[8] == 'A')                             

                                     ...

                                     else

                                     ...

                            }

                   }

         }


参考:

[1]FreeRTOS官网手册.Mastering the FreeRTOS Real Time Kernel-- A Hands-On Tutorial Guide

 


  • 10
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值