# Lab 3
<!-- {%hackmd hackmd-dark-theme %} -->
> 國立成功大學 資訊工程學系
嵌入式作業系統分析與實作 Analysis and Implementation of Embedded Operating Systems [CSIE7618] 2022 Spring
> GitHub: https://github.com/cpt1020/EmbeddedOS-Lab3
## Objective
- 瞭解如何用Serial Peripheral Interface和motion sensor (LIS3DSH)做communication
- 瞭解如何設置LIS3DSH的state machine以觸發Interrupt
- 瞭解如何使用FreeRTOS的semaphore相關API,以及Deferred Interrupt Handling
## Requirement


[助教提供的Lab 3 Demo影片](https://youtube.com/shorts/HtXRA1kaxHs)
:::success
Summary
- 平常沒事的時候只有綠色LED燈在閃爍
- 當晃動板子,trigger sensor interrupt,然後在此lab要使用deferred interrupt handling task
- ISR會給semaphore讓handler task醒來
- ISR會啟動/關閉紅色LED
- 也就是紅色LED若本來沒亮,那晃動一次後他就會亮,再晃動一次就不亮,再晃動一次又會亮,依此類推。但若是橘色LED在閃爍時晃動,則無效(見下說明)
- handler task的任務是讓橘色LED閃爍5次
- 當橘色LED燈在閃爍時,此時若晃動板子++不可++ trigger sensor interrupt
:::
:::info
[Deferred Interrupt Handling](https://www.freertos.org/deferred_interrupt_processing.html)
- 有時候我們不希望ISR佔用CPU太久的時間,所以我們可以讓ISR先處理最重要的部分,剩餘較不重要的部分就讓ISR去喚醒handling task,讓handling task去執行剩下的任務
- Handling task的priority必須要高於原本正在執行的task才行,這樣當ISR結束了,才會換handling task去做事,而不是中斷發生前的task

- t2:一個低優先權的task被ISR preempt了
- t3:ISR結束,並且ISR喚醒handler task。由於handler task的優先權比綠色的task還高,所以ISR結束後會是handler task搶到CPU
- t4:handler task結束,並回到blocked state,等待下一次的ISR把他叫醒。這時綠色的task才能搶到CPU,繼續做事
:::
## Hint

:::info
最後一點,`OUTS1` 的部分後面會再提到~
:::

### Template Code
[助教提供的 `main.c` 的 template code](https://drive.google.com/file/d/1kMFB7B11ZrqZzadKGVUU9tR0DSEA43eH/view?usp=sharing)
## Prerequisites and Configuration Setup
- Project前置設定:https://hackmd.io/@cpt/embeddedOS_lab0
- ==++**Motion Sensor (LIS3DSH)設定與說明**++==: https://hackmd.io/@cpt/embeddedOS_motion_sensor
### Interrupt設定

### Pin Configuration in STM32CubeIDE

## FreeRTOS APIs
以下是這次Lab會用到關於semaphore的APIs
- [`xSemaphoreGiveFromISR`](https://www.freertos.org/a00124.html)
- 這次的lab由於是ISR會發送semaphore,所以必須要用這個才行,不可使用 `xSemaphoreGive`
- [`xSemaphoreCreateBinary`](https://www.freertos.org/xSemaphoreCreateBinary.html)
- FreeRTOS的binary semaphore主要是用來synchronization,不建議拿來做mutual exclusion
- 因為FreeRTOS的binary semaphore沒有支援priority inheritance,所以可能會發生priority inversion
- 若要mutual exclusion的話建議用mutex (`xSemaphoreCreateMutex`),因為FreeRTOS的mutex就有支援priority inheritance的機制
- [`xSemaphoreTake`](https://www.freertos.org/a00122.html)
- 用來接收semaphore或mutex
## State Machine of LIS3DSH

講義有這麼一頁,是LIS3DSH的registers要怎麼設定才能設定sensor interrupt。我去查這好像是比較新版的 [LIS3DSH: 3-axis digital output accelerometer](https://drive.google.com/file/d/13lmWzrJA0MERsK2inaA48x4zjTfCU25x/view?usp=sharing) 關於怎麼設定Wake-up State Machine的configuration,但新版的這個document網路上都下載不到,STM官網也沒有@@ (好怪)
目前網路上下載得到的 [LIS3DSH: 3-axis digital output accelerometer](https://drive.google.com/file/d/13lmWzrJA0MERsK2inaA48x4zjTfCU25x/view?usp=sharing) 是2012版的,2012版的configuration跟2014版的有一點點不太一樣,但我測試起來效果目前看起來一樣@@,2012版的如下:

我在 [Motion Sensor (LIS3DSH)設定與說明](https://hackmd.io/@cpt/embeddedOS_motion_sensor) 的 [State Machine of LIS3DSH] 這一部有做簡單的說明
## Code
```cpp
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
#define LONG_TIME 0xffff
#define WAKEUP_STATE_MACHINE_CONFIG \
MEMS_Write(0x21,0x01); \
MEMS_Write(0x23,0x48); \
MEMS_Write(0x20,0x67); \
MEMS_Write(0x24,0x00); \
MEMS_Write(0x57,0x55); \
MEMS_Write(0x40,0x05); \
MEMS_Write(0x41,0x11); \
/* USER CODE END PM */
```
- `LONG_TIME` 是 FreeRTOS 的 semaphore 的教學頁面有用到的
- 我把 Wake-up state machine 的 register 的 configuration 寫成一個 macro,方便後續使用
```cpp
/* USER CODE BEGIN PV */
SemaphoreHandle_t xSemaphore = NULL;
uint8_t data;
/* USER CODE END PV */
```
- 先宣告一個 semaphore
- `data` 則是用在 `MEMS_Write` 和 `MEMS_Read`
```cpp
void MEMS_Write(uint8_t address,uint8_t data){
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,&address,1,10);
HAL_SPI_Transmit(&hspi1,&data,1,10);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3,GPIO_PIN_SET);
}
void MEMS_Read(uint8_t address,uint8_t *data){
address |= 0x80;
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_3,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,&address,1,10);
HAL_SPI_Receive(&hspi1,data,1,10);
HAL_GPIO_WritePin(GPIOE,GPIO_PIN_3,GPIO_PIN_SET);
}
```
- 助教給的code,用來read/write LIS3DSH 的 register
```cpp
void Green_LED_Task(void *pvParameters)
{
for(;;)
{
// Green LED blinks
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
uint32_t beginTime = HAL_GetTick();
while (HAL_GetTick() - beginTime < 500/portTICK_RATE_MS) {
}
}
}
```
- 這段程式碼就是每0.5秒,會去執行一次 `HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12)`
:::info
`portTICK_RATE_MS` 之相關定義如下:
```cpp
// FreeRTOS/include/FreeRTOS.h
#define portTICK_RATE_MS portTICK_PERIOD_MS
```
```cpp
// FreeRTOS/portable/ARM_CM4F
#define portTICK_PERIOD_MS ( ( TickType_t ) 1000 / configTICK_RATE_HZ )
```
```cpp
// FreeRTOS/include/FreeRTOSConfig.h
#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )
```
:::
:::info
`HAL_GetTick()`:

:::
```cpp
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* toggle Red LED */
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_14);
/* Give the semaphore to unblock the handler task */
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
```
:::info
這是ISR,當LIS3DSH產生sensor interrupt,這個ISR就會被呼叫。我們可以設定要做的事情:
1. 變換Red LED的state,使得每次sensor interrupt被產生,Red LED就從亮->暗,或暗->亮
2. 給semaphore,使得handler task被叫醒,這部分的程式碼就依據官方網站 [`xSemaphoreGiveFromISR`](https://www.freertos.org/a00124.html) 的例子去寫即可
另外,在 `Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c` 內可以看到:
```cpp
/**
* @brief EXTI line detection callbacks.
* @param GPIO_Pin Specifies the pins connected EXTI line
* @retval None
*/
__weak void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(GPIO_Pin);
/* NOTE: This function Should not be modified, when the callback is needed,
the HAL_GPIO_EXTI_Callback could be implemented in the user file
*/
}
```
可以看到這個function被標示為 `__weak`,`__weak` 是一個 GCC (GNU Compiler Collection) 的 extension,它用來標記一個 function,表示這個 function 是一個 weak symbol。
在嵌入式系統的開發中,這種標記通常用於允許 programmer 重新定義該function,並且這種重新定義不會導致linking error。如果這個function沒有被使用者重新定義,則使用預設的function。
在這裡,`HAL_GPIO_EXTI_Callback` 被標記為 `__weak`,這表示programmer可以重新定義這個function,以便於處理external interrupt。如果沒有重新定義這個function,則會使用預設的function,這樣就不會產生連接錯誤。
:::
```cpp
void vHandlerTask( void *pvParameters )
{
for(;;)
{
/* Take the semaphore */
if(xSemaphoreTake(xSemaphore, LONG_TIME) == pdTRUE) {
// semaphore was obtained
// Orange LED blinks 5 times
for (int i = 0; i < 10; ++i) {
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_13);
uint32_t beginTime = HAL_GetTick();
while (HAL_GetTick() - beginTime < 500/portTICK_RATE_MS) {
}
}
// reset interrupt register
MEMS_Read(LIS3DSH_OUTS1_ADDR, &data);
}
}
}
```
:::info
- 接收 semaphore 的部分就依據官網的 [`xSemaphoreTake`](https://www.freertos.org/a00122.html) 的例子去寫
- 比較需要注意的是,最後記得要reset interrupt registers,否則我們的開發板只能發出一次interrupt
- 前面Hint的部分助教有提到 `OUTS1` 這個register,以下是他的說明:
-  
- 所以只要去read這個register,就會恢復到發出interrupt之前的狀態
- 另外,也可以不用 `MEMS_Read(LIS3DSH_OUTS1_ADDR, &data);`,而改用 `WAKEUP_STATE_MACHINE_CONFIG` 將register的值都恢復到 Wake-up state machine一開始的設定
- reset interrupt registers這件事必須要在 `vHandlerTask` 最後才行,因為lab其中一個要求是,當橘色LED在閃爍的時候,若此時晃動開發板不能發出interrupt。所以要讓橘色LED閃爍五次結束後才reset interrupt registers
:::
```cpp
int main(void)
{
/* USER CODE BEGIN 2 */
xSemaphore = xSemaphoreCreateBinary();
WAKEUP_STATE_MACHINE_CONFIG
xTaskCreate(Green_LED_Task, "Green LED", 1000, NULL, 1, NULL);
xTaskCreate(vHandlerTask, "Handler Task", 1000, NULL, 4, NULL);
vTaskStartScheduler();
/* USER CODE END 2 */
}
```
:::info
- Create task的時候,記得 `vHandlerTask` 的priority要比 `Green_LED_Task` 還要高
- [Priority的範圍是 `0` ~ `(configMAX_PRIORITIES - 1)`](https://www.freertos.org/RTOS-task-priority.html)
```cpp
// FreeRTOS/include/FreeRTOSConfig.h
#define configMAX_PRIORITIES ( 5 ) // 預設是5
```
:::