蓝桥杯单片机学习记录

板子为国信长天CT107D,视频资料为小蜜蜂,这里将记录学习的一些笔记和心得。

一.基础知识篇

1.绪论

内核资源分布浓缩图:

2.LED指示灯的基本控制

一些常用的逻辑门

按位右移和按位左移:

注意这里是整体移动,末尾补零,比如以下这个向左移一位(<<)

假设是左移两位的话,也是类似的

然后按位右移也是类似的,要求整体进行移动

 

 假如是右移四位的话

点亮LED可以通过移位操作,逐个点亮,也可以直接将P0口赋值,一键点亮(点亮多少都可以) 

这里点亮led需要通过138译码器进行操作,138译码器一般是选中哪位,哪位就不亮

3.继电器与蜂鸣器的基本控制

P0^7默认和其他引脚一样,只要关注06和04即可

达林顿管是一个非门(ULN2003)

通过实现Y5C给LE使能,然后给高电平信号,通过达林顿管实现非门运算,就可以实现继电器和蜂鸣器的控制。

switch语句的用法->可以将138译码器整合成一个函数,然后直接控制即可,提高代码简洁性

swich(表达式)

{

  case 1:
      语句1;
      break;

  case 2:
      语句2;
      break;

  .......

  default:
      break;

}

然后补充一个小小的知识点:

unsigned char ->0~255
如果需要存储范围较小的整数数据,且占用空间较小,则可以选择 unsigned char。

unsigned int ->0~65535
如果需要存储较大范围的整数数据,则应选择 unsigned int。

HC138译码器定义的另一种方法

这种定义的含义就是在一开始P2处在一个全为高电平的状态,一共8位,分别是2^0~2^7,用数字信号表示就是1111 1111,然后先与一个0x1f,0x1f数字信号就是0001 1111,秉持着与门有零为零,先将后三位置零,就是2^5~2^7置零,然后再对5~7进行操作,后面并就是对后三位进行操作,也是从高位到低位这样,比如0x80,即1000 0000,就是让2^7=1,2^6=0,2^5=0;得到y4c为低电平的状态。

void InitHC138(unsigned char n)
{
	switch(n)
	{
		case 4:
			P2 = (P2 & 0X1f) | 0x80;
		break;
		case 5:
			P2 = (P2 & 0X1f) | 0xa0;
		break;
		case 6:
			P2 = (P2 & 0X1f) | 0xc0;
		break;
		case 7:
			P2 = (P2 & 0X1f) | 0xe0;
		break;
	}
}

4.共阳数码管的静态显示

这里单片机的数码管是共阳极的,就是说要给一个低电平才能触发,比如要点亮数字1,在图中可以知道数字1的段码是b,c,这时候就要给到b,c低电平,其他给到高电平,也是按照从高位到低位,即dp~a,数字信号表示为1111 1001,转化为16进制就是0xf9,其他的转化也是类似的。

然后这里选中哪一个数字是由com端控制,com端是给定高电平就选中,同时受到y6c的控制

数码管在显示之后一定要延迟!!!

5.共阳数码管的动态显示 

和静态的不同,这里利用的是数码管的余晖效应,也就是人眼的视觉残留效果,因为本身是用一个p0口控制全部,同时点亮的话就会只能生成一个数字,然后如果是分别点亮,每一位亮1~2ms,就能实现数码管显示不同的数字。

月份更新的时候要延时显示,这样能够在更新时候让显示类容停留片刻,防止月份更新之后前面的熄灭,一般这里用的是STC的延时函数,然后delay(2)是比较合适的。

主函数:

void main(){
	while(1){
		display();
		month++;
		if(month>12){
			month=1;
		}
		Delay_smg(10);
	}
}

延时函数:

void Delay_smg(unsigned char t)
{
		while(t--)
		{
			display(); //数码管显示
		}
}

6.独立按键的基本操作

       这里先用sbit定义引脚,然后判断是否为0,为0就是按下(因为是和GND相连),这里要进行消抖,因为不消抖无法判断是否已经按下,具体操作就是就是按下之后(判断第一次是否为0),然后延时一段时间,再判断第二次是否为0,然后就可以进行其他操作,这里如果是和数码管挂钩的话,那么每次按下的状态瞬间,都要去重新显示一下数码管,不然会一闪一闪。

     这里还学习了状态机的设计,就是定义一个变量,给定这个变量一个数值,然后每改变一种状态就让数值变化一次,通过数值判断进行操作。

     这里实现的流程主要是定义好引脚,比如不同的按键对应不同的引脚,因为有一端是接地的,所有如果按键按下,电路就会导通,定义的方法如下:

sbit S7=P3^0;
sbit S6=P3^1;
sbit S5=P3^2;
sbit S4=P3^3;

7.矩阵键盘的基本操作

记得要改变跳帽的接线

因为接的是两个io端口,一个io端口输出0,按键按下,接通,另一个io端口也是输出0,逐行逐行扫描,逐列读取状态。

这里是通过先定义一行为0,其余行为1,然后判断一列一列是否为0,为0即按下,那么就可以进行其他操作

示例代码(这里还加入了其他功能,参考性比较低,但是实现的大体流程就是如下):

unsigned char sta_key=0;
void scankeys(){
	if(S7==0){
		delay(5);
		if(S7==0){
			if(sta_key==0){
				L1=0;
				sta_key=1;
			}
			else if(sta_key==1){
				L1=1;
				sta_key=0;
			}
		}
		while(S7==0);
	}
	if(S6==0){
		delay(5);
		if(S6==0){
			if(sta_key==0){
				L2=0;		
				sta_key=2;
			}
			else if(sta_key==2){
				L2=1;
				sta_key=0;
			}
			while(S6==0);
		}
	}
	if(S5==0){
		delay(5);
		if(S5==0){
			if(sta_key==1){
				L3=0;
				while(S5==0);
				L3=1;
			}else{
				L5=0;
				while(S5==0);
				L5=1;
			}
		}
	}
	if(S4==0){
		delay(5);
		if(S4==0){
			if(sta_key==1){
				L4=0;
				while(S4==0);
				L4=1;
			}else{
				L6=0;
				while(S4==0);
				L6=1;
			}
		}
	}
}

这里存在一个问题,就是一样的代码,这里为什么不能实现动态显示

8.中断系统与外部中断应用

这里可以参考手册439——中断结构图,去配置中断,中断号要记住

中断执行流程,简单来说,就是跳开当前执行的程序,去执行其他程序,执行完其他程序,在执行回原来的程序

中断系统结构图

可以参考      单片机—外部中断与定时器 学习笔记_6318h用0x怎么表示-CSDN博客

这里使用中断时,需要将J5打到独立按键处,因为这里是共用端口

 

按照自然优先级从高到低排序

这张图是理解中断的关键,首先是中断源接受到中断请求,在TCON(串口为SCON)发出中断请求,中断标志位置1,发生中断,若置零,则不发生中断,然后在IE打开中断系统,IE可以理解为门这样子,有一个小门和一个大门,把门打开,才能中断。

在TCON中,比如拿外部中断0(INT0)举例,IT0=1为下降沿触发,即由高电平变为低电平时触发,然后IT0=0时就是高电平触发,这里通过IT0门,到IE0,IE0置1,中断打开,到IE,IE中两个门都置1,允许中断,IP用的比较少。

以下这张图解释的更为清晰,这里IP是改变优先级的意思,这里用的比较少 

还需要记住中断号(最左边的)

这里写中断的函数记得是中断初始化函数+中断服务函数(中断后要干什么在这里写,记得加上中断号,否则系统不知道是中断函数) ,中断初始化函数要在while(1)之前调用一下。

9.定时器的基本原理与应用

tmod中,高4位控制t1,低四位控制t0

这里没有自动重装,每次都需要自己重新赋予初值 

这里做了第一个案例:秒表

这里让定时器运行和之前的中断是差不多的,这里多了TMOD,以及TR(timer run,控制定时器运行和停止)通过利用16位加法计数器,每满65536(2^16)溢出作为一次定时周期,就是说最多只能计数65.5ms,就比如你想要计数0.5s,可以设定一个变量,并且赋值为0,0.5s就是500ms,可以让定时周期设置为50ms,然后计时10次,这样就能实现0.5s的计数,其他计数也是类似的。

函数体也是类似的,中断初始化函数+中断服务函数

void InitTimer0()
{
	TMOD = 0x01;  //模式选择-16位
	TH0 = (65535 - 50000) / 256;
	TL0 = (65535 - 50000) % 256; //计数50ms
	ET0 = 1;
	EA = 1; //打开门
	TR0 = 1;  //打开定时/计数器
}

10.PWM脉宽调制

利用高电平在一个周期内所占时间进行调制,LED灯是低电平亮,所以占空比越高,灯越暗(占空比越高,低电平所占比例越小)

11.串口通讯 

这里我的目的是只要能够实现收发就可以了,复杂的就不做了。

其实总体在手册上都有,主要是配置tmod,tcon,这里比定时器还多了个scon

配置参照

这里大部分已经给出,需要修改的是:

记住这里是用的定时器1

AUXR=0x00

TH1 = 0xfd;
TL1 = 0xfd;

初始化函数

参考手册627,还有个smod,就是对TH和TL配置,参考11.0594,两个都要配置成0xfd

中断服务函数也是参照stc,这里中断我只要收字节,所以把下面的(TI)删了,设置一个变量存储SBUF的值(这是串口收到的值)

 

 然后接下来写个发的函数,直接发就可以了,记得发16位的(0x kk之类的)

void SendByte(unsigned char dat)
{
	SBUF = dat;
	while(TI == 0);
	TI = 0;
}

void main()
{
	InitUart();
	SendByte(0x5a);
	SendByte(0xa5);
	while(1);
}

一些具体的外设:

SCON

通用异步8位UART:0x50 

12.IO拓展技术与存储器映射拓展(了解)

存储器映射拓展 

可以直接通过地址去访问和实现功能,不用通过hc138和或非门的选择,可以起到节省代码的作用。当然跳帽记得从IO转到MM,但是一般都是IO拓展方式,存储器拓展如果有矩阵键盘,就会有冲突,因为占用了P3^6的io口

13.实训1 

P0口复用,拿一个按键举例,并与并,这里主要是防止相互影响

	if(S4==0){
		delay(2);
		if(S4==0){		
			while(S4==0){
				show_time();
			}
			hc138(4); //这三句一定要这样写

			static_led=(static_led|0x80)&(~static_led | 0x7f); //先将操作位设为高电平,然后与上取反的结果,或0不变,与1不变。

			P0=static_led;
		}
	}

这里有个新思路(防止冲突)对于P0口 ,就是说我可以再定义一个专门给led显示的函数,类似hc138的,防止数码管和led冲突,每次用之前,关hc138,赋完值之后,再打开hc138,然后再关闭

void Set_HC573(unsigned char channel, unsigned char dat){  P2 = (P2 & 0x1f) | 0x00;       //赋值之前,关闭所有锁存器,防止冲突  P0 = dat;                      //设置待赋值数据  switch(channel)                //根据参数,选通锁存器,向目标赋值  {    case 4:      P2 = (P2 & 0x1f) | 0x80;  //Y4输出0,LED控制    break;    case 5:      P2 = (P2 & 0x1f) | 0xa0;  //Y5输出0,蜂鸣器和继电器控制    break;    case 6:      P2 = (P2 & 0x1f) | 0xc0;  //Y6输出0,数码管位选    break;    case 7:      P2 = (P2 & 0x1f) | 0xe0;  //Y7输出0,数码管段码    break;    case 0:      P2 = (P2 & 0x1f) | 0x00;  //所有锁存器不选择    break;  }  P2 = (P2 & 0x1f) | 0x00;      //赋值完成后,关闭所有锁存器}

unsigned char stat_led = 0xff; //定义LED灯当前状态

void LED_Control(){  stat_led &= ~0x80;           Set_HC573(4, stat_led);    //L8灯点亮  Delay(200);                //延时  stat_led |= 0x80;           Set_HC573(4, stat_led);    //L8灯熄灭  Delay(200);  

14.模块化编程 

特别要注意,要声明才能使用函数、变量,比如在下方的.c文件引入头文件,直接从source group里面新建.h和.c文件就可以了,注意要编译

以下参考:蓝桥杯单片机组——榨干选手资源包(芯片数据手册)_蓝桥杯单片机定时器中断芯片资料手册-CSDN博客

15.pcf8591(模数转换器)

ad:模拟转数字;

da:数字转模拟

控制字为0x41就是读取的光敏电阻

控制字读取0x43为滑动变阻器  

参考芯片手册第五页(了解怎么读写地址):

先确定器件地址,图中A2,A1,A0都是默认置零,如果最低位是0,就是write,如果是1,就是read

功能选择相关:

这里如果是选择通道1,即0x01,就是光敏电阻,如果是0x03,就是滑动变阻器,但是,如果想两者都显示的话,0x03就变成了光敏电阻,0x01就变成了滑动变阻器 

 然后写相关代码也是可以根据表去写,比如我想写ad是,模数转换,就根据说明书第6页(这里标注有ad),注意sendbyte和sendack是不同的,就是因为这个原因导致显示一直为255不变。

具体代码:

接收到信号,不用等待,直接发送指令说不用了,然后返回的结果*1.96,因为数字输出是255,要转为电压输出,就是500,255*1.96=500,也可以在显示函数里面乘,但是这里要注意要进行强制类型转化,因为最后得到的数是无限不循环小数,要把小数部分舍去,如第二个代码图,但是记得接收的变量也必须是整形的,可以用unsigned int类型

unsigned char pcf8591_ad(unsigned char addr){
    unsigned char temp;
	I2CStart();
	I2CSendByte(0x90);
	I2CWaitAck();
	
	I2CSendByte(addr);
	I2CWaitAck();
	
	I2CStart();
	I2CSendByte(0x91);
	I2CWaitAck();
	
	temp=I2CReceiveByte();
	I2CSendAck(1);
	I2CStop();
	return temp;
}
//强制类型转换,由255变成500,再输出出来
unsigned int receive_ad;

void show_ad(){
	receive_ad=(int)(pcf_adread()*1.96);
	showsmg_pot(0,receive_ad/100);
	delay(2);
	showsmg_nopot(1,receive_ad/10%10);
	delay(2);
	showsmg_nopot(2,receive_ad%100%10);
	delay(2);
}

da,也是类似的,但是配置功能(使能转换)那一步需要为4,0x41,0x43都可以,可以记作发三次字节,最后一次发自己要的,dat是自己输入的数字量,范围是0~255,这里通过*51实现转换,因为电压输出是5v,转化成数字信号输出必需转化成255,5*51=255

还有个stop 

16.ds18b20(温度传感器)

比如读到23.7,想逐位显示到数码管上,就可以先/10,得到23,再%10,得到3,以此类推

可以查看关于ds18b20的数据手册(18页):

这里可以参考第一个,第一个是对多个ds18b20进行操作,第二个是对单个ds18b20进行操作,但是官方添加了很多的寄存器,所以参考第一个表格,但是第一个表格也要做一些更改,这里是要跳过ROM操作,所以具体如下:

这里是不需要用到ROM操作,所以在以上标注出来的presence和55h还有64-bit ROM code是直接跳过的,参考第二个表格(skip rom):

然后我们就可以根据第一个去写自己的代码:

/**
  * @brief  以带小数点的形式读取温度
  * @param  None
  * @retval temperature - float
  *
  */
float rd_temperature_f(void)
{
    unsigned char low,high;
  
  	init_ds18b20();
  	Write_DS18B20(0xCC);		//跳过ROM
  	Write_DS18B20(0x44);		//开始温度转换
  	Delay_OneWire(200);			

  	init_ds18b20();
  	Write_DS18B20(0xCC);		//跳过ROM
  	Write_DS18B20(0xBE);		//读取温度寄存器

  	low = Read_DS18B20();
  	high = Read_DS18B20();
	
	return ((high<<8)|low)/16.0;
}

 这里读取ds18b20,首先读到低八位,然后再读取高八位,先用变量存储低八位,再存储高八位,然后需要将他们合并在一起,通过“并”的操作。

除了以上步骤外还要修改onewire.c的延时函数,由于单总线协议没有时钟线,因此必须有准确的时间要求,而官方提供的是普通51单片机的代码,与IAP的时钟不一致,可以理解为IAP15的时钟比51单片机的时钟快12倍,也就是说源代码中的1us实际上在IAP15上运行只有1/12us,所以需要乘12倍。

/**
  * @brief  更改为之前12倍的延时
  * @param  None
  * @retval t - 代表延时的时长 单位:us
  * @author 
  */
void Delay_OneWire(unsigned int t)  //STC89C52RC ->IAP15
{
	unsigned char i;
	while(t--)
	{
		for(i=0;i<12;i++);
	}
}

 17.ds1302

注意看给的原始代码有没有问题,参考芯片图,这里IO是SDA,注意头文件要引入

#include <intrins.h>还有芯片的头文件

这里io是sda(data),ce是rst,sck不变

这里是采用bcd码(4位二进制数表示一个十进制数,转换时通过/16或者%16)来显示(就是在读取的时候要通过这个转换才能得到十进制的数,比如我读取到0x59,通过/16得到5,%16得到9),比如你想设定初始时间,就要先写入ds18b20,然后再读出ds18b20,比如想写入20年 4月18日 周六 23:59:55 那么可以设一个数组(这里采取由低位到高位排序):time[ ]={0x55,0x59,0x23,0x18,0x04,0x06,0x20},注意这里日期是夹在月和年中间。

这里8e,也就是倒数第二个,是保护位,如果写1,打开保护,写0,关闭保护。

在写入数据之前,打开保护位(0x00),在写完数据之后,关闭保护位(0x80)

注意在这里读数据时,高八位通过/16转换出来,低八位通过%16转换出来(这里是转换十进制) 

这里函数是比较容易实现的,参考相关数据手册第9页即可,注意写保护的打开和关闭

//读取和设定初值

void setclock(){
	Write_Ds1302_Byte(0x8e,0x00);

	Write_Ds1302_Byte(0x84,0x29);
	Write_Ds1302_Byte(0x82,0x55);
	Write_Ds1302_Byte(0x80,0x55);

	Write_Ds1302_Byte(0x8e,0x80);
}
void getclock(){
	time[2]=Read_Ds1302_Byte(0x85);
	time[1]=Read_Ds1302_Byte(0x83);
	time[0]=Read_Ds1302_Byte(0x81);
}

//显示:
void showclock(){
	getclock();
				show_smg(0,time[2]/16,1);
				delay(2);
				show_smg(1,time[2]%16,1);
				delay(2);
				show_smg(2,17,1);
				delay(2);
				show_smg(3,time[1]/16,1);
				delay(2);
				show_smg(4,time[1]%16,1);
				delay(2);
				show_smg(5,17,1);
				delay(2);
				show_smg(6,time[0]/16,1);
				delay(2);
				show_smg(7,time[0]%16,1);
				delay(2);}
//主函数:
void main(){
	setclock();
	while(1){
		showclock();
	}
}

18.at24c02 

 这是也是用的单总线,iic,注意和pcf区分开,这个主要是起到保存数据的作用

这里要注意写/读地址的地址位(参考相应数据手册11页):

解释一下:R/W,这里如果是1,选中R,反之如果是0,选中W

在向AT24C02写入数据时,从发出命令到写完成,这个中间大致需要花费10ms的时间,如果再次期间重复发送就会导致数据错误,因此在官方IIC.c代码的基础上首先需要添加一个10ms延时函数,保证数据传输的稳定性。

AT24C02写入流程:
(1)开启总线;(2)发送器件地址;(3)等待器件应答;
(4)发送要写入数据的地址;(5)等待应答;(6)发送写入的数据;
(7)等待应答;(8)关闭总线;(9)必要的延时;
一开二验三应答;
四地五答六数据;
七答八关九延时。
写入数据代码如下:

先写地址再写数据,记得延时,先写位置再写地址

参考,只需要记住start,device a ,ack,wode a,ack,data,ack,stop

void Write_Eeprom(unsigned char add,unsigned char val)
{
    i2c_start();
    i2c_sendbyte(0xa0);//写
    i2c_waitack();
    i2c_sendbyte(add);
    i2c_waitack();
    i2c_sendbyte(val);
    i2c_waitack();
    i2c_stop();
	Delay10ms();
}

AT24C02读取流程:
读数据流程大致与写入相同,需要先写入要读取的数据地址add,然后写入读取的命令,最后按位接收数据代码如下,顺序记不住可以参考芯片手册(pcf8091的,有个bite write和Sequential Read,分别是字节写入和顺序读取),这里注意是要再写一次,每次读完,都要说不要读了,不用再等待,这里和pcf8091类似,也是要开始两次:

参考:

unsigned char Read_Eeprom(unsigned char add)
{
	unsigned char da;
	i2c_start();
	i2c_sendbyte(0xa0);
	i2c_waitack();
	i2c_sendbyte(add);
	i2c_waitack();
	
	i2c_start();
	i2c_sendbyte(0xa1);
	i2c_waitack();
	da = i2c_receivebyte();
	i2c_sendack(1); 
	i2c_stop();
	
	return da;
}

 然后在自己需要的位置写代码:

	write_eeprom(1,(led_set_cnt[1]/100));  //向AT24C02地址0x00中写入数据
	delay();//延时10ms
	write_eeprom(2,(led_set_cnt[2]/100));
	delay();
	write_eeprom(3,(led_set_cnt[3]/100));
	delay();
	write_eeprom(4,(led_set_cnt[4]/100));

19.555定时器 (测频率用)

注意这里读取数据的时候,是要按照以下格式写,可以认为是每次都取出最低位(先除去高位)

方法一	
if(get_mai){
	if(get_mai>9999){
	show_smg(4,get_mai/10000);
	delay(2);
	}
	else if(get_mai>999){
	show_smg(4,get_mai/1000%10);
	delay(2);}
	else if(get_mai>99){
	show_smg(4,get_mai/100%10);
	delay(2);}
	else if(get_mai>9){
	show_smg(5,get_mai/10%10);
	delay(2);

方法二;
	buff[3]=~t_display[num/10000];
	buff[4]=~t_display[num/1000%10];
	buff[5]=~t_display[num/100%10];
	buff[6]=~t_display[num/10%10];
	buff[7]=~t_display[num%10];

这里使用到P3^4引脚,和定时器0冲突,这里是通过两个定时器来实现的,一个是定时器0,一个是定时器1,这里是通过计时1s,输出计了多少数,tmod是先高位配置定时器1,低位配置定时器0,定时器0使用8位重装载并且用作计数器

计数器计算的是有多少脉冲,直接用变量赋值就可以了,然后定时器定时到1s,也就是20个50ms,读取变量的值,就实现了频率的测量。

 

最终是要显示dat_f,直接显示就可以了,一般是10000+,所以从10000+操作(也就是类似/10000这样子)

20.超声波模块(测量距离用)

记住两个脚N_a1和N_b1

a1是输入端,b1是输出端

实现:

1.初始化函数(超声波发射函数),发射8个方波信号,意思就是占空比为50%,一个周期内高电平占一半,低电平占一半

超声波 读取函数(通过定时器1实现),这里tmod的配置参考stc-isp,是与0x0f,16为自动重装载

注意tx和rx

然后直接用变量读取就可以了

void show_dis(){
	wave_data=wave_read();
	show_smgea(0,18);
	delay(2);
	show_smgea(6,wave_data/10%10);
	delay(2);
	show_smgea(7,wave_data%10);
	delay(2);
}

二.实训

1.模拟一

实现:将输入数据(高字节)存入E2PROM内部地址2; 将输入数据(低字节)存入E2PROM内部地址3;

write_eeprom(0x02,num_value >> 8);

Delay5ms();

write_eeprom(0x03,num_value & 0x00ff);

Delay5ms();

功能没有完全实现,实现了一部分:

main函数

按键不知道为什么切换不了

#include <STC15F2K60S2.H>
#include "ds1302.h"
#include "iic.h"
//https://blog.csdn.net/2301_80600743/article/details/135716602
//https://blog.csdn.net/m0_60524373/article/details/123340419?ops_request_misc=&request_id=&biz_id=102&utm_term=at24c02%E5%AD%98%E9%AB%98%E4%BD%8D&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-1-123340419.142^v100^pc_search_result_base8&spm=1018.2226.3001.4187
sbit R1=P3^0;
sbit R2=P3^1;
sbit R3=P3^2;
sbit R4=P3^3;
sbit L1=P4^4;
sbit L2=P4^2;
sbit L3=P3^5;
sbit L4=P3^4;
void show_clock();
void show_recond();
void show_input();
unsigned char sta_count=0;
unsigned char time[7];
unsigned char input_num=0;
unsigned char count_time=0;
unsigned char state[4];
unsigned char keynum;
unsigned char i;
void hc138(unsigned char channal){
	switch(channal){
		case 0:
			P2=(0x1f&P2)|0x00;
			break;
		case 4:
			P2=(0x1f&P2)|0x80;
			break;
		case 5:
			P2=(0x1f&P2)|0xa0;
			break;
		case 6:
			P2=(0x1f&P2)|0xc0;
			break;
		case 7:
			P2=(0x1f&P2)|0xe0;
			break;
	}
}
void delay(unsigned char countime)		//@12.000MHz
{
	while(countime--){
	unsigned char i, j;

	i = 12;
	j = 169;
	do
	{
		while (--j);
	} while (--i);}
}
unsigned char SMG[]={
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
0x88, //A
0x83, //b
0xc6, //C
0xa1, //d
0x86, //E
0x8e, //F
0x7f, //.
0xbf
};

void scankeys(){
	//ÅжϵڶþÐÐ
	R2=0;
	R1=R3=R4=1;
	L1=L2=L3=L4=1;
	if(L1==0&&sta_count==1){
		delay(2);
		if(L1==0){
			if(count_time<4){
				state[count_time]=0;
				count_time++;
		}
	}
}
	else if(L2==0&&sta_count==1){
		delay(2);
		if(L2==0){
			if(count_time<4){
				state[count_time]=1;
				count_time++;
		}
	}
	}
	else if(L3==0&&sta_count==1){
		delay(2);
		if(L3==0){
			if(count_time<4){
				state[count_time]=2;
				count_time++;
		}
	}
	}
	else if(L4==0&&sta_count==1){
		delay(2);
		if(L4==0){
			if(count_time<4){
				state[count_time]=3;
				count_time++;
		}
	}
	}
	//ÅжϵÚÈýÐÐ
	R3=0;
	R1=R2=R4=1;
	L1=L2=L3=L4=1;
	if(L1==0&&sta_count==1){
		delay(2);
		if(L1==0){
			while(L1==0);
			for(i=0;i<4;i++){
				state[i]=0;
			}
		}
	}
	else if(L2==0&&sta_count==1){
		delay(2);
		if(L2==0){
			if(count_time<4){
				state[count_time]=4;
				count_time++;
		}
	}
	}
	else if(L3==0&&sta_count==1){
		delay(2);
		if(L3==0){
			if(count_time<4){
				state[count_time]=5;
				count_time++;
		}
	}
	}
	else if(L4==0&&sta_count==1){
		delay(2);
		if(L4==0){
			if(count_time<4){
				state[count_time]=6;
				count_time++;
		}
	}
	}
	//ÅжϵÚËÄÐÐ
	R4=0;
	R1=R3=R2=1;
	L1=L2=L3=L4=1;
	if(L1==0&&sta_count==1){
		delay(2);
		if(L1==0){
		while(L1==0);
		if(sta_count==0){
			sta_count=1;
		}
		else if(sta_count==1){
			sta_count=0;
}
	}
	}
	else if(L2==0&&sta_count==1){
		delay(2);
		if(L2==0){
			if(count_time<4){
				state[count_time]=7;
				count_time++;
		}
	}
	}
	else if(L3==0&&sta_count==1){
		delay(2);
		if(L3==0){
			if(count_time<4){
				state[count_time]=8;
				count_time++;
		}
	}
	}
	else if(L4==0&&sta_count==1){
		delay(2);
		if(L4==0){
			if(count_time<4){
				state[count_time]=9;
				count_time++;
		}
	}
	}
}
//ÊýÂë¹ÜÏÔʾ
void show_smg(unsigned char pos,unsigned char channal){
	hc138(6);
	P0=0x01<<pos;
	hc138(7);
	P0=SMG[channal];
}
//ģʽ¶þ
//ÊäÈëÊý×Ö

void set_clock(){
	//´ò¿ª±£»¤
	Write_Ds1302_Byte(0x8e,0x00);

	Write_Ds1302_Byte(0x80,0x59);
	delay(5);
	Write_Ds1302_Byte(0x82,0x09);
	delay(5);
	Write_Ds1302_Byte(0x84,0x23);
	delay(5);
	Write_Ds1302_Byte(0x86,0x01);
	delay(5);
	Write_Ds1302_Byte(0x88,0x02);
	delay(5);
	Write_Ds1302_Byte(0x8a,0x06);
	delay(5);
	Write_Ds1302_Byte(0x8c,0x24);
	delay(5);
	//¹Ø±Õ±£»¤
	Write_Ds1302_Byte(0x8e,0x80);
}
unsigned char de1302[]={0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};
void get_time(){
	char t;
	for(t=0;t<7;t++){
		time[t]=Read_Ds1302_Byte(de1302[t]);
	}
}
void show_screen(){
			switch(sta_count){
			case 0:
				show_smg(0,time[2]/16);
				delay(2);
				show_smg(1,time[2]%16);
				delay(2);
				show_smg(2,17);
				delay(2);
				show_smg(3,time[1]/16);
				delay(2);
				show_smg(4,time[1]%16);
				delay(2);
				show_smg(5,17);
				delay(2);
				show_smg(6,time[0]/16);
				delay(2);
				show_smg(7,time[0]%16);
				delay(2);
				break;
			case 1:
				show_smg(0,12);
				delay(2);
			if(count_time==0)
			{
				show_smg(4,state[0]);
				delay(2);
				show_smg(5,state[1]);
				delay(2);
				show_smg(6,state[2]);
				delay(2);
				show_smg(7,state[3]);
				delay(2);
			}
			if(count_time==1)
			{
				show_smg(7,state[0]);
				delay(2);
			}
			else if(count_time==2){
				show_smg(6,state[0]);
				delay(2);
				show_smg(7,state[1]);
				delay(2);
		}
			else if(count_time==3)
			{
				show_smg(5,state[0]);
				delay(2);
				show_smg(6,state[1]);
				delay(2);
				show_smg(7,state[2]);
				delay(2);
			}
			else if(count_time==4){
				show_smg(4,state[0]);
				delay(2);
				show_smg(5,state[1]);
				delay(2);
				show_smg(6,state[2]);
				delay(2);
				show_smg(7,state[3]);
				delay(2);
				
			}
			break;
		}
}
void init_led(){
	hc138(4);
	P0=0xff;
}
void main(){
	set_clock();
	init_led();
	while(1){
		get_time();
		scankeys();
		show_screen();
	}
}

2.第十届

这里考察了pcf8591
注意要启动两次
用iic
实现流程:
总线开始
写 写的地址
等待响应

写入地址
等待响应

总线开始()
写 读的地址
等待响应
用变量接收读的结果
发送1(用sendack而不是sendbyte,前面用的是sendbyte)
总线停止

返回变量

这里555定时器不知道为什么运行不了,但是大概思路大差不差,在中断初始函数,直接定义定时器1和定时器0,因为我要定时器0做为计数器,定时器1作为定时器,定时器0是接收脉冲信号的,所以tl0和th0都要设置成0xff(255),这里是看定时1s收到多少个脉冲。这里tmod是0x16(定时器1是定时器正常设置成0x01,定时器0是计数器,选中计数器c(1),8位自动重载10,所以tmod是要设置成0x16),设置完之后,要把全部门都打开

da和上面不一样,三个发送,发送写的地址,发送0x41(这里一定要有4,表示选的是da,也就是转为模拟信号),发送数字信号,如果想和ad联动,可以将ad得到的值作为参数放入da中

点亮led,可以在hc138(channal,dat),然后p0=dat,sta-led(设一个初始状态)=0xff,应该每次保留,可以实现不冲突,这里也是要收到状态机的指令再操作,然后通过|和&的操作进行赋值。

还有状态机的设置,非常好用,可以每个按键都设一个状态机(这是重点)

注意if语句和if外面的语句,等于号是一个还是两个

3.第十一届

这里主要用的是状态机来写的,这里有一个新的思路,就是引入定时器,可以参考stc-isp:

这里要自己打开门,比如et1,ea等等,通过1ms的定时器,不断去扫描状态

这里按键去抖动也可以参考(效果相对来说还是比较好的):

 但是有个重影问题没解决

	R4=0;
	R3=1;
	C3=C4=1;
	if(C3==0){
		delay(2);
		if(C3==0){
		if(mode==0){
			mode=1;
		}
		else if(mode==1){
			mode=2;
		}
		else if(mode==2){
			mode=0;
		}
		
	}}

4.第十二届

那个ds18b20卡了很久,最后发现原因是DQ写错了,本来是1.4,赋值成了1.2。

注意有一点非常重要,最后精度换算的时候,要除以16.0,而不是16,除以16得不到结果

还有变量的赋值也要仔细,因为我温度得到的是类似27.5这样的,所以函数要写为浮点类型(float),然后取值的变量也要写成float类型,读取的时候要进行强制类型转换,(unsigned

char),不然也读不出来:

完整代码(一点问题也没有)

//温度获取
float get_temper(){
	unsigned char low,high;
	init_ds18b20();
	Write_DS18B20(0xcc);
	Write_DS18B20(0x44);
	
	init_ds18b20();
	Write_DS18B20(0xcc);
	Write_DS18B20(0xbe);
	
	low=Read_DS18B20();
	high=Read_DS18B20();
	
	return ((high<<8)|low)/16.0;
}
//读温度并且显示出来
float temp;
void show(){
	temp=get_temper();
	show_smg(0,(unsigned char)temp/10);
	delay(2);
	show_smg(1,(unsigned char)temp%10);
	delay(2);
	show_smg(2,(unsigned char)(temp*10)%10);
	delay(2);
}

上面的显示函数,比如我想将27.5显示出来,/10就是得到2,%10就是得到7,然后乘以10再%10就是得到5,不要乘以10再转换,这样精度会大幅度下降。

这里晶振最好改成12,变得没这么快:

5.第十四届

按键功能多的时候,可以将按键设为一个函数,类似:

总体的做题思路:

1.模板写好:

138锁存器

数码管点亮(位选,段选)

led初始状态(0xff),用与和并进行操作,熄灭,就并上含1的,点亮,就与上含1的取反

数码管编码

main函数

初始化函数

2.外设功能写好

3.给led灯赋予一个初始状态(0xff),直接对位进行操作

毕 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值