stm32108键C-B全调性_动态可视化乐谱钢琴
108键全调性钢琴
- 一 基本介绍
- 1 项目简介
- 2 实现方式
- 3 项目构成
- 二 实现过程
- 0 前置基本外设驱动
- 1 声音控制
- 2 乐谱录入&基础乐理
- 3 点阵屏谱点动态刷新
- 4 项目交互控制
- 5 录入新曲子过程
- 三 展示,与链接视频地址
- 1 主要功能函数一览
- 2 下载链接
- 3 视频效果
一 基本介绍
本章为项目整体介绍
1 项目简介
本项目通过摇杆模块控制模式切换,TFTST7735S屏幕LED与点阵屏Max7219显示交互效果,喇叭输出声音。摇杆可控制8种模式,摇杆Z轴控制确定返回:
- 播放音乐1-2-3
- 控制声音:增加,减小,串口直接设定具体值
- 摇杆音乐模式
- 点阵谱点开关
下为具体特点介绍:
(1)广泛音调支持
支持从1=CC#DD#EFF#GG#AA#B的所有音调,还可以设置大小调,或者用升降半调作为起始音,还能使用不同的八度区作为起始音等级,完美适配数字简谱,几乎可以演奏你熟悉的任何曲子。内部代码会根据设置的初始参数,算出该调性下其余一百零七个键的频率,不需要提前打表一堆数组,节省空间。
(2)精准还原声音
因为支持修改每个音符的升降半调和升降度,并能精准控制音长节拍,所以播放音乐分辨率很高,音准极好,类似一台一百零八键全频率的电子钢琴。录入了3首音乐:含克罗地亚狂想曲与梦回还。
(3)使用简单
录一首谱子只需:一个纯数字的一维乐谱数组,和一个一键设置调性的函数,用来配置新曲子。前者只要按照顺序和一定的规律对着数字简谱抄数字,后者更简单,根据简谱信息填两三个字母数字。不用懂很多,只要会看数字简谱。可播放任何单手数字简谱。
(4)点阵灯谱扫描
可以通过点阵屏显示不同音符和节拍长度。一条扫描线从顶部扫描至底部,每个音符都有长度与不同位置,触碰到扫描线即消失,类似音游。有两个注意点,一是如何让扫描线和声音同步变化,对时序必须严格控制。二是如何及时更新不同音符在点阵屏上的谱点,因为点阵屏长度有限,所以一定会遇到某个音符触碰底部,部分被截断的情况,需要及时在屏幕顶部恢复被截断的信息。
详细效果,见底部视频
项目涉及到许多常用外设,如用像是
AD控制摇杆模块,脉宽调制PWM控制声音,串口收发数据,SPI,IIC`等基础知识。
2 实现方式
- 音符 1-7:PWM控制喇叭发出不同声音。
- 不同调性:了解乐理基础,根据数字简谱,提取参数,编写算法。
- 乐谱录入:一个音符含:
度数/本身频率/时长/升降半调4个综合信息,通过4位十进制表示。(第二章最后详细解释) - 动态乐谱显示:点阵屏驱动控制,精准到控制每一个点,从左到右x列对应以音符1-7,y轴表示音符长度,一行则表示最小节拍时长。
- 交互:摇杆控制功能切换,TFT与LED显示内容变化。
3 项目构成
(1) 软件环境
上传程序:keil5
图片转c数组:img-lcd
字体编辑:PctoLcd
串口测试:串口助手
(2) 所需硬件
- stm32F103C8T6
- TFTst7735_RGB128*160——8引脚
- 小喇叭 (或蜂鸣器,大喇叭)
- 点阵屏MAX7219
- 摇杆模块:XYZ3轴
- LED灯RGB3色(非必须)
(3)接线
| 串口 | stm32 |
|---|---|
| RX | A9 |
| TX | A10 |
| OLED | stm32 |
|---|---|
| SCL | B8 |
| SDA | B9 |
| ST7735 | stm32 |
|---|---|
| GND | 电源地 |
| VCC | 3.3v电源 |
| SCL | 接PA5 |
| SDA | 接PA7 |
| RES | 接PB0 |
| DC | 接PB1 |
| CS | 接PA4 |
| BL | 空/3.3v |
| MAX7219点阵屏 | stm32 |
|---|---|
| Din | A6 |
| Cs | B13 |
| Clk | B14 |
| LED | stm32 |
|---|---|
| R | B10 |
| G | B11 |
| B | B13 |
| 摇杆模块 | stm32 |
|---|---|
| X | A0 |
| Y | A1 |
| Z | C15 |
| 喇叭 | stm32 |
|---|---|
| 红 | A2 |
| 黑 | GND |
喇叭,TFT, 摇杆为必要外设。如不开灯谱,点阵屏亦可不接
二 实现过程
本章详解每一步如何实现
0 前置基本外设驱动
基础驱动实现,本文不在赘述。详细解释请跳转以下文章:
MAX7219 & IIC过程
TFTST7735 & SPI过程
串口收发数据 & UART协议
PWM脉宽调制
AD模数转换
1 声音控制
本节解释如何利用pwm发出不同频率,响度的声音
(1)声音特性
音调:指声音的高低,由物体振动的频率决定,频率越高,音调越高;频率越低,音调越低
响度:表示声音的大小或强弱,由振幅和人耳与声源的距离决定。振幅越大,距离越近,响度越大;振幅越小,距离越远,响度越小
声音的物理量
频率:单位时间内物体振动的次数,单位为赫兹(Hz),人类能听到的音频范围是20Hz~20kHz。
周期:物体完成一次振动所需的时间,单位为秒(s),与频率成反比。
(2)PWM控制声音
控制发出不同声音,就是是控制声音不同的音调与响度,也就是频率与声音大小。回想使用PWM控制舵机时,舵机频率是固定的50Hz,通过改变占空比控制舵机角度。而控制声音完全不同:
- 第一:需要生成
多种PWM频率 - 第二:修改占空比,仅能改变
声音大小,不改变音调频率。
故而在使用PWM播放音乐是,一定会面临需要经常变化PWM频率的问题,而我们知道:
PWM频率=(定时器频率/PSC)/ARR = 定时器频率/(PSC * ARR)
所以改变PWM频率,本质是不断修改PSC或ARR的值,为了简便二者改一个就可以。如:把pwm频率修改为:fre,则只要令
ARR=(定时器频率/PSC)/fre
PSC不变,故分子为常数。以72Mh时钟频率,PSC=72为例,修改PWM频率代码为:
/*
功能:运行中,修改PWM的频率
参数:修改后的频率 fre
公式:PWM频率=(定时器频率/PSC)/ARR = 定时器频率/(PSC * ARR)
*/
void PWM_SetFrequency(uint16_t fre) {int arr=72000000/72/fre;TIM2->ARR = arr;
}
正常人耳能听到声音的频率范围:大致在20Hz~20000Hz之间
108键钢琴的频率范围:是从最低音A0的27.5Hz到最高音C8的4186.01Hz
(3)代码核心部分
PWM.h
#ifndef __PWM_H
#define __PWM_H
/*
Tim2 4通道可控制4路PWM信号,使用通道3
PWM输出引脚:A2
*/
void PWM_Init(void);
void PWM_SetCompare3(uint16_t Compare); //改声音大小 1-999
void PWM_SetFrequency(uint16_t fre); //改声音发声#endif
PWM.c 初始设定
#include "stm32f10x.h" // Device header
#include "PWM.h"
/*
功能:PWM初始化
*/
void PWM_Init(void)
{//仅举核心部分TIM_TimeBaseInitStructure.TIM_Period = 1000 - 1; //初始给个1000TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //分频后为1MH
}
/*
功能:TIM2通道3-A2,占空比 (改变每个周期内高电平或低电平的持续时间)
参数:Compare要写入的CCR的值,范围:0~20000 ,即为ARR范围
*/
void PWM_SetCompare3(uint16_t Compare)
{TIM_SetCompare3(TIM2, Compare); //设置CCR1的值,精确地设置高电平持续时间
}
/*
功能:运行中,修改PWM的频率
参数:计数周期,需要的频率
公式:PWM频率=(定时器频率/PSC)/ARR = 定时器频率/PSC * ARR
*/
void PWM_SetFrequency(uint16_t fre) {int arr=1000000.0/fre;TIM2->ARR = arr;
}
2 乐谱录入&基础乐理
数字简谱阅读基础必备:
基础乐理
(1)音符规律
由于音符间的音符十二平均律,相邻键(含黑键)频率乘以1.0594630943(二的十二次方根),频率大一度为2倍关系,低一度除以2。故只要确定1=?初始调性,即可计算出剩余108键的频率值,存入一个二维数组即可,大小为[9][12],代表0-8 九个度数区间的1-7音符频率、以及中间的黑键频率,对应钢琴9个区,每个区为12个键。
(2)大小调
大小调简单理解为就是一种1-7不同位置的按法,大小调频率表相同,不需要重新计算。区分大小调只需在代码种只需要加入2个映射数组即可(20种弹法也没有问题)
计算频率参考全全局变量值:
//------------------------计算频率参数-------------------
/*
音符数组-标准钢琴108键
1 8个度区,升降度直接改一维下标
2 每个度区,含cdefgab以及降半调共12个全黑白键,设计一映射数组1-7到第二维度,降半调+10计算
3 乐谱4位十进制:半调(0-2) 音调:1-7 音度:0-8 (大1则高8度) 时长:1-2-4 拍数
*///计算频率参考数组
uint16_t note[9][12]={//C C# D D# E F F# G G# A A# B{16,17,18,19,20,21,23,24,25,27,29,30}, //0 八度区 {32,34,36,38,41,43,46,48,51,55,58,61}, //1{65,69,73,77,82,87,92,97,103,110,116,123},//2{130,138,146,155,164,174,785,196,207,220,233,246}, //3{261,277,293,311,329,349,369,392,415,440,466,493}, //4{523,554,587,622,659,698,739,783,830,880,932,987}, //5{1046,1108,1174,1244,1318,1396,1480,1568,1661,1760,1864,1975},//6{2093,2217,2349,2489,2637,2793,2960,3136,3322,3520,3729,3951},//7{4186},//8 C8
};
//在note中找值的映射数组
uint8_t book_note[8]={0,0,2,4,5,7,9,11};//计算107个频率生成数组,存入此处,一曲一更,覆盖。
uint16_t note2[9][12]={0};//大调映射 0-0 1-0 2-2 3-4 4-5 5-7 6-9 7-11(下一个1略) 全全半全全全(半),+2 +2 +1 +2 +2 +2 +(1)
uint8_t major_scale[8]={0,0,2,4,5,7,9,11};//
//小调映射 0-0 1-0 2-2 3-3 4-5 5-7 6-8 7-10(下一个1略) 全半全全半全(全)+2 +1 +2 +2 +1 +2 +(2)
uint8_t minor_key[8]={0,0,2,3,5,7,8,10};
生成对应调性如下:另外107键的频率过程:
/*
功能:制造专属歌曲的频率数组
参数Scale:音调 C——B,c-b 大小调
参数dp: 0-1-2 降半调,无,升半调
参数degree:度数0-8
解释:适配 'do'=(C-B,c-b,#/b,0-8)全音调,108键全区每次播放歌曲前,调用此函数,计算出合适的频率并保存例:make_scale(C,1,4) do=C4make_scale(D,2,5) do=D#5
原理:根据一个参考数组,算出当前调性下的初始'do'的频率,往后排11个键,就是该度下所有键上下跨度,乘2或除2就可以得出。
*/
void make_scale(int Scale,int dp,int degree){//大小调do频率固定int t;double ans=1.0594630943;double tem=0;if(Scale=='a'||Scale=='b') //定初始调位置1-7t=6+Scale-'a';else t=Scale-'c'+1;if(Scale=='A'||Scale=='B')t=6+Scale-'A';else t=Scale-'C'+1;t=book_note[t]+dp-1; //定do的位置,dp-1 -1 0 1,计算升降调tem=note[degree][t];//不管大小调,初始do频率相同,变化都的是2-7位置,也就是2个映射数组//degree度,12键音note2[degree][0]=tem;for(int i=1;i<12;i++){tem*=ans; //保留进度note2[degree][i]=round(tem); //四舍五入}//degreee-1~0for(int i=degree-1;i>=0;i--)for(int j=0;j<12;j++)note2[i][j]=note2[i+1][j]/2;//degreee+1~8for(int i=degree+1;i<=8;i++)for(int j=0;j<12;j++)note2[i][j]=note2[i-1][j]*2;
}
(3)曲谱录入
由于本项目对于音准的高要求,所以简谱中每个音符包含4个信息:是否升降半调、本身音调、是否升降度、节拍。不需要引入字母,使用4位数字记录,个十百千位的数字记录信息,做法类似于寄存器2进制写入数据。
- 千:0 1 2
降/平/升半调 (升降半调,即#/b,输出数组后一位或前一位元素) - 百:频率 1-7
- 十:度:0-8度 (升度频率乘2)
- 个:记录节拍 1 2 4 (根据曲速可计算
一拍时长t,t除以4/(1、2、4)便是该音符发声时长,为了方便记忆,否则设为421也可以,本质是得出有几个最小一拍) - 曲速:如曲速为112,则一拍时长为:60/112 s
如实际谱子:
/*
曲1:Dream of cherry tree
1=Db 大调 beat=68 4/4 68太慢了,提高到80
*/
double beat1=60.0/80*1000; //DC曲速 原曲:60.0/68
uint16_t song_DC[]={1351,1251,1352,1552,1351,1351,1051,1041,1251,1351,1251,1352,1552,1161,1161,1061,1162,1752,1652,1552,1351,1251,1352,1552,1351,1351,1051,1542,1642,1152,1252,1351,1251,1352,1552,1361,1361,1061,1262,1162,1752,1552,1651,1051,1551,1051,1451,1551,1652,1552,1351,1251,1051,1152,1742,1642,1152,1741,1041,1041,1041,1552,1652,1752,1162,1651,1652,1352,1451,1551,1652,1452,1351,1251,1042,1352,1742,1252,1151,1152,1644,1744,1152,1742,1152,1252,1352,1252,1251,1351,1251,1352,1552,1361,1042,1262,1162,1752,1652,1552,1551,1351,1251,1352,1552,1361,1042,1462,1362,1262,1162,1752,1751,1042,1752,1162,1262,1161,1162,1552,1651,1652,1452,1552,1452,1352,1252,1151,1051,1051,1051
};
(4)播放声音
遍历乐谱数组,依次播放每个音符即可。下为播放一个音符的函数:
/*
功能:按照时长演奏一个音符(不含点阵屏变化)
参数ans:复合音符
参数btspeed:一拍的曲速单位:ms
参数scale:大调1 小调0
解释:根据音符信息,演奏音符,oled显示参数无灯模式效果精准,音长更及时
*/
void unlight_Sound(int ans,int btspeed,int scale){int half=ans/1000; //0 1 2 降半音b 正常 升半音#int msc=ans/100%10; //1-7 ,不含0 8int degree=ans/10%10; //0-8 度int tm=ans%10; //节拍 1 2 4 int fre;//<1> fre为获取二维数组下标,1-7与大小调映射if(scale) fre=major_scale[msc]; //大小调-判断else fre=minor_key[msc];if(half-1==1) fre++; //<2> 判断是否升降半调else if(half-1==-1) fre--; // -1 0 1 升/降半调,前一个或后一个元素if(msc==0) PWM_SetCompare3(0); //<3>修改声音大小,空音设占空比0else PWM_SetCompare3(voice);PWM_SetFrequency(note[degree][fre]); //<4>设置声音频率发声tm=btspeed/tm; //<5>控制发声时长Delay_ms(tm); //OLDED参数显示OLED_ShowString(3,7,"degree:"); OLED_ShowNum(3,14,degree,1);OLED_ShowString(4,1,"fre:");OLED_ShowNum(4,5,note[degree][fre],4);OLED_ShowString(4,10,"msc:");OLED_ShowNum(4,14,msc,1);//PWM_SetCompare3(0);//每个音符后额外停顿,曲谱精准时,无需设置//Delay_ms(5);
}
此函数仅用于播放声音,不控制点阵屏,注意与下面函数区分
3 点阵屏谱点动态刷新
点阵屏8*8XY轴代表信息:
X:1-7列对应音符1-7
Y:每一行代表音符发声时长,一行为最小节拍,通过60/曲速计算得出,Delay进行控制
扫描线会从上至下扫描,碰到音符,使其消失,和乐谱中每个音符的时长对应同步。此算法设计包含以下部分:
- 扫描线
变化与乐谱的声音长度同步(以Delay延时最小节拍为基础) - 当扫描线达到
顶端-立刻向后更新一屏的音符信息(非同步,而是某时刻更新) 预处理曲谱中每一个音符的显示信息(代码计算处理,人无需计算)- 恢复被截断的底部音符保证正确性(准确性)
(1)预处理乐谱每个音符的点阵信息
预处理每个音符在点阵屏上出现的起始行与结束行位置,存入标记数组msc_sort中。用于后续输出某音符在点阵屏上的具体位置,注意截断:
/*
功能: 并预处理曲铺每个音符灯点位置信息
参数: 1-3,播放歌曲前,先调用此函数
解释:msc_sort数组覆盖,每次播放前都需调用获取每个音符在点阵屏上 出现的起始行与结束行位置,存入msc_sort*起始不可超过8,结尾可大于等于8,会在动态更新点阵屏时利用结尾特性,判断触底截断音符
*/
void load_msc(int num){int tem,mucis_num; if(num==1){mucis_num=sizeof(song_DC)/sizeof(song_DC[0]); //音符数msc_sort[0][0]=1;msc_sort[0][1]=4/(song_DC[0]%10);for(int i=1;i<mucis_num;i++) //每一个的值,依据前一个结尾计算{tem=msc_sort[i-1][1];if(tem>=8) tem=tem%8+1;else tem+=1;int r=4/(song_DC[i]%10); //当前音符所占1/4节拍数,就是需要几个点点亮msc_sort[i][0]=tem; //起始必须小于等于8msc_sort[i][1]=tem+r-1; //结尾可以大于等于8}}if(num==2){//同上}if(num==3){//同上}
}
(2)显示单个音符谱点函数
欲显示整个乐谱,需先从显示一个音符开始:
/*
功能:显示一个音符的位置
参数k:曲库中下标,音符
参数ans:复合音符值
介 绍:在预处理数组中记录了开始点与结束点,此处显示时超过底部不予显示在动态更新乐谱时,在对确实的另外半截手动设置填补,否则会覆盖第一行
*/
void note_show(int k,int ans){int t=4/(ans%10); //音符长度int x=ans/100%10; //音符0-7int y=msc_sort[k][0]; //音符起始行,不会超过8//向下显示t个音符,超过不显示,所以不用msc_sort[1]for(int i=0;i<t;i++){Max7219_ShowGraph(y,x,x);y++;if(y>8) break; //先显示一次,在结束}
}
需注意,由于音符触底超长部分,需要截断,留在下一次显示,所以超过第8行不显示。
补充截断部分已在对应处单独编写代码。
(3)扫描线、声音、音符点、同步方式(核心)
此处与上述unlight_Sound数组区别是,添加的while循环部分。单纯播放声音,只需要PWM写入频率然后延时即可。但添加乐谱后,由于需要控制扫描线与音符声长同步,以2个最小节拍长的音符为例,则实际过程为:
PWM
发声->扫描线1出现->Delay最小节拍1->扫描线1消失->扫描线2出现->Delay最小节拍2->扫描线2消失。
这样可以做出声音与点阵屏同步更新的效果,以最小延时为基础,人眼是察觉不到先后顺序的,形成声画同步效果。其次是,每个音符的谱点并非是:只有播放到该音符,才更新一个,而是当扫描线出现在顶部时,一次提前更新一屏幕的内容,这样才有提前读谱的效果(类似全民k歌),而判段什么时候到达底部,以及如何恢复被截断的音符部分:通过预处理的谱点数组msc_sort实现。
/*
功能:按照时长演奏一个音符,扫描线对应同步变化
参数1:音符编号
参数3:复合数组 (需要预处理点阵,所以要传递数组)
参数3:beat节拍速度
参数4:大调1,小调0
解释:扫描线于声音同步,乐谱点是在每次扫描到第一行时更新满屏。扫描线:与声音同步,每个最小节拍间隔,点阵屏都要亮灭一次乐谱点阵显示:每次到第一行时,刷新一整个点阵屏的音符点,向后遍历遇到底部停止难点在于补全上一次末尾超长截断的灯点
*/
void light_Sound(int k,uint16_t *ans,int btspeed,int scale){int half=ans[k]/1000; //0 1 2 降半音b 正常 升半音#int msc=ans[k]/100%10; //0-7int degree=ans[k]/10%10; //0-8 度int tm=4/(ans[k]%10); //几个1/4节拍 1 2 4 int fre; //<1> fre为获取二维数组下标,1-7与大小调映射if(scale) fre=major_scale[msc]; //大小调-判断else fre=minor_key[msc];if(half-1==1) fre++; //<2> 是否升/降半调,-1 + 1前一个或后一个元素else if(half-1==-1) fre--; if(msc==0) PWM_SetCompare3(0); //<3> 音量设置,注意0不发声else PWM_SetCompare3(voice);PWM_SetFrequency(note[degree][fre]); //<4>发声int y=msc_sort[k][0]; //扫描线位置 1-8行while(tm--) //tm个最小节拍{if(y==1){ //<5> 扫描到第一行,立刻向后更新一屏的乐谱点阵for(int i=0;i<8;i++){if(i==0){ //当前字符是否为被截断字符,并恢复截断长度if(msc_sort[k][1]>=8){ //避免连续出现一个长度,无法到达下一个,从而断屏for(int n=1;n<=msc_sort[i][1]-8;n++)Max7219_ShowGraph(n,msc,msc);}elsenote_show(k,ans[k]);}else{note_show(k+i,ans[k+i]); //显示单个字符的点阵if(msc_sort[k+i][1]>=8) //到底,结束,超长部分不显示break;}}}Max7219_ShowGraph(y,1,8); //<6>扫描线随 最小节拍更新Delay_ms(btspeed/4.0); //发音,同时亮灯 Max7219_Clearline(y);y++; //扫描线位置更新,最多到9if(y>8) y=y%8; //超过8,则%8为1-7,不能在加1,会跳过空行}OLED_ShowString(3,7,"degree:");OLED_ShowNum(3,14,degree,1);OLED_ShowString(4,1,"fre:");OLED_ShowNum(4,5,note[degree][fre],4);OLED_ShowString(4,10,"msc:");OLED_ShowNum(4,14,msc,1);//PWM_SetCompare3(0);//每个音符间额外停顿//Delay_ms(5);
}
此处为 模拟过程,想要直接理解有难度,最好自己去编写尝试
(4)完整播放乐曲函数
最终封装遍历音乐的函数,可设置是否打开点阵屏模式与大小调选择,由于基于最小延时节拍,同步点阵与声音,所以会占据部分cpu性能,若只需播放音乐的话,可设置关闭。
/*
功能:播放音乐
参数1:曲目1-3
参数2:1 灯光 0 非灯光
参数3:1 大调 0 小调
解释:可选择灯光模式
*/
void Play_msc(int num,int mode,int scale){//可调控曲速if(num==1){int r=sizeof(song_DC)/sizeof(song_DC[0]);if(mode){for(int i=0;i<r;i++) //有动态灯谱light_Sound(i,song_DC,beat1,scale); //<2>修改2,大小调,1为大PWM_SetCompare3(0);//播放完静音}else{for(int i=0;i<r;i++)unlight_Sound(song_DC[i],beat1,scale);PWM_SetCompare3(0);}}if(num==2){//同上}if(num==3){//同上}
}
变速: 如一首曲子需要
多个曲速,可以在对应for循环内部中判断,当到达某个音符时 ,改变beat值接口,加几个if语句即可解决。
4 项目交互控制
本质为利用摇杆8个模拟值区间(上下左右与斜对角),控制8个不同模式,利用不同flag状态控制界面
rocker_music函数效果为,根据AD采集摇杆信息,判断摇杆位置0-8值,来播放不同的音调1-7
TFT_update效果为:根据不同数字,显示不同的文字内容
AD摇杆模块的实现,过于基础不再描述。包括设置全局变量声音、串口通信获取信息、TF图片文字显示。
/*接线
1 串口连接:RX-A9 TX-A10
2 OLED连接:SCL-B8 SDA-B9
3 TFTST7735:GND 电源地VCC 3.3v电源SCL 接PA5SDA 接PA7RES 接PB0DC 接PB1CS 接PA4 BL 空/3.3v
4 MAX7219点阵屏: Din-A6 Cs-B13 Clk-B14
5 LEd_RGB: B10 B11 A3
6 摇杆:XYZ: x-A0 y-A1 z-C15
7 喇叭:A2 (另一根线接GND)
*/
#include "stm32f10x.h" // USE_STDPERIPH_DRIVER
#include "delay.h" //--no-multibyte-chars
#include "OLED.h"
#include "Serial.h"#include "LED.h"
#include "KEY.h"
#include "AD.h"
#include "PWM.h"#include "St7735tft.h" //TFT
#include "Max7219.h" //点阵屏
#include "playmusic.h" //主要功能集成函数int main(void)
{AD_Init();PWM_Init();LED_Init();KEY_Init();TFT_Init();Max7219_InitDisplay();OLED_Init();Serial_Init();TFT_UI(); //显示自定义界面OLED_ShowString(1,1,"SKY_Music");int z,m1,mode=0; //z轴状态,m1与mode用来记录摇杆位置int falg_rocker=0;//摇杆标记/*C-B调全升降调打表,以do=4度为例,最大支持0-8度区间for(int i='A';i<='G';i++){for(int j=0;j<=2;j++)make_scale(i,j,4);Serial_Printf("\r\n");}*/while (1){//可随时串口更新音量if(Serial_RXString())set_voice();z=KEY_Getnum(); //z轴状态OLED_ShowString(3,1,"Z:");OLED_ShowNum(3,3,z,1);//自由编曲独占模式if(falg_rocker){if(z==1) falg_rocker=0;rocker_music();continue;}//记录摇杆值m1=Get_rocker(); //摇杆一松手会归0,所以需要2个值记录,无法及时TFT_update(m1);if(m1!=0) mode=m1; //记录变化的操作/*@@@@@串口测试,获取值Serial_Printf("z=%d m1=%d mode=%d\r\n",z,m1,mode); *///调用功能if(z&&mode==1){ //上:DC Dream of cherry treeload_msc(1);//载入歌曲make_scale('D',DOWN,4); //调性D,b降半调,度数4 1=Db4Play_msc(1,light_Turn,MAJOR); //曲目编号,灯光模式,MAJOR大调}else if(z&&mode==2){ //左上:add声音lower_voice();TFT_update(2);}else if(z&&mode==3){ //左:克罗迪亚load_msc(2);make_scale('E',DOWN,4); //1=Eb4,小调Play_msc(2,light_Turn,MAJOR); }else if(z&&mode==4){ //左下:low声音upper_voice();TFT_update(4);}else if(z&&mode==5){ //下:梦回还load_msc(3);Delay_ms(400);//曲目长,更新下数组make_scale('A',NORMAL,4); //A4 不降调Play_msc(3,light_Turn,MAJOR);}else if(z&&mode==6){ //下右:空}else if(z&&mode==7){ //右: 自由摇滚-独占,rocker音乐falg_rocker=1; }else if(z&&mode==8){ //右上:灯光模式light_Turn=!light_Turn;TFT_update(8);LED_On(1);Delay_ms(100);LED_On(2);Delay_ms(150);LED_On(3);Delay_ms(200);LED_Off(1);LED_Off(2);LED_Off(3);}}
}//摇杆的按钮挂个中断,及时反应
void EXTI15_10_IRQHandler(void){ //(5)创建中断函数//startup_stm32f0x_md.s找——->Table Mapped,DCD栏以EXTI_IRQHandler结尾//判断是不是15通道,exti.h————>EXTI_GetITStatus与EXTI_ClearITPendingBit函数if(EXTI_GetITStatus(EXTI15_10_IRQn)==RESET){EXTI_ClearITPendingBit(EXTI_Line15);//清除标志位Serial_Printf("Z按钮进入中断\r\n");}
}
5 录入新曲子过程
(1)录入乐谱
进入Song.h文件:
- 乐谱数组
覆盖3首中随机一首的信息即可。 - 修改对应beat曲速,数字简谱会有曲速说明
以一小节为例,4/4拍:

录谱过程:

实际录谱时,使用
语音识别输入,与excel批量处理数字数据,很快捷,因为规律十分的简单
(2)修改带调性函数
进入main.c文件,修改对应调性,大小调参数即可:
//根据编号信息修改
else if(z&&mode==3){load_msc(2);make_scale('E',DOWN,4); //1=Eb4, 改A-G,DOWN/NORMAL/RISE 降平升半调,初始度数0-8(一般为4)Play_msc(2,light_Turn,MAJOR); //改大小调,MAJOR或MIRROR }
#为升:使用RISE
b为降:用DOWN
否则:NORMAL
A-G大小写通用
大调:MAJOR
小调:MIRROR
三 展示,与链接视频地址
本章内容为:主要功能函数展示与实现效果,附上下载链接
1 主要功能函数一览
#ifndef __PLAYMUSIC_H
#define __PLAYMUSIC_H/*song中引用文件:
note[9][12] 频率文件
book_note[8] 映射
song_DC[] 曲库1
song_Croatia[] 曲库2
song_DreamBack[] 曲库3
beat1 曲1节拍
beat2
beat3
voice 声音
*/#define RISE 2 //升半调
#define NORMAL 1
#define DOWN 0 //降半调
#define MAJOR 1 //大调
#define MINOR 0 //小调//playmusic定义
extern int light_Turn; //开关灯光模式
extern uint16_t msc_sort[800][2]; //预处理:每个音符在点阵屏初始位置信息//----------------功能函数-----------------------
//音乐播放
void set_voice(void); //串口准确更新声音1-999
void lower_voice(void); //按一下,+200
void upper_voice(void);void load_msc(int num); //载入曲谱灯铺信息
void make_scale(int Scale,int dp,int degree);//制作不同曲目调108键调性表
void rocker_music(void);//摇杆音乐
void Play_msc(int num,int mode,int scale); //曲目编号,灯光模式0-1//TFT
void TFT_UI(void);
void TFT_update(int k);//-------------------内部函数-----------------------
//灯
void unlight_Sound(int ans,int btspeed,int scale); //音符无灯
void light_Sound(int k,uint16_t *ans,int btspeed,int scale); //音符带灯,动态更新乐谱
void note_show(int k,int ans); //显示单个音符的点阵(超过底部部分不显示)#endif
2 下载链接
点击下载源文件:
与君共勉
3 视频效果
stm32自制可视化乐谱108键钢琴,小白轻松奏神曲
相关文章:
stm32108键C-B全调性_动态可视化乐谱钢琴
108键全调性钢琴 一 基本介绍1 项目简介2 实现方式3 项目构成 二 实现过程0 前置基本外设驱动1 声音控制2 乐谱录入&基础乐理3 点阵屏谱点动态刷新4 项目交互控制5 录入新曲子过程 三 展示,与链接视频地址1 主要功能函数一览2 下载链接3 视频效果 一 基本介绍 …...
mysql之规则优化器RBO
文章目录 MySQL 基于规则的优化 (RBO):RBO 的核心思想:模式匹配与规则应用RBO 的主要优化规则查询重写 (Query Rewrite) / 查询转换 (Query Transformation)子查询优化 (Subquery Optimization) - RBO 的重中之重非相关子查询 (Non-Correlated Subquery)…...
MySQL数据库——表的约束
1.空属性(null/not null) 两个值:null(默认的)和not null(不为空) 数据库默认字段基本都是字段为空,但是实际开发时,尽可能保证字段不为空,因为数据为空没办法…...
vue2.x 中子组件向父组件传递数据主要通过 $emit 方法触发自定义事件方式实现
在 Vue 2.x 中,子组件向父组件传递数据主要通过 自定义事件 的方式实现。具体步骤如下: 1. 子组件通过 $emit 触发事件 子组件可以使用 $emit 方法触发一个自定义事件,并将数据作为参数传递给父组件。 语法: this.$emit(事件名…...
洛谷 P1102 A-B 数对(详解)c++
题目链接:P1102 A-B 数对 - 洛谷 1.题目分析 2.算法原理 解法一:暴力 - 两层for循环 因为这道题需要你在数组中找出来两个数,让这两个数的差等于定值C就可以了,一层for循环枚举A第二层for循环枚举B,求一下看是否等于…...
python用 PythonNet 从 Python 调用 WPF 类库 UI 用XAML
pythonnet 是pythonhe.net通用的神器不多介绍了. 这次这基本上跟python没有关系了. 和winform一样先导包 import clr clr.AddReference("PresentationFramework.Classic, Version3.0.0.0, Cultureneutral, PublicKeyToken31bf3856ad364e35") clr.AddReference(&…...
C++——list模拟实现
目录 前言 一、list的结构 二、默认成员函数 构造函数 析构函数 clear 拷贝构造 赋值重载 swap 三、容量相关 empty size 四、数据访问 front/back 五、普通迭代器 begin/end 六、const迭代器 begin/end 七、插入数据 insert push_back push_front 八、…...
YOLOv11-ultralytics-8.3.67部分代码阅读笔记-utils.py
utils.py ultralytics\data\utils.py 目录 utils.py 1.所需的库和模块 2.def img2label_paths(img_paths): 3.def get_hash(paths): 4.def exif_size(img: Image.Image): 5.def verify_image(args): 6.def verify_image_label(args): 7.def visualize_image_ann…...
Linux 内核 RDMA CM 模块分析:drivers/infiniband/core/cma.c
一、引言 随着高性能计算和大数据处理需求的不断增长,远程直接内存访问(RDMA)技术在数据中心和高性能计算领域得到了广泛应用。RDMA 允许数据直接在不同系统的内存之间传输,而无需经过 CPU 和操作系统的干预,从而显著提高了数据传输效率和系统性能。Linux 内核中的 RDMA …...
Flask flash() 消息示例
目录 安装 Flask 入门:Flask flash() 基本示例 进阶:使用 Flask-WTF Flash 登录结果消息 详解:get_flashed_messages() 详解:flash() 消息的完整生命周期 Flask 提供 flash() 用于向 用户传递临时消息,通常用于: • 表单提交成功或失败 • 用户登录、注册、退出提…...
ImGui 学习笔记(三)—— 隐藏主窗口窗口关闭检测
ImGui 的主窗口是平台窗口,默认是可见的,这会影响视觉效果。那么怎么隐藏 ImGui 的主窗口呢? 这很简单,但是需要针对后端做一些修改。 本文仅介绍在 glfwopengl3 和 win32dx11 两种实现上如何修改。 在 win32dx11 实现上&#…...
ubuntu磁盘清理垃圾文件
大头文件排查 #先查看是否是内存满了,USER 很高即是满了 du -f#抓大头思想,优先删除大文件#查看文件目录 内存占用量并排序,不断文件递归下去 du --max-depth1 -h /home/ -h | sort du --max-depth1 -h /home/big/ -h | sort 缓存文件清理…...
vue-fastapi-admin 部署心得
vue-fastapi-admin 部署心得 这两天需要搭建一个后台管理系统,找来找去 vue-fastapi-admin 这个开源后台管理框架刚好和我的技术栈所契合。于是就浅浅的研究了一下。 主要是记录如何基于原项目提供的Dockerfile进行调整,那项目文件放在容器外部…...
大语言模型微调的公开JSON数据
大语言模型微调的公开JSON数据 以下是一些可用于大语言模型微调的公开JSON数据及地址: EmoLLM数据集 介绍:EmoLLM是一系列能够支持理解用户、帮助用户心理健康辅导链路的心理健康大模型,其开源了数据集、微调方法、训练方法及脚本等。数据集按用处分为general和role-play两种…...
C++STL容器之set
1.介绍 set容器是C标准模板库(STL)中的一个关联容器,用于存储唯一的元素。set中的元素是自动排序的,不允许重复。set通常基于红黑树(一种自平衡二叉查找树)实现,因此插入、删除和查找操作的时间…...
《微软量子芯片:开启量子计算新纪元》:此文为AI自动生成
量子计算的神秘面纱 在科技飞速发展的今天,量子计算作为前沿领域,正逐渐走进大众的视野。它宛如一把神秘的钥匙,有望开启未来科技变革的大门,而微软量子芯片则是这把钥匙上一颗璀璨的明珠。 量子计算,简单来说,是一种遵循量子力学规律调控量子信息单元进行计算的新型计算…...
使用AI创建流程图和图表的 3 种简单方法
你可能已经尝试过使用 LLMs 生成图像,但你有没有想过用它们来创建 流程图和图表?这些可视化工具对于展示流程、工作流和系统架构至关重要。 通常,在在线工具上手动绘制图表可能会耗费大量时间。但你知道吗?你可以使用 LLMs 通过简…...
从波士顿动力到Figure AI:探寻人工智能驱动的机器人智能化
一、引言 1.1 研究背景与意义 在科技飞速发展的当下,机器人智能化已成为全球科技竞争的关键领域,深刻影响着人类社会的生产与生活方式。从工业制造到日常生活服务,从医疗保健到探索未知领域,机器人正逐步渗透进各个行业,展现出巨大的发展潜力与应用价值。其智能化水平的…...
算法——KMP算法(Knuth-Morris-Pratt算法)
KMP算法(Knuth-Morris-Pratt算法)是一种高效的字符串匹配算法,用于在主文本字符串中快速查找模式字符串的出现位置。其核心思想是通过预处理模式字符串,利用部分匹配信息(即“失败函数”或“next数组”)避免…...
一周学会Flask3 Python Web开发-flask3模块化blueprint配置
锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 我们在项目开发的时候,多多少少会划分几个或者几十个业务模块,如果把这些模块的视图方法都写在app.py…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
Golang dig框架与GraphQL的完美结合
将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用,可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器,能够帮助开发者更好地管理复杂的依赖关系,而 GraphQL 则是一种用于 API 的查询语言,能够提…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
【决胜公务员考试】求职OMG——见面课测验1
2025最新版!!!6.8截至答题,大家注意呀! 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:( B ) A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...
SpringTask-03.入门案例
一.入门案例 启动类: package com.sky;import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCach…...
智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
使用Spring AI和MCP协议构建图片搜索服务
目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
