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; } ``` 結果如下 ![](https://i.imgur.com/Q75hafn.png) [參考資料](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卡),的話會出現以下畫面。 ![](https://i.imgur.com/GQhRrQB.png) ### 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上。 耳機不能是有麥克風功能的(三環),有的話要轉接。