设计指南
源码说明
源代码位于:
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 传输格式的定义
模块架构设计
RT-Thread 音频框架
RT-Thread 定义了一套音频框架,driver 层的驱动就是为了对接该音频框架。该框架在录音端和播放端采用了两种不同的机制,现对录音端和播放端的框架进行简单说明。
播放端框架
如上图所示,应用层读取的音频数据写入到内存池的 block 中,并用数据队列对内存池的 block 数据进行管理。从数据队列中依次取出音频数据,写入到 TX buffer 中,TX buffer 在原理上是一个环形缓冲区,最后通过 DMA 将音频数据写入到硬件的 TXFIFO 中。所以, playback 端的 driver 层驱动主要是负责 TX buffer 环形缓冲区的管理,及时写入音频数据,并用 DMA 搬运到硬件。
录音端框架
如上图所示,RT-Thread 音频框架在录音端的设计与播放端不同。在录音端框架虚拟了一个 pipe 设备,其实就是一个 ring buffer,读写 pipe 设备就是对 ring buffer 的读写。 driver 层驱动负责管理一个 RX buffer, RX buffer 在逻辑上也是一个环形缓冲区。 DMA 负责将 MIC 端接收到的数据搬运到 RX buffer,再通过写 pipe 设备将音频数据写入到 pipe 的 ring buffer 中。应用层代码则通过 rt_device_read 每次从 pipe 设备中读取音频数据,再将音频数据写入到 wav 文件。
注解
与 audio 不同的是, I2S 还需要实现 codec 芯片的驱动设计才能完成音频的播放和录音功能。
关键流程设计
初始化流程
1.释放 reset 和 clock 信号
2.注册 I2S 设备
playback 流程
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 中写入数据。
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 的传输。
DMA 中断流程
DMA 每传输完 period_len 长度的数据后,触发一次 DMA 中断,然后通过 DMA 回调函数的逐级调用,最终调用 rt_audio_tx_complete 对 TX buffer 进行填充,每次填充 period_len 长度的音频数据。
record 流程
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 设备写入数据。
start 流程
按照 RT-Thread audio 的框架,在执行 rt_device_open
时,就会调用 start 流程,开始音频的录制,然后再通过 rt_device_control
设置音频的格式(采样率,通道数等)。按照这个流程,最开始可能会录制一段不符合设置的音频格式的数据,这显然是不合理的。所以,在 driver 层的驱动实现中, start 流程并未做任何处理,而是在设置完音频格式后才开始音频的录制。
DMA 中断流程
DMA 每传输完 period_len 长度的数据后,触发一次 DMA 中断,然后通过 DMA 回调函数的逐级调用,最终调用 rt_audio_rx_done
,将 RX buffer 的数据写入到 pipe 设备,每次写入 period_len 长度的音频数据。
数据结构设计
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;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
接口设计
driver 层接口设计
driver 层可以同时实现 playback 和 record 两个功能,主要根据音频数据流的方向进行判别。
drv_i2s_sound_init
函数原型
rt_err_t drv_i2s_sound_init(struct rt_audio_device *audio)
功能说明 | I2S 的初始化函数 |
---|---|
参数定义 | audio:指向音频设备的指针 |
返回值 | RT_EOK:执行成功 |
注意事项 | 无 |
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 参数的指针 | |
返回值 | 无 |
注意事项 | 无 |
drv_i2s_sound_start
函数原型
rt_err_t drv_i2s_sound_start(struct rt_audio_device *audio, int stream)
功能说明 | 开始音频播放 |
---|---|
参数定义 | audio:指向音频设备的指针 |
stream:音频数据流方向 | |
返回值 | RT_EOK:执行成功 |
-RT_EINVAL:参数非法 | |
注意事项 | 无 |
drv_i2s_sound_stop
函数原型
rt_err_t drv_i2s_sound_stop(struct rt_audio_device *audio, int stream)
功能说明 | 结束音频播放 |
---|---|
参数定义 | audio:指向音频设备的指针 |
stream:音频数据流方向 | |
返回值 | RT_EOK:执行成功 |
-RT_EINVAL:参数非法 | |
注意事项 | 无 |
drv_i2s_sound_pause
函数原型
rt_err_t drv_i2s_sound_pause(struct rt_audio_device *audio, int enable)
功能说明 | 暂停/恢复音频播放 |
---|---|
参数定义 | audio:指向音频设备的指针 |
enable:音频暂停和恢复播放使能位 (0 为恢复,非 0 为暂停) | |
返回值 | RT_EOK:执行成功 |
注意事项 | 无 |
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:参数不支持 | |
注意事项 | 无 |
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:参数不支持 | |
注意事项 | 无 |
drv_i2s_sound_get_playback_avail
函数原型
rt_size_t drv_i2s_sound_get_playback_avail(struct rt_audio_device *audio)
功能说明 | 获取音频缓存的数据大小 |
---|---|
参数定义 | audio:指向音频设备的指针 |
返回值 | 缓存数据的大小 |
注意事项 | 无 |
hal 层接口设计
hal 层接口也是分 playback,record 两部分进行设计,下面以 playback 端的接口进行说明。
hal_i2s_protocol_select
函数原型
int hal_i2s_protocol_select(aic_i2s_ctrl *i2s, i2s_protocol_t protocol)
功能说明 | 设置传输协议 |
---|---|
参数定义 | i2s:指向 aic_i2s_ctrl 的指针 |
protocol:传输协议 | |
返回值 | 0:执行成功 |
-EINVAL:参数不支持 | |
注意事项 | 无 |
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:执行成功 |
注意事项 | 无 |
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:参数不支持 | |
注意事项 | 无 |
hal_i2s_polarity_set
函数原型
void hal_i2s_polarity_set(aic_i2s_ctrl *i2s, i2s_polarity_t polarity)
功能说明 | 选择 LRCK 的左右通道极性 |
---|---|
参数定义 | i2s:指向 aic_i2s_ctrl 的指针 |
polarity:LRCK 的左右通道极性 | |
返回值 | 无 |
注意事项 | 无 |
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:参数不支持 | |
注意事项 | 无 |
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:音频数据流的方向 | |
返回值 | 无 |
注意事项 | 无 |
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 的指针 | |
返回值 | 无 |
注意事项 | 无 |
hal_i2s_playback_stop
函数原型
void hal_i2s_playback_stop(aic_i2s_ctrl *i2s)
功能说明 | 停止播放 |
---|---|
参数定义 | i2s:指向 aic_i2s_ctrl 的指针 |
返回值 | 无 |
注意事项 | 无 |