1. 程式人生 > >STM32-FreeRTOS快速學習之總結1

STM32-FreeRTOS快速學習之總結1

sendto size ould scheduler 功能 函數指針 clas ont 偽代碼

1. 基礎知識
註意:在RTOS中是優先值越高則優先級越高(和ucos/linux的相反)
在移植的時候,主要裁剪FreeRTOS/Source/portable文件夾,該文件夾用來針對不同MCU做的一些處理,如下圖所示,我們只需要使用:
技術分享圖片

1.1配置工程時,選擇memMang時,一般使用heap_4.c

  • heap_4: 優點在於可以有效的利用內存碎片來合並為一個大內存.缺點在於只能用來一個ram裏.
  • heap_5: 一般針對有外部RAM才用到,優點在於可以同時利用內部ram和外部ram來進行內存碎片合並.

最終添加的庫文件有:
技術分享圖片

然後我們在分配釋放內存的時候,就盡量使用RTOS帶的函數

來實現,分配/釋放函數如下所示:

void *pvPortMalloc( size_t xWantedSize );    
void vPortFree( void *pv );

1.2 添加頭文件路徑

  • 添加FreeRTOS\include
  • 添加FreeRTOS\portable\RVDS\ARM_CM3
  • 並將原子中的FreeRTOSConfig.h也復制到我們項目的FreeRTOS\include中(用來配置RTOS系統)

2. FreeRTOSConfig.h配置介紹
一般會寫configXXXXX或者INCLUDE_XXXX類似的宏,這兩個宏區別在於:

  • configXXXXX

用來實現不同功能,比如定義configUSE_COUNTING_SEMAPHORES為1時,表示使用計數信號量

  • INCLUDE_XXXX

用來是否將某個API函數編譯進程序中.
比如定義INCLUDE_xTaskGetSchedulerState 為1 時,則將會編譯xTaskGetSchedulerState()函數,如下圖所示:

技術分享圖片

3. FreeRTOS任務狀態

3.1 運行態
指當前任務正在運行.
3.2 就緒態
指當前任務正在等待調度,因為有個高優先級/同優先級的任務正在運行中
3.3 阻塞態
當前任務處於等待外部事件通知或通過vTaskDelay()

函數進入休眠了,外部事件通知常見有信號量、等待隊列、事件標誌組、任務通知.
3.4 掛起態
類似於暫停,表示不會再參與任務調度了,通過vTaskSuspend()實現,重新恢復調度則使用xTaskResume()

4. FreeRTOS中斷配置

4.1 回憶stm32 NVIC中斷
Stm32可以設置NVIC中斷組數為0~4,其中0~4區別在於如下圖所示:、
技術分享圖片

比如我們設置為NVIC_PriorityGroup_4時:
表示搶占優先級為4bit(即為2^4,為0~15個搶占優先級),副優先級為0bit(表示沒有).

4.2 搶占優先級和副優先級的區別:

  • 1. 搶占優先級和副優先級的值越低,則優先級越高
  • 2. 高的搶占優先級的中斷可以直接打斷低的搶占優先級的中斷
  • 3. 高的副優先級的中斷不可以打斷低的副優先級的中斷(只是兩個相同搶占優先級的中斷同時來的時候,只會優先選擇高的副優先級)

4.3 FreeRTOS中斷配置宏

  • configKERNEL_INTERRUPT_PRIORITY

用來配置中斷最低搶占優先級,也就是可以FreeRTOS可以管理的最小搶占優先級,所以使用FreeRTOS時,我們盡量設置stm32為NVIC_PriorityGroup_4,這樣就可以管理16個優先級了.

  • configMAX_SYSCALL_INTERRUPT_PRIORITY

用來配置FreeRTOS能夠安全管理的的最高優先級.比如原子的FreeRTOSConfig.h裏就設置為5,而0~4的優先級中斷就不會被FreeRTOS因為開關中斷而禁止掉(一直都會有),並且不能調用RTOS中的”FromISR”結尾的API函數.

  • 如下圖所示(來自原子手冊):

技術分享圖片


4.3 FreeRTOS中斷開關函數

portENABLE_INTERRUPTS();    
//開中斷,將configMAX_SYSCALL_INTERRUPT_PRIORITY至 configKERNEL_INTERRUPT_PRIORITY之間的優先級中斷打開

portDISABLE_INTERRUPTS();    
//關中斷,將configMAX_SYSCALL_INTERRUPT_PRIORITY至 configKERNEL_INTERRUPT_PRIORITY之間的優先級中斷禁止掉

5.任務常用API函數

5.1 xTaskCreate創建任務函數
定義如下:

xTaskCreate(    TaskFunction_t pxTaskCode,     //任務函數,用來供給函數指針調用的
const char * const pcName,              //任務的字符串別名
const uint16_t usStackDepth,             //任務堆棧深度,實際申請到的堆棧是該參數的4倍
void * const pvParameters,               //函數參數,用來供給指針調用的
UBaseType_t uxPriority,                //優先級,越高優先級高,範圍為0~configMAX_PRIORITIES-1 
                            //註意優先級0會創建為空閑任務, 優先級configMAX_PRIORITIES-1會創建一個軟件定時器服務任務(管理定時器的)
TaskHandle_t * const pxCreatedTask ); //任務句柄,該句柄可以用於掛起/恢復/刪除對應的任務

//返回值 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(-1):表示創建任務堆空間不足pdPASS(1):創建成功

5.2 taskENTER_CRITICAL()和taskEXIT_CRITICAL()
用於任務中進入/退出臨界區,調用taskENTER_CRITICAL()主要會關閉其他任務調度.而taskEXIT_CRITICAL()則會恢復任務調度,一般用於初始化外設等.


5.3 taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR()
用於在中斷函數中進入/退出臨界區,作用和上面一樣

5.4 掛起/恢復/刪除任務函數

void vTaskSuspend( TaskHandle_t xTaskToSuspend );        //掛起一個任務,參數為掛起任務的句柄,如果為NULL則表示掛起自身任務


void vTaskResume( TaskHandle_t xTaskToResume );     //恢復一個任務

BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume);//從中斷函數中恢復一個任務,返回1表示恢復成功

void vTaskDelete( TaskHandle_t xTaskToDelete );   //刪除一個任務,如果從任務函數中退出的話,則需要調用vTaskDelete(NULL)來刪除自身任務

5.5 vTaskDelay()延時函數

void vTaskDelay( const TickType_t xTicksToDelay );    //參數表示延時的系統滴答數

比如延時500ms可以寫為: vTaskDelay( 500/portTICK_RATE_MS );
portTICK_RATE_MS是個宏,表示當前系統的1個滴答需要多少ms,而500/portTICK_RATE_MS則表示當前500ms需要多少個系統滴答數.


6. 隊列
6.1簡介
隊列用於任務與任務或者任務與中斷之間的通信.比如key任務檢測到按鍵按下時,則可以通過隊列向lcd顯示任務發送信息,使得lcd切換界面.
隊列采用先進先出存儲機制.隊列發送數據可以有兩種方式:淺拷貝、深拷貝.

  • 數據量不大的情況下,都使用深拷貝(會分配新的空間,並進行數據拷貝,缺點在於耗時)
  • 數據量大的情況下,都使用淺拷貝(通過指針方式,前提是要發送的數據必須不會被釋放的)

6.2隊列的優點
隊列可以通過任何任務或者中斷進行訪問,可以隨時存取數據消息.
並且出入隊的時候可以進行任務阻塞,比如某個任務進行讀消息出隊時,如果沒有消息,則可以實現進入休眠狀態,直到有消息才喚醒任務.

6.3隊列創建刪除相關API

QueueHandle_t xQueueCreate( uxQueueLength, uxItemSize );    
 //動態創建隊列,內存會交給RTOS自動分配
 // uxQueueLength:隊列長度(表示隊列中最大多少條消息),uxItemSize:每個隊列消息的長度(以字節為單位)
 //返回值: NULL(0, 表示分配失敗),非0(表示返回該隊列分配好的地址)
 //註意:使用自動分配時,需要配置configSUPPORT_DYNAMIC_ALLOCATION宏為1,否則只能由用戶來分配.

QueueHandle_t xQueueCreateStatic( uxQueueLength, uxItemSize, pucQueueStorage, pxQueueBuffer ); //靜態創建隊列,內存需要由用戶事先分配好 // uxQueueLength:隊列長度(表示隊列中最大多少條消息),uxItemSize:每個隊列消息的長度(以字節為單位) // pucQueueStorage:指向用戶事先分配好的存儲區內存(必須為uint8_t型) // pxQueueBuffer:指向隊列結構體,用來提供給RTOS初始化.然後給用戶使用 //返回值: NULL(0, 表示分配失敗),非0(表示返回該隊列分配好的地址) vQueueDelete( QueueHandle_t xQueue ); //刪除隊列,並釋放空間 xQueueReset( xQueue ); //將隊列裏的消息清空一次,也就是恢復初始狀態

6.4隊列出入隊相關API

xQueueSend( xQueue, pvItemToQueue, xTicksToWait );
//插入隊尾,和xQueueSendToBack函數效果一致
// xQueue:隊列句柄
//PvItemToQueue:消息數據,會通過數據拷貝到隊列中,如果想使用淺拷貝,則可以發送一個變量來存儲要真正發送的緩沖區地址即可.
// xTicksToWait:阻塞時間,單位為RTOS時鐘滴答值,如果configTICK_RATE_HZ是1000,則填入的值表示阻塞的是多少ms,否則的話需要通過X/portTICK_RATE_MS來轉換一下,才能實現阻塞Xms.
//xTicksToWait==0:表示入隊滿了,則直接退出該函數
// xTicksToWait==portMAX_DELAY:表示一直阻塞,直到隊列有空位為止.
//註意: INCLUDE_vTaskSuspend宏必須為1,否則任務無法進入休眠狀態實現阻塞效果.
//返回值: errQUEUE_FULL(隊列已滿) pdPASS(通過)

xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ); //插入隊頭,參數和上面描述一致

xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ); //插入隊尾,參數和上面描述一致 xQueueOverwrite( xQueue, pvItemToQueue ); //將之前未出隊的舊數據全部清空,然後再入隊,該函數適用於長度為1的隊列 xQueueReceive( xQueue, pvBuffer, xTicksToWait ); //從隊列頭部讀出一個消息,並且這個消息會出隊(刪除掉) xQueuePeek( xQueue, pvBuffer, xTicksToWait ); //從隊列頭部讀出一個消息,但是這個消息不會出隊(不會刪除)

PS:這些API函數只能用於任務裏調用,如果要在中斷服務函數中調用,則在函數名後添加FromQueue即可,比如xQueueSendFromQueue()函數

6.5示例-偽代碼

按鍵任務向打印任務發送按鍵消息隊列,代碼如下:

QueueHandle_t Key_Queue; //按鍵值消息隊列句柄

int main()
{
   //...省略N行代碼

   Key_Queue=xQueueCreate(1,sizeof(u8)); //創建消息Key_Queue,長度為1
   
//
創建兩個任務:key_task()、print_task() //...省略N行代碼 }
key_task()
//獲取按鍵值 {   while(1)   {     key=KEY_Scan(0); //掃描按鍵     if((Key_Queue!=NULL)&&(key)) //消息隊列Key_Queue創建成功,並且按鍵被按下     {       err=xQueueSend(Key_Queue,&key,10);       if(err==errQUEUE_FULL) //發送按鍵值       {         printf("隊列Key_Queue已滿,數據發送失敗!\r\n");       }     }     vTaskDelay(10); //延時10個時鐘節拍   } } print_task() //打印按鍵值 {   u8 key;   while(1)   {     if(Key_Queue!=NULL)     {       if(xQueueReceive(Key_Queue,&key,portMAX_DELAY))//請求消息Key_Queue       {         printf("key=%d\r\n",key);       }     }     vTaskDelay(10); //延時10個時鐘節拍   } }

7. RTOS軟件定時器

7.1簡介
在之前的任務創建的時候有講到過,RTOS會自動創建一個優先級configMAX_PRIORITIES-1的軟件定時器服務任務(管理定時器的).
所以我們寫一個定時器回調函數時,則會被該定時器服務任務調用,所以在我們軟件定時器函數中不能使用vTaskDelay()阻塞之類的API函數,否則會將系統中的定時器服務函數給阻塞掉.

7.2 FreeRTOSConfig.h相關的定時器配置

#define configUSE_TIMERS 1              //為1時啟用軟件定時器

#define configTIMER_TASK_PRIORITY 31    //設置軟件定時器優先級可設置的值範圍為0~31

#define configTIMER_QUEUE_LENGTH 5      //軟件定時器隊列長度

#define configTIMER_TASK_STACK_DEPTH 200 //設置每個軟件定時器任務堆棧大小

7.3定時創建相關API

TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,    //定時器字符串別名
                    const TickType_t xTimerPeriodInTicks,          
                   //需要定時的周期值,比如通過200/ portTICK_RATE_MS來轉換實現定時200毫秒                     const UBaseType_t uxAutoReload,        
                   //是否重載(周期性/單次性),若為pdTRUE(1)表示為周期性,為pdFALSE(0)表示為單次                     void * const pvTimerID,
                    //定時器ID號,一般用於多個定時器共用一個定時器回調函數,否則填0即可                     TimerCallbackFunction_t pxCallbackFunction);//定時器回調函數
xTimerDelete( xTimer, xTicksToWait );
//刪除定時器
//xTicksToWait:指定該定時器在多少時鐘節拍數之前刪除掉,為0則立即刪除,一般設為100(如果設為0,則如果在該操作之前還有其它設置定時器操作的話,則不會進行阻塞等待,從而返回false)

7.4 定時器其它常用API

xTimerChangePeriod( xTimer, xNewPeriod, xTicksToWait );
//修改定時器周期,在中斷中則使用xTimerChangePeriodFromISR()
// xNewPeriod:要修改的周期值 
//xTicksToWait:指定該定時器在多少時鐘節拍數之前修改好,為0則立即刪除
//xTimerReset( xTimer, xTicksToWait );
//復位定時器,讓定時器重新計數,在中斷中則使用xTimerResetFromISR()
// xTicksToWait:和上面內容類似

xTimerStart( xTimer, xTicksToWait );
//啟動定時器,如果定時器正在運行的話調用該函數的結果和xTimerReset()一樣, 在中斷中則使用xTimerResetFromISR ()

xTimerStop( xTimer, xTicksToWait );
//停止定時器, 在中斷中則使用xTimerStopFromISR ()

STM32-FreeRTOS快速學習之總結1