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. 播放端框架¶
 
如上图所示,应用层读取的音频数据写入到内存池的block中,并用数据队列对内存池的block数据进行管理。从数据队列中依次取出音频数据,写入到TX buffer中,TX buffer在原理上是一个环形缓冲区,最后通过DMA将音频数据写入到硬件的TXFIFO中。所以,playback端的driver层驱动主要是负责TX buffer环形缓冲区的管理,及时写入音频数据,并用DMA搬运到硬件。
6.5.5.2.1.2. 录音端框架¶
 
如上图所示,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. 初始化流程¶
- 释放reset和clock信号 
- 注册i2s设备 
6.5.5.3.2. playback流程¶
6.5.5.3.2.1. init流程¶
- 初始化DMA传输的起始地址,buf_len以及period_len 
- 注册hal层的回调函数 
- 初始化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流程¶
- 填充TX buffer 
- 设置DMA传输的参数,调用 - hal_i2s_playback_start开始音频数据传输
- 调用codec_start启用codec芯片 
- 使能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流程¶
- 初始化DMA传输的起始地址,buf_len以及period_len 
- 注册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的指针 | 
| 返回值 | 无 | 
| 注意事项 |