6.5.5. 设计指南

6.5.5.1. 源码说明

源代码位于:

  • bsp/artinchip/drv/i2s/drv_i2s_sound.c: I2S的driver层驱动

  • bsp/artinchip/hal/i2s/hal_i2s.c: i2s模块的hal层驱动

  • bsp/artinchip/include/hal/hal_i2s.h: i2s模块的hal层头文件,用于定义寄存器相关

  • bsp/artinchip/include/hal/hal_i2s_format.h: i2s模块的hal层头文件,用于i2s传输格式的定义

6.5.5.2. 模块架构设计

6.5.5.2.1. RT-Thread 音频框架

RT-Thread定义了一套音频框架,driver层的驱动就是为了对接该音频框架。该框架在录音端和播放端采用了两种不同的机制,现对录音端和播放端的框架进行简单说明。

6.5.5.2.1.1. 播放端框架

../../../_images/playback_arch1.png

如上图所示,应用层读取的音频数据写入到内存池的block中,并用数据队列对内存池的block数据进行管理。从数据队列中依次取出音频数据,写入到TX buffer中,TX buffer在原理上是一个环形缓冲区,最后通过DMA将音频数据写入到硬件的TXFIFO中。所以,playback端的driver层驱动主要是负责TX buffer环形缓冲区的管理,及时写入音频数据,并用DMA搬运到硬件。

6.5.5.2.1.2. 录音端框架

../../../_images/record_arch1.png

如上图所示,RT-Thread 音频框架在录音端的设计与播放端不同。在录音端框架虚拟了一个pipe设备,其实就是一个ringbuffer,读写pipe设备就是对ringbuffer的读写。driver层驱动负责管理一个RX buffer,RX buffer在逻辑上也是一个环形缓冲区。DMA负责将MIC端接收到的数据搬运到RX buffer,再通过写pipe设备将音频数据写入到pipe的ringbuffer中。应用层代码则通过 rt_device_read 每次从pipe设备中读取音频数据,再将音频数据写入到wav文件。

注解

与audio不同的是,i2s还需要实现codec芯片的驱动设计才能完成音频的播放和录音功能

6.5.5.3. 关键流程设计

6.5.5.3.1. 初始化流程

  1. 释放reset和clock信号

  2. 注册i2s设备

6.5.5.3.2. playback流程

6.5.5.3.2.1. init流程

  1. 初始化DMA传输的起始地址,buf_len以及period_len

  2. 注册hal层的回调函数

  3. 初始化codec

I2S模块使用DMA传输音频数据,DMA采用环形链表形式,依次将音频数据传送到硬件。所以需要配置DMA传输时的起始地址(即TX buffer地址)以及buf_len,period_len。在driver层驱动,将buf_len配置为period_len的4倍,DMA每传输period_len长度的数据,触发一次DMA中断,通知CPU向TX buffer中写入数据。

6.5.5.3.2.2. start流程

  1. 填充TX buffer

  2. 设置DMA传输的参数,调用 hal_i2s_playback_start 开始音频数据传输

  3. 调用codec_start启用codec芯片

  4. 使能PA

为保证DMA传输音频数据的连续性,需要在DMA开始传输前,先向TX buffer中填充数据。在playback的driver层驱动,是将TX buffer填充满后,才开始DMA的传输。

6.5.5.3.2.3. DMA中断流程

DMA每传输完period_len长度的数据后,触发一次DMA中断,然后通过DMA回调函数的逐级调用,最终调用 rt_audio_tx_complete 对TX buffer进行填充,每次填充period_len长度的音频数据。

6.5.5.3.3. record流程

6.5.5.3.3.1. init流程

  1. 初始化DMA传输的起始地址,buf_len以及period_len

  2. 注册hal层的回调函数

I2S模块使用DMA传输音频数据,DMA采用环形链表形式,依次将音频数据传送到硬件。所以需要配置DMA传输时的起始地址(即RX buffer地址)以及buf_len,period_len。在driver层驱动,将buf_len配置为period_len的2倍,DMA每传输period_len长度的数据,触发一次DMA中断,通知CPU向pipe设备写入数据。

6.5.5.3.3.2. start流程

按照RT-Thread audio的框架,在执行 rt_device_open 时,就会调用start流程,开始音频的录制,然后再通过 rt_device_control 设置音频的格式(采样率,通道数等)。按照这个流程,最开始可能会录制一段不符合设置的音频格式的数据,这显然是不合理的。所以,在driver层的驱动实现中,start流程并未做任何处理,而是在设置完音频格式后才开始音频的录制。

6.5.5.3.3.3. DMA中断流程

DMA每传输完period_len长度的数据后,触发一次DMA中断,然后通过DMA回调函数的逐级调用,最终调用 rt_audio_rx_done ,将RX buffer的数据写入到pipe设备,每次写入period_len长度的音频数据。

6.5.5.4. 数据结构设计

6.5.5.4.1. hal层数据结构

hal_i2s.h的数据结构

struct aic_i2s_buf_info
{
    void *buf;
    uint32_t buf_len;
    uint32_t period_len;
};

