Try   HackMD

2025 成大嵌入式作業系統分析與實作 Lab1

github

題目說明

  • Must using MultiTask, with Inter-Task Communication (ITC) mechanism
  • Two Tasks: LED-task and Button-task
  • LED-task will have two states (S1, S2)
    • S1: First, the Red LED lights up for 1 second, followed by the Orange LED
      lighting up for 1 second (with the Red LED turned off), then the Green LED
      lights up for 1 second (with both the Red and Orange LEDs turned off). This
      sequence repeats, cycling through the Red, Orange, and Green LEDs.
    • S2: Only Orange LED is blinking (2 second ON, 2 second OFF, …)
  • Button-task: If the button is pressed, the LED-task will switch to another state
    (S1→S2).
    • Debounce handling
    • Edge detection handling

bounce and debounce

bounce: 當機械按鈕被按下時,內部的機械接觸可能會導致多次開關信號,這稱為「彈跳效應」。
debounce 是用來限制函數在短時間內多次執行的機制,當偵測到按鈕按下時,先忽略短時間內的變化,直到訊號穩定,才確認一次按鍵輸入。

image
使用 vTaskDelay() 讓程式延遲一段時間再讀取穩定後的狀態。
image

void vTaskBlueButtonControl(void* pvPatameters)
{
  for(;;) {
    if (HAL_GPIO_ReadPin(BLUE_BUTTON_GPIO_Port, GPIO_PIN_0))
    {
      vTaskDelay(pdMS_TO_TICKS(100)); // Delay 100 ms,等 Bounce 結束

      // 等待按鈕鬆開,避免重複觸發
      while (HAL_GPIO_ReadPin(BLUE_BUTTON_GPIO_Port, GPIO_PIN_0))
      {
        vTaskDelay(pdMS_TO_TICKS(10));
      }
    }
  }
}

實作

FreeRTOS設定

STM32 Port 設定 (修改 .ioc檔)

image

image

將PD12、PD13、PD14、PA0做對應設定
左鍵: 選擇模式
右鍵: 重新命名

程式碼

  1. include RTOS相關程式碼 (順序不能調換,會報錯)
  2. 因為要使用 message queue,所以也要 include
/* USER CODE BEGIN Includes */
#include "FreeRTOS.h"
#include "task.h"
/* USER CODE END Includes */
  1. 宣告 message queue
QueueHandle_t MsgQueue = NULL; // 宣告 queue handle 
  1. 藍色按鈕控制函式 vTaskBlueButtonControl()
    透過訊息佇列 xQueueSend() 通知另一個 LED 控制任務 (vTaskLEDControl) 來切換 LED 狀態
void vTaskBlueButtonControl(void* pvPatameters)
{
  unsigned int state = 0;
  unsigned int count = 0;

  for(;;) {
    // 檢查Pin腳有無訊號傳入
    if (HAL_GPIO_ReadPin(BLUE_BUTTON_GPIO_Port, GPIO_PIN_0))
    {
      vTaskDelay(pdMS_TO_TICKS(100)); // Debounce
      // 等待按鈕釋放避免產生多次觸發
      while (HAL_GPIO_ReadPin(BLUE_BUTTON_GPIO_Port, GPIO_PIN_0))
      {
        vTaskDelay(pdMS_TO_TICKS(10));
      }

      // 更新 state
      ++count;
      state = count & 0x01;
        
      // 透過 MsgQueue 通知 vTaskLEDControl 現在 LED 的顯示狀態
      xQueueSend(MsgQueue, &state, pdMS_TO_TICKS(10));
        
      // 主動讓出 CPU 給其他任務
      taskYIELD();
    }
  }
}
  1. LED 狀態函式
  • led_state == 1 時,LED 會紅→橙→綠 依序閃爍,每顆 LED 亮 1 秒
  • led_state == 0 時,橙色 LED 會 閃爍 2 秒開、2 秒關
void vTaskLEDControl(void* pvParameters)
{
  unsigned int led_state = 1; // LED 預設狀態為 1 (循環三色)

  for (;;)
  {
    unsigned int curr = 0; // 紀錄當前 LED 燈的狀態 (對應到 switch case)

    // **模式 1: 三色循環**
    curr = 0; 
    while (led_state == 1)
    {
      switch (curr)
      {
        case 0:
          // **紅燈亮 1 秒**
          HAL_GPIO_WritePin(RED_LED_GPIO_Port, GPIO_PIN_14, GPIO_PIN_SET);
          vTaskDelay(pdMS_TO_TICKS(1000));
          HAL_GPIO_WritePin(RED_LED_GPIO_Port, GPIO_PIN_14, GPIO_PIN_RESET);
          break;
        case 1:
          // **橙燈亮 1 秒**
          HAL_GPIO_WritePin(ORANGE_LED_GPIO_Port, GPIO_PIN_13, GPIO_PIN_SET);
          vTaskDelay(pdMS_TO_TICKS(1000));
          HAL_GPIO_WritePin(ORANGE_LED_GPIO_Port, GPIO_PIN_13, GPIO_PIN_RESET);
          break;
        case 2:
          // **綠燈亮 1 秒**
          HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GPIO_PIN_12, GPIO_PIN_SET);
          vTaskDelay(pdMS_TO_TICKS(1000));
          HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GPIO_PIN_12, GPIO_PIN_RESET);
          break;
      }
      
      // **循環切換 LED 顏色**
      ++curr;
      curr = curr % 3; // 確保 curr 在 0~2 之間循環

      // **檢查訊息佇列 (非阻塞)**
      // 若有新的 LED 狀態則更新
      xQueueReceive(MsgQueue, &led_state, 0);
    }

    // **模式 2: 橙燈閃爍**
    curr = 0;
    while (led_state == 0)
    {
      switch (curr)
      {
        case 0:
          // **橙燈亮 2 秒**
          HAL_GPIO_WritePin(ORANGE_LED_GPIO_Port, GPIO_PIN_13, GPIO_PIN_SET);
          vTaskDelay(pdMS_TO_TICKS(2000));
          HAL_GPIO_WritePin(ORANGE_LED_GPIO_Port, GPIO_PIN_13, GPIO_PIN_RESET);
          break;
        case 1:
          // **橙燈滅 2 秒**
          HAL_GPIO_WritePin(ORANGE_LED_GPIO_Port, GPIO_PIN_13, GPIO_PIN_RESET);
          vTaskDelay(pdMS_TO_TICKS(2000));
          break;
      }

      // **切換閃爍狀態**
      ++curr;
      curr = curr & 0x01; // 讓 curr 在 0 和 1 之間循環

      // **檢查訊息佇列 (非阻塞)**
      xQueueReceive(MsgQueue, &led_state, 0);
    }
  }
}