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
RXA9
TXA10
OLEDstm32
SCLB8
SDAB9
ST7735stm32
GND電源地
VCC3.3v電源
SCL接PA5
SDA接PA7
RES接PB0
DC接PB1
CS接PA4
BL空/3.3v
MAX7219點陣屏stm32
DinA6
CsB13
ClkB14
LEDstm32
RB10
GB11
BB13
搖桿模塊stm32
XA0
YA1
ZC15
喇叭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鍵鋼琴,小白輕松奏神曲


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/70651.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/70651.shtml
英文地址,請注明出處:http://en.pswp.cn/web/70651.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【p-camera-h5】 一款開箱即用的H5相機插件,支持拍照、錄像、動態水印與樣式高度定制化。

【開源推薦】p-camera-h5&#xff1a;一款輕量級H5相機插件開發實踐 一、插件背景 在Web開發中&#xff0c;原生攝像頭功能的集成往往面臨以下痛點&#xff1a; 瀏覽器兼容性問題視頻流與水印疊加實現復雜移動端適配困難功能定制成本高 為此&#xff0c;p-camera-h5 —— 一…

交叉編譯curl(OpenSSL)移植ARM詳細步驟

運行配置腳本 使用 Configure 腳本配置 OpenSSL&#xff0c;指定目標平臺和安裝路徑&#xff1a; curl downloads 各個版本 Old 1.1.1 Releases | OpenSSL Library 各個版本 從 OpenSSL 官網下載源碼包 tar -xzf openssl-1.1.1b.tar.gz cd openssl-1.1.1b/運行配置腳本 使…

大語言模型中的 Token如何理解?

在大語言模型中&#xff0c;Token 是文本處理的基本單元&#xff0c;類似于“文字塊”&#xff0c;模型通過將文本分割成Token來理解和生成內容。舉一個形象一點的例子&#xff0c;可以理解為 AI 處理文字時的“最小積木塊”。就像搭樂高時&#xff0c;每塊積木是基礎單位一樣&…

表單制作代碼,登錄動畫背景前端模板

炫酷動效登錄頁 引言 在網頁設計中,按鈕是用戶交互的重要元素之一。一個炫酷的按鈕特效不僅能提升用戶體驗,還能為網頁增添獨特的視覺吸引力。今天,我們將通過CSS來實現一個“表單制作代碼,登錄動畫背景前端模板”。該素材呈現了數據符號排版顯示出人形的動畫效果,新穎有…

1分鐘用DeepSeek編寫一個PDF轉Word軟件

一、引言 如今&#xff0c;在線工具的普及讓PDF轉Word成為了一個常見需求&#xff0c;常見的pdf轉word工具有收費的wps&#xff0c;免費的有pdfgear&#xff0c;見下文&#xff1a; PDFgear:一款免費的PDF編輯、格式轉化軟件-CSDN博客 還有網上在線的免費pdf轉word工具smallp…

【找工作】C++和算法復習(自用)

文章目錄 C頭文件自定義排序函數stl 算法數據結構樹狀數組 數學字符串manacherkmp 自用隨便記錄 C 排序 stl 頭文件 全能頭文件&#xff1a; #include<bits/stdc.h>自定義排序函數 bool compare(const int &odd1,const int &odd2) {return odd1>odd2; }…

請談談 React 中的狀態管理,如何使用 Context API 和 Redux 進行狀態管理?

一、Context API 深度應用 1. 核心實現原理 通過createContext創建上下文對象&#xff0c;使用Provider組件包裹需要共享狀態的組件樹&#xff0c;子組件通過useContext Hook或Consumer組件消費數據。 代碼示例&#xff08;主題切換場景&#xff09;&#xff1a; // 創建上…

kotlin的Int類型調用toString()會導致空指針嗎

DeepSeek R1 思考 38 秒 思考過程 用戶的問題是關于Kotlin中Int類型調用toString()是否會導致空指針異常。首先&#xff0c;我需要回憶Kotlin的基本類型系統。Kotlin中的Int屬于基本數據類型&#xff0c;但它在語言層面被表示為非空類型。也就是說&#xff0c;Int變量本身不能…

網絡運維學習筆記 017 HCIA-Datacom綜合實驗01

