$regfile = "m8def.dat" ' 设置使用的AVR型号为ATmega8
$crystal = 8000000 ' 设置AVR系统时钟频率为8MHz
' -------------------- 硬件引脚定义 --------------------
Su_port Alias Pinb.0 ' 将PB0引脚命名为声音输出端口
' -------------------- 端口初始化 --------------------
Config Su_port = Output ' 将声音输出端口设置为输出模式
Portb = &B11111110 ' 端口B初始化: PB0为输出(0),PB1-PB7启用内部上拉电阻(1)
' ==================== 变量定义 ====================
' 音乐控制变量
Dim Mu_tempo As Word ' 速度值,决定每个音符的基础时长(毫秒)
Dim Mu_octave As Byte ' 当前八度值(范围1-7)
Dim Mu_octdiv As Word ' 八度分频值,用于计算不同八度的频率
Dim Mu_pldata As String * 40 ' 音乐数据行缓冲区,每行最多40个字符
Dim Mu_dtlen As Byte ' 当前数据行的剩余字符长度
Dim Mu_dtposi As Byte ' 当前数据行的读取位置
Dim Mu_str As String * 1 ' 从数据行读取的单个字符
Dim Mu_freq As Word ' 计算出的音符频率值
' 通用临时变量
Dim Mu_temp1 As Byte ' 临时变量Byte型 No.1 - 常用于存储ASCII码或中间结果
Dim Mu_temp2 As Byte ' 临时变量Byte型 No.2 - 常用于存储音符索引
Dim Mu_temp3 As Byte ' 临时变量Byte型 No.3 - 常用于存储音符时值等级
Dim Mu_temp4 As Byte ' 临时变量Byte型 No.4 - 常用于存储音符代码
Dim mu_h As Byte ' 高八度标志
Dim mu_L As Byte ' 低八度标志
Dim mu_Z As Byte ' 当前八度标志
Dim Mu_tempw1 As Word ' 临时变量Word型 No.1 - 常用于存储音符时长
Dim Mu_tempw2 As Word ' 临时变量Word型 No.2 - 常用于存储频率计算值
Dim Mu_templ1 As Long ' 临时变量Long型 No.1 - 用于高精度计算
' ==================== I2C及OLED显示屏初始化 ====================
Config Scl = Portc.5 ' 设置I2C时钟引脚(SCL)为PC5
Config Sda = Portc.4 ' 设置I2C数据引脚(SDA)为PC4
Config Twi = 400000 ' 设置I2C总线速度为400kHz
I2cinit ' 初始化I2C硬件
$lib "i2c_twi.lbx" ' 使用TWI硬件I2C库(非软件模拟)
$lib "glcdSSD1306-I2C.lib" ' 使用SSD1306 OLED屏幕专用库
' 图形LCD配置: 128列 x 64行,使用SSD1306控制器
Config Graphlcd = Custom , Cols = 128 , Rows = 32 , Lcdname = "SSD1306"
Cls ' 清屏
Setfont font8x8TT ' 选择8x8字体
' ==================== 主程序循环 ====================
Do ' 无限循环开始
' 播放第一首乐曲:莫扎特 G大调弦乐小夜曲 第一乐章
Restore Mu_data ' 将数据指针指向音乐数据表1的起始位置
Gosub Mu_play ' 调用音乐播放子程序
cls
Wait 2 ' 播放完成后等待2秒钟
' 播放第二首乐曲:苏联国歌《牢不可破的联盟》
Restore Mu_data1 ' 将数据指针指向音乐数据表2的起始位置
Gosub Mu_play ' 调用音乐播放子程序
cls
Wait 2 ' 播放完成后等待2秒钟
Loop ' 重复循环,实现音乐连续播放
End ' 程序结束(实际不会执行到这里,因为循环无限)
' ==================== 音乐播放子程序 ====================
' 功能: 主音乐播放流程控制
' 调用关系: 主程序 → Mu_play → Mu_playini → Mu_play1 → Mu_playsub → (Mu_datcnv/Mu_noterd)
Mu_play:
Gosub Mu_playini ' ① 初始化:读取速度、设置初始八度
Mu_play1:
Gosub Mu_playsub ' ② 解析并执行一个音乐代码(音符/八度/休止符)
If Mu_temp1 = 0 Then Goto Mu_play1 ' ③ 如果未结束(Mu_temp1=0),继续处理下一个音符
Return ' ④ 当前曲目播放完毕,返回主程序
' -------------------- 初始化子程序 --------------------
' 功能: 初始化音乐播放参数,读取速度值
Mu_playini:
Mu_octave = 4 ' 将八度初始值设为4 (中央C所在的八度)
Mu_octdiv = 80 ' 设置默认八度分频值(O4对应的值)
Read Mu_temp1 ' 读取第一个数据(速度值,例如120或85)
Mu_tempo = 60000 / Mu_temp1 ' 将速度转换为毫秒:60000ms/速度值 = 每四分音符毫秒数
Mu_tempo = Mu_tempo * 4 ' 计算全音符的时长:四分音符时长 × 4
Mu_dtlen = 0 ' 初始化数据行长度为0,表示需要读取新行
Return
' -------------------- 音乐代码处理子程序 --------------------
' 功能: 解析并执行一个音乐指令(音符、八度变化、休止符等)
Mu_playsub:
' === 第1步:获取下一个字符 ===
If Mu_dtlen = 0 Then
Read Mu_pldata ' 从DATA语句读取一行音乐数据
Mu_dtlen = Len(mu_pldata) ' 获取这行数据的字符长度
Mu_dtposi = 1 ' 设置读取位置为第1个字符
End If
Gosub Mu_datcnv ' 从数据行读取一个字符到Mu_temp1
Mu_temp1 = Mu_temp1 And &HDF ' 将ASCII码转换为大写(清除第6位,即小写转大写)
' === 第2步:OLED显示处理(调试用)===
If Mu_temp1 = &H41 Then Lcdat 3 , 60 , "6" ' A -> 6
If Mu_temp1 = &H42 Then Lcdat 3 , 60 , "7" ' B -> 7
If Mu_temp1 = &H43 Then Lcdat 3 , 60 , "1" ' C -> 1
If Mu_temp1 = &H44 Then Lcdat 3 , 60 , "2" ' D -> 2
If Mu_temp1 = &H45 Then Lcdat 3 , 60 , "3" ' E -> 3
If Mu_temp1 = &H46 Then Lcdat 3 , 60 , "4" ' F -> 4
If Mu_temp1 = &H47 Then Lcdat 3 , 60 , "5" ' G -> 5
' === 第3步:判断指令类型 ===
If Mu_temp1 = &H4F Then Goto Mu_plays2 ' ASCII 4F = "O" - 八度改变指令
If Mu_temp1 = &H52 Then Goto Mu_plays3 ' ASCII 52 = "R" - 休止符指令
If Mu_temp1 > &H47 Then Return ' ASCII 47 = "G"以上,可能是"Z"结束代码
' === 第4步:处理音符指令(A-G) ===
Mu_temp1 = Mu_temp1 - &H40 ' 将A~G (ASCII 41-47) 转换为数字1~7
Mu_temp2 = Mu_temp1 + Mu_temp1 ' 乘以2,为查表做准备(表中每项占2字节)
Gosub Mu_datcnv ' 读取下一个字符(检查是否有升降音)
' 检查是否为升音(#)或降音($)
If Mu_temp1 < &H31 Then ' ASCII小于"1"的字符可能是#或$
If Mu_temp1 = &H23 Then ' ASCII 23 = "#" 升半音
Mu_temp2 = Mu_temp2 + 1 ' 索引加1,对应升半音的频率
Else ' 否则是降音"$"
Mu_temp2 = Mu_temp2 - 1 ' 索引减1,对应降半音的频率
End If
Gosub Mu_datcnv ' 再读取下一个字符(音符时值)
End If
Gosub Mu_noterd1 ' 解析音符时值(全分、二分、四分等)
' === 第5步:计算音符频率 ===
Mu_temp2 = Mu_temp2 - 1 ' 将1-7转换为0-6的索引
Mu_freq = Lookup(mu_temp2 , Mu_scale) ' 查表获取基础频率
Mu_freq = Mu_freq / Mu_octdiv ' 根据当前八度调整频率
Mu_tempw2 = Mu_freq * 12 ' 计算SOUND指令需要的频率参数
Mu_templ1 = _xtal / Mu_tempw2 ' 根据晶振频率计算分频值
Mu_tempw2 = Mu_templ1 ' 保存分频值
' === 第6步:计算音符时长 ===
Mu_templ1 = Mu_tempw1 * Mu_freq ' 时长 × 频率
Mu_templ1 = Mu_templ1 / 1000 ' 转换为毫秒
Mu_tempw1 = Mu_templ1 ' 保存时长参数
' === 第7步:OLED显示高低音符号 ===
IF Mu_octave = mu_h THEN Lcdat 1 , 60 , chr(46) ' 高音显示点在上方
IF Mu_octave = mu_L THEN Lcdat 4 , 60 , chr(46) ' 低音显示点在下方
IF Mu_octave = mu_Z THEN
Lcdat 1 , 60 , chr(32) ' 中音清除上方点
Lcdat 4 , 60 , chr(32) ' 中音清除下方点
ENDIF
' === 第8步:输出声音 ===
Sound Su_port , Mu_tempw1 , Mu_tempw2 ' 在PB0产生指定频率和时长的方波
Mu_plays4:
Mu_temp1 = 0 ' 设置标志,表示指令处理完成
Return
' -------------------- 八度处理子程序 --------------------
' 功能: 处理"O"指令,改变当前八度
Mu_plays2:
Gosub Mu_datcnv ' 读取八度参数
If Mu_temp1 < &H31 Then ' 如果参数是+或-(ASCII小于"1")
If Mu_temp1 = &H2B Then ' ASCII 2B = "+"
Mu_octave = Mu_octave + 1 ' 八度增加
Else ' 否则是"-"
Mu_octave = Mu_octave - 1 ' 八度降低
End If
Else ' 否则是数字1-7
Mu_octave = Mu_temp1 - &H30 ' 将ASCII数字转换为数值
' 指定中、低、高音频段分频值
mu_h = Mu_octave + 1 ' 高八度阈值(当前八度+1)
mu_L = Mu_octave - 1 ' 低八度阈值(当前八度-1)
mu_Z = Mu_octave ' 当前八度
End If
Mu_temp1 = Mu_octave - 1 ' 转换为0-6的索引
Mu_octdiv = Lookup(mu_temp1 , Mu_ocdiv) ' 查表获取对应的分频值
Goto Mu_plays4
' -------------------- 休止符处理子程序 --------------------
' 功能: 处理"R"指令,静音指定时长
Mu_plays3:
Gosub Mu_noterd ' 解析休止符时长
Waitms Mu_tempw1 ' 等待指定毫秒(静音)
Goto Mu_plays4
' -------------------- 字符读取子程序 --------------------
' 功能: 从当前数据行读取一个字符并转换为ASCII码
Mu_datcnv:
Mu_str = Mid(mu_pldata , Mu_dtposi , 1) ' 从缓冲区提取一个字符
Mu_dtposi = Mu_dtposi + 1 ' 读取位置后移
Mu_dtlen = Mu_dtlen - 1 ' 剩余长度减1
Mu_temp1 = Asc(mu_str) ' 将字符转换为ASCII码
Return
' -------------------- 音符/休止符时长解析子程序 --------------------
' 功能: 从数据中提取音符时值并计算实际时长
Mu_noterd:
Gosub Mu_datcnv ' 读取时值参数
Mu_noterd1:
Mu_temp4 = Mu_temp1 ' 保存时值代码
Select Case Mu_temp4
Case &H36 : ' ASCII "6" - 64分音符
Mu_temp3 = 6 ' 右移6位(除以64)
Mu_dtposi = Mu_dtposi + 1 ' 跳过下一个字符
Mu_dtlen = Mu_dtlen - 1
Case &H33 : ' ASCII "3" - 32分音符
Mu_temp3 = 5 ' 右移5位(除以32)
Mu_dtposi = Mu_dtposi + 1
Mu_dtlen = Mu_dtlen - 1
Case &H31 : ' ASCII "1" - 可能是全音符或16分音符
Mu_temp3 = 0 ' 先假设是全音符
If Mu_dtlen <> 0 Then ' 如果还有字符
Gosub Mu_datcnv ' 再读一个字符
If Mu_temp1 = &H36 Then ' 如果是"6"(16分音符)
Mu_temp3 = 4 ' 右移4位(除以16)
Else ' 否则是真正的全音符
Mu_dtposi = Mu_dtposi - 1 ' 回退指针
Mu_dtlen = Mu_dtlen + 1
End If
End If
Case &H38 : ' ASCII "8" - 8分音符
Mu_temp3 = 3 ' 右移3位(除以8)
Case &H32 : ' ASCII "2" - 2分音符
Mu_temp3 = 1 ' 右移1位(除以2)
Case Else : ' 其他情况(默认) - 4分音符
Mu_temp3 = 2 ' 右移2位(除以4)
End Select
Mu_tempw1 = Mu_tempo ' 取全音符时长
If Mu_temp3 <> 0 Then ' 如果不是全音符
Shift Mu_tempw1 , Right , Mu_temp3 ' 右移计算实际时长
End If
Return
' ==================== 数据表 ====================
' 音阶频率数据表
' 格式:每个音阶对应的频率值(以字WORD类型存储)
' 排列顺序:Ab, A, A#=Bb, B, (占位), C, C#=Db, D, D#=Eb, E, (占位), F, F#=Gb, G, G#
Mu_scale:
Data 33228% , 35200% , 37296% , 39512% , 39512% , 20930% , 22176% , 23496%
Data 24892% , 26372% , 26372% , 27936% , 29600% , 31360% , 33228%
' 八度分频数据表
' 对应八度1-7的分频系数,用于将基础频率调整到正确的八度
' 值越小,频率越高(高八度)
Mu_ocdiv:
Data 640% , 320% , 160% , 80% , 40% , 20% , 10%
' ==================== 音乐数据表 ====================
' 音乐代码语法说明:
' Ox : 改变八度 [1~7] (O4A对应440Hz标准音)
' + / - : 基于当前八度值增减(例如O+表示升一个八度)
' C D E F G A B : 分别对应Do Re Mi Fa Sol La Si
' # : 升高半音
' $ : 降低半音
' 1 2 4 8 16 32 64 : 音符时值(全音符、二分、四分、八分等)
' Rx : 休止符(长度与音符相同,例如R4表示四分休止)
' Z : 结束代码,标记音乐数据结束
' 曲目1:莫扎特 G大调弦乐小夜曲 第一乐章
Mu_data:
Data 120 ' TEMPO 速度值(每分钟120拍)
Data "o7"
Data "d4r8o-a8o+d4r8o-a8o+d8o-a8o+d8f#8a4r4"
Data "g4r8e8g4r8e8g8e8c#8e8o-a4o+r4"
Data "d4d4d8f#8e8d8d8c#8c#4c#8e8g8c#8"
Data "e8d8d4d8f#8e8d8d8c#8c#4c#8e8g8c#8"
Data "d8d8d16c#16o-b16o+c#16d8d8f#16e16d16e16"
Data "f#8f#8a16g16f#16g16a4r4z"
' 曲目2:苏联国歌《牢不可破的联盟》- 带歌词注释
Mu_data1:
Data 85 ' TEMPO 庄严的进行曲速度(每分钟85拍)
Data "o4"
Data "r8g8o+c4o-g8a8b4e8e8a4g8f8g4c8c8"
Data "d4d8e8f4f8g8a4b8o+c8d4o-g4"
Data "o+e4d8c8d4o-g8g8o+c4o-b8a8b4e8e8"
Data "a4g8f8g4c8c8o+c4o-b8a8g4g4"
Data "o+e2d8c8o-b8o+c8d4o-g4g2"
Data "o+c4c4o-b8a8g8a8b4e4e4r4"
Data "o+c4o-a8b8o+c4o-a8b8"
Data "o+c4o-a4o+c8f4r8"
Data "f2e8d8c8d8"
Data "e4c8c2"
Data "d2c8o-b8a8b8"
Data "o+c4o-a8a2"
Data "o+c4o-b8a8g4c8c8"
Data "o+c4o-b8a8g4r8g8"
Data "g2a4b4"
Data "o+c1z" ' Z标记音乐结束
' ==================== 程序结束 ====================
$include "../font8x8TT.font" ' 包含8x8字体文件
屏幕录制 2.zip
(1.01 MB, 下载次数: 0)
|