蓝桥杯单片机省赛复习汇总

本文是作者的赛后总结

目录

各个模块

1.138译码器

2.关闭外设 

3.Led

4.数码管

5.键盘

6.温度采集

7.iic

DAC模块

ADC模块

EEPROM 

DS1302

定时器1

定时器0 


各个模块

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;
        }
 
}

  • 13
    点赞
  • 88
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值