用EVM_MSPM0L1306制作的电子琴
该项目使用了EVM_MSPM0L1306,实现了使用矩阵键盘、蜂鸣器、流水灯和电位计设计音乐键盘,让不同按键发出不同音调,电位计用来控制声音大小,流水灯用来显示声音档位。的设计,它的主要功能为:基于EVM_MSPM0L1306制作的电子琴。使用PWM驱动蜂鸣器发声,PWM频率决定音调,高低电平占空比决定声音大小。。
标签
嵌入式系统
测试
显示
开发板
aramy
更新2024-07-18
37

硬件介绍:
EVM_MSPM0L1306开发板搭载了MSPM0L1306微控制器,该芯片基于高性能的Arm® Cortex®-M0+内核,兼具低功耗与高效能特性。开发板配备了多种外设和接口,包括ADC、DAC、UART、SPI、I2C、PWM等,还搭配1.3寸OLED显示屏

板子分两个部分,一块是核心板,集成了MSPM0L1306微控制器,并引出了各个引脚。一块是扩展板,扩展板采用Type-C接口供给5V电源,同时为核心板提供电源,并可向外输出5V、3.3V电源电压。扩展板集成了Debug调试器及虚拟串口,可通过SWD模式进行代码烧录和Debug调试,通过虚拟串口进行通信,集成SPI协议的1.3寸OLED显示屏、FLASH、陀螺仪、IIC协议的EEPROM,以及常见的矩阵键盘、蜂鸣器、8位LED、8位拨码开关等外设,并提供蓝牙、WiFi无线模块二选一接口。

任务选择:
第一次接触MSPM0L1306这款芯片,处于学习入门的阶段。所以选择难度较小的任务一:使用矩阵键盘、蜂鸣器、流水灯和电位计设计音乐键盘,让不同按键发出不同音调,电位计用来控制声音大小,流水灯用来显示声音档位。

对任务简单分析。矩阵键盘,一共16个按键,占用8个IO口,8个IO口,形成4X4的矩阵,读取对应的IO口的高低电平,即可获取键盘信息。蜂鸣器,这里的蜂鸣器是一个无源蜂鸣器,要想驱动蜂鸣器发出声音,就需要提供指定频率的波来驱动。单片机可以使用PWM方波来驱动蜂鸣器,通过控制PWM方波的频率,控制蜂鸣器的音调;控制高低电平占空比,控制蜂鸣器声音的大小。还有一个电位计,可以通过ADC来读取电位器的电压,来获得旋钮的调整。

任务实现:

1、系统初始化。这里开发工具我使用的是Keil+sysconfig。首先使用sysconfig进行配置。要驱动蜂鸣器,就需要配置PWM。系统时钟32M赫兹,使用256分频,这样PWM的频率范围可以在1.91Hz~62.5kHz之间选择,普通声音的频率在2000Hz以下,满足需求。

电位器使用管脚A27与单片机相连,设置ADC,使用单次读取的方式去读取电位器的电压值。

image.png

板子上还有个OLED,将它驱动起来,使用硬件SPI进行驱动。

image.png

再配置一下串口,串口是调试用的好帮手,可以很方便地通过串口去调试程序。

image.png

最后就是LED灯和键盘的GPIO了,键盘使用横竖4X4矩阵的方式读取,使用8个GPIO,但是LED灯遇到个麻烦,led灯的gpio口部分和spi、串口的gpio复用了,使用了spi和串口,就导致有些led灯无法控制,这里只剩下4颗led灯可以控制了。

image.png

2、编码实现

ADC读取部分参考了官方的例程。由于使用的是单次ADC读取,所以每次读取后都需要重置ADC状态,以便下次读取。ADC读取使用中断来判断是否读取完成。

volatile bool gCheckADC = false,changeStat=false;
// 读取电位器的值,电位器的值 从0~3715,最终是用来驱动蜂鸣器,方波占空比不应该超过50%
uint16_t getADCValue()
{
DL_ADC12_startConversion(ADC12_0_INST);
while (false == gCheckADC)
{
}
uint16_t gAdcResult = DL_ADC12_getMemResult(ADC12_0_INST, DL_ADC12_MEM_IDX_0);
gCheckADC = false;
DL_ADC12_enableConversions(ADC12_0_INST);
return gAdcResult;
}

void ADC12_0_INST_IRQHandler(void)
{
switch (DL_ADC12_getPendingInterrupt(ADC12_0_INST))
{
case DL_ADC12_IIDX_MEM0_RESULT_LOADED:
gCheckADC = true;
break;
default:
break;
}
}

蜂鸣器是一个无源蜂鸣器,由一个三极管驱动。看电路图,蜂鸣器旁边有跳线,可以直接与PA15短接,但是PA15被LED灯使用了,这里就是用空闲端口PA22作为PWM输出管脚,来驱动蜂鸣器,使用杜邦线连接。通过修改PWM频率来决定音调,修改占空比来决定音量。实验发现低电平占比从0%~10%声音大小是有明显变化的,超过10%后音量基本无变化了。所以音量控制就从0~10变化。电位器是一个精密电位器,旋转幅度很大,读取值从0~3715,需要旋转好几圈,不方便操作,所以电位器也限制取值在0~500之间,将电位器ADC读取的值映射到0~10的音量上去。

