基于MSPMOL1306套件制作的音乐键盘
该项目使用了Keil uVision5、C语言、lMSPMOL1306套件,实现了音乐键盘的设计,它的主要功能为:使用矩阵键盘、蜂鸣器、流水灯和电位计设计音乐键盘,让不同按键发出不同音调,电位计用来控制声音大小,流水灯用来显示声音档位。。
标签
测试
ADC
开发板
参加活动
Ccc111
更新2024-07-18
广东东软学院
146

1、项目介绍

  • 使用矩阵键盘、蜂鸣器、流水灯和电位计设计音乐键盘
  • 让不同按键发出不同音调
  • 电位计用来控制声音大小
  • 流水灯用来显示声音档位。

EVM_MSPMOL1306开发套件是基于MSPMOL1306开发板的基础上,集成了了FLASH、EEPROM、陀螺仪、矩阵键盘、OLED等多个模块的开发板子,同时可以直接通过TYPEC接口,利用板载DAP下载器去下载代码,特别方便。对于备战电赛的同学来说,可以快速上手学习。可用于电赛的控制类、测量类、信号类的题型。

2、设计思路

对于从来没有用过MSPMOL1306开发板的我来说,最难的地方可能就是开发环境、预计代码中的配置部分。经过在CSDN、b站上面搜索的信息中,我发现可以部署Keil的环境,基于Keil的环境去写代码,这样子就不用再去熟悉一个新的IDE,并且可以通过SysConfig这个软件去完成我的配置部分的工作,增加了我的工作效率。所以我们接下来只需要下载3个东西,分别是MSPM0 SDK、Keil、SysConfig。再解决完开发环境的问题之后,这个项目的任务就已经变得没有那么困难了,只需要将任务里的东西一步步实现,然后再将它们都组合起来就可以完成了。

3、环境部署

  • MSPM0 SDK

MSPM0 SDK是一个有大量资源的文件,里面有基于MSPMOL1306(不仅限1306)的各种demo,可以让你根据demo中的代码,快速上手使用这个开发板子,同时里面的tool文件夹中也有可以部署你keil环境的文件。需要使用的同学可以自行去TI的官网下载MSPM0-SDK Software development kit (SDK) | TI.com


  • Keil

相信Keil是一个大家都很熟练的软件了,在这里就不多做对Keil的介绍了,这里需要注意的一个点是似乎如果要使用keil去启动SysConfig的配置的话,版本必须安装 5.38 及以后的版本(在群里似乎见过因为Keil版本问题而配置失败的)。点击左上角的Help选择About uVision可以查看你的Keil版本。


  • SysConfig

这个软件就是直接将你需要使用的外设的配置软件了,类似与HAL。它也是我在这次活动中使用次数最多的软件,通过这个软件你可以进行简单的引脚配置,后文中我也会去将配置的具体参数展示出来。需要使用的同学可以自行去TI的官网下载SYSCONFIG IDE, configuration, compiler or debugger | TI.com


  • 环境

在安装了上面这些软件之后,打开MSPM0 SDK然后打开tools/keil目录,找到MSPM0_SDK_syscfg_menu_import.cfg,打开里面修开SDK和SysConfig的地址,修改为自己的安装路径。下面那张图就是我修改之后的文件,需要修改的项是Command以及Initial Foleder后面的,如果下载的SDK和Sysconfig版本不一样,第一行也需要修改版本号。

在修改完之后,打开Keil,选择Tools,选择最下方那个选项,打开之后选择Import,导入你刚刚修改的文件,就可以在Keil直接打开Sysconfig

image.png

image.png

(注意需要在这个文档上选择tools使用Sysconfig)image.png

4、硬件框图

  • 蜂鸣器

题目要求中,将蜂鸣器发出不同的音调。配合上板子上的无源蜂鸣器,我想大家肯定会想到通过改变的频率去改变蜂鸣器的震动频率,从而发出do、re、mi、fa、so、la、xi等音调,并且还可以控制发出高低音。(需要使用跳帽把蜂鸣器与PWM引脚连接起来)

  • 矩阵键盘

