蓝桥杯单片机(CT107D_V3.0)学习笔记

前言

本文章是我在小蜜蜂B站视频下学习的,视频教学完后进行记录,此篇为视频学习笔记。

文章目录

  • 前言
  • LED指示灯的基本控制
    • 74HC138(三八译码器)
    • 74HC573锁存器
  • 蜂鸣器和继电器的控制
    • ULN2003
    • 控制
    • 三八译码器的模块化
  • 共阳数码管
    • 共阳数码管的静态显示
    • 共阳数码管的动态显示
  • 按键
    • 独立按键
    • 矩阵按键
  • 中断系统与外部中断服务
    • 中断服务函数编写
  • 定时器
    • 定时器综合应用
  • PWM脉宽调制信号
    • 操作
  • 串口通信
    • 基本操作
    • 上位机发送数据给单片机执行
    • 传输字符串
    • BCD - DEC
  • 存储器映射扩展技术
  • 单总线温度传感器DS18B20
    • 温度转换与读取流程
    • Ds18b20数据处理
    • 操作流程
  • DS1302
    • 基本信息
    • 基本操作
  • NE555定时器与频率测量
    • 操作
  • PCF8591
    • 基本信息
    • iic(i²c)通信协议理论讲解
    • 代码编写
    • 实际应用
  • AT24C02存储器
    • 基本信息
    • 写入方式
    • 读取方式
    • 应用
  • 超声波
    • 基本信息
    • 代码编写


在这里插入图片描述

LED指示灯的基本控制

74HC138(三八译码器)

在这里插入图片描述

类似于二进制的记忆方法
在这里插入图片描述

74HC573锁存器

蜂鸣器和继电器的控制

ULN2003

在这里插入图片描述
中间是非门输入1输出0,输入0输出1。

控制

关掉蜂鸣器的代码

void InitSystem(){
	HC138_A2 = 1;
	HC138_A1 = 0;
	HC138_A0 = 1;//Y5
	//或者:OutPutP0(5,0x00);
	P0 = 0x00;
}

在这里插入图片描述
蜂鸣器
继电器

需要在N_BUZZ给低电平,蜂鸣器就会叫。回溯到输入端,需要在7B处给高电平,经过ULN2003反转后成为低电平;要想做到上述操作,就要使锁存器解锁,因此要给Y5低电平。
继电器分析过程类似,不赘述。
继电器吸合放开:

HC138_A2 = 1;
HC138_A1 = 0;
HC138_A0 = 1;//Y5
	P0 = 0x10;//继电器吸合
	Delay(60000);
	Delay(60000);
	P0 = 0x00;//放掉

三八译码器的模块化

为了后续代码整洁,把三八译码器进行了模块化。

void InitHC138(unsigned int n){
	switch(n){
		case 0:P2_7=0;P2_6=0;P2_5=0;break;
		case 1:P2_7=0;P2_6=0;P2_5=1;break;
		case 2:P2_7=0;P2_6=1;P2_5=0;break;
		case 3:P2_7=0;P2_6=1;P2_5=1;break;
		case 4:P2_7=1;P2_6=0;P2_5=0;break;
		case 5:P2_7=1;P2_6=0;P2_5=1;break;
		case 6:P2_7=1;P2_6=1;P2_5=0;break;
		case 7:P2_7=1;P2_6=1;P2_5=1;break;
	}
}

如果可以的话用16进制可以省略消影的延时,但是我对位操作符的运算不太熟悉。

共阳数码管

在这里插入图片描述
顺时针转从顶上那根开始分别是A B C D E F G小数点是 DP。
用哪个“8”是Y6锁存器控制,一个“8”的中的哪根管子亮就是Y7。
通过实验得出该数码管为共阳数码管。
在这里插入图片描述

共阳数码管的静态显示

0-9以及一些符号的段码(Segmented)用数组存起来:

unsigned char SEG[18] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xbf};

0~9为数字0-9,10-15为AbCdEF,16为-
然后把输出做一个函数,可以方便引用。

void ShowSMG(unsigned char pos,unsigned dat){
	OutPutP0(6,0x01 << (pos-1));//选中公共端
	OutPutP0(7,SEG[dat]);//输出数字
	Delay(20);
}

注:此处延时为11.0592Hz以1毫秒为单位的。
至此,晶体管静态显示就变成了一个,选中、输出的过程。
做一个输出:

void SMGSTATIC(){
	unsigned char i,j;
	for(i = 1;i <= 8;i++){
		for(j = 0;j <= 9;j++){
			ShowSMG(i,j);
			Delay(1000);
		}
	}
}

展示:

数码管静态显示

共阳数码管的动态显示

实际上是极短时间内的静态显示,由于人眼的视觉残留和发光二极管的余晖效应(灭了没完全灭),而呈现的多位数输出效果。

void main()
{
	while(1){
		ShowSMG(1,1);
		Delay(1);
		ShowSMG(2,2);
		Delay(1);
		ShowSMG(3,3);
		Delay(1);
	}
}

展示:
在这里插入图片描述

按键

独立按键

unsigned char Key(){
	unsigned char KeyNumber = 0;
	if(P3_0 == 0){Delay(20);while(P3_0 == 0);Delay(20);KeyNumber = 7;}
	if(P3_1 == 0){Delay(20);while(P3_1 == 0);Delay(20);KeyNumber = 6;}
	if(P3_2 == 0){Delay(20);while(P3_2 == 0);Delay(20);KeyNumber = 5;}
	if(P3_3 == 0){Delay(20);while(P3_3 == 0);Delay(20);KeyNumber = 4;}
	
	return KeyNumber;
}

松开给反馈
若要按下给反馈,则将执行的语句写入两段消抖之间。

矩阵按键

#include <REGX52.H>
#include "Delay.h"

sfr P4 = 0xC0;

sbit C1 = P4^4;
sbit C2 = P4^2;
sbit C3 = P3^5;
sbit C4 = P3^4;

sbit R1 = P3^0;
sbit R2 = P3^1;
sbit R3 = P3^2;
sbit R4 = P3^3;

/**
  *@brief 矩阵按键返回键码
  *@param 无
  *@retval 返回键码,但是是从1-16,而非板子上的4-19.
  */
unsigned char MatrixKey(){
	unsigned char KeyNumReturn;
	
	R1 = 0;R2 = 1;R3 = 1;R4 = 1;
	C1 = 1;C2 = 1;C3 = 1;C4 = 1;
	if(C1 == 0){Delay(20);while(C1 == 0);Delay(20);KeyNumReturn = 4;}
	if(C2 == 0){Delay(20);while(C2 == 0);Delay(20);KeyNumReturn = 8;}
	if(C3 == 0){Delay(20);while(C3 == 0);Delay(20);KeyNumReturn = 12;}
	if(C4 == 0){Delay(20);while(C4 == 0);Delay(20);KeyNumReturn = 16;}
	
	R1 = 1;R2 = 0;R3 = 1;R4 = 1;
	C1 = 1;C2 = 1;C3 = 1;C4 = 1;
	if(C1 == 0){Delay(20);while(C1 == 0);Delay(20);KeyNumReturn = 3;}
	if(C2 == 0){Delay(20);while(C2 == 0);Delay(20);KeyNumReturn = 7;}
	if(C3 == 0){Delay(20);while(C3 == 0);Delay(20);KeyNumReturn = 11;}
	if(C4 == 0){Delay(20);while(C4 == 0);Delay(20);KeyNumReturn = 15;}
	
	R1 = 1;R2 = 1;R3 = 0;R4 = 1;
	C1 = 1;C2 = 1;C3 = 1;C4 = 1;
	if(C1 == 0){Delay(20);while(C1 == 0);Delay(20);KeyNumReturn = 2;}
	if(C2 == 0){Delay(20);while(C2 == 0);Delay(20);KeyNumReturn = 6;}
	if(C3 == 0){Delay(20);while(C3 == 0);Delay(20);KeyNumReturn = 10;}
	if(C4 == 0){Delay(20);while(C4 == 0);Delay(20);KeyNumReturn = 14;}
	
	R1 = 1;R2 = 1;R3 = 1;R4 = 0;
	C1 = 1;C2 = 1;C3 = 1;C4 = 1;
	if(C1 == 0){Delay(20);while(C1 == 0);Delay(20);KeyNumReturn = 1;}
	if(C2 == 0){Delay(20);while(C2 == 0);Delay(20);KeyNumReturn = 5;}
	if(C3 == 0){Delay(20);while(C3 == 0);Delay(20);KeyNumReturn = 9;}
	if(C4 == 0){Delay(20);while(C4 == 0);Delay(20);KeyNumReturn = 13;}
	
	
	return KeyNumReturn;
}

中断系统与外部中断服务

一般来说,51单片机有5个中断源(忽略定时/计数器2),分2个优先级,这个5个中断源按照自然优先级从高到低依次为:
在这里插入图片描述

void Int0_Routine(void) 	interrupt 0;
void Timer0_Rountine(void) 	interrupt 1;
void Int1_Routine(void) 	interrupt 2;
void Timer1_Rountine(void) 	interrupt 3;
void UART1_Routine(void) 	interrupt 4;//串口中断
void ADC_Routine(void) 		interrupt 5;
void LVD_Routine(void) 		interrupt 6;
void PCA_Routine(void) 		interrupt 7;
void UART2_Routine(void) 	interrupt 8;
void SPI_Routine(void) 		interrupt 9;
void Int2_Routine(void) 	interrupt 10;
void Int3_Routine(void) 	interrupt 11;
void Timer2_Routine(void) 	interrupt 12;
void Int4_Routine(void) 	interrupt 16;

在这里插入图片描述

每个中断源都对应着一个固定的入口地址,也就是中断向量,它们依次是:
在这里插入图片描述

中断服务函数编写

一般情况下,中断的处理函数有两个,其一为中断初始化函数,其二为中断服务函数。初始化函数就是一个普通的函数,而中断服务函数却有特殊的格式要求:
<1> 中断函数没有返回值,也不能带参数
<2> 函数名后面要跟一个关键字interrupt,说明这是一个中断服务函数。
<3> 在关键字interrupt后面要跟上中断号,说明这个中断服务函数是为哪个中断服务的。

void Init_INT0(){
	IT0 = 1;
	EX0 = 1;
	EA = 1;
}
void ServiceINT0() interrupt 0{
	L8 = 0;
	Delay(5000);
	Delay(5000);
	Delay(5000);
	Delay(5000);
	L8 = 1;
}

这个功能是在L1闪烁的时候,L8插进来亮一会儿。

中断演示


中断内事情要少做,因此,把任务放在中断外面,中断函数内部就转换一个自定义的状态就好了。

unsigned char TEST;
void ServiceINT0() interrupt 0{
	TEST++;
}
void main()
{
	Init_INT0();
	InitHC138(4);
	while(1){
		Working();
		Delay(1);
		if(TEST){CheckInterrupt();TEST = 0;}
	}
}

定时器

寄存器初始赋值:TH0 = (65535 - 脉冲数) / 256;
在这里插入图片描述
选择定时器0,因此高四位全部0;
用TR0来启动定时器,GATE为0;
选择定时功能,C/T为0;
选择16为,M1 M0为01;
因此给寄存器TMOD = 0x01;
定时器的中段初始化函数也可以在烧录软件种获取,但是所得代码还需要添加:

ET0 = 1;
EA = 1;

来打开。
示例:

void Timer0_Init(void)		//10毫秒@12.000MHz
{
	AUXR &= 0x7F;			//定时器时钟12T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0xF0;				//设置定时初始值
	TH0 = 0xD8;				//设置定时初始值
	
	ET0 = 1;
	EA = 1;
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
}

其服务函数为:

void ServiceTimer0()interrupt 1{
	
	count++;
	
	if(count%10 == 0){
		L1 = ~L1;
	}
	if(count == 100){
		L8 = ~L8;
		count = 0;
	}
}

展示:

中断演示

定时器综合应用

在这里插入图片描述

#include <STC15F2K60S2.H>
#include "Delay.h"
#include "InitHC138.h"

sbit S4 = P3^3;
sbit S5 = P3^2;

unsigned char SEG[18] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e,0xbf};
unsigned char Pos[9] = {0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,};
unsigned char t_min,t_sec,t_50ms;//分、秒、50毫秒

unsigned char Check = 0;

void Timer0_Init(void)		//50毫秒@11.0592MHz
{
	AUXR &= 0x7F;			//定时器时钟12T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0x00;				//设置定时初始值
	TH0 = 0x4C;				//设置定时初始值
	
	EA = 1;
	ET0 = 1;
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
}

ShowSMG(unsigned char pos,dat){
	OutPutP0(6,Pos[pos]);
	OutPutP0(7,SEG[dat]);
	Delay(1);
}

void Display(){
	ShowSMG(7,t_50ms/10);ShowSMG(8,t_50ms%10);
	ShowSMG(6,16);
	ShowSMG(4,t_sec/10);ShowSMG(5,t_sec%10);
	ShowSMG(3,16);
	ShowSMG(1,t_min/10);ShowSMG(2,t_min%10);
}

void ScanKey(){
	if(S4 == 0){//S4用于暂停、启动
		Delay(10);
		if(S4 == 0){
			TR0 = ~TR0;
			while(S4 == 0){
				Display();
			}
		}
	}
	if(S5 == 0){//S5用于清零
		if(S5 == 0){
			t_min = 0;
			t_sec = 0;
			t_50ms= 0;
		}
	}
}

void main()
{
	Timer0_Init();
	while(1){
		ScanKey();
		Display();
	}
}

void ServiceTimer0()interrupt 1{
	t_50ms++;
	if(t_50ms == 20){
		t_sec++;
		t_50ms = 0;
	}
	if(t_sec == 60){
		t_min++;
		t_sec = 0;
	}
	if(t_min == 90){
		t_min = 0;
	}
}

PWM脉宽调制信号

用途:直流电机调速 灯泡亮度 声音大小……
原理:把一个周期的PWM脉冲(比如100Hz,10000微秒)分成几份(比如100份,100微秒),让其中的50份置低电平,剩下的50份置高电平,这样在一个周期里面的LED灯就会呈现只有一半的亮度。
在这里插入图片描述

操作

来写一个题目:
在这里插入图片描述

据此写出一个100微秒的定时器,TR0不急着打开:
定义一个count来数产生中断的次数,一次中断代表100微秒,pwm_duty用来标记转换电平所在时刻,比如60,就是在6000微秒后转换电平。
每到1个周期后把count清零,并且让灯重新亮回去

unsigned char count = 0;
unsigned char pwm_duty = 0;
void Timer0_Init(void)		//100微秒@12.000MHz
{
	AUXR &= 0x7F;			//定时器时钟12T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0x9C;				//设置定时初始值
	TH0 = 0xFF;				//设置定时初始值
	EA = 1;
	ET0 =1;
	TF0 = 0;				//清除TF0标志
}

void ServiceTimer0 ()interrupt 1{
	count++;
	if(count == pwm_duty){
		L1 = 1;
	}else if(count == 100){
		L1 = 0;
		count = 0;
	}
}

按键相关:

sbit S7 = P3^0;

unsigned char stat = 0;//熄灭状态
void ScanKeys(){
	if(S7 == 0){
		Delay(5);
		if(S7 == 0){
			switch(stat){
				case 0:L1 = 0;TR0 = 1;pwm_duty = 10;stat = 1;break;
				case 1:L1 = 0;pwm_duty = 50;stat = 2;break;
				case 2:L1 = 0;pwm_duty = 90;stat = 3;break;
				case 3:L1 = 1;TR0 = 0;stat = 0;break;
			}
			while(S7 == 0);
		}
	}
}

串口通信

波特率:串口每秒传输的位数
SMOD = 0时,TH1=
在这里插入图片描述
在这里插入图片描述

基本操作

初始化可以在烧录程序上获取,但是要记得中段和总中段都要打开:
ES = 1;
EA = 1;

void Uart1_Init(void)	//9600bps@12.000MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR |= 0x40;		//定时器时钟1T模式
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x20;		//设置定时器模式
	TL1 = 0xD9;			//设置定时初始值
	TH1 = 0xD9;			//设置定时重载值
	ET1 = 0;			//禁止定时器中断
	EA = 1;
	ES = 1;
	TR1 = 1;			//定时器1开始计时
}

有两种发送完成的方式,可以参考这个文章:
单片机的串口通信—查询和中断

上位机发送数据给单片机执行

先自定义一个命令变量:

unsigned char Command = 0x00;

这里采用的是中断方式来接收数据:

void ServiceUART()interrupt 4{
	if(RI == 1){
		Command = SBUF;
		RI = 0;
	}
}

然后把SBUF寄存器中的值提取出来并存在Command里面。
然后做一个将值转换成命令的程序:

void Working(){
	if(Command != 0x00){
		switch(Command & 0xf0){
			case 0xa0:P0 = ((P0 | 0x0f)&(~Command | 0xf0));Command = 0x00;break;
			case 0xb0:P0 = ((P0 | 0xf0)&(~Command<<4 | 0x0f));Command = 0x00;break;
			case 0xc0:SendString("GoGoGo!!!");Command = 0x00;break;
		}
	}
}

主函数如下:

void main()
{
	InitSystem();
	Uart1_Init();
	SendString("HelloWorld!\0");
	while(1){
		Working();
	}
}

效果如下:
在这里插入图片描述
在这里插入图片描述

传输字符串

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

void SendString(unsigned char *str){
	while(*str != '\0'){
		SendByte(*str++);
	}
}

BCD - DEC

BCD码转十进制:DEC = BCD / 16 * 10 + BCD % 16 ;

