该项目主要基于MSPMOL1306武汉大学备战电赛使用的主要板卡制作的一款音乐键盘
主要功能:可以通过矩阵键盘发出 do re mi fa sol la si的音调,还可以通过滑动变阻器控制声音大小,最后可以观察LED灯来知道声音的大小
硬件部分:为了突出这款硬件板的功能,我尽可能的使用了板子上的硬件资源,但是在使用主要设备蜂鸣器时,因为一些引脚冲突我无法直接使用板子上的蜂鸣器。其实我可以用一根母对母的杜邦线解决问题的,但是考虑到板子上的蜂鸣器声音比较小,而且用手机录制的视频没有专业设备,就自己接了一个喇叭,效果和直接使用板子上的是一样的。
设计思路:通过改变PWM的频率来控制音调的高低,通过ADC采集可调电阻的阻值计算需要的声音大小,再通过LED展现。
重要的代码讲解:
其实整个项目想要实现的功能其实相对比较简单,我个人认为比较难的是设置PWM的频率也就是PWM的周期。我们需要通过这个修改PWM的频率来修改PWM的声调,通过修改PWM的占空比来设置PWM的音量大小。我通过反复比对图形化设置界面和它自动生成的PWM设置的代码才发现他的是如何设置PWM波的频率的。它本身就是使用这个结构体来配置PWM的频率的:
在这个结构体里面,period就是系统主频的分频系数,32000000/period才得出PWM频率。我通过这一点得出我们想要的PWM频率 = 32000000/period。
但是,这个结构体是常量,这时候我们就得十分注意,必须再配置PWM之后把这个常量结构体改成普通变量结构体,这样我们就能修改period的值了。再修改完period之后我们再调用SYSCFG_DL_PWM_0_init这个函数重新初始化PWM就可以做到修改PWM频率的操作了。这时候我们把这种操作封装成一个函数,就可以修改PWM的频率了。
void Set_Music(uint32_t music)
{
gPWM_0Config.period = music;
SYSCFG_DL_PWM_0_init();
DL_TimerG_startCounter(PWM_0_INST);
}
在使用这个函数修改PWM的频率的时候要注意:
不要再修改配置了,因为每次修改配置会把gPWM_0Config的这个结构体改成只读的,这时候如果再修改就会报错,如果实在需要配置其他部分的配置,记得在配置之后把这个结构体的static const 删掉
这时候我们就要计算需要计算我们需要的音符的频率是多少了,我百度了一下获得了音符与频率的对照图:
我们根据刚刚的公式,假设我们需要一个 中音1 他的频率是523,这就是我们需要的频率,period就应该设置为 32000000/523,得出的结果是:61185,小数舍去
我们把这个数字弄到图形化界面里面,看看对不对:
可以看到结果刚好是523HZ,说明我们的计算结果是对的,那么我们根据频率和音符的对照表一个个把period的频率算出来,得到一个数组:
uint32_t MUSIC[7] = {61185,54514,48558,45845,40816,36363,32388};
这个数组分别对应音符的1、2、3、4、5、6、7
那么我们再使用的时候只需要把这个数组的作为参数就可以通过PWM改变蜂鸣器的声调了。那么这一部分就搞定了。
剩下比较有难度的部分是ADC读取。我们要读取电位计上的电压,从而通过电压改变PWM的占空比来控制声音的大小。在配置ADC的部分是相对比较繁琐的,而且我试过配置完成后只会触发一次就不会再触发的情况,就是读取了一次ADC之后就不会再读取第二次。我反复配置之后才发现需要把他的Enable Repeat Mode沟上才会连续的读取电压。这个部分也卡了我挺久的,一直不清楚为什么会发生这种情况。
然后就是把转换到的数据 既是 ADC的值转换为 声量,其实我只是把他和占空比大概和4096卓一个等比例对换:
//开始转换
DL_ADC12_startConversion(ADC12_0_INST);
delay_ms(5);
//读取结果
if(ADC_flag)
{
ADC_Val = DL_ADC12_getMemResult(ADC12_0_INST,DL_ADC12_MEM_IDX_0);
ADC_flag = false;
}
Set_volume(ADC_Val);
剩下的就是LED的基础的电灯函数了,函数实现展示:
void Set_LED(uint8_t num)
{
if(num == 1)
{
LED_state(1,0,0,0,0,0,0,0);
}
if(num == 2)
{
LED_state(1,1,0,0,0,0,0,0);
}
if(num == 3)
{
LED_state(1,1,1,0,0,0,0,0);
}
if(num == 4)
{
LED_state(1,1,1,1,0,0,0,0);
}
if(num == 5)
{
LED_state(1,1,1,1,1,0,0,0);
}
if(num == 6)
{
LED_state(1,1,1,1,1,1,0,0);
}
if(num == 7)
{
LED_state(1,1,1,1,1,1,1,0);
}
if(num == 8)
{
LED_state(1,1,1,1,1,1,1,1);
}
}
//上面的函数使用这个来控制
void LED_state(uint8_t A,uint8_t B,uint8_t C,uint8_t D,uint8_t E,uint8_t F,uint8_t G,uint8_t H)
{
if(A)
{
DL_GPIO_clearPins(LED_PORT, LED_LED0_PIN);
}
else
{
DL_GPIO_setPins(LED_PORT,LED_LED0_PIN);
}
if(B)
{
DL_GPIO_clearPins(LED_PORT, LED_LED1_PIN);
}
else
{
DL_GPIO_setPins(LED_PORT,LED_LED1_PIN);
}
if(C)
{
DL_GPIO_clearPins(LED_PORT, LED_LED2_PIN);
}
else
{
DL_GPIO_setPins(LED_PORT,LED_LED2_PIN);
}
if(D)
{
DL_GPIO_clearPins(LED_PORT, LED_LED3_PIN);
}
else
{
DL_GPIO_setPins(LED_PORT,LED_LED3_PIN);
}
if(E)
{
DL_GPIO_clearPins(LED_PORT, LED_LED4_PIN);
}
else
{
DL_GPIO_setPins(LED_PORT,LED_LED4_PIN);
}
if(F)
{
DL_GPIO_clearPins(LED_PORT, LED_LED5_PIN);
}
else
{
DL_GPIO_setPins(LED_PORT,LED_LED5_PIN);
}
if(G)
{
DL_GPIO_clearPins(LED_PORT, LED_LED6_PIN);
}
else
{
DL_GPIO_setPins(LED_PORT,LED_LED6_PIN);
}
if(H)
{
DL_GPIO_clearPins(LED_PORT, LED_LED7_PIN);
}
else
{
DL_GPIO_setPins(LED_PORT,LED_LED7_PIN);
}
}
实物展示:
(具体实物效果图片不好展示,推荐移步B站看视频效果)
总结:其实使用这块开发板子开发还是相对轻松简单的,但是MSPM0L1306的资源太少了,感觉不是很够用,但是遇到问题确实不少,感觉其实还可以再优化一下开发板的IO口配置,或者把SPI的屏幕换成软件IIC,以解决SPI屏幕不能和其他外设一起使用的问题。我觉得一块开发板基本的交互屏幕是比较重要的,虽然也有串口但是串口还得一直接着电脑,我个人是比较喜欢用屏幕调试的,所以这次在做这个项目的时候,本来一开始是想做其他项目的,但是因为这个问题比较尴尬,所以看在时间也不多的情况下就做了这个音乐键盘。我希望可以把这块开发板再升级一下,换成IIC的屏幕或许就能解决IO口冲突资源不够的情况发生。还可以减少IO口的使用。再就是,这个开发板要一直带着typec线供电,要不考虑下加点其他供电口?因为一直插着typec就像再床上玩手机充电不能翻身一样,有点难受。不过也是可以克服的小问题。希望我的文章和作品对你有帮助。
代码开源:
链接:https://pan.baidu.com/s/1KRna8aWZ9JKKHidKJs2kPQ?pwd=h327
提取码:h327