STM32F411实现usb声卡,I2S驱动UDA1334A

 UDA1334A音频模块简介


1.该模块工作在I2S协议,也支持LSB 16/20/24数据,理论可以比PCM5102更加保真

2. 只需要关注VIN,GND,WSEL,DIN,BCLK即可,其余引脚都是默认(上一排默认均为低电平),除了:使用其他协议,多级设备级联。

STM32F411CEU6

一款开源的mini单片机,

用到的I2S-2引脚:

PB10:I2S-CLK  连接模块的  BCLK

PB12:I2S-WS  连接模块的  WSEL

PB15:I2S-SD  连接模块的  DIN

5V 连接模块的  VIN

GND 连接模块的  GND

主要配置和代码:

0.cubemx初始化代码

               RCC 使用外置晶振,配置时钟

               debug模式sw串口

 1.cubemx----usb_otg_fs,配置如图,选择device——only,其余为默认(注意D+的usb需要设置为上拉,实测不上拉也可以,建议上拉)

2.打开usb_device,选择音频设备,将频率设置为48000,其余默认

3.设置I2S,DMA和NVIC,usb默认为0,其余见图,注意DMA为half word,GPIO设置为超高速

4.增大单片机的堆栈大小,确保usb库正常运行

5.生成初始化代码后,打开 usbd_audio_if.c 文件,仅需要简单的修改即可以实现声卡,

打开注释的2行代码,当接收到数据,即开始播放

打开注释的代码,与usb接受的回调函数配合,实现连续播放

6.上述简单实现经常出现破音,因为usb接收和DMA发送不同步造成的,可以在usb接受的回调函数中,搬运走数据,单独播放

然后在I2S回调函数中实现buffer检测和持续播放

usb缓存50ms/帧的数据后开始播放,并在主程序监测两者不匹配的程度

实测,usb接受的快,i2s播放的慢,当mis match达到一定程度后,直接舍弃150个buffer

如果想实现更加无感的数据平滑,当usb接受的数据超过i2s播放的一定程度时,则可以压缩USB接受到的数据帧,逐渐对齐到i2s播放的buffer

以上就可以完成了稳定的USB声卡,我自己懒得调整了,直接暴力舍弃

总结1:

1.由于没有实现暂停、继续和停止播放的控制命令,所以在切换不同声源时,会出现持续播放已经缓存的最后一点缓存声音,导致存在杂音

2.由于直接舍弃了buffer,则发生不匹配时,可以明显感知到破音,可以通过仔细的调整i2s播放的buffer来确保两者同步

3.但是上面这样理论上会导致1ms中有那么1-2个数据质量降低,对于hifi的玩家是不能接受的,实测由于时钟比较精准(i2s = 47.991,失真率为-0.01%),实际很少很少能出现150ms的破音,所以就这样吧

补充:

为了充分发挥这个24bit的音频模块,可以使用以下配置为LSB 24 bit的协议,并且需要修改stm32 的usb音频协议配置

1.i2s协议修改:

2.usb协议修改:

2.1 修改usb audio.c文件,一个pack的大小为288字节,通过以下公式计算:48000(频率)*2(声道数)*3(24bit位数据)/ 1000 = 288字节,将这个字节转化为usb枚举所需要的字节数

#define AUDIO_PACKET_SZE(frq) \
  (uint8_t)(((frq * 2U * 3U) / 1000U) & 0xFFU), (uint8_t)((((frq * 2U * 3U) / 1000U) >> 8) & 0xFFU)

修改为3个字节,24bit数据,

2.2 修改usb audio.h文件,找到以下宏定义并修改,以及修改usb库所用的缓存buffer大小

//修改单个buffer的数据大小 
#define AUDIO_OUT_PACKET                              (uint16_t)(((USBD_AUDIO_FREQ * 2U * 3U) / 1000U))
//修改总缓存的buffer的大小
#define AUDIO_OUT_PACKET_NUM                          60U 
//总的数据大小
#define AUDIO_TOTAL_BUF_SIZE                          ((uint16_t)(AUDIO_OUT_PACKET * AUDIO_OUT_PACKET_NUM))

2.3 计算usb所用到的栈区内存大小,并且重新修改stm32的堆栈大小

  在void USBD_AUDIO_Sync(USBD_HandleTypeDef *pdev, AUDIO_OffsetTypeDef offset)函数中

uint32_t BufferSize = AUDIO_TOTAL_BUF_SIZE / 2U; 

可知大小=48 * 2 * 3 * 60  * 2 =  34,560 byte = 0x8700  栈大小需要大于这个数值,本例设置为0x10000,本例堆大小为0x4000

注意由于修改了usb库,如果使用stm32 cubemx重新配置堆栈大小初始化代码,刚才对usb库的修改都会消失!!!!!

3.24bit的usb数据重组为32位封包的24位LSB的数据包:下面有详细说明

需要用到的数组和变量,注意需要在对应的文件内声明以及补完include


uint32_t i2s2_buffer[40][48*2] __attribute__((used));
uint32_t i2s2_nop_buffer[48*2] __attribute__((used));


uint32_t usb_rece_index = 0;
uint32_t usb_rece_all = 0;

uint32_t i2s_play_index = 0;
uint32_t i2s_play_all = 0;

