# 2025 成大嵌入式作業系統分析與實作 Lab1 [github](https://github.com/yuyuan0625/Embedded-System-FreeRTOS-Development) ## 題目說明 - 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](https://hackmd.io/_uploads/rJ2Whth2ke.png) 使用 [`vTaskDelay()`](https://blog.freertos.org/Documentation/02-Kernel/04-API-references/02-Task-control/01-vTaskDelay) 讓程式延遲一段時間再讀取穩定後的狀態。 ![image](https://hackmd.io/_uploads/r1STaF32Jg.png) ```c 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](https://hackmd.io/_uploads/S1yDr_2hke.png) ![image](https://hackmd.io/_uploads/r1ZsB_h3kl.png) > 將PD12、PD13、PD14、PA0做對應設定 > 左鍵: 選擇模式 > 右鍵: 重新命名 ### 程式碼 1. include RTOS相關程式碼 (順序不能調換,會報錯) 2. 因為要使用 message queue,所以也要 include ```c /* USER CODE BEGIN Includes */ #include "FreeRTOS.h" #include "task.h" /* USER CODE END Includes */ ``` 3. 宣告 message queue ```c QueueHandle_t MsgQueue = NULL; // 宣告 queue handle ``` 4. 藍色按鈕控制函式 `vTaskBlueButtonControl()` 透過訊息佇列 `xQueueSend()` 通知另一個 LED 控制任務 (`vTaskLEDControl`) 來切換 LED 狀態 ```c 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(); } } } ``` 5. LED 狀態函式 - led_state == 1 時,LED 會紅→橙→綠 依序閃爍,每顆 LED 亮 1 秒 - led_state == 0 時,橙色 LED 會 閃爍 2 秒開、2 秒關 ```c 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); } } } ```