详解:先将BCD的前一位提出BCD/16,再提出BCD后一位BCD%16。最后将两位数整合转换成十进制。多位BCD码同理。

十进制转BCD码:BCD = DEC / 10 * 16 + DEC % 10 ;同上分别提出高低两位,做BCD转换。
————————————————

SendByte((hour / 10 << 4) | (hour % 10));
SendByte((min / 10 << 4) | (min % 10));
SendByte((sec / 10 << 4) | (sec % 10));

存储器映射扩展技术

引入头文件:absacc.h
将J13跳线帽短接1-2后,可以点亮LED灯只用一行代码就可以实现

XBYTE[0xc000] = 0xfe;

但是在蓝桥杯单片机上用MM模式的话,P36的引脚会被占用,在使用矩阵键盘时会受影响。

单总线温度传感器DS18B20

温度转换与读取流程

微处理器读取单个DS18B20的温度数据,可参考以下步骤:
【1】DS18B20复位。
【2】写入字节0xCC,跳过ROM指令。
【3】写入字节0x44,开始温度转换。
【4】延时700~900ms。
【5】DS18B20复位。
【6】写入字节0xCC,跳过ROM指令。
【7】写入字节0xBE,读取高速暂存器。
【8】读取暂存器的第0字节,即温度数据的LSB。
【9】读取暂存器的第1字节,即温度数据的MSB。
【10】 DS18B20复位。,表示读取数据结束。
【11】将LSB和MSB整合成为一个16位数据。
【12】判断读取结果的符号,进行正负温度的数据处理。

Ds18b20数据处理

DS18B20以16位带符号位扩展的二进制***补码***形式读出。
低4位为小数部分,中间7位为整数部分。
高5位为扩展符号位,即BIT15~BIT11为00000,读出的数据为正温度,若为11111,则为负温度。在应用开发中,首先要对读出的温度数据的符号位进行判断,再根据正负温度的不同,进行相应的处理。
在这里插入图片描述
DS18B20的分辨率为0.0625。
读出数据为正温度时,将LSB和MSB整合成的16位整数,直接乘以0.0625即可。
读出数据为负温度时,则需要将LSB和MSB整合成的16位整数,取反加1后,再乘以0.0625,因为温度数据是以补码形式表示的。
例如:
读出结果为00A2H,温度值 = 162×0.0625 = 10.125 摄氏度。
读出结果为FF5EH,取反加1就是00A2H,温度值则为 -10.125 摄氏度。
上电复位的时候,温度寄存器中的值为0x0550,+85摄氏度

操作流程

在这里插入图片描述
官方给的底层驱动代码(2023)没有定义总线的地址,加上:

sbit DQ = P1^4;

根据模块化编程的格式,编辑好底层驱动代码的头文件并且在主程序中引用。

#ifndef __ONEWIRE_H
#define __ONEWIRE_H

void Delay_OneWire(unsigned int t);
void Write_DS18B20(unsigned char dat);
unsigned char Read_DS18B20(void);
bit init_ds18b20(void);

#endif

根据上面提到的操作步骤,引用合适的函数编写读取温度的代码。

void Read_DS18B20_TEMP(){
	unsigned char LSB,MSB;
	
	init_ds18b20();//【1】DS18B20复位。
	Write_DS18B20(0xcc);//【2】写入字节0xCC,跳过ROM指令。
	Write_DS18B20(0x44);//【3】写入字节0x44,开始温度转换。
	DelaySMG(700);//【4】延时700~900ms。
	init_ds18b20();//【5】DS18B20复位。
	Write_DS18B20(0xcc);//【6】写入字节0xCC,跳过ROM指令。
	Write_DS18B20(0xbe);//【7】写入字节0xBE,读取高速暂存器。

	LSB = Read_DS18B20();//【8】读取暂存器的第0字节,即温度数据的LSB。
	MSB = Read_DS18B20();//【9】读取暂存器的第1字节,即温度数据的MSB。
	
	init_ds18b20();//【10】 DS18B20复位。,表示读取数据结束。
	
	temp = 0x0000;
	temp = MSB;
	temp = (temp << 8) | LSB;//【11】将LSB和MSB整合成为一个16位数据。
	if((temp & 0xf800) == 0x0000){//【12】判断读取结果的符号,进行正负温度的数据处理。
		temp >>= 4;//把小数位去掉
		temp = temp * 10;//整数位乘十
		temp = (temp + (LSB & 0x0f)) * 0.625;//把小数位加回去,再乘分辨率
	}
}

如果发现呈现的是83.5度(有可能还有缺位数,3.5之类的),就要去改官方给的底层驱动代码。
我所用的原来的驱动的测试环境是12T,12MHz,将烧录软件的晶振频率修改,并将初始化函数的延时相关数据全部乘以10~12,需要慢慢调,我乘以十二比较清晰。

bit init_ds18b20(void)
{
  	bit initflag = 0;
  	
  	DQ = 1;
  	Delay_OneWire(144);
  	DQ = 0;
  	Delay_OneWire(960);
  	DQ = 1;
  	Delay_OneWire(120); 
    initflag = DQ;     
  	Delay_OneWire(60);
  
  	return initflag;
}

在这里插入图片描述

温度计

DS1302

基本信息

日历时钟寄存器
在这里插入图片描述1、秒寄存器(80H 和 81H)的位 7(CH)为时钟暂停标志。 CH为0时,时钟振荡停止; CH为1时,时钟开始运行。
2、控制寄存器(8EH和8FH)的位7(WP)为写保护位:
WP 为0时,可对任何的时钟或 RAM 寄存器进行写操作;
WP 为1时,禁止对任一寄存器进行写操作。

控制字格式与数据定义
在这里插入图片描述
都是用BCD码进行存储
BIT7:必须为1,若为0,则不能把数据写入 DS1302中。
BIT6:0表示存取日历时钟数据,1表示存取 RAM 数据;
BIT5~BIT1:表示操作单元的地址。
BIT0:0表示写操作,1表示读操作。

