AiPi-Eyes-S1是安信可开源团队专门为Ai-M61-32S设计的一款开发板,支持WiFi6、BLE5.3。所搭载的Ai-M61-32S 模组具有丰富的外设接口,具体包括 DVP、MJPEG、Dispaly、AudioCodec、USB2.0、SDU、以太网 (EMAC)、SD/MMC(SDH)、SPI、UART、I2C、I2S、PWM、GPDAC、GPADC、ACOMP 和 GPIO 等。
AiPi-Eyes-S1集成了SPI屏幕接口,DVP摄像头接口,外置ES8388音频编解码芯片以及预留TF卡座,并且引出USB接口,可接入USB摄像头。
从零开始学习小安派:
1、零基础开发小安派-Eyes-S1【入门篇】——初识小安派-Eyes-S1
2、零基础开发小安派-Eyes-S1【入门篇】——安装VMware与Ubuntu
3、入门篇:零基础开发小安派-Eyes-S1——新建工程并烧录调试
4、零基础开发小安派-Eyes-S1入门篇——Win下SSH连接Linux
5、零基础开发小安派-Eyes-S1【入门篇】——Samba共享文件夹
6、零基础开发小安派-Eyes-S1【入门篇】——工程文件架构
7、零基础开发小安派-Eyes-S1【外设篇】——GPIO 输入输出
8、零基础开发小安派-Eyes-S1【外设篇】——GPIO中断编程
9、零基础开发小安派-Eyes-S1【外设篇】——PWM
10、零基础开发小安派-Eyes-S1【外设篇】——UART
11、零基础开发小安派-Eyes-S1【外设篇】——I2C
12、零基础开发小安派-Eyes-S1【外设篇】——ADC
13、零基础开发小安派-Eyes-S1【外设篇】——FLASH
I2S(Inter-IC Sound)是一种广泛应用于数字音频传输的串行接口标准。它最初由 Philips 开发,用于解决在集成电路之间传输音频数据的问题。I2S 协议定义了音频数据的传输格式、时序和控制信号。在 I2S 只能同时存在一个主设备和发送设备,主设备可以是发送设备也可以是接收设备,提供 BCK 和 FS 的设备为主设备。
01了解小安派-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;
};
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);
3.bflb_i2s_deinit
说明:I2S 逆初始化。
void bflb_i2s_deinit(struct bflb_device_s *dev);
4.bflb_i2s_link_txdma
说明:I2S RX DMA 使能开关
void bflb_i2s_link_txdma(struct bflb_device_s *dev, bool enable);
5.bflb_i2s_link_rxdma
说明:I2S TX DMA 使能开关
void bflb_i2s_link_rxdma(struct bflb_device_s *dev, bool enable);
6.bflb_i2s_txint_mask
说明:I2S TX fifo 阈值中断屏蔽开关,开启后超过设定阈值则触发中断。
void bflb_i2s_txint_mask(struct bflb_device_s *dev, bool mask);
7.bflb_i2s_rxint_mask
说明:I2S RX fifo 阈值中断屏蔽开关,开启后超过设定阈值则触发中断。
void bflb_i2s_rxint_mask(struct bflb_device_s *dev, bool mask);
8.bflb_i2s_errint_mask
说明:I2S 错误中断屏蔽开关。
void bflb_i2s_errint_mask(struct bflb_device_s *dev, bool mask);
9.bflb_i2s_get_intstatus
说明:获取 I2S 中断标志。
uint32_t bflb_i2s_get_intstatus(struct bflb_device_s *dev);
返回的中断标志有以下选项:
#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);
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)
02示例——I2S 传输 8388 音频数据,实现边录音边播放
首先附上 8388 的芯片手册:8388 芯片手册https://docs.ai-thinker.com/_media/19050105%E9%9F%B3%E9%A2%91%E8%A7%A3%E7%A0%81%E8%8A%AF%E7%89%87-%E9%A1%BA%E8%8A%AF-es8388.pdf
其次博流在 SDK 里提供了 8388 的驱动库,可以直接使用,在使用移植其它的.C 和.H 文件时,可以参考如下的方法。
复制 Project_basic 工程,粘贴成为新的工程文件,将其修改成自用的工程名称(这里笔者的工程名是 I2S_8388)。
1.移植驱动文件
在 components 下创建新的库文件夹,这里命名为 8388,将 AiPi-OPEN/AiPi-Open-Kits/aithinker_Ai-M6X_SDK/examples/peripherals/i2s/i2s_es8388/例程下的 bsp_es8388.c 和 bsp_es8388.h 复制下来,放在 8388 文件夹下。
在8388 文件夹下,修改 bsp_es8388.c 的 253 行,将注释的 LOUT&ROUT 一行取消注释,并注释 ES8388_Write_Reg(0x04, 0x24),具体如下:
2.修改 CMakeLists.txt
添加 8388 文件夹路径。
这里需要注意的是,要链接一个脚本,添加下面的这一行。
sdk_set_linker_script($ENV{BL_SDK_BASE}/bsp/board/bl616dk/bl616_flash_old.ld)
3.修改 flash_prog_cfg.ini
将 boot2_isp_mode 置零,[FW]下的工程文件名修改。
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 donern");
}
//DMA通道0中断服务函数
void dma0_ch1_isr(void *arg)
{
printf("rx donern");
}
//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 initrn");
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 initrn");
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 initrn");
uint32_t num = bflb_dma_channel_lli_reload(dma0_ch0, tx_llipool, 100, tx_transfers, 1);
//配置lii信息,将前面配置的信息填入即可
printf("tx dma lli num: %d rn", num);
bflb_dma_channel_lli_link_head(dma0_ch0, tx_llipool, num);
//开启循环链表模式,头尾链接
printf("tx dma lli num: %d rn", num);
num = bflb_dma_channel_lli_reload(dma0_ch1, rx_llipool, 100, rx_transfers, 1);
printf("rx dma lli num: %d rn", 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 initnr");
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 芯片是两路音频输入和输出,对应关系如下:
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !