# 2024 嵌入式作業系統分析與實做 Final Project
### 專案名稱: 倉鼠智慧照顧系統 Hamster Smart Care System
[Github](https://github.com/ssheep773/FreeRTOS_AutomaticFeeder)
## 動機
倉鼠的飼主遇到以下幾個問題:
1.忘記餵食飼料
2.觀察倉鼠有無按時吃飯
3.觀察倉鼠的活動量
## 實作內容
#### 1. 餵食模組:
每隔固定時間投放飼料,同時感測倉鼠有無站在飼料區前進食,若無則發出聲音提示
#### 2. 運動計時模組:
透過滾輪的圈數計算倉鼠的運動量,使用紅外線感應器,搭配滾輪上的擋板,檢測滾輪圈數。
使用紅外線檢測,易於按裝在現有飼養盒中
#### 3. 顯示模組:
運用 LCD 面板顯示倉鼠的相關資訊
1. 進食次數與多久未進食
2. 所跑圈數
#### 實作環境:
Evaluation Boards: STM32F407G-DISC1
IDE: STM32CubeIDE
OS: FreeRTOS
Language: C
## 程式碼說明
### 所有的任務列表
```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` :實際的圈數
```c
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 釋放給其他需要的任務。
```c
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.h` 和 `i2c-lcd.c` 控制 LCD 面板。
並接收來
分為兩個模式顯示根據 displaymode 決定,
這裡使用 `for (;;)` 讓畫面一直更新顯示內容,並透過 `MsgQueue` 接收 `ButtonTask()` 的訊號,當數值改變時跳出顯示迴圈,切換顯示模式。
```c
// 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()` 的方式等待,蜂鳴器不需要太精確的時間控制。
```c
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 的方式
> 原本的設計是希望透過兩個閥門控制,第一個閥門(下閥)用於控制倒出飼料,第二的閥門(上閥)用於將飼料填滿兩個閥門之間的空間,而這個空間就是每次飼料的投放量。(最後僅實作一個)
```c
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 沒有布林值的數值型態。
當觸發時會中斷前的任務,然後執行此中斷函式,稍微減緩同步問題。
```c
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**
### 馬達的硬體設置




### 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 |