DS1302读寄存器和写寄存器的地址是不一样的。将读寄存器地址、写寄存器地址和日历时钟数据定义成三个数组。(提前定义可以直接引用)

//定义DS1302读操作的日历时钟存储器地址
unsigned char DS1302_ReadAddress[7] = {0x81,0x83,0x85,0x87,0x89,0x8b,0x8d};
//定义Ds1302写操作的日历时钟存储器地址
unsigned char DS1302_WriteAddress[7]= {0x80,0x82,0x84,0x86,0x88,0x8a,0x8c};
//定义DS1302日历时钟的7个配置参数:18年2月17日23时50分30秒
unsianed char TIME[7] =(0x30,0x50,0x23, 0x17, 0x02, 0x06, 0x18}:

SPI接口时序

在控制字指令输入后的下一个SCLK时钟信号的下降沿,数据从DS1302读出
在控制字指令输入后的下一个SCLK时钟信号的上升沿,数据被写入DS1302;
在这里插入图片描述

基本操作

创建头文件、核对引脚

#include <STC15F2K60S2.H>
#ifndef __DS1302_H
#define __DS1302_H

sbit SCK = P1^7;		
sbit SDA = P2^3;		
sbit RST = P1^3; 

void Write_Ds1302(unsigned  char temp);
void Write_Ds1302_Byte( unsigned char address,unsigned char dat );
unsigned char Read_Ds1302_Byte ( unsigned char address );

#endif

初始化以及显示函数

void DS1302_Config(){
	char i;
	Write_Ds1302_Byte(0x8e,0x00);//解锁,使得数据可以被写入
	for(i = 0;i < 7;i++){
		Write_Ds1302_Byte(Write_DS1302_adrr[i],time[i]);
	}
	Write_Ds1302_Byte(0x8e,0x80);//恢复保护
}

void Read_DS1302_Timer(){
	char i;
	for(i = 0;i < 7;i++){
		time[i] = Read_Ds1302_Byte(Read_DS1302_adrr[i]);
	}
}

void ShowDate(){
	ShowSMG(1,Seg_Table[(time[2] / 16)]);
	ShowSMG(2,Seg_Table[(time[2] % 16)]);
	
	ShowSMG(3,Seg_Table[16]);
	ShowSMG(4,Seg_Table[(time[1] / 16)]);
	ShowSMG(5,Seg_Table[(time[1] % 16)]);
	
	ShowSMG(6,Seg_Table[16]);
	ShowSMG(7,Seg_Table[(time[0] / 16)]);
	ShowSMG(8,Seg_Table[(time[0] % 16)]);
}

main函数部分

void main()
{
	DS1302_Config();
	while(1){
		Read_DS1302_Timer();
		ShowDate();
	}
}

NE555定时器与频率测量

在这里插入图片描述
由电阻和电容形成的RC振荡电路,左上方的滑动变阻器(在单片机主板的右下角“频率输出旋钮”)可以改变频率,其输出的信号经过NE555成为方波后可以引发计时器0的电平变化形成计数,因此可以写一个程序来采样、读取它的的频率。
也可以采取它的频率建立数学模型做一些控制输出的操作,比如灯泡亮灭、蜂鸣器音调等等。

操作

将J3处的SIGNAL和P34短接
变量定义:

unsigned int count_f =0;//实时计数
unsigned int dat_f = 0;//采取一秒的次数
unsigned char count = 0;//50ms*20=1s

定时器的初始化:

void TimerInit(void)		//50毫秒@12.000MHz
{
	TMOD = 0x07;	//设置定时器模式
	TL0 = 0xff;		//设置定时初值
	TH0 = 0xff;		//设置定时初值	
	TL1 = 0xB0;		//设置定时初值
	TH1 = 0x3C;		//设置定时初值	
	TF0 = 0;		//清除TF0标志
	TF1 = 0;		//清除TF1标志
	ET1 = 1;    
	ET0 = 1;
	TR0 = 1;		//计数器0开始工作
	TR1 = 1;		//定时器1开始计时	
	EA = 1;	
}

中断服务函数与显示模块:

void Timer0_Isr(void) interrupt 1
{
	count_f++;
}
void Timer1_Isr(void) interrupt 3
{
	count++;
	if(count == 20){
		dat_f = count_f;
		count = 0;
		count_f = 0;
	}
}

void ShowHz(){
	ShowSMG(1,Seg_Table[15]);
	ShowSMG(2,0xff);ShowSMG(3,0xff);
	if(dat_f > 9999){ShowSMG(4,Seg_Table[dat_f / 10000]);}
	if(dat_f > 999){ShowSMG(5,Seg_Table[(dat_f / 1000) % 10]);}
	if(dat_f > 99){ShowSMG(6,Seg_Table[(dat_f / 100) % 10]);}
	if(dat_f > 9){ShowSMG(7,Seg_Table[(dat_f / 10) % 10]);}
	ShowSMG(8,Seg_Table[dat_f % 10]);
}

主程序:

void main()
{
	TimerInit();
	while(1){
		ShowHz();
	}
}

展示:

555定时器与频率测量

PCF8591

用于数模转换、读取电压

基本信息

在这里插入图片描述
A0 A1 A2接地说明A0 = A1 = A2 = 0;
**SCL(时钟线)**连接引脚P20,**SDA(数据线)**连接引脚P21
如下图是芯片、光敏电阻以及滑动变阻器所在位置
在这里插入图片描述
AIN1 和 AIN3分别外接 光敏电阻 和 滑动变阻器 来采集数据
在这里插入图片描述
在这里插入图片描述

地址:前四位固定,A0 A1 A2均接地固定为0,只有最后一位可变用于读写
控制字如下图
在这里插入图片描述
第三第四位(从左往右)蓝桥杯单片机默认00
ANALOGUE OUTPUT ENABLE FLAG表示是否允许输出

iic(i²c)通信协议理论讲解

用两根线来进行通讯
SDA为数据线,SCL为时钟线
谁控制SCL谁就是主设备,反之就是从设备
时序在这里插入图片描述
开始信号:SCL为高电平时,SDA由低到高变化,开始传输数据。
结束信号:SCL为高电平时,SDA由高到低变化,结束传输数据。
应答信号:每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据。
在这里插入图片描述

应答信号为低电平的时候为有效应答(ACK) 表示成功接收到了该字节 在这里插入图片描述
应答信号为高电平时规定为非应答位(NACK) 一般表示字节没接收成功
在这里插入图片描述
每发送一个字节(8个bit)“在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据
应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答

在这里插入图片描述
发送数据把byt中的数据与上0x80,读取byt的第一位,如果为一,那就把SDA此时电平拉高,传输数据1,然后进入下一个循环,将byt的数据往左移一位,读取此时的byt的第一位(原先的第二位),进行相同操作,周而复始,将八位数据通过SDA的高低电平进行传输。
在这里插入图片描述
读取数据先让da向左移一位,但da初始是0x00,没变化,然后从SDA读取数据,如果SDA为就让da的第一位为1,周而复始,完成数据的采集,数据采集和数据接受是一个镜像的过程,这也是为什么在发送数据的时候进行按位与操作的时候采用0x80而不是0x01,否则数据就会反过来。

A/D转换:模拟量(Analogue signal)与数字量(Digital Signal)之间的转换。

代码编写

AD读取
1.I²C传输开始信号。
2.写PCF8591地址。
3.等待PCF8591回应。
4.读PCF8591
5.等待主机回应。
6.I²C传输结束。

unsigned char AD_READ(unsigned char addr){
	unsigned char temp;
	I2CStart();//1.I²C传输开始信号。
	I2CSendByte(0x90);//2.写PCF8591地址。
	I2CWaitAck();
	I2CSendByte(addr);//指向滑动变阻器0x43或者光敏电阻0x41
	I2CWaitAck();
	
	I2CStart();
	I2CSendByte(0x91);//4.读PCF8591
	I2CWaitAck();//5.等待主机回应。
	temp = I2CReceiveByte();//读取数据
	I2CSendByte(1);//非应答信号,为了告诉主机不再读取数据
	I2CStop();//I²C传输结束。
	
	return temp;
}

DA转换

1、I2C传输开始信号。
2、写PCF8591地址。
3、等待PCF8591回应。
4、写控制字节。
5、等待PCF8591回应。
6、写DAC的值。
7、等待PCF8591回应。
(6到7步可以一直循环进行,且DAC的值可以一直改变,只要没有重新12C开始信号,或者结束信号,DAC输出就一直是最后一个输出的值。)
8、12C传输结束。

void Da_Write(unsigned char addr){//addr代表数字量,比如255(5V)
	I2CStart();//1、I2C传输开始信号。
	I2CSendByte(0x90);//2、写PCF8591地址。
	I2CWaitAck();
	I2CSendByte(0x41);//允许DA输出
	I2CWaitAck();
	I2CSendByte(addr);//写入模拟量数据
	I2CWaitAck();
	I2Cstop();
}

实际应用

初始状态晶体管无示数。
按下独立按键S4后切换到状态1,右边三位显示滑动变阻器电压示数(0~255),左边是三位显示电压示数,保留两位小数,中间用两个-隔开。
按下独立按键S4后切换到状态2,右边三位显示光敏电阻电压示数(0~255),左边是三位显示电压示数,保留两位小数,中间用两个-隔开。
按下S4循环切换状态
代码:

#include <STC15F2K60S2.H>
#include "Delay.h"
#include "InitHC138.h"
#include "iic.h"

unsigned char dat;
unsigned char Mode = 0;
unsigned int tmp = 0;

code unsigned char Seg_Table[17] = 
{
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
0xbf  //-
};

unsigned char AD_READ(unsigned char addr){
	unsigned char temp;
	I2CStart();
	I2CSendByte(0x90);
	I2CWaitAck();
	I2CSendByte(addr);
	I2CWaitAck();
	I2CStart();
	I2CSendByte(0x91);
	I2CWaitAck();
	temp = I2CReceiveByte();
	I2CSendByte(1);
	I2CStop();
	
	return temp;
}

void DA_WRITE(unsigned char addr){
	I2CStart();
	I2CSendByte(0x90);
	I2CWaitAck();
	I2CSendByte(0x41);
	I2CWaitAck();
	I2CSendByte(addr);
	I2CWaitAck();
	I2CStop();
}

void Showdat1(){
	dat = AD_READ(0x43);
	dat = AD_READ(0x43);
	dat = AD_READ(0x43);
	ShowSMG(8,Seg_Table[dat % 10]);
	ShowSMG(7,Seg_Table[dat / 10 % 10]);
	ShowSMG(6,Seg_Table[dat / 100 % 10]);
	tmp = dat * 100 / 51;
	ShowSMG(5,Seg_Table[16]);
	ShowSMG(4,Seg_Table[16]);
	ShowSMG(3,Seg_Table[tmp % 10]);
	ShowSMG(2,Seg_Table[(tmp / 10 % 10)]);
	ShowSMG(1,Seg_Table[tmp / 100 % 10] & 0x7f);
}
void Showdat2(){
	dat = AD_READ(0x41);
	dat = AD_READ(0x41);
	dat = AD_READ(0x41);
	ShowSMG(8,Seg_Table[dat % 10]);
	ShowSMG(7,Seg_Table[dat / 10 % 10]);
	ShowSMG(6,Seg_Table[dat / 100 % 10]);
	tmp = dat * 100 / 51;
	ShowSMG(5,Seg_Table[16]);
	ShowSMG(4,Seg_Table[16]);
	ShowSMG(3,Seg_Table[tmp % 10]);
	ShowSMG(2,Seg_Table[tmp / 10 % 10]);
	ShowSMG(1,Seg_Table[tmp / 100 % 10] & 0x7f);
}