#include "pwmctl.h"
//修改pwm频率,PWM 频率构成 以 PWM_0_INST_CLK_FREQ 值为基数 输出频率=PWM_0_INST_CLK_FREQ/period
//入口 需要的频率
volatile uint16_t currPPC;
uint16_t UpdateFrequency(uint16_t audiofrequency)
{
DL_TimerG_PWMConfig DL_TimerG_PWMConfig_RC;
currPPC=PWM_0_INST_CLK_FREQ / audiofrequency;
DL_TimerG_PWMConfig_RC.period = currPPC;
DL_TimerG_PWMConfig_RC.pwmMode = DL_TIMER_PWM_MODE_EDGE_ALIGN_UP;
DL_TimerG_PWMConfig_RC.startTimer = DL_TIMER_START;

// DL_TIMER_STOP
DL_TimerG_initPWMMode(
PWM_0_INST, (DL_TimerG_PWMConfig *)&DL_TimerG_PWMConfig_RC);
return currPPC;
}
//修改占空比 0 输出3.3v电压,
void setPwmDuty(uint16_t setval){
DL_TimerG_setCaptureCompareValue(PWM_0_INST, currPPC-(currPPC*setval)/100, DL_TIMER_CC_1_INDEX);
}

增加个OLED的显示,使用硬件SPI驱动。显示两个内容。1、当前声音频率信息(期望值和实际值)。2、音量大小值(0~10),并且使用剩下端口驱动的4颗LED灯同步展示音量大小。

#include "oled_spi.h"
#include "oledfont.h"

// 向SSD1306写入一个字节。
// dat:要写入的数据/命令
// cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat, u8 cmd)
{
if (cmd)
{
OLED_DC_Set();
}
else
{
OLED_DC_Clr();
}

while (DL_SPI_isBusy(SPI_OLED_INST))
{
}
DL_SPI_transmitData8(SPI_OLED_INST, dat);
while (DL_SPI_isBusy(SPI_OLED_INST))
{
}

OLED_DC_Set();
}

void OLED_Set_Pos(unsigned char x, unsigned char y)
{
OLED_WR_Byte(0xb0 + y, OLED_CMD);
OLED_WR_Byte(((x & 0xf0) >> 4) | 0x10, OLED_CMD);
OLED_WR_Byte((x & 0x0f) | 0x01, OLED_CMD);
}
// 开启OLED显示
void OLED_Display_On(void)
{
OLED_WR_Byte(0X8D, OLED_CMD); // SET DCDC命令
OLED_WR_Byte(0X14, OLED_CMD); // DCDC ON
OLED_WR_Byte(0XAF, OLED_CMD); // DISPLAY ON
}
// 关闭OLED显示
void OLED_Display_Off(void)
{
OLED_WR_Byte(0X8D, OLED_CMD); // SET DCDC命令
OLED_WR_Byte(0X10, OLED_CMD); // DCDC OFF
OLED_WR_Byte(0XAE, OLED_CMD); // DISPLAY OFF
}
// 清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
void OLED_Clear(void)
{
u8 i, n;
for (i = 0; i < 8; i++)
{
OLED_WR_Byte(0xb0 + i, OLED_CMD); // 设置页地址(0~7)
OLED_WR_Byte(0x02, OLED_CMD); // 设置显示位置—列低地址
OLED_WR_Byte(0x10, OLED_CMD); // 设置显示位置—列高地址
for (n = 0; n < 128; n++)
OLED_WR_Byte(0, OLED_DATA);
} // 更新显示
}

