在调试装置时需要一个稳定的声音源,想到之前买的有不少stc的MCU芯片,可以用它来产生几个声音信号。于是写了这个程序。
八音盒能产生七个不同的乐音,而音乐就是由这些基本乐音组合构成(有点脸红)。所以本例程就是个基于MCU的乐音产生程序。
用MCU产生一个乐音本身很简单,比如要产生一个110HZ的乐音,只要用MCU生成一个频率为110HZ的PWM方波,然后输出电路上加个一阶低通滤波器,驱动喇叭就听到声音了。
但当我们需要产生多个乐音时,就遇到一个问题,不同乐音的频率不同,需要的滤波器的参数就不一样,只用一个滤波器时,频率高的乐音会受到较大的衰减。结果低音很强,高音听不见了。
当然可以用七个不同的低通滤波器,应对七个频率,用一个cd4051进行选择,mcu输出不同频度时,控制cd4051接通不同的滤波器。不过这样硬件就多了几样。而且无法适应更多的频率。
常用解决办法是采用一个高频率的载波,然后把需要产生的声音频率信号调制到载波上。这样就节省了滤波器。这就是spwm波。它是一个脉冲宽度按正弦规律变化的PWM波。下面给出一个实现方法。
stc32g12k128具有硬件pwm功能。只要做好设置,它能自动产生符合设置频率和脉宽的PWM波,不需要占用MCU时间。我们只要合理控制它的脉宽变化,就能得到需要的SPWM波了。所以程序简单稳定。精度也很高。
我选择系统主频用24MHZ,载波频率为55*512=28160HZ。55为参考频率,512为对应该频率时正弦波表的点数。因为人耳可闻声频率上限20KHZ,所以这个频率不会对乐音产生干扰,同时它又能保证每个周期对应一个波点数据。最大限度的保证产生的声音的精度。对其它频率的乐音,载波频率不变,只改变正弦波表的点数。始终保证系统在最大精度上运行。该载波频率对应的周期设置值为:24000000/28161=852=0x0354
STC官方技术手册上有现成的产生spwm波的例程。就直接套用了,包括pwm设置和pwm中断服务。本例程主要是解决正统波表的生成,管理,使用。乐谱表的建立和使用。乐曲的播放管理。
对spwm波来讲,每个频率的正弦波都需要一组正统波表数据,建立正统波表时,我使用了软件
Spwm_calc.exe。
它的界面如图所示。中值采用420,幅值415,调制度0.98,在55HZ时的点数512.其它频率的点数值通过计算获得。产生了七组数据存放在头文件sin_table1.h里,每组数据的第一个数放的是本组数据的个数(也就是使用的点数)。第二个开始才是正弦波表的数据。也就是说。读波表数据要从第二个开始读。这是为了编程序简单方便。
系统设置了三个数组,一个是正弦波表,放在头文件里。第二个是波表索引数组u16 *sin_table_index[],存放正弦波表的数据的地址,它是乐谱与正弦波表的过渡。第三个是乐谱
u8 music_score[33][3]。
乐谱表中一组数据有三个,第一个是音符名,程序根据它通过索引数组读取对应正弦波表。第二个是音组名。1是最低音,2是低音,3是中音4是高音,5是最高音。这个数据决定从波表中读取数据的数量,数据越少音频越高。比如读512个是最低音的6(la),那么读256个就是低音的6(la),高了八度。频率高一倍。以此类推。这样七组波表数据可用以播放几十个乐音。第三个是音长(乐音的持续时间)。本例程只设置了三个数值,1是全音,2是半音,4 是4分音。仅用来展示一下音长管理的方法。想多加几个音长设置很容易。
例程使用了四个中断服务:
PWMA中断,基本照抄官方例程,用来产生spwm波。不同的是这里输出的正弦波频率不是固定的。所以读波表时不能用固定首地址。而是用了一个变量*p存放正弦波表首地址值,方便切换频率。
外中断int0控制程序运行,主要是把等待切换为运行。如果不用这个中断。可以让程序开机后就自动循环播放。在中断服务程序中。控制变量CC的值,从而影响主程序里语句
While(cc);的运行。
在调试时发现中断服务里改变cc值后,主程序里的while语句没有响应。接上stc-link1d仿真器后观察到,中断发生后cc值确实发生了变化,但while语句不理会,没有如预期的那样跳出死循环。把这个现象放到群里咨询时,有高人指出。这是由于keil编译不合理所致,解决的办法是声名变量CC时加一个限制符volatile。试了一下,果然解决了问题。
中断T1是避免按键振动产生干扰的延时。这两个都不重要,可以不用。
中断T0是核心,它控制一个乐音的播放时间,同时设定播放所需要的所有参数。各语句的作用在程序里做了注明。
void t0_sever() interrupt 1//确定一个音符的输出参数,包括乐音的持续时间,音高数据的地址,数据量,读取参数{ read_music_long(cnt);//读当前音符的播放时间,并设定对应的延时 pp2=music_score[cnt][1];//读取本音符的音组值,以确定读波表数据时的偏移量 pp3=pow(2,pp2);//计算本音符数据偏移量,在式中给pp2加整数能成倍提高输出频率 pp0=*sin_table_index[music_score[cnt][0]-1];///pp2;//读取本音符的数据量 pp1=(sin_table_index[music_score[cnt][0]-1]+1);//取本音符数据指针初值 p=pp1;//赋正弦数据指针初值 cnt++;//准备读下一个数据 if(cnt>33)//乐谱播放完成,这里的33是根据乐谱数据的参数设定的。如果改变乐谱数据量,这里要做对应变化,用小了不能完整播放,用大了会出错 { cc=0;//播放结束 ET0=0;//关中断 PWMA_IER = 0x00; //关中断 PWMA_ENO = 0x00;//关闭PWM输出 } }
复制代码 音长子程序里有三个软件定时程序,是直接使用stc官方的软件延时工具产生的。全音用了一秒,半音0.5秒,四分音用0.25秒。
乐音播放所需要的控制参数都是在这个中断服务里确定的。PWM中断服务则负责按参数进行输出。
本例程的主程序很简单,包括系统设置和播放管理两项。直接列出来吧:
void main(void){ mcu_initial();//mcu设置程序 //打开播放程序,播放完成后重新进入等待 while(1)//这个是重入语句 { cc=1;//等待状态,由中断int0改变,如果屏蔽这个语句则程序自动重复播放乐曲 while(cc); //初始化播放指针并开始播放 cnt=0;//把播放计数复位到开始位置 ET0=1; TR0 = 1; //定时器0开始计时 PWMA_IER = 0x01; //使能中断 PWMA_ENO |= 0x01; //使能输出 PWMA_ENO |= 0x02; //使能输出 cc=1; while(cc);//等待播放完成,由T0中断服务程序控制这里的CC值 } }
复制代码 为听到产生的声音,我使用了唯创的PWM功率放大模块WT1312,它能把spwm信号变成推动喇叭的正弦信号并直接推动喇叭发声。其电路如图所示:把mcu的spwm输出端直接连上功放芯片的PWM输入端就行了。因电流较大。芯片电源单独接了一个4.2/3.7V锂电池。二者不需要共地。功放的输入阻抗是100K,对MCU输出要求很低。它的体积很小,又只用了一个电容,所以我直接用sop23-10/dip10转接板当功放板了。接好的板子如图。MCU部分电路没有特殊要求,使用最小系统板就行,我在实验中用了stc32g12k128的降龙棍系统板。音频测试视频体积太大没法传上来。因为测试电路和这个八音盒程序都没使用晶振,所以会有些误差。效果整体还是很不错的。完整的程序见附件,欢迎大家批评指正。