致谢订阅专栏的朋友
外设篇
1、关闭蜂鸣器、led等外设
//初始化函数,关闭蜂鸣器继电器、led灯
void sys_init()
{
P2=P2&0X1F|0XA0; //打开Y5C
P0=0X00; //关闭蜂鸣器继电器
P2=P2&0X1F; //关闭Y5C
P2=P2&0X1F|0X80; //打开Y4C
P0=0Xff; //关闭led灯
P2=P2&0X1F; //关闭Y4C
}
注意事项
蜂鸣器继电器是高电平开启,而LED灯是低电平开启
Y4C和Y5C不要混淆。一切看着原理图敲代码。不要认为自己背的住。如果写错还要二次调试。一样耽搁时间
2、中断的初始化与中断函数
//定时器0定时初始化
void Timer0Init(void) //1毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;//不可忘记
}
//定时器0计数初始化
void Counter0(void)
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD=TMOD&0XF0|0X05;
TL0=0;
TH0=0;
TF0=0;
TR0=1;
}
//定时器1初始化
void Timer1Init(void) //1毫秒@12.000MHz
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x18; //设置定时初值
TH1 = 0xFC; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET1=1;//不可忘记
}
外部中断0初始化,这里举例下降沿触发
void Int0Init()
{
EX0=1;//允许Int0中断
IT0=1;//下降沿触发,即按下瞬间触发
}
外部中断1初始化,这里举例低电平触发
void Int1Init()
{
EX1=1;//允许Int0中断
IT1=0;//低电平触发,即按下持续期间触发
}
//中断号,外部中断0是0,定时器0是1,外部中断1是2,定时器1是3。如果忘记查阅手册
//主函数要写EA=1;开启总中断
注意事项:
1、定时器频率一定要选正确。12MHZ的误差是0,并且符合题目要求。
2、定时器最好选用12T模式,一次计数表示1us,
3、ET0、ET1、EA不要忘记。
4、记得主函数要调用初始化函数。
5、中断interrupt忘记怎么敲不要着急,范例程序有。
6、外部中断和按键有冲突,注意区分。
7、定时器0的计数引脚是P34。用作NE555频率读取,要注意此时矩阵按键后两列将失效不准确。
8、定时器0用作计数模式时,不要开启ET0中断。并且读取完TH0、TL0后要重装初值。
9、中断函数里面的计数变量要注意定义的类型。如果是unsigned char,那最大值是255.如果是unsigned int,那最大值是65536。并且在使用后一定要清零。否则只能使用一次。
10、中断函数最好只用来定时、PWM输出、数码管显示、led显示。不要用来读取温度以及其他外设。
3、数码管显示
/********************** 将字符串转化为相应的段码数组 ************************/
void Conversion(u8 *buf,u8 *duanma)
{
u8 i,j=0,temp;
for(i=0;i<8;i++,j++)
{
switch(buf[j])
{
//根据要显示的字符获取共阳极数码管编码
case '0': temp = 0xc0; break;
case '1': temp = 0xf9; break;
case '2': temp = 0xa4; break;
case '3': temp = 0xb0; break;
case '4': temp = 0x99; break;
case '5': temp = 0x92; break;
case '6': temp = 0x82; break;
case '7': temp = 0xf8; break;
case '8': temp = 0x80; break;
case '9': temp = 0x90; break;
case 'A': temp = 0x88; break;
case 'B': temp = 0x83; break;
case 'C': temp = 0xc6; break;
case 'D': temp = 0xA1; break;
case 'E': temp = 0x86; break;
case 'F': temp = 0x8E; break;
case 'H': temp = 0x89; break;
case 'L': temp = 0xC7; break;
case 'N': temp = 0xC8; break;
case 'P': temp = 0x8c; break;
case 'U': temp = 0xC1; break;
case '-': temp = 0xbf; break;
case ' ': temp = 0xff; break;
default: temp = 0xff;
}
if(buf[j+1]=='.')//如果数组的下一位是小数点,就将它与上一位合并
{
temp = temp&0x7f;
j++;
}
duanma[i] = temp;
}
}
/********************** 一位显示函数 ************************/
void display(u8 *duanma,u8 pos)
{
//消隐
P0=0Xff; //段选全灭
P2=P2&0X1F|0XE0; //打开Y7C
P2=P2&0X1F; //关闭Y7C
//送位选
P0=0X01<<pos; //选择位置
P2=P2&0X1F|0XC0; //打开Y7C
P2=P2&0X1F; //关闭Y7C
//送段码
P0=duanma[pos]; //段选全灭
P2=P2&0X1F|0XE0; //打开Y7C
P2=P2&0X1F; //关闭Y7C
}
注意事项
1、buf数组是存放待显示字符串的,里面不是段码值。最好定义为10位,防止不能完全装载字符串,比如数组里面有两个小数点。
2、duanma数组是存放待显示字符串的段码的,因为数码管就只有8位,所以定位位8位就可以了。
3、在获得字符串的时候,sprintf函数是在stdio.h库中,因此要记得#include <stdio.h>,
%8bu,表示待显示变量是unsigned char类型的,宽度八位,不足八位补空格。%08bu表示不足八位补0。
%8d表示待显示变量是int类型。宽度为8位,不足8位补空格。%08d表示不足八位补0。
%8.2f表示待显示变量是float类型。宽度为8位,保留两位小数。不足8位补空格。%08.2f表示不足八位补0。
4、将字符串转化为段码数组时,记得i,j,temp;三个变量的区别。i是用来for循环八次,并将temp值装进duanma数组。j是用来switch挨个判断当前位的段码值是多少,以及下一位j+1是不是小数点。如果是temp还需要&0x7f。并且j++;
5、一位字符显示这个函数,要记得段码消隐。并且P0要写在P2之前。也就是说。在你允许P0数据更新之前,就要准备好P0的值。否则会导致数码管亮度不均匀。
4、按键函数
//独立按键
unsigned char button(void)
{
static unsigned char value,count=0;
unsigned char key_press;
static bit flag;
key_press=P3&0X0F;
if( key_press!=0X0F){count++;}else count=0;
if(count==2)
{
flag=1;
count=0;
if(P30==0)value=7;
if(P31==0)value=6;
if(P32==0)value=5;
if(P33==0)value=4;
}
if(key_press==0X0F&&flag==1)
{
flag=0;
return value;
}
return 0;
}
//矩阵按键
unsigned char readkbd()//矩阵按键读取函数
{
static unsigned char state=0;//state=0,进入第一次扫描;state=1;进入第二次扫描
static unsigned char value;//按键返回值,直到按键释放后返回该值
static unsigned char hang;//第一次获得哪一行?
switch(state)
{
//进入第一次检测,检测哪一行被按下
case 0:
P3=0X0F;P42=0;P44=0;//按键的行值置1,列值置0;
if(P3!=0X0F)//如果按键值改变了,就判断是哪一行
{
if(P30==0){hang=1;state=1;}//第一行
if(P31==0){hang=2;state=1;}//第二行
if(P32==0){hang=3;state=1;}//第三行
if(P33==0){hang=4;state=1;}//第四行
}else state=0;//无按键按下,停在第一次检测
break;
//第二次检测,检测哪一列被按下
case 1:
P3 = 0XF0;P42=1;P44=1;//列值置1,行值置0
if(P44==0)value=(4-hang)+4;//第一列四行的值,7 6 5 4
if(P42==0)value=(8-hang)+4;//第二列四行的值,8 9 10 11
if(P35==0)value=(12-hang)+4;//第三列四行的值,12 13 14 15
if(P34==0)value=(16-hang)+4;//第四列四行的值,16 17 18 19
break;
}
//等待按键释放
P3=0X0F;P42=0;P44=0;
if(state==1&&P3==0X0F)
{
state =0;
return value;
}
return 0;
}
独立按键注意事项
1、独立按键中返回值、计数值、非抖动标志是static的,而按键状态key_press不是static的。
2、判断是否按下时,如果按下,count要加一,如果没有按下,count要清零。当count=2,要将非抖动标志置1表示人为按下了。并且count要清零,以便下一次判断。
3、返回按键值的前提是按键松开。也就是key_press=0x0f并且非抖动标志flag=1。返回按键值后,flag要清零。
4、如果题目要求按住某个独立按键时间长一秒才能响应该按键,我们可以if(count==100)或者1000ms运行一次独立按键函数。
5、如果题目要求按住某个独立按键才处理某函数,否则停止处理。那我们可以单独处理该按键。比如S4按下执行温度读取函数,如下是具体思路
sbit S4=P3^3;
if(key_flag)//key_flag在定时器中断中控制。每10ms执行一次
{
key_flag=0;
if(S4==0){temp_flag=1;}//每10ms判断一次S4的值,如果处于低电平状态,temp_flag=1;
else temp_flag=0;
}
//当temp_flag=1;执行温度读取函数
if(temp_flag){readtemp();}
矩阵按键注意事项
1、变量全部是是static。一共三个,按键返回值value,按键当前状态state,行值hang。
2、state=0,表示第一次监测,如果监测到按键按下。那么state要等于1,表示进入第二次监测。同时要读取hang值。如果没有监测到按键按下,那么要停在当前状态。直到有键按下。
3、state=1,表示第二次监测,如果监测到按键按下,那么要根据hang值和当前列值得到value。
4、等待按键释放, P3=0X0F;P42=0;P44=0;一定要用这句话,通过判断4行按键有没有变化,来确定是否松开按键。只有松开按键才返回value,并且state状态要清零,方便下一次监测。
共同注意事项
1、中断函数每隔10ms将key_flag置1,主函数检测到key_flag=1后开始执行按键读取函数,并且进行相应的按键处理。同时key_flag清零;
5、DS1302读取&写入函数
/****底层驱动函数略过****/
//读取函数
void ReadTime(unsigned char *GetTime)//读取时钟值,存入数组GetTime中,时:分:秒形式
{
unsigned char temp;//BCD码转换为十进制
temp = Read_Ds1302_Byte(0x85);//读出小时值
GetTime[0] = (temp>>4)*10+(temp&0xf);
temp = Read_Ds1302_Byte(0x83);//读出分钟值
GetTime[1] = (temp>>4)*10+(temp&0xf);
temp = Read_Ds1302_Byte(0x81);//读出秒值
GetTime[2] = (temp>>4)*10+(temp&0xf);
}
//写入函数
void SetTime(unsigned char *WriteTime)//{00:00:00}
{
unsigned char temp;
Write_Ds1302_Byte(0x8E,0X00);//允许写操作
temp = ((WriteTime[0]/10)<<4)+WriteTime[0]%10;//写入时
Write_Ds1302_Byte(0x84,temp);
temp = ((WriteTime[1]/10)<<4)+WriteTime[1]%10;//写入分
Write_Ds1302_Byte(0x82,temp);
temp = ((WriteTime[2]/10)<<4)+WriteTime[2]%10;//写入秒
Write_Ds1302_Byte(0x80,temp);
Write_Ds1302_Byte(0x8E,0X80);//禁止写操作
}
注意事项
1、读取函数,读出来的值是BCD码,要经过转化才能得到十进制的数,
2、写入函数,写进去的数也要是BCD码,
3、控制寄存器(8EH)的BIT7,该位是写入的控制位,WP=0,允许写入;WP=1,禁止写入
4、地址(81H)的BIT7是CH,该位是时钟暂停标志,CH=0,计时结束;CH=1,计时开始。
5、用一个数组来保存这些值,是比较方便的,读取和写入都用一个数组也可以的,
6、命令地址这些不要背,看芯片手册写才是最正确的
6、AT24C04读取&写入函数
/*************底层驱动略***************/
/*************写入函数***************/
void writeRom(unsigned char add,unsigned char dat)
{
IIC_Start();//启动总线
IIC_SendByte(0xA0);//向器件发送写命令+器件地址
IIC_WaitAck();//向单片机发送应答信号
IIC_SendByte(add);//向器件发送存储首地址
IIC_WaitAck();//向单片机发送应答信号
IIC_SendByte(dat);//向器件发送存储数据,这里可以是多个字节,存储地址依次延后
IIC_WaitAck();//向单片机发送应答信号
IIC_Stop();//结束总线
Delay2ms();//短暂延时,写入数据
}
/*************读取函数***************/
unsigned char readRom(unsigned char add)
{ unsigned char dat;
IIC_Start();//启动总线
IIC_SendByte(SlaveAddrW);//向器件发送写命令+器件地址
IIC_WaitAck();//向单片机发送应答信号
IIC_SendByte(add);//存储地址
IIC_WaitAck();//向单片机发送应答信号
IIC_Start();//重启总线,输入命令
IIC_SendByte(SlaveAddrR);//向器件发送读命令+器件地址
IIC_WaitAck();//向单片机发送应答信号
dat=IIC_RecByte();//接收数据
IIC_SendAck(1); //发送非应答信号,停止接收
IIC_Stop();//停止总线
return dat;
}
注意事项
1、启动总线后,第一件事绝对是发送器件地址以及读/写命令。
2、主机向从机发送一个字节后,一定要等待从机发送应答信号。然后才能继续发送或者是其他操作
3、从机向主机发送完数据后,主机一定要发送一个非应答信号,表示接收结束了
4、写入操作结束后,最好短暂延时2ms,别直接用iic的延时函数
5、如果要重新发送读取写入命令,一定要重启总线。
6、要记得读取函数是有返回值的,不要忘记return dat
7、PCF8591读取&写入函数
/**********底层驱动略*********/
/**********读取函数*********/
unsigned char read_rb(unsigned char channel)//选择通道,取值0、1、2、3
{
unsigned char dat;
IIC_Start();
IIC_SendByte(0x90); //写
IIC_WaitAck();
IIC_SendByte(channel);
IIC_WaitAck();
IIC_Stop();
IIC_Start();
IIC_SendByte(0x91); //读
IIC_WaitAck();
dat = IIC_RecByte();
IIC_SendAck(1);
IIC_Stop();
return dat;
}
/**********写入函数*********/
void write_DAC(unsigned char x)
{
IIC_Start();
IIC_SendByte(0x90); //写
IIC_WaitAck();
IIC_SendByte(0x43); //允许输出
IIC_WaitAck();
IIC_SendByte(x);
IIC_WaitAck();
IIC_Stop();
}
注意事项:
1、PCF8591和AD24C02都是同一总线上的设备,因此注意事项都是一样的,
2、使用DAC功能时,要发送给0x43这条命令,否则不能启用DAC功能
3、读取函数的返回值时0到255,要转化为0到5v电压,要/51.0,注意这里的保存变量应该设为float类型
4、写入函数的入口参数时0到255,在写入之前,最好先看看写入0的时候,输出电压是多少,通过跳线帽短接out和AIN0就能通过读取通道0来获知输出电压了。
5、写入函数,只需要开启一次总线,并且需要持续写入
8、DS18B20读取函数
/***********该驱动延时快了12倍,要调整*********/
//单总线延时函数
void Delay_OneWire(unsigned int t) //STC89C52RC
{
unsigned char i;
while(t--)
{
for(i=0;i<12;i++);//这里是添加的代码
}
}
/******其他驱动略*****/
//DS18B20温度采集程序:整数
u8 Read_Temp_d(void)
{
u8 low,high;
char temp; //这是一个有符号的数
init_ds18b20();
Write_DS18B20(0xCC);
Write_DS18B20(0x44); //启动温度转换
Delay_OneWire(200);
init_ds18b20();
Write_DS18B20(0xCC);
Write_DS18B20(0xBE); //读取寄存器
low = Read_DS18B20(); //低字节
high = Read_DS18B20(); //高字节
temp = high<<4; //只取了整数部分和符号位
temp |= (low>>4);
return temp;
}
//温度采集,含小数
float ReadTemp_Float(void)
{
u16 temp;
float temperature;
u8 LSB,HSB;
init_ds18b20();
//写入温度转换指令
Write_DS18B20(0xcc);//跳过ROM
Write_DS18B20(0x44);//开始温度转换
Delay_OneWire(200);
init_ds18b20();
Write_DS18B20(0xcc);//跳过ROM
Write_DS18B20(0xBE);//读取寄存器
LSB = Read_DS18B20();
HSB = Read_DS18B20();
temp = ((HSB&0X0F)<<8)|LSB;
temperature = temp*0.0625;
return temperature;
}
注意事项
1、 对返回数据的高八位低八位进行不同的数据处理,可以得到12位分辨率的浮点型和整型数据。
2、整型数据,只取高八位的低四位,低八位的高四位。
3、浮点型数据,一定要注意整合高八位和低八位时,要用unsigned int这种16位的数据类型来保存。否则将出现数据丢失,整合后的数据还需要乘以0.0625.
4、0xcc是跳过rom,如果不写,读取数据将会很慢。
5、0x44是温度转换命令。如果不写,将一直显示温度85.
6、保险起见,发送完温度转化命令后,适当延时200到800ms。
7、发送命令前,记得init_ds18b20();
8、官方提供的延时是有问题的,要扩大12倍。for(i=0;i<12;i++);或者说t=t*12:
9、如果整合数据想要一句话完成,那一定要注意运算符的优先级
9、串口通信
//初始化函数
void Uart1Init(void) //串口1,9600bps@12.000MHz
{
SCON = 0x50; //8位数据,可变波特率
AUXR |= 0x40; //定时器1时钟为Fosc,即1T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设定定时器1为16位自动重装方式
TL1 = 0xC7; //设定定时初值
TH1 = 0xFE; //设定定时初值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
TI = 1;//未发送完成
ES=1; //这里是自己加的
EA=1; //这里是自己加的
}
//串口发送字符
void uart_send(unsigned char ch)
{
ES=0; //关串口中断,
SBUF=ch;
while(TI==0); //等待发送完成
TI=0; //清除中断标记
ES=1; //开中断
}
//串口1字符串发送
void Uart1_Sendstring(unsigned char *Str)
{
ES=0; //关串口中断
while(*Str != '\0')
{
SBUF = *Str;
while(TI == 0);
TI = 0;
Str++;
}
ES=1; //开串口中断
}
//串口接收 ( 通过中断 )
void uart1receive() interrupt 4
{
if (RI==1)
{ RI=0;
buf=SBUF;
}
}
注意事项
1、串口初始化波特率一定要注意频率要设为11.0592,同时IRC的频率也要设置11.0592mhz,否者收发数据将是乱码
2、串口发送最好采用查询法发送,发送数据前关闭ES,发送结束后要打开ES。TI =1,表示一帧数据发送结束。
3、字符串的发送要注意数据帧格式,要有一个结束标记比如\r\n或者’\0’这一类的。
4、中断接收,要注意RI=1,表示一帧数据接收完毕,并申请中断,要求CPU从接收SBUF取走数据。
总体设计篇
大体思路我分为四个板块,四个板块环环相扣。以第十一届为例。
板块1——按键处理模块
按键处理通常以界面为中心,一般不同界面screen下,按键会有不同的处理方式。在不同的界面下。有些按键是无效的,那我们不处理该案件的返回值即可。
//按键处理
if(key_flag)
{
key_flag=0;
key=readkey();
if(key!=0)
{
switch(screen)
{
//默认界面,数据界面
case 0:
switch(key)
{
case 12 :if(++screen==3)screen=0;key_count=0;break;
default:key_count++;break;//key_count无效按键
}break;
//参数界面
case 1:
switch(key)
{
case 12 :if(++screen==3)screen=0;key_count=0;break;
case 16 :Vp=Vp+0.5;if(Vp==5.5)Vp=0;key_count=0;break;
case 17 :Vp=Vp-0.5;if(Vp==-0.5)Vp=5.0;key_count=0;break;
default:key_count++;break;
}break;
//计数界面
case 2:
wirteROM(0,Vp*10);
Vp=readROM(0)/10.0;
switch(key)
{
case 12 :if(++screen==3)screen=0;key_count=0;break;
case 13 :count=0;key_count=0;break;
default:key_count++;break;
}break;
}
if(key_count>=3)
{
L3=0xfb;
}
else L3=0xff;
}
}
板块2——显示处理
显示界面的依据是screen,不同的值对应不同的界面。
显示的数据就要涉及到数据处理,也就是板块3
//显示处理
if(dis_flag)
{
dis_flag=0;
switch(screen)
{
case 0 :sprintf(buf,"U %3.2f",Vain3);
break;
case 1 :sprintf(buf,"P %3.2f",Vp);
break;
case 2 :sprintf(buf,"N %5d",count);
break;
}
}
conversion(&duanma,&buf);
板块3——数据处理
一般就处理几个外设的读取写入函数,根据这些数据来控制不同的LED灯亮灭。
//数据更新模块 100ms
if(AD_flag)
{
AD_flag=0;
Vain3=readADC(3)/51.0;//读取电压值
if(Vain3>Vp){flag=1;L1_count++;}//如果处于高于参数状态,flag置1,并开始计时
if(L1_count>=50){L1=0XFE;} //超过5s,点亮L1
else L1=0xff; //否则熄灭
if(Vain3<Vp&&flag==1) //如果低于于参数,并上一次是高于参数的
{
count++;//计数值加一
flag=0;//清除标记
L1_count=0;
}
if(count%2==0)
{
L2=0XFF;
}else L2=0xfd;
}
板块四——中断函数
中断函数是整个系统运行的核心。它通过定时来实现上面三个模块的定时执行。控制思想是标志法。通过控制标志量来决定是否运行相关函数。
整体思路:
先写按键模块(记得检查按键函数是否正确),然后写显示界面。通过按键来控制显示界面的切换。确定没有问题后,开始进行数据处理。逐渐填充显示界面的数据。中断函数根据主函数需要进行相关编写。
第十三届模拟题(第十一届国赛题)
这是今天刚做的,,大概三个小时,还没检查调试,先发出来看看,变量名随便取的,所以可能看不懂是啥意思.
没调试,最近太忙了,都没时间管蓝桥杯了
链接:https://pan.baidu.com/s/1j6Ljc_q-Njg-9bZvzHe8Ag?pwd=066h
提取码:066h


本文是蓝桥杯备赛的总结,重点讲解了外设如蜂鸣器、LED、数码管、DS1302、AT24C04、PCF8591和DS18B20的使用,以及中断初始化和按键处理。提供了详细的注意事项和函数使用技巧,包括中断函数的设计原则和数码管显示的方法。最后提到了总设计篇的四大板块:按键处理、显示处理、数据处理和中断函数,强调了中断函数在系统中的核心地位。

8911

被折叠的 条评论
为什么被折叠?



