蓝桥杯单片机模块代码综合(考前快速复习)
今天做的是蓝桥杯所有模块的综合,只展示模块的核心代码,所以只能作为复习资料,而不是学习资料。这次给原理图全在官方给的资料里,考试记不起来可以在文件夹里找到!
(一)LED
核心代码示例:
void lighten(u8 led)
{
P0=~led;
P2=P2&0X1F|0X8F;
P2=P2&0X1F;
}
原理图:
回忆一下,译码器选择Y4使得其为低电平,J13中WR与地连接,所以或非门后Y4C为1,锁存器才能打开,P0的信息才能给右侧。二极管左侧为0才能亮,所以书写代码时我们常用~取反,使得P0取1为亮,取0为暗,符合人们的思维习惯。
(二)外中断
核心代码示例:
void open()
{
EA=1;
EX1=1;
EX0=1;
IT1=1;
IT0=1;
}
void stop1() interrupt 0
{
P0=~1;
P2=P2&0X1F|0X80;
P2=P2&0X1F;
}
void stop2() interrupt 2
{
P0=~0;
P2=P2&0X1F|0X80;
P2=P2&0X1F;
}
原理图:
打开中断→使用中断就可以,注意中断与中断,中断与主函数之间不能同时使用相同的引脚和期间,后果不可估计,如LED与数码管。
(三)定时器
核心代码示例:
void Timer0Init(void) //50毫秒@11.0592MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x00; //设置定时初值
TH0 = 0x4C; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
}
STC-ISP直接提供代码。中断号码在上一个模块的原理图中。
(四)数码管
核心代码示例:
void translate(u8 org[],u8 tran[])
{
u8 k,j,mid;
for(j=0,k=0;j<8;j++,k++)
{
switch(org[k])
{
case '0': mid = 0xc0; break;
case '1': mid = 0xf9; break;
case '2': mid = 0xa4; break;
case '3': mid = 0xb0; break;
case '4': mid = 0x99; break;
case '5': mid = 0x92; break;
case '6': mid = 0x82; break;
case '7': mid = 0xf8; break;
case '8': mid = 0x80; break;
case '9': mid = 0x90; break;
case '-': mid = 0xbf; break;
default: mid = 0xff;
}
if(org[k+1]=='.')
{
mid=mid&0x7f;
k++;
}
tran[j]=mid;
}
}
void display(u8 tran[],u8 wei)
{
P0=0XFF;
P2=P2&0X1F|0XE0;
P2&=0X1F;
P0=1<<wei;
P2=P2&0X1F|0XC0;
P2=P2&0X1F;
P0=tran[wei];
P2=P2&0X1F|0XE0;
P2=P2&0X1F;
}
void stop0() interrupt 1
{
count++;
if(count%1000==0)
{
i++;
count=0;
}
display(tran,wei);
if(++wei==8) wei=0;
}
选择对应锁存器(一)就说过了吧,不重复了。我们输入的内容在org数组中,translate函数将其翻译为共阳数码管的对应内容,再通过display函数显示,定时器快速切换,使得肉眼认为其一直亮。注:如果位选段选部分互换,可能消影不理想。
(五)矩阵键盘
核心代码展示:
u8 search()
{
u16 key;
u8 key_return;
P44=0;P42=1;P35=1;P34=1;
key=key|(P3&0X0F);
P44=1;P42=0;P35=1;P34=1;
key=(key<<4)|(P3&0X0F);
P44=1;P42=1;P35=0;P34=1;
key=(key<<4)|(P3&0X0F);
P44=1;P42=1;P35=1;P34=0;
key=(key<<4)|(P3&0X0F);
switch(~key)
{
case 0x8000: key_return = 4; break; // S4
case 0x4000: key_return = 5; break; // S5
case 0x2000: key_return = 6; break; // S6
case 0x1000: key_return = 7; break; // S7
case 0x0800: key_return = 8; break; // S8
case 0x0400: key_return = 9; break; // S9
case 0x0200: key_return = 10; break; // S10
case 0x0100: key_return = 11; break; // S11
case 0x0080: key_return = 12; break; // S12
case 0x0040: key_return = 13; break; // S13
case 0x0020: key_return = 14; break; // S14
case 0x0010: key_return = 15; break; // S15
case 0x0008: key_return = 16; break; // S16
case 0x0004: key_return = 17; break; // S17
case 0x0002: key_return = 18; break; // S18
case 0x0001: key_return = 19; break; // S19
default: key_return = 0;
}
return key_return;
}
void key_translate()
{
new=search();
if(new!=old&&new!=0)
{
if(new>=14)
org[7]=new-14+'A';
else org[7]=new-4+'0';
}
old=new;
}
外中断时,我们的J5的23相连,此时是12。其确定按键的形式为P34,P35,P42,P44,分别单独置0,确定列。再通过P30,P31,P32,P33确定行,从而锁定按键。记住记录按件情况的key是十六位的。
后一个函数是根据扫描的结果执行想要的结果,通过OLD,NEW参数让一次按下只识别一次,当然如需按下切换界面,松手消失之类,需要将功能写在 if(new!=old&&new!=0)外,这句话应该看不懂,除非你做过相应省赛。
(六)时钟芯片
核心代码示例:
void read()
{
u8 mid;
mid=Read_Ds1302_Byte(0x85);
time_now[0]=(mid>>4)*10+(mid&0x0f);
mid=Read_Ds1302_Byte(0x83);
time_now[1]=(mid>>4)*10+(mid&0x0f);
mid=Read_Ds1302_Byte(0x81);
time_now[2]=(mid>>4)*10+(mid&0x0f);
}
void stop0() interrupt 0
{
u8 mid;
Write_Ds1302_Byte(0x8e,0);
mid=((time_set[0]/10)<<4)+time_set[0]%10;
Write_Ds1302_Byte(0x84,mid);
mid=((time_set[1]/10)<<4)+time_set[1]%10;
Write_Ds1302_Byte(0x82,mid);
mid=((time_set[2]/10)<<4)+time_set[2]%10;
Write_Ds1302_Byte(0x80,mid);
Write_Ds1302_Byte(0x8e,0x80);
}
最核心的图就是这个,底层代码解释就看我以前的文章吧。其存储方式是将十位和各位分开来,所以需要转化。读取和存储看最后一位(传输时时第一位,单线嘛) ,地址图最左侧两列。写时需要打开写保护,写完后记得关闭。
(七)温度模块
核心代码示例:
void rd_temperature(unsigned char *zhen1,unsigned char *zhen2)
{
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);
Delay_OneWire(10);
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0xbe);
*zhen1=Read_DS18B20();
*zhen2=Read_DS18B20();
}
底层代码修改见前面的文章。
我们使用的只有0XCC,0XBE,0X44。初始化→跳过ROM→转化/读温度。
读温度遵循以下规则:
先读LS BYTE,后MS BYTE。主函数中 zhen1=&low;zhen2=&high; rd_temperature(zhen1,zhen2); temp=low+(high*256);,实际温度还需除以16,因为LS BYTE最低位为0.125。
(八)模数转换:
核心代码示例:
u8 adc(u8 adress)
{
u8 mid;
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(adress);
IIC_WaitAck();
IIC_Start();
IIC_SendByte(0x91);
IIC_WaitAck();
mid=IIC_RecByte();
IIC_SendAck(1);
IIC_Stop();
return mid;
}
初始化后第一步,本单片机中A2A1A0已经为0,写就第0位为0,读时1。
第二步一般是0X43或0X41,可以看之前文章理解。
本模块用IIC为底层,sendack函数1是代表接受结束,0代表还想继续接受。stop函数也不可以少。
(九)掉电存储数据
核心代码展示:
u8 atr()
{
u8 result;
IIC_Start();
IIC_SendByte(0xa0);
IIC_WaitAck();
IIC_SendByte(0);
IIC_WaitAck();
IIC_Start();
IIC_SendByte(0xa1);
IIC_WaitAck();
result=IIC_RecByte();
IIC_SendAck(1);
IIC_Stop();
return result;
}
读和写在这里很清楚,按照顺序来就好,与上同,多个字节写法可以见我以前写的文章。
(10)串口
核心代码展示:
void open()
{
EA=1;
ET0=1;
ES=1;
}
void UartInit(void) //9600bps@11.0592MHz
{
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x40; //定时器1时钟为Fosc,即1T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设定定时器1为16位自动重装方式
TL1 = 0xE0; //设定定时初值
TH1 = 0xFE; //设定定时初值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
void out(u8 *a)
{
while(*a!='\0')
{
SBUF=*a;
while(TI==0);
TI=0;
a++;
}
}
void chuankou() interrupt 4
{
if(RI)
{
org[i++]=SBUF;
RI=0;
}
if(i==8) i=0;
}
串口也是中断的一种,波特率的计算在STC-ISP中给出,从现在开始,为了避免BUG,各定时器不要重复出现。接收时RI会变成1,接受结束要清零。将要发出的信息写入SBUF,发送完成后TI变成1,发送结束要清零,用while清零。SBUF发送和接受在物理结构上独立,但使用时地址相同。
(11)超声波(前面文章没写,这里多啰嗦一点)
核心代码展示:
u16 flag;
float a;
sbit TX=P1^0;
sbit RX=P1^1;
void Timer1Init(void) //12.5微秒@12.000MHz
{
AUXR |= 0x40; //定时器时钟1T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x6A; //设置定时初值
TH1 = 0xFF; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 0; //定时器1关闭计时
}
void me_dis()
{
u8 num=10;
flag=0; /*清空溢出标志*/
TX=0;
TL1=0X6A;
TH1=0xFF;
TR1=1; /*设置定时器初值,每次溢出均为12.5毫秒*/
while(num--)
{
while(!TF1);/*等待溢出*/
TX=TX^1;/*高低电平翻转*/
TF1=0;/*消除溢出位*/
} /*发送五个周期的超声波*/
TR1=0; /*暂停计时*/
TL1=0;
TH1=0; /*设置初值,方便计算的同时也将可测量距离达到最大*/
TR1=1; /*开始计时*/
while(RX&&!TF1); /*未接受到信号时RX=1,未溢出时TF1=0,两者其一发生变化则跳出while*/
TR1=0; /*暂停计时*/
if(TF1) /*判断是否溢出*/
{
TF1=0;
flag=1;/*溢出标志*/
}
}
void main()
{
close();
Timer0Init();
open();
Timer1Init();
while(1)
{
if(count>1000)
{
count=0;
me_dis();
a=((TH1*256+TL1)*0.017/12);/* 0.017= 10^-6*340/2*10^2,单位是厘米*/
if(flag==0)
sprintf(org," %02d.%03d",(u16)a,((a-(u16)a)*1000));/*在keil中无论如何都读取两个字节,而char为1字节,float为4字节,读取时可能出现错误,用上方的方式读取可以避免错误的发生*/
else
sprintf(org,"%04d%04d",9999,9999);/*超过测量范围显示99999999,但因int最大为65535,所以用两个9999显示*/
}
translate(org,tran);
}
}
首先我们需要满足超声波40kHZ的需求,并且他要求有50%的占空比。对应40kHZ的周期为1/400000,依据占空比为百分之五十,在一个周期之内有一半的时间发送的信号为高电平,有一半的时间发送的信号为低电平,经过单位的换算,我们知道对应的每一次高低电平的时间为12.5微秒,并且要求高低电平来回交换。要精确的达到12.5微秒,光靠延时函数是很难做到的,在单片机内我们可以用定时器来满足较为精准的时间需求。定时器的设置就不讲了,不懂得话看我之前的文章或者STC15用户手册。
之后就是计算距离的长度,我们知道声音的速度大约为340m/s,假如我们要在数码管上显示的单位为厘米,那么本处换算后就是34000cm/s。距离与速度的关系是x=vt,下面计算物体与单片机的距离,唯一所缺的就是时间。依据上一段所说,在单片机内时间测量较为精准的就是定时器,所以本处我们也可以用定时器来测量声音传播的时间。
如何计算时间呢?(在下面的程序中我们使用的是定时器1,本处我们就拿定时器1举例)我们可以观察TH1和TL1的变化,如果(TH1<<8)+TL1所构成的16位二进制的数字加1,代表时间经过了1/定时器频率(如果采用12T模式,还需要将定时器的频率除以12),之前提到定时器的读和写的地址是相同的,所以我们只需要将接受到信号时的(TH1<<8)+TL1减去发送信号时的(TH1<<8)+TL1,再将这个数据乘以每次加1的时间我们就可以得到公式中所需要的t。
最后我们还要注意超声波的发送包括去和回两个过程,所以我们最后算出的距离是去和回的两次路径的总和,所以我们需要将最终的结果除以2。
(十二) NE555
核心代码展示:
void timer_set()
{
TL0=0XFf-15;
TH0=0XFF;/*定时器0每接受到16次脉冲计数一次*/
TL1=0xCD;
TH1=0xD4;/*定时器1在16位自动重装载下计时1ms*/
AUXR|=0x40;/*设置定时器1为1T模式*/
TMOD|=0x04;/*设置定时器0为计数模式*/
ET0=1;
ET1=1;
EA=1;/*打开中断*/
TR1=1;
TR0=1;/*两个计时器开始工作*/
}
之后适当修改主函数并设置两次中段的内容:
void main()
{
close();
timer_set();
while(1)
{
sprintf(org,"%08d",(int)frequency*16);
translate(org,tran);
}
}
void time_0() interrupt 1/*没中断一次代表计数了16次*/
{
number++;
}
void time_1() interrupt 3/*每1s输出一次实际频率的十六分之一*/
{
count++;
if(count==1000)
{
frequency=number;
number=0;
count=0;
}
display(tran,x);
if(++x==8) x=0;
}
NE555是一个信号发生电路,有三个5kΩ的电阻分压,故称555定时器。NE555是一个纯硬件的设计,所以它没有可以编程的部分,唯独可以调节的是图中Rb3的阻值,一旦RB3的阻值确定,那么它的功能也就此确定。NE555没有可以编程的部分。
在我们使用的单面机上,P34与NET SIG相靠近,而定时器0的计数模式可以对P34外部的脉冲进行计数,所以我们将P34和NET SIG相联,用定时器0的计数模式,对NE555发出的信号进行计数,通过这种方式来计算它的频率。
好了,所有模块结束,祝比赛顺利!