STM32-FreeRTOS快速學習之總結1
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