(十四)零基础开发小安派-Eyes-S1【外设篇】——I2S

[复制链接]
查看3173 | 回复19 | 2023-11-23 19:59:41 | 显示全部楼层 |阅读模式

本帖最后由 Ai-Thinker小泽 于 2023-11-24 11:36 编辑

零基础开发小安派-Eyes-S1【外设篇】——I2S

I2S(Inter-IC Sound)是一种广泛应用于数字音频传输的串行接口标准。它最初由Philips开发,用于解决在集成电路之间传输音频数据的问题。I2S协议定义了音频数据的传输格式、时序和控制信号。在I2S只能同时存在一个主设备和发送设备,主设备可以是发送设备也可以是接收设备,提供BCK和FS的设备为主设备。

一、了解小安派-Eyes-S1的I2S

特点: 支持主模式和从模式 支持多种协议(Normal I2S、Left-Justified、Right-Justified、PCM、TDM/TDM64) 支持单/双声道,在TDM模式下支持四声道/六声道 支持 8/11.025/16/22.05/32/44.1/48/96/192 KHz 采样率

1.struct bflb_i2c_config_s

说明:i2s配置的结构体。

struct bflb_i2s_config_s {
uint32_t bclk_freq_hz;
uint8_t role;
uint8_t format_mode;
uint8_t channel_mode;
uint8_t frame_width;
uint8_t data_width;
uint8_t fs_offset_cycle;

uint8_t tx_fifo_threshold;
uint8_t rx_fifo_threshold;
};
parameter description
bclk_freq_hz I2S比特率,比特率=采样率帧宽度(每个通道位数)通道数
role 主或从
format_mode 格式模式
channel_mode 通道模式
frame_width 帧宽度
data_width 数据宽度,在3/4/6通道模式下,帧宽度与数据宽度必须一致
fs_offset_cycle 首位数据偏移
tx_fifo_threshold 发送通道阈值(小于16)
rx_fifo_threshold 接收通道阈值(小于16)

role可以为下列参数:

#define I2S_ROLE_MASTER 0
#define I2S_ROLE_SLAVE  1

format_mode可以为下列参数:

#define I2S_MODE_LEFT_JUSTIFIED       0 /* 左对齐或Philips标准 */
#define I2S_MODE_RIGHT_JUSTIFIED      1 /* 右对齐 */
#define I2S_MODE_DSP_SHORT_FRAME_SYNC 2 /* DSP模式A/B短帧同步 */
#define I2S_MODE_DSP_LONG_FRAME_SYNC  3 /* DSP模式A/B长帧同步 */

channel_mode可以为下列参数:

#define I2S_CHANNEL_MODE_NUM_1 0
#define I2S_CHANNEL_MODE_NUM_2 1
#define I2S_CHANNEL_MODE_NUM_3 2 /* 仅DSP模式,帧宽度与数据宽度必须一致 */
#define I2S_CHANNEL_MODE_NUM_4 3 /* 仅DSP模式,帧宽度与数据宽度必须一致 */
#define I2S_CHANNEL_MODE_NUM_6 4 /* 仅DSP模式,帧宽度与数据宽度必须一致 */

frame_width和data_width可以为下列参数:

#define I2S_SLOT_WIDTH_8  0
#define I2S_SLOT_WIDTH_16 1
#define I2S_SLOT_WIDTH_24 2
#define I2S_SLOT_WIDTH_32 3

2.bflb_i2s_init

说明:I2S初始化。

void bflb_i2s_init(struct bflb_device_s *dev, const struct bflb_i2s_config_s *config);
parameter description
dev 设备句柄
config 配置项

3.bflb_i2s_deinit

说明:I2S逆初始化。

void bflb_i2s_deinit(struct bflb_device_s *dev);
parameter description
dev 设备句柄

4.bflb_i2s_link_txdma

说明:I2S RX DMA 使能开关

void bflb_i2s_link_txdma(struct bflb_device_s *dev, bool enable);
parameter description
dev 设备句柄
enable 是否使能DMA

5.bflb_i2s_link_rxdma

说明:I2S TX DMA使能开关