void ScanKey(){
	if (P33 == 0){
		Delay(5);
		while(P33 == 0){
			ShowSMG(8,Seg_Table[dat % 10]);
			ShowSMG(7,Seg_Table[dat / 10 % 10]);
			ShowSMG(6,Seg_Table[dat / 100 % 10]);
			ShowSMG(5,Seg_Table[16]);
			ShowSMG(4,Seg_Table[16]);
			ShowSMG(3,Seg_Table[tmp % 10]);
			ShowSMG(2,Seg_Table[(tmp / 10 % 10)]);
			ShowSMG(1,Seg_Table[tmp / 100 % 10] & 0x7f);
		}
		Mode++;
		Mode = Mode % 3;
	}
}

void main(){
	InitSysAll();
	while(1){
		ScanKey();
		if(Mode == 1){
			Showdat1();
		}else if(Mode == 2){
			Showdat2();
		}else if(Mode == 0){
			ShowSMG(3,0xff);
			ShowSMG(2,0xff);
			ShowSMG(1,0xff);
		}
		
		DA_WRITE(255);
	}
}

展示:

PCF8591

AT24C02存储器

在这里插入图片描述

基本信息

02代表2K的内存大小,大概256字节。这就是一个内存卡
A0~A2器件地址,均接地,均为0.
SDL数据线 SCL时钟线。
WP接地不使能。
在这里插入图片描述

写入方式

字节写

void Write_24C02_Byte(unsigned char addr, unsigned char dat)
{
  IIC_Start();          //起始信号
  IIC_SendByte(0xa0);    //EEPROM的写设备地址
  IIC_WaitAck();        //等待从机应答
  IIC_SendByte(addr);    //内存单元地址
  IIC_WaitAck();        //等待从机应答
  IIC_SendByte(dat);    //内存写入数据
  IIC_WaitAck();        //等待从机应答
  IIC_Stop();            //停止信号
}

页写入

//字符数组;以八位倍数的地址表示哪一页去写地址;写多少数据(不要大于8) 
void Write_24C02_Page(unsigned char *EEPROM_String, unsigned char addr,unsigned char num)
{
	  I2CStart();//发送开启信号
	  I2CSendByte(0xa0);    //EEPROM的写设备地址
	  I2CWaitAck();        //等待从机应答
	  
	  I2CSendByte(addr);    //写入要存储的数据地址
	  I2CWaitAck();        //等待从机应答
	  
	  while(num--)
	  {
	  	I2CSendByte(*EEPROM_String++);//将要的信息写入
	  	I2CWaitAck();        //等待从机应答
	  	I2C_Delay(200);
	  }
	  I2CStop();            //停止信号
}

读取方式

任意地址读取

unsigned char Read_24C02(unsigned char addr)
{
  unsigned char tmp;
  //首先,进行一个伪写操作
  IIC_Start();          //起始信号
  IIC_SendByte(0xa0);    //EEPROM的写设备地址
  IIC_WaitAck();        //等待从机应答
  IIC_SendByte(addr);    //内存单元地址
  IIC_WaitAck();        //等待从机应答
  //然后,开始字节读操作
  IIC_Start();          //起始信号
  IIC_SendByte(0xa1);    //EEPROM的读设备地址
  IIC_WaitAck();        //等待从机应答
  tmp = IIC_RecByte();  //读取内存中的数据
  IIC_SendAck(1);        //产生非应答信号
  IIC_Stop();            //停止信号
  return tmp;
}

连续顺序读取

void Read_24C02_Page(unsigned char *Read_EEPROM_String, unsigned char addr,unsigned char num)
{
	  I2CStart();//发送开启信号
	  I2CSendByte(0xa0);    //EEPROM的写设备地址
	  I2CWaitAck();        //等待从机应答
	  
	  I2CSendByte(addr);    //写入要存储的数据地址
	  I2CWaitAck();        //等待从机应答
	  
	  while(num--)
	  {
	  	*Read_EEPROM_String++ = I2CReceiveByte();//将要的信息写入
	  	if(num)I2CSendAck(0);发送应答
	  	else I2CSendAck(1);不应答
	  }
	  I2CStop();            //停止信号
}

应用

我用字节写的方式写一段应用。

短按独立按键S4切换编辑数据,长按S4切换编辑、写入模式。
按S5切换数据位数
按S6给所在位数加1
中间显示编辑位数,1代表十位,2代表个位。
1和7位置的晶体管代表正在编辑的数字

#include <STC15F2K60S2.H>
#include "Delay.h"
#include "InitHC138.h"
#include "iic.h"

unsigned char DataPosi = 0;//数据存储位置
unsigned char EditPosi = 0;//编辑位置

//用二维数组来存储数字,[0][0],代表要写入的第一个数据的十位数
unsigned char Dat[2][2] = {0};
unsigned char Mode = 0;//模式编号,0为编辑(写入)模式,1为读取模式
unsigned char Key_Stat = 0;//标记S4的下按与否
unsigned char count = 0;//长按计时
//***************读写驱动代码
void Write_24C02_Byte(unsigned char addr, unsigned char dat)
{
  I2CStart();          //起始信号
  I2CSendByte(0xa0);    //EEPROM的写设备地址
  I2CWaitAck();        //等待从机应答
  I2CSendByte(addr);    //内存单元地址
  I2CWaitAck();        //等待从机应答
  I2CSendByte(dat);    //内存写入数据
  I2CWaitAck();        //等待从机应答
  I2CStop();            //停止信号
}

unsigned char Read_24C02_Byte(unsigned char addr)
{
  unsigned char tmp;
  //首先,进行一个伪写操作
  I2CStart();          //起始信号
  I2CSendByte(0xa0);    //EEPROM的写设备地址
  I2CWaitAck();        //等待从机应答
  I2CSendByte(addr);    //内存单元地址
  I2CWaitAck();        //等待从机应答
  //然后,开始字节读操作
	I2CStart();          //起始信号
  I2CSendByte(0xa1);    //EEPROM的读设备地址
  I2CWaitAck();        //等待从机应答
  tmp = I2CReceiveByte();  //读取内存中的数据
  I2CSendAck(1);        //产生非应答信号
  I2CStop();            //停止信号
  return tmp;
}
//***************共阳晶体管段码
code unsigned char Seg_Table[17] = 
{
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
0xbf  //-
};

