基于EVM_MSPM0L1306开发套件制作音乐键盘
该项目使用了EVM_MSPM0L1306开发套件,实现了音乐键盘的设计,它的主要功能为:按下不同按键使蜂鸣器发出不同音调,调节电位计控制声音大小,LED亮的数量显示声音大小。
标签
显示
ADC
开发板
参加活动
EliorFoy
更新2024-07-18
北京邮电大学
207

基于EVM_MSPM0L1306开发套件制作音乐键盘

本项目基于EVM_MSPM0L1306开发板使用矩阵键盘、蜂鸣器、流水灯和电位计设计音乐键盘,让不同按键发出不同音调,电位计用来控制声音大小,流水灯用来显示声音档位。

开发板介绍

EVM_MSPM0L1306开发板实物图:

34240bdbb9ecff7d567f1c2c817a1fb.jpg

从图中可以看出,该开发板板载了许多资源,左上角可以外接蓝牙模块和WiFi模块可以进行联网和无线通信,然后下面是一个无源蜂鸣器可以通过输入PWM波进行发声,接下来是ADC和DAC以及EEPROM和FLASH存储、陀螺仪, 右侧有一个七针OLED屏、矩阵键盘以及八个LED灯。核心板底下有一个debug调试器,采用DAP-Link方便烧录程序和调试。本项目即使用了矩阵键盘进行按键扫描,读取不同按键后更改PWM波频率使蜂鸣器发出不同的音调。通过读取电位计的电压,旋转电位计能够控制PWM占空比并亮不同数量的LED显示声音响度。

EVM_MSPM0L1306开发板的底板原理图如图所示:

根据原理图,我们分别构建了按键、OLED、ADC以及蜂鸣器相关代码。

按键部分,从原理图可以看出,矩阵键盘与MCU的PA0、PA1等引脚直接相连,可以通过GPIO读取按键按下状态,这里为了简洁也因为项目功能并不太复杂,我们没有使用外部中断而是直接将其封装为了一个模块,循环读取每一个按键的值并返回给函数调用处。通过外部中断也可以实现,但是需要再Sysconfig中配置GPIO为输入中断模式以及需要开启开启按键引脚的GPIOA端口中断。代码如下:

#include "key.h"

int getKeyValue(void)
{
int h_arr[4] = {KEY_H1_PIN, KEY_H2_PIN, KEY_H3_PIN, KEY_H4_PIN};
int v_arr[4] = {KEY_V1_PIN, KEY_V2_PIN, KEY_V3_PIN, KEY_V4_PIN};
int i, j = 0;
int key_value = 0;
for (i = 0; i < 4; i++)
{
delay_cycles(1000);

DL_GPIO_setPins(KEY_PORT, h_arr[i]);
DL_GPIO_clearPins(KEY_PORT, h_arr[(i + 1) % 4]);
DL_GPIO_clearPins(KEY_PORT, h_arr[(i + 2) % 4]);
DL_GPIO_clearPins(KEY_PORT, h_arr[(i + 3) % 4]);

delay_cycles(100000);

for (j = 0; j < 4; j++)
{
if (DL_GPIO_readPins(KEY_PORT, v_arr[j]) != 0)
{
key_value = j * 4 + i + 1;
}
delay_cycles(1000);
}

delay_cycles(1000);
}

return key_value; // 没有按下,返回0
}

代码中KEY_H1_PIN等是由Sysconfig配置的宏定义,对应相关的PA引脚,代码逻辑就是一个简单的循环扫描读取。

OLED部分,我们按照直播例程直接调用封装好的模块进行调用,显示按键按下(即不同音调选择)和声音响度的显示(其实是电位计电压的转换值)。当然也可以自己手搓一遍IIC通信获得更好的显示体验,这里我们就从简了。相关代码如下:

int main(void)
{
//...​
OLED_Init();
OLED_Clear();

while(1)
{
key_value = getKeyValue();
adc_value = adc_getValue();
sound = (int)((adc_value/4096.0*3.3)*350);
DL_TimerG_setCaptureCompareValue(PWM_0_INST,sound,GPIO_PWM_0_C0_IDX);
soundled(sound);
OLED_ShowNum(50,0,sound,4,16);
OLED_ShowString(0,0,"sound:");
OLED_ShowString(5,10,"Tone:");
if(key_value != 0)
{
OLED_ShowNum(50, 10, (unsigned int)key_value, 4, 16);
DL_Timer_setLoadValue(PWM_0_INST,key_value*50+100);
}
}
}

首先进行OLED的初始化和全屏清除(Init和Clear),然后就是显示两行分别是sound和Tone,也采用的是循环扫描显示对应采集到的数据和按下按键的值。

ADC部分,我们也为了简便性没有使用中断和DMA,而是利用中断的相关寄存器获得相关采样,并没有使用中断服务函数,代码如图所示:

//...
while(1)
{
key_value = getKeyValue();
adc_value = adc_getValue();
sound = (int)((adc_value/4096.0*3.3)*350);
DL_TimerG_setCaptureCompareValue(PWM_0_INST,sound,GPIO_PWM_0_C0_IDX);
soundled(sound);
OLED_ShowNum(50,0,sound,4,16);
OLED_ShowString(0,0,"sound:");
OLED_ShowString(5,10,"Tone:");
if(key_value != 0)
{
OLED_ShowNum(50, 10, (unsigned int)key_value, 4, 16);
DL_Timer_setLoadValue(PWM_0_INST,key_value*50+100);
}
}
unsigned int adc_getValue(void)
{
unsigned int gAdcResult = 0;

DL_ADC12_startConversion(ADC12_0_INST);

gAdcResult = DL_ADC12_getMemResult(ADC12_0_INST, ADC12_0_ADCMEM_ADC_CH0);

return gAdcResult;
}