void bflb_i2s_link_rxdma(struct bflb_device_s *dev, bool enable);
parameter description
dev 设备句柄
enable 是否使能DMA

6.bflb_i2s_txint_mask

说明:I2S TX fifo 阈值中断屏蔽开关,开启后超过设定阈值则触发中断。

void bflb_i2s_txint_mask(struct bflb_device_s *dev, bool mask);
parameter description
dev 设备句柄
mask 是否屏蔽中断

7.bflb_i2s_rxint_mask

说明:I2S RX fifo 阈值中断屏蔽开关,开启后超过设定阈值则触发中断。

void bflb_i2s_rxint_mask(struct bflb_device_s *dev, bool mask);
parameter description
dev 设备句柄
mask 是否屏蔽中断

8.bflb_i2s_errint_mask

说明:I2S错误中断屏蔽开关。

void bflb_i2s_errint_mask(struct bflb_device_s *dev, bool mask);
parameter description
dev 设备句柄
mask 是否屏蔽中断

9.bflb_i2s_get_intstatus

说明:获取 I2S中断标志。

uint32_t bflb_i2s_get_intstatus(struct bflb_device_s *dev);
parameter description
dev 设备句柄
retrun 中断标志

返回的中断标志有以下选项:

#define I2S_INTSTS_TX_FIFO  (1 << 1)
#define I2S_INTSTS_RX_FIFO  (1 << 2)
#define I2S_INTSTS_FIFO_ERR (1 << 3)

10.bflb_i2s_feature_control

说明:控制I2S功能。

int bflb_i2s_feature_control(struct bflb_device_s *dev, int cmd, size_t arg);
parameter description
dev 设备句柄
cmd I2S的命令
arg 用户数据

cmd可以为下列参数:

#define I2S_CMD_CLEAR_TX_FIFO    (0x01)
#define I2S_CMD_CLEAR_RX_FIFO    (0x02)
#define I2S_CMD_RX_DEGLITCH      (0x03)
#define I2S_CMD_DATA_ENABLE      (0x04)
#define I2S_CMD_CHANNEL_LR_MERGE (0x05)
#define I2S_CMD_CHANNEL_LR_EXCHG (0x06)
#define I2S_CMD_MUTE             (0x07)
#define I2S_CMD_BIT_REVERSE      (0x08)

arg可以为下列参数:

#define I2S_CMD_DATA_ENABLE_TX (1 << 1)
#define I2S_CMD_DATA_ENABLE_RX (1 << 2)

二、示例——I2S传输8388音频数据,实现边录音边播放

首先附上8388的芯片手册:8388芯片手册

其次博流在SDK里提供了8388的驱动库,我们可以直接使用,在使用移植其它的.C和.H文件时,可以参考如下的方法。

复制Project_basic工程,粘贴成为新的工程文件,将其修改成我们的工程名称,这里我的工程名是I2S_8388。 微信截图_20231123101842.png

1.移植驱动文件

在components下创建新的库文件夹,我命名为8388,将AiPi-OPEN/AiPi-Open-Kits/aithinker_Ai-M6X_SDK/examples/peripherals/i2s/i2s_es8388/例程下的bsp_es8388.c和bsp_es8388.h复制下来,放在我们的8388文件夹下。

微信截图_20231123102711.png

微信截图_20231123102827.png

在我们自己的8388文件夹下,修改bsp_es8388.c的253行,将注释的LOUT&ROUT一行取消注释,并注释ES8388_Write_Reg(0x04, 0x24),具体如下:

微信截图_20231123103531.png

2.修改CMakeLists.txt

添加刚刚我们的8388文件夹路径。 微信截图_20231123103922.png

这里需要注意的是,要链接一个脚本,添加下面的这一行。

sdk_set_linker_script($ENV{BL_SDK_BASE}/bsp/board/bl616dk/bl616_flash_old.ld)

微信截图_20231123104419.png

3.修改flash_prog_cfg.ini

将boot2_isp_mode置零,[FW]下的工程文件名修改。

微信截图_20231123110639.png

MAIN