//***************显示函数
void ShowData(){
	ShowSMG(1,Seg_Table[Dat[0][0]]);
	ShowSMG(2,Seg_Table[Dat[0][1]]);
	
	ShowSMG(7,Seg_Table[Dat[1][0]]);
	ShowSMG(8,Seg_Table[Dat[1][1]]);
}

//***************模式扫描函数
void ScanMode(){
	if(Mode == 1){//读取然后显示
		Dat[0][0] = Read_24C02_Byte(0x00) / 10;
		Dat[0][1] = Read_24C02_Byte(0x00) % 10;
		Dat[1][0] = Read_24C02_Byte(0x01) / 10;
		Dat[1][1] = Read_24C02_Byte(0x01) % 10;
		ShowData();
	}else if(Mode == 0){//根据不同情况显示编辑页面
		ShowData();
		switch(DataPosi){
			case 0:ShowSMG(3,Seg_Table[16]);break;
			case 1:ShowSMG(6,Seg_Table[16]);break;
		}
		switch(EditPosi){
			case 0:ShowSMG(4,Seg_Table[0]);ShowSMG(5,Seg_Table[1]);break;
			case 1:ShowSMG(4,Seg_Table[0]);ShowSMG(5,Seg_Table[2]);break;
		}
	}
}
//***************按键扫描模块
void ScanKey(){
	if(P33 == 0){
		Delay(5);
		while(P33 ==0){
			ShowData();
			Key_Stat = 1;
		}
		Key_Stat = 0;
		if(count <= 35){
			EditPosi = 0;
			DataPosi++;
			DataPosi = DataPosi % 2;
		}else if(count > 35){
			Mode++;
			Mode %= 2;
		}
	}
	if(P32 == 0){
		Delay(5);
		while(P32 == 0){
			ShowData();
		}
		EditPosi++;
		EditPosi %= 2;
	}
	if(P31 == 0){
		Delay(5);
		while(P31 == 0){
			ShowData();
		}
		Dat[DataPosi][EditPosi]++;
		Dat[DataPosi][EditPosi] %= 9;
	}
	if(P30 == 0){
		Delay(5);
		while(P30 == 0){
			ShowData();
			Write_24C02_Byte((0x00 + DataPosi),(Dat[DataPosi][0] * 10) + Dat[DataPosi][1]);
		}
		
	}
}
//***************定时器与中断服务函数,用于长短按检测
void Timer0_Isr(void) interrupt 1
{
	if(Key_Stat == 1){
		count++;
	}
}

void Timer0_Init(void)		//10毫秒@12.000MHz
{
	AUXR &= 0x7F;			//定时器时钟12T模式
	TMOD &= 0xF0;			//设置定时器模式
	TL0 = 0xF0;				//设置定时初始值
	TH0 = 0xD8;				//设置定时初始值
	TF0 = 0;				//清除TF0标志
	TR0 = 1;				//定时器0开始计时
	ET0 = 1;				//使能定时器0中断
	EA = 1;
}
//***************主函数
void main()
{
	Timer0_Init();
	while(1){
		ShowData();
		ScanKey();
		ScanMode();
	}
}

展示:

AT24C02

超声波

基本信息

在这里插入图片描述
N A1为发送部分
CX20106A为接收部分(N B1)
在这里插入图片描述
使用超声波模块需要用跳线帽把短接1-3和2-4(板子上也有说明)
在这里插入图片描述
TX= P1^0:
RX= P1^1;
当TX=1时为成功发送信号
当TX=0时为发送信号失败
当RX=0时为成功接受信号
当RX=1时为接受信号失败

声速V=332+0.607t(m/s)
距离L = V * T / 2(m)
实现步骤
实现步骤
1-产生8个40KHz的超声波信号,通过TX引脚发射出去,
2-启动定时器,计算计数脉冲。
3-等待超声波信号返回,如果接收到反射回来的信号,RX引脚变为低电平。
4-停止定时器,读取脉冲个数,即获得时间T。
5.-根据公式,进行距离的计算

代码编写

方波是占空比为50%的矩形波,它是一种非正弦周期函数的波形。方波包括有低电平为零的方波与低电平为负的方波。本文中的方波均为低电平为负的方波。

void Ut_Wave_Init()//超声波初始化函数 产生8个40Mhz的方波信号
{
	unsigned char i;
	for(i = 0;i < 8;i++)
	{
		TX = 1;
		Delay(12);
		TX = 0;
		Delay(12);
	}
}

读取超声波

void Ut_Wave_Read()
{
	TMOD &= 0x0F;			//设置定时器模式
	TL1 = 0x00;				//设置定时初始值
	TH1 = 0x00;				//设置定时初始值
	
	Ut_Wave_Init();
	
	TR1 = 1;				//定时器1开始计时
	while((RX == 1) && (TF1 == 0));
	TR1 = 0;				//定时器1停止计时
	
	if(TF1 == 0){//定时器没有溢出,没有超出测量范围
		Time = TH1 << 8 | TL1;
		Distance = Time * 17 / 1000;//给出距离
	}else{
		TF1 = 0;//清楚溢出标志位
		Distance = 999;
	}
}

显示模块

void ShowDictance(){
	if(Distance == 999){
		ShowSMG(8,Seg_Table[16]);
		ShowSMG(7,Seg_Table[16]);
		ShowSMG(6,Seg_Table[16]);
	}else {
		ShowSMG(8,Seg_Table[Distance % 10]);
		ShowSMG(7,Seg_Table[Distance / 10 % 10]);
		ShowSMG(6,Seg_Table[Distance / 100 % 10]);
	}
	
}

主程序

void main()
{
	InitSysAll();
	while(1){
		Ut_Wave_Read();
		ShowDictance();
	}
}

展示

超声波

  • 13
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值