题目要求中,按下不同的按键可以发出不同音调,配合上板子上的矩阵键盘,可以很快完成这个功能。在核心板的下方,有一个拨码开关,只需要将它们向下拨,就可以将矩阵键盘的所有行上拉。然后根据逐行扫描法,就可以完成键盘这个功能。(拔模开关向下打之后PA0、PA1、PA7、PA12全部被拉高)

  • 电位计

题目要求中可以改变声音的大小,配合板子上的电位计,经过ADC转换,可以是实现这个功能。(同样需要跳帽将电位计与ADC引脚连接起来)

  • 流水灯

题目中要求用LED流水灯去展示当前声音的挡位,这个就不多做叙述了。(发现LED与很多SPI的引脚连接在一起,初始化的时候需要注意,不然可能会导致使用不正常)

5、功能实现

  • 发出不同的音调

让蜂鸣器发出不同的声音,其实就是改变PWM的频率就可以改变的,但是我们首先就要配置一个简单的PWM配置。打开Sysconfig,可以看到定时器下面有个Timer-PWM选项,选中之后电机右上方ADD选项

image.png

在这里我简单的讲一下这些配置的是什么东西

9025be3807e4664b1e6d092248a627c.png

配置之后我们可以看到ti_msp_dl_config.h与ti_msp_dl_config.c多了你的配置代码

ti_msp_dl_config.h

/* Defines for PWM_0 */
#define PWM_0_INST TIMG4
#define PWM_0_INST_IRQHandler TIMG4_IRQHandler
#define PWM_0_INST_INT_IRQN (TIMG4_INT_IRQn)
#define PWM_0_INST_CLK_FREQ 32000000
/* GPIO defines for channel 1 */
#define GPIO_PWM_0_C1_PORT GPIOA
#define GPIO_PWM_0_C1_PIN DL_GPIO_PIN_15
#define GPIO_PWM_0_C1_IOMUX (IOMUX_PINCM16)
#define GPIO_PWM_0_C1_IOMUX_FUNC IOMUX_PINCM16_PF_TIMG4_CCP1
#define GPIO_PWM_0_C1_IDX DL_TIMER_CC_1_INDEX

ti_msp_dl_config.c

static const DL_TimerG_ClockConfig gPWM_0ClockConfig = {
.clockSel = DL_TIMER_CLOCK_BUSCLK,
.divideRatio = DL_TIMER_CLOCK_DIVIDE_1,
.prescale = 31U
};

static const DL_TimerG_PWMConfig gPWM_0Config = {
.pwmMode = DL_TIMER_PWM_MODE_EDGE_ALIGN_UP,
.period = 0,
.startTimer = DL_TIMER_START,
};

SYSCONFIG_WEAK void SYSCFG_DL_PWM_0_init(void) {

DL_TimerG_setClockConfig(
PWM_0_INST, (DL_TimerG_ClockConfig *) &gPWM_0ClockConfig);

DL_TimerG_initPWMMode(
PWM_0_INST, (DL_TimerG_PWMConfig *) &gPWM_0Config);

DL_TimerG_setCaptureCompareOutCtl(PWM_0_INST, DL_TIMER_CC_OCTL_INIT_VAL_LOW,
DL_TIMER_CC_OCTL_INV_OUT_ENABLED, DL_TIMER_CC_OCTL_SRC_FUNCVAL,
DL_TIMERG_CAPTURE_COMPARE_1_INDEX);

DL_TimerG_setCaptCompUpdateMethod(PWM_0_INST, DL_TIMER_CC_UPDATE_METHOD_IMMEDIATE, DL_TIMERG_CAPTURE_COMPARE_1_INDEX);

DL_TimerG_enableClock(PWM_0_INST);

DL_TimerG_setCCPDirection(PWM_0_INST , DL_TIMER_CC1_OUTPUT );

}