struct aic_i2s_transfer_info
{
    struct aic_dma_chan *dma_chan;
    struct aic_audio_buf_info buf_info;
    int transfer_type;
};

struct aic_i2s_ctrl
{
    unsigned long reg_base;
    uint32_t irq_num;
    uint32_t clk_id;
    uint32_t idx;
    struct aic_audio_transfer_info tx_info;   //TX buffer的参数
    struct aic_audio_transfer_info rx_info;   //RX buffer的参数
    i2s_callback callback;
    void *arg;
};

hal_i2s_format.h的数据结构

typedef enum
{
    I2S_MODE_MASTER,
    I2S_MODE_SLAVE,
} i2s_mode_t;

typedef enum
{
    I2S_PROTOCOL_I2S,
    I2S_PROTOCOL_LEFT_J,
    I2S_PROTOCOL_RIGHT_J,
    I2S_PCM_SHORT,
    I2S_PCM_LONG,
} i2s_protocol_t;

typedef enum
{
    I2S_LEFT_POLARITY_LOW,
    I2S_LEFT_POLARITY_HIGH,
} i2s_polarity_t;

typedef enum
{
    I2S_SAMPLE_RATE_8000              = 8000U,
    I2S_SAMPLE_RATE_11025             = 11025U,
    I2S_SAMPLE_RATE_12000             = 12000U,
    I2S_SAMPLE_RATE_16000             = 16000U,
    I2S_SAMPLE_RATE_22050             = 22050U,
    I2S_SAMPLE_RATE_24000             = 24000U,
    I2S_SAMPLE_RATE_32000             = 32000U,
    I2S_SAMPLE_RATE_44100             = 44100U,
    I2S_SAMPLE_RATE_48000             = 48000U,
    I2S_SAMPLE_RATE_96000             = 96000U,
    I2S_SAMPLE_RATE_192000            = 192000U,
    I2S_SAMPLE_RATE_256000            = 256000U,
} i2s_sample_rate_t;

typedef enum
{
    I2S_SAMPLE_WIDTH_8BIT = 8U,
    I2S_SAMPLE_WIDTH_12BIT = 12U,
    I2S_SAMPLE_WIDTH_16BIT = 16U,
    I2S_SAMPLE_WIDTH_20BIT = 20U,
    I2S_SAMPLE_WIDTH_24BIT = 24U,
    I2S_SAMPLE_WIDTH_28BIT = 28U,
    I2S_SAMPLE_WIDTH_32BIT = 32U,
} i2s_sample_width_t;

typedef enum
{
    I2S_LEFT_CHANNEL,
    I2S_RIGHT_CHANNEL,
    I2S_LEFT_RIGHT_CHANNEL,
} i2s_sound_channel_t;

typedef enum
{
    I2S_STREAM_PLAYBACK     = 0,
    I2S_STREAM_RECORD       = 1,
} i2s_stream_t;

typedef struct
{
    i2s_mode_t              mode;
    i2s_protocol_t          protocol;
    i2s_polarity_t          polarity;
    i2s_sample_rate_t       rate;
    i2s_sample_width_t      width;
    i2s_sound_channel_t     channel;
    uint32_t                sclk_nfs;
    uint32_t                mclk_nfs;
    i2s_stream_t            stream;
} i2s_format_t;

6.5.5.4.2. driver层数据结构

struct aic_i2s_sound
{
    struct rt_audio_device audio;
    aic_i2s_ctrl i2s;
    struct codec *codec;
    i2s_format_t format;    //i2s的传输格式
    rt_uint8_t volume;      //playback音量
    char *name;             //设备名
    uint32_t i2s_idx;
    uint8_t record_idx;
};

static struct aic_i2s_sound snd_dev[] =
{
#ifdef AIC_USING_I2S0
    {
        .name = "i2s0_sound",
        .i2s_idx = 0,
    },
#endif
#ifdef AIC_USING_I2S1
    {
        .name = "i2s1_sound",
        .i2s_idx = 1,
    },
#endif
};

6.5.5.5. 接口设计

6.5.5.5.1. driver层接口设计

driver层可以同时实现playback和record两个功能,主要根据的是音频数据流的方向进行判别。

6.5.5.5.1.1. drv_i2s_sound_init

函数原型

rt_err_t drv_i2s_sound_init(struct rt_audio_device *audio)

功能说明

i2s的初始化函数

参数定义

audio:指向音频设备的指针

返回值

RT_EOK:执行成功

注意事项

6.5.5.5.1.2. drv_i2s_sound_buffer_info

函数原型

void drv_i2s_sound_buffer_info(struct rt_audio_device *audio, struct rt_audio_buf_info *info)

功能说明

获取音频的TX buffer参数

参数定义

audio:指向音频设备的指针
info:用于获取TX buffer参数的指针

返回值

注意事项

6.5.5.5.1.3. drv_i2s_sound_start

函数原型

rt_err_t drv_i2s_sound_start(struct rt_audio_device *audio, int stream)

功能说明

开始音频播放

参数定义

audio:指向音频设备的指针
stream:音频数据流方向

返回值

RT_EOK:执行成功
-RT_EINVAL:参数非法

注意事项

6.5.5.5.1.4. drv_i2s_sound_stop

函数原型

rt_err_t drv_i2s_sound_stop(struct rt_audio_device *audio, int stream)

功能说明

结束音频播放

参数定义

audio:指向音频设备的指针
stream:音频数据流方向

返回值

RT_EOK:执行成功
-RT_EINVAL:参数非法

注意事项

6.5.5.5.1.5. drv_i2s_sound_pause

函数原型

rt_err_t drv_i2s_sound_pause(struct rt_audio_device *audio, int enable)

功能说明

暂停/恢复音频播放

参数定义

audio:指向音频设备的指针
enable:音频暂停和恢复播放使能位(0为恢复,非0为暂停)

返回值

RT_EOK:执行成功

注意事项

6.5.5.5.1.6. drv_i2s_sound_configure

函数原型

rt_err_t drv_i2s_sound_configure(struct rt_audio_device *audio, struct rt_audio_caps *caps)

功能原型

音频设备配置接口,用于配置采样格式,采样率,通道数等接口

参数定义

audio:指向音频设备的指针
caps:指向配置参数的指针

返回值

RT_EOK:执行成功
-RT_ERROR:参数不支持

注意事项

6.5.5.5.1.7. drv_i2s_sound_getcaps

函数原型

rt_err_t drv_i2s_sound_getcaps(struct rt_audio_device *audio, struct rt_audio_caps *caps)

功能说明

获取音频设备的参数

参数定义

audio:指向音频设备的指针
caps:指向配置参数的指针

返回值

RT_EOK:执行成功
-RT_ERROR:参数不支持

注意事项

6.5.5.5.1.8. drv_audio_get_i2s_sound_avail

函数原型

rt_size_t drv_i2s_sound_get_playback_avail(struct rt_audio_device *audio)

功能说明

获取音频缓存的数据大小

参数定义

audio:指向音频设备的指针

返回值

缓存数据的大小

注意事项

6.5.5.5.2. hal层接口设计

hal层接口也是分playback,record两部分进行设计,下面以playback端的接口进行说明。

6.5.5.5.2.1. hal_i2s_protocol_select

函数原型

int hal_i2s_protocol_select(aic_i2s_ctrl *i2s, i2s_protocol_t protocol)

功能说明

设置传输协议

参数定义

i2s:指向aic_i2s_ctrl的指针
protocol:传输协议

返回值

0:执行成功
-EINVAL:参数不支持

注意事项

6.5.5.5.2.2. hal_i2s_sample_width_select

函数原型

int hal_i2s_sample_width_select(aic_i2s_ctrl *i2s, i2s_sample_width_t width)

功能说明

设置采样通道的位宽

参数定义

i2s:指向aic_i2s_ctrl的指针
width:通道的位宽

返回值

0:执行成功

注意事项

6.5.5.5.2.3. hal_i2s_mclk_set

函数原型

int hal_i2s_mclk_set(aic_i2s_ctrl *i2s, i2s_sample_rate_t sample_rate, uint32_t mclk_nfs)

功能说明

设置MCLK的时钟频率

参数定义

i2s:指向aic_i2s_ctrl的指针
sample_rate:采样率
mclk_nfs:MCLK的时钟频率

返回值

0:执行成功
-EINVAL:参数不支持

注意事项

6.5.5.5.2.4. hal_i2s_polarity_set

函数原型

void hal_i2s_polarity_set(aic_i2s_ctrl *i2s, i2s_polarity_t polarity)

功能说明

选择LRCK的左右通道极性

参数定义

i2s:指向aic_i2s_ctrl的指针
polarity:LRCK的左右通道极性

返回值

注意事项

6.5.5.5.2.5. hal_i2s_sclk_set

函数原型

int hal_i2s_sclk_set(aic_i2s_ctrl *i2s, i2s_sample_rate_t sample_rate, uint32_t sclk_nfs)

功能说明

设置SCLK/LRCK的比率

参数定义

i2s:指向aic_i2s_ctrl的指针
sample_rate:采样率
sclk_nfs:需要设置的比率

返回值

0:执行成功
-EINVAL:参数不支持

注意事项

6.5.5.5.2.6. hal_i2s_channel_select

函数原型

void hal_i2s_channel_select(aic_i2s_ctrl *i2s, i2s_sound_channel_t channel, i2s_stream_t stream)

功能说明

I2S通道数量设置

参数定义

i2s:指向aic_i2s_ctrl的指针
channel:指向i2s_sound_channel_t的变量,表示要使用的通道
stream:音频数据流的方向

返回值

注意事项

6.5.5.5.2.7. hal_i2s_playback_start

函数原型

void hal_i2s_playback_start(aic_i2s_ctrl *i2s, i2s_format_t *format)

功能说明

开始播放

参数定义

i2s:指向aic_i2s_ctrl的指针
format:指向i2s_format_t的指针

返回值

注意事项

6.5.5.5.2.8. hal_i2s_playback_stop

函数原型

void hal_i2s_playback_stop(aic_i2s_ctrl *i2s)

功能说明

停止播放

参数定义

i2s:指向aic_i2s_ctrl的指针

返回值

注意事项