uint8_t i2s_play_flag = 0;
uint32_t dismatch = 0;
/**
  * @brief  AUDIO_PeriodicT_FS
  * @param  cmd: Command opcode
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
static int8_t AUDIO_PeriodicTC_FS(uint8_t *pbuf, uint32_t size, uint8_t cmd)
{
  /* USER CODE BEGIN 5 */

		//usb接受字节L1L, L1M, L1H 分别是最低有效字节、中间字节和最高有效字节。
	//usb 接受的三个比特数据是:01 02 ab  //usb数据 0xab0201  
	//	I2S发送这三个比特,数据重组:0x 02(usb中间) 01(usb-lsb) xx(自动归零) ab(msb)
	//  实际这个32bit: 0x020100ab 就可以正确的发送usb接受的数据了
	for(uint32_t x = 0; x < 96; x++){  	//48 x 2 x 3的byte总量
    // 直接从缓冲区读取24位数据并按照格式转换为32位整数
    uint32_t index = 3 * x;  // 3 bytes per sample
		uint8_t one_ch[3];
		one_ch[0] = *(pbuf+index); //lsb
		one_ch[1] = *(pbuf+index+1);
		one_ch[2] = *(pbuf+index+2); //msb
	
   // 将24位数据左对齐并存储到I2S缓冲区    // 注意:STM32的I2S通常在传输时使用左对齐格式
    i2s2_buffer[usb_rece_index][x] =  (one_ch[1]<<24) | (one_ch[0]<<16) | one_ch[2]; 
	}  //注意i2s的DMA宽为half word,地址不增加,如果使用fifo,内存数据为word,fifo满,brust=4

	usb_rece_all++;
	usb_rece_index = usb_rece_all % 40;
	if(usb_rece_all==3){
	HAL_I2S_Transmit_DMA(&hi2s2,(uint16_t*)i2s2_buffer[i2s_play_index],96); //注意外设为半字宽
	}

  return (USBD_OK);
  /* USER CODE END 5 */
}

4.补完i2s的回调函数,监控缓存buffer,解决破音问题:

void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s)
{

			if(hi2s->Instance==SPI2){
				//测试下来,usb接收的速度稍微快一点
				(usb_rece_index>i2s_play_index)?(dismatch=usb_rece_index-i2s_play_index):\
		(dismatch=i2s_play_index-usb_rece_index);
				
			if(dismatch<=1){
							
				HAL_I2S_Transmit_DMA(&hi2s2,(uint16_t*)i2s2_nop_buffer,96); 
			}	  //播放一个空buffer,让usb的数据先刷新,相当于舍弃了40ms/个buffer的数据
			else{
				i2s_play_all++;  //完成了1次发送
				i2s_play_index=i2s_play_all%40;  //生成下个传输的序号
				HAL_I2S_Transmit_DMA(&hi2s2,(uint16_t*)i2s2_buffer[i2s_play_index],96); 
			}

			}
}

这样基本没有感知到过破音了

### 回答1: 这是一个文件系统错误。XFS是一种文件系统,uda2是设备名称。错误消息说在 "xlog burite" 操作时出现了元数据输入/输出错误,错误发生在 daddr0x1002?4,数据长度为 8192 字节,错误编号为 5。这可能是由于磁盘错误或者其他原因导致的。建议检查磁盘是否正常工作,并尝试使用文件系统修复工具进行修复。 ### 回答2: 这个错误信息表示在XFS文件系统中发生了元数据I/O错误,具体是在"xlog burite"操作中,daddr0x10024位置处的长度为8192的数据发生了错误,错误代码为5。 这种错误通常是由于磁盘故障、文件系统损坏或硬件故障引起的。元数据是文件系统中存储文件和目录结构的信息,包括文件的大小、位置、权限等,所以遇到元数据错误可能会导致文件系统无法正常访问和操作文件。 解决这个问题的方法一般有以下几种: 1. 首先,可以尝试重启计算机,有时候这个错误只是临时的,重启后可能会恢复正常。 2. 检查磁盘是否出现故障或损坏,可以使用磁盘检测工具来扫描和修复磁盘错误。 3. 对文件系统进行检查和修复,可以使用XFS文件系统特定的修复工具如"xfs_repair"来检查并尝试修复文件系统中的问题。 4. 如果以上方法都无法解决问题,可能需要考虑备份数据并重新格式化磁盘,然后重新创建文件系统并恢复数据。 总之,XFS文件系统的元数据I/O错误需要及时处理,以确保文件系统的正常运行和数据的安全性。如果有需要,建议寻求专业人士的帮助来解决这个问题。 ### 回答3: 这个错误信息提到的是XFS文件系统中的一个问题。针对这个错误,您可以进行以下解释: XFS(扩展文件系统)是一种高性能的文件系统,广泛用于Linux操作系统。它使用了一种日志记录技术,该技术被称为xlog。在这个错误信息中,"xlog burite"是指XFS文件系统中的一个常见操作,用于将日志中的数据写入磁盘。 由于某种原因,在执行"burite"操作时发生了错误。错误信息中提到的"daddr 0x1002"是指在写入过程中遇到了磁盘地址0x1002的问题。这个地址指向了磁盘上的某个数据块。 而"len 8192"表示每次写入的数据长度为8192字节。错误信息中的"error 5"表示遇到了I/O错误,这意味着在进行读写操作时出现了问题。 出现这种错误可能有多种原因,例如硬件故障、磁盘损坏、操作系统错误等。解决这个问题的方法通常是检查磁盘健康状态,确保硬件和操作系统的正常运行。可以尝试重新启动系统,并使用磁盘检测和修复工具来检查和修复潜在的硬件或文件系统问题。 如果问题仍然存在,可能需要进一步的故障排除或寻求专业帮助来解决这个问题。因为错误信息中提到的问题是在XFS文件系统中,所以可能需要联系XFS文件系统的开发团队或相关的技术支持人员来获取更详细的指导和解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值