GPIO的初始化以及使能部分就不再具体展示,主要展示配置的属性部分

修改PWM的频率部分,我找了一下他库里面的函数,找到了这个可以改变PWM波周期的函数

int MusicF[8] = {0,1698,1513,1429,1272,1134,1010,899 };//提前将do,re,mi,fa,so,la,xi的频率对应周期存下来
DL_TimerG_setLoadValue(PWM_0_INST, MusicF[0]);//重新装载PWM周期
delay_ms(100);
DL_TimerG_setLoadValue(PWM_0_INST, MusicF[1]);//重新装载PWM周期
delay_ms(100);
DL_TimerG_setLoadValue(PWM_0_INST, MusicF[2]);//重新装载PWM周期
delay_ms(100);
DL_TimerG_setLoadValue(PWM_0_INST, MusicF[3]);//重新装载PWM周期
delay_ms(100);
DL_TimerG_setLoadValue(PWM_0_INST, MusicF[4]);//重新装载PWM周期
delay_ms(100);
DL_TimerG_setLoadValue(PWM_0_INST, MusicF[5]);//重新装载PWM周期
delay_ms(100);
DL_TimerG_setLoadValue(PWM_0_INST, MusicF[6]);//重新装载PWM周期
delay_ms(100);
DL_TimerG_setLoadValue(PWM_0_INST, MusicF[7]);//重新装载PWM周期
delay_ms(100);

PWM的频率 = 时钟频率 / ((分频系数+1 )* (周期+1))

  • 矩阵键盘

Key.c

参考了硬禾科技发出来的代码,用逐行扫描法,逐渐每一行拉低低电平,比如第一行拉为低电平的时候,其他行为高点平,这个时候读取每一列的GPIO,如果某一列的读取到低电平,就意味着第一行的那一列被按下了。

//矩阵键盘宏定义
/* Port definition for Pin Group MAT_KEY */
#define MAT_KEY_PORT (GPIOA)
/* Defines for ROW0: GPIOA.0 with pinCMx 1 on package pin 1 */
#define MAT_KEY_ROW0_PIN (DL_GPIO_PIN_0)
#define MAT_KEY_ROW0_IOMUX (IOMUX_PINCM1)
/* Defines for ROW1: GPIOA.1 with pinCMx 2 on package pin 2 */
#define MAT_KEY_ROW1_PIN (DL_GPIO_PIN_1)
#define MAT_KEY_ROW1_IOMUX (IOMUX_PINCM2)
/* Defines for ROW2: GPIOA.7 with pinCMx 8 on package pin 11 */
#define MAT_KEY_ROW2_PIN (DL_GPIO_PIN_7)
#define MAT_KEY_ROW2_IOMUX (IOMUX_PINCM8)
/* Defines for ROW3: GPIOA.12 with pinCMx 13 on package pin 16 */
#define MAT_KEY_ROW3_PIN (DL_GPIO_PIN_12)
#define MAT_KEY_ROW3_IOMUX (IOMUX_PINCM13)
/* Defines for COL0: GPIOA.13 with pinCMx 14 on package pin 17 */
#define MAT_KEY_COL0_PIN (DL_GPIO_PIN_13)
#define MAT_KEY_COL0_IOMUX (IOMUX_PINCM14)
/* Defines for COL1: GPIOA.14 with pinCMx 15 on package pin 18 */
#define MAT_KEY_COL1_PIN (DL_GPIO_PIN_14)
#define MAT_KEY_COL1_IOMUX (IOMUX_PINCM15)
/* Defines for COL2: GPIOA.17 with pinCMx 18 on package pin 21 */
#define MAT_KEY_COL2_PIN (DL_GPIO_PIN_17)
#define MAT_KEY_COL2_IOMUX (IOMUX_PINCM18)
/* Defines for COL3: GPIOA.18 with pinCMx 19 on package pin 22 */
#define MAT_KEY_COL3_PIN (DL_GPIO_PIN_18)
#define MAT_KEY_COL3_IOMUX (IOMUX_PINCM19)
#include "ti_msp_dl_config.h"

