2021 嵌入式作業系統分析與實做 Lab5 report
===
[Lab5](https://docs.google.com/presentation/d/1DXcgOGfFjXAtjCBJNEZ9rX9eH_8PEqDY9kw91_gjlpg/edit#slide=id.gd7a91f09cc_0_99)
contribute by `Liao712148`
###### tags: `FreeRTOS`
### Lab要求
* Step1:
* Import the project
* Step2:
* Implement printf(lab5_0)
* 修改printf.h使得專案可以直接使用printf輸出結果到console(uart tx)
* Step3:
* Install the sd card device to developement board(lab5_1)
* Step4:
* Read and write file by multi-task(lab5_2)
* Step5:
* Playback the wav file to DAC(lab5_3, lab5_4)
* 請disable掉task1與task2 並且enable task3
* 請在task3中加入適當的函式 使得wav檔案可以順利播放(都在waveplayer.c中)(lab5_3)
* 請放入多個wav檔案 並且利用藍色的push button實做按下時 可以播放下一首歌(lab5_4)
### function介紹
FRESULT f_open (FIL* FileObject, const XCHAR* FileName, BYTE ModeFlags );
引數:
* FileObject :將被建立的檔案物件結構的指標。
* XCHAR :要被操作(打開或建立)的檔案名稱。
* ModeFlags :描述被打開後的檔案,應該要如何操作。
* FA_READ :指定讀訪物件,可以從檔案中讀取資料。
* FA_WRITE :從buffer中打資料寫入該檔案。
* FA_OPEN_EXISTING :開啟檔案,如果檔案不存在則開啟失敗。
* FA_OPEN_ALWAYS :如果檔案存在則開啟,否則創立檔案。
* FA_CREATE_NEW :創立一個新的檔案,如果該檔案已經存在了則建立失敗。
* FA_CREATE_ALWAYS :創立一個新的檔案,如果檔案已經存在就將其覆蓋。
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw )
引數:
* fp :要被操作(寫入)的檔案。
* buff :把要寫入的資料放在buffery,再從buffer寫入fp。
* btw :要寫入檔案的資料數目。
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br)
引數:
* fp :要被操作(讀取)的檔案。
* buff :把檔案中的資料讀取到buffer中。
* btr :要從檔案中讀取的資料數目。
FRESULT f_close (FIL* FileObject)
引數:
* FileObject :把檔案關閉。
#### Step 1:
一般要開發使用像 `STM32` 這類的單片機,如果要做 Debug 將訊息往外輸出 最常用的就是 UART 串口。根據LAB3 , LAB4都是利用`HAL_UART_Transmit()`將字串傳送,在透過USB to TTL的RX與開發板的TX相連。這樣就可以透過console,即可監看要輸出的資料。所以根據此概念,因此將改寫printf.h中的內容
```cpp=
/**
* @brief Retargets the C library printf function to the USART.
* @param None
* @retval None
*/
PUTCHAR_PROTOTYPE
{
/* Place your implementation of fputc here */
/* e.g. write a character to the EVAL_COM1 and Loop until the end of transmission */
HAL_UART_Transmit(&huart4, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
```
結果如下

[參考資料](http://www.makdev.net/2018/06/printf-uart.html)
#### Step 2:
**請接板子後面的針腳 接正面容易鬆脫**
* pe4請接到sd卡板子的cs
* ttl的gnd與sd卡板子的gnd接到開發板gnd
* sd卡板子的vcc接到開發板5v
* sd卡板子的sck接到開發板的PA5
* sd卡板子的MISO接到開發板的PA6
* sd卡板子的MOSI接到開發板的PA7
* ttl的RX接到開發板的TX
#### Step 3:
Task1 是負責將buffer中的資料寫入到檔案(TEST.TXT)中,而Task2 是負責將檔案中(TEST.TXT)的資料讀入到buffer中,所以如果一個task在使用檔案資料時,也有另外一個task要對該檔案的內容做操作,就會發生錯誤。
所以在某個task在操作檔案時,會希望其他task不能對其檔案做修改,因此可以用`taskENTER_CRITICAL()`,`taskEXIT_CRITICAL()`,來讓task是讀寫檔案時不會被其他task打斷。也可以用`mutex()`,`vTaskSuspendAll()`,`xTaskResumeAll()`,來確保使用檔案這個動作是`atmoic`。
```cpp=
void Task1(void *pvParameters) {
uint8_t count=0;
for (;;) {
FIL test;
if ( xSemaphoreTake(xMutex, 0xffff) == pdTRUE ) {
if (f_open(&test, "TEST.TXT", FA_WRITE) != 0) {
printf("open file err in task1\r\n");
} else {
printf("file open ok in task1\r\n");
}
char buff[13];
memset(buff, 0, 13);
if(count%2){
sprintf(buff, "%s", "DataWriteT");
}else{
sprintf(buff, "%s", "DataWriteF");
}
UINT byteswrite;
f_write(&test, buff, 12, &byteswrite);
f_close(&test);
}
xSemaphoreGive(xMutex);
count++;
vTaskDelay(1);
}
}
```
```cpp=
void Task2(void *pvParameters) {
while (1) {
FIL test_1;
if ( xSemaphoreTake(xMutex, 0xffff) == pdTRUE ) {
if (f_open(&test_1, "TEST.TXT", FA_READ) != 0) {
printf("open file err in task2\r\n");
} else {
printf("file open ok in task2\r\n");
}
char buff[11];
memset(buff, 0, 11);
UINT bytesread;
f_read(&test_1, buff, 11, &bytesread);
printf("data read is %s in task 2\r\n", buff);
f_close(&test_1);
}
xSemaphoreGive(xMutex);
vTaskDelay(1);
}
}
```
有把critical section保護好的話(一次只有一個task去讀取SD卡),的話會出現以下畫面。

### critical section , mutex, vTaskSuspendAll 比較
critical section : 只能用在同一個process下產生的不同的threads,不能在process之間使用,Critical Section本身不是核心物件,相關函式的呼叫一般都在使用者模式內執行,執行時間較快。可以視為是light weight mutex。如果有設立`configMAX_SYSCALL_INTERRUPT_PRIORITY`,會把priority低於 `configMAX_SYSCALL_INTERRUPT_PRIORITY`的interrupt給disable掉。context switch也是受interrupt trigger,所以在cirtical section 中的task不會被preemptive掉。
mutex : 可以在不同的processs之間使用,Mutex 是核心物件,相關函式的執行需要使用者模式(User Mode)到核心模式(Kernel Mode)的轉換,需要用到system call。所以叫耗費時間。也有priority inheritence的機制可以盡量避免priority inversion。
vTaskSuspendAll : 會suspend scheduler 所以之後就不會有context switcht產生。但是不會disable interrupt 所以依舊有可能跳去執行ISR,可是如過 interrupt是要去呼叫某個task,該task不會馬上被執行,要等到scheduler恢復後才會被調用。
[參考資料](https://stackoverflow.com/questions/800383/what-is-the-difference-between-mutex-and-critical-section)
[參考資料](https://lionrex.pixnet.net/blog/post/51358802)
[參考資料](https://www.itread01.com/content/1549795324.html)
#### Step 4:
```cpp=
void Task3(void *pvParameters) {
AUDIO_PLAYER_Start(0);
for (;;) {
AUDIO_PLAYER_Process(TRUE);
}
}
```
第`2`行 :參數是0表示會從檔案中第0個index開始播放。
第`4`行 :參數是TRUE表示當播完最後一個audoio file播完時會回到第一個audoio file開始播放。循環播放的意思
#### Step 5:
按下藍色的button會去trigger 一個 interrupt,而它的ISR會去修改AudioState,這個變數被定義在`waveplayer.c`中,是一個type為AUDIO_PLAYBACK_StateTypeDef的一個類似狀態機功能的變數,當中的狀態有AUDIO_STATE_NEXT(切換到下一個audio file),AUDIO_STATE_PREVIOUS(切換到上一個audio file),AUDIO_STATE_STOP(停止),AUDIO_STATE_RESUME(恢復),...,狀態。根據lab要求,我們會希望在按下blue buttom時會去切換到下一首。所以會透過ISR將AudioState的狀態修改為AUDIO_STATE_NEXT。
```cpp=
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
BaseType_t xHigherPriorityTaskWoken;
if (GPIO_Pin == GPIO_PIN_0) {
xHigherPriorityTaskWoken = pdFALSE;
AudioState = AUDIO_STATE_NEXT;/*修改AudioState的狀態*/
if (flag == pdTRUE) {
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
} else {
//do nothing
}
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_14);
uint8_t data_1 = 0x5f | 0x80;
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &data_1, 1, 10);
//// HAL_Delay(10);
HAL_SPI_Receive(&hspi1, &data_1, 1, 10);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_3, GPIO_PIN_SET);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
```
#### 心得
謝謝助教的幫忙。
SD卡常常讀不到,很擔心是不是做錯。從新插進SD卡板,==有可能==就可以讀的到。
記得耳機要插在開發板上,而不是PC上。
耳機不能是有麥克風功能的(三環),有的話要轉接。