每次循环就会读取ADC中的采样值,通过将ADC采集的数据换算为电压再线性转换能够将采样值固定在一个区间方便后续LED根据区间进行不同的响度显示以及PWM波控制响度。

蜂鸣器部分,我们通过Sysconfig配置PWM配置了一个100Hz的1000计次的PWM波,通过改变比较值即可改变占空比,从而改变发出声音的响度。由于分配的引脚不能为PA15,我们改用了另外的引脚并用杜邦线进行连接。

image.png

代码如图所示:

DL_TimerG_setCaptureCompareValue(PWM_0_INST,sound,GPIO_PWM_0_C0_IDX);

其中sound即是从电位计读取电压转换过来的比例数,当sound变化的时候就计数到不同的值进行电平置高置低形成不同占空比的PWM波。

LED部分,设计的代码逻辑比较简洁,即通过判断sound所在的区间来设置GPIO的输出电平,代码如图所示:

void soundled(int sound){
if (sound<10){
DL_GPIO_setPins(LED_PORT, LED_LED0_PIN);
DL_GPIO_setPins(LED_PORT, LED_LED1_PIN);
DL_GPIO_setPins(LED_PORT, LED_LED2_PIN);
DL_GPIO_setPins(LED_PORT, LED_LED3_PIN);}
else if(10<sound&&sound<=200){
DL_GPIO_clearPins(LED_PORT, LED_LED0_PIN);
DL_GPIO_setPins(LED_PORT, LED_LED1_PIN);
DL_GPIO_setPins(LED_PORT, LED_LED2_PIN);
DL_GPIO_setPins(LED_PORT, LED_LED3_PIN);
}
else if(200<sound&&sound<=350){
DL_GPIO_clearPins(LED_PORT, LED_LED1_PIN);
DL_GPIO_clearPins(LED_PORT, LED_LED1_PIN);
DL_GPIO_setPins(LED_PORT, LED_LED2_PIN);
DL_GPIO_setPins(LED_PORT, LED_LED3_PIN);
}
else if(350<sound&&sound<=500){
DL_GPIO_clearPins(LED_PORT, LED_LED2_PIN);
DL_GPIO_clearPins(LED_PORT, LED_LED1_PIN);
DL_GPIO_clearPins(LED_PORT, LED_LED2_PIN);
DL_GPIO_setPins(LED_PORT, LED_LED3_PIN);
}
else if(500<sound){
DL_GPIO_clearPins(LED_PORT, LED_LED3_PIN);
DL_GPIO_clearPins(LED_PORT, LED_LED1_PIN);
DL_GPIO_clearPins(LED_PORT, LED_LED2_PIN);
DL_GPIO_clearPins(LED_PORT, LED_LED3_PIN);
}
}
int main(void)
{
//...
while(1)
{
key_value = getKeyValue();
adc_value = adc_getValue();
sound = (int)((adc_value/4096.0*3.3)*350);
DL_TimerG_setCaptureCompareValue(PWM_0_INST,sound,GPIO_PWM_0_C0_IDX);
soundled(sound);
//...
}
}


每次循环都会判断sound进行LED的设置,从而实现不同响度显示不同数量的LED灯。

简单的流程图如下:

b8ccad5e26bf4723d1d3795803b3a95.png

实物展示

按下按键改变pwm波的频率实现不同按键不同音调

可以看到pwm波频率随按键的变化,相关OLED屏幕上也有所显示按下不同的按键,视频如下:



调节电位器改变pwm波的占空比展示如下:



可以看到转动电位计的时候PWM波随转动而发生变化,从而使得响度发生变化,OLED上有所显示响度的大小变化,LED亮的数量也随响度增加到一定程度范围增加。

遇到的主要难题及解决方法

由于是第一次接触ti的开发板,不熟悉它的配置和使用,在sysconfig以及工程文件的结构上不太理解,导致有些库导入不正确或者路径设置不正确导致一些编译错误,但是多跟着例程配置一遍环境以及分析几次就基本能够熟悉环境的搭建、移植和使用了。

其次是PA15的引脚无法直接输出PWM波,作为初学者我们也不知道是什么原因,于是我们选择换一个引脚进行输出,并通过杜邦线连接到蜂鸣器。

然后是中断的使用我们有时候会遇到许多冲突,所以我们就尽量没有使用中断,因为项目比较简单,不使用中断也能够实现相应的效果。但是使用中断是更好的,能够节省MCU的相关资源,只是不太熟练,部分代码报错有些摸不着头脑。

未来的计划或建议

首先是自己对本项目的计划是能够使用中断进行按键的检测、电位计的取样以及DMA的转运,从而能对开发板有更加熟悉的认识以及使用,后续也会继续完成MSPM0L1306的后面几个任务加深理解和使用,对没有使用到的板载资源也会尝试去做一些小项目进行测试。

然后期待的是能够有更多的例程能够供初学者学习使用,因为刚学习stm32并不久,迁移到ti还有一些难点,如果能够有更多例程比如将板子资源都有一个小的例子驱动,既能规范我们代码的写法也能方便我们快速上手不必从一千多页的文档中苦苦搜搜。

附件下载
MSPM0L1306音乐键盘.rar
团队介绍
来自北京邮电大学本科生文豪
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号