概述
图 PSoC 6与耳机
写上一篇关于EInk的文章的时候,就想着要写这一篇了.因为作者本人除了喜欢阅读之外,还比较喜欢音乐.也喜欢听也喜欢唱K,故此对音频相关的技术也较为感兴趣,至于水平只能说稍知一二.看到这个CY8CKIT开发套件上带有数字麦克风,就动了心思.本文使用开发板子上的资源做一个简单的录音与放音实验,研究一下相关知识.
录音理论与实践
录音方面,简单来讲就是将声音信号定时采集成数字信号以备后述处理.最常见的当然是驻极体的麦克风:
图 驻极体音频采集参考电路
采集出来的信号叫做PCM,数学原理上就一个一维数组,其单元对应每个时刻的声音震动的幅度.播放时将其恢复为介质振动即可还原当初的声音.
信号大致这种样子:
图 PCM信号参考例子
注意:上述信号为CY8CKIT板子通过PDM采样转换为PCM的实际例子
至于采样率,采样深度,压缩算法等等,感兴趣的同学可以找一找本人之前发的系列贴子,因为内容较多在此不一一照搬过来了.
今天要使用的录音器件并非驻极体麦克风,而是一个PDM格式的麦克风SPK0838HT4H-B,原理图:
图 数字麦克风原理图
这种麦克风出来的信号并非直接的PCM, 而是所谓的PDM信号,类似于下图所示:
图 PDM信号参考例子
简单来讲,PDM信号就是1 bit的ADC采样出来的结果.
之所以使用PDM,不使用PCM输出,理论上有很多依据.简而言之,是因为声音在介质中传播是纵向传输,亦即传播方向与振动方向是相同的,故此用脉冲密度能比脉冲幅度更忠实地反映真实的情形.当然这种麦克风还有其余的优点,如体积比驻极体的麦克风小很多,功耗也更低,稳定性,一致性之类的参数都要强不少.目前的趋势是驻极体形式的麦克风逐步被这种麦克风取代,如智能手机中都是使用这种麦克风.
由于采集进来的第一手数据为PDM形式,故此需要首先将其转换为PCM格式以便进一步的处理与播放.处理的方法一般是通过软件抽样+滤波,Cypress的PSoC 6器件的方式较为特殊,可以使用UDB资源对PDM进行硬件解码转换为PCM信号,省去软件上的处理.此处在后文详述.
放音理论与实践
对于PCM信号,播放起来较为直观,直接将其输出到DAC,再经过可选的滤波/放大,输出到放音设备即可播放.
但是很多微型控制器没有DAC外设,此时可以通过PWM+低通滤波器来模拟DAC进行播放.这也是所谓的D类放大器的原理.
图 PWM+滤波器模拟DAC
这两种方法本文都会使用,DAC与PWM分别作为一个通道进行输出.
顶层设计与硬件连接
图 顶层设计
其中有三个模块(PDM_PCM,VDAC,UART)有Prototype水印,表示该模块只是做演示之用,还没有到达能应用到产品的成熟度.本文也的确是仅仅作演示之用,故此可以不加理会.
首先看PDM_PCM模块几个参数的计算:
最终想要达到的采样率为8KHz, Sinc抽样率为64, HFClk1计算为16.384MHz.
下面的Audio Timer用作播放更新,故此也设定为8 KHz产生一次ISR:
图 8 KHz定时器用作播放更新
注意产生的频率有一定的误差,只要在一定范围内是可以接受的.某些要求苛刻的音频设备使用比较不常见的晶体频率即是为了最大程度提高播放精度.
根据现有条件,直接将耳机的两个通道分别接在VDAC与PWM输出上:
图 硬件连接细节
根据实验,阻抗大的耳机效果好一点点,因为引脚驱动有限的缘故.
图 入耳式的耳机阻抗稍大一点
软件
因为主要模块都是硬件生成的,所以软件上也只是控制一个流程而已.流程就是录制-播放两个过程进行循环.此例子与上一篇的例子不同,仅仅使用了Cortex M4的内核,M0+内核除了使能Cortex M4之外没有做任何事情.
初始化各模块:
/* Enable global interrupts. */
__enable_irq();
/* Initialize the PCM interrupt and enable it */
Cy_SysInt_Init(&PCM_ISR_cfg, PCM_ISR_Handler);
NVIC_EnableIRQ(PCM_ISR_cfg.intrSrc);
//The Audio Play ISR
Cy_SysInt_Init(&AudioPlayISR_cfg, AudioPlay_ISR_Handler);
NVIC_EnableIRQ(AudioPlayISR_cfg.intrSrc);
/* Start the UART component for reporting the volume */
UART_Start();
AudioTimer_Start();
VDAC_Start();
AudioPWM_Start();
/* Star the PCM component */
Cy_PDM_PCM_Init(PDM_PCM_HW, &PDM_PCM_config);
Cy_PDM_PCM_Enable(PDM_PCM_HW);
此时各模块已经开始工作,刚刚开始是录音模式.
PDM_PCM的ISR:
void PCM_ISR_Handler(void)
{
/* Set the PCM flag */
flag = 1;
/* Disable PCM ISR to avoid multiple calls to this ISR */
NVIC_DisableIRQ(PCM_ISR_cfg.intrSrc);
}
此处做标记表示有一个FIFO的数据转换为PCM完成需要拿走.主循环中将其放入播放buffer:
for (uint32_t i = 0; i < PDM_PCM_RX_FIFO_TRG_LVL; i++)
{
g_AudioBuff[g_RecIdx++]=(int16_t)PDM_PCM_ReadFifo();
}
一旦buffer满了即转为播放模式,当中需要用户按下SW2按钮:
printf("To Enter Play Mode.\n");
printf("Press SW2\n");
/* Clear SW2 interrupt */
Cy_GPIO_ClearInterrupt(SW2_PORT, SW2_NUM);
while(0 == (Cy_GPIO_GetInterruptStatus(SW2_PORT, SW2_NUM)))
{
__WFI();
}
printf("Play...\n");
g_playRecFlag = true;
g_PlayIdx = 0;
在Audio Timer的ISR中更新播放Sample:
void AudioPlay_ISR_Handler(void)
{
if((true == g_playRecFlag) && (g_PlayIdx < SAMPLE_SIZE))
{
int16_t tmpV16 = g_AudioBuff[g_PlayIdx++];
VDAC_SetValueBuffered(tmpV16>>4);
int8_t tmpV8 = tmpV16/256;
if(tmpV8 < 0)
{
AudioPWM_SetCompare0(256 - tmpV8);
}
else
{
AudioPWM_SetCompare0(127 + tmpV8);
}
}
AudioTimer_ClearInterrupt(CY_TCPWM_INT_ON_CC_OR_TC);
}
注意采集进来的数据为16bit有符号数据,而DAC为12bit,PWM为8bit,需要加以转换.一旦播放完毕需要用户按下SW2重新开始录音.过程与上面的过程类似,反复循环.
根据实际体验,目前这个PDM_PCM转换结果不是很完美,噪音较多,也有可能是本人使用不当.如有进一步的研究结果再来汇报.
总结,资源与参考
Cypress的这个开发板子CY8CKIT能玩的点较多,拿到板子忍不住做了不少实验.其实编辑说搞一两篇就算交差了,这次也算超额完成任务.官方也有一篇音频例程,讲的是检测当前环境的噪音水平,本人认为过于简单于是在此基础上完成了本文的实验.
关于音频,本人一向很感兴趣,以前也写过不少此方面的文章.包括本文做的一些实验其实是之前文章的延伸,故此一些过于细节的部分不想赘述,感兴趣的读者可以在本站找一下之前的文章配合阅读.目前人工智能,语音识别比较热门,作者也有心再做一点此方面的实验,但是这个话题就太大,不知道能否写出文章来.
代码下载连接:
链接: https://pan.baidu.com/s/1nw4o2Pr 密码: cjtc
转自:http://www.21ic.com/evm/evaluate/MCU/201802/752583_2.htm