本篇文章探討Raspberry Pi Pico 使用PIO功能,模擬I2S界面,連接MAX98357A DAC播放Wave聲音檔。 開發界面使用VSCode + C-SDK。
一、PIO與MAX98357A I2S界面對應
根據MAX98357A datasheet可支援16、24、32bit, 8kHz, 16kHz, 32kHz, 44.1kHz,
48kHz, 88.2kHz取樣速率。分成左右兩聲道。
若是檔案為單聲道時,則左右聲道播放相同內容。
根據Datasheet 16與32 bit的時間脈序如下圖所示,因此24bit時為後補0成為32bit再送出給MAX98357A。
根據以上分析,PIO 程式碼分成mono與streo兩段,分別處理。
- 使用pio side-set pin 輸出LRCLK與BCLK。 bit0:BCLK, bit1:LRCLK
- Mono audio
根據上圖每輸出1bit,pio StateMachine使用4 clock cycle。 因為register ISR未使用,因此可用來當scratch register用,第9行先將OSR存在ISR中,第19行相同內容輸出到右聲道。單聲道時autopull=false。
- Stereo audio
PIO 程式開始時將bits per sample存到scratch register y。
二、SDCard code:
使用no-OS-FatFS-SD-SPI-RPi-Pico程式碼。
三、PIO DMA:
將檔案讀取的資料透過DMA直接送PIO StateMachin TX_FIFO中:
四、成果影片
五、原始程式碼:
1. PIO
;===== mono ====== ; 4 pio clock cycles for 1 bit .program i2s_pio_dma_mono .side_set 2 loop1: out pins, 1 side 0b00 [1] jmp x--, loop1 side 0b01 [1] out pins, 1 side 0b10 mov osr, isr side 0b10 ;isr as scratch register, the same output for right channel mov x, y side 0b11 [1] loop2: out pins, 1 side 0b10 [1] jmp x--, loop2 side 0b11 [1] out pins, 1 side 0b00 public entry_point_m: pull side 0b00 mov isr, osr side 0b01 mov x,y side 0b01 ;===== stereo ====== ; 2 pio clock cycles for 1 bit .program i2s_pio_dma_stereo .side_set 2 loop1: out pins, 1 side 0b00 jmp x--, loop1 side 0b01 out pins, 1 side 0b10 mov x, y side 0b11 loop2: out pins, 1 side 0b10 jmp x--, loop2 side 0b11 out pins, 1 side 0b00 public entry_point_s: mov x,y side 0b01
2. main.c
#include <stdio.h> #include "pico/stdlib.h" #include "hardware/dma.h" #include "hardware/pio.h" #include "sd_card.h" #include "ff.h" #include "pio_dma.h" #include "hardware/clocks.h" uint8_t f_buf0[4010], f_buf1[4010]; //double buffer uint8_t *file_ptr = f_buf0; uint8_t *fifo_ptr = f_buf1; uint8_t *temp_ptr=NULL; bool read_flag=true; uint byte_trans_count=0; uint MAX_BYTE_TRANS=4000; struct i2s_pio_dma_param_t { //for pio pio_program_t program; uint8_t bitsPerSample; uint8_t offset; uint8_t entry_point; uint8_t bitInsCycles; // 4 pio clock cycles for mono, 2 pio clocks for stereo //for dma int chan_num; uint8_t dma_size; } i2s_pio_dma_param; struct wave_file_header_t { char blockID[4]; uint32_t totalSize; char typeHeader[4]; char fmt[4]; uint32_t headerLen; uint16_t typeOfFormat; uint16_t numbeOfChannel; uint32_t sampleRate; uint32_t byteRate; uint16_t blockAlign; uint16_t bitsPerSample; char dataHeader[4]; uint32_t dataSize; } wave_file_header; void trans_wav(); void i2s_pio_init(PIO pio, uint sm, uint sideset_base, uint out_base, uint numberOfChannels, uint bitsPerSample, uint freq) { uint offset = 0; pio_sm_config c; if (numberOfChannels == 1) { offset = pio_add_program(pio, &i2s_pio_dma_mono_program); c = i2s_pio_dma_mono_program_get_default_config(offset); i2s_pio_dma_param.program = i2s_pio_dma_mono_program; i2s_pio_dma_param.entry_point = i2s_pio_dma_mono_offset_entry_point_m; sm_config_set_out_shift(&c, false, false, bitsPerSample); } else { offset = pio_add_program(pio, &i2s_pio_dma_stereo_program); c = i2s_pio_dma_stereo_program_get_default_config(offset); i2s_pio_dma_param.program = i2s_pio_dma_stereo_program; i2s_pio_dma_param.entry_point = i2s_pio_dma_stereo_offset_entry_point_s; sm_config_set_out_shift(&c, false, true, bitsPerSample); } i2s_pio_dma_param.offset = offset; i2s_pio_dma_param.bitsPerSample = bitsPerSample; pio_gpio_init(pio, out_base); pio_gpio_init(pio, sideset_base); pio_gpio_init(pio, sideset_base+1); pio_sm_set_consecutive_pindirs(pio, sm, sideset_base, 2, true); pio_sm_set_consecutive_pindirs(pio, sm, out_base, 1, true); sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); sm_config_set_out_pins(&c, out_base, 1); sm_config_set_sideset_pins(&c, sideset_base); float div = clock_get_hz(clk_sys)/freq; sm_config_set_clkdiv(&c, div); pio_sm_init(pio, sm, offset, &c); } void i2s_pio_enable(PIO pio, uint sm) { dma_channel_set_irq1_enabled(i2s_pio_dma_param.chan_num, true); pio_sm_set_enabled(pio, sm, true); pio_sm_exec(pio, sm, pio_encode_set(pio_y, i2s_pio_dma_param.bitsPerSample-2)); pio_sm_exec(pio, sm, pio_encode_jmp(i2s_pio_dma_param.offset+i2s_pio_dma_param.entry_point)); } void i2s_reset(PIO pio, uint sm) { pio_sm_drain_tx_fifo(pio, sm); pio_sm_set_enabled(pio, sm, false); pio_remove_program(pio, &i2s_pio_dma_param.program, i2s_pio_dma_param.offset); pio_clear_instruction_memory(pio); dma_channel_unclaim(i2s_pio_dma_param.chan_num); irq_set_enabled(DMA_IRQ_1,false); irq_remove_handler(DMA_IRQ_1, trans_wav); dma_channel_set_irq1_enabled(i2s_pio_dma_param.chan_num, false); } void i2s_pio_dma_init(PIO pio, uint sm) { i2s_pio_dma_param.chan_num = dma_claim_unused_channel(true); dma_channel_config c = dma_channel_get_default_config(i2s_pio_dma_param.chan_num); channel_config_set_write_increment(&c, false); channel_config_set_read_increment(&c, true); channel_config_set_dreq(&c, pio_get_dreq(pio, sm, true)); channel_config_set_transfer_data_size(&c, i2s_pio_dma_param.dma_size); //DMA_SIZE_8,16,32 dma_channel_acknowledge_irq1(i2s_pio_dma_param.chan_num); // clear channel interrupt(ints1) dma_channel_set_irq1_enabled(i2s_pio_dma_param.chan_num, true); // enable irq1 (inte1) irq_add_shared_handler(DMA_IRQ_1, trans_wav, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); irq_set_enabled(DMA_IRQ_1, true); dma_channel_configure(i2s_pio_dma_param.chan_num, &c, (void*) (PIO0_BASE+PIO_TXF0_OFFSET), fifo_ptr, MAX_BYTE_TRANS>> i2s_pio_dma_param.dma_size, false); //DMA_SIZE_8 or 16 or 32 } void trans_wav() { if (dma_channel_get_irq1_status(i2s_pio_dma_param.chan_num)) { dma_channel_acknowledge_irq1(i2s_pio_dma_param.chan_num); temp_ptr = file_ptr; file_ptr=fifo_ptr; fifo_ptr=temp_ptr; dma_channel_set_trans_count(i2s_pio_dma_param.chan_num, byte_trans_count >> i2s_pio_dma_param.dma_size, false); dma_channel_set_read_addr(i2s_pio_dma_param.chan_num, fifo_ptr,true); read_flag=true; if (byte_trans_count < MAX_BYTE_TRANS) { dma_channel_set_irq1_enabled(i2s_pio_dma_param.chan_num, false); } } } void i2s_init() { i2s_pio_dma_param.bitInsCycles=2; if (wave_file_header.numbeOfChannel == 1) i2s_pio_dma_param.bitInsCycles=4; uint freq = wave_file_header.sampleRate*i2s_pio_dma_param.bitInsCycles*wave_file_header.bitsPerSample*2; i2s_pio_dma_param.dma_size = wave_file_header.bitsPerSample >> 4; // DMA_SIZE_8, 16, 32 i2s_pio_dma_init(pio0, 0); i2s_pio_init(pio0, 0, 16, 18, wave_file_header.numbeOfChannel,wave_file_header.bitsPerSample, freq); i2s_pio_enable(pio0, 0); } void playWave(char* fn) { printf("%s\n", fn); FRESULT fr; uint br; FIL pfile; fr = f_open(&pfile, fn, FA_READ); if (fr != FR_OK) { printf("open file error\n"); return; } f_read(&pfile, &wave_file_header, 44, &br); if (fr != FR_OK) { printf("open file error\n"); return; } i2s_init(); f_read(&pfile, fifo_ptr, MAX_BYTE_TRANS, &byte_trans_count); read_flag=true; dma_channel_set_trans_count(i2s_pio_dma_param.chan_num, byte_trans_count >> i2s_pio_dma_param.dma_size, false); dma_channel_set_read_addr(i2s_pio_dma_param.chan_num, fifo_ptr,true); while (1) { if (read_flag) { read_flag=false; f_read(&pfile, file_ptr, MAX_BYTE_TRANS, &byte_trans_count); if (byte_trans_count < MAX_BYTE_TRANS) { f_close(&pfile); i2s_reset(pio0,0); break; } } } } int main() { FRESULT fr; FATFS fs; stdio_init_all(); if (!sd_init_driver()) { printf("SD Initial Error\n"); return 0; } fr = f_mount(&fs, "0:", 1); if (fr != FR_OK) { printf("bool read_flag=true;mount error\n"); return 0; } playWave(demo1.wav"); playWave("demo2.wav"); while(true) { tight_loop_contents(); } return 0; }
3. CMakeLists.txt
# Generated Cmake Pico project file cmake_minimum_required(VERSION 3.13) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) # Initialise pico_sdk from installed location # (note this can come from environment, CMake cache etc) set(PICO_SDK_PATH "/your-path/pico/pico-sdk") set(PICO_BOARD pico CACHE STRING "Board type") # Pull in Raspberry Pi Pico SDK (must be before project) include(pico_sdk_import.cmake) if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") endif() project(pio_dma C CXX ASM) # Initialise the Raspberry Pi Pico SDK pico_sdk_init() # Add executable. Default name is the project name, version 0.1 add_executable(pio_dma main.c ) pico_set_program_name(pio_dma "pio_dma") pico_set_program_version(pio_dma "0.1") pico_enable_stdio_uart(pio_dma 1) pico_enable_stdio_usb(pio_dma 0) # Add the standard library to the build target_link_libraries(pio_dma pico_stdlib) # Add the standard include files to the build target_include_directories(pio_dma PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required ) # Add any user requested libraries target_link_libraries(pio_dma hardware_dma hardware_pio FatFs_SPI ) # Tell CMake where to find other source code add_subdirectory(FatFs_SPI build) pico_add_extra_outputs(pio_dma) # Enable usb output, disable uart output pico_enable_stdio_usb(${PROJECT_NAME} 0) pico_enable_stdio_uart(${PROJECT_NAME} 1)
Hi, this is very useful. Can you provide the git repository for the code? I am unable to replicate the code using the information provided in the blog. It would be really helpful if you could provide the repository link.
回覆刪除