// 在指定位置显示一个字符,包括部分字符
// x:0~127
// y:0~7 (页地址)
void OLED_ShowChar(u8 x, u8 y, char chr)
{
unsigned char c = 0, i = 0;
c = chr - ' '; // 得到偏移后的值
if (x > Max_Column - 1)
{
x = 0;
y = y + 2;
}
if (SIZE == 16)
{
OLED_Set_Pos(x, y);
for (i = 0; i < 8; i++)
OLED_WR_Byte(F8X16[c * 16 + i], OLED_DATA);
OLED_Set_Pos(x, y + 1);
for (i = 0; i < 8; i++)
OLED_WR_Byte(F8X16[c * 16 + i + 8], OLED_DATA);
}
else
{
OLED_Set_Pos(x, y + 1);
for (i = 0; i < 6; i++)
OLED_WR_Byte(F6x8[c][i], OLED_DATA);
}
}
// m^n函数
u32 oled_pow(u8 m, u8 n)
{
u32 result = 1;
while (n--)
result *= m;
return result;
}
// 显示数字
// x,y :起点坐标
// len :数字的位数
// size2:字体大小
// num:数值(0~4294967295);
void OLED_ShowNum(u8 x, u8 y, u32 num, u8 len, u8 size2)
{
u8 t, temp;
u8 enshow = 0;
for (t = 0; t < len; t++)
{
temp = (num / oled_pow(10, len - t - 1)) % 10;
if (enshow == 0 && t < (len - 1))
{
if (temp == 0)
{
OLED_ShowChar(x + (size2 / 2) * t, y, ' ');
continue;
}
else
enshow = 1;
}
OLED_ShowChar(x + (size2 / 2) * t, y, temp + '0');
}
}
// 显示一个字符号串
void OLED_ShowString(u8 x, u8 y, char *chr)
{
unsigned char j = 0;
while (chr[j] != '\0')
{
OLED_ShowChar(x, y, chr[j]);
x += 8;
if (x > 120)
{
x = 0;
y += 2;
}
j++;
}
}
// 显示汉字
void OLED_ShowCHinese(u8 x, u8 y, u8 no)
{
u8 t, adder = 0;
OLED_Set_Pos(x, y);
for (t = 0; t < 16; t++)
{
OLED_WR_Byte(Hzk[2 * no][t], OLED_DATA);
adder += 1;
}
OLED_Set_Pos(x, y + 1);
for (t = 0; t < 16; t++)
{
OLED_WR_Byte(Hzk[2 * no + 1][t], OLED_DATA);
adder += 1;
}
}
/***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为需要使用的页的范围1~8*****************/
void OLED_DrawBMP(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, const unsigned char BMP[])
{
unsigned int j = 0;
unsigned char x, y;

if (y1 % 8 == 0)
y = y1 / 8;
else
y = y1 / 8 + 1;
for (y = y0; y < y1; y++)
{
OLED_Set_Pos(x0, y);
for (x = x0; x < x1; x++)
{
OLED_WR_Byte(BMP[j++], OLED_DATA);
}
}
}

// 初始化SSD1306
void OLED_Init(void)
{
OLED_WR_Byte(0xAE, OLED_CMD); //--turn off oled panel
OLED_WR_Byte(0x02, OLED_CMD); //---set low column address
OLED_WR_Byte(0x10, OLED_CMD); //---set high column address
OLED_WR_Byte(0x40, OLED_CMD); //--set start line address Set Mapping RAM Display Start Line (0x00~0x3F)
OLED_WR_Byte(0x81, OLED_CMD); //--set contrast control register
OLED_WR_Byte(0xCF, OLED_CMD); // Set SEG Output Current Brightness
OLED_WR_Byte(0xA1, OLED_CMD); //--Set SEG/Column Mapping 0xa0左右反置 0xa1正常
OLED_WR_Byte(0xC8, OLED_CMD); // Set COM/Row Scan Direction 0xc0上下反置 0xc8正常
OLED_WR_Byte(0xA6, OLED_CMD); //--set normal display
OLED_WR_Byte(0xA8, OLED_CMD); //--set multiplex ratio(1 to 64)
OLED_WR_Byte(0x3f, OLED_CMD); //--1/64 duty
OLED_WR_Byte(0xD3, OLED_CMD); //-set display offset Shift Mapping RAM Counter (0x00~0x3F)
OLED_WR_Byte(0x00, OLED_CMD); //-not offset
OLED_WR_Byte(0xd5, OLED_CMD); //--set display clock divide ratio/oscillator frequency
OLED_WR_Byte(0x80, OLED_CMD); //--set divide ratio, Set Clock as 100 Frames/Sec
OLED_WR_Byte(0xD9, OLED_CMD); //--set pre-charge period
OLED_WR_Byte(0xF1, OLED_CMD); // Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
OLED_WR_Byte(0xDA, OLED_CMD); //--set com pins hardware configuration
OLED_WR_Byte(0x12, OLED_CMD);
OLED_WR_Byte(0xDB, OLED_CMD); //--set vcomh
OLED_WR_Byte(0x40, OLED_CMD); // Set VCOM Deselect Level
OLED_WR_Byte(0x20, OLED_CMD); //-Set Page Addressing Mode (0x00/0x01/0x02)
OLED_WR_Byte(0x02, OLED_CMD); //
OLED_WR_Byte(0x8D, OLED_CMD); //--set Charge Pump enable/disable
OLED_WR_Byte(0x14, OLED_CMD); //--set(0x10) disable
OLED_WR_Byte(0xA4, OLED_CMD); // Disable Entire Display On (0xa4/0xa5)
OLED_WR_Byte(0xA6, OLED_CMD); // Disable Inverse Display On (0xa6/a7)
OLED_WR_Byte(0xAF, OLED_CMD); //--turn on oled panel

OLED_WR_Byte(0xAF, OLED_CMD); /*display ON*/
OLED_Clear();
OLED_Set_Pos(0, 0);
}

效果展示:

image.png

image.png

image.png

心得体会
第一次接触MSPM0L1306开发板,在各位老师带领下,入门过程非常顺畅。板子非常好玩,功能强劲。感谢电子森林带来的这次活动。期待各位老师们的作品,继续学习。

附件下载
msp_project.zip
团队介绍
单片机业余爱好者,瞎捣鼓小能手。
团队成员
aramy
单片机业余爱好者,瞎捣鼓小能手。
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号