#include <U8g2lib.h>
#define LED 5 // LED接腳
#define ISR_PIN 33 // 中斷輸入腳
//中斷服務例程(ISR,Interrupt Service Routine)
U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
// 宣告一個128x64像素的SSD1306 OLED顯示屏物件
//SSD1306: 這是一種常見的OLED顯示屏驅動IC型號。
//128X64: 顯示屏的解析度是128x64像素。
//NONAME: 未命名,表示這個顯示屏沒有具體的商標或型號名稱。
//1: 指的是使用的通信接口,這裡可能是I2C。
//HW_I2C: 表示使用硬體I2C接口進行通信。
// U8G2_R0通常用於指定顯示屏的顯示方向。在這裡 U8G2_R0 表示沒有旋轉,即顯示屏不進行旋轉。
// U8X8_PIN_NONE是U8g2库中的一個宏,用於指定不使用U8g2庫所需的任何Pin腳,通常用於初始化
//宏(Macro)是一種在編譯過程中進行文本替換的機制。它是一種預處理器指令
//通常由編譯器的預處理器(Preprocessor)處理,用來將程式碼中的某些文本片段替換為指定的內容。
//U8g2庫是適用於嵌入式系統的顯示驅動程式庫。它主要用於驅動各種基於I2C和SPI接口的小型顯示屏,例如OLED和LCD。
//這個函式庫的特點是佔用資源少,適合在記憶體和處理能力有限的微控制器上使用。
//U8g2庫提供了豐富的繪圖功能,可以顯示文字、圖形和各種符號,方便開發者在小螢幕上進行資訊顯示。
uint8_t score = 0; // 紀錄成績的變數,初始值為0
volatile bool flashLight = false;
// 是否快閃LED的標誌變數,使用volatile修飾符表明在中斷中被修改
//volatile關鍵字是用來阻止編譯器因誤認某段程式碼無法被程式碼本身所改變,而造成的過度優化。
SemaphoreHandle_t xSem; // 2進位信號量的處理物件
//是 FreeRTOS 中用來表示信號量(Semaphore)的數據類型。
//信號量是一種多任務間同步和通信的機制,用於控制對共享資源的訪問。
// 中斷服務函式,當中斷觸發時會將flashLight設置為true並釋放信號量
void IRAM_ATTR ISR() {
flashLight = true;
xSemaphoreGiveFromISR(xSem, NULL); // 從中斷服務函式中釋放信號量
//xSemaphoreGiveFromISR是FreeRTOS中的一個函數,用於在中斷服務例程(ISR)中給予信號量
//專門用於在中斷處理中給予信號量。
//NULL 是一個在C和C++中常見的宏定義,通常用來表示指標的空值或無效值。
}
// OLED顯示任務
//OLED(Organic Light-Emitting Diode,有機發光二極管)是一種特殊類型的顯示技術
//它使用有機材料作為發光層,當電流通過時會發光。
void taskOLED(void *pvParam) {
//void:表示指標指向的內容是未知的或未指定具體型態的。
//*:表示這是一個指標。
//pvParam:這是變量的名稱,通常用來表示指向某種參數的指標。
//FreeRTOS中,void *pvParam 經常用於傳遞任務(Task)的參數。當創建一個新的任務時
//可以使用 xTaskCreate 函數來指定任務的函數和參數。
u8g2.begin(); // 初始化U8g2顯示屏物件
u8g2.setFont(u8g2_font_inb63_mn); // 設置字型為63像素高的數字字體
String txt; // OLED顯示的文字
while (1) {
// 根據score的值來決定顯示的文字
if (score < 10) { // 若score小於10,則在前面補零顯示
txt = "0" + String(score);
} else {
txt = String(score);
}
// 開始顯示新的一頁
u8g2.firstPage();
do {
u8g2.drawStr(12, 63, txt.c_str()); // 在OLED上顯示文字
} while (u8g2.nextPage()); // 繼續下一頁
vTaskDelay(pdMS_TO_TICKS(100)); // 延遲100毫秒
}
}
// 成績更新任務
void taskScore(void *pvParam) {
uint32_t preMs = 0; // 紀錄上次觸發的時間
while (1) {
if (xSemaphoreTake(xSem, portMAX_DELAY) == pdPASS) { // 等待並取得信號量
//xSemaphoreTake 是 FreeRTOS 中用來請求(take)一個信號量的函數。
//portMAX_DELAY 是一個常數,表示在請求信號量時允許的最大延遲時間。
//如果設置為 portMAX_DELAY,表示函數會一直等待,直到成功取得信號量或者有中斷發生。
//pdPASS 是 FreeRTOS 中的一個常量,表示函數執行成功的狀態碼。具體的值通常是 1。
// 檢查與上次觸發的時間間隔,小於1000 tick則不執行
if (xTaskGetTickCountFromISR() - preMs < 1000) {
//xTaskGetTickCountFromISR是 FreeRTOS 中的一個函數
//用於從中斷服務例程(ISR)中獲取系統的tick計數。
//用來計量時間的基本單位,類似於系統運行的節拍數或時鐘週期數。
//通常用來計算時間間隔或實現時間相關的操作。
continue;
//是一個控制流程的關鍵字,用於在迴圈中跳過本次迴圈剩餘的程式碼,直接進入下一次迴圈的迭代
}
if (++score == 100) // score加1,若達到100則重新歸零
score = 0;
preMs = xTaskGetTickCountFromISR(); // 更新上次觸發的時間
}
}
}
// LED閃爍任務
void taskBlink(void *pvParam) {
uint8_t counter = 0; // 閃爍LED的計數器
pinMode(LED, OUTPUT); // 設置LED接腳為輸出模式
while (1) {
digitalWrite(LED, !digitalRead(LED)); // 切換LED狀態(閃爍)
if (!flashLight) { // 若flashLight為false,則延遲500毫秒
vTaskDelay(pdMS_TO_TICKS(500));
} else {
if (++counter == 10) { // 若counter達到10,則重置counter並將flashLight設置為false
counter = 0;
flashLight = false;
}
vTaskDelay(pdMS_TO_TICKS(100)); // 延遲100毫秒
}
}
}
void setup() {
Serial.begin(115200); // 初始化串口通信,波特率115200
pinMode(ISR_PIN, INPUT_PULLUP); // 設置中斷腳為上拉電阻模式
attachInterrupt(ISR_PIN, ISR, RISING); // 附加中斷處理函式到中斷腳,中斷方式為上升沿觸發
//attachInterrupt 是 Arduino 中用來設置中斷服務例程(ISR)的函數。
//在使用中,你需要指定中斷引腳(如 ISR_PIN)和要執行的 ISR 函數(如 ISR)。
xSem = xSemaphoreCreateBinary(); // 創建一個二進位信號量對象
//xSemaphoreCreateBinary 是 FreeRTOS 中用來創建二元信號量(binary semaphore)的函數。
//二元信號量是一種信號量,只能取值0或1。通常用於同步和互斥操作。
if (xSem == NULL) { // 檢查是否成功創建信號量對象
Serial.println("無法建立「2進位號誌」物件~");
}
// 創建多個任務
xTaskCreate(taskScore, "score task", 1000, NULL, 2, NULL);
// 創建成績更新任務,堆棧大小為1000字節,優先級2
xTaskCreate(taskBlink, "blink task", 1000, NULL, 1, NULL);
// 創建LED閃爍任務,堆棧大小為1000字節,優先級1
xTaskCreate(taskOLED, "OLED task", 1500, NULL, 1, NULL);
// 創建OLED顯示任務,堆棧大小為1500字節,優先級1
}
void loop() {
uint32_t preMs = xTaskGetTickCount(); // 紀錄當前tick計數
//xTaskGetTickCount是 FreeRTOS 中用來獲取系統運行時間的函數。
//它返回從 FreeRTOS 啟動以來的系統tick計數,每一個tick通常對應一個固定的時間間隔
//可以用來計算時間的過去或者做定時操作。
vTaskDelay(1000); // 延遲1000毫秒
uint32_t now = xTaskGetTickCount(); // 獲取當前tick計數
int diff = now - preMs; // 計算時間差
printf("時間差:%u滴答\n", diff); // 輸出時間差到串口
}
MPU 0