// main.c
#include <stc15.h>
#define ulong unsigned long
#define uint unsigned int
#define uchar unsigned char
#define CST_DIG_BEGIN 0 // 数码管扫描起始位
#define CST_DIG_END 7 // 数码管扫描结束位
/*---------全局变量定义---------*/
// 音乐播放相关
const uchar *music_track_ptr; // 指向当前播放的乐谱数据
volatile uint music_current_note_idx; // 当前乐谱中的音符索引
volatile uchar music_current_bpm; // 当前乐曲的BPM
volatile uint music_tick_counter; // Timer2节拍计数器
enum MusicPlayerState { // 音乐播放器状态
MUSIC_STATE_INITIAL_PAUSE, // 曲目开始前的长暂停
MUSIC_STATE_PLAYING_NOTE,
MUSIC_STATE_NOTE_PAUSE // 音符之间的短暂停
};
volatile enum MusicPlayerState music_current_state;
// 数码管显示及ADC相关
uchar ucT0_ms_count = 0; // Timer0中断计数
uchar ucDigtmp0 = 0;
uchar ucDigtmp1 = 0;
uchar ucDigtmp2 = 0;
uchar ucDigtmp3 = 0;
uchar ucDigtmp4 = 0;
uchar ucDigtmp5 = 0;
uchar ucDigtmp6 = 0;
uchar ucDigtmp7 = 0;
__bit adc_sample_state = 1; // ADC采样状态位 1:光, 0:温度
int temperature_value = 0; // 温度值
uint temperature_abs_val; // 温度绝对值
uint photo_adc_raw_val = 0; // 光敏ADC原始值
uchar temp_digit_100 = 0, temp_digit_10 = 0, temp_digit_1 = 0;
uchar photo_digit_100 = 0, photo_digit_10 = 0, photo_digit_1 = 0;
// 温度查表 (ADC值到实际温度的映射)
__code int arrThermLUT[] = {
/* 内容此处省略 */
};
// Timer1重装载值,用于产生不同音高
__xdata uint reset_timer1_pitch_values[]= {
/* 内容此处省略 */
/* 音符索引对照表,由低音到高音升序
C C# D D# E F F# G G# A A# B
1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 1
13 ,14 ,15 ,16 ,17 ,18 ,19 ,20 ,21 ,22 ,23 ,24 2
25 ,26 ,27 ,28 ,29 ,30 ,31 ,32 ,33 ,34 ,35 ,36 3
37 ,38 ,39 ,40 ,41 ,42 ,43 ,44 ,45 ,46 ,47 ,48 4
49 ,50 ,51 ,52 ,53 ,54 ,55 ,56 ,57 ,58 ,59 ,60 5
*/
};
// 乐谱数据
__code uchar track1_data[]= { // un secret
190, // BPM 每分钟节拍数
35,2, //乐谱 格式为一个音调加持续的时长的组合
/* 内容此处省略 */
0,0 // 停止标记
};
__code uchar track2_data[]= { // 彩豆森林
/* 内容此处省略 */
};
__code uchar track3_data[]= { // mopemope
/* 内容此处省略 */
};
/*--------- 音乐播放核心函数 ---------*/
void music_load_next_note_event(void); // 前向声明
void music_init_track(const uchar* selected_track_data) {
EA = 0; // 关键操作时关闭中断
TR1 = 0; // 确保Timer1停止发声
music_track_ptr = selected_track_data;
music_current_bpm = music_track_ptr[0];
music_current_note_idx = 1; // 指向第一个音高数据
music_current_state = MUSIC_STATE_INITIAL_PAUSE;
music_tick_counter = 400; // 曲目开始前的2秒暂停
EA = 1;
}
// 根据当前乐谱位置加载音符或休止符信息,并设置播放状态和计时器
void music_load_next_note_event(void) {
uchar pitch, duration_val;
pitch = music_track_ptr[music_current_note_idx];
duration_val = music_track_ptr[music_current_note_idx + 1];
if (pitch == 0 && duration_val == 0) { // 乐曲结束标记
music_init_track(music_track_ptr); // 重新播放当前曲目
return;
}
// 设置Timer1以产生音高对应的方波 (T1CLKO)
if (pitch != 0 && reset_timer1_pitch_values[pitch] != 0) {
TH1 = (65536 - reset_timer1_pitch_values[pitch]) / 256;
TL1 = (65536 - reset_timer1_pitch_values[pitch]) % 256;
TR1 = 1; // 启动Timer1发声
} else TR1 = 0; // 休止符或无效音高,停止Timer1
// 根据BPM和时值计算音符持续的Timer2节拍数
// 2000为一个经验值,用标准值3000误差很明显
music_tick_counter = 2000U / music_current_bpm * duration_val;
music_current_note_idx += 2; // 指向下一个音符的音高
music_current_state = MUSIC_STATE_PLAYING_NOTE;
}
// Timer2中断服务程序,驱动音乐播放状态转换
void music_state_machine_tick(void) __interrupt(12) __using(0) {
if (music_tick_counter > 0) music_tick_counter--;
if (music_tick_counter == 0) {
uchar duration_val_for_pause;
switch (music_current_state) {
case MUSIC_STATE_INITIAL_PAUSE: // 初始暂停结束
music_load_next_note_event();
break;
case MUSIC_STATE_PLAYING_NOTE: // 当前音符播放结束
TR1 = 0; // 停止发声
music_current_state = MUSIC_STATE_NOTE_PAUSE;
// 获取刚播放完音符的时值,用于计算音符后暂停的长度
// music_current_note_idx 已经指向了下一个音符的音高,所以上一个音符的时值在 music_current_note_idx - 1
duration_val_for_pause = music_track_ptr[music_current_note_idx - 1];
// 检查是否是乐曲结束标记
if (duration_val_for_pause == 0 && music_track_ptr[music_current_note_idx - 2] == 0) {
music_init_track(music_track_ptr); // 重新开始
return;
}
// 根据BPM和时值计算音符后暂停的Timer2节拍数
music_tick_counter = 1000U / music_current_bpm * duration_val_for_pause;
break;
case MUSIC_STATE_NOTE_PAUSE: // 音符间暂停结束
music_load_next_note_event();
}
}
}
/*---------数码管与发光二极管显示函数--------*/
void Seg7LedDisplay(uchar s, uchar e) {
unsigned char arrSegSelect[] = {
0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x40, 0x00
};
static uchar seg_scan_digit_idx = 0; // 数码管位选扫描索引
P2 = (P2 & 0xf0) | seg_scan_digit_idx;
switch(seg_scan_digit_idx) {
case 0: P0 = arrSegSelect[ucDigtmp0]; break;
case 1: P0 = arrSegSelect[ucDigtmp1]; break;
case 2: P0 = arrSegSelect[ucDigtmp2]; break;
case 3: P0 = arrSegSelect[ucDigtmp3]; break;
case 4: P0 = arrSegSelect[ucDigtmp4]; break;
case 5: P0 = arrSegSelect[ucDigtmp5]; break;
case 6: P0 = arrSegSelect[ucDigtmp6]; break;
case 7: P0 = arrSegSelect[ucDigtmp7]; break;
default: P0 = 0x00;
}
if(++seg_scan_digit_idx > e) seg_scan_digit_idx = s;
}
/*---------数码管显示数据更新函数--------*/
void Seg7LedUpdate(void) {
if (temperature_value < 0) ucDigtmp0 = 10; // '-'
else ucDigtmp0 = temp_digit_100;
ucDigtmp1 = temp_digit_10;
ucDigtmp2 = temp_digit_1;
ucDigtmp3 = 11; // 熄灭
ucDigtmp4 = 11;
ucDigtmp5 = photo_digit_100;
ucDigtmp6 = photo_digit_10;
ucDigtmp7 = photo_digit_1;
}
/*---------初始化ADC函数--------*/
void InitAdcTherm(void) {
P1ASF = 0x18;
ADC_RES = 0;
ADC_RESL = 0;
ADC_CONTR = 0x8b; // P1.3 热敏电阻
CLK_DIV = 0x20;
}
void InitAdcPhoto(void) {
P1ASF = 0x18;
ADC_RES = 0;
ADC_RESL = 0;
ADC_CONTR = 0x8c; // P1.4 光敏电阻
CLK_DIV = 0x20;
}
void InitADC_Alternating(void) {
if(adc_sample_state) InitAdcPhoto();
else InitAdcTherm();
adc_sample_state = !adc_sample_state;
EADC = 1; // 允许ADC中断
ADC_CONTR |= 0x08; // ADC_START = 1, 启动ADC转换
}
/*---------数据转换子函数--------*/
void Convert_ThermToDigit(void) { // 将温度信息按位转换
if(temperature_value < 0) temperature_abs_val = -temperature_value;
else temperature_abs_val = temperature_value;
temp_digit_100 = temperature_abs_val / 100;
temp_digit_10 = (temperature_abs_val % 100) / 10;
temp_digit_1 = temperature_abs_val % 10;
if (temperature_abs_val < 100) temp_digit_100 = 11; // 熄灭百位
if (temperature_abs_val < 10 && temperature_value >=0) temp_digit_10 = 11; // 熄灭十位
}
void Convert_PhotoToDigit(void) { // 将光照信息按位转换
photo_digit_100 = photo_adc_raw_val / 100;
photo_digit_10 = (photo_adc_raw_val % 100) / 10;
photo_digit_1 = photo_adc_raw_val % 10;
if (photo_adc_raw_val < 100) photo_digit_100 = 11;
if (photo_adc_raw_val < 10) photo_digit_10 = 11;
}
/*---------ADC中断服务函数--------*/
void ADC_Interrupt_Process(void) __interrupt(5) __using(2) {
EA = 0; // 保护性关闭中断
if(adc_sample_state == 0) { // 上次启动的是光敏 (adc_sample_state从1变为0) -> 这是光敏结果
photo_adc_raw_val = (ADC_RES << 8) | ADC_RESL;
Convert_PhotoToDigit();
} else { // 上次启动的是热敏 (adc_sample_state从0变为1) -> 这是热敏结果
temperature_value = arrThermLUT[((ADC_RES << 8) | ADC_RESL) / 4 - 1];
Convert_ThermToDigit();
}
ADC_CONTR &= ~0x10; // 清ADC_FLAG中断标志
EA = 1; // 恢复中断
}
/*---------T0定时器中断服务处理函数--------*/
void Timer0_ISR_Display_ADC(void) __interrupt(1) __using(1) {
TL0 = 0xCD; // 重装初值 for ~1ms @ 11.0592MHz/1T
TH0 = 0xD4;
ucT0_ms_count++;
Seg7LedDisplay(CST_DIG_BEGIN, CST_DIG_END);
if(!ucT0_ms_count) { // 每溢出一次更新ADC和显示数据
InitADC_Alternating(); // 启动下一次ADC采样 (温度/光照交替)
Seg7LedUpdate(); // 更新数码管显示缓冲区
}
}
/*---------主函数---------*/
void main(void) {
// 硬件IO口模式初始化
P0M1 = 0x00;
P0M0 = 0xff; // P0推挽输出 (数码管段选)
P2M1 = 0x00;
P2M0 = 0x08; // P2.3推挽输出
// Timer0 初始化 (用于数码管扫描和ADC触发)
AUXR |= 0x80; // Timer0/1工作在1T模式
TL0 = 0xCD; // ~1ms 定时
TH0 = 0xD4;
ET0 = 1; // 使能Timer0中断
TR0 = 1; // 启动Timer0
// Timer1 初始化 (用于音乐发声 T1CLKO)
INT_CLKO |= (1<<1); // 使能T1CLKO输出到P3.4
P3M0 |= (1<<4); // P3.4设为推挽输出
P3M1 &= ~(1<<4);
// Timer2 初始化 (用于音乐节拍定时)
AUXR |= 0x04; // 定时器时钟1T模式
T2L = 0x00; // 设置定时初始值
T2H = 0x28; // 设置定时初始值
AUXR |= 0x10; // 定时器2开始计时
IE2 |= 0x04; // 使能定时器2中断
EA = 1; // 打开总中断
music_init_track(track1_data);
while(1) {
// 按键检测,用于切换曲目
if (!P17) { // K3 (P1.7)
while(!P17); // 等待按键释放
music_init_track(track1_data);
} else if (!P33) { // K2 (P3.3)
while(!P33);
music_init_track(track2_data);
} else if (!P32) { // K1 (P3.2)
while(!P32);
music_init_track(track3_data);
}
}
}