本文是作者的赛后总结
目录
各个模块
1.138译码器
通过P25,P26,P27来命中其他模块
//入口参数:要命中串口几
//函数名称:138选择函数
//函数作用:通过入口参数使138选择对应锁存器
//返回值:无
void Choice_138(unsigned char num)
{
num=(num<<5);
P2=P2&0x1f|num;
P2=P2&0X1F;
}
2.关闭外设
关闭外设主要是通过U9进行操作,
值得注意的是ULN2003中内置一个反向器
此外,竞赛板上的蜂鸣器是有源蜂鸣器.输入低电平有效
继电器是当N Relay为低电平时,电磁铁会对衔铁产生一个吸引力使得衔铁与m2接触,这个时候继电器就打开了,并且由于衔铁上接了GND,所以L10这个灯就会被点亮。
为了关闭所有外设,我们需要使P0=0X00;
(对继电器单独操作是P04
对蜂鸣器的单独操作是P06)
现在头文件SysInit.h中定义
#include <STC15F2K60S2.H>
#include <bsp_138.H> //使其可以使用我们写的Choice_138()
在c文件SysInit.c中添加
#include <SysInit.h>
void SysInit()
{
P0=0x00;
Choice_138(5);
}
3.Led
锁存器Y4中发光二极管是高电平熄灭,低电平点亮,不符合我们日常操作习惯,故将数据先进行反向处理
在头文件bsp_Led.h中添加
#include <STC15F2K60S2.H>
#include <bsp_138.H>
void Led_Disp(unsigned char ucLed);
在c文件bsp_Led.c中添加
//函数名称:Led点亮函数
//入口参数:要点亮的对应Led的Hex码
//函数作用:点亮对应二进制的Led
//返回值:无
#include <bsp_Led.H>
void Led_Disp(unsigned char ucLed)
{
ucLed = ~ucLed;
P0 =ucLed;
Choice_138(4);
}
4.数码管
数码管的点亮是通过锁存器Y6实现段选,通过锁存器Y7实现位选
竞赛板的数码管是共阳极数码管,低电平点亮
在头文件bsp_Seg.h中添加
#include <STC15F2K60S2.H>
#include <bsp_138.H>
void Seg_Tran(unsigned char *seg_string, unsigned char *seg_buf);
void Seg_Disp(unsigned char *pucSeg_Code,unsigned char ucSeg_Pos);
在C文件bsp_Seg.c中添加
#include <bsp_seg.H>
void Seg_Tran(unsigned char *seg_string, unsigned char *seg_buf)
{
unsigned char i=0;//buf[i],i=0~7
unsigned char j=0;//seg_string[j],j=0~10
unsigned char temp;//字符串转换为段码的中间存储变量
for(i=0;i<=8;i++,j++) //如果没有小数点,就写i<8
{
switch(seg_string[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 'n': temp = 0xC8; break;
case '-': temp = 0xbf; break;
case ' ': temp = 0xff; break;
default : temp = 0xff; break;
}
if(seg_string[j+1] == '.')//如果字符串里边出现了‘.’,要把刚刚出炉的temp值改变。
{
temp &= 0x7f;//把dp位点亮
j++;//跳过带'.'的位置
}
seg_buf[i] = temp;//将转换后的段码值传递给Buf存储。
}
}
void Seg_Disp(unsigned char *pucSeg_Code,unsigned char ucSeg_Pos)
{
P0 = 0xFF;
Choice_138(7);
P0=1<<ucSeg_Pos;
Choice_138(6); // 0xC0,选通Y6,也就是位码的锁存器,将数据透传过去
P0 = pucSeg_Code[ucSeg_Pos];//段码送入
Choice_138(7); // 0xE0,选通Y7,也就是段码的锁存器,将数据透传过去
}
在主函数中我们要给字符串pucSeg_Buf赋值要用到sprintf()函数,该函数包括在stdio.h函数库中
在man.c中添加代码
void Seg_Proc()
{
if(Seg_Slow_Down) return;
Seg_Slow_Down=1;
sprintf(pucSeg_Buf,"11111111");
Seg_Tran(pucSeg_Buf,pucSeg_Code);
}
void Timer_1() interrupt 3
{
if(++Seg_Slow_Down==500) Seg_Slow_Down=0;//定时500ms点亮一次数码管
Seg_Disp(pucSeg_Code,ucPos);
if(++ucPos==8) ucPos=0;
}
void main()
{
while(1)
{
Seg_Proc();
}
}
5.键盘
竞赛板中的键盘支持2种模式,独立键盘和矩阵键盘两种。通过跳线帽J5来实现
独立键盘只有S4到S7四个按键,只需要分别检测四个引脚电平就可以读出
unsigned char Key_Read_BTN(void)
{
unsigned char Key_Value;
if(P30 == 0) Key_Value = 7;
else if(P31 == 0) Key_Value = 6;
else if(P32 == 0) Key_Value = 5;
else if(P33 == 0) Key_Value = 4;
else Key_Value = 0;
return Key_Value;
}
矩阵键盘则稍微复杂一点,先列进行定位,随后再对行进行定位
unsigned char Key_Read(void)
{
unsigned int Key_New;//16位的数值,用于存放P3直接读取的第四位键值
unsigned char Key_Value;//返回值
P44 = 0; P42 = 1; P35 = 1; P34 = 1; // 第一列扫描
Key_New = P3 & 0X0F; //P3 = 0000 1000 ---P37~P30
P44 = 1; P42 = 0; P35 = 1; P34 = 1; // 第二列扫描
Key_New = (Key_New << 4) | (P3 & 0X0F); //将原来的数值挪到次4位,本次数值放到最低4位,占用了8位
P44 = 1; P42 = 1; P35 = 0; P34 = 1; // 第三列扫描
Key_New = (Key_New << 4) | (P3 & 0X0F); //将原来的数值挪到次次4位,本次数值放到最低4位,占用了12位
P44 = 1; P42 = 1; P35 = 1; P34 = 0; // 第四列扫描
Key_New = (Key_New << 4) | (P3 & 0X0F); //将原来的数值挪到次次4位,本次数值放到最低4位,占用了16位
switch(~Key_New)//Key_Value的数值对应按键的编号
{
case 0x8000: Key_Value = 4; break;
case 0x4000: Key_Value = 5; break;
case 0x2000: Key_Value = 6; break;
case 0x1000: Key_Value = 7; break;
case 0x0800: Key_Value = 8; break;
case 0x0400: Key_Value = 9; break;
case 0x0200: Key_Value = 10; break;
case 0x0100: Key_Value = 11; break;
case 0x0080: Key_Value = 12; break;
case 0x0040: Key_Value = 13; break;
case 0x0020: Key_Value = 14; break;
case 0x0010: Key_Value = 15; break;
case 0x0008: Key_Value = 16; break;
case 0x0004: Key_Value = 17; break;
case 0x0002: Key_Value = 18; break;
case 0x0001: Key_Value = 19; break;
default : Key_Value = 0;
}
return Key_Value;
}
在主函数采集键盘读取的信息时,要进行消抖处理,防止多次触发或误触发
void Key_Proc()
{
if(Key_Slow_Down) return;
Key_Slow_Down=1; //定时器减速,控制多少秒读取一次键盘
Key_New=Key_Read();//这里决定是采用独立键盘还是矩阵键盘
Key_Down=Key_New&(Key_Old^Key_New);
Key_Old=Key_New;
if(Key_Down)
{
//用户自己写的代码
}
}
6.温度采集
温度采集功能的实现是通过板上的DS18B20芯片实现的,DS18B20通过onewire主线连接
根据芯片手册中
我们需要对采集值*0.0625才能得到实际温度值
注意,在接受DS18B20传递的数据时,DS18B20会先传递低八位数据,再传递高八位数据,要对两次传递的数据进行整合
temp=(high<<4)|(low>>4);
在主办方给出的官方资料包中有onewire的驱动参考代码,所以我们只需要在给出的参考代码onewire.c略做修改就可以了.
float fRead_Temperture()
{
float temp;
unsigned char low,high;
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);
twice:;
init_ds18b20();
Write_DS18B20(0xcc); //跳过读取ROM
Write_DS18B20(0xbe); //获取暂存器数据
low = Read_DS18B20(); //温度低8位数据
high = Read_DS18B20(); //温度高8位数据
temp = (high<<4|low>>4)*0.0625;
while(temp==85)
{
goto twice;
}
return temp;
}
利用goto语句解决了上电时,显示温度85的问题
在onewire.h中增添声明
float fRead_Temperture();
此外,由于我们定时器一般采用12t模式,所以在onewire的延时函数中要把t*12,将Delay_OneWire()修改为
void Delay_OneWire(unsigned int t) //STC89C52RC
{
t=t*12;
while(t--);
}
7.iic
蓝桥杯省赛关于iic主要考察了ADC,DAC和EEPROM三个功能,先在iic.c中添加以下函数
DAC模块
void PCF8591_Dac(unsigned char dat)
{
IIC_Start();//发送开启信号
IIC_SendByte(0x90);//选择PCF8591芯片,确定写的模式
IIC_WaitAck();//等待PCF8591反馈
IIC_SendByte(0x41);//使能DA转换(随便写通道编号,不影响,主要的功能是使能DA)
IIC_WaitAck();//等待PCF8591反馈
IIC_SendByte(dat);//将待转换的数据发送出去
IIC_WaitAck();//等待PCF8591反馈
IIC_Stop();//停止发送
}
ADC模块
unsigned char PCF8591_Adc(unsigned char address)
{
unsigned char temp;
IIC_Start();//发送开启信号
IIC_SendByte(0x90);//选择PCF8591芯片,确定写的模式
IIC_WaitAck();
IIC_SendByte(address);//使能DA转换(随便写通道编号,不影响,主要的功能是使能DA)
IIC_WaitAck();//等待PCF8591反馈
IIC_Start();
IIC_SendByte(0x91);
IIC_WaitAck();
temp = IIC_RecByte();//接收数据
IIC_SendAck(1);//选择不应答
IIC_Stop();//停止发送
return temp;
}
ADC和DAC都是通过PCF8591来实现的数据采集和模拟,从给出的芯片资料可以看见
使用PCF8591时,不管是ADC还是DAC都要先传递地址,1001是固定字,最后一位0为写模式,1为读模式,PCF8591在iic上的地址是000
随后发送控制字节
其中0是固定字节,第7位是模拟输出允许(1为有效),默认为允许,第6和第5位是模拟输入选择,从控制字节的介绍和下方原理图可以知道,竞赛板上的输入是四路单端输入,第三位是自动增益,一般我们默认是为0关闭,第一位和第二位选择我们要命中的通道编号,通道1是光敏,通道三是滑动变阻器
在头文件中我们定义
#define Photo_Res_Channel 0x41 //选中光敏电阻的控制字节
#define Adj_Res_Channel 0x43 //选中滑动变阻器的控制字节
void PCF8591_Dac(unsigned char dat);
unsigned char PCF8591_Adc(unsigned char address);
在之后的操作中,我们通过调用选择相应通道的字节的宏定义,就可以选择通道了
EEPROM
同PCF8591一样,EEPROM的使用同样需要先发送地址,A2,A1,A0都为0
写EEPROM的方法有两种,一种是一个字符一个字符的储存,另外一种是八个字符八个字符的储存,
这里给出储存八个字符的方法,储存一个字符的方法只需要去掉循环稍作改动
写入的地址务必是8的倍数
//函数名:写EEPROM函数
//入口参数:需要写入的字符串,写入的地址(务必为8的倍数),写入数量
//返回值:无
//函数功能:向EERPOM的某个地址写入字符串中特定数量的字符。
void EEPROM_Write(unsigned char* EEPROM_String, unsigned char addr, unsigned char num)
{
IIC_Start();//发送开启信号
IIC_SendByte(0xA0);//选择EEPROM芯片,确定写的模式
IIC_WaitAck();//等待EEPROM反馈
IIC_SendByte(addr);//写入要存储的数据地址
IIC_WaitAck();//等待EEPROM反馈
while(num--)
{
IIC_SendByte(*EEPROM_Strng++);//将要写入的信息写入
IIC_WaitAck();//等待EEPROM反馈
IIC_Delay(200);
}
IIC_Stop();//停止发送
}
读取也是可以每次只读一个字符也可以连续读八个字符,这里只给出连续读八个字符的方法
//函数名:读EEPROM函数
//入口参数:读到的数据需要存储的字符串,读取的地址(务必为8的倍数),读取的数量
//返回值:无
//函数功能:读取EERPOM的某个地址中的数据,并存放在字符串数组中。
void EEPROM_Read(unsigned char* EEPROM_String, unsigned char addr, unsigned char num)
{
IIC_Start();//发送开启信号
IIC_SendByte(0xA0);//选择EEPROM芯片,确定写的模式
IIC_WaitAck();//等待EEPROM反馈
IIC_SendByte(addr);//写入要读取的数据地址
IIC_WaitAck();//等待EEPROM反馈
IIC_Start();//发送开启信号
IIC_SendByte(0xA1);//选择EEPROM芯片,确定读的模式
IIC_WaitAck();//等待EEPROM反馈
while(num--)
{
*EEPROM_String++ = IIC_RecByte();//将要写入的信息写入
if(num) IIC_SendAck(0);//发送应答
else IIC_SendAck(1);//不应答
}
IIC_Stop();//停止发送
}
写入的地址务必是8的倍数
EEPROM read的时候读取一个就应答一次,不读取了不应答
头文件中对两个函数进行声明
void EEPROM_Read(unsigned char* EEPROM_String, unsigned char addr, unsigned char num);
void EEPROM_Write(unsigned char* EEPROM_String, unsigned char addr, unsigned char num);
DS1302
ds1302写入数据和读出数据时,都是用的BCD码表示的。在数码管上显示时,我们使用的是十进制数,所以我们需要在每次写入和读取时,将BCD转化为DEC或者DEC转化为BCD
对芯片DS1302的控制我们通过发送控制字节来控制的
读取和写入的控制字节相差1
在DS1302的头文件中添加代码
#define DecToBCD(dec) (dec/10*16)+(dec%10)
#define BCDToDec(bcd) (bcd/16*10)+(bcd%16)
void Write_Ds1302_Byte( unsigned char address,unsigned char dat );
unsigned char Read_Ds1302_Byte( unsigned char address );
在ds1302.c中我们定义写入和读取两个函数
/函数名:设置DS1302时分秒函数
//入口参数:包含时分秒数据的数组指针
//返回值:无
//函数功能:设置DS1302时分秒函数
void Set_Rtc(unsigned char* ucRtc)
{
unsigned char temp;//中间局部变量,存放时分秒
Write_Ds1302_Byte(0x8e, 0);//wp = 0, 允许写操作
temp = (DecToBcd(ucRtc[0])) ;//数组的第0个数据,小时,用BCD码的形式存储
Write_Ds1302_Byte(0x84, temp);//写入到小时的寄存器
temp = (DecToBcd(ucRtc[1])) ;//数组的第1个数据,分钟,用BCD码的形式存储
Write_Ds1302_Byte(0x82, temp);//写入到分钟的寄存器
temp = (DecToBcd(ucRtc[2])) ;//数组的第2个数据,秒,用BCD码的形式存储
Write_Ds1302_Byte(0x80, temp);//写入到秒的寄存器
Write_Ds1302_Byte(0x8e, 0x80);//wp = 1, 不允许写操作
}
//函数名:读取DS1302时分秒函数
//入口参数:将读取到的时分秒数据存放到数组的指针
//返回值:无
//函数功能:读取DS1302时分秒函数,将读取到的数据放到数组指针指向的位置
void Read_Rtc(unsigned char* ucRtc)
{
unsigned char temp;//中间局部变量,存放时分秒
temp = Read_Ds1302_Byte (0x85);//读取小时数据
ucRtc[0] = (BcdToDec(temp));
temp = Read_Ds1302_Byte (0x83);//读取分钟数据
ucRtc[1] = (BcdToDec(temp));/
temp = Read_Ds1302_Byte (0x81);//读取秒数据
ucRtc[2] = (BcdToDec(temp));
}
定时器1
在比赛时,定时器的代码编写可以通过官方给的软件来生成
我一般是选择定时长度1毫秒,12T模式,16位自动重装载
但是生成后需要在里面加上打开定时器1中断打开,从用户手册里面我们可以看见,定时器1的中断是ET1
讲生成的代码修改为
void Timer1Init(void) //1毫秒@12.000MHz
{
AUXR &= 0xBF; //定时器时钟12T模式
TMOD &= 0x0F; //设置定时器模式
TL1 = 0x18; //设置定时初值
TH1 = 0xFC; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
EA=1;
ET1=1;
}
同时要在头文件添加声明
定时器0
定时器0在比赛中常常用来测量NE555或者用来控制PWM占空比.这里给出测量NE555频率的方法
先修改定时器0的代码
void Timer0Init(void) //0秒@12.000MHz
{
AUXR |= 0x80; //定时器时钟1T模式
TMOD |= 0x05; //0x04 0x05都可
TL0 = 0x00; //设置定时初始值
TH0 = 0x00; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 0;
}
提取数据,由于ne555输出频率的范围是500-20000HZ最多需要5个数码管显示
//定时器1轮询
void Timer1Handle(void) interrupt 3
{
//定时器计算1S脉冲个数
if(GlobalState == STATE_FREQMEASURE) //如果是在计算频率状态
{
if(AllTime == 0) //ALLTime为计时的变量
{
TR0 = 1; //开启计数
AllTime++;
}
else if(AllTime < 2000) //如果小于2s钟的话(我是1ms加一次,2000*1ms = 2s)
{
AllTime++;
}
else //如果2s到了的话
{
AllTime = 0; //清空计时变量
TR0 = 0; //停止计时
FreqCnt = (((uint)TH0 << 8) |(uint)TL0)/2; //将脉冲频率取出/2
TH0 = 0; //清除脉冲计数现有值,做好初始化
TL0 = 0;
}
}