文章目錄 綜合實驗1實驗需求總部特性 分支8分支9 配置一、 基本配置&#xff08;IP二層VLAN鏈路聚合&#xff09;ACC_SWSW-S1SW-S2SW-Ser1SW-CoreSW8SW9DHCPISPGW 二、 單臂路由GW 三、 vlanifSW8SW9 四、 OSPFSW8SW9GW 五、 DHCPDHCPGW 六、 NAT缺省路由GW 七、 HTTPGW 綜合實…

基于Hadoop的汽車大數據分析系統設計與實現【爬蟲、數據預處理、MapReduce、echarts、Flask】

文章目錄 有需要本項目的代碼或文檔以及全部資源&#xff0c;或者部署調試可以私信博主 項目介紹爬蟲數據概覽HIve表設計Cars Database Tables 1. cars_data2. annual_sales_volume3. brand_sales_volume4. city_sales_volume5. sales_volume_by_year_and_brand6. sales_distri…

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…

Java所有運算符理解

Java 運算符 算術運算符 表格中的實例假設整數變量A的值為10&#xff0c;變量B的值為20&#xff1a; 操作符描述例子加法 - 相加運算符兩側的值A B 等于 30-減法 - 左操作數減去右操作數A – B 等于 -10*乘法 - 相乘操作符兩側的值A * B等于200/除法 - 左操作數除以右操作數…

紛析云:賦能企業財務數字化轉型的開源解決方案

在企業數字化轉型的浪潮中&#xff0c;財務管理的高效與安全成為關鍵。紛析云憑借其開源、安全、靈活的財務軟件解決方案&#xff0c;為企業提供了一條理想的轉型路徑。 一、開源的力量&#xff1a;自主、安全、高效 紛析云的核心優勢在于其100%開源的財務軟件源碼。這意味著…

Golang深度學習

前言 在2009年&#xff0c;Google公司發布了一種新的編程語言&#xff0c;名為Go&#xff08;或稱為Golang&#xff09;&#xff0c;旨在提高編程效率、簡化并發編程&#xff0c;并提供強大的標準庫支持。Go語言的設計者們希望通過Go語言能夠解決軟件開發中的一些長期存在的問…

博客系統筆記總結 2( Linux 相關)

Linux 基本使用和程序部署 基本命令 文件操作 顯示當前目錄下的文件 ls&#xff1a;顯示當前目錄下的文件 ll&#xff1a;以列表的形式展示&#xff0c;包括隱藏文件 進入目錄 && 顯示當前路徑 cd&#xff1a;進入目錄&#xff08;后面跟相對路徑或者絕對路徑&…

開源基準測試模擬器:BlueROV2 水下機器人的控制

拜讀An Open-Source Benchmark Simulator: Control of a BlueROV2 Underwater Robot 非常感謝Esben Uth的幫助。 本文介紹了在 Simulink? 中實現的常用且低成本的遙控潛水器 &#xff08;ROV&#xff09; BlueROV2 的仿真模型環境&#xff0c;該環境已針對水下航行器的基準控…

Unity打包APK報錯 using a newer Android Gradle plugin to use compileSdk = 35

Unity打包APK報錯 using a newer Android Gradle plugin to use compileSdk 35 三個報錯信息如下 第一個 WARNING:We recommend using a newer Android Gradle plugin to use compileSdk 35This Android Gradle plugin (7.1.2) was tested up to compileSdk 32This warning…

HTML5特殊字符

HTML中常用的特殊符號一般都以“&”開頭&#xff0c;以“;”結束。

本地大模型編程實戰(23)用智能體(Agent)實現基于SQL數據構建問答系統(2)

本文將用 智能體(Agent) 實現對 SQLite 數據庫的查詢&#xff1a;用戶用自然語言提出問題&#xff0c;智能體也用自然語言根據數據庫的查詢結果回答問題。 本次將分別在英文、中文環境下&#xff0c;使用 qwen2.5 、 MFDoom/deepseek-r1-tool-calling:7b 以及 llama3.1 做實驗。…

nodejs npm install、npm run dev運行的坎坷之路

1、前面的種種都不說了&#xff0c;好不容易運行起來oap-portal項目&#xff0c;運行idm-ui項目死活運行不起來&#xff0c;各種報錯&#xff0c;各種安裝&#xff0c;各種卸載nodejs&#xff0c;卸載nvm&#xff0c;重裝&#xff0c;都不好使。 2、甚至后來運行npm install會…