本文所述来自方天宇学长的线上蓝桥杯经验分享会整理。
目录
0 关于bilibili网课推荐
基础部分跟小蜜蜂老师足够,进阶外设部分强推up主机械狗
主要还是形成自己的代码风格,或者说形成自己的代码模板。
1 STC头文件的添加(STC15F2K60S2.H)
打开STC-ISP
打开keil,新建新工程,选择如下:
#include<STC15F2K60S2.H>即可
2 书写习惯
2.1 变量定义
typedef unsigned char uchar;
typedef unsigned int uint;
2.2 38译码器相关函数封装
#define selec(y,x) P0=x;P2|=y;P2&=0x1f;//此宏定义很关键,可以节省大量代码
P0 = _ _ _ _ _ _ _ _
P2 |= y:
P2 = _ _ _ _ _ _ _ _ => P2 = x x x _ _ _ _ _
(Y4: P2 |= 0x80; Y5: P2 |= 0xa0; Y6: P2 |= 0xc0; Y7:P2 |= 0xe0)
P2 &= 0x1f(前序数据已锁存,此时清零无影响):
P2 = x x x _ _ _ _ _ => P2 = 0 0 0 _ _ _ _ _
2.2 LED和数码管
定义与声明
#define selec(y,x) P0=x;P2|=y;P2&=0x1f;//此宏定义很关键,可以节省大量代码
uchar code t_display[]={ //????
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black - H J K L N o P U t G Q r M y
0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46}; //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1
uchar code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; //可从烧录软件中复制
uchar smg[8]={16,16,16,16,16,16,16,16};//数码管相关数组
uchar L[9]={0,0,0,0,0,0,0,0,0};//LED相关数组
uchar countkey;//按键扫描计时
uint counttemp;//温度读取计时
uint temp;
其中,code t_display[]和code T_COM[]为STC-ISP中自带:
处理(整体放于中断之中,与主体独立开)
void Timer0Init(void) //1??@12.000MHz
{
AUXR |= 0x80; //?????1T??
TMOD &= 0xF0; //???????
TL0 = 0x20; //???????
TH0 = 0xD1; //???????
TF0 = 0; //??TF0??
TR0 = 1; //???0????
EA=1;
ET0=1;
}
void servicet0() interrupt 1
{
static uchar dspcom=0;
//led处理代码
selec(0x80,~(L[8]*128+L[7]*64+L[6]*32+L[5]*16+L[4]*8+L[3]*4+L[2]*2+L[1]))
//由此开始的后续五行代码是数码管处理代码
selec(0xc0,0x00);//先关闭所有数码管,消除虚影
selec(0xe0,~t_display[smg[dspcom]]);//段选
selec(0xc0,T_COM[dspcom]);//位选
if(++dspcom==8)//扫描
dspcom=0;
++countkey;//计时变量 加一次就是1ms
++counttemp;
}
其中,void Timer0Init(void)为STC-ISP中自带(EA=1;ET0=1;自己加):
想要 L3亮,L2、L1灭:
L[3]=1; L[2]=0; L[1]=0;
smg[0]=12; //从左往右第0位显示字母C
smg[5]=temp/100; //第5位显示百位(实际表示十位)
smg[6]=temp/10%10+32; //第6位显示十位(实际表示个位) +32:数字加上小数点的显示方式
smg[7]=temp%10; //第7位显示个位(实际表示第一位小数)
for(i=1;i<5;i++) //第1~4位熄灭
smg[i]=16; //16对应black,即直接熄灭
2.3 独立按键
这里的按键的前三行代码有异常精妙之处,详见:最精辟和实用的按键处理程序 - 51单片机
#define set 1
uchar trg,cnt;//独立按键相关变量
uchar smgstat;
uchar bitset;
uchar tset1,tset2;
uchar tmax,tmin;
void keyscan()//按键扫描函数
{
uchar readdat=P3^0xff,i=0;
trg=readdat&(readdat^cnt);
cnt=readdat; //这三行代码很关键,注意理解
if(trg&0x01) //s7按下
{
if(smgstat==set)
if(bitset==1)
{
if(tmax>0)
tmax--;
}
else if(bitset==2)
{
if(tmin>0)
tmin--;
}
}
else if(trg&0x02) //s6按下
{
if(smgstat==set)
if(bitset==1)
{
if(tmax<99)
tmax++;
}
else if(bitset==2)
{
if(tmin<99)
tmin++;
}
}
else if(trg&0x04) //s5按下
{
if(smgstat==set)
if(bitset==1)
{
bitset=2;
}
else if(bitset==2)
{
bitset=1;
}
}
else if(trg&0x08) //s4按下
{
if(smgstat==wendu)
{
smgstat=set;
tset1=tmax;
tset2=tmin;
bitset=1;
}
else if(smgstat==set)
{
if(tmin>tmax)
{
tmax=tset1;
tmin=tset2;
L[4]=1;
}
else
{
L[4]=0;
}
smgstat=wendu;
}
}
}
主函数while(1)中放一个扫描函数,10ms扫一次
if(countkey>9)//按键扫描10ms
{
countkey=0;
keyscan();
}
2.4 矩阵按键
void keyscan()
{
uchar i;
static uchar keystat=0; //必须是静态变量
uchar keyx=0,keyy=0;
P3=0x0f;P4=0x00;
if(!P30) keyx=3;
else if(!P31) keyx=2;
else if(!P32) keyx=1;
else if(!P33) keyx=0;
P3=0xf0;P4=0xff;
if(!P34) keyy=4;
else if(!P35) keyy=3;
else if(!P42) keyy=2;
else if(!P44) keyy=1;
keyval=keyx+keyy*4;
/*
y x 0 1 2 3
1 4 5 6 7
2 8 9 10 11
3 12 13 14 15
4 16 17 18 19
*/
switch(keystat)
{
case 0:
if(keyval!=0) keystat=1;
else{ for(i=0;i<9;i++) L[i]=0;}
break;
case 1:
if(keyval==0)
keystat=0; //这句不能少
else
{
keystat=2;
value = keyval; //这句不能少
switch(keyval)
{
case 4:
L[1]=L[2]=1;
break;
case 8:
L[3]=L[4]=1;;
break;
case 12:
L[5]=L[6]=1;
break;
case 16:
L[7]=L[8]=1;
break;
default: ;break;
}
}
break;
case 2:
if(keyval==0) keystat=0; //这两句都不能少
break;
}
}
void main()
{
initsys();
while(1)
{
if(keyval>9)
{
smg[6]=keyval/10; //十位
smg[7]=keyval%10; //个位
}
else if(keyval<10&&keyval>0)
{
smg[6]=16; //灭
smg[7]=keyval%10; //个位
}
else if(keyval==0)
{
smg[6]=16; //灭
smg[7]=16; //灭
}
if(countkey==10)
{
countkey=0;
keyscan();
}
}
另一种情况(第十届国赛超声波dac):
static uchar keystat=0;
uchar keyx=0,keyy=0;
P32=P33=1;P34=P35=0;
if(!P32) keyx=1; //这里很关键,防止串口通信被破坏。
else if(!P33) keyx=0;
P32=P33=0;P34=P35=1;
if(!P34) keyy=4;
else if(!P35) keyy=3;
2.5 NE555频率测量
注意:板子上P34需和SIGNAL通过短路帽相短接。
#include "stc15f2k60s2.h"
typedef unsigned char uchar;
typedef unsigned int uint;
#define selec573(y,x) P0=x;P2=y;P2=0;
uchar code t_display[]={ //????
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black - H J K L N o P U t G Q r M y
0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46}; //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1
uchar code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; //??
uchar smg[8]={16,16,16,16,16,16,16,16};
uint fre=0,fre2;
uint count;
void T0init()//定时器0工作在计数
{
AUXR |=0x80;
TMOD |=0x04;
TH0=0xff;
TL0=0xff;
TR0=1;
EA=1;
ET0=1;
}
//上面这个没得复制
void serviceT0() interrupt 1
{
fre++;
}
void T1init()//定时器1工作在定时
{
AUXR |= 0x40; //?????1T??
TMOD &= 0x0F; //???????
TL1 = 0x20; //???????
TH1 = 0xD1; //???????
TF1 = 0; //??TF1??
TR1 = 1; //???1????
ET1=1;
}
void serviceT1() interrupt 3
{
static uchar dspcom=0;
selec573(0xc0,0x00);
selec573(0xe0,~t_display[smg[dspcom]]);
selec573(0xc0,T_COM[dspcom]);
if(++dspcom==8)
dspcom=0;
if(++count>499) //500ms
{
count=0;
ET0=0;//停止计数
fre2= 2*fre;
fre=0;
ET0=1;//开始计数
}
}
void initsys()
{
selec573(0x80,0xff);
selec573(0xa0,0x00);
T0init();
T1init();
}
void main()
{
initsys();
while(1)
{
smg[3]=fre2/10000;//数码管显示示例
smg[4]=fre2/1000%10;
smg[5]=fre2/100%10;
smg[6]=fre2/10%10;
smg[7]=fre2%10;
}
}
2.6 PWM脉冲调制
#include <STC15F2K60S2.H>
typedef unsigned char uchar;
typedef unsigned int uint;
#define selec(y,x) P0=x;P2=y;P2=0;
uchar code t_display[]={ //????
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black - H J K L N o P U t G Q r M y
0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46}; //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1
uchar code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; //??
uchar smg[8]={16,16,16,16,16,16,16,16};
uchar L[9]={0,0,0,0,0,0,0,0,0};
uchar countkey;
uchar keyval;
uchar pwmval;
uchar countpwm;
uchar buzz,relay;
void Timer0Init(void) //100us@12.000MHz 定时器中断周期时间为100微秒也就是
{
AUXR |= 0x80; //?????1T??
TMOD &= 0xF0; //???????
TL0 = 0x50; //???????
TH0 = 0xFB; //???????
TF0 = 0; //??TF0??
TR0 = 1; //???0????
ET0=1;
EA=1;
}
void servicet0() interrupt 1
{
static uchar dspcom=0;
uchar temp;
if(++temp<pwmval)// pwmval就是用于控制占空比的变量
{
L[1]=L[2]=1;//控制该led灯的亮度
buzz=1;//蜂鸣器
}
else
{
L[1]=L[2]=0;
buzz=0;
}
selec(0x80,~(L[8]*128+L[7]*64+L[6]*32+L[5]*16+L[4]*8+L[3]*4+L[2]*2+L[1]*1))
selec(0xA0,buzz*64+relay*16)
//蜂鸣器与继电器处理函数(十三届国赛还用到了电机引脚,是同理)
if(temp>9)
{
selec(0xc0,0x00);
selec(0xe0,~t_display[smg[dspcom]]);
selec(0xc0,0x01<<dspcom);
if(++dspcom==8)
dspcom=0;
temp=0;
}
++countkey;
}
void keyscan()
{
static uchar keystat=0;
uchar keyx=0,keyy=0,i;
P3=0x0f;P4=0x00;
if(!P30) keyx=3;
else if(!P31) keyx=2;
else if(!P32) keyx=1;
else if(!P33) keyx=0;
P3=0xf0;P4=0xff;
if(!P34) keyy=4;
else if(!P35) keyy=3;
else if(!P42) keyy=2;
else if(!P44) keyy=1;
keyval= keyx+keyy*4;
switch(keystat)
{
case 0:if(keyval!=0)keystat=1;break;
case 1:
if(keyval==0)
keystat=0;
else
{
keystat=2;
switch(keyval)
{
case 4: pwmval+=1;break;//按键控制pwm波占空比
case 5: pwmval=2;break;
case 6: pwmval=9;break;
}
}
break;
case 2:
if(keyval==0)
{
keystat=0;
for(i=0;i<9;i++)
L[i]=0;
}
}
}
void main()
{
selec(0xa0,0x00);
Timer0Init();
while(1)
{
// if(countpwm<pwmval)
// {
// L[1]=L[2]=1;
// }
// else if(countpwm==10)
// {
// L[1]=L[2]=0;
// countpwm=0;
// }
//
smg[6]=pwmval;
smg[7]=0;
if(countkey>9)
{
countkey=0;
keyscan();
}
}
}
2.7 超声波测距
注意:软件延时的_nop_()需要#include "intrins.h"这个头文件
#include <STC15F2K60S2.H>
#include "intrins.h"//主要是软件延时的_nop_()需要此头文件
#define selec(y,x) P0=x;P2|=y;P2&=0x1f;
typedef unsigned char uchar;
typedef unsigned int uint;
sbit TX=P1^0;//这里很关键,要预定义。
sbit RX=P1^1;
uchar code t_display[]={ //????
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black - H J K L N o P U t G Q r M y
0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46}; //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1
uchar code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; //??
uchar smg[8]={16,16,16,16,16,16,16,16};
uint distance;//注意变量类型
uchar countdis;
bit L1;
//======以下三个软件延时根据硬件误差情况选一个能用的,都不能用再试试14us,10us。==============
//======比赛直接用12us,都可以测距成功=============
//超声波测距需要占用一个定时器
void Delay12us() //@12.000MHz
{
unsigned char i;
_nop_();
_nop_();
i = 33;
while (--i);
}
void Delay13us() //@12.000MHz
{
unsigned char i;
_nop_();
_nop_();
i = 36;
while (--i);
}
void Delay11us() //@12.000MHz
{
unsigned char i;
_nop_();
_nop_();
i = 30;
while (--i);
}
void Timer0Init(void) //1??@12.000MHz
{
AUXR |= 0x80; //?????1T??
TMOD &= 0xF0; //???????
TL0 = 0x20; //???????
TH0 = 0xD1; //???????
TF0 = 0; //??TF0??
TR0 = 1; //???0????
EA=1;
ET0=1;
}
void servicet0() interrupt 1 //一套常规操作
{
static uchar dspcom=0;
//selec(0x80,~L1);
selec(0xc0,0x00);
selec(0xe0,~t_display[smg[dspcom]]);
selec(0xc0,T_COM[dspcom]);
if(++dspcom==8)
dspcom=0;
++countdis;
}
void sendwave()//发送超声波===========
{
uchar i;
for(i=0;i<16;i++)//每隔12us取一次反,发送八个周期的波形
{
TX=~TX;
Delay12us();
}
}
uint getdis()//接收波并计算距离的函数
{
uint distan=0; //不能用uchar
TH1=TL1=0;//定时器1清零
sendwave();//发送超声波
TR1=1;//开始计数
while((RX==1)&&(TF1==0));//计数溢出,或收到脉冲
TR1=0; //停止计数
if(TF1==0)//如果没有溢出
{
distan=(TH1<<8|TL1)*0.017+0.5;//0.017是根据音速等计算出来的参数。加0.5是为了补偿误差(其他地方同理)
}
else
{
TF1=0;
distan=999;
}
return distan;//注意返回值
}
void main()
{
selec(0x80,0xff);
selec(0xa0,0x00);
Timer0Init();
while(1)
{
if(countdis>199)//199ms采集一次
{
countdis=0;
distance=getdis();
if(distance<10)
L1=1;
else
L1=0;
smg[5]=distance/100;
smg[6]=distance/10%10;
smg[7]=distance%10;
}
}
}
其中,void Delay12us()等延时函数为STC-ISP中自带:
3 学长的知识点总结
知识点总结
①LED:闪烁,状态表征等。
②数码管、蜂鸣器和继电器。
③按键:独立按键和矩阵按键的短按、长按、松开触发、双击。其中矩阵按键避免与其他功能引脚的冲突(如第十届国赛题)。
④NE555频率测量。
⑤IIC:数模转换(PCF8591)、EEPROM(AT24c02)。
⑥DS18B20
⑦DS1302
⑧超声波测距
⑨PWM脉宽调制
⑩串口通信(多字节和单字节收发识别)——可靠后学习,省赛一般不考,但是无法保证不会像十三届第二次省赛一样,考以前没在省赛考过的超声波测距。