uint8_t Key()
{
uint8_t KeyNum = 0;
//行扫描
//ROW 0111
//0111-1011-1101-1110
DL_GPIO_clearPins(MAT_KEY_PORT, MAT_KEY_ROW0_PIN);
DL_GPIO_setPins(MAT_KEY_PORT, MAT_KEY_ROW1_PIN | MAT_KEY_ROW2_PIN | MAT_KEY_ROW3_PIN);
delay_ms(10);
if(!DL_GPIO_readPins(MAT_KEY_PORT, MAT_KEY_COL0_PIN))
{
KeyNum = 1;
}
else if(!DL_GPIO_readPins(MAT_KEY_PORT, MAT_KEY_COL1_PIN))
{
KeyNum = 2;
}
else if(!DL_GPIO_readPins(MAT_KEY_PORT, MAT_KEY_COL2_PIN))
{
KeyNum = 3;
}
else if(!DL_GPIO_readPins(MAT_KEY_PORT, MAT_KEY_COL3_PIN))
{
KeyNum = 4;
}
// 行扫描
// ROW 1011
DL_GPIO_clearPins(MAT_KEY_PORT, MAT_KEY_ROW1_PIN);
DL_GPIO_setPins(MAT_KEY_PORT, MAT_KEY_ROW0_PIN | MAT_KEY_ROW2_PIN | MAT_KEY_ROW3_PIN);
delay_ms(10);
if (!DL_GPIO_readPins(MAT_KEY_PORT, MAT_KEY_COL0_PIN))
{
KeyNum = 5;
}
else if (!DL_GPIO_readPins(MAT_KEY_PORT, MAT_KEY_COL1_PIN))
{
KeyNum = 6;
}
else if (!DL_GPIO_readPins(MAT_KEY_PORT, MAT_KEY_COL2_PIN))
{
KeyNum = 7;
}
else if (!DL_GPIO_readPins(MAT_KEY_PORT, MAT_KEY_COL3_PIN))
{
KeyNum = 8;
}
return KeyNum;
}
  • 修改声音的大小

现在我们解决了发出不同声调的声音,但是题目中还要求我们可以控制声音的大小,这个也可以通过直接改变PWM的占空比就可以改变,也就是占空比越大,那么蜂鸣器的声音就会越大,所以我就将电位器与改变PWM波的占空比两者结合在一起,也就是旋转我们板子上的电位器,就可以改变PWM的占空比。那么使用电位器就要需要使用上ADC功能了,继续打开我们的Sysconfig,配置ADC部分。

04f4e301c3fbb735ffc21985561dc4a.png


493d289d8ed2aed35a1b92e673cec93.png

e11e0337582e01e31655439e471bdaf.png

在这里和大家说一个小技巧,如果你不清楚你配置的引脚,或者不确定你现在配置的是哪个引脚,那你可以查看Sysconfig里面的芯片图片,可以直接看到你引脚的信息十分方便。只需要把鼠标移动到对应的引脚上面,就可以看到该引脚上的资源,如果标绿了就是你配置好的功能。

1431a54d17444eb47f3761a42655f76.png

ti_msp_dl_config.h

//ADC宏定义
/* Defines for ADC12_0 */
#define ADC12_0_INST ADC0
#define ADC12_0_INST_IRQHandler ADC0_IRQHandler
#define ADC12_0_INST_INT_IRQN (ADC0_INT_IRQn)
#define ADC12_0_ADCMEM_0 DL_ADC12_MEM_IDX_0
#define ADC12_0_ADCMEM_0_REF DL_ADC12_REFERENCE_VOLTAGE_VDDA
#define ADC12_0_ADCMEM_0_REF_VOLTAGE_V 3.3
#define GPIO_ADC12_0_C0_PORT GPIOA
#define GPIO_ADC12_0_C0_PIN DL_GPIO_PIN_27

