Keil程序仿真 & 算法及性能评估
2022.4.18~2022.4.30
实验环境:个人电脑、Keil、STC-ISP、STC-B学习板、步进电机
实验目的:
任务1:完善实验三(步进电机驱动)。
任务2:在完善实验三基础上,实现蜂鸣器可控发声和指示。
i.Beep模块按分层设计(底层驱动、向应用层提供API函数)
ii.按键模块按分层设计(底层驱动、向应用层提供事件及API函数)
iii.(选做)在Beep模块设计完成基础上,实现播放音乐的功能(参照STCBSP提供的Music模块API)
实验条件:同前
实验内容:
任务1:完善实验三(步进电机驱动)
实验3已完善,详情见实验3
任务2:在完善实验三基础上,实现蜂鸣器可控发声和指示。
(1)Beep模块按分层设计(底层驱动、向应用层提供API函数) 首先定义相关引脚:蜂鸣器的电源引脚在P3.4
编写驱动函数:
将相关引脚设置为推挽模式,并设置好定时器的模式:如下本次蜂鸣器使用的是定时器1,控制定时器的寄存器见操作手册,每个寄存器的功能如下图所示,其中TH1和TL1表示定时器1的初始值(表示T1的高8位和低8位),系统启动后,T1,每个时钟加一,直到溢出后触发定时器中断并重装初始值(这需要模式设置)
所以如图SysClock/(Hz*2)就表示T1需要经过多少次加1才能溢出,然后将这个数值乘上每个时钟周期的时间即可得到,发生一次定时器中断所需要的的时间
(这里T1设置的是12不分频模式1T速度是分频的12倍,所以如果是12T分频模式还需要乘12才是定时器中断一次的时间)
利用定时器中断进行蜂鸣器的发声:不断将引脚反转从而达到使蜂鸣器震动发声的效果,beep_time用于控制蜂鸣器发声的时间,当蜂鸣器不发生时需要将引脚置于低电平(减少功耗)
设置蜂鸣器属性:设置蜂鸣器发声的频率和时间(通过修改定时器的频率做到)
然后根据频率计算出应该进行中断的次数来控制时间(beep_time 单位10ms)
(2)按键模块按分层设计(底层驱动、向应用层提供事件及API函数)
对于按键的引脚而言,原理图如下,所以当key1,2,3按下后相关的引脚电平会为0,抬起时为1,我们可以根据按键电平的跳变来判断按键是抬起还是按下
定义引脚和驱动函数:驱动函数只需将相关引脚设置为准双向口来确保可以输出高低电平同时不会因电流过大(如推挽模式)而将引脚烧坏
我们定义两个状态:一个当前状态和上一时刻状态,我们可以通过这两个状态的不同来判断按键是否按下或抬起
大致原理如下:
这里我使用的是40ms内如果相关按键电平为0的时间超过25ms则我们认为是按键按下状态,否则为抬起状态,相关代码如下:
当判断有按键事件发生时,运行回调函数CallBack,这个函数需要用于自己提供
最后获取按键事件状态:在按键获取一次事件后需要将按键事件复位
(3)(选做)在Beep模块设计完成基础上,实现播放音乐的功能(参照STCBSP提供的Music模块API)
首先想要播放音乐我们需要通过蜂鸣器发出每一个音调的声音,相关音调的频率如下(因为是使用蜂鸣器模块作为底层,所以这里驱动函数是空的):
每个字母表示相关音调的7个音节的低中高音(比如C数组前7位表示,C调的低音do、re、mi、fa、so、la、si再后面7位表示中音最后7位高音)
然后我们编写播放音调的函数,变量syl取值为0x11-0x17,0x21-0x27,0x31-0x37,每个字节的高4位表示低中高音,第4位表示音节,所以我们获取相关频率可以按照如图方式进行,然后变量beats表示这个音节的节拍数(取值单位为1/16个节拍,如0x10表示1个节拍),beatsPM表示1分钟的节拍数(比如60就表示一个节拍1s,这个变量用于控制1个节拍的时间),然后我们通过这两个变量计算出蜂鸣器发声的时间(图中公式是为了减少因为除法带来的误差进行化简后的结果)
然后我们开始播放音乐:
通常情况下每次取两字节第一个字节表示音节,第二个字节表示节拍
如果遇到控制字符(如修改音调或改变每分钟节拍数)则进行相关的处理,没有遇到的情况如下:播放一次后songsit(用于记录播放位置)加2,播放完成后改变音乐模式为enumModeStop
相关控制字符如改变节拍率:改变后跳转songsit到下一个可以播放的字符位置即可,跳转时判断一下是否到歌曲结束部分,其余控制字符同理。
设置音乐播放的初始属性:
改变音乐播放状态:注意在停止时将变量复位
获取音乐播放状态:该函数用于后续对这几个函数进行封装后连续播放音乐
最后进行这几个功能的汇总测试:
用“定时器2”及其“中断”控制(无源)蜂鸣器发声,要求发声频率可以改变和显示;
注:发声频率在200~2000Hz范围内;
按键控制发声频率变化(每次变化步长 >= 100Hz);
发声频率在数码管上显示;
步进电机(4个流水灯)快慢随发声频率同步变化。
通过按键控制频率,按键1蜂鸣器将按照设定的频率发声1s,按键2增加频率同时增加电机的运行速度,按键3降低频率同时降低电机的运行速度。
显示部分通过10ms回调实时显示。
具体代码如下图所示
完整代码
蜂鸣器头文件
#include "STC15F2K60S2.H"
sbit beep_bit = P3 ^ 4;
unsigned int Hz = 1000;
extern code long int SysClock;
unsigned int beep_time = 0;
void _BeepInit()
{
P3M0 = 0X10; // 保持其他模式不变,P3.4设置为推挽输出
P3M1 = 0X00;
beep_bit = 0;
AUXR |= 0x40; //定时器1工作在1T、16位自动重装初值
TMOD &= 0x0F; //计算器方式控制高4位控制T1低4位控制T0,这里只控制T1
TL1 = 65536 - (SysClock / (Hz * 2));
TH1 = (65536 - SysClock / (Hz * 2)) >> 8; //两者都是T1计数器
TR1 = 1; //允许T1开始计数
ET1 = 1;
}
void beep_play(void) interrupt 3
{
if (beep_time)
{
beep_time--;
beep_bit = ~beep_bit;
}
else
beep_bit = 0;
}
void _SetBeep(unsigned int hz, unsigned int time)
{
Hz = hz;
TL1 = 65536 - (SysClock / (Hz * 2));
TH1 = (65536 - SysClock / (Hz * 2)) >> 8; //两者都是T1计数器
beep_time = time * (Hz/50);
}
按键头文件
#include "STC15F2K60S2.H"
enum KeyName
{
enumKey1,
enumKey2,
enumKey3
}; //按键名
enum KeyActName
{
enumKeyNull,
enumKeyPress,
enumKeyRelease,
enumKeyFail
}; //按键动作名
sbit KEY1 = P3 ^ 2;
sbit KEY2 = P3 ^ 3;
sbit KEY3 = P1 ^ 7;
bit state1 = 1, state2 = 1, state3 = 1; //按键状态1:抬起,0按下
bit Lstate1 = 1, Lstate2 = 1, Lstate3 = 1;
enum KeyActName KeyAct1 = enumKeyNull, KeyAct2 = enumKeyNull, KeyAct3 = enumKeyNull, KeyAct;
unsigned char count = 40;
unsigned char key_num1 = 0, key_num2 = 0, key_num3 = 0;
void Key_Init() //初始化函数
{
// IT1 = 1;
// IT0 = 1; //下降沿触发
P3M0 &= 0xf3;
P3M1 &= 0xf3; // p3.2,p3.3准双向口
P1M0 &= 0x7f;
P1M1 &= 0x7f;
// EX0 = 1;
// EX1 = 1; //开启INT0,INT1的中断服务
}
void KeyJudge(void (*CallBack)()) // 1ms延时,按键状态改变函数(核心)
{
if (KEY1 == 0)
key_num1++;
if (KEY2 == 0)
key_num2++;
if (KEY3 == 0)
key_num3++;
if (count != 0)
count--;
else //改变状态
{
count = 40; //复位
if (key_num1 >= 25) // 40ms内有25ms时间处于按下状态则认为是按下
state1 = 0; //按下状态
else
state1 = 1; //抬起状态
if (key_num2 >= 25)
state2 = 0; //按下状态
else
state2 = 1; //抬起状态
if (key_num3 >= 25)
state3 = 0; //按下状态
else
state3 = 1; //抬起状态
if ((Lstate1 == 1 && state1 == 0) || (Lstate1 == 0 && state1 == 1)) //按键1按下或抬起
{
if (Lstate1 == 1 && state1 == 0)
KeyAct1 = enumKeyPress;
else
KeyAct1 = enumKeyRelease;
CallBack();
}
if ((Lstate2 == 1 && state2 == 0) || (Lstate2 == 0 && state2 == 1)) //按键2按下或抬起
{
if (Lstate2 == 1 && state2 == 0)
KeyAct2 = enumKeyPress;
else
KeyAct2 = enumKeyRelease;
CallBack();
}
if ((Lstate3 == 1 && state3 == 0) || (Lstate3 == 0 && state3 == 1)) //按键3按下或抬起
{
if (Lstate3 == 1 && state3 == 0)
KeyAct3 = enumKeyPress;
else
KeyAct3 = enumKeyRelease;
CallBack();
}
Lstate1 = state1;
Lstate2 = state2;
Lstate3 = state3;
key_num1 = 0;
key_num2 = 0;
key_num3 = 0;
}
}
char _GetKeyAct(char key) //事件获取一次后就状态取消
{
switch (key)
{
case enumKey1:
KeyAct = KeyAct1;
KeyAct1 = enumKeyNull;
return KeyAct;
case enumKey2:
KeyAct = KeyAct2;
KeyAct2 = enumKeyNull;
return KeyAct;
case enumKey3:
KeyAct = KeyAct3;
KeyAct3 = enumKeyNull;
return KeyAct;
default:
return enumKeyFail;
}
}
音乐播放器头文件
extern void _SetBeep(unsigned int hz, unsigned int time);
code unsigned int A[21] = {221, 248, 278, 294, 330, 371, 416, 441, 495, 556, 589, 661, 742, 833, 882, 990, 112, 1178, 1322, 1484, 1665};
code unsigned int _B[21] = {248, 278, 294, 330, 371, 416, 467, 495, 556, 624, 661, 742, 833, 935, 990, 1112, 1178, 1322, 1484, 1665, 1869};
code unsigned int C[21] = {131, 147, 165, 175, 196, 221, 248, 262, 294, 330, 350, 393, 441, 495, 525, 589, 661, 700, 786, 882, 990};
code unsigned int D[21] = {147, 165, 175, 196, 221, 248, 278, 294, 330, 350, 393, 441, 495, 556, 589, 661, 700, 786, 882, 990, 1112};
code unsigned int E[21] = {165, 175, 196, 221, 248, 278, 312, 330, 350, 393, 441, 495, 556, 624, 661, 700, 786, 882, 990, 1112, 1248};
code unsigned int F[21] = {175, 196, 221, 234, 262, 294, 330, 350, 393, 441, 495, 556, 624, 661, 700, 786, 882, 935, 1049, 1178, 1322};
code unsigned int G[21] = {196, 221, 234, 262, 294, 330, 371, 393, 441, 495, 556, 624, 661, 742, 786, 882, 990, 1049, 1178, 1322, 1484};
// unsigned int D[7]={294,330,350,393,441,495,556};
// unsigned int DHigh[7]={589,661,700,786,882,990,1112};
// tone: 音调。F9,FA,FB,FC,FD,FE,FF分别对应G\A\B\C\D\E\F调,
unsigned char beatsPM = 60; //每分钟节拍数,最好为60的倍数
unsigned char tone = 0xFD;
unsigned int time;
enum PlayState
{
enumBeepOK,
enumBeepBusy,
enumBeepFail
};
enum PlayerMode
{
enumModeInvalid = 0, //播放模式非法
enumModePlay, //播放
enumModePause, //暂停(可恢复续放)
enumModeStop
}; //停止(结束)
enum PlayerMode MusicMode = enumModeStop;
unsigned char *_song;
unsigned int songsit = 0;
unsigned int begin;
unsigned int end;
unsigned int songsize;
unsigned int delay = 1;
bit repeat = 0;
enum MusicKeyword
{
enumMscSetBeatsPM = 0xF0, //音乐编码中关键字: 设置 音乐节拍
enumMscSetTone, //音乐编码中关键字: 设置 音调
enumMscRepeatBegin, //音乐编码中关键字: 设置 重复开始
enumMscRepeatEnd
}; //音乐编码中关键字: 设置 重复结束
void MusicPlayerInit()
{
// BeepInit();
}
// 0x11 — 0x17 :对应低音 do、re、mi、fa、so、la、si、 0x21 — 0x27 :对应中音 do、re、mi、fa、so、la、si
// 0x31 — 0x37 :对应高音 do、re、mi、fa、so、la、si
// 其中“节拍数”部分:
// 0x01-0xFF:单位1/16拍。也即十六进制中,高4位表示整拍数,低4位表示分拍数(1/16)
// 如:发音2拍: 0x20
// 发音半拍: 0x08
// 发音1拍半:0x18
// 函数返回值:enumBeepOK:调用成功
// enumBeepBusy:忙(上一音未按设定发完,或因蜂鸣器正在发音)
// enumBeepFail:调用失败(音调参数tone不对,或音高编码scale不对)
// enumMscDrvSeg7 : 启用LED
// enumMscDrvLed : 启用Light
// enumMscDrvSeg7andLed : 启用LED和Light
// enumMscSetBeatsPM : 设置节拍率, 后面再跟 节拍率(1字节)
// enumMscSetTone : 设置音调, 后面再跟 音调(1字节):0xFA 或 0xFB 或 0xFC 或 0xFD 或 0xFE 或 0xFF 或 0xF9
// enumMscRepeatBegin : 设置音乐播放重复开始处。重复一次(暂不能多次),暂不能嵌套(嵌套无效或可能导致不可预期结果)
// enumMscRepeatEnd : 设置音乐播放重复结束处
char PlayTone(unsigned char syl, unsigned char beats)
{
switch (tone)
{
case 0xFA: // A调
time = (int)(beats * 25) / (int)(beatsPM / 15);
_SetBeep(A[((syl >> 4) - 1) * 7 + (syl & 0x0F) - 1], time);
return enumBeepOK;
case 0xFB: // B调
time = (int)(beats * 25) / (int)(beatsPM / 15);
_SetBeep(_B[((syl >> 4) - 1) * 7 + (syl & 0x0F) - 1], time);
return enumBeepOK;
case 0xFC: // C调
time = (int)(beats * 25) / (int)(beatsPM / 15);
_SetBeep(C[((syl >> 4) - 1) * 7 + (syl & 0x0F) - 1], time);
return enumBeepOK;
case 0xFD: // D调
time = (int)(beats * 25) / (int)(beatsPM / 15);
_SetBeep(D[((syl >> 4) - 1) * 7 + (syl & 0x0F) - 1], time);
return enumBeepOK;
case 0xFE: // E调
time = (int)(beats * 25) / (int)(beatsPM / 15);
_SetBeep(E[((syl >> 4) - 1) * 7 + (syl & 0x0F) - 1], time);
return enumBeepOK;
case 0xFF: // F调
time = (int)(beats * 25) / (int)(beatsPM / 15);
_SetBeep(F[((syl >> 4) - 1) * 7 + (syl & 0x0F) - 1], time);
return enumBeepOK;
case 0xF9: // G调
time = (int)(beats * 25) / (int)(beatsPM / 15);
_SetBeep(G[((syl >> 4) - 1) * 7 + (syl & 0x0F) - 1], time);
return enumBeepOK;
default:
return enumBeepFail;
}
}
void PlayMusic() // 1ms回调
{
if (MusicMode == enumModePlay) //处于播放状态
{
delay--;
if (delay != 0)
return;
if (_song[songsit] == enumMscSetBeatsPM) //改变节拍率
{
beatsPM = _song[songsit + 1];
if (songsit + 2 == end)
{
if (repeat == 0) //不用重复
{
MusicMode = enumModeStop;
songsit = 0;
delay = 1;
return;
}
else //需要重复
{
repeat = 0;
songsit = begin;
delay = 1;
return;
}
}
else
songsit += 2;
}
if (_song[songsit] == enumMscSetTone) //改变音调
{
tone = _song[songsit + 1];
if (songsit + 2 == end)
{
if (repeat == 0) //不用重复
{
MusicMode = enumModeStop;
songsit = 0;
delay = 1;
return;
}
else //需要重复
{
repeat = 0;
songsit = begin;
delay = 1;
return;
}
}
else
songsit += 2;
}
if (_song[songsit] == enumMscRepeatEnd) //记录重复结束位置
{
end = songsit;
if (songsit + 1 == songsize) //最后一个字符
{
if (repeat == 0) //不用重复
{
MusicMode = enumModeStop;
songsit = 0;
delay = 1;
return;
}
else //需要重复
{
repeat = 0;
songsit = begin;
delay = 1;
return;
}
}
else
songsit++;
}
if (_song[songsit] == enumMscRepeatBegin) //记录重复开始位置
{
begin = songsit + 1;
repeat = 1; //进行重复
if (songsit + 1 == songsize) //最后一个字符
{
if (repeat == 0) //不用重复
{
MusicMode = enumModeStop;
songsit = 0;
delay = 1;
return;
}
else //需要重复
{
repeat = 0;
songsit = begin;
delay = 1;
return;
}
}
else
songsit++;
}
if (_song[songsit] != 0x00) //休止符
PlayTone(_song[songsit], _song[songsit + 1]); //播放音节
delay = (int)(_song[songsit + 1] * 250) / (int)(beatsPM / 15); //以下一个音节的节拍数作为延时
if (songsit + 2 == end) //播放完毕
MusicMode = enumModeStop;
else
songsit += 2;
}
}
void SetMusic(unsigned char _beatsPM, unsigned char _tone, unsigned char *pt, unsigned int datasize)
{
beatsPM = _beatsPM;
tone = _tone;
_song = pt;
songsit = 0;
end = datasize;
songsize = datasize;
}
void SetPlayerMode(enum PlayerMode play_ctrl)
{
MusicMode = play_ctrl;
if (MusicMode == enumModeStop)
{
songsit = 0;
delay = 1;
}
}
unsigned char GetPlayMode()
{
if (MusicMode == enumModeStop)
return enumBeepOK;
else
return enumBeepBusy;
}
测试主程序(不含音乐测试,想测试音乐将主函数最后的注释SetPlayMode取消即可自动开始播放音乐)
//功能:双通道秒表,K3、K1独立控制两个秒表计时开始和结束,计时分辨率0.01S
#include "STC15F2K60S2.H"
//#include "beep.h"
//#include "Key.h"
#include "sys.h"
#include "motor.h"
#include "STC15F2K60S2.H"
#include "displayer.h"
#include "my_beep.h"
#include"my_key.h"
#include"my_music.h"
#include "song.c"
code unsigned long SysClock=11059200; //必须。定义系统工作时钟频率(Hz),用户必须修改成与实际工作频率(下载时选择的)一致
unsigned int Hh=1000;
#ifdef _displayer_H_ //显示模块选用时必须。(数码管显示译码表,用戶可修改、增加等)
code char decode_table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x00,0x08,0x40,0x01, 0x41, 0x48,
/* 序号: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 */
/* 显示: 0 1 2 3 4 5 6 7 8 9 (无) 下- 中- 上- 上中- 中下- */
0x3f|0x80,0x06|0x80,0x5b|0x80,0x4f|0x80,0x66|0x80,0x6d|0x80,0x7d|0x80,0x07|0x80,0x7f|0x80,0x6f|0x80 };
/* 带小数点 0 1 2 3 4 5 6 7 8 9 */
#endif
unsigned int TextHz=200;
unsigned int speed=1;
void Display_CallBack()
{
Seg7Print(10,10,10,10,TextHz/1000,TextHz/100%10,TextHz/10%10,TextHz%10);
}
void key_callback()
{
if(_GetKeyAct(enumKey1)==enumKeyPress)
{
_SetBeep(TextHz,100);
}
if(_GetKeyAct(enumKey2)==enumKeyPress)
{
if(TextHz!=2000)
{
TextHz+=200;
speed++;
SetMotor(enumStepMotor1, speed, 10000);
}
}
if(_GetKeyAct(enumKey3)==enumKeyPress)
{
if(TextHz!=200)
{
TextHz-=200;
speed--;
SetMotor(enumStepMotor1, speed, 10000);
}
}
}
void CallBack_1ms()
{
PlayMotor();
PlayMusic();
KeyJudge(key_callback);
}
void main()
{
MySTC_Init();
DisplayerInit();
Key_Init();
MusicPlayerInit();
_BeepInit();
MotorInit();
SetDisplayerArea(0,7);
Seg7Print(10,10,10,10,10,10,10,10);
LedPrint(0x0f);
SetMusic(60,0xFC,song,sizeof(song));
SetMotor(enumStepMotor1, speed, 10000);
//SetEventCallBack(enumEventKey,key_callback);
SetEventCallBack(enumEventSys1mS,CallBack_1ms);
SetEventCallBack(enumEventSys10mS,Display_CallBack);
//SetPlayerMode(enumModePlay);
while (1)
{
MySTC_OS();
}
}