在開發實際應用系統時,我們經常需要考慮數據的實時性和多任務,嵌入式實時操作系統的出現為實現這一目的提供了很好的助力。FreeRTOS是近年來比較流行的嵌入式實時操作系統,而且是開源免費的,STM32CubeMX對它也提供了支持。我們可以使用STM32CubeMX很方便的添加上FreeRTOS,只需要在配置界面找到“MiddleWares”並將其下的FreeRTOS選為“Enable”就可添加上FreeRTOS:

配置完成后,更新源碼就會在軟件中添加上FreeRTOS的相關文件,各文件的功能網上已經有很多介紹,就不啰嗦了,而且也不屬於我們需要說明的內容。

添加完了之后,源碼中就會添加創建一個缺省任務的代碼。因為最少要有一個任務才有意義。查看main()函數,就會出現如下這樣一段代碼:
/* Create the thread(s) */
/* definition and creation of defaultTask */
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Start scheduler */
osKernelStart();
其實這段代碼就實現兩個功能:創建任務和情動任務調度。不過ST在FreeRTOS的基礎上做了進一步的封裝。其中osKernelStart()函數用於啟動任務調度,這個函數比較簡單只不過是調用了一下FreeRTOS中的任務調度函數vTaskStartScheduler(),其實現原型如下:
osStatus osKernelStart (void)
{
vTaskStartScheduler();
return osOK;
}
而實現任務創建的就是osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);和defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);這兩行代碼。這兩行代碼就是創建了一個任務顯示名稱為defaultTask的StartDefaultTask任務。帶是代碼看起來有點不容易理解,其實是ST進行了進一步封裝的結果。
首先osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128)其實是一個宏,其定義的原型如下:
#define osThreadDef(name, thread, priority, instances, stacksz) \
const osThreadDef_t os_thread_def_##name = \
{ #name, (thread), (priority), (instances), (stacksz) }
那么osThreadDef_t又是個什么呢?其實是ST定義的創建任務的參數結構體:
typedef struct os_thread_def {
char *name; ///< Thread name
os_pthread pthread; ///< start address of thread function
osPriority tpriority; ///< initial thread priority
uint32_t instances; ///< maximum number of instances of that thread function
uint32_t stacksize; ///< stack size requirements in bytes; 0 is default stack size
} osThreadDef_t;
所以第一句的含義就很明確了,就是定義了一個osThreadDef_t類型的變量用於存儲用於創建任務的參數。第二句很明顯是一個函數調用,但參數與我們見到的FreeRTOS中的創建任務的函數有些不同。其實它的第一個參數osThread(defaultTask)也是一宏:
#define osThread(name) \
&os_thread_def_##name
其實就是調用第一句中定義的osThreadDef_t類型的結構體變量。這樣就明確了,第二句實際上就是調用FreeRTOS中的任務創建函數創建任務:
osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument)
{
TaskHandle_t handle;
if (xTaskCreate((TaskFunction_t)thread_def->pthread,(const portCHAR *)thread_def->name,
thread_def->stacksize, argument, makeFreeRtosPriority(thread_def->tpriority),
&handle) != pdPASS) {
return NULL;
}
return handle;
}
所以我們要創建其他的任務,只需照此設計即可。當然任務中的內容根據需要修改,STM32CubeMX添加的缺省任務,參數和實現內容也可以更改。在這里我們不對缺省任務做太多修改,在其任務函數中添加部分我們的內容。
考慮到我們所應用到的功能,我們需要6個任務,利用上缺省任務我們還需要添加5個任務。之所以要添加這么多的任務是因為我們不想讓各種功能相互影響。在某一外設出現操作錯誤時不會影響其他部分的應用,這也是我們使用多任務實時操作系統的原因之一。
缺省任務我們讓它來跑邏輯控制。再增加幾個任務分別來跑各種功能,由於AD和DA都是通過SPI1通訊來完成的,所以采用同一個任務。
首先定義幾個任務操作句柄:
/*定義任務句柄*/
osThreadId defaultTaskHandle;
osThreadId addaTaskHandle;
osThreadId ndirTaskHandle;
osThreadId lcdTaskHandle;
osThreadId ethernetTaskHandle;
osThreadId paraTaskHandle;
在聲明對應的任務處理函數:
/*聲明任務處理函數*/
void StartDefaultTask(void const * argument);
void ADDATask(void const * argument);
void NDIRTask(void const * argument);
void LCDTask(void const * argument);
void EthernetTask(void const * argument);
void ParameterTask(void const * argument);
編寫各任務處理函數,由於各種功能在之前已經實現了所以任務函數比較簡單:
/* 缺省任務,用來處理邏輯控制 */
void StartDefaultTask(void const * argument)
{
for(;;)
{
/*邏輯處理*/
LogicOperation();
osDelay(1);
}
}
/* AD/DC數據處理任務函數 */
void ADDATask(void const * argument)
{
for(;;)
{
/*獲取AD采集的測量值*/
GetMeasuredValue();
/*設置模擬量輸出*/
SetOutputValue();
osDelay(1);
}
}
/* 遠紅外炭氫檢測數據處理任務函數 */
void NDIRTask(void const * argument)
{
for(;;)
{
/*獲取CH4測量值*/
GetNDIRData();
osDelay(1);
}
}
/* LCD顯示通訊處理任務函數 */
void LCDTask(void const * argument)
{
for(;;)
{
/*顯示屏數據通訊*/
LCD_DataExchange();
osDelay(1);
}
}
/* 以太網通訊處理任務函數 */
void EthernetTask(void const * argument)
{
for(;;)
{
/*以太網通訊處理*/
EthernetProcess();
osDelay(1);
}
}
/* 參數存取處理任務函數 */
void ParameterTask(void const * argument)
{
for(;;)
{
/*參數的存儲、恢復等處理*/
ParameterProcess();
osDelay(1);
}
}
再在系統中創建任務:
/* Create the thread(s) */
/* definition and creation of defaultTask */
osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128);
defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL);
/* USER CODE BEGIN RTOS_THREADS */
osThreadDef(addaTask, ADDATask, osPriorityNormal, 0, 128);
addaTaskHandle = osThreadCreate(osThread(addaTask), NULL);
osThreadDef(ndirTask, NDIRTask, osPriorityNormal, 0, 128);
ndirTaskHandle = osThreadCreate(osThread(ndirTask), NULL);
osThreadDef(lcdTask, LCDTask, osPriorityNormal, 0, 128);
lcdTaskHandle = osThreadCreate(osThread(lcdTask), NULL);
osThreadDef(ethernetTask, EthernetTask, osPriorityNormal, 0, 128);
ethernetTaskHandle = osThreadCreate(osThread(ethernetTask), NULL);
osThreadDef(paraTask, ParameterTask, osPriorityNormal, 0, 128);
paraTaskHandle = osThreadCreate(osThread(paraTask), NULL);
/* USER CODE END RTOS_THREADS */
至此在系統中添加FreeRTOS的過程完成,編譯調試看看結果:

編譯無錯誤,運行正常。通過IO中斷看看運行結果與沒有操作系統時有沒有區別:

在運行一段時間后再看看結果:

系統運行無誤,與我們所希望的一樣。
