STM32CubeMX+MDK通过I2S接口进行音频输入输出(全双工读写一个DMA回调)续-音质问题解决总结

server/2024/9/25 23:11:34/

一、前言

之前进行了STM32CubeMX+MDK通过I2S接口进行音频输入输出(全双工读写一个DMA回调)的研究总结:
https://juejin.cn/post/7339016190612881408#heading-34

后续音质问题解决了,目前测试下来48khz的双声道使用效果很好,由于比较重要,这里单独补充总结一下可能音质原因(包括杂音),这对于调试stm32的i2s录放音很有帮助,不管stm32作为i2s从模式还在作为i2s主模式都是有用的,上述总结的全双工DMA回调处理接口是不需要变的。

二、资料收集

https://blog.csdn.net/Fairchild_1947/article/details/123391638?spm=1001.2014.3001.5501

这里的时钟配置对我有很大的启发,对应f4的芯片对应i2s的时钟配置必须是按照这样固定的,否则音质一定会打折扣。

三、音质问题可能原因总结(重要)

  • 1、是采样率匹配问题,采样率设置不对一定会导致杂音、快进慢放等问题;
  • 2、一个是单双声道问题,生成wav文件时如果声卡设备转换为双声道但是按照单声道处理就会有问题,数据量是双声道的,但是存储时处理了一半;(部分声卡固定是双声道,即使你的播放文件是单声道的,但是经过声卡播放后就是双声道的音,这时单片机通过i2s接收和发送都必须是按照双声道处理
  • 3、还有一个可能就是声卡设备的干扰问题,比如声卡i2s或者某些引脚接线不对导致影响数字信号采集,比如单片机作为主模式可选配的主时钟接入了声卡导致音质影响,实际声卡有其自己的主时钟mclk,声卡的该引脚就不能接入单片机了,否则单片机的i2s的mclk和声卡的mclk互相影响会导致音质问题;
  • 4、还有发现一个问题,声卡i2s的收和发的数据长度设置不一致,比如发设置了标准的16位长度,而接收设置了16位扩展32位长度,导致i2s初始化后接收可能正常,但是发送时声卡收不到数据从而导致录音没有声音,这一点也是很奇怪的,目前猜测可能是声卡双声道传输的问题导致的,所以一般出现设置16位数据长度16位数据帧接收没问题但是发送发送没有声音的话可以试着设置成16位数据32位帧;
  • 5、如果使用spi flash+fatfs进行音频文件写入的,那么音频波形快进样式也可能是spi flash的驱动问题导致的,正片擦除后写入时写入速度快就不会有这个问题,需要在写入前先擦除,如果写入时再擦除会影响写入速度从而影响录音;(调试时可以用示波器先确认硬件对应引脚输出的频率是否符合预期)
    采样率的时钟频率F4按照下图配置即可(比如48khz的固定为258的PLLI2SN和3的PLLi2SR,不同外部输入时钟可以设置对应分频后为1MHZ,比如8MHZ的则设置8分频,25MHZ的设置为25):spi flash理论上是不适合实时读写的,一般开机时写入一次,后续读取即可,因为spi flash的擦除是很耗时的,所以音频文件的实时读写在方案设计之初就需要考虑这一点,对于实时音频存储一定要注意这一点(目前考虑可以直接通过usb3.0等高速接口透传出来音频输出或者使用SD卡等方式);
  • 6、时钟配置配置这里给出一个参考,外部高速时钟不同可能会有一些差异,但是一定要按照固定的时钟分频去配置,比如我这里是25MHZ的:(如果是8MHZ的则M设置为8,后面N和R固定为258和3)

如下是参考网友的i2s时钟分频:

对于不同的HSE,我们为了达到上述一样的i2s时钟分频,需要根据25或8MHZ的不同HSE做不同的分频系数,比如25MHZ的HSE则对应的PLLM分频系统设置25,8MHZ的设置8,这样后面的PLLI2SN和PLLI2SR就能像上图一样固定了(HAL库的一般STM32CubeMX会自动配置时钟,这里往往自动配置的是不对的,要注意这一点)。

参考非HAL库中设置采样率的这一段代码可以更好的理解上面这一配置:

//采样率计算公式:Fs=I2SxCLK/[256*(2*I2SDIV+ODD)]
//I2SxCLK=(HSE/pllm)*PLLI2SN/PLLI2SR
//一般HSE=8Mhz 
//pllm:在Sys_Clock_Set设置的时候确定,一般是8
//PLLI2SN:一般是192~432 
//PLLI2SR:2~7
//I2SDIV:2~255
//ODD:0/1
//I2S分频系数表@pllm=8,HSE=8Mhz,即vco输入频率为1Mhz
//表格式:采样率/10,PLLI2SN,PLLI2SR,I2SDIV,ODD
const u16 I2S_PSC_TBL[][5]=
{{800 ,100,2,24,0},		//8Khz采样率{1102,429,4,19,0},		//11.025Khz采样率 {1600,213,2,13,0},		//16Khz采样率{2205,429,4, 9,1},		//22.05Khz采样率{3200,213,2, 6,1},		//32Khz采样率{4410,271,2, 6,0},		//44.1Khz采样率{4800,100,2, 4,0},		//48Khz采样率{8820,316,2, 3,1},		//88.2Khz采样率{9600,344,2, 3,1},  	//96Khz采样率{17640,361,2,2,0},  	//176.4Khz采样率 {19200,393,2,2,0},  	//192Khz采样率
};  
//设置IIS的采样率(@MCKEN)
//samplerate:采样率,单位:Hz
//返回值:0,设置成功;1,无法设置.
u8 I2S2_SampleRate_Set(u32 samplerate)
{ u8 i=0; u32 tempreg=0;samplerate/=10;//缩小10倍   for(i=0;i<(sizeof(I2S_PSC_TBL)/10);i++)//看看改采样率是否可以支持{if(samplerate==I2S_PSC_TBL[i][0])break;}RCC_PLLI2SCmd(DISABLE);//先关闭PLLI2Sif(i==(sizeof(I2S_PSC_TBL)/10))return 1;//搜遍了也找不到RCC_PLLI2SConfig((u32)I2S_PSC_TBL[i][1],(u32)I2S_PSC_TBL[i][2]);//设置I2SxCLK的频率(x=2)  设置PLLI2SN PLLI2SRRCC->CR|=1<<26;					//开启I2S时钟while((RCC->CR&1<<27)==0);		//等待I2S时钟开启成功. tempreg=I2S_PSC_TBL[i][3]<<0;	//设置I2SDIVtempreg|=I2S_PSC_TBL[i][4]<<8;	//设置ODD位tempreg|=1<<9;					//使能MCKOE位,输出MCKSPI2->I2SPR=tempreg;			//设置I2SPR寄存器 return 0;
}

四、最后

基本上stm32使用i2s的音视频总结这两篇文章就够用了,其它的stm32作为usb声卡或者录音也只是以stm32作为i2s主模式来驱动像wm8978这类音频编解码器即可,反而不会遇到这种直接和声卡交互stm32作为从模式可能引起的一些音质问题(调试过程中使用示波器来测量引脚频率可以帮助排除一些硬件问题,这样更能明确问题处理方向)。


http://www.ppmy.cn/server/23756.html

相关文章

GITEE 基于OAuth2的API V5版本

为了构建更好的码云生态环境&#xff0c;我们推出了基于OAuth2的API V5版本。 API V5接口使用方式以及Url都参照GitHub&#xff0c;为了各位开发者更好的兼容已经存在的第三方应用。 API 使用条款 OSCHINA 用户是资源的拥有者&#xff0c;需尊重和保护用户的权益。不能在应用…

严把质量关,饮片追溯系统应用,信息化追溯助力用药安全-亿发

中药饮片作为我国中药产业的重要组成部分&#xff0c;在医药工业中发挥着至关重要的作用。近年来&#xff0c;中药饮片行业虽然取得了稳步增长&#xff0c;但同时也面临着产业集中度低、竞争激烈、质量良莠不齐等诸多挑战。为了应对这些问题&#xff0c;国家和各地纷纷加强中药…

C语言例题(递归、二分查找、冒泡排序)

一、递归案例 有5个人坐在在一起&#xff0c;问第5个人多少岁&#xff1f;他说比第4个人大两岁。问第4个人岁数&#xff0c;他说比第3个人大两岁。问第3个人&#xff0c;又说比第2个人大两岁。问第2个人&#xff0c;说比第1个人大2岁。最后问第1个人&#xff0c;他说是10岁。请…

二叉树06(leetcode654,617,700,98)

参考资料&#xff1a; https://programmercarl.com/0654.%E6%9C%80%E5%A4%A7%E4%BA%8C%E5%8F%89%E6%A0%91.html 654. 最大二叉树 题目描述&#xff1a; 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为…

学习C语言的指针

有一阵没更新了&#xff0c;因为最近比较繁忙&#xff0c;所以更新比较慢&#xff0c;还在慢慢学习 话不多说&#xff0c;开始今天的内容&#xff0c;聊一聊C语言指针。 很多小伙伴可能会被指针这个名字吓到&#xff0c;觉得很难&#xff0c;实际上确实有点难&#xff0c;但是…

tcp inflight 守恒算法背后的哲学

tcp inflight 守恒拥塞控制的正确性 很久以前我开始纠结 tcp 锯齿&#xff0c;很多年后我知道这叫 capacity-seeking&#xff0c;甚至说 tcp 属于 capacity-seeking protocol 的原因就是它早已深入人心的 aimd 行为&#xff0c;而该行为生成了 tcp 锯齿。 在消除锯齿&#xf…

乐观锁和悲观锁

概述 悲观锁 坏事一定会发生&#xff0c;所以先做预防&#xff08;上锁&#xff09; 写多读少 乐观锁&#xff1a;坏事不一定会发生&#xff0c;所以事后补偿 写少读多 悲观锁 select for update&#xff0c;sychronized等&#xff0c;乐观锁&#xff0c;乐观锁如cas和版本号 …

Redis基本數據結構 ― List

Redis基本數據結構 ― List 介紹常用命令範例1. 將元素推入List中2. 取得List內容3. 彈出元素 介紹 Redis中的List結構是一個雙向鏈表。 LPUSH LPOP StackLPUSH RPOP QueueLPUSH BRPOP Queue(消息隊列) 常用命令 命令功能LPUSH將元素推入列表左端RPUSH將元素推入列表右…