此篇博客從 esp-idf 的 i2s 示例 出發來學習實踐 i2s。
1. i2s 配置
直接查看 i2s 示例中配置 i2s 的部分。
i2s_config_t i2s_config = {
.mode = I2S_MODE_MASTER | I2S_MODE_TX, // Only TX
.sample_rate = SAMPLE_RATE,
.bits_per_sample = 16,
.channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels
.communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB,
.dma_buf_count = 6,
.dma_buf_len = 60,
.use_apll = false,
.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1 //Interrupt level 1
};
i2s_pin_config_t pin_config = {
.bck_io_num = I2S_BCK_IO,
.ws_io_num = I2S_WS_IO,
.data_out_num = I2S_DO_IO,
.data_in_num = I2S_DI_IO //Not used
};
i2s_driver_install(I2S_NUM, &i2s_config, 0, NULL);
i2s_set_pin(I2S_NUM, &pin_config);
以上我們可以發現使用 i2s 前需要配置使用以下內容 & API:
- 配置
i2s_config
mode
設置模式,目前只支持 TXsample_rate
設置採樣率bits_per_sample
設置採樣深度channel_format
設置左右聲道communication_format
設置交流格式dma_buf_count
設置 DMA Buffer 計數dma_buf_len
設置 DMA Buffer 長度use_apll
設置是否獲得精確時鐘intr_alloc_flags
設置用來分配中斷
- 配置
pin_config
bck_io_num
設置串行時鐘引腳ws_io_num
設置左右聲道的時鐘引腳data_out_num
設置數據輸出引腳data_in_num
此條一般設置爲 -1
- 使用
i2s_driver_install
來安裝 i2s 驅動,函數定義爲esp_err_t i2s_driver_install(i2s_port_t i2s_num, const i2s_config_t *i2s_config, int queue_size, void* i2s_queue)
- 使用
i2s_set_pin
來設置 i2s 引腳,函數定義爲esp_err_t i2s_set_pin(i2s_port_t i2s_num, const i2s_pin_config_t *pin)
2. i2s 使用
直接查看 i2s 示例中使用 i2s 的部分。
int *samples_data = malloc(((bits+8)/16)*SAMPLE_PER_CYCLE*4);
unsigned int i, sample_val;
double sin_float, triangle_float, triangle_step = (double) pow(2, bits) / SAMPLE_PER_CYCLE;
size_t i2s_bytes_write = 0;
printf("\r\nTest bits=%d free mem=%d, written data=%d\n", bits, esp_get_free_heap_size(), ((bits+8)/16)*SAMPLE_PER_CYCLE*4);
triangle_float = -(pow(2, bits)/2 - 1);
for(i = 0; i < SAMPLE_PER_CYCLE; i++) {
sin_float = sin(i * 2 * PI / SAMPLE_PER_CYCLE);
if(sin_float >= 0)
triangle_float += triangle_step;
else
triangle_float -= triangle_step;
sin_float *= (pow(2, bits)/2 - 1);
if (bits == 16) {
sample_val = 0;
sample_val += (short)triangle_float;
sample_val = sample_val << 16;
sample_val += (short) sin_float;
samples_data[i] = sample_val;
} else if (bits == 24) { //1-bytes unused
samples_data[i*2] = ((int) triangle_float) << 8;
samples_data[i*2 + 1] = ((int) sin_float) << 8;
} else {
samples_data[i*2] = ((int) triangle_float);
samples_data[i*2 + 1] = ((int) sin_float);
}
}
i2s_set_clk(I2S_NUM, SAMPLE_RATE, bits, 2);
//Using push
// for(i = 0; i < SAMPLE_PER_CYCLE; i++) {
// if (bits == 16)
// i2s_push_sample(0, &samples_data[i], 100);
// else
// i2s_push_sample(0, &samples_data[i*2], 100);
// }
// or write
i2s_write(I2S_NUM, samples_data, ((bits+8)/16)*SAMPLE_PER_CYCLE*4, &i2s_bytes_write, 100);
free(samples_data);
以上我們可以發現使用 i2s 需要使用以下內容 & API:
- 首先創建一個空間來儲存要播放的音頻,如上述代碼中的
samples_data
- 在
samples_data
中放置需要播放的音頻數據段 - 使用
i2s_set_clk(I2S_NUM, SAMPLE_RATE, bits, 2)
進行時鐘調整,函數定義爲esp_err_t i2s_set_clk(i2s_port_t i2s_num, uint32_t rate, i2s_bits_per_sample_t bits, i2s_channel_t ch)
- 使用
i2s_write(I2S_NUM, samples_data, ((bits+8)/16)*SAMPLE_PER_CYCLE*4, &i2s_bytes_write, 100)
進行音頻播放,函數定義爲i2s_write(i2s_port_t i2s_num, const void *src, size_t size, size_t *bytes_written, TickType_t ticks_to_wait)
- 循環以上步驟來進行完整的音頻播放
3. i2s 注意事項
- i2s 一次讀取播放的字節選擇 512 或 1024 字節比較合適,字節效果越大越好,但是越大越吃資源。
i2s_set_clk
API 如果較 i2s 初始配置有太多改動,考慮在此 API 後加個 1 ms 的延時。- 如果提前知道音頻的信息(採樣率,單雙聲道等),可以在 i2s config 裏提前配置。
BCLK = sample_rate x sample_length x channel
。