Try   HackMD

2024 嵌入式作業系統分析與實做 Final Project

專案名稱: 倉鼠智慧照顧系統 Hamster Smart Care System

Github

動機

倉鼠的飼主遇到以下幾個問題:
1.忘記餵食飼料
2.觀察倉鼠有無按時吃飯
3.觀察倉鼠的活動量

實作內容

1. 餵食模組:

每隔固定時間投放飼料,同時感測倉鼠有無站在飼料區前進食,若無則發出聲音提示

2. 運動計時模組:

透過滾輪的圈數計算倉鼠的運動量,使用紅外線感應器,搭配滾輪上的擋板,檢測滾輪圈數。
使用紅外線檢測,易於按裝在現有飼養盒中

3. 顯示模組:

運用 LCD 面板顯示倉鼠的相關資訊

  1. 進食次數與多久未進食
  2. 所跑圈數

實作環境:

Evaluation Boards: STM32F407G-DISC1
IDE: STM32CubeIDE
OS: FreeRTOS
Language: C

程式碼說明

所有的任務列表

/*Final_project/Final_project/Core/Src/main.c*/
  MsgQueue = xQueueCreate(1, sizeof(unsigned int));  // sign for switch display mode
  xTaskCreate(InfraredTask, "InfraredTask", 128, NULL, 1, NULL);   // detect running lap
  xTaskCreate(ButtonTask, "ButtonTask", 128, NULL, 1, NULL);       // switch lcd displaymode
  xTaskCreate(Display, "Display", 128, NULL, 1, NULL);             // show lcd content
  xTaskCreate(BuzzerTask, "BuzzerTask", 128, NULL, 1, NULL);       // sound beep to remind eating
  xTaskCreate(Motor_Task, "Motor_Task", 128, NULL, 1, NULL);       // check if have eaten
  xTaskCreate(TimerTask, "TimerTask", configMINIMAL_STACK_SIZE, NULL, 1, &xTimerTaskHandle);  // Timer

紅外線感測滾輪圈數 InfraredTask()

在原有的滾輪上加裝擋板,用於紅外線的感測。每當紅外線感測到檔板時增加圈數,並且是當狀態改變時才增加,避免檔板剛好停在感應處,造成一直增加圈數的問題。
objectDetected :感測到檔板
RunningLap :實際的圈數

void InfraredTask(void *pvParameters)
{
    for (;;)
    {
         if (HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_4) == GPIO_PIN_RESET) {
            if (objectDetected == 0) {
                objectDetected = 1;
                RunningLap++;
            }
         }
         else
            objectDetected = 0;
        vTaskDelay(10);
    }
}

切換顯示模式的按鈕 ButtonTask()

task 用於紀錄切換的模式,count 紀錄按鈕按壓次數
這裡切換顯示模式的方法,是透過檢查 count 是奇數或是偶數來切換,再透過 MsgQueue 將切換的資訊傳遞給其他任務,最後為了減少切換的延遲,使用 taskYIELD() 將 CPU 釋放給其他需要的任務。

void ButtonTask(void *pvParameters) {
	unsigned int task = 0;
	unsigned int count = 0;
	for(;;)
	{
		if(HAL_GPIO_ReadPin(btn_blue_GPIO_Port, GPIO_PIN_0))
		{
			HAL_Delay(100);//debounce
			while(HAL_GPIO_ReadPin(btn_blue_GPIO_Port, GPIO_PIN_0))
			{;}
			++count ;
			if(count & 0x01)
				task = 1;
			else
				task = 0;
			xQueueSend(MsgQueue,(int * ) &task,1);
			taskYIELD();
		}
	}
}

LCD 螢幕顯示 Display()

透過藍色按鈕切換兩種顯示模式,顯示餵食狀況與運動狀況

實作方法:
運用 i2c-lcd.hi2c-lcd.c 控制 LCD 面板。
並接收來

分為兩個模式顯示根據 displaymode 決定,
這裡使用 for (;;) 讓畫面一直更新顯示內容,並透過 MsgQueue 接收 ButtonTask() 的訊號,當數值改變時跳出顯示迴圈,切換顯示模式。

// Final_project/Final_project/Core/Src/main.c
#include "i2c-lcd.h" // for LCD module

void Display(void *pvParameters)
{
	unsigned int displaymode = 0;
	for(;;)
	{
		char buffer[30];
		if  (displaymode == 0){
			for (;;) {
				lcd_clear();
				lcd_put_cur(0, 0);
				sprintf(buffer, "eat: %u    %lu",eat,timeCounter);
				lcd_send_string(buffer);

				lcd_put_cur(1, 0);
				memset(buffer, 0, sizeof(buffer));
				sprintf(buffer, "noneat time: %lu", wait_timeCounter);
				lcd_send_string(buffer);

				xQueueReceive(MsgQueue, &displaymode, 0);
				vTaskDelay(10);
				if(displaymode == 1) break;
			}
		}
		else if(displaymode == 1){
		    for (;;) {
				lcd_clear();
				lcd_put_cur(0, 0);
				sprintf(buffer, "RunningLap: %lu", RunningLap);
				lcd_send_string(buffer);

				xQueueReceive(MsgQueue, &displaymode, 0);
				vTaskDelay(500);
				if(displaymode == 0) break;
		    }
		}
	}
}