ti_msp_dl_config.c

/* ADC12_0 Initialization */
//ADC转换完成的标识,在ADC的中断里设为有效;
volatile bool gCheckADC;
volatile uint16_t gAdcResult;

static const DL_ADC12_ClockConfig gADC12_0ClockConfig = {
.clockSel = DL_ADC12_CLOCK_SYSOSC,
.divideRatio = DL_ADC12_CLOCK_DIVIDE_1,
.freqRange = DL_ADC12_CLOCK_FREQ_RANGE_24_TO_32,
};
SYSCONFIG_WEAK void SYSCFG_DL_ADC12_0_init(void)
{
DL_ADC12_setClockConfig(ADC12_0_INST, (DL_ADC12_ClockConfig *) &gADC12_0ClockConfig);
DL_ADC12_initSingleSample(ADC12_0_INST,
DL_ADC12_REPEAT_MODE_DISABLED, DL_ADC12_SAMPLING_SOURCE_AUTO, DL_ADC12_TRIG_SRC_SOFTWARE,
DL_ADC12_SAMP_CONV_RES_8_BIT, DL_ADC12_SAMP_CONV_DATA_FORMAT_UNSIGNED);
DL_ADC12_configConversionMem(ADC12_0_INST, ADC12_0_ADCMEM_0,
DL_ADC12_INPUT_CHAN_0, DL_ADC12_REFERENCE_VOLTAGE_VDDA, DL_ADC12_SAMPLE_TIMER_SOURCE_SCOMP1, DL_ADC12_AVERAGING_MODE_DISABLED,
DL_ADC12_BURN_OUT_SOURCE_DISABLED, DL_ADC12_TRIGGER_MODE_AUTO_NEXT, DL_ADC12_WINDOWS_COMP_MODE_DISABLED);
DL_ADC12_setSampleTime0(ADC12_0_INST,0);
DL_ADC12_setSampleTime1(ADC12_0_INST,32000);
/* Enable ADC12 interrupt */
DL_ADC12_clearInterruptStatus(ADC12_0_INST,(DL_ADC12_INTERRUPT_MEM0_RESULT_LOADED));
DL_ADC12_enableInterrupt(ADC12_0_INST,(DL_ADC12_INTERRUPT_MEM0_RESULT_LOADED));
DL_ADC12_enableConversions(ADC12_0_INST);
}

ADC采样中断函数

void ADC12_0_INST_IRQHandler(void)
{
switch(DL_ADC12_getPendingInterrupt(ADC12_0_INST))
{
case DL_ADC12_IIDX_MEM0_RESULT_LOADED:
//结果存储寄存器有更新
gCheckADC = true;
gAdcResult = DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_0);
break;
default:
break;
}
}

做好这些工作之后,我们就可以使用ADC了,当然使用前还需要初始化一下

image.png

并且因为我们配置的时候选择了软件触发,所以还需要使用这个函数

1716194876259.png

  • LED流水灯

这个就是很简单的工作了,相信大家也很熟悉了。

Led.c

#include "ti_msp_dl_config.h"

void LedFind(uint8_t num)
{
switch(num)
{
case 1:
DL_GPIO_setPins(LED_PORT,LED_LED1_PIN| LED_LED2_PIN | LED_LED3_PIN);
DL_GPIO_clearPins(LED_PORT,LED_LED0_PIN);
break;
case 2:
DL_GPIO_setPins(LED_PORT,LED_LED2_PIN | LED_LED3_PIN);
DL_GPIO_clearPins(LED_PORT,LED_LED0_PIN | LED_LED1_PIN);
break;
case 3:
DL_GPIO_setPins(LED_PORT,LED_LED3_PIN);
DL_GPIO_clearPins(LED_PORT,LED_LED0_PIN| LED_LED1_PIN | LED_LED2_PIN);
break;
case 4:
DL_GPIO_clearPins(LED_PORT,LED_LED0_PIN| LED_LED1_PIN | LED_LED2_PIN | LED_LED3_PIN);
break;
}
}

