在 Xilinx SDK 2018.3 中使用 FreeRTOS 開發多工系統
在 Xilinx SDK 2018.3 中使用 FreeRTOS 開發多工系統
專案簡介
這篇文章要跟大家分享如何在 Xilinx Software Development Kit (SDK) 2018.3 版本中,使用 FreeRTOS 來開發一個多工系統。我們會實作任務管理、UART 中斷處理以及任務間的通訊機制。
開發環境
- Xilinx SDK 2018.3
- FreeRTOS 即時作業系統
- 開發板:Xilinx Zynq-7000 系列
系統架構
程式處理流程
flowchart TD A[系統開機] --> B[初始化階段] B --> C1[建立UART佇列] B --> C2[建立UART號誌] B --> C3[初始化UART裝置] C1 --> D[建立起始任務] C2 --> D C3 --> D D --> E[啟動FreeRTOS排程器] E --> F[起始任務執行] F --> G1[設定UART中斷] F --> G2[建立UART中斷處理任務] F --> G3[建立Hi列印任務] F --> G4[建立Hello列印任務] G1 --> H[刪除起始任務] G2 --> H G3 --> H G4 --> H H --> I[多工任務並行執行] I --> J1[UART中斷處理] I --> J2[Hi任務執行] I --> J3[Hello任務執行] J1 --> K[接收指令處理] K --> L1[任務控制指令] K --> L2[計數器重置指令] L1 --> I L2 --> I
任務架構
系統包含以下幾個主要任務:
- 起始任務(優先權:1)
- UART中斷處理任務(優先權:2)
- Hi列印任務(優先權:2)
- Hello列印任務(優先權:2)
通訊機制
- 使用佇列(Queue)處理 UART 接收的資料
- 使用號誌(Semaphore)處理 UART 傳送同步
- 任務優先權管理
處理流程說明
-
初始化階段
- 建立 UART 通訊用的佇列(32 位元組)和號誌
- 初始化 UART 裝置(鮑率 115200)
- 建立起始任務(優先權 1)
-
任務建立階段
- 起始任務負責建立其他任務
- 設定 UART 中斷處理機制
- 建立三個工作任務(優先權 2)
- 完成後自行刪除
-
執行階段
- UART 中斷處理任務等待接收指令
- Hi 任務每 500ms 執行一次
- Hello 任務每 1200ms 執行一次
- 透過 UART 指令可動態控制任務
-
指令處理流程
- 接收字元存入指令緩衝區
- 遇到換行字元就解析執行指令
- 支援任務暫停/繼續/計數器重置等操作
完整程式碼實現
1. 主程式 (main_cpu0.c)
/*******************************************************************************
* 檔案名稱: main_cpu0.c
* 說明: 主程式進入點,負責初始化UART及建立FreeRTOS工作任務
* 作者: TienYao
*******************************************************************************/
#include <stdio.h>
/* FreeRTOS相關標頭檔 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* Xilinx相關標頭檔 */
#include "xparameters.h"
#include "xuartps.h"
#include "xscugic.h"
/* 使用者自訂標頭檔 */
#include "user_task.h"
#include "uart_intr.h"
int main() {
/* 變數宣告 */
int status;
XUartPs_Config *uartPsConfig;
/* 建立UART通訊用的佇列和號誌 */
xUartRxQueue = xQueueCreate(32, sizeof(uint8_t));
xUartTxSemaphore = xSemaphoreCreateBinary();
/* 檢查佇列和號誌是否建立成功 */
if (xUartRxQueue == NULL || xUartTxSemaphore == NULL) {
return XST_FAILURE;
}
/* 初始化UART */
status = uart_init(uartPsConfig);
if(status != XST_SUCCESS) {
printf("UART初始化失敗\n");
return XST_FAILURE;
}
/* 建立起始任務 */
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
);
/* 啟動FreeRTOS排程器 */
vTaskStartScheduler();
/* 永久迴圈 */
while(1);
return 0;
}
2. UART中斷處理 (uart_intr.h)
/*******************************************************************************
* 檔案名稱: uart_intr.h
* 說明: UART中斷相關定義與函式宣告
* 作者: TienYao
*******************************************************************************/
#ifndef SRC_UART_INTR_H_
#define SRC_UART_INTR_H_
#include "xparameters.h"
#include "xuartps.h"
#include "xscugic.h"
/* UART與中斷控制器裝置ID定義 */
#define UART_0_DEVICE_ID XPAR_PS7_UART_0_DEVICE_ID
#define SCUGIC_0_DEVICE_ID XPAR_PS7_SCUGIC_0_DEVICE_ID
#define XUARTPS_0_INTR XPAR_XUARTPS_0_INTR
/* UART裝置實體 */
XUartPs uartPs;
/* 函式原型宣告 */
int uart_init(XUartPs_Config *);
#endif /* SRC_UART_INTR_H_ */
3. UART中斷處理實作 (uart_intr.c)
/*******************************************************************************
* 檔案名稱: uart_intr.c
* 說明: UART中斷初始化與設定
* 作者: TienYao
*******************************************************************************/
#include "uart_intr.h"
int uart_init(XUartPs_Config *uartPsConfig) {
int status;
/* 查找並取得UART裝置設定 */
uartPsConfig = XUartPs_LookupConfig(UART_0_DEVICE_ID);
if(uartPsConfig == NULL) {
return XST_FAILURE;
}
/* 初始化UART裝置 */
status = XUartPs_CfgInitialize(&uartPs, uartPsConfig, uartPsConfig->BaseAddress);
if(status != XST_SUCCESS) {
return XST_FAILURE;
}
/* 設定UART運作模式、鮑率及FIFO臨界值 */
XUartPs_SetOptions(&uartPs, XUARTPS_OPER_MODE_NORMAL);
XUartPs_SetBaudRate(&uartPs, 115200);
XUartPs_SetFifoThreshold(&uartPs, 1);
return XST_SUCCESS;
}
4. 使用者任務定義 (user_task.h)
/*******************************************************************************
* 檔案名稱: user_task.h
* 說明: FreeRTOS任務相關定義與函式宣告
* 作者: TienYao
*******************************************************************************/
#ifndef SRC_USER_TASK_H_
#define SRC_USER_TASK_H_
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "xscugic.h"
/* 中斷控制器實體
* 此變數為Xilinx通用中斷控制器(GIC)的實體
* extern關鍵字表示此變數在其他檔案中定義
* 用於處理所有系統中斷,包含UART中斷
*/
extern XScuGic xInterruptController;
/* UART通訊用佇列和號誌 */
QueueHandle_t xUartRxQueue;
SemaphoreHandle_t xUartTxSemaphore;
/* 起始任務相關定義 */
#define START_TASK_PRIO 1
#define START_STK_SIZE 128
TaskHandle_t StartTask_Handler;
void start_task();
/* UART中斷處理任務相關定義 */
#define UART_INT_TASK_PRIO 2
#define UART_INT_STK_SIZE 250
TaskHandle_t uart_int_task_handler;
void uart_int_task();
/* Hi列印任務相關定義 */
#define PRINT_HI_TASK_PRIO 2
#define PRINT_HI_STK_SIZE 250
TaskHandle_t print_hi_Task_handler;
void print_hi_task();
/* Hello列印任務相關定義 */
#define PRINT_HELLO_TASK_PRIO 2
#define PRINT_HELLO_STK_SIZE 250
TaskHandle_t print_hello_Task_handler;
void print_hello_task();
/* UART中斷處理函式 */
void uart_intr_handler(void *);
#endif /* SRC_USER_TASK_H_ */
5. 使用者任務實作 (user_task.c)
/*******************************************************************************
* 檔案名稱: user_task.c
* 說明: FreeRTOS任務實作,包含UART中斷處理、Hi/Hello列印任務等
* 作者: TienYao
*******************************************************************************/
#include "user_task.h"
#include "uart_intr.h"
#include <stdio.h>
/* Hi和Hello任務的計數器 */
unsigned hiLoop = 0; /* Hi任務的列印次數計數器 */
unsigned helloLoop = 0; /* Hello任務的列印次數計數器 */
/**
* @brief UART中斷處理函式
* @param call_back_ref: UART裝置指標
* @note 處理UART接收和傳送中斷
*/
void uart_intr_handler(void *call_back_ref) {
XUartPs *UartPs = (XUartPs *)call_back_ref;
uint32_t IsrStatus = XUartPs_ReadReg(UartPs->Config.BaseAddress, XUARTPS_ISR_OFFSET);
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
/* 處理接收中斷 */
if (IsrStatus & XUARTPS_IXR_RXOVR) {
uint8_t ReceivedChar = XUartPs_ReadReg(UartPs->Config.BaseAddress, XUARTPS_FIFO_OFFSET);
xQueueSendFromISR(xUartRxQueue, &ReceivedChar, &xHigherPriorityTaskWoken);
}
/* 處理傳送中斷 */
if (IsrStatus & XUARTPS_IXR_TXEMPTY) {
xSemaphoreGiveFromISR(xUartTxSemaphore, &xHigherPriorityTaskWoken);
}
/* 清除中斷狀態並檢查是否需要切換任務 */
XUartPs_WriteReg(UartPs->Config.BaseAddress, XUARTPS_ISR_OFFSET, IsrStatus);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
/**
* @brief UART中斷處理任務
* @param pvParameters: 任務參數(未使用)
* @note 處理UART接收到的指令
*/
void uart_int_task(void *pvParameters) {
uint8_t ReceivedChar;
char cmd[16];
u8 cnt = 0;
while (1) {
/* 等待接收字元 */
if (xQueueReceive(xUartRxQueue, &ReceivedChar, portMAX_DELAY) == pdTRUE) {
if(ReceivedChar == '\r') { /* 收到換行字元,表示指令結束 */
xil_printf("%s\n", cmd);
/* 解析並執行指令 */
if(!strcmp("stop_hi", cmd)) {
vTaskSuspend(print_hi_Task_handler); /* 暫停Hi任務 */
} else if(!strcmp("go_hi", cmd)) {
vTaskResume(print_hi_Task_handler); /* 繼續Hi任務 */
} else if(!strcmp("clear_go_loop", cmd)) {
hiLoop = 0; /* 重置Hi計數器 */
} else if (!strcmp("stop_hello", cmd)) {
vTaskSuspend(print_hi_Task_handler); /* 暫停Hello任務 */
} else if(!strcmp("go_hello", cmd)) {
vTaskResume(print_hi_Task_handler); /* 繼續Hello任務 */
} else if(!strcmp("clear_hello_loop", cmd)) {
helloLoop = 0; /* 重置Hello計數器 */
}
else {
printf("未知指令\n"); /* 未知指令 */
}
cnt = 0;
} else {
cmd[cnt] = ReceivedChar; /* 儲存指令字元 */
cnt++;
cmd[cnt] = '\0'; /* 字串結尾 */
}
}
}
}
/**
* @brief Hi列印任務
* @note 每500毫秒列印一次Hi
*/
void print_hi_task() {
while(1) {
vTaskDelay(500); /* 延遲500毫秒 */
printf("Hi 次數 %d\n", hiLoop++); /* 列印Hi和計數 */
}
}
/**
* @brief Hello列印任務
* @note 每1200毫秒列印一次Hello
*/
void print_hello_task() {
while(1) {
vTaskDelay(1200); /* 延遲1200毫秒 */
printf("Hello 次數 %d\n", helloLoop++); /* 列印Hello和計數 */
}
}
/**
* @brief 起始任務
* @note 初始化系統並建立其他任務
*/
void start_task() {
taskENTER_CRITICAL(); /* 進入臨界區 */
/* 設定UART中斷 */
XScuGic_Connect(&xInterruptController, XUARTPS_0_INTR,
(Xil_InterruptHandler) uart_intr_handler, (void *)&uartPs);
XUartPs_SetInterruptMask(&uartPs, XUARTPS_IXR_RXOVR);
Xil_ExceptionInit();
XScuGic_Enable(&xInterruptController, XUARTPS_0_INTR);
/* 建立UART中斷處理任務 */
xTaskCreate(
(TaskFunction_t)uart_int_task,
(const char*)"uart_int_task",
(uint16_t)UART_INT_STK_SIZE,
(void*)NULL,
(UBaseType_t)UART_INT_TASK_PRIO,
(TaskHandle_t*)&uart_int_task_handler
);
/* 建立Hi列印任務 */
xTaskCreate(
(TaskFunction_t)print_hi_task,
(const char*)"print_hi_task",
(uint16_t)PRINT_HI_STK_SIZE,
(void*)NULL,
(UBaseType_t)PRINT_HI_TASK_PRIO,
(TaskHandle_t*)&print_hi_Task_handler
);
/* 建立Hello列印任務 */
xTaskCreate(
(TaskFunction_t)print_hello_task,
(const char*)"print_hello_task",
(uint16_t)PRINT_HELLO_STK_SIZE,
(void*)NULL,
(UBaseType_t)PRINT_HELLO_TASK_PRIO,
(TaskHandle_t*)&print_hello_Task_handler
);
vTaskDelete(StartTask_Handler); /* 刪除起始任務 */
taskEXIT_CRITICAL(); /* 退出臨界區 */
}
特色功能
1. 動態任務控制
- 可以透過 UART 指令動態控制任務的暫停和繼續
- 支援計數器重置功能
- 任務優先權可調整
2. 中斷處理機制
- 使用佇列處理 UART 接收中斷
- 使用號誌處理 UART 傳送同步
- 支援高優先權任務搶佔
使用說明
UART 指令列表
stop_hi
:暫停 Hi 任務go_hi
:繼續 Hi 任務clear_go_loop
:重置 Hi 計數器stop_hello
:暫停 Hello 任務go_hello
:繼續 Hello 任務clear_hello_loop
:重置 Hello 計數器
技術重點
- FreeRTOS 任務管理
- 中斷處理
- 任務間通訊(佇列、號誌)
- 臨界區保護
- UART 通訊協定
注意事項
在 Zynq-7000 上使用 FreeRTOS 加入 UART 中斷功能時,要特別注意以下幾點:
-
中斷控制器初始化
- FreeRTOS BSP 裡的
portZynq7000.c
已經定義好全域中斷控制器XScuGic xInterruptController
- PS 只有一個中斷控制器資源,如果自己重新初始化中斷控制器(直接使用 FreeRTOS BSP 裡的
portZynq7000.c
定義的中斷控制器就好),可能會導致任務無法正常執行 - 一定要使用
extern XScuGic xInterruptController
宣告這個全域變數 - 其它就用 Xilinx 的方法進行 config 與 connect 設定就可以了
- FreeRTOS BSP 裡的
-
初始化時序
- 中斷初始化一定要在執行緒啟動前完成
- 不能在
main
函式中初始化中斷 - 要在
vTaskStartScheduler()
之前完成所有中斷設定
未來展望
- 加入更多任務管理功能
- 優化中斷處理機制
- 增加更多通訊介面支援
- 加入任務監控功能