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 视频效果 一 基本介绍 …...
OpenIPC开源FPV之Adaptive-Link安装
OpenIPC开源FPV之Adaptive-Link安装 1. 源由2. 介绍2.1 天空端安装2.2 地面端安装 3. 问题汇总3.1 安装脚本问题3.2 网络安装问题3.3 非SSC30KQ/SSC338Q硬件3.4 代码疑问 4. 总结5. 后续 1. 源由 鉴于飞行过程,发现一些马赛克现象,且60FPS桌面30FPS的录…...
2016年下半年试题二:论软件设计模式及其应用
论文库链接:系统架构设计师论文 论文题目 软件设计模式(Software DesignPatter)是一套被反复使用的、多数人知晓的、经过分类编目的代码设计经验的总结。使用设计模式是为了重用代码以提高编码效率增加代码的可理解性、保证代码的可靠性。软件设计模式是软件开发中的…...
UE(虚幻)学习(五)初学创建NPC移动和遇到的问题
最近在学习UE中遇到一些问题,把这些问题记录一下,因为实在废了很大功夫。 在学习了UE5的例子中的第三人称移动Demo,想实现几个NPC在场景内移动。 本来想自己写一个类,遇到一堆问题花费了好几天时间,所以我把问题写下来…...
《Keras 2 :使用 RetinaNet 进行对象检测》:此文为AI自动翻译
《Keras 2 :使用 RetinaNet 进行对象检测》 作者:Srihari Humbarwadi 创建日期:2020/05/17 最后修改日期:2023/07/10 描述:实施 RetinaNet:用于密集对象检测的焦点损失。 (i) 此示例使用 Keras 2 在 Colab 中查看 • 介绍 目标检测是计算机中非常重要的问题 视觉。在…...
mfy学习笔记
创建表并导入数据 CREATE TABLE sales (id INT,salesperson STRING,region STRING,sales_amount INT,sale_date DATE );INSERT INTO sales (id, salesperson, region, sales_amount, sale_date) VALUES (1, Alice, North, 1000, 2023-01-01), (2, Bob, South, 1500, 2023-01-0…...
mysql 学习19 MYSQL管理以及使用工具
系统数据库 常用工具 mysql [rootCentOS7 home]# mysql -h192.168.245.131 -P3306 -uroot -pmimaroot -e "select * from tb_sanguo_user;" centositcast [rootCentOS7 home]# mysql -h192.168.245.131 -P3306 -uroot -pmimaroot centositcast -e "select * fr…...
Linux | RHEL / CentOS 中 YUM history / downgrade 命令回滚操作
注:英文引文,机翻未校。 在 RHEL/CentOS 系统上使用 YUM history 命令回滚升级操作 作者: 2daygeek 译者: LCTT DarkSun 为服务器打补丁是 Linux 系统管理员的一项重要任务,为的是让系统更加稳定,性能更加…...
JavaScript基础(函数及面向对象)
函数 定义函数 Java定义方法: public 返回值类型 方法名(){ return 返回值 } 定义函数方法一 eg:定义一个绝对值函数 function abs(x) {if (x>0){return x;}else {return -x;}} 调用函数: 注意:一旦执行到return代表函数…...
Open WebUI中的Pipelines是什么
Open WebUI中的Pipelines是什么 Open WebUI中的Pipelines(管道或流水线)是一种允许开发者定制使用大模型流程的机制,能为OpenAI兼容的UI客户端带来模块化、定制化的工作流。以下是一些Pipelines实例及使用方法: 函数调用管道(Function Calling Pipeline) 实例:假设要让…...
伪404兼容huawei生效显示404
根据上述思考,以下是详细的中文分步说明: --- **步骤 1:获取目标设备的User-Agent信息** 首先,我们需要收集目标设备的User-Agent字符串,包括: 1. **iPhone设备的User-Agent**: Mozi…...
Linux 病毒扫描工具 ClamAV 使用
Linux 病毒扫描工具 ClamAV 使用 功能安装包管理器安装手动安装RPM/DEB包配置用户与目录更新病毒库 基本使用命令扫描文件/目录常用参数 高级配置实时监控(clamd服务)定时自动扫描 常见问题解决病毒库更新失败权限错误扫描速度慢 功能 官网:h…...
Mobaxterm服务器常用命令(持续更新)
切换文件夹 cd path # for example, cd /gpu03/deeplearning/进入不同GPU ssh mgmt ssh gpu01 ssh gpu03寻找文件位置 find /path -name file_name #for example, find / -name lib #在根目录下搜寻名为lib文件 #for example, find /home/deeplearning -name "lib"…...
[特殊字符]《封印adb的黑暗通道:让系统文件成为魔法禁书区的终极指南》[特殊字符]
第一章:当adb变成泄密特洛伊木马 "曾经,adb是程序员的阿拉丁神灯,如今却成了产品经理的噩梦!" —— 某秃头CTO的血泪控诉 某日,产品经理惊恐发现:自家黑科技APP竟被竞争对手用adb pull轻松窃取…...
LangChain教程 - RAG - 支持的100种向量数据库
系列文章索引 LangChain教程 - 系列文章 随着人工智能和机器学习应用的快速发展,尤其是在自然语言处理(NLP)、图像识别、推荐系统等领域,对高效的向量存储和检索需求日益增长。向量存储用于保存来自深度学习模型或其他机器学习算…...
如何使用tushare pro获取股票数据——附爬虫代码以及tushare积分获取方式
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么?二、使用步骤 1.引入库2.读入数据 总结 一、Tushare 介绍 Tushare 是一个提供中国股市数据的API接口服务,它允许用户…...
ESP32S3:解决RWDT无法触发中断问题,二次开发者怎么才能使用内部RTC看门狗中断RWDT呢?
目录 基于ESP32S3:解决RWDT无法触发中断问题引言解决方案1. 查看报错日志2. 分析报错及一步一步找到解决方法3.小结我的源码基于ESP32S3:解决RWDT无法触发中断问题 引言 在嵌入式系统中,RWDT(看门狗定时器)是确保系统稳定性的重要组件。然而,在某些情况下,RWDT可能无法…...
如何在视频中提取关键帧?
在视频处理中,提取关键帧是一项常见的任务。下面将介绍如何基于FFmpeg和Python,结合OpenCV库来实现从视频中提取关键帧的功能。 实现思路 使用FFmpeg获取视频的关键帧时间戳:FFmpeg是一个强大的视频处理工具,可以通过命令行获取…...
基于 Python 的电影市场预测分析系统设计与实现(源码 + 文档)
大家好,今天要和大家聊的是一款基于 Python 的“电影市场预测分析”系统的设计与实现。项目源码以及部署相关事宜请联系我,文末附上联系方式。 项目简介 基于 Python 的“电影市场预测分析”系统主要面向以下用户角色:电影制片方、电影发行…...
2022年全国职业院校技能大赛网络系统管理赛项模块A:网络构建(样题6)-网络部分解析-附详细代码
目录 附录1:拓扑图 附录2:地址规划表 1.SW1 2.SW2 3.SW3 4.SW4 5.VSU 6.SW7 7.R1 8.R2 9.R3 10.AC1 11.AC2 12.EG1 13.EG2 附录1:拓扑图 附录2:地址规划表...
道路三维数字化技术产品方案介绍(软硬件一体,适用于各等级公路)
目录 1、方案介绍2、设备系统简介3、设备系统技术指标4、数据处理软件简介5、数据管理平台简介6、成果展示 1、方案介绍 交通运输部印发《关于推进公路数字化转型加快智慧公路建设发展的意见》,推动公路建设、养护、运营等全流程数字化转型。提出了2027年和2035年的…...
速通HTML
目录 HTML基础 1.快捷键 2.标签 HTML进阶 1.列表 a.无序列表 b.有序列表 c.定义列表 2.表格 a.内容 b.合并单元格 3.表单 a.input标签 b.单选框 c.上传文件 4.下拉菜单 5.文本域标签 6.label标签 7.按钮标签 8.无语义的布局标签div与span 9.字符实体 HTML…...
从 Spring Boot 2 升级到 Spring Boot 3 的终极指南
一、升级前的核心准备 1. JDK 版本升级 Spring Boot 3 强制要求 Java 17 及以上版本。若当前项目使用 Java 8 或 11,需按以下步骤操作: 安装 JDK 17:从 Oracle 或 OpenJDK 官网下载,配置环境变量(如 JAVA_HOME&…...
Java 大视界 —— Java 大数据在智慧能源微电网能量管理中的关键技术(100)
💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也…...
springboot实现多文件上传
springboot实现多文件上传 代码 package com.sh.system.controller;import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.PostMap…...
vue自定义指令千分位
问题 开发的时候经常会遇到需要在输入框中输入数字转为千分位,点击填写时又转为数字的情况 解决 因此直接在vue中注入自定义指令,通过使用自定义指令达到效果;限制input输入框只能输入数字和一位小鼠带你 自定义指令-千分位 // 自定义指令-千…...
深入理解Redis:数据类型、事务机制及其应用场景
在当今快速发展的技术领域中,Redis作为一种高性能的内存数据库,已经被广泛应用于各种场景,从简单的缓存实现到复杂的数据处理任务。其灵活性和高效性主要来源于对多种数据结构的支持以及强大的功能特性,如事务处理、持久化选项、高…...
JVM生产环境问题定位与解决实战(三):揭秘Java飞行记录器(JFR)的强大功能
提到飞行记录器,或许你的脑海中并未立刻浮现出清晰的画面,但一说起“黑匣子”,想必大多数人都能恍然大悟,知晓其重要性及用途。在航空领域,黑匣子作为不可或缺的设备,默默记录着飞行过程中的每一项关键数据…...
MongoDB私人学习笔记
俗话说“好记性不如烂笔头”,编程的海洋如此的浩大,养成做笔记的习惯是成功的一步! 此笔记主要是ZooKeeper3.4.9版本的笔记,并且笔记都是博主自己一字一字编写和记录,有错误的地方欢迎大家指正。 一、基础知识…...
Ant Design按钮样式深度适配:实现<Button>与<a>标签颜色完美同步
Ant Design按钮样式深度适配:实现与标签颜色完美同步 问题现象诊断 组件结构原型 <Button type"link" disabled{disabled}><a href"...">下载</a> </Button>样式冲突表现 状态按钮颜色链接颜色视觉问题启用态Ant蓝…...
linux下软件安装、查找、卸载
目录 常见安装方式有三种: 1.源码安装。 2.rpm安装方式。 3.yum/apt工具级别安装。 对于前两种安装方式,因为软件可能有依赖关系(安装的软件依赖于某些库,而这些库又依赖于某些库,这些都需要手动安装)…...
鸿蒙开发深入浅出01(基本环境搭建、页面模板与TabBar)
鸿蒙开发深入浅出01(基本环境搭建、页面模板与TabBar) 1、效果展示2、下载 DevEco Studio3、创建项目4、新建页面模板5、更改应用信息6、新建以下页面7、Index.ets8、真机运行9、图片资源文件 1、效果展示 2、下载 DevEco Studio 访问官网根据自己的版本…...
【DeepSeek】-macOS本地终端部署后运行DeepSeek如何分析图片
【DeepSeek】-macOS本地终端部署后运行DeepSeek如何分析图片 根据您的需求,目前需要了解以下几个关键点及分步解决方案: --- 一、现状分析 1. Ollama 的限制: - 目前Ollama主要面向文本大模型,原生不支持直接上传/处理图片 …...
微信小程序源码逆向 MacOS
前言 日常工作中经常会遇到对小程序的渗透测试,微信小程序的源码是保存在用户客户端本地,在渗透的过程中我们需要提取小程序的源码进行问题分析,本篇介绍如何在苹果电脑 MacOS 系统上提取微信小程序的源码。 0x01 微信小程序提取 在苹果电…...
2025年2月科技热点深度解析:AI竞赛、量子突破与开源革命
引言 2025年的科技领域持续呈现爆发式增长,AI大模型竞争白热化、量子计算商业化加速、开源工具生态繁荣成为本月最受关注的议题。本文结合最新行业动态,从技术突破、商业布局到开发者生态,全面解析当前科技热点,为读者提供深度洞…...
Wireshark简单教程
1.打开Wireshark,点击最上面栏目里面的“捕获”中的“选项” 2.进入网卡选择界面,选择需要捕获的选择,这里我选择WLAN 3.双击捕获选择出现下面界面 4.点击如下图红方框即可停止捕获 5.点击下图放大镜可以进行放大 6.你也可以查询tcp报文如下图...
stm32单片机个人学习笔记16(SPI通信协议)
前言 本篇文章属于stm32单片机(以下简称单片机)的学习笔记,来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记,只能做参考,细节方面建议观看视频,肯定受益匪浅。 STM32入门教程-2023版 细…...
面试题——简述Vue 3的服务器端渲染(SSR)是如何工作的?
面试题——简述Vue3的服务器端渲染(SSR)是如何工作的? 服务器端渲染(SSR)已经成为了一个热门话题。Vue 3,作为一款流行的前端框架,也提供了强大的SSR支持。那么,Vue 3的SSR究竟是如何…...
Ubuntu 22.04安装K8S集群
以下是Ubuntu 22.04安装Kubernetes集群的步骤概要 一、设置主机名与hosts解析 # Master节点执行 sudo hostnamectl set-hostname "k8smaster" # Worker节点执行 sudo hostnamectl set-hostname "k8sworker1"# 所有节点的/etc/hosts中添加: ca…...
Ubuntu搭建esp32环境 配置打开AT指令集 websocket功能
1,搭建前提 环境搭建参考乐鑫官网给的本地编译 ESP-AT 工程方法 因为公司电脑和网络的特殊性,不能正确解析域名(仅在浏览器上可以访问) ,所以这边访问的时候改成了ssh 未了避免使用外网困难的问题,这里用…...
java八股文-消息队列
一、MQ基础篇 1. 什么是消息队列? 消息队列(MQ)是分布式系统中实现异步通信的中间件,解耦生产者和消费者。 2. 使用场景有哪些? 异步处理(如注册后发送邮件)系统解耦(不同服务通过…...
[晕事]今天做了件晕事65,gcc,cmake, pragam
文章目录 晕事cmake,unity,对gcc pragma指令有没有影响pragma指令的影响pragma指令的使用规范使用注意事项:晕事 最近在某些不能有优化的函数前加了指令 #pragma GCC optimize ("O0")我记得是这个指令只影响当前编译单元。 但是被人找上来了, 因为这个文件所牵…...
SAP-ABAP:使用ST05(SQL Trace)追踪结构字段来源的步骤
ST05 是 SAP 提供的 SQL 跟踪工具,可以记录程序运行期间所有数据库操作(如 SELECT、UPDATE、INSERT)。通过分析跟踪结果,可以精准定位程序中结构字段对应的数据库表。 步骤1:激活ST05跟踪 事务码 ST05 → 点击 Activa…...
STM32--SPI通信讲解
前言 嘿,小伙伴们!今天咱们来聊聊STM32的SPI通信。SPI(Serial Peripheral Interface)是一种超常用的串行通信协议,特别适合微控制器和各种外设(比如传感器、存储器、显示屏)之间的通信。如果你…...
cpu、mem监控
deepseek 1、安装依赖2、psutil库3、streamlit库4、实战4.1 单机CPU和内存使用率监控4.2 多机CPU和内存使用率监控 1、安装依赖 /usr/bin/python3 -m pip install psutil streamlit2、psutil库 psutil是一个跨平台的Python库,用于获取系统使用情况的信息…...
工程实践中常见的几种设计模式解析及 C++ 实现
工程实践中常见的几种设计模式解析及 C 实现 在软件工程中,设计模式是一种通用的解决方案,用于解决常见问题和优化代码结构。它们通过提供一种规范化的编程思想,帮助开发者写出更高效、可维护和可扩展的代码。本文将介绍几种在工程实践中常见…...
ollama在linux上进行部署——离线安装说明
1. 官网下载ollama压缩包 https://ollama.com/download/ollama-linux-amd64.tgz sudo tar -C /usr -xzf ollama-linux-amd64.tgz #解压安装 2. 添加systemctl服务启动文件 添加服务文件:/etc/systemd/system/ollama.service [Unit] DescriptionOllama …...
(一)趣学设计模式 之 单例模式!
目录 一、啥是单例模式?二、为什么要用单例模式?三、单例模式怎么实现?1. 饿汉式:先下手为强! 😈2. 懒汉式:用的时候再创建! 😴3. 枚举:最简单最安全的单例&a…...
基于无人机遥感的烟株提取和计数研究
一.研究的背景、目的和意义 1.研究背景及意义 烟草作为我国重要的经济作物之一,其种植面积和产量的准确统计对于烟草产业的发展和管理至关重要。传统的人工烟株计数方法存在效率低、误差大、难以覆盖大面积烟田等问题,已无法满足现代烟草种植管理的需求…...
在windows下安装windows+Ubuntu16.04双系统(上)
这篇文章的内容主要来源于这篇文章,给文章很详细的介绍了如何从windows下安装windowsubuntu16.04双系统。我刚开始装双系统都是参照这个方法,该作者前后更新了两个版本,在这里对其稍微进行整理一下。 一、准备:(这里推…...