蜂鳴器警示 Beep()

主要是透過 GPIO_PIN_RESET 發出聲音,以及 GPIO_PIN_SET 停止聲音,來控制蜂鳴器的聲響

這裡應該改以使用 vTaskDelay() 的方式等待,蜂鳴器不需要太精確的時間控制。

void Beep(void){
	uint32_t From_begin_time = HAL_GetTick();
	HAL_GPIO_WritePin(GPIOA, Beep_Pin, GPIO_PIN_RESET);
	while(HAL_GetTick() - From_begin_time < 50 / portTICK_RATE_MS){}

	From_begin_time = HAL_GetTick();
	HAL_GPIO_WritePin(GPIOA, Beep_Pin, GPIO_PIN_SET);
	while(HAL_GetTick() - From_begin_time < 500 / portTICK_RATE_MS){}
}

馬達驅動餵食器 Motor_Task()

利用 PWM 控制馬達的運行

if (timeCounter % FEEDING_INTERVAL_SECONDS ==0) 控制餵食頻率,此專案設定每 15 秒投放一次飼料。
初始化馬達 HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4)

轉動馬達分為三個步驟,首先是快速轉動閥門,接著短暫的停頓,再將閥門快速關閉。
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 150) 控制馬達的轉速,目的是將馬達轉動 90 度並稍微停頓後轉回,模擬到飼料的情境。

這裡使用 while(HAL_GetTick() - From_begin_time < 500 / portTICK_RATE_MS){} 不是使用 vTaskDelay(500) 是因為 vTaskDelay(500) 會受到其他任務執行時間的影響,而馬達轉動需要較精確的時間控制,所以使用此 busy waiting 的方式

原本的設計是希望透過兩個閥門控制,第一個閥門(下閥)用於控制倒出飼料,第二的閥門(上閥)用於將飼料填滿兩個閥門之間的空間,而這個空間就是每次飼料的投放量。(最後僅實作一個)

void Motor_Task(void *pvParameters){
    for (;;){
        if (timeCounter % FEEDING_INTERVAL_SECONDS ==0){
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15, GPIO_PIN_RESET);
        HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);

        uint32_t From_begin_time = HAL_GetTick();
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 150);
        while(HAL_GetTick() - From_begin_time < 500 / portTICK_RATE_MS){}

        From_begin_time = HAL_GetTick();
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 50);
        while(HAL_GetTick() - From_begin_time < 500 / portTICK_RATE_MS){}

        From_begin_time = HAL_GetTick();
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, 150);
        while(HAL_GetTick() - From_begin_time < 500 / portTICK_RATE_MS){}
        }
    }
}

時間計算器 TimerTask()

我們選擇使用軟體的方式來計算時間,雖然 STM32 也有內建硬體計時器可以使用,但由於需要將時間計算與馬達等模組的硬體設置進行同步,因此決定使用軟體計算方式來保持一致性。

void TimerTask(void *pvParameters)
{
    TickType_t xLastWakeTime;
    const TickType_t xFrequency = pdMS_TO_TICKS(1000); // 1 second as cycle

    // record currnet time
    xLastWakeTime = xTaskGetTickCount();
    for (;;)
    {
        // wait until next cycle
        vTaskDelayUntil(&xLastWakeTime, xFrequency);
        // update timer
        timeCounter++;
        wait_timeCounter++;
    }
}

壓力感測模組 HAL_GPIO_EXTI_Callback()

使用硬體中斷 call back 函數,並在函數內部判斷 GPIO_Pin 來處理不同的 GPIO 中斷。
當觸摸壓力感測器,表示有進食因此重設未進食時間,同時用黃燈警示。
touch = 1 這裡使用數值而非布林值是因為 FreeRTOS 沒有布林值的數值型態。
當觸發時會中斷前的任務,然後執行此中斷函式,稍微減緩同步問題。

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) //  touch pressure sensor
{
    if (GPIO_Pin == GPIO_PIN_5)
    {
        HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);  // yellow light
        touch = 1;                               // set torch to True(1)
        wait_timeCounter = 0;                    // reset wait_timeCounter
    }
}

硬體設定與腳位

SYS -> Timebase Source : TIM7

馬達的硬體設置

螢幕擷取畫面 2024-07-15 220054
螢幕擷取畫面 2024-07-15 220113

螢幕擷取畫面 2024-07-15 220135

螢幕擷取畫面 2024-07-15 220153

Pin 腳

因為需要連接 5 個設備,有些設備的 VCC 不是接建議的 5V ,但是還是可以運作

LCD screen STM board
GND GND
VCC 5V
SDA PB9
SCL PB6
馬達 SG-90 STM board
GND (棕色) GND
VCC (紅色) 5V
PWM 輸入 (橘色) PA3
紅外線 STM board
GND GND
VCC VDD
OUT PC4
蜂鳴器 STM board
GND GND
VCC 3V
I/O PA1
壓力感測器 STM board
GND GND
VCC VDD
SIG PC5