Ở trong các bài viết trước, chúng ta đã tìm hiểu về tệp âm thanh .wav và cách để phát được âm thanh bằng PWM hoặc DAC. Đồng thời, chúng ta cũng đã tìm hiểu cách giao tiếp với thẻ nhớ SD qua giao thức SPI. Để có thể thực hiện được phát âm thanh từ tệp .wav trong thẻ nhớ thì các bạn cần đọc qua hai bài viết đó để có thể dễ dàng thực hiện hơn. Link bài viết các bạn có thể truy cập ở đây:
Phần 1: Phát âm thanh bằng PWM và DAC. Xem tại đây.
Phần 2: Giao tiếp thẻ nhớ SD Card bằng SPI và sử dụng hệ thống FATFS. Xem tại đây.
Phần 3. Phát âm thanh từ file .wav trên thẻ nhớ
Tiếp tục với phần 3, mình sẽ hướng dẫn các bạn sử dụng DAC để phát âm thanh. Tệp âm thanh mình sử dụng là .wav 8bit, 16kHz. Tất cả các cấu hình hoàn toàn giống với cấu hình mình đã thực hiện ở hai bài viết trước đó. Các bạn có thể theo dõi kỹ hơn, còn trong bài viết này mình sẽ cấu hình mà không giải thích lại nữa nhé!
Bước 1: Tạo mới project, chọn vi điều khiển bạn sử dụng có DAC. Vào System Core, chọn SYS, chọn Serial Wire.
Bước 2: Ở thẻ Clock Configuration, chọn tần số xung clock HCLK là 72MHz.
Bước 3: Cấu hình DAC, như hình vẽ. Chú ý, bật Timer 6 Trigger Out event.
Cấu hình bật DMA của DAC.
Bước 4: Cấu hình TIM6, tần số lấy mẫu âm thanh của chúng ta là 16KHz, nên mình sẽ cấu hình Timer 6 như sau:
- Tích vào Activated
- Prescaler: 36 – 1
- Counter Period: 125 – 1
- Triger Envent Selection chọn Update Event
Bước 5: Cấu hình SPI1 ở chế độ Full-Duplex Master
- Data Size: 8 bit
- Prescaler: 16
- Các thông số còn lại giữ nguyên
Bước 6: Sử dụng chân PB0 làm chân GPIO Output, kết nối với chân CS của module thẻ nhớ.
Bước 7: Vào Middleware, cấu hình FATFS, tick vào ô User-defined.
- USE_LFN: chọn Enable with static working buffer on the BSS
- MAX_SS: 4096
- Còn lại giữ mặc định
Bước 8: Sử dụng nút nhấn PC13 với chức năng ngắt ngoài. Các bạn sử dụng nút nhấn tương ứng trên board của các bạn.
Bước 9: Nhấn vào biểu tượng sinh code.
Bước 10: Các bạn vào Core > Src mở file stm32f3xx_it.c và thêm các phần mã code như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/* USER CODE BEGIN TD */ extern uint16_t Timer1, Timer2; /* USER CODE END TD */ … void SysTick_Handler(void) { /* USER CODE BEGIN SysTick_IRQn 0 */ if(Timer1 > 0) Timer1--; if(Timer2 > 0) Timer2--; /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ HAL_SYSTICK_IRQHandler(); /* USER CODE END SysTick_IRQn 1 */ } |
Bước 11: Trong file fatfs_sd.h, các bạn kiểm tra lại chân IO mà các bạn đã khai báo dùng để kết nối với chân CS của module thẻ nhớ đã đúng chưa, nếu chưa đúng thì sửa lại.
1 2 3 4 |
extern SPI_HandleTypeDef hspi1; #define HSPI_SDCARD &hspi1 #define D_CS_PORT GPIOB #define S_CS_PIN GPIO_PIN_0 |
Bước 12: Trong file fatfs_sd.c, các bạn thêm thư viện sau tương ứng với vi điều khiển mà bạn đang dùng, nếu bạn dùng F1 hay F4 thì chỉ cần sửa lại là được, ở đây mình sử dụng F3.
1 |
#include "stm32f3xx_hal.h" |
OKE! Như vậy là chúng ta đã xong các bước cấu hình và khởi tạo ban đầu cho project. Bây giờ chúng ta sẽ cùng xây dựng một hàm có chức năng phát âm thanh đơn giản nha.
Đầu tiên mình sẽ khai báo một bộ đệm để lưu trữ dữ liệu đọc từ thẻ nhớ. Kích thước bộ đệm mình sẽ định nghĩa nó để dễ dàng sửa đổi. Đồng thời, mình khai báo thêm hai biến để thao tác với tệp.
1 2 3 4 |
#define BUF_LEN 8192 uint16_t bufSize = 0; // kich thuoc du lieu doc ra uint32_t addStep = 0; // vi tri du lieu dang doc ra tren toan bo kich thuoc cua tep uint8_t buf[BUF_LEN] = {0}; |
Và sau đây là hàm dùng để phát âm thanh. Tham số truyền vào là tên của tệp tin với đuôi là .wav (cũng có thể là .txt nếu các bạn đổi định dạng từ .wav sang .txt).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
FRESULT playAudio(char* name) { //mount the SD f_mount(&fatfs, "", 0); //mở file if(f_open(&myFile, name, FA_READ) != FR_OK) Error_Handler(); else { //lấy kích thước của file uint32_t f_size = myFile.fsize; memset(buf, 0, BUF_LEN); do { /* nếu tệp âm thanh của bạn nhỏ hơn kích thước bộ đệm thì cho bufSize = f_size ngược lại bằng BUF_LEN. Chú ý bufSize là số lượng byte mà bạn đọc ra */ if(f_size < BUF_LEN) bufSize = f_size; else bufSize = BUF_LEN; // di chuyển vị trí con trỏ đọc đến đầu vị trí cần đọc dữ liệu f_lseek(&myFile, addStep); f_read(&myFile, buf, bufSize, (UINT *)&br); //bật timer và bắt đầu truyền dữ liệu qua DAC HAL_TIM_Base_Start(&htim6); HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t *) buf, bufSize, DAC_ALIGN_8B_R); while(HAL_DAC_GetState(&hdac1) != HAL_DAC_STATE_READY); //giảm kích thước file đi bufSize (byte) và tăng vị trí con trỏ đọc f_size -= bufSize; addStep += bufSize; } while(f_size > 0); addStep = 0; // reset vị trí con trỏ đọc f_lseek(&myFile, addStep); // đặt lại vị trí con trỏ đọc về 0 //dừng timer và DAC HAL_TIM_Base_Stop(&htim6); HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1); //đóng file f_close(&myFile); } f_mount(&fatfs, "", 1); return 0; } |
Dữ liệu đọc ra từ thẻ nhớ sẽ được lưu vào bộ đệm buf. Nếu kích thước của tệp nhỏ hơn kích thước bộ đệm thì đọc toàn bộ dữ liệu ra. Nếu kích thước của tệp lớn hơn thì chúng ta phải chia nhỏ ra để đọc, mỗi lần đọc ra số lượng byte sẽ bằng BUF_LEN. Dữ liệu trong bộ đệm sẽ được phát ra bằng DAC (DMA).
Sau mỗi lần phát hết một lượng dữ liệu, phải thực hiện lại quá trình copy lại dữ liệu và phát ra cho đến khi toàn bộ tệp được đọc ra hết. Để làm được, kích thước tệp phải giảm đi bằng cách lấy kích thước tổng trừ đi lượng dữ liệu đã đọc, và vị trí con trỏ đọc phải tăng lên. Cứ như thế dữ liệu sẽ được đọc hết. Sau khi tệp đã được đọc hết và phát hoàn thành, cần phải đặt lại giá trị của con trỏ để lần phát tiếp theo không bị lỗi. Ngoài ra, mình sẽ sử dụng một nút nhấn, để khi nhất nút thì thiết bị bắt đầu phát nhạc.
Mã chương trình:
File main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
#include "main.h" #include "fatfs.h" /* USER CODE BEGIN Includes */ #include "string.h" #include "stdio.h" #include "fatfs_sd.h" /* USER CODE END Includes */ /* USER CODE BEGIN PD */ #define BUF_LEN 8192 /* USER CODE END PD */ /* Private variables ---------------------------------------------------------*/ DAC_HandleTypeDef hdac1; DMA_HandleTypeDef hdma_dac1_ch1; SPI_HandleTypeDef hspi1; TIM_HandleTypeDef htim6; /* USER CODE BEGIN PV */ ////////fatfs - sd card variables///// FATFS fatfs, *fs; FIL myFile, fil; FRESULT res; UINT br; uint16_t bufSize = 0; // kich thuoc du lieu doc ra uint32_t addStep = 0; // vi tri du lieu dang doc ra tren toan bo kich thuoc cua tep uint8_t buf[BUF_LEN] = {0}; uint8_t playFlag = 0; /* USER CODE END PV */ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_SPI1_Init(void); static void MX_DAC1_Init(void); static void MX_TIM6_Init(void); FRESULT playAudio(char* name) { //mount the SD f_mount(&fatfs, "", 0); //mở file if(f_open(&myFile, name, FA_READ) != FR_OK) Error_Handler(); else { //lấy kích thước của file uint32_t f_size = myFile.fsize; memset(buf, 0, BUF_LEN); do { /* nếu tệp âm thanh của bạn nhỏ hơn kích thước bộ đệm thì cho bufSize = f_size ngược lại bằng BUF_LEN. Chú ý bufSize là số lượng byte mà bạn đọc ra */ if(f_size < BUF_LEN) bufSize = f_size; else bufSize = BUF_LEN; // di chuyển vị trí con trỏ đọc đến đầu vị trí cần đọc dữ liệu f_lseek(&myFile, addStep); f_read(&myFile, buf, bufSize, (UINT *)&br); //bật timer và bắt đầu truyền dữ liệu qua DAC HAL_TIM_Base_Start(&htim6); HAL_DAC_Start_DMA(&hdac1, DAC_CHANNEL_1, (uint32_t *) buf, bufSize, DAC_ALIGN_8B_R); while(HAL_DAC_GetState(&hdac1) != HAL_DAC_STATE_READY); //giảm kích thước file đi bufSize (byte) và tăng vị trí con trỏ đọc f_size -= bufSize; addStep += bufSize; } while(f_size > 0); addStep = 0; // reset vị trí con trỏ đọc f_lseek(&myFile, addStep); // đặt lại vị trí con trỏ đọc về 0 //dừng timer và DAC HAL_TIM_Base_Stop(&htim6); HAL_DAC_Stop_DMA(&hdac1, DAC_CHANNEL_1); //đóng file f_close(&myFile); } f_mount(&fatfs, "", 1); return 0; } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin == GPIO_PIN_13) { playFlag = !playFlag; for(int i = 0; i < 50000; i++); while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13) == 0); for(int i = 0; i < 50000; i++); EXTI -> PR = GPIO_Pin; } } int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_SPI1_Init(); MX_FATFS_Init(); MX_DAC1_Init(); MX_TIM6_Init(); while (1) { /* USER CODE BEGIN 3 */ if(playFlag == 1) { playAudio("tapit.wav"); } } /* USER CODE END 3 */ } |
Vậy mình đã hướng dẫn cho các bạn cách đọc file .wav và phát âm thanh từ thẻ nhớ. Đây là đề tài mà nhóm mình khá tâm huyết trong quá trình lên ý tưởng, thực hiện và hoàn thiện sản phẩm. Mình hy những chia sẻ này sẽ mang lại nhiều hữu ích đến các bạn. Chúc các bạn thành công!
Giới thiệu đến các bạn video Demo của nhóm thực tập.
Nếu bạn quan tâm đến các chủ đề liên quan, hãy nhấn like ? và subcribe ? tại kênh youtube của Cộng đồng kỹ thuật TAPIT để nhận thông báo về những video với các nội dung liên quan nhé!
Nhóm thực tập tại Cộng đồng Kỹ thuật TAPIT
Hoàng Văn Bình, Nguyễn Quang Linh