prettyprint

2022年11月9日 星期三

Raspberry Pi Pico PIO(Programmable IO) Episode 4: I2S audio (Play WAVE File In SDCard from MAX98357A DAC)

本篇文章探討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

每輸出1bit,pio StateMachine 使用2 clock cycles,使用autopull=true


 PIO 程式開始時將bits per sample存到scratch register y。

 
PIO StateMachine clock frequency = 取樣速率*(每個bit的clock cycle數)*取樣位元數*2
註:   2: 為左右聲道



二、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)


沒有留言:

張貼留言