#include "board.h"
#include "bflb_gpio.h"
#include "bflb_l1c.h"
#include "bflb_mtimer.h"

#include "bflb_i2c.h"

#include "bl616_glb.h"
#include "bflb_dma.h"
#include "bsp_es8388.h"
#include "bflb_i2s.h"
//头文件

struct bflb_device_s *i2s0;
struct bflb_device_s *dma0_ch0;
struct bflb_device_s *dma0_ch1;
//I2S外设句柄,DMA两个通道

ATTR_NOCACHE_RAM_SECTION uint8_t rx_buffer[32000];
//DMA缓冲数组

static ES8388_Cfg_Type ES8388Cfg = {
    .work_mode = ES8388_CODEC_MDOE,          /*!< ES8388 work mode */
    .role = ES8388_SLAVE,                    /*!< ES8388 role */
    .mic_input_mode = ES8388_DIFF_ENDED_MIC, /*!< ES8388 mic input mode */
    .mic_pga = ES8388_MIC_PGA_0DB,           /*!< ES8388 mic PGA */
    .i2s_frame = ES8388_LEFT_JUSTIFY_FRAME,  /*!< ES8388 I2S frame */
    .data_width = ES8388_DATA_LEN_16,        /*!< ES8388 I2S dataWitdh */
};
    /**
     * ES8388配置结构体{
     * 工作模式:编码器模式下工作
     * 角色:从机
     * 麦克风输入模式:麦克风不同输入模式
     * PGA增益:0DB
     * I2S帧:左对齐
     * 数据宽度:16位
     * }
     */


void dma0_ch0_isr(void *arg)
{
    printf("tx done\r\n");
}
//DMA通道0中断服务函数

void dma0_ch1_isr(void *arg)
{
    printf("rx done\r\n");
}
//DMA通道1中断服务函数

void i2s_gpio_init()
{
    struct bflb_device_s *gpio;

    gpio = bflb_device_get_by_name("gpio");

    /* I2S_FS 左右声道线,主机时输出,从机时输入*/
    bflb_gpio_init(gpio, GPIO_PIN_13, GPIO_FUNC_I2S | GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);

    /* I2S_DI 数据输入线*/
    bflb_gpio_init(gpio, GPIO_PIN_10, GPIO_FUNC_I2S | GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);

    /* I2S_DO 数据输出线*/
    bflb_gpio_init(gpio, GPIO_PIN_11, GPIO_FUNC_I2S | GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);

    /* I2S_BCLK 时钟线,主机时输出,从机时输入*/
    bflb_gpio_init(gpio, GPIO_PIN_20, GPIO_FUNC_I2S | GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);

    /* I2S_MCLK 主时钟输出线*/
    bflb_gpio_init(gpio, GPIO_PIN_14, GPIO_FUNC_CLKOUT | GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_1);

    /* I2C0_SCL */
    bflb_gpio_init(gpio, GPIO_PIN_0, GPIO_FUNC_I2C0 | GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_2);

    /* I2C0_SDA */
    bflb_gpio_init(gpio, GPIO_PIN_1, GPIO_FUNC_I2C0 | GPIO_ALTERNATE | GPIO_PULLUP | GPIO_SMT_EN | GPIO_DRV_2);

    //8388的初始化需要I2C来配置
}