ti_msp_dl_config.h

//LED宏定义
/* Port definition for Pin Group LED */
#define LED_PORT (GPIOA)
/* Defines for LED0: GPIOA.8 with pinCMx 9 on package pin 12 */
#define LED_LED0_PIN (DL_GPIO_PIN_8)
#define LED_LED0_IOMUX (IOMUX_PINCM9)
/* Defines for LED1: GPIOA.9 with pinCMx 10 on package pin 13 */
#define LED_LED1_PIN (DL_GPIO_PIN_9)
#define LED_LED1_IOMUX (IOMUX_PINCM10)
/* Defines for LED2: GPIOA.10 with pinCMx 11 on package pin 14 */
#define LED_LED2_PIN (DL_GPIO_PIN_10)
#define LED_LED2_IOMUX (IOMUX_PINCM11)
/* Defines for LED3: GPIOA.11 with pinCMx 12 on package pin 15 */
#define LED_LED3_PIN (DL_GPIO_PIN_11)
#define LED_LED3_IOMUX (IOMUX_PINCM12)
/* Defines for LED4: GPIOA.2 with pinCMx 3 on package pin 6 */
#define LED_LED4_PIN (DL_GPIO_PIN_2)
#define LED_LED4_IOMUX (IOMUX_PINCM3)
/* Defines for LED5: GPIOA.3 with pinCMx 4 on package pin 7 */
#define LED_LED5_PIN (DL_GPIO_PIN_3)
#define LED_LED5_IOMUX (IOMUX_PINCM4)
/* Defines for LED6: GPIOA.15 with pinCMx 16 on package pin 19 */
#define LED_LED6_PIN (DL_GPIO_PIN_15)
#define LED_LED6_IOMUX (IOMUX_PINCM16)
/* Defines for LED7: GPIOA.4 with pinCMx 5 on package pin 8 */
#define LED_LED7_PIN (DL_GPIO_PIN_4)
#define LED_LED7_IOMUX (IOMUX_PINCM5)

6、主要代码片段说明

基于我自己的习惯, 我将主要的控制代码放到了定时中断函数里面去处理。首先我提前测试出了七个音调频率对应的周期,存在一个名为MusicF的数组里面,在我通过按键去改变频率的时候,按键返回的值就是对应了数组的下标,然后就可以产生不同的音调了。当我不按下按键的时候,默认返回0,对应MusicF[0]就是0。

image.png

随后软件开启ADC转换,因为我用的是8bits,也就是得到的范围为0到2的八次方,并且根据采集回来不同的值亮不同的灯。

image.png

最后是声音的改变,我一开始是想着,在四个ADC范围里面(0~64,65~128,127~192,193~256)设置四个占空比,后来发现这样子电位器的旋转就是作为一个无用功,而且如果这样子做的话会使得声音是一个突变,特别突兀。所以我就想把他做成随着电位器旋转,然后声音线性的变大或者变小。所以我对他设置占空比的函数进行了一个修改,没有直接给ADC转换后的数字值,我将他的范围等比例缩小到0~30。

b134625bd518a91486ad01ae3e7d6ba.jpg

7、遇到的困难

Sysconfig的不熟悉,不了解图形化软件配置的过程,软件没有汉化难以使用。解决方法,通过B站,CSDN等多个平台去学习软件的使用教程,通过一些实例去了解基本的初始化流程,使用翻译软件增加工作效率。

8、未来的建议

拓展一些多的功能,让这个项目增加一些用户交互的功能,更加全面。

  • 比如使用显示屏显示当前音量,可以引用一些UI界面增加用户交互的功能。
  • 矩阵键盘多增加几个对应频率的音调,比如说高音的以及低音的
  • 可以快速的通过开关去”一键静音“,”一键开启“。





附件下载
empty_project.rar
Keil
团队介绍
cc
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号