8 min read

在 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. 起始任務(優先權:1)
  2. UART中斷處理任務(優先權:2)
  3. Hi列印任務(優先權:2)
  4. Hello列印任務(優先權:2)

通訊機制

  • 使用佇列(Queue)處理 UART 接收的資料
  • 使用號誌(Semaphore)處理 UART 傳送同步
  • 任務優先權管理

處理流程說明

  1. 初始化階段

    • 建立 UART 通訊用的佇列(32 位元組)和號誌
    • 初始化 UART 裝置(鮑率 115200)
    • 建立起始任務(優先權 1)
  2. 任務建立階段

    • 起始任務負責建立其他任務
    • 設定 UART 中斷處理機制
    • 建立三個工作任務(優先權 2)
    • 完成後自行刪除
  3. 執行階段

    • UART 中斷處理任務等待接收指令
    • Hi 任務每 500ms 執行一次
    • Hello 任務每 1200ms 執行一次
    • 透過 UART 指令可動態控制任務
  4. 指令處理流程

    • 接收字元存入指令緩衝區
    • 遇到換行字元就解析執行指令
    • 支援任務暫停/繼續/計數器重置等操作

完整程式碼實現

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 指令列表

  1. stop_hi:暫停 Hi 任務
  2. go_hi:繼續 Hi 任務
  3. clear_go_loop:重置 Hi 計數器
  4. stop_hello:暫停 Hello 任務
  5. go_hello:繼續 Hello 任務
  6. clear_hello_loop:重置 Hello 計數器

技術重點

  1. FreeRTOS 任務管理
  2. 中斷處理
  3. 任務間通訊(佇列、號誌)
  4. 臨界區保護
  5. UART 通訊協定

注意事項

在 Zynq-7000 上使用 FreeRTOS 加入 UART 中斷功能時,要特別注意以下幾點:

  1. 中斷控制器初始化

    • FreeRTOS BSP 裡的 portZynq7000.c 已經定義好全域中斷控制器 XScuGic xInterruptController
    • PS 只有一個中斷控制器資源,如果自己重新初始化中斷控制器(直接使用 FreeRTOS BSP 裡的 portZynq7000.c 定義的中斷控制器就好),可能會導致任務無法正常執行
    • 一定要使用 extern XScuGic xInterruptController 宣告這個全域變數
    • 其它就用 Xilinx 的方法進行 config 與 connect 設定就可以了
  2. 初始化時序

    • 中斷初始化一定要在執行緒啟動前完成
    • 不能在 main 函式中初始化中斷
    • 要在 vTaskStartScheduler() 之前完成所有中斷設定

未來展望

  1. 加入更多任務管理功能
  2. 優化中斷處理機制
  3. 增加更多通訊介面支援
  4. 加入任務監控功能