void i2s_dma_init()
{
    static struct bflb_dma_channel_lli_pool_s tx_llipool[100];
    static struct bflb_dma_channel_lli_transfer_s tx_transfers[1];
    static struct bflb_dma_channel_lli_pool_s rx_llipool[100];
    static struct bflb_dma_channel_lli_transfer_s rx_transfers[1];
    //DMA支持lli模式,分配两个内存池,txllipool和rxllipool分别给通道0和通道1

    //在传输数据时需要填充transfer_s,这里定义两个transfer_s,一个作为发送,一个作为接收,后续需要填充内容有源地址、目标地址和长度

    struct bflb_i2s_config_s i2s_cfg = {
        .bclk_freq_hz = 16000 * 16 * 2, /* bclk = Sampling_rate * frame_width * channel_num */
        .role = I2S_ROLE_MASTER,
        .format_mode = I2S_MODE_LEFT_JUSTIFIED,
        .channel_mode = I2S_CHANNEL_MODE_NUM_2,
        .frame_width = I2S_SLOT_WIDTH_16,
        .data_width = I2S_SLOT_WIDTH_16,
        .fs_offset_cycle = 0,

        .tx_fifo_threshold = 0,
        .rx_fifo_threshold = 0,
    };
    //I2S的结构体配置

    struct bflb_dma_channel_config_s tx_config = {
        .direction = DMA_MEMORY_TO_PERIPH,
        .src_req = DMA_REQUEST_NONE,
        .dst_req = DMA_REQUEST_I2S_TX,
        .src_addr_inc = DMA_ADDR_INCREMENT_ENABLE,
        .dst_addr_inc = DMA_ADDR_INCREMENT_DISABLE,
        .src_burst_count = DMA_BURST_INCR1,
        .dst_burst_count = DMA_BURST_INCR1,
        .src_width = DMA_DATA_WIDTH_16BIT,
        .dst_width = DMA_DATA_WIDTH_16BIT,
    };
        /**
     * DMA配置结构体{
     * DMA传输方向:从内存到外设
     * DMA源请求:无
     * DMA目标请求:I2S_TX
     * DMA源地址自增:开
     * DMA目标地址自增:关
     * DMA源突发传输个数:0
     * DMA目标突发传输个数:0
     * DMA源地址位宽:16位
     * DMA目标地址位宽:16位
     */

    struct bflb_dma_channel_config_s rx_config = {
        .direction = DMA_PERIPH_TO_MEMORY,
        .src_req = DMA_REQUEST_I2S_RX,
        .dst_req = DMA_REQUEST_NONE,
        .src_addr_inc = DMA_ADDR_INCREMENT_DISABLE,
        .dst_addr_inc = DMA_ADDR_INCREMENT_ENABLE,
        .src_burst_count = DMA_BURST_INCR1,
        .dst_burst_count = DMA_BURST_INCR1,
        .src_width = DMA_DATA_WIDTH_16BIT,
        .dst_width = DMA_DATA_WIDTH_16BIT
    };
      /**
     * DMA配置结构体{
     * DMA传输方向:从外设到内存
     * DMA源请求:I2S_TX
     * DMA目标请求:无
     * DMA源地址自增:关
     * DMA目标地址自增:开
     * DMA源突发传输个数:0
     * DMA目标突发传输个数:0
     * DMA源地址位宽:16位
     * DMA目标地址位宽:16位
     */

    printf("i2s init\r\n");
    i2s0 = bflb_device_get_by_name("i2s0");
    /* i2s init */
    bflb_i2s_init(i2s0, &i2s_cfg);

    /* enable dma */
    bflb_i2s_link_txdma(i2s0, true);
    bflb_i2s_link_rxdma(i2s0, true);
    //I2S_DMA_TX_RX使能

    printf("dma init\r\n");
    dma0_ch0 = bflb_device_get_by_name("dma0_ch0");
    dma0_ch1 = bflb_device_get_by_name("dma0_ch1");

    bflb_dma_channel_init(dma0_ch0, &tx_config);
    bflb_dma_channel_init(dma0_ch1, &rx_config);
    //DMA通道初始化


    bflb_dma_channel_irq_attach(dma0_ch0, dma0_ch0_isr, NULL);
    bflb_dma_channel_irq_attach(dma0_ch1, dma0_ch1_isr, NULL);
    //DMA通道中断完成触发回调,回调里打印发送或接收完成

    tx_transfers[0].src_addr = (uint32_t)rx_buffer;
    tx_transfers[0].dst_addr = (uint32_t)DMA_ADDR_I2S_TDR;
    tx_transfers[0].nbytes = sizeof(rx_buffer);
    /**发送内容填充
    * 起始地址:缓冲数组
    * 目标地址:I2S的发送寄存器地址
    * 数据大小:缓冲数组大小
    */

    rx_transfers[0].src_addr = (uint32_t)DMA_ADDR_I2S_RDR;
    rx_transfers[0].dst_addr = (uint32_t)rx_buffer;
    rx_transfers[0].nbytes = sizeof(rx_buffer);
    /**接收内容填充
    * 起始地址:I2S的接收寄存器地址
    * 目标地址:缓冲数组
    * 数据大小:缓冲数组大小
    */

   /********将接收到音频数据通过DMA不断存入缓冲数组中,通过DMA将缓冲数组中的信息不断发送出去,实现录音并播放********/

    printf("dma lli init\r\n");
    uint32_t num = bflb_dma_channel_lli_reload(dma0_ch0, tx_llipool, 100, tx_transfers, 1);
    //配置lii信息,将前面配置的信息填入即可

    printf("tx dma lli num: %d \r\n", num);

    bflb_dma_channel_lli_link_head(dma0_ch0, tx_llipool, num);
    //开启循环链表模式,头尾链接

    printf("tx dma lli num: %d \r\n", num);
    num = bflb_dma_channel_lli_reload(dma0_ch1, rx_llipool, 100, rx_transfers, 1);
    printf("rx dma lli num: %d \r\n", num);
    bflb_dma_channel_lli_link_head(dma0_ch1, rx_llipool, num);

    bflb_dma_channel_start(dma0_ch0);
    bflb_dma_channel_start(dma0_ch1);
    //启动DMA传输
}

//时钟源初始化
void mclk_out_init()
{
    /* output MCLK,
    Will change the clock source of i2s,
    It needs to be called before i2s is initialized
    clock source 25M
    */
    GLB_Set_I2S_CLK(ENABLE, 2, GLB_I2S_DI_SEL_I2S_DI_INPUT, GLB_I2S_DO_SEL_I2S_DO_OUTPT);
    // GLB_Set_Chip_Clock_Out3_Sel(GLB_CHIP_CLK_OUT_3_I2S_REF_CLK);
    GLB_Set_Chip_Clock_Out2_Sel(GLB_CHIP_CLK_OUT_2_I2S_REF_CLK);
}

int main(void)
{
    board_init();

    /* gpio init */
    i2s_gpio_init();
    /* mclk clkout init */

    mclk_out_init();

    printf("es8388 init\n\r");

    ES8388_Init(&ES8388Cfg);
    //8388初始化,传入8388配置结构体

    ES8388_Set_Voice_Volume(90);

    /* i2s init */
    i2s_dma_init();

    /* enable i2s tx and rx */
    bflb_i2s_feature_control(i2s0, I2S_CMD_DATA_ENABLE, I2S_CMD_DATA_ENABLE_TX | I2S_CMD_DATA_ENABLE_RX);

    while (1) {
        bflb_mtimer_delay_ms(1);
    }
}

运行结果

录音和播放并易展示,需要注意的是,8388芯片是两路音频输入和输出,他们的对应关系如下: 微信截图_20231123194427.png

本帖被以下淘专辑推荐:

回复

使用道具 举报

妖猊 | 2023-11-23 21:08:45 | 显示全部楼层
最后图片看不到
回复 支持 反对

使用道具 举报

iiv | 2023-11-23 21:59:05 | 显示全部楼层
泽哥你图片裂了
回复 支持 反对

使用道具 举报

爱笑 | 2023-11-24 08:49:01 | 显示全部楼层
马上抓小泽过来修改
用心做好保姆工作
回复 支持 反对

使用道具 举报

方源 | 2023-11-24 11:28:08 | 显示全部楼层
泽哥:你不要给我哇哇叫
回复 支持 反对

使用道具 举报

干簧管 | 2023-11-24 13:38:04 | 显示全部楼层
泽哥牛逼
回复

使用道具 举报

WT_0213 | 2023-11-25 12:23:26 | 显示全部楼层
正好用到I2C, 赞一下
回复 支持 反对

使用道具 举报

心云 | 2023-11-25 18:06:47 | 显示全部楼层
回复

使用道具 举报

干簧管 | 2023-11-25 19:06:37 | 显示全部楼层
回复

使用道具 举报

san | 2023-11-26 22:09:39 | 显示全部楼层
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则