C51单片机

单片机

一丶 C51单片机的准备

名言:发现问题,定位问题,之后去处理问题。

通过C51 学习一些外设,标准协议和非标准的协议 以项目的形式进行学习。

  • C51 对C语言的要求较低。
  • STM32 需要C语言要求 指针+结构体

1.开发环境的安装 Keil

  • 1.以管理员的身份运行
  • 2.点击File(文件),点击License management(管理认证) 打开页面
  • 3.在第一个single-user Licence (单用户认证)找到Cid(competer id ) 复制该框里的内容到注册机
  • 4.将从注册机里面的得到的返回码 填入 LIC这个空里面

2.创建一个Keil工程

想要控制单片机,需要用到Keil工程的方式进行组织。

  • 1.创建C51单片机文件夹 -——-在该文件内创建一个Template(模板)文件夹。
  • 2.在Template文件夹内 创建一个.c文件
    注意 点击查看--------文件---------文件拓展名。去掉.txt 变成.c文件。
  • 3.进入Keil–在工具栏里找到Project(项目)------New uvison(新建一个集成开发环境)放到 与第二个步骤相同的位置里面
  • 4.保存到Template文件夹里面---------命名为muban
  • 5.选择目标开发板所适合的参数
    1.选择Altium点击加号 2.选择架构STC89C51 会自动拷贝(STARTUP.A51)启动代码.“解释启动代码,即单片机上电以后会执行一段汇编”
    该目标单片机是STC公司,但不存在这个。选择Altium来代替,点击加号。
    STC89C51,后面的89C51是单片机的架构。
    1. 在右边的框里得到一个 黄色的Target1------点击前面的加号,得到一个黄色的Source Group1
  • 2.右键添加 已存在 的main.c文件到Source Group1里面

3.main.c写入C语言

C语言-------转成单片机可认识的hex

    1. C语言必须转变成 单片机可认识的执行文件。
    1. 单片机只认识01序列化的2进制。
      通过配置工具栏里的Target options------Output------选择HEX file-----实现将C语言转换成单片机可认识的二进制文件。
#include "reg52.h"

sbit led1 = P3^7;蓝灯
sbit led2 = P3^6;黄灯

void main()
{
    led1 = 0;
    led2 = 0;
   while(1);
}

4.Hex程序烧录到单片机

生成的hex文件烧路到单片机内,思路;将电脑磁盘上已存在的hex文件下发到单片机。需要借助USB转串口的驱动。

  • @第一步,完成单片机 与电脑的 连接
    1.USB转串口驱动—安装ch340驱动。
    2.接入板子,打开此电脑----打开属性—打开设备管理器,查看端口COM,查看是否成功。
    1,2步此时成功完成了单片机与电脑的连接。
  • @第二步,通过STC-ISP(STC互联网服务供应商)把代码烧录到单片机里。
    安装STC-ISP软件。
  • 1.打开STC-ISP软件,选择相应的单片机型号。
  • 2.选择相应的端口号COM。
  • 3.打开程序文件,注意文件的时间和文件的名称,防止打开不是想要打开的文件
    @第三步,动手烧录代码。
  • 1.先接通电源
  • 2.单片机按键关闭,看到红灯微微的亮起
  • 3.点击 下载/编程即可
  • 4.单片机按键打开

总结

完成了工程的创建-------代码的编写-------代码的编译-----代码的烧写全部的流程。

二丶 什么是单片机

1.单片机

  • 单片机指的是那个黑色的芯片。
    单片机是一个集成电路芯片。CPU,RAM(内存),I/O口,中断处理系统,计时器等等集成在一个硅片上构成的小而完善的微型计算机系统。
  • 单片机开发板,基于单片机设计一些外接模块,以满足学习,运行需求。
  • 最小系统;晶振+电源 通过插针引出来了。
  • 区别在于高级点的C51有 按键 灯 针脚 数码管 ESP8266 显示屏
  • 基于普通的单片机:通过杜邦线+插针接入模块即可。方便做项目,便捷。

2.单片机芯片手册

手册不要从头看到尾巴,而是当成字典去查。

命名规则面试问

在这里插入图片描述

STC89C51RC
1.89 标号
2.C 电源供电
3.51/52(k) 程序的空间大小,程序跑起来所占的内存空间大小。
RAM 内存
40 工作频率
工作的温度 封装类型 管教数


单片机的特性

11.定时器
12.中断
13.通用异步串口(串行通信接口)
14.封装方式 一般使用LQFP44,比较小而方便。

在这里插入图片描述

1.时钟,用于定时器。
2.USB供电或者4节干电池供电
3.I/O空 通常使用P1~P4的端口,最好不使用P0口。(开漏输出,举一个例子。即当使用继电器的时候必须加上拉电阻)
4.ISP(在系统可编程)IAP(在应用可编程),无需专用编程器。通过串口就可烧录进去。
5.看门狗。狗必须隔几分钟喂一次固定的代码,否则狗会不停的叫。单片机放在幕盒里,当程序出错,无法人工手动启动。看门狗可以重新上电,每隔几分钟’喂一下狗,即喂的是固定的代码’。当程序出错了,没有运行这几行代码,看门狗狗就会叫,重新启动系统,使处于正常的状态。
6.51RC看门狗是默认不使用的。

单片机认知总结

  • 单片机 外节电路(插针,针脚 )I/O口(不使用P0口)
    单片机的串口有单独的引出 ,但也可以使用P3^0 和P3 ^1口
    Type接口
    USB转串口ch340驱动模块
  • 未来开发需要用到芯片手册 +以及单片机的电路图
  • 在电路原理图中 有相应的 按键 灯 标号

三丶单片机的思维发散

单片机做什么事

单片机 可以做的事情 I/O口的供电 和 串口的数据传输

  • 单片机编程 人通过写代码 告诉单片机他应该做的工作 给
    对应 情景单片机写纸条
  • HEX通过ISP下载到单片机,单片机重新上电执行程序。
    对应 情景单片机看到纸条,去做纸条上的事情
  • 单片机 可以做的事情 I/O口的供电,串口的数据传输
  • 数据传输的过程 ;传感器通过杜邦线传给 单片机引脚数据,单片机从自己的引脚中读取穿过来的数据。

单片机的寻址

寻址属于汇编的领域,目前不需要了解。

  • 单片机如何找到I/O口
    单片机的大脑通过 寻址找到I/O口,一些地址数据代表了某些I/O口,比如说头文件
    对于人眼看到的单片机上面写的P0^1口实物标号,在单片机头文件已经配好了

main.c 编译之后会存在一个reg52.h,单片机的I/O口实物的标号 已经配置好了。

sfr P0    = 0x80;
sfr P1    = 0x90;
sfr P2    = 0xA0;
sfr P3    = 0xB0;

四丶单片机的IO口和具体引脚

P0,P1,P2,P3,P4
尽量不使用P0口,因为当这个P0口接模块,获取数据的时候必须加上拉电阻。
尽量多使用P1,P2,P3.

P4口的访问

  • 一般没有P4,但对RC系列其他具有P4口访问时,需要添加P4的接口。
    传统头文件没有P4需要接里面。即在芯片手册里找到 P4相关的代码拷贝到reg52.h文件里。

在这里插入图片描述

P0.0口 到P0.6口 6个针脚
P1,P2,P3口的针脚数相同,都是7个针脚。

  • 如何找到I/O口
    通过sfr指令,用来直接描述硬件地址。“一组I/O‘起始地址中的数据。对应于代码里 sfr P0 = 0x80.

操作整个I/O口

非标准,标准C语言不认识sfr和sbit指令,故需要加 reg52.头文件
sfr P0 = 0x80.

操作I/O口对应的某个针脚

  • sbit 找到可寻址空间里的一个具体的位
  • 一个字节 = 8 位
    sbit
sbit EA = IE^7;
EA是处理中断管理寄存器,也存在很多位,可以把某个单独的位拿出来。
sbit ES = IE^4;
ES是串口中断,IE^4的第四位用于管理中断。对应于IE口的第4位。

I/O输入输出

该单片机共有5组输入 输出口。P3
访问每一组I/O口里面的某个具体的引脚。P3^7

  • I/O口的输入; 把外面的东西拿进来(都是对于单片机而言的)

  • I/O口的输出;把东西给外面
    模块上的OUT表示含义是 模块的向外界输出的输出口
    模块上的IN表示含义是 接收外界的数据的 输入口

  • C51无需配置输入输出口,C51单片机比粗暴,直接是大脑的逻辑思维即可,引脚的输入和输出的功能无需配置。但STM32需要配置。
    STM32配置的相关代码:

main()函数里定义一个变量。
io_data是一个变量,用于承接针脚传过来的数据(外界的数据)。
IOPORT是个针脚。

IOPORT理解为针脚,IOPORT做数值时为其他变量(io_data)赋值时,代表输入;IOPORT做变量被赋值时,代表输出。


- 1.IOPORT在等号右边,理解为 是数值。io_data被赋值,表示的含义是 单片机内被输入了消息。
- 2.IOPORT在等号左边,理解为 是变量,即针脚。针脚被赋值,表示的含义是 单片机通过针脚去影响外接电路。


sbit IFfire(IOPORT) = P1^0;
int main()
{
   int io_data;
   io_data = IFfire(IOPORT);
   if(io_data == 0){
    }
   IFfire(IOPORT)  = 1;   
  
   return 0;

}

五丶点亮第一个灯

1.编程实现LED闪烁

让LED闪烁,LED = 0;0是亮,1是灭。
高电平 1 是灭。
低电平 0 是亮。
单片机执行指令是需要消耗时间的,即通过执行 一些无关紧要的命令来达到延时的效果。

  • 步骤:1.亮 停留几秒(延时)
  • 2.灭 停留几秒(延时)

软件延时计算器

选择软件延时计时器,选择系统频率:11.0592。

同时_nop_()需要一个头文件。会执行 4*6*203下,为什么不能直接使用这个相乘代表的数:因为会产生越界的问题
即unsigned char 无法装那么多,即那么大的数无法装到这个里面,会越界。

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 43;
	j = 6;
	k = 203;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

LED1灯实现闪烁

#include "reg52.h"
#include  "intrins.h"
sbit LED1 = P3^6;

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 43;
	j = 6;
	k = 203;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

int main()
{
	  LED1 = 0;
	  Delay1000ms();
    LED1 = 1;
	  Delay1000ms();
	return 0;
}

2.单片机原理图(初中电路)

在这里插入图片描述
D4 是完整的串联电路,当单片机一上电,D4灯立马会亮。
D5与D6处于悬空,是否会亮取决于 通过代码把后面写成GND即可。
LED1代表一个标号,可在电路图中查找到这个标号。该处的LED1与芯片的LED1是相连的,即他们直接是导线相连。

针脚默认电平
  • 通过代码改变针脚的原本状态,将原来的状态该为GND即可(低电平)。原理默认情况下的针脚都是高电平。

当单片机上电以后,单片机的针脚默认是处于高电平。要想让该处的针脚 改为GDN,只需要一行代码即可。LED1对应的针脚是P3^7口,即只需要让P3 ^7口成为0即可。

点亮LED1和LED2
#include "reg52.h"
#include  "intrins.h"//_nop_()º¯ÊýÓÃ
sbit LED1 = P3^6;
sbit LED2 = P3^7;

int main()
{
	  LED1 = 0;
	  LED2  = 0;
	return 0;
}

3. 单片机里的 逻辑按键

在程序的世界里,我们通过逻辑来实现。
学习逻辑按键的 操作原理。

  • 电路接通是 一条线上不只是高电平,即一段是高电平(接5V),另一端是低电平(接地)。
    手动按下单片机的按键 --------对应引起的是 逻辑电平的改变--------即,检测是否if(KEY1 = =0)语句成立,成立则代表按键按下了。
  • 按键被用手按下 等价于 该语句成立 if(KEY1 == 0)
  • 在这里插入图片描述
    SW1是逻辑按键,当未按下时,整条电路是高电平。
    当按下按键时,KEY1通过按键SW2接地了GND处于低电平-。
  • 通过判断SW1按键是否被按下,可以通过检测KEY1的值是否被按下。

A.判断SW1是否被按下

结合上面的那个图里的KEY1与这个图里的KEY1来一起去看。这其实是同意个,可理解为他们之间通过导线相连的。
在这里插入图片描述

当SW1被按下时,与GND接通,KEY1表现为低电平,即KEY1的值为0;
当SW1未被按下时,与上拉电阻5V相连,即KEY1的值为1;

B1 查询法按键控制灯

目的; 通过现象显示出按键被按下。
当按下按键,那条路与END导通,故KEY1针脚为低电平,即值是0;只要检测当针脚上的数据是0就可以证明出按键被按下。
查询法是否被按下条件语句:key1(P2^1) == 0,通过LED1来显示结果。
当KEY1==0 KEY1是低电平的时候,证明相关的按键被按下。
同理当KEY2 ==0,证明相关的按键被按下。

实现的按下该针脚就是低电平,通过 LED1来知道该针脚是低电平。

C.一个按键

一个按键KEY1被按下 现象显示(通过LED显示)

按键被按下无现象显示,可以借助 LED的亮灭来 检测按键 是否被按下。

// 一个按键,只控制一栈灯亮。按下按键,灯亮。再次按下该按键,灯仍然亮。
#include "reg52.h"
sbit LED1 = P3^6;
sbit KEY1 = P2^1;

void main()
{
	  while(1){
			
     if(KEY1 == 0){
       LED1  = 0;
     }
	}

}
一个按键控制灯亮灭

按键被按下,或者不被按下,都通过一栈灯的 亮 灭 来显示。不要这样理解:一个按键控制灯亮灭,这样是为了好记。

  • 即按键被按下对应的KEY1的值始终是0低电平,
  • 第一次按下,此时KEY1 == 0,让灯亮。
  • 再一次被按下,此时KEY1== 0,让灯灭。

实现的功能是按键一 按一下 打开灯1,再按一下关闭灯1,而按键二 还是原来的功能;仅让灯1关闭。

该部分 重点注意!LED

  • 在使用翻转,需要先初始化这个值
    KEY1 ==0;
  • 后面的判断
    if(KEY1 == 0){
    LED1 = !LED1; //原来不用翻转法,仅会开灯LED1 = 0;
    }
#include "reg52.h"
#include "intrins.h"
sbit LED1 = P3^6;
sbit LED2 = P3^7;
sbit KEY1 = P2^1;
sbit KEY2 = P2^0;

void Delay50ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 3;
	j = 26;
	k = 223;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}


void main()
{
	 LED1 = 1;
	  while(1){//
			
     if(KEY1 == 0){ //°´¼ü±»°´Ï£¬KEY1ÓëGNDÁ¬½Ó£¬±íÏÖΪµÍµçƽ¡£
			 Delay50ms();//Èí¼þÏû¶¶
			 if(KEY1 == 0){
              LED1  = !LED1; //LED1 = 0;
			    }
    }
		 if(KEY2 == 0){//KEY2λÖÃÓëGND½Óͨ£¬±íÏÖΪµÍµçƽ£¬ÖµÎª0
			     if(KEY2 == 0){
						 Delay50ms();
              LED1 =1;
           }
      }
		 
	}

}

D.二个按键

第一个按键 让LED1的亮/灭,第二个按键 只让LED1的灭。

二个按键KEY1 KEY2按下的 现象显示(通过LED显示)

两个按键 用一栈灯来显示。
判断按键的现象,可以间接转化成 这个问题; 两个按键控制 一盏灯,一个按键相当于 开灯,一个按键相当于 灭灯。

即按键KEY1被按下,可以通过 LED亮 来显示。
即按键KEY2被按下,可以通过LED灭 来显示。

按键1控制LED1开灯,按键2控制LED2关灯

//两个不同的按键控制 一盏灯的亮灭
#include "reg52.h"
sbit LED1 = P3^6;
sbit LED2 = P3^7;
sbit KEY1 = P2^1;
sbit KEY2 = P2^0;
void main()
{
	  while(1){
			
     if(KEY1 == 0){
       LED1  = 0;
     }
		 if(KEY2 == 0){
       LED1 =1;
      }
		 
	}

}
二个按键控制一盏灯

按键一控制灯的亮灭,按键二只控制灯的灭

#include "reg52.h"
#include "intrins.h"
sbit LED1 = P3^6;
sbit LED2 = P3^7;
sbit KEY1 = P2^1;
sbit KEY2 = P2^0;

void Delay50ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 3;
	j = 26;
	k = 223;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}


void main()
{
	 LED1 = 1;
	  while(1){//
			
     if(KEY1 == 0){ //°´¼ü±»°´Ï£¬KEY1ÓëGNDÁ¬½Ó£¬±íÏÖΪµÍµçƽ¡£
			 Delay50ms();//Èí¼þÏû¶¶
			 if(KEY1 == 0){
              LED1  = !LED1; //LED1 = 0;
			    }
    }
		 if(KEY2 == 0){//KEY2λÖÃÓëGND½Óͨ£¬±íÏÖΪµÍµçƽ£¬ÖµÎª0
			     if(KEY2 == 0){
						 Delay50ms();
              LED1 =1;
           }
      }
		 
	}

}

B2中断法按键控制灯

E软件消抖

开发板,器件发生抖动会导致灯亮或者灯灭,震动一些会出现一些问题。
消除抖动的方法:延时函数(一般50ms即可)然后在进原来条件的判断下增加一个相同的判断

_nop_()函数,需要添加头文件#include "intrins.h"
Delay(50ms);
//推荐把下面的50ms换成50us
#include "reg52.h"
#include "intrins.h"
sbit LED1 = P3^6;
sbit LED2 = P3^7;
sbit KEY1 = P2^1;
sbit KEY2 = P2^0;

void Delay50ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 3;
	j = 26;
	k = 223;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}


void main()
{                  //这里的两个if是并列查询的。
	  while(1){//查询法检测按键是否被按下
			
     if(KEY1 == 0){ // KEY1位置和END接通,表现为低电平,值为0
			 Delay50ms();//软件消抖
			 if(KEY1 == 0){
                LED1  = 0;
			    }
    }
	 if(KEY2 == 0){//按键被按下,KEY2与GND接通,表现为低电平,值为0
       LED1 =1;
      }
		 
	}

}

F mark标记状态位控制LED

不希望在判断里面,去控制外设。借助标志位,用0和1来代表记住了某个I/O口的状态,在需要修改外设的地方再去修改。

  • 不着急让灯亮或者灯灭。而是记住让灯亮或者让灯灭的状态。不在判断处点灯,而在其他地方点灯。
  • 物理按键被按下,修改的不是真正的I/O口,而是修改标记位的值。
在这里插入代码片
代码可读性define
打开灯用 ON 1表示,关闭灯用 0 OFF表示.

#define ON_STATUS 1;
#define OFF_STATUS 0;
标记位型 打开

此时的打开关闭,是生活里的打开关闭。打开 用数字1表示

逻辑电位 型 打开

此时的打开关闭 ,是电路逻辑里的打开关闭。 打开用 低电平数字0来表示。

mark标志位(状态机)

#define ON_STATUS 1;
#define OFF_STATUS 0
int LEDmark = 0;(变量定义必须放在首行。)

  • 对于标志位打开就是ON,就是1
  • 关闭就是OFF,就是0.下面也是根据这个标志位的字面意思来,打开关闭灯。即根据标识位 写相应的逻辑电位,操作引脚对应的I/O口。
    定义一个变量,变量的值是0或者1,用0或者1表示我记住了某个I/O口的状态,在需要修改I/O口的位置,进行修改。
    按键操作修改的不是I/O口,而是修改变量的值。当业务逻辑复杂时,这样子做特别好。
mark(标志位)+define(可读性)相关代码
  • 前面需要的
    #define OFF_STATUS 0; 关状态
    #define ON_STATUS 1;开状态
    int LEDmark = 0; 初始化标志位 为 关状态

-后面需要的
if(LEDmark == ON_STATUS){
LED1 = 0;
} else{
LED1 = 1;
}

按下按键1,打开灯,再按下按键1,关闭灯。按键2只是会关闭灯。

最大的用法是  把原本再条件判断里执行的I/O操作,通过标志位,转移到其他的地方去操作I/O口。
#include "reg52.h"
#include "intrins.h"
sbit LED1 = P3^6;
sbit KEY1 = P2^1;
sbit KEY2 = P2^0;

#define ON_STATUS 1
#define OFF_STATUS 0
int LEDmark = 0;

void Delay50ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 3;
	j = 26;
	k = 223;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}


void main()
{
	 
	  while(1){//
			
     if(KEY1 == 0){ //°´¼ü±»°´Ï£¬KEY1ÓëGNDÁ¬½Ó£¬±íÏÖΪµÍµçƽ¡£
			 Delay50ms();//Èí¼þÏû¶¶
			 if(KEY1 == 0){
              LEDmark = ON_STATUS; //LED1 = 0;
			    }
    }
		 if(KEY2 == 0){//KEY2λÖÃÓëGND½Óͨ£¬±íÏÖΪµÍµçƽ£¬ÖµÎª0
			     if(KEY2 == 0){
						 Delay50ms();
              LEDmark = OFF_STATUS;
           }
      }
		  if(LEDmark == ON_STATUS){
           LED1 = 0;
      }else{
           LED1 = 1;
     }
			
			
			
	}

}

4. @@@LED1 = 0的含义@@@

针脚一般在等号右边,代表 的是针脚里从外设捕获的信息
针脚一般在等号左边,代表的是改变I/O口的电平,进而去影响针脚相应的外设
LED1(P3^6)表示的是针脚,即在这个的含义是 针脚被赋值,该针脚处电平为低电平,进而影响针脚相接的外接电路。
if(KEY1 == 0) ,KEY1表示针脚所收到的数据。

翻转法+标志位

  • 在使用翻转法前,必须先初始化LED = 1;,然后是LED = !LED;
  • 在使用标志位前,也要初始化标志位,并且还需要可读化。
    LEDmark = 0 ;
    #define ON_STATUS 1头文件后面不需要加分号。

六丶小项目

一丶电动车防盗系统 学习基本IO口

  • 按下按键近距离 打开警报
  • 当有震动打开警报
  • 4G定位

对应模块

震动传感器模块
继电器模块
喇叭
433M射频无线接受发送模块
杜邦线

1.震动传感器认识

震动传感器

如何知道他发生了震动?即震动之后他的信号表现是什么?
通过震动传感器来学习I/O口,通过LED来观察模块是否发生。

在这里插入图片描述

在这里插入图片描述
针脚;
电源的正级。
电源的负极。
数字量信号输出。
当产生震动时,输出低电平。绿色灯会亮。

  • 产品不震动,AO口
  • 产品震动,输出低电平,绿色指示灯常亮,模块的AO口不适用,不接线
  • 不使用AO口,使用DO口。
    接线方式
单片机                 模块
5V                      VCC
GND                     GND
DO                     P3^3 

  • 借助可视化的灯,反应出不可视化的针脚所处电平。
    当震动发生时P3^3口上的针脚是低电平。通过检测是否是低电平,借助灯亮灭的可视化来 反映出 针脚电平不可视化。

实现:当有震动时,亮个两秒,不震动了灯灭。

  • 注意当接有外设要重新烧率程序的时候,需要把外设与电平机相连的5V断开连接,烧录成功后,重新连上模块。
  • 软件定时不太准确,选择STC-T1缓解一下,优化一下。
  • nop()所需头文件 #include “intrins”

震动传感器原理

震动传感器的DO口与单片机的针脚相连(P3^3口),震动传感器在感受到震动时,会把把与他相连的I针脚的电位拉低,即该针脚处于低电平。

  • 通过借助可视化的LED灯的变化,来验证出该针脚的电平。

在这里插入图片描述

震动传感器相关代码

#include "reg52.h"
#include "intrins.h"

sbit LED1    = P3^6; //根据原理图,设配变量LED指向P3组IO口的第7口
sbit vibrate = P3^3; //DO接到了P3^3口
   
		  
void Delay500ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 4;
	j = 129;
	k = 119;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}


void main()
{
   while(1){//查询法判断是否发生震动
      if(vibrate == 0){
         LED1 = 0;
	   Delay500ms();
     }else{
         LED1 = 1;
      }
   
    }

}

2.继电器的认识

单片机玩的开关即继电器。继电器是可编程的电子开关。

继电器的参数

  • 5VDC 可由单片机供电
  • 当正常接入时,会看到PWR(power)指示灯亮。
  • 可控制交流电220V 10A的插座。
  • 正常接入电路,PWR红色指示灯亮起。
    接线方式
单片机                      模块
5V                           VCC
GND                           GND
某个IO口                     IN

参数解释

  • IN的作用:
  • NO COM :共称为常开。当未收到IN口的低电平信号时,NO—COM口是处于断开状态,类似于一个开关。当给低电平,内部联通状态(铁片吸合)。当给他高电平,内部断开状态。
  • 图里在NO口和COM口的电源可以是 220V交流电,也可以是电池。
  • 这样理解就行:当IN口处于低电平时,NO–COM常开内部是相连的,即导通状态。NO–COM–电源—灯泡构成的串联电路是连通的。

继电器原理

当正常继电器接入电路时,红色PWR灯会亮。
与继电器结合,当发生震动时,让IN口是低电平,左边继电器绿色的灯会亮。导致继电器内部的铁片被放下,进而导致COM与NO口接通,继电器右边实物(负载)连接会接通,灯亮。


继电器+震动传感器

在这里插入图片描述在这里插入图片描述

发生震动,继电器打开,照成继电器右边实物电路导通,警报响一会,继电器关闭(警报不响)。

  • 解析代码:发生震动,去用if语句读震动传感器的引脚
  • 发生震动,震动传感器 给单片机IO口一个低电平。(震动传感器引脚)
    这个作为条件,当发生震动 去手动发送让继电器导通的 语句。
  • 流程步骤
    振动源----------vibrate =0-----------if(vibrate == 0)----------switcher = 0--------负载接通,正常工作

继电器和震动传感器结合的代码

#include "reg52.h"
#include "intrins.h"
     //外界有震动,会使对应单片机的针脚的电平处于低电平
		  // 针脚电平借助LED亮灭来显示
			//1.振动源-------震动传感器IO口变成低电平
			//2.不断检测是否是低电平,若是则让单片机的一个IO口输出低电平
			 //3.单片机一个IO口 与继电器IN相连,当满足条件手动置IN=0,负载正常工作

sbit vibrate  = P3^3;
sbit switcher = P2^0;

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void main()
{
   while(1){
      if(vibrate == 0){
         switcher = 0;
				 Delay1000ms();
				 switcher = 1;
     }else{
         switcher = 1;
      }
   
    }

}

总结

  • 继电器,当IN电位是低电平的时候,左边绿色指示灯会亮。同时若右边有负载的话,负载会正常工作。
  • 震动传感器 发生震动的时候往C51单片机里面灌输一个信号(单片机的针脚)
  • 继电器 是 C51的IO口往继电器里面灌输一个低电平(IN口),通过编程让单片机拉低整个导线的电平,导致针脚IN口低电平,导致COM和ON口接通,进而导致负载正常工作。

3.无线射频模块433M

信号传输能力挺牛逼的,特定条件下一般比蓝牙,无线更远。
433M是远距离传输模块。软件工程师 只关心电路连接以后,收到信号的表现是什么。

发送端 遥控器 A B C D
按下按钮,会在空气中传播一个电磁波。接受模块会收到对应的波形。不同的按钮,被接受模块解析出来波形是不一样的。

  • A按下会有A波形,B按下会有B波形。C有C波形,不同的按键对应不同的波形。不同的波形被处理,引起模块针脚电平的变化。
  • A按下,D0输出一个高电平。
  • 433M模块被接到单片机上,引起单片机针脚的变换。
    -433M模块D0 —对应按键A
    D1---------B
    D2--------- C
    D3----------D

原理

点击A,发射波形,波形被433模块处理。导致接受模块会输出高电平。

433M控制继电器的代码

在这里插入图片描述

不断的去查询针脚的电平,两个if语句是并列的。

发送端按键C发送电磁波,被接收模块接受,接收模块使与单片机相连的针脚,的电位处于高电位。当检测到该针脚是高电位时,接通继电器。

#include "reg52.h"
#include "intrins.h"
    

sbit D3_ON    = P1^0;       
sbit D4_OFF   = P1^1; //接受模块与单片机相连的针脚
sbit switcher = P2^0;


void main()
{
   while(1){ //不断检测这两个针脚,当该针脚是高电平时,就接通继电器。
      if(D3_ON == 1){
         switcher = 0;
			}
				    
			
		  if(D4_OFF == 1){
         switcher = 1;
      }
   
    }

}

4.简易电动车防盗系统

  • 震动传感器模块P1^3
  • 433M使用两个按键P1^1 P1 ^2
  • 继电器 P1^0

在这里插入图片描述
在这里插入图片描述

简易电动车报警器相关的代码

1.先写开发流程

前期写代码没有思路的时候,就先写中文。把你的思维逻辑,开发流程记录下来,转化成代码即可

2.基于流程进行代码的填充

1.检测A按键是否被按下

  • 被按下,警报模式J_ON,长响
  • 未被按下,非警报模式J_OFF,短响
  • 警报模式;使用标记位J_ON,解除警报模式J_OFF
    -当在解除警报模式下,震动不会导致警报声响。
    2.当在警报模式下,是否发生震动
  • 发生震动,继电器接通,一直响
  • 不发生震动,继电器不接通
警报模式标记位
电动车防盗器的代码(有BUG,借助中断)
信号的丢失解决

中断:终止你正在数数,让你先去处理紧急的事情。处理完在返回来去数数。
在延时期间,按下发送器的按钮,会导致信号的丢失。通过后面的中断来解决。即你正在数数,他正在响,中断会在有紧急事情来临时,告诉该进程,停止在做的事情(数数),去对信号进行捕获。

5.总结认识提升

  • 无外乎两种,即一个是外设自主的改变单片机引脚的高低电平。
  • 另一个是单片机(人为的写入语句)检测到设定的条件满足,单片机改变相应的针脚

二丶感应开关盖垃圾桶 学习定时器+计时器

智能垃圾桶。学习定时器的配置,定时器中断,以及寄存器。

所需模块

  • 舵机(PWM脉冲波)
  • 测距模块

PWM脉冲波

PWM脉冲波,涉及波长 ,涉及到时间,故需要学习定时器。定时器是必须学习的知识点。

1.定时器

  • 前面所有关于时间的运算都是使用的软件延时器,是让CPU数数。会占用CPU的时间,以电动车那个项目为例,会导致信号丢失的问题。
  • 定时器是专门用来数数,计时的。
  • C51中定时器和计数器是同一个硬件支持的,通过配置寄存器可以把这个硬件当成定时器或者计数器来使用。
  • 定时器和计数器都是输出。定时器是靠内部震荡电路进行输出。
  • 计数器是数外面的信号。每经历一次负跳变(从高电平变到低电平)会计数一次,一个负跳变会使计数器加1.
  • 计数器是通过外面信号的变化(针脚的电平变化),然后满足条件加1.
  • 定时器/计数器的名称
    T0,T1,T2
    T0用于正常的业务上的数数。
    T1用于串口。

定时器振荡电路

  • 定时器的本质原理:每经过一个机器周期,就加1.一个机器周期是由5个时钟周期构成的。

  • 时钟周期是单片机最小基本时间单位,由晶振的频率绝定,T = 1/f.

  • 一般用t表示机器周期,一般用T表示时钟周期。即t = 5T. = 5* 1/f

  • 这里的硬件特指晶振,晶振的频率在这里是11.0592.,频率元器件

  • 定时器是靠硬件内部的震荡电路实现的。

晶振

在这里插入图片描述

晶振对于电子产品来说,很重要,相当于一个心脏。数字电路的工作离不开晶振。
为什么这里的晶振频率是选用11.0592,因为后面串口也需要使用定时器,串口工作的波特率一般是11.05926.相互匹配,更稳定,定时更准确

震荡周期(T)

震荡周期,又称为时钟周期。是单片机晶振的频率的倒数。
即T = 1/f(f是晶振的频率,定时器,振荡电路)。
时钟周期是单片机系统中最小的时间单位。就像对人来说,s是人类最基本的时间单位。

  • 对于机器而言,快与慢去研究最小的基本单位,即时钟周期。晶振的频率越大,时钟周期越小,工作频率更高效。
  • 一个时钟周期:规定的,以晶振内部做某件事所需要的耗费的时间定义为时钟周期(时钟时间)。
  • 把一条指令的执行过程(取指,译码,执行),每完成一个基本操作都会加一个时钟周期。
机器周期 (t)

一般用t表示机器周期,一般用T表示时钟周期。即t = 5T. = 5* 1/f
机器周期一般由若干个时钟周期构成。

在这里插入图片描述

在这里插入图片描述

每经过一个机器周期

晶振跳一下,经过一个机器周期,cnt++。加一,耗费一个机器周期。耗费1.085us.在寄存器里加1.

在这里插入图片描述

在这里插入图片描述

2.寄存器

定时器需要被管理,借助寄存器来管理。将寄存器理解成一个管家,而定时器理解成一栋豪华的房子。
寄存器的地址是对 CPU而言的。

  • 寄存器是什么?一直存在但不好解释。
    sfr指令是配置寄存器的地址。
    sfr P3 = 0xB0; P3是寄存器,通过sfr指令给寄存器分配地址是0xB0;
    往0xB0所代表的寄存器里写入某个指令,CPU不断的检测寄存器,当检测到这个指令,CPU会采取某项动作和措施。
  • sfr 是特殊 功能 寄存器
  • C51具有IO口,串口,定时器
  • IO口操作 有对应的IO口寄存器地址
    sfr P0 = 0XB0;
  • 操作定时器 也有对应的定时器寄存器
    例如 1.sfr TCON = 0x88;
    2.sfr TMOD = 0x89;
    3.sfr TL0 = 0x8A;
    这些都是寄存器,用于管理定时器的。
CPU的好帮手寄存器

CPU进行一系列工作时,调用某个功能的时候,需要借助寄存器来管理这个功能。
CPU想用用定时器。 CPU会先找到管理定时器的人,即定时器寄存器。
告诉寄存器,让他满足自己的需求,帮CPU干活。程序员通过配置寄存器,实现一些功能,例如配置定时器相关的寄存器,配置串口相关的寄存器。

  • 程序员先配置对应的寄存器(即,先给寄存器分配地址),之后在单片机程序里找到寄存器的某个具体的位,通过配置这个位,来实现想要的功能,即一般是实现针脚对应的高低电平的变化。
对寄存器简单的认识

在这里插入图片描述

寄存器的最核心就是内存,对于内存而言具有大小。故寄存器在(以定时器寄存器而言)会出现越界的情况,为了防止越界,自己有专门的检测位。例如,TCON里的B5位下的TFO可以知道是否越界

通过最先配置寄存器的某个具体位,然后修改该寄存器具体位下的 某个变量 达到 想要的目的。

  • 定时器计数也有个天花板,即当到达数到一定条件,就会触发这个标记。TFO时定时器T0溢出中断标志。
    TFO是TCON寄存器里面的第B5(Bite 5);类似于点灯,PO口的第三个引脚。P0^3;

  • TCON寄存器的第五位 TCON寄存器第B5五位

  • P0口的第三个针脚P0^3

  • 寄存器的第几位 具体而言就是一个内存。

  • IO口的某个针脚 具体而言也是一个内存。P0^3 = 1;实际上就是 先找到这个寄存器(IO口),再深入找到具体的某位(某个针脚),通过写入指令01代码,修改这个内存(针脚深入层面紧密相连的)

  • 寄存器的控制性(控制寄存器)
  • 寄存器的存储性(状态寄存器)

3.配置定时器寄存器的准备工作

定时器如何定时10ms
  • 数数不一定必须从0开始数,由寄存器TL0的复位值决定,一般默认没有配置的情况下,从0开始数数。复位值具有8个位,位排列组合产生的情况,来代表具体的数字。
  • 电脑不懂10进制,一般只懂2进制。即0和1,通过0和1组成的不同的顺序,来代表实际生活中的数字。
  • TL0 和TH0都是寄存器,都可以根据自己的位产生不同的排列组合,一般将他们一起使用即原来的8位,现在成了16位,即可产生的排列组合更多,代表的现实生活中的数字更多。
两位寄存器
  • 电脑 和现实生活 通过代替法,产生联系。
    两位的寄存器,根据01排列原理 (任取两位组合) 可产生 4种排列组合,代表4个现实生活中的数字。
  • 01排列原理 2的多少次方(多少:寄存器的位数)
@@@16位寄存器如何定义10ms

在这里插入图片描述

  • TL0是8位,可代表256个数字,即相当于 晶振跳256下,经过了256个机器周期,数数的极限:256下 等价于 计数的时间极限:(1.085us*12)*256 s =71ms会爆表。
  • 寄存器从0开始只会数到256,经过的是256个机器周期,耗时71ms.故只能从0数到71ms.
  • 根据寄存器特殊的构造,只有当爆表的情况下才能检测出来。一般使用寄存器都会事先设定一个初始值X(从第几下开始),(因为定时器寄存器只会从0数到65536(2的16次方))。
总结
  • 一个机器周期 是1.082us,定时器寄存器爆表耗时71ms,即65536个机器周期。10ms需要经过9216个机器周期(距离爆表是9216下)。即从56320开始数,转化成10进制DC00。
  • CPU对外设IO口以及定时器等的所有控制 都要通过寄存器。定时器也具有定时器寄存器。
  • TFO寄存器可知道什么时候爆表。使用定时器时,一般通过TL0寄存器定义初始化一个初始值。寄存器具有16位,只有在爆表的时候才会被检测出来(2的16次方代表的数字)
  • 而定时器 时利用定时器寄存器爆表的功能特性 做成的,本质上是机器周期(震荡电路)

4.寄存器配置

定时器控制寄存器TCON
  • 在TH0和TL0寄存器中加1,默认从0开始计时,最多能数65536下,累计计时71ms,71ms不太好用,使用方便的10ms.
  • 自己配置Yms 公式
    (65536-X)*1.085us = Yms 注意单位的变化,求出的X,需要转换成HEX形式
  • 怎么知道爆表
    TCON寄存器的bit5能表示爆表:但爆表的时候,硬件会修该bit5(TFO)上面的数据,改成1(置1).向CPU请求中断,只有当响应这个中断,才有硬件清“0”(或者通过程序查询清“0”).
  • 怎样知道开始计时
    TCON寄存器的bit4(TR0),通过编程让TRO这个位为1,表示开始即使,相当于按下了闹钟键。
  • TFO硬件自动置1,如果使用中断,响应中断会存在软件清0,如果不使用中断,必须采用手动程序清0的方式。(@@@)
定时器模式寄存器
  • 定时器中存在很多模式,定时器模式寄存器TMOD
    即定时器寄存器具有16位时,是否全部都用上这些位,由定时器的模式来决定。
    TMOD寄存器存在8位,高四位管理定时器1,低四位管理定时器0,
    低四位里的M1,M0这两个位,决定了定时器1的模式
    高四位里面同时也存在M1,M0这两个位,决定了定时器0的模式

在这里插入图片描述

在这里插入图片描述

TMOD模式选择

每一个定时器都是由8位组成的。体现在配置的时候,即TL0和TH0。

  • 以定时器0的00模式为例:13位定时器。即TH0整个8位全用,TL0只用低5位参与分频。二的13次方8192,爆表累计时8.88832ms
  • 以定时器0的01模式为例:16位定时器,TH0的8位和TL0的八位都使用。累计爆表最大计时:71ms
  • 模式的区别在于定时的总时长是不同的。
  • 以定时器0的10模式为例:8为自动重载计时器,只是使用TL0,(8位),累计爆表277us,使用情况不多,当溢出时,将TH0存放的值自动装入TH0。
    01模式
    11模式 表示的含义是 定时器0的TLO作为一个定时器,使用属于定时器0自己的标准定时器0的控制位控制。TH0也作为一个定时器,但是 是使用的是定时器1的控制位控制。
  • 这里的定时器一般包括TH0和TL0两部分,一个定时器只有一个标准控制位,11模式的意思是,把定时器0分成两个定时器,故需要两个标准控制位,故需要向定时器1求助,借助定时器1的标准控制位。
  • 就是一个定时器分成两个定时器来用,但是每一个定时器都需要配一个标准控制位,故需要使用定时器1的控制位作为定时器0的控制位。
  • 控制位指的是溢出爆表这些东西,即TCON里面的某些,如TF0等。
    TL0 使用定时器0自己的控制位,TH0 必须借助定时器1的控制位
    在这里插入图片描述
    在这里插入图片描述

5.选择工作方式,配置TOMD

1.配置定时器模式选择位,选择定时器模式为16位
2.给初值,定10ms
3.配置定时器控制寄存器,打开开始计时的位

TMOD = 0x01(00000001);-------------配置定时器模式选择寄存器

TL0 = Ox00;
TH0 = 0xDC;                --------配置定时器,确定10ms


TCON 中(直接配置具体的位即可,因为在re52.h中,
预先有这样的语句   sbit TF0   = TCON^5;这样的语句
)


TF0 = 1;       --------配置定时器控制寄存器,完成 打开定时器 ,打开溢出标志
TR0 = 1;
注意TF0的软件清0

一般情况下溢出标志位,需要用到中断。当使用中断的时候,硬件会自动的清零。当未使用中断的时候,必须人为的用程序清0.

10ms

赋初值:
TH0 = 0x00;
TL0 = 0xDC;
当溢出标志位爆表了,需要重新的赋初值。注意cnt++的清零,当100次表示一秒重新让cnt清零,为下一秒的计算做准备。

定时器(未用中断)实现控制LED一秒亮灭代码

步骤 :定时器实现灯的亮灭(未用中断)
1.配置TMOD寄存器,选择计时器模式
2.确定定时器为10ms, TH0 TL0初始化
3.配置TOCN寄存器,打开定时器(打开为1),打开溢出标志位(打开为0)
4.不断的检测是否爆表,不断的检测cnt == 100
5.当爆表时,未用中断,必须使用软件清零
每次爆表都需要重新的 初始化TH0和 TL0

#include "reg2.h"
sbit LED1 = P3^6;

void Time0()
{
   TMOD = 0x01;
	
	 TL0  = 0x00;
	 TH0  = 0xDC;
	
   TF0  = 1;
   TR0  = 1;
    	
}

void main()
{
	 int cnt = 0;
	 Time0();   //定时器0(未开中断)
	 LED1 = 0;
	 while(1)  {
 
  if(TF0 == 1){      //不断的检测是否爆表,爆表了硬件会自动置1
     TF0 =0;    //未用中断需要软件清零
	   cnt++;
	   TL0 = 0x00;
	   TH0 = 0xDC;
	   if(cnt ==100){//当满足1s的条件时,cnt清零为下一秒做准备
           cnt = 0;
		      LED1 = !LED1;
     }

  }
}
}





。。。。





注意标志变量的清零

if(cnt ==100){//当满足1s的条件时,cnt清零为下一秒做准备
cnt = 0;
LED1 = !LED1;
}

注意软件清零

if(TF0 == 1){ //不断的检测是否爆表,爆表了硬件会自动置1
TF0 =0; //未用中断需要软件清零
}

6.自己写的Time0与原版的比较

自己写的Time0()定义10ms

void Time0()
{
   TMOD = 0x01;
	
	 TL0  = 0x00;
	 TH0  = 0xDC;
	
   TF0  = 1;
   TR0  = 1;
    	
}

软件定时器所提供的 Time0() 定义10ms

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

必备知识

  • 4个二进制(具有16种排列) 表示1个16进制的数。
  • 二进制01,十六进制ABCDEF
  • 从16进制到2进制 用到8421法即可。
  • 配置TMOD寄存器 口诀:清零用&,置1用或。
  • 清零的时候,对具体需要清0的部分用0,不需要的地方用F
    这里的0是0000
  • 在置1的时候,对具体需要置1的部分用F,不需要的地方用0
    这里的1是1111
按位操作
  • 逻辑与&
    出发点:在与操作中 与1&保持原状,与0&输出为0
  • 逻辑或|
    出发点:两个数是否存在1.在两个数中只要存在1个1,那么结果就是1,否则结果是0.
口诀应用

清0用&,置1用1.根据口诀,结合需求自己构造即可或者借助定时器计算器

TMOD &= 0xF0; //设置定时器模式 高四位不变,低四位清0
TMOD |= 0x01; //设置定时器模式 高四位不变,低四位置1

口诀推导必看

在这里插入图片描述

AUXR时钟的电磁辐射

降低单片机时钟对外界的电磁辐射,该寄存器用以管理电磁辐射

  • 8位寄存器,但只是用2位,其他位弃用。1表示禁止ALE信号输出。
  • AUXR = 0x01;
  • 示波器感受到时钟对外界的电磁辐射。或感受到干扰了可能与AUXR有关。
    在这里插入图片描述
定时器时钟模式

正常单片片机时钟模式,可以不用配。直接在烧录时不勾选6T即可。默认情况下是12T模式。

7.中断
  • 中断请求,中断处理
  • 多个中断源存在优先级,CPU最先响应优先级最高(最紧急的事件)。
  • 操作系统提供了多线程:多个while(1)语句同一时间一起来执行。但C51不具备这些线程,故需要硬件的支撑。
  • 单线程在作战,即当整个进程内出现两个while(1),
    两个while(1)代表两件事 需要借助中断处理函数,与线程的思想类似。多个while(1)语句根据紧急性轮流来执行。
  • 传统C51不使用硬件支持中断的时候,只能在一个while(1)里一直跑,跑不出来,而使用C51的中断硬件支持,使C51具有了类似于多线程的能力。但是不是多线程:能力较弱,即当出现紧急事件的时候,会跳出第一个while(1)去处理另外一个while(1)里的东西,处理完之后,又重新的返回第一个while(1)里面紧接着执行
  • C51类多线程即还是一心一意,只不过是加了个中断系统。即做能力,着这件事,当中断发生,立马去做另一件事,即同一时刻也是 着手一件事情。
  • 而多线程也不能一心二用。即是同一时刻,也不能同时着手干两件事情。如 同一时刻 1.数数 2.检测信号的到来。但计算机运行速度快,多线程+sleep 给人一种 多线程可以同时做两件事的感觉,实质上是根据竞争能力的大小 轮流做事情。
线程与中断
  • 线程+对CPU竞争,即各个线程之间也是相互竞争的,即CPU的竞争。并且需要在每一个(一般除了主线程不用sleep()类)线程的while(1)配合使用。
  • C51进程 +中断 当出现新事情,立马去执行新事情,然后返回做旧事情。
  • 多线程能执行下去取决于 竞争能力,进程+中断不断进行下去,取决于中断处理系统
8. 多个中断源

外部中断0,1,2
定时器中断012
串口

中断优先级

目前最关心的是用中断处理定时器

  • 对于同一类型的中断源的优先级,优先级的级数可通过配置寄存器来配置。

  • 处理中断,响应中断:中断处理函数,被硬件调用。硬件知道当发生溢出,发生中断 时为你做一些东西,但不知道为你调什么函数,故需要明确的指出中断号,,在变成汇编语言后中断号会被烧率到单片机某个地方,此时当单片机发生中断时会去调用这个处理函数。

9. 中断的结构以及中断寄存器

当中断进行中断请求的时候,需要配置中断允许控制寄存器,然后通过中断优先级控制寄存器------选择对应的中断源去处理对应的中断请求。

  • 使用任何中断,EA都需要(EA = 1).
  • 可位寻址寄存器,指的是可以配置寄存器的某个具体的位
  • 在中断之前需要注册一个中断处理函数。interupt 1;
    在这里插入图片描述
    在这里插入图片描述

中断+中断处理函数 控制LED灯

  • 打开定时器0中断,用于溢出中断,即当溢出导致发生中断的时候,硬件自动让系统调用 预先设定好的函数即可,去处理响应中断。
  • 注册中断处理函数 定时0对应中断处理函数的 注册序号是 interupt 1;
  • 硬件中断 无需程序清0语句(TF0 = 0);
中断打开之后再打开定时器

TF0 = 0;//溢出标志
ET0 = 1;//中断副开关
EA = 1;中断总开关

TR0 = 1;// 打开定时器0。

定时器中断 控制LED

提前打开定时器0,定时器0用于硬件溢出中断。当发生溢出时,硬件
自动的调用中断处理函数,去响应中断。
-一般推荐使用中断,借助中断也需要cnt++去满足爆表的条件,当达到爆表100次后,通过语句cnt =0;重新为下一次的1s做准备

#include "reg52.h"
#include "intrins.h"

         //main()主线程实现点灯LED1,定时器0溢出中断实现电灯LED2
				 
sfr  AUXR = 0x8e;
sbit LED1 = P3^6 ;
sbit LED2 = P3^7;

int cnt = 0;   //全局变量

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 43;
	j = 6;
	k = 203;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
void Timer0Init(void)		//10??@11.0592MHz
{
	AUXR &= 0x7F;		//?????12T??
	TMOD &= 0xF0;		//???????
	TL0 = 0x00;		//??????
	TH0 = 0xDC;		//??????
	TF0 = 0;		//??TF0??
	ET0 = 1;                  //注意需要自己配置中断的两个开关。同时把定时器打开放到最后
	EA  = 1;       
	TR0 = 1;		//???0????
	
}
				 				 
void main()
{
	 LED2 = 0;
   Timer0Init();    //定时器初始化
	 while(1){
		   LED1 = 0;
       Delay1000ms();
       LED1 = 1;
       Delay1000ms();		 
     }
 }	 
		 
Timer0Handler()interrupt 1    定时器0的中断处理函数
{
     cnt++;//ͳ¼Æ±¬±í´ÎÊý
	           //ÖØи³³õÖµ
	 if(cnt == 100){
		 cnt= 0 ;   //µ±100´Î±íʾ1s£¬ÖØÐÂÈÃCnt´Ó0¿ªÊ¼£¬¼ÆËãÏÂÒ»´ÎµÄ1s
		 LED2= !LED2; 
   }
}
注意小问题
  • int cnt = 0;注意全局变量的定义,把cnt放到最前面,作为全局变量

中断认识提升

main()进程中执行一个while(1)事件(如去实现一个灯LED1的亮灭)。同时定器器中断的处理函数中:有另一个事件 LED2的亮灭(当满足爆表条件时,需要先去处理),即此时CPU不在去执行main()进程点LED1灯的操作。而是去处理紧急事件LED2的亮灭。处理完之后CPU立马返回接着执行LED1的亮灭。中断接着等待中断请求的发生。

总结 定时器 + 中断处理函数

目前所学到了定时器+ 中断处理函数 +IO口的基本操作

10.PWM开发SG90电机

通过调整占空比,来实现对舵机的旋转角度的控制。
PWM pulse width Modulation 脉冲宽度调试

  • 一系列的脉冲宽度调制,等效出对应的波形。对模拟信号进行编码。也就是通过调节占空比,调节信号丶能量的变化
  • 看书学编程类似于有字天书
单片机输出针脚电平
  • 当外设是一般的器件时,针脚是0或者是高电平就可以驱动相应的外设工作。
  • 而当外设是SG90时,必须输出一段波形,才能驱动起来SG90进行工作。
  • 一定时间周期内,4ms为一个周期,电平高低的跳变产生的波形来驱动对应的SG90模块
  • 占空比:一个周期内,高电平占用周期时长的百分比。
如何输出PWM信号
  • 借助内部硬件模块产生–精准度高。
  • STC89C51的硬件资源 IO口,串口,定时器,没有PWM
  • 借助普通IO口进行模拟(定时器+IO口),模拟实现PWM波形。
常见的舵机
  • 一般舵机有3根线,
    黄色的线是PWM信号,向黄色的信号线灌入PWM波
    红色的线VCC
    灰色是GND
  • 舵机的频率大约 50hz
控制方式

占空比来决定 旋转的角度
在这里插入图片描述

计时器定义初值0.5ms(一个PWM周期20ms)

高电平占用的时间不同,转舵机动的角度不同。

  • 编程实现的准备工作 ,使用定时器0定义出一个20ms.关心单位是0.5ms,需要40个。定时器的初值是0.5ms.cnt++;
  • 每次爆表经过0.5ms,需要爆表40次。总共经过20ms.
  • sbit SG90 = P1^1; 单片机向针脚输出对应的波形。
占空比

每经过20ms存在一个高电平维持0.5ms ---------旋转0°

  • 默认情况下,刚一上电,让SG90的PWM是高电平(上电原始状态)
  • 系统一上电给一个PWM一个高电平,当第一次爆表不满足条件,立马把cnt拉到低电平去。
注意上电原始状态 和爆表状态下的电平的
  • 一开始上电 让SG90的针脚为高电平
  • 当 爆表的次数达到最大数时 执行cnt = 0语句以及 SG90 = 1;(最终爆表下的状态),准备下一次的波形
想要的波形
  • 电平恒定 ;sg90_con = 1;始终是高电平,无法转动舵机
  • 电平动态变化:需要的是在一个20ms的周期内,实现的跳变,即电平是动态变化的。

根据时序图来驱动外设

舵机编程实战

这里插入图片描述

  • 注意那个定时器计算器 一定要选择的是16位模式,如果要是选择16位自动重载模式的话会导致程序的无法运行(神坑!!!)

舵机从0度转到135度;从135度转到0度。每隔两秒的时间做这件事情。

  • 以0度和135度为例:
  • jd = 20ms周期内 高电平的占空比,即高电平持续时间/20ms周期

0度用 jd = 0表示
135度 用jd = 3表表示

if(cnt <jd){
sg90_con= 1; //在一个周期内(20ms), 一个sg90周期内高电平,持续的时间
}else{
sg90_con = 0;
}
if(cnt == 40){
cnt = 0; //为下一个sg90周期做准备
sg90_con = 1; //为下一个sg90 初始化电平起始状态 .
}

#include "reg52.h"
#include "intrins.h"

         //main()Ö÷Ïß³ÌʵÏÖµãµÆLED1
				 //¶¨Ê±Æ÷ Òç³öÖжÏʵÏÖµçµÆLED2
sfr  AUXR = 0x8e;
sbit sg90_con = P1^1;
int cnt = 0;
int jd;
void Delay200ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 9;
	j = 104;
	k = 139;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
void Delay2000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 85;
	j = 12;
	k = 155;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}



void Timer0Init(void)		//0.5??@11.0592MHz
{
	AUXR &= 0x7F;		//?????12T??
	TMOD &= 0xF0;		//???????
	TMOD |= 0x01;		//???????
	TL0 = 0x33;		//??????
	TH0 = 0xFE;		//??????
	TF0 = 0;		//??TF0??
	TR0 = 1;		//???0????
	ET0 =1;
	EA  = 1;
}


void main()
{
    Delay200ms();	// ÉϵçÎȶ¨
	  Timer0Init(); //¶¨Ê±Æ÷0ÖжÏ
	
	jd = 1;              //Ò»¿ªÊ¼ ½Ç¶È³õʼ»¯Îª1£¬ÒòΪ¸Õ¿ªÊ¼ÊÇÐýת0¶È
	cnt = 0;           //Íü¼ÇÕâ¸ö
	sg90_con = 1;    //@@@@      ¸ÕÉϵçʱ£¬Ò»¿ªÊ¼ËûµÄֵδ֪µÄ£¬ÉϵçÒԺ󣬶¨Ê±Æ÷Ò»³õʼ»¯ÒÔºó£¬ÈÃËû³ÉΪ¸ßµçƽ
	
	
	while(1){
	
	
   jd = 4;                                               //------135¶È
   cnt = 0;       		//@@@@   ×¢Òâjd´óµÄÔÚÇ°Ãæ¡£	                 //ÓпÉÄÜ jd = 4ʱ£¬cntµÄλÖÃ
	Delay2000ms();
	
	jd = 1;
  cnt = 0;               //½Ç¶ÈÇл»ÁË£¬ÐèÒªcnt¹éÁã¡        //	0¶È
	Delay2000ms();
  }
}



void Timer0Handler () interrupt 1
{
  cnt++;
	TL0 = 0x33;		//??????  ??@@@@  ×¢Òâµ±±¬±íÁËÐèÒª ³õʼ»¯
	TH0 = 0xFE;	                    //³õÖµÖØ×°
	
	
	if(cnt < jd){
    sg90_con = 1;             //ÒòΪ¸ÕÉϵçµÄʱºòmain()£¬¾ÍµÈÓڸߵçƽÁ
		                           //µÚÒ»´Î±¬±í¾ÍÓ¦¸ÃÀ­µÍÁË£¬¹Ê²»ÓÃСÓÚµÈÓÚ¡£main()ÖÐÉϵç³õʼ»¯µ¼ÖÂÉÙÒ»´Î¡
  }else{                    //²¨ÐλæÖÆ
    sg90_con = 0;
   }
	 
	 if(cnt == 40){
     cnt = 0;             //±¬±í´ïµ½×î´ó±¬±íÊý  cntÒ»µÈÓÚ0£¬¾ÍÈÃËûµÈÓڸߵçƽ¡£
		 sg90_con = 1;
  }
}

舵机的上电初始电位&&&cnt++&&cnt=0
  • 舵机的上电初始电位:sg90_con = 1;
    1.用于舵机刚上电,进行初始化电位.在定时器初始化后面,加上这个语句,对舵机刚上电初始化电位。

  • 舵机初始化状态 是两种情况。

  • 刚接通电源 。 第一种是真正意义上的单片机上电后模块初始化电位

  • 已经接通电源,,舵机进行下一个周期前电位初始化 。第二种是 舵机再进入下一个周期时的 上电初始化电位状态
    2.当达到爆表最大数时,舵机回到最初的电位sg90_con = 1

  • 类似的还应用于LED的翻转,即在确定是翻转灯的时候,需要对该翻转灯进行初始化电位状态。

  • cnt = 0的作用
    1.全局变量cnt
    2.初始化状态时,和sg90_con配合使用
    3.当发生爆表达到最大爆表的时候,使用语句cnt =0;,为下一个设定的时间做准备。(爆表一次0.5us,达到爆表最大数需要40次)
    if(cnt == 40){
    cnt = 0;
    sg90_con = 1;
    }

注意的一些小细节
  • 舵机一上电,需要初始化 舵机电位状态
  • 产生PWM波需要时间:
    定时器中断 每爆一次表需要cnt++
    当达到最大爆表数时,需要cnt = 0以及 sg90_con = 1即,cnt清零以及舵机状态初始化电位状态
  • PWM 需要的时间 +IO口电平 ,实现一个周期内的动态电平输出。
图片代码

超声波测距传感器认识

在这里插入图片描述

测距模块HCSR04(2cm-40cm)

接线方式

单片机                         HCSR04测距模块
5V                                GCC
END                               END
P1^5                              Trig
P1^6                              Echo


  • 工作逻辑
    单片机让模块发送波,Trig
    知道波回来了 Ercho 显示,检测到波返回来了
  • 时间差*340m/s /2 即可 = 距离

超声波发出去,定时器启动;
定时器返回来,定时器关闭

超声波的时序图

  • 第一步,触发信号:给模块至少10us的高电平。
    Trig = 1 ;持续至少10us

  • 单片机给模块发送触发信号,模块内部开始工作,开始制造超声波

  • 第二步,模块向外界发送超声波 了的标志&&&超声波反弹回来的标志:
    Echo信号,由低电平跳转到高电平,表示开始发波
    Echo信号,由高电平跳转到低电平,表示超声波回来了。
    时间怎么算:发波时打开定时器,波返回来时关闭定时器

  • 可以使用定时器,但不知到什么时候开始使用,什么时候开始关闭定时器。

牛逼的Echo
  • 由Echo标志的变化可得知:
    在一段时间内,当Echo = 1时,打开定时器0
    在一段时间内,当Echo = 0时,关闭定时器0
编程实现超声波测距
1.Trig,给Trig端口至少10us的高电平
2.由低电平跳转到高电平,表示开始发送波
          波发出的那一刻,打开定时器0
3.由高电平跳转到低电平,表示超声波返回来了
          波返回来的那一刻,关闭定时器0
4.计算时间
5。计算距离
代码学习小模块
定时器0也用作计数器0
  • 定时器0用与定时的时候,根据所需要定时的时间规格计算出一个初值X,转换为HEX即可,并且需要配合中断一起使用。当检测到爆表的时候,硬件累计加1,当达到最大爆表数的时候,完成一次 计时20ms(如40x0.5ms)
  • 而这里的是当计数器使用,硬件从0开始到完全爆表,需要经过65536下,即71ms.而这里超声波测距,一般用不到这么长的计数时间,故不需要借助中断。当计数器使用时,需要将定时器0的初值TH0和TL0设置为0即可。后续计算时间的时候需要将这两个TH0和TL0拼接起来。
TH0和TL0的拼接

可以的到一个规律,10进值的数高位向左移动一位,相当于 原数的基础上扩大10的n次方倍。n表示的是向左移动几位。
同理可得到规律,2进制的数高位向左移动:原数的基础上扩大2的n次方倍。TH0高位向左移动8位,为TL0腾出低位
TH0256
将他们拼接到一起 :表达式,(TH0
256)+TL0

ECH0标志

Echo 标志
1.由低电平 变成高电平 表示硬件内部发生出了超声波。
2.由高电平 变成低电平 表示超声波已经返回来了

在这里插入图片描述

跳跃电平标志语句while(Echo == 0);

瞻前的思想,即 满足前一刻的电平条件 导致阻塞。

定时器0数据清零

在一次while(1)循环中,定时器0数据需清零,即重新定时器0初值(TH0,TL0)。以便下一次的测距。

图片代码

在这里插入图片描述

在这里插入图片描述

编程实现
#include "reg52.h"
#include "intrins.h"
                     /*1.定时器0做计数器,先不打开定时器0
										   2.驱动超声波(Trig高电平持续10ms)
											  不断的执行下面代码while(1)
											 3.Echo标志,当低到高电平,打开计数器0
											    (       while(Echo)跳跃电平标志) 当高到低电平,关闭计数器0
										 	 4.计算时间TH0,TL0拼接后计算
											 5.操作外设
											 6.为下一次测距做准备,为计数器重新赋初值
											 
											 
										 
										 
										 */
sbit D6 = P3^6;
sbit D7 = P3^7;
sbit Trig = P1^5;
sbit Echo = P1^6;
sfr AUXR =0x8E;

void Delay10us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 25;
	while (--i);
}


void Timer0Init(void)		//100微秒@11.0592MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0;	//设置定时初值
	TH0 = 0;		//设置定时初值
}
	
void StartHC()
{
   Trig = 0;
	 Trig = 1;
	 Delay10us();
	 Delay10us();
	 Trig = 0;
	 
}
int main()

{
	       double time;
	       double dis;

	                            //1.定时器0做计数器,先不打开定时器0
	       Timer0Init();
										          
	       
								             //不断的执行下面代码while(1)
	       while(1){	          
                    					 //2.驱动超声波(Trig高电平持续10ms)
											       // 3.Echo标志,当低到高电平,打开计数器0
			        				      //  (       while(Echo)跳跃电平标志) 当高到低电平,关闭计数器0
				StartHC();
				while(Echo == 0);
			  TR0 = 1;
			  while(Echo == 1);
				TR0 = 0;
										 	     // 4.计算时间TH0,TL0拼接后计算
			  time = (TH0*256+TL0)*1.085;
				 dis = time*0.017;
											    // 5.操作外设
				if(dis < 10){
              D6 = 0;
					    D7 = 1;
  
        }else{
             D6 = 1;
				     D7 = 0;
 
      }
									      // 6.为下一次测距做准备,为计数器重新赋初值
	    TH0 = 0;
      TL0 = 0;
	  }			
	 										 
}								 
							
	
	

一般情况下,烧录进去代码需要重启单片机

  • 若未重启单片机,可能导致超声波模块无法使用。多重启几次单片机即可

感应开关盖垃圾桶

学习编程的调试能力。
用其他模块代码,提前验证一下该代码的可行性。判断代码OK,判断接线OK,去进行二次开发。

  • 功能描述
    当发生震动,按键按下,检测靠近的时候。自动开盖,并伴随滴一声,2秒后关闭盖子。
    多个触发因子,需用到中断。
  • 硬件说明
    sg90舵机,超声波模块,蜂鸣器模块。
  • 接线说明
    舵机控制口P1^1口 定时器0
    超声波Trig接1.5 定时器1
    Echo接1.6
    蜂鸣器接P2^0
    震动传感器接P3^2口(外部中断0)

超声波–使用定时器1(用于计数,初值TH0,TL0为0)

定时器用于计数,一般不使用中断。

TM0D &= 0XF0;
TM0D |= 0X0F;


后面使用的是TR1注意区分定时器0的TR0;
#include "reg52.h"
#include "intrins.h"
                     /*1.定时器0做计数器,先不打开定时器0
										   2.驱动超声波(Trig高电平持续10ms)
											  不断的执行下面代码while(1)
											 3.Echo标志,当低到高电平,打开计数器0
											    (       while(Echo)跳跃电平标志) 当高到低电平,关闭计数器0
										 	 4.计算时间TH0,TL0拼接后计算
											 5.操作外设
											 6.为下一次测距做准备,为计数器重新赋初值
											 
											 
										 
										 
										 */
sbit D6 = P3^6;
sbit D7 = P3^7;
sbit Trig = P1^5;
sbit Echo = P1^6;
sfr AUXR =0x8E;

void Delay10us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 25;
	while (--i);
}


void Timer0Init()		//100微秒@11.0592MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x10;		//设置定时器模式
	
	TL1 = 0;	//设置定时初值
	TH1 = 0;		//设置定时初值
}
	
void StartHC()
{
   Trig = 0;
	 Trig = 1;
	 Delay10us();
	 Trig = 0;
	 
}
int main()

{
	       double time;
	       double dis;

	                            //1.定时器0做计数器,先不打开定时器0
	       Timer0Init();
										          
	       
								             //不断的执行下面代码while(1)
	       while(1){	          
                    					 //2.驱动超声波(Trig高电平持续10ms)
											       // 3.Echo标志,当低到高电平,打开计数器0
			        				      //  (       while(Echo)跳跃电平标志) 当高到低电平,关闭计数器0
				StartHC();
				while(Echo == 0);
			  TR1 = 1;
			  while(Echo == 1);
				TR1 = 0;
										 	     // 4.计算时间TH0,TL0拼接后计算
			  time = (TH1* 256 + TL1)*1.085;
				 dis = time*0.017;
											    // 5.操作外设
				if(dis > 10){
              D6 = 0;
					    D7 = 1;
  
        }else{
             D6 = 1;
				     D7 = 0;
 
      }
									      // 6.为下一次测距做准备,为计数器重新赋初值
	    TH1 = 0;
      TL1 = 0;
	  }			
	 										 
}								 
							
	
	
	
	
	
	
封装超声波测距代码
double get_distance ()

在这个封装函数里,不涉及dis,直接将代表 dis的结果返回即可。

double get_distance ()
{
        double time;
	      StartHC();
				while(Echo == 0);
			  TR1 = 1;
			  while(Echo == 1);
				TR1 = 0;
										 	     // 4.计算时间TH0,TL0拼接后计算
		    time = (TH1* 256 + TL1)*1.085;
			  
											    // 5.操作外设
				
     
									      // 6.为下一次测距做准备,为计数器重新赋初值
	       TH1 = 0;
         TL1 = 0;
 return  (time*0.017);


}

openstatusLight() 控制外设函数
void openstatusLight()
{
  D6 = 0;
	D7 = 1;
  

}
void closestatusLight()
{
    D6 = 1;
		 D7 = 0;

}

封装后的超波代码
#include "reg52.h"
#include "intrins.h"
                     /*1.定时器0做计数器,先不打开定时器0
										   2.驱动超声波(Trig高电平持续10ms)
											  不断的执行下面代码while(1)
											 3.Echo标志,当低到高电平,打开计数器0
											    (       while(Echo)跳跃电平标志) 当高到低电平,关闭计数器0
										 	 4.计算时间TH0,TL0拼接后计算
											 5.操作外设
											 6.为下一次测距做准备,为计数器重新赋初值
											 
											 
										 
										 
										 */
sbit D6 = P3^6;
sbit D7 = P3^7;
sbit Trig = P1^5;
sbit Echo = P1^6;
sfr AUXR =0x8E;

void Delay10us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 25;
	while (--i);
}


void Timer0Init()		//100微秒@11.0592MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x10;		//设置定时器模式
	
	TL1 = 0;	//设置定时初值
	TH1 = 0;		//设置定时初值
}
	
void StartHC()
{
   Trig = 0;
	 Trig = 1;
	 Delay10us();
	 Trig = 0;
	 
}
double get_distance ()
{
        double time;
	      StartHC();
				while(Echo == 0);
			  TR1 = 1;
			  while(Echo == 1);
				TR1 = 0;
										 	     // 4.计算时间TH0,TL0拼接后计算
		    time = (TH1* 256 + TL1)*1.085;
			  
											    // 5.操作外设
				
     
									      // 6.为下一次测距做准备,为计数器重新赋初值
	       TH1 = 0;
         TL1 = 0;
 return  (time*0.017);


}
void openstatusLight()
{
  D6 = 0;
	D7 = 1;
  

}
void closestatusLight()
{
    D6 = 1;
		 D7 = 0;

}

int main()

{
	     
	       double dis;
	
	       Timer0Init();
	       while(1){
			      dis =  get_distance ();
	          if(dis > 10){
                 openstatusLight();
            }else{
            	   closestatusLight();
            }		
         }						
							
 }
	
	
	
	
超声波+舵机模块的代码
#include "reg52.h"
#include "intrins.h"
                     /*1.定时器0做计数器,先不打开定时器0
										   2.驱动超声波(Trig高电平持续10ms)
											  不断的执行下面代码while(1)
											 3.Echo标志,当低到高电平,打开计数器0
											    (       while(Echo)跳跃电平标志) 当高到低电平,关闭计数器0
										 	 4.计算时间TH0,TL0拼接后计算
											 5.操作外设
											 6.为下一次测距做准备,为计数器重新赋初值
											 
											 
										 
										 
										 */
sbit D6 = P3^6;
sbit D7 = P3^7;
sbit Trig = P1^5;
sbit Echo = P1^6;
sbit SW1 = P2^1;
sbit SW2 = P2^0;
sfr AUXR =0x8E;				 //?¨ê±?÷ ò?3??D??êμ??μ?μ?LED2
sbit sg90_con = P1^1;

int cnt = 0;
int jd;


void Delay10us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 25;
	while (--i);
}

void Delay200ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 9;
	j = 104;
	k = 139;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void Delay500ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	_nop_();
	i = 22;
	j = 3;
	k = 227;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}



void Timer1Init()		//100微秒@11.0592MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x10;		//设置定时器模式
	
	TL1 = 0;	//设置定时初值
	TH1 = 0;		//设置定时初值
}
	
void Timer0Init(void)		//0.5??@11.0592MHz
{
	AUXR &= 0x7F;		//?????12T??
	TMOD &= 0xF0;		//???????
	TMOD |= 0x01;		//???????
	TL0 = 0x33;		//??????
	TH0 = 0xFE;		//??????
	TF0 = 0;		//??TF0??
	TR0 = 1;		//???0????
	ET0 =1;
	EA  = 1;
}


void StartHC()
{
   Trig = 0;
	 Trig = 1;
	 Delay10us();
	 Trig = 0;
	 
}

void SG90_init_0()
{
  jd = 1;              //ò??aê? ???è3?ê??ˉ?a1£?òò?a???aê?ê?Dy×a0?è
	cnt = 0;           //íü???a??
	sg90_con = 1;



}
double get_distance ()
{
        double time;
	      StartHC();
				while(Echo == 0);
			  TR1 = 1;
			  while(Echo == 1);
				TR1 = 0;
										 	     // 4.计算时间TH0,TL0拼接后计算
		    time = (TH1* 256 + TL1)*1.085;
			  
											    // 5.操作外设
				
     
									      // 6.为下一次测距做准备,为计数器重新赋初值
	       TH1 = 0;
         TL1 = 0;
 return  (time*0.017);


}
void openstatusLight()
{
  D6 = 0;
	D7 = 1;
	
  

}
void closestatusLight()
{
    D6 = 1;
		 D7 = 0;

}
void openDusbin()
{

   jd = 4;                                               //------135?è
   cnt = 0;   
   Delay500ms();

}
void closeDusbin()
{
   jd  = 1;
	 cnt = 0;
	 Delay200ms()	;
  	


}
int main()

{
	     
	       double dis;
	       Timer0Init();
	       Timer1Init();
	       SG90_init_0();
	       while(1){
			      dis =  get_distance ();
	          if(dis < 10 || SW1 == 0 ){
                 openstatusLight();
							   openDusbin();
            }else {
            	   closestatusLight();
							   closeDusbin();
            }		
         }						
							
 }
	
 void Timer0Handler () interrupt 1
{
  cnt++;
	TL0 = 0x33;		//??????  ??@@@@  ×¢òaμ±±?±íá?Dèòa 3?ê??ˉ
	TH0 = 0xFE;	                    //3??μ??×°
	
	
	if(cnt < jd){
    sg90_con = 1;             //òò?a??é?μ?μ?ê±oòmain()£??íμèóú??μ???á
		                           //μúò?′?±?±í?íó|??à-μíá?£?1ê2?ó?D?óúμèóú?£main()?Dé?μ?3?ê??ˉμ???éùò?′??
  }else{                    //2¨D?????
    sg90_con = 0;
   }
	 
	 if(cnt == 40){
     cnt = 0;             //±?±í′?μ?×?′ó±?±íêy  cntò?μèóú0£??íè???μèóú??μ????£
		 sg90_con = 1;
  }
}
	
	
	
	
1.加入按键开盖

注意按键的时候 一定要停留很长一段时间,否则有可能照成失败。

if(dis< 10 || SW1 == 0)
{
    openstatusLight();
    closeDusbin();
}
2.加入震动传感器 开盖
  • CPU在数数,使用中断的方法。一旦发生低电平,用使用外部中断0
    当加入震动传感器的时候,震动传感器会不是很灵敏。使用外部中断优化
    默认情况下 超声波一直是大于10cm,而大于10cm的条件是 ,即会一直存在数数的操作 Delay200ms() ;,按键和dis<10总能找到空隙去勉强进行,但是震动传感器改变针脚的高低电平比较微弱。故会到这这种情况的发生。推荐解决的方法:震动传感器使用 外部中断优化
外部中断处理函数

INT0 P3^2 外部中断0
EX0 = 1; EA = 1;一般情况下,别的定时器0中断打开过了,总中断EA。故在外部中断处理的时候,只需要考虑EX0中断即可。EX0是IE寄存器的B0位。

  • EX0外部中断0,中断允许位。
  • EX0外部中断的触发条件是下降沿触发(电平的跳变),或者是低电平触发。
  • 震动传感器的作用是 是针脚的电平变低。 故外部中断中 不选用 电平跳变出触发,而是采用低电平触发的模式。
  • 低电平 触发 IT = 0;
  • 下降沿触发 IT = 1;
加入的代码

在main()之前加入第一段代码,在main()之后加入第二段代码。

  • main()里面定义mark_vibrate = 0
  • 打开外部中断INT0
  • 在外部中断处理函数中将标记mark_vibrate = 1;
  • 业务逻辑中不断的检测这个标志的变化,来进行开关盖。
void EX0_init()
{
EX0 = 1; 打开外部中断0(大前提是EA = 1总中断已经打开)
IT = 0 (低电平触发中断条件);
}
  • 一旦外部中断 满足低电平的条件,用标记符去记住他的状态。

  • main()中定义一个标记 int mark_vibrate = 0;默认是0,当发生震动的时候标记为1.CPU在数数,不能靠CPU来捕获这个震动信号,而是需要靠硬件的外部中断捕获,并标记。处理完之后,一定要记住去恢复这个标记。

  • 代码量多的时候,为了省空间,可以用char 代替int 型

void EX0_Handler interrupt 0
{
  mark_vibrate = 1;

}
问题的来源

while(1){
			      dis =  get_distance ();
	          if(dis < 10 || SW1 == 0 ){
                 openstatusLight();
							   openDusbin();
            }else {
            	   closestatusLight();
							   closeDusbin();
            }		
         }			


--------------------------------------------------			

void closeDusbin()
{
   jd  = 1;
	 cnt = 0;
	 Delay200ms()	;
  	


}
外部中断INTO 0

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • INT0 P3^2 外部中断0 固定的外部,P3 ^2;
  • 定时器0 的中断处理函数 标号 intertrupt 1
  • 外部中断0的标号 interrupt 0
3.添加蜂鸣器模块 滴滴声

当垃圾盖打开的时候,蜂鸣器滴的一声触发。
该项目 用于 sbit beep = P2^0;
只用于开盖的时候,
加入 beep = 0;
Delayxxms();
语句即可。

void openDusbin()
{
   jd = 3;
   cnt = 0;
   char n = 0;
   beep = 0;
   for(n= 0;n<4;n++){
    Delay150ms();
   }
   beep = 1;
   
   Delay2000ms();

}

再次复习进阶篇

1.SG90舵机的代码
#include "reg52.h"
#include "intrins.h"
                      /* 利用定时器0和IO口模拟PWM波形
								   1.Timer0intit();定时器0模式,初值,中断ET0 =1和EA =1,定时开启
									 2.SG90_init_0();舵机上电初始化电平,jd = 0,cnt= 0,sg90_con =1
									 3.3main()中while(1)角度jd不断的切换。 jd = 3,cnt =0,Delay2000ms();
									 3.3Timer0Handler() interrupt 1 cnt++,定时器再赋初值,以及满足条件的电平跳转,以及为下一周期的准备(cnt准备和高电平提前准备)。
									     */

sbit  SG90_con = P1^1;
sbit  AUXR = 0x8E;
char jd ;
char cnt = 0;

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


void Delay500ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 4;
	j = 129;
	k = 119;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}


void SG90_init_0()
{
  jd = 1;
	cnt = 0;
	SG90_con = 1;

}
void main()
{

    Timer0Init();
	  SG90_init_0();
    
	  while(1){
     
			 jd = 5;
			 cnt = 0;
			 Delay500ms();
			
			 jd = 1;
			 cnt =0;
			 Delay500ms();
   }
 }
void	Timer0Handler() interrupt 1 
	 {
		 cnt++;
		 TL0 = 0x33;		
	   TH0 = 0xFE;
		 if(cnt<jd){
        SG90_con = 1;
    }else{
       SG90_con = 0;
     }
     
		 if(cnt ==40 ){
       cnt = 0;
			 SG90_con = 1;

     }
   }

核心问题:使用定时器0的时候一定要注意打开中断

EA = 1; ET0 = 1;

疑难点
  • SG90_init_0() 刚上电驱动问题。&&当进刚进入下一个周期的驱动问题
    因为刚上电舵机的电平未知,故刚上电的时候,电平初始化为高电平。即相当于画了第一条线。定时器0中断处理函数在这个阶段的主要作用就是负责低电平的时间,当第一次爆表的时候(即第一次进入中断处理函数)cnt ++后等于1.此时if-else语句只进行第二个分支,即只负责低电平的输出。持续时间20ms,即爆表40次。
  • 角度切换下cnt = 0的问题。当切换角度的时候,cnt必须重0开始进行,防止出错。举一个例子,当jd = 1,的时候cnt = 36(此时可能是第6个周期内的cnt),而在这个时候,因为电平是横线拉低,舵机仍能满足条件。但是这个cnt如果不清零的话,就会去影响jd= 3时的PWM的波形的输出。

![在这里插入在这里插入图片描述

2.HC-SR04超声波模块的代码
#include "reg52.h"
#include "intrins.h"
                          /*  超声波驱动+计时器1(不使用中断)(受条件打开和关闭)+计算距离
													    实现不断的检测,当距离小于10cm的时候,黄灯亮。当距离大于等于,蓝灯亮。
															计时器的初值(TH0,TL0)是两个0.
															1.计时器模式等初始化Timer0_Init();
															2. get_distance 
															   2.1每测一次距 就驱动预热发波准备。Start_HC驱动超声波(Trig).,
															   2.2while(Ech0)设定条件,打开关闭计时器0.
															   2.3拼接TH0和TL0,计算时间.
														  3.main()不断的测距 业务逻辑if-else控制外设。
										
													
													
													*/
													
sbit AUXR = 0x8E;
sbit  D6    = P3^6;
sbit  D7    = P3^7;
sbit  Trig  = P1^5;
sbit  Echo  = P1^6;

void Delay10us()		//@11.0592MHz
{
	unsigned char i;

	i = 2;
	while (--i);
}


 void Timer1_Init()
{
   AUXR &= 0xBF;		
   TMOD &= 0x0F;		
	 TMOD |= 0x10;
   TL1   = 0;
	 TH0   = 0;
	// TF1  = 0;		中断溢出标志(EA,ET1,计时器1不使用中断)
}
   	

													
 void Start_HC()
{
	Trig = 0;      //类似于刚上电初状态电平
	
	Trig = 1;
	Delay10us();    //三条语句相当于画了一条悬空的线。起电平。延时持续时间。终电平
	Trig = 0;

}

 double get_distance()
{
     double time ;
	
	   Start_HC();
	   while(Echo == 0);
	   TR1 = 1;
	   while(Echo == 1);
	   TR1 = 0;
		
	
	 time = 1.085*(TH1 *256 + TL1);
	
	 TL1 = 0;    //计时器重新赋初值,再次准备计时
	 TH1 = 0;
	 
	 return time*0.017;


}
void main()
{ 
	double dis;
	Timer1_Init();
	while(1){
    
		 dis = get_distance();
		
		 if(dis <10){
        D6 = 0;
			  D7 = 1;
     }else{
        D6 = 1;
			  D7 = 0;
       }		 
	 }

 }
  	
	

核心问题;明白 Start_HC的意义,每次测距的时候都调用一次Start_HC
  • Start_HC发送超声波前的预热准备,即每次 测距的时候都调用他一次。并且注意一下 Start_HC的代码写法。(起电平,持续延时时间,终电平)。

double get_distance()
{
     double time ;
	
	   Start_HC();
	   while(Echo == 0);
	   TR1 = 1;
	   while(Echo == 1);
	   TR1 = 0;
		
	
	 time = 1.085*(TH1 *256 + TL1);
	
	 TL1 = 0;    //计时器重新赋初值,再次准备计时
	 TH1 = 0;
	 
	 return time*0.017;


}
  • 注意每次再测距完之后,需要重新的给计时器恢复到初值的状态。
    time = 1.085*(TH1 *256 + TL1);
	
	 TL1 = 0;    //计时器重新赋初值,再次准备计时
	 TH1 = 0;
	 
	 return time*0.017;

注意点

超声波模块未正常的时候,多启动几次。可能代码是正确的,但是有可能是模块造成的偶然错误。可以采用单片机上复位的标志解决即可。一般按4-5次复位键即可正常的启动

3.舵机SG90+超声波模块代码
#include "reg52.h"
#include "intrins.h"
                          /*  超声波驱动+计时器1(不使用中断)(受条件打开和关闭)+计算距离
													    实现不断的检测,当距离小于10cm的时候,黄灯亮。当距离大于等于,蓝灯亮。
															计时器的初值(TH0,TL0)是两个0.
															1.计时器模式等初始化Timer0_Init();
															2. get_distance 
															   2.1每测一次距 就驱动预热发波准备。Start_HC驱动超声波(Trig).,
															   2.2while(Ech0)设定条件,打开关闭计时器0.
															   2.3拼接TH0和TL0,计算时间.
														  3.main()不断的测距 业务逻辑if-else控制外设。
										
													
													
													*/
													
sbit AUXR = 0x8E;
sbit  D6    = P3^6;
sbit  D7    = P3^7;
sbit  Trig  = P1^5;
sbit  Echo  = P1^6;
sbit  SG90_con = P1^1;

char jd ;
char cnt = 0;


void Delay10us()		//@11.0592MHz
{
	unsigned char i;

	i = 2;
	while (--i);
}

void Delay500ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 4;
	j = 129;
	k = 119;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

 void Timer1_Init()
{
   AUXR &= 0xBF;		
   TMOD &= 0x0F;		
	 TMOD |= 0x10;
   TL1   = 0;
	 TH0   = 0;
	// TF1  = 0;		中断溢出标志(EA,ET1,计时器1不使用中断)
}

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

													
 void Start_HC()
{
	Trig = 0;      //类似于刚上电初状态电平
	
	Trig = 1;
	Delay10us();    //三条语句相当于画了一条悬空的线。起电平。延时持续时间。终电平
	Trig = 0;

}

void SG90_init_0()
{
  jd = 1;
	cnt = 0;
	SG90_con = 1;

}

 double get_distance()
{
     double time ;
	
	   Start_HC();
	   while(Echo == 0);
	   TR1 = 1;
	   while(Echo == 1);
	   TR1 = 0;
		
	
	 time = 1.085*(TH1 *256 + TL1);
	
	 TL1 = 0;    //计时器重新赋初值,再次准备计时
	 TH1 = 0;
	 
	 return time*0.017;


}
void openstatusLight()
{
    D6 = 0;
	  D7 = 1;

}
void closestatusLight()
{
     D6 = 1;
	   D7 = 0;


}

void openDusbin()
{
		 jd = 5;
		 cnt = 0;
		Delay500ms();	

}

void closeDusbin()
{
        jd = 1;
			 cnt =0;
	Delay500ms();	


}
void main()
{ 
	double dis;
	Timer1_Init();
	 Timer0Init();
	  SG90_init_0();
	while(1){
    
		 dis = get_distance();
		
		 if(dis <10){
			 openstatusLight();
			 openDusbin();
       
     }else{
			 closestatusLight();
       closeDusbin();
       }		 
	 }

 }
 
 
 
 
  	
void	Timer0Handler() interrupt 1 
	 {
		 cnt++;
		 TL0 = 0x33;		
	   TH0 = 0xFE;
		 if(cnt<jd){
        SG90_con = 1;
    }else{
       SG90_con = 0;
     }
     
		 if(cnt ==40 ){
       cnt = 0;
			 SG90_con = 1;

     }
   }


注意点

一般是把舵机的代码拷贝到超声波里面。
当从另一个代码里拷贝的时候,拷贝完成后一定要检测。注意定时器0要写在main()函数里面

  • 在while(1)里面调用不断的调用切换函数 就相当于不断的切换角度
    切换角度的函数
存在的问题 当手保持小于10ccm不动的时候,会出现BUG,即舵机不断的卡住。
  • 出现问题的原因:
void openDusbin()
{
		 jd = 5;
		 cnt = 0;
		Delay500ms();	

}
4.添加按键开盖的功能

按键只用负责开盖即可,因为超声波同时也在测距不停的工作,此时超声波检测到的是原状态 大于10cm,即体现在垃圾桶上即关盖的情况。

在这里插入图片描述
KE1 是P2.1口

5. 添加震动传感器开盖的功能代码(多次震动问题)

多次震动才能开盖,用户体验不好。

#include "reg52.h"
#include "intrins.h"
                          /*  超声波驱动+计时器1(不使用中断)(受条件打开和关闭)+计算距离
													    实现不断的检测,当距离小于10cm的时候,黄灯亮。当距离大于等于,蓝灯亮。
															计时器的初值(TH0,TL0)是两个0.
															1.计时器模式等初始化Timer0_Init();
															2. get_distance 
															   2.1每测一次距 就驱动预热发波准备。Start_HC驱动超声波(Trig).,
															   2.2while(Ech0)设定条件,打开关闭计时器0.
															   2.3拼接TH0和TL0,计算时间.
														  3.main()不断的测距 业务逻辑if-else控制外设。
										
													
													
													*/
													
sbit AUXR = 0x8E;
sbit  D6    = P3^6;
sbit  D7    = P3^7;
sbit  SW1   = P2^1;
sbit  Trig  = P1^5;
sbit  Echo  = P1^6;
sbit  SG90_con = P1^1;
sbit  vibrate = P3^2;

char jd ;
char cnt = 0;




void Delay10us()		//@11.0592MHz
{
	unsigned char i;

	i = 2;
	while (--i);
}

void Delay500ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 4;
	j = 129;
	k = 119;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

 void Timer1_Init()
{
   AUXR &= 0xBF;		
   TMOD &= 0x0F;		
	 TMOD |= 0x10;
   TL1   = 0;
	 TH0   = 0;
	// TF1  = 0;		中断溢出标志(EA,ET1,计时器1不使用中断)
}

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

													
 void Start_HC()
{
	Trig = 0;      //类似于刚上电初状态电平
	
	Trig = 1;
	Delay10us();    //三条语句相当于画了一条悬空的线。起电平。延时持续时间。终电平
	Trig = 0;

}

void SG90_init_0()
{
  jd = 1;
	cnt = 0;
	SG90_con = 1;

}

 double get_distance()
{
     double time ;
	
	   Start_HC();
	   while(Echo == 0);
	   TR1 = 1;
	   while(Echo == 1);
	   TR1 = 0;
		
	
	 time = 1.085*(TH1 *256 + TL1);
	
	 TL1 = 0;    //计时器重新赋初值,再次准备计时
	 TH1 = 0;
	 
	 return time*0.017;


}
void openstatusLight()
{
    D6 = 0;
	  D7 = 1;

}
void closestatusLight()
{
     D6 = 1;
	   D7 = 0;


}

void openDusbin()
{
		 jd = 5;
		 cnt = 0;
		Delay500ms();	

}

void closeDusbin()
{
      jd = 1;
			 cnt =0;
	 Delay500ms();	


}
void main()
{ 
	double dis;
	 
	Timer1_Init();
	 Timer0Init();
	 
	  SG90_init_0();
	while(1){
    
		 dis = get_distance();
		
		 if(dis <10 ||SW1 == 0||vibrate == 0){
			 
			 openstatusLight();
			 openDusbin();
			
       
     }else{
			 closestatusLight();
       closeDusbin();
       }		 
	 }

 }
 
 
 
 
  	
void	Timer0Handler() interrupt 1 
	 {
		 cnt++;
		 TL0 = 0x33;		
	   TH0 = 0xFE;
		 if(cnt<jd){
        SG90_con = 1;
    }else{
       SG90_con = 0;
     }
     
		 if(cnt ==40 ){
       cnt = 0;
			 SG90_con = 1;

     }
   }

出现的问题:多次震动从才能去开盖,用户体验不好
  • 按键产生的电平是较为持久的,即当CPU数完数,当检测距离,按键,震动的条件的时候,按键产生的电平仍然是存在的。即当按下一次按键,垃圾桶就会开盖
  • 震动动传感器产生的电平是短暂的,如果当产生的电平瞬间,没有被CPU捕获到(即,判断检测距离,按键,震动的条件的时候),这个震动传感器产生的电平信号就会丢失。
  • @@@@@@@解决方法,借助硬件外部中断的方法。
    硬件的作用是当检测到中断发生的时候(此中断用于检测P3^2口的针脚变化),通知CPU,即相当于把CPU调过来,让CPU先处理中断函数里的代码。处理完之后,CPU又自动返回到原来的位置,(在这里CPU原本是在数数)
  • 当CPU数数的时候,调用中断,已经把低电平的信号捕获到,即已经做好了标记,当CPU数完数之后,来到执行条件判断的代码,知道了在前几秒(外部发生了震动这个信号,即CPU距离,震动,按键中 震动条件满足:一般是上一刻的震动信号)。
  • 刚刚CPU正在数数,当来到条件判断的时候,这个标志符是 上一时刻的震动信号。
在这里插入代码片
解决的方法

解决方法,借助硬件外部中断的方法。
硬件的作用是当检测到中断发生的时候(此中断用于检测P3^2口的针脚变化),通知CPU,即相当于把CPU调过来,让CPU先处理中断函数里的代码。处理完之后,CPU又自动返回到原来的位置,(在这里CPU原本是在数数)

  • 当CPU数数的时候,调用中断,已经把低电平的信号捕获到,即已经做好了标记,当CPU数完数之后,来到执行条件判断的代码,知道了在前几秒(外部发生了震动这个信号,即CPU距离,震动,按键中 震动条件满足:一般是上一刻的震动信号)。

在入片描述

外部中断

注意加入P3^2引脚,注意全局变量的定义,注意标志的清零

char mark_vibrate = 0;



void EX0_Init()
{
   IT0 = 0;
	 EA  = 1;
	 EX0 = 1;

}
					
外部中断处理函数
  • Timer0Handler() interrupt 1 定时器0的中断 的中断号
  • EX0_Handler () interrupt 0 外部中断0的 中断号

void EX0_Handler () interrupt 0
{
mark_vibrate = 1;

}

6.已解决多次震动才能开盖的问题
#include "reg52.h"
#include "intrins.h"
                          /*  超声波驱动+计时器1(不使用中断)(受条件打开和关闭)+计算距离
													    实现不断的检测,当距离小于10cm的时候,黄灯亮。当距离大于等于,蓝灯亮。
															计时器的初值(TH0,TL0)是两个0.
															1.计时器模式等初始化Timer0_Init();
															2. get_distance 
															   2.1每测一次距 就驱动预热发波准备。Start_HC驱动超声波(Trig).,
															   2.2while(Ech0)设定条件,打开关闭计时器0.
															   2.3拼接TH0和TL0,计算时间.
														  3.main()不断的测距 业务逻辑if-else控制外设。
										
													
													
													*/
													
sbit AUXR = 0x8E;
sbit  D6    = P3^6;
sbit  D7    = P3^7;
sbit  SW1   = P2^1;
sbit  Trig  = P1^5;
sbit  Echo  = P1^6;
sbit  SG90_con = P1^1;

char jd ;
char cnt = 0;
char mark_vibrate = 0;



void Delay10us()		//@11.0592MHz
{
	unsigned char i;

	i = 2;
	while (--i);
}

void Delay500ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 4;
	j = 129;
	k = 119;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

 void Timer1_Init()
{
   AUXR &= 0xBF;		
   TMOD &= 0x0F;		
	 TMOD |= 0x10;
   TL1   = 0;
	 TH0   = 0;
	// TF1  = 0;		中断溢出标志(EA,ET1,计时器1不使用中断)
}

void Timer0Init(void)		//0.5毫秒@11.0592MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x33;		//设置定时初值
	TH0 = 0xFE;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	EA   = 1;
	ET0  =  1;
	TR0 = 1;		//定时器0开始计时
	 
}
void EX0_Init()
{
   IT0 = 0;
	 EA  = 1;
	 EX0 = 1;

}
													
 void Start_HC()
{
	Trig = 0;      //类似于刚上电初状态电平
	
	Trig = 1;
	Delay10us();    //三条语句相当于画了一条悬空的线。起电平。延时持续时间。终电平
	Trig = 0;

}

void SG90_init_0()
{
  jd = 1;
	cnt = 0;
	SG90_con = 1;

}

 double get_distance()
{
     double time ;
	
	   Start_HC();
	   while(Echo == 0);
	   TR1 = 1;
	   while(Echo == 1);
	   TR1 = 0;
		
	
	 time = 1.085*(TH1 *256 + TL1);
	
	 TL1 = 0;    //计时器重新赋初值,再次准备计时
	 TH1 = 0;
	 
	 return time*0.017;


}
void openstatusLight()
{
    D6 = 0;
	  D7 = 1;

}
void closestatusLight()
{
     D6 = 1;
	   D7 = 0;


}

void openDusbin()
{
		 jd = 5;
		 cnt = 0;
		Delay500ms();	

}

void closeDusbin()
{
      jd = 1;
			 cnt =0;
	 Delay500ms();	


}
void main()
{ 
	double dis;
	 
	Timer1_Init();
	 Timer0Init();
	   EX0_Init();
	  SG90_init_0();
	while(1){
    
		 dis = get_distance();
		
		 if(dis <10 ||SW1 == 0||mark_vibrate == 1){
			 
			 openstatusLight();
			 openDusbin();
			 mark_vibrate = 0;
       
     }else{
			 closestatusLight();
       closeDusbin();
       }		 
	 }

 }
 
 
 
 
  	
void	Timer0Handler() interrupt 1 
	 {
		 cnt++;
		 TL0 = 0x33;		
	   TH0 = 0xFE;
		 if(cnt<jd){
        SG90_con = 1;
    }else{
       SG90_con = 0;
     }
     
		 if(cnt ==40 ){
       cnt = 0;
			 SG90_con = 1;

     }
   }

void EX0_Handler () interrupt 0
{
    mark_vibrate = 1;

}
7.添加蜂鸣器模块

sbit beep = P2^0;

void openDusbin()
{
		 jd = 5;
		 cnt = 0;
	
	  
    beep = 0;
    Delay500ms();
    beep = 1;
	 
		Delay500ms();	

}
8.最后一个BUG解决,完美之做
  • openDusbin()函数里的cnt = 0,造成的这个问题。不断的调用openDusbin(),不断的执行cnt=0,影响了定时器0中断函数对 PWM波形的绘制,即根本原因是波形的不完善照成了舵机的不断抽抽。

最后一个BUG,特别的激动!!!!!!
当手在小于10cm保持不动,会造成舵机不断的抽抽,以及蜂鸣器的不断响
通过借助一个标识符来 解决这个问题即可。

  • 当小于10cm会一直调用openDusbin()这个函数
  • 这个函数里面的cnt = 0 变成0以后,抽触了一下。做一个标志位来判断。如果一直小于10cm,实际上他的状态没有改变
  • jd =1 jd =3之间的切换,才会造成垃圾盖的开和关。
  • 解决方法:定义一个变量,保存上一个状态的角度。关心上一次角度是多少,上一次角度不等于本次角度,就执行下面的代码,这样就规避了这个问题。
标志位规避的思想

全局变量里引入一个 char jd_bak;不去赋初值。。

  • 使用标志位的时候,注意标志位的初始化,以及更新标志位或者是恢复标志位
    main()函数进来,一直小于10cm一直调用打开垃圾桶函数,
  • 当第一次调用进入函数的时候,jd = 3与jd_bak 不相同,执行下面的代码。
    把 3赋予 jd_bak.(最后去执行 jd_bak = jd这个语句)(即,注意更新角度jd_bak的值)。即此时jd_bak = 3.当下一次 仍然是手保持不动,小于10cm,扔掉用这个函数jd = 3与 jd_bak相同,不满足条件,不执行下面的代码,完美的规避了这个问题。
  • 当距离大于10cm,也给一个标志位,即jd_bak = jd;
  • 打开盖的时候,需要这个标志位,注意判断条件,注意更新角度
  • 关闭盖的时候,也需要这个标志位jd_bak = jd;
    ####### 解决抽抽的代码核心
    char jd_bak ;(和jd一样不需要初始化)
void openDusbin()
{   
	  
		 jd = 5;
	   if(jd_bak != jd){
		 cnt = 0;
	
    beep = 0;
    Delay500ms();
    beep = 1;
	 
		Delay500ms();	
		jd_bak = jd;
			 
		 }

}

void closeDusbin()
{
      jd = 1;
	    jd_bak = jd;
			 cnt =0;
	    Delay1000ms();	
	  


}
9.我的第一个完美的垃圾桶代码 2022年8月28日下午17:00
#include "reg52.h"
#include "intrins.h"
                          /*  超声波驱动+计时器1(不使用中断)(受条件打开和关闭)+计算距离
													    实现不断的检测,当距离小于10cm的时候,黄灯亮。当距离大于等于,蓝灯亮。
															计时器的初值(TH0,TL0)是两个0.
															1.计时器模式等初始化Timer0_Init();
															2. get_distance 
															   2.1每测一次距 就驱动预热发波准备。Start_HC驱动超声波(Trig).,
															   2.2while(Ech0)设定条件,打开关闭计时器0.
															   2.3拼接TH0和TL0,计算时间.
														  3.main()不断的测距 业务逻辑if-else控制外设。
										
													
													
													*/
													
sbit AUXR = 0x8E;
sbit  D6    = P3^6;
sbit  D7    = P3^7;
sbit  SW1   = P2^1;
sbit  Trig  = P1^5;
sbit  Echo  = P1^6;
sbit  SG90_con = P1^1;
sbit beep  = P2^0;

char jd ;
char jd_bak;
char cnt = 0;
char mark_vibrate = 0;



void Delay10us()		//@11.0592MHz
{
	unsigned char i;

	i = 2;
	while (--i);
}

void Delay500ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 4;
	j = 129;
	k = 119;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}


 void Timer1_Init()
{
   AUXR &= 0xBF;		
   TMOD &= 0x0F;		
	 TMOD |= 0x10;
   TL1   = 0;
	 TH0   = 0;
	// TF1  = 0;		中断溢出标志(EA,ET1,计时器1不使用中断)
}

void Timer0Init(void)		//0.5毫秒@11.0592MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x33;		//设置定时初值
	TH0 = 0xFE;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	EA   = 1;
	ET0  =  1;
	TR0 = 1;		//定时器0开始计时
	 
}
void EX0_Init()
{
   IT0 = 0;
	 EA  = 1;
	 EX0 = 1;

}
													
 void Start_HC()
{
	Trig = 0;      //类似于刚上电初状态电平
	
	Trig = 1;
	Delay10us();    //三条语句相当于画了一条悬空的线。起电平。延时持续时间。终电平
	Trig = 0;

}

void SG90_init_0()
{
  jd = 1;
	cnt = 0;
	SG90_con = 1;

}

 double get_distance()
{
     double time ;
	
	   Start_HC();
	   while(Echo == 0);
	   TR1 = 1;
	   while(Echo == 1);
	   TR1 = 0;
		
	
	 time = 1.085*(TH1 *256 + TL1);
	
	 TL1 = 0;    //计时器重新赋初值,再次准备计时
	 TH1 = 0;
	 
	 return time*0.017;


}
void openstatusLight()
{
    D6 = 0;
	  D7 = 1;

}
void closestatusLight()
{
     D6 = 1;
	   D7 = 0;


}

void openDusbin()
{   
	  
		 jd = 5;
	   if(jd_bak != jd){
		 cnt = 0;
	
    beep = 0;
    Delay500ms();
    beep = 1;
	 
		Delay500ms();	
		jd_bak = jd;
			 
		 }

}

void closeDusbin()
{
      jd = 1;
	    jd_bak = jd;
			 cnt =0;
	    Delay1000ms();	
	  


}
void main()
{ 
	double dis;
	 
	Timer1_Init();
	 Timer0Init();
	   EX0_Init();
	  SG90_init_0();
	while(1){
    
		 dis = get_distance();
		
		 if(dis <10 ||SW1 == 0||mark_vibrate == 1){
			 
			 openstatusLight();
			 openDusbin();
			 mark_vibrate = 0;
       
     }else{
			 closestatusLight();
       closeDusbin();
       }		 
	 }

 }
 
 
 
 
  	
void	Timer0Handler() interrupt 1 
	 {
		 cnt++;
		 TL0 = 0x33;		
	   TH0 = 0xFE;
		 if(cnt<jd){
        SG90_con = 1;
    }else{
       SG90_con = 0;
     }
     
		 if(cnt ==40 ){
       cnt = 0;
			 SG90_con = 1;

     }
   }

void EX0_Handler () interrupt 0
{
    mark_vibrate = 1;

}

三丶基于蓝牙控制的智能插座 串口

单片机 通过串口 发送一些指令,控制外设,或者接受来自外设的信息。

串口

  • 串行通信接口,简称串口。COM口
  • 单片机连接电脑通过COM9口。数据一位位(高低电平 0101的传送)的顺序传送。通信线路简单,只要一对传输线就可以实现双向通信。
  • 串口是设备之间 接线通信的一种方式。(电脑文件通过 串口下载到单片机里面
  • 一对传输线(发送和接受)就可以实现双向通信。通信的本质就是数据的传送。单片机执行01HEX代码,一位一位的传过去的。
半双工&&全双工(面试会考)
  • 半双工只有一条数据线。同一时刻,要么只能发送,要么只能接受。
  • 而全双工 使用的是两天数据线。
  • 串口传送速度比较慢一点。
关于串口的电器标准和协议
  • RS-232 标准串口 电脑主机的串口 最高速率20kh/s. 电脑九针串口
    点对点的,只能一个发设备,一个收设备。即不能同时连接2个单片机。最大传输距离15m,数据传输的速度20kb/s,
    在这里插入图片描述

  • RS422 一个发设备,多个(10个)收设备。即能同时接10个单片机。传输距离1219M,传输速度更快。

  • RS-485
    可以接32个设备。

UART异步串行接口(串口的一种)
  • 异步体现在 两个设备的速度不同,单片机配置CPU速度比较慢,单片机配置CPU高,两者不同相提并论。不使用同一个时钟。
  • 单片机 和 电脑各自使用各自的一个时钟。称为异步。。。
  • RS232属于异步通信的一种。
  • RS232电平方式
    TTL电平。
    逻辑上的0和1.
    对于RS232来说,逻辑1为-3- -15V的电压,逻辑0是3-15V。
    笔记本与单片机RS232电平 通信,需要USB转串口线。(USB转232的接口。)
  • TTL电平方式
    TTL广泛的应用,逻辑1等于5V,逻辑0是0V

输出高电平>= 2.4V,输出低电平<=0.4V
输入高电平>=2.0V,输入低电平<=0.8V

前面继电器导通,单片机输出一个低电平 <0.4V,实际上是逻辑0

  • RS232电平 的单片机需要借助 USB转232的接口。)
    在这里插入图片描述

  • 单片机最小系统 需要借助 TTL转串口工具。( 工具上有ch340驱动 )
    TX—单片机P3^1口
    RX--------P3^0口
    在这里插入图片描述

  • 单片机自带ch340驱动的 只需要用Type -C数据线即可

模块学习

学习 通过串口传输,玩很多模块。
模块存在即合理。只是用即可,不用关心模块里面的架构和模块的实现
串口穿透的WIFI模块 ESP8266协议栈和架构
蓝牙模块 内部协议栈
语音模块 语音识别的序列

编程下的串口

  • 不管是TTL还是RS232,对于程序软件而言,都是一样的。对于编程而言是没有影响的,0代表的是低电平,1代表的是高电平。
    对于单片机输出和输出(接到单片机上的模块,单片机向模块输入的实际电压,模块向单片机输入的实际电压)而言:
    不一样的地方是,当外面接的是RS232电平,逻辑1 代表的是-单片机向外输出 - 3V到-15V。
    如果外面接的是TTL电平,逻辑1 代表的是单片机向外输出 >=2.4V

  • 代码层面上,逻辑1就是高电平。逻辑0就是低电平。

串口的编程关键要点

串口的接线主要关注的是TXD和RXD,
交叉接线,交叉接线,交叉接线。

  • 芯片一和芯片二通过串口进行传数据。
  • TXD数据的发送引脚,对应单片机的P3^1口
  • RXD数据的输入引脚,对应单片机的P3^0口

模块(蓝牙等)需要借助ch340 TTL插到PC端,才能和电脑实现通信。
带有CH340驱动的单片机,和PC端通信 只需要借助一根数据线即可。

一般情况下模块与单片机的通信

在这里插入图片描述

带有CH340驱的动单片机和PC端的驱动
在这里插入图片描述

数据的缓冲区,又称SBUF(是一个寄存器)
  • SBUF也是个寄存器,同时包括 输入数据缓冲区 和 输出数据缓冲区。
    • 他们的地址都是99H,但是代表的是两个独立的8位寄存器。
  • 发送缓冲区 只能写入,不能读
  • 接受缓冲区 只能读,不能写入
    可以从 SBUF里取数据,可以往SBUF里写数据。
  • 对数据的访问:赋值和被赋值的问题
    等号右边的理解成东西
    char data = SBUF 把SBUF里面的数据拿出来,放入data里
    SBUF = data 把数据这个东西 放入SBUF里面
MCU内核数据传输流程

在这里插入图片描述

MCU读取SBUF(输入数据缓冲器) 方向向左。。。。。。。外部设备
MCU写入SBUF(输出数据缓冲器)方向向右。。。。。。外部设备

  • 单片机和PC端的时钟是不同,双方通信 需要约定一个时钟,即通信的速度。即后面提到的波特率。
  • 可以借助单片机的串口助手,配置数据传输的速度,即波特率。
  • 单片机 借助串口助手 与PC端通信 ,需要配置 波特率(双方传递数据的速度),停止位,奇偶校验位等
  • 单片机和PC端的通信 需要配置SBUF寄存器和其他的寄存器(与停止位选择,奇偶校验位有关
    在这里插入图片描述

单片机发送字符给PC端

在C51中在Keil中 data是一个关键字,定义变量不要使用data作名字,可以任意使用一个如c_data;为一个名字。

  • 单片机发送数据,只需要往SBUF里写数据即可。至于SBUF里的数据怎样到PC端,这个怎样不用管

  • 从位置上判断发送缓冲器还是接收缓冲器
    SBUF位于等号的左边,是发送缓冲器。
    SBUF位于等号的右边,是接受缓冲器。

1.配置C51串口的通信方式

使用 串口助手里的 波特率计算器即可,粘贴到代码里即可

上位机和单片机都需要配置一下 波特率等

上位机 都过串口助手 配置即可
单片机 需要写代码配置。使用波特率计算器计算出代码,拿来用即可

  • 单片机配置波特率需要用到频率(涉及到时间),需要借助定时器,选择定时器1即可。
  • UART的数据位是8位 定时器选择12T模式即可
  • AUXR寄存器的地址是 AUXR = 0x8E; 头文件 #inlcude "intrins.h "
单片机心跳包的一些配置操作和注意点

证明我单片机还在,业务放在 其他线程里(如中断函数里面)

查看单片机发送的数据的方式,打开串口助手,选择文本模式,选择串口COM口,配置波特率等。可以在接收缓冲区里看到数据。

  • 注意选择定时器选择8位自动重载定时器。,(串口)
  • 串口定时器使用的是8位自动重载,而前面所用的定时器都是16位(没有自动重载)(如舵机等)注意,注意,注意
理解发送&&接收

SBUF 左边发送,右边接收,发送接收。
无论是PC端的发送缓冲区还是单片机端的发送缓冲区。都理解为发送端即可

一句话理解:1.先定位确定主体,是单片机还是PC端/外界。
然后这个动词 是 对刚刚确定的主体而言的。
2. SBUF位于等号左边是 发送缓冲器(发送端),等号的右边是 接收缓冲区(接收端)

                 在单片机里,发送缓冲区,即单片机发送数据(给外界发)
                 在单片机里,接收缓冲区,即单片机接收数据(来自外界的数据)
                  在PC端,发送缓冲区,即PC端向单片机发送数据
                  在PC端,接收缓冲区,即PC端接收来自单片机的数据

 
往发送缓冲区里面写入数据,就完成数据的发送。把数据放到这个发送缓冲区里,就完成了据向外界的发送。

在单机里 发送缓冲区,这个发送,是单片机执行的动作。
在单片机里 接收缓冲区 ,这个接受,是单片机接收的动作。
2.心跳包代码

注意定时器1选用 8位自动重载

#include "reg52.h"
#include "intrins.h"
sbit AUXR = 0X8E;

void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}

void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
}

void main()
{  
	  char data_msg = 'a';
	  UartInit(); 
    while(1){
		
    SBUF = data_msg;
		Delay1000ms();
		
   }  
}
3.串口相关的寄存器配置

void UartInit(void) //9600bps@11.0592MHz
{
PCON &= 0x7F; //波特率不倍速
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器1时钟为Fosc/12,即12T
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xFD; //设定定时初值
TH1 = 0xFD; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}

PCON寄存器

在这里插入图片描述

PCON具体文字解释

PCON &= 0x7F; //波特率不倍速

PCON &= 0x7F;
0111 1111   高八位清零,其他位保持不变(SMOD = 0,波特率不加倍)
SMOD =0,  表示波特率不倍速。
SMOD0 = 1 添加帧错误的检测,做精细通信的时候用到。或者发生帧错误以后,发出一个警报,当SMOD0配置成1的时候,需要看SCON具体的数据表现。,数据反馈在SM0/FEZ这个位
如下图中的SCON详细资料

  • 当发生帧错误的时候,可以查询到,即在PCON的SMOD0打开帧错误检测。
    在SCON里的SM0/FE里面检测到帧错误,(同时SM0的另一个作用是 和SM1共同决定 串口的工作模式)并且需要软件清零。无效的帧错误,由硬件自动清零。

SCON寄存器

八位数据,可变波特率

  • 可能与波特率,奇偶校验位,停止位等有关。

在这里插入图片描述

1.SM0和SM1串口模式的选择。
  • 串口的工作方式必须多种,满足不同场合的需求。通过SM0和SM1来配置串口的工作方式。
  • 推荐使用模式1和模式3

在这里插入图片描述
SMOD是波特率是否倍数 ,即方式0和方式2 都是固定的,此时,因为SMOD的改变就两种,故理解为固定的

方式1和方式3是可配的 因为都涉及到 定时器1的溢出率,涉及到TH1,涉及到定时器1,涉及到晶振。

  • 串口也需要定时器,一般采用定时器1。(对于C51而言),定时器运行要注意 打开定时器 TR1 = 1;即可

  • 单片机的速度由晶振提供,如何访问到晶振,借助定时器去访问到晶振。
    定时器的时钟周期是 晶振的倒数。

  • 串口涉及到波特率(数据交互应在同一频率,类似于相同的时钟),故也需要要通过定时器访问到晶振,需要用到定时器

  • 串口工作需要用到定时器(定时器可以访问到晶振,单片机的速度由晶振提供)。因为 波特率(有点像频率,速度)由内部定时计数器产生,用软键设置不同的波特率和选择不同的工作方式。

2 .SM2允许多级通信控制位(一般不使用)

涉及开始的电气协议,即一个发送端,多个接收端。一般不去使用。

3. REN允许/禁止串行接收控制位

REN =1 允许串口接收数据。
串口收不到数据,检测是否 REN允许串口接收数据。

4. TI 发送中断请求标志 RI中断请求标志位

收到数据以后,C51单片机硬件自动置1,向主机请求中断,响应中断后必须软件复位RI = 0

数据发送完之后,由硬件置1,向主机请求中断,响应中断后必须使软件复位
TI = 0.

在这里插入图片描述

5.TB8奇偶校验位&&RB8停止位

奇偶校验位 没使用 写0即可。
停止位没使用 写0即可。
不用关心 这两个即可。

SCON具体文字解释
  • SCON = 0x50; //8位数据,可变波特率
    0101 0000
    在这里插入图片描述
  • AUXR &= 0xBF; // AUXR减少电磁辐射
    AUXR &= 0xFE; //
    在这里插入图片描述

ALEOFF = 1 ;禁止ALE信号输出

  • TMOD &= 0x0F; //清除定时器1模式位
    TMOD |= 0x20; //设定定时器1为8位自动重装方式
    为什么以前定时器总是选择16位,因为量程大,累计71ms,并且需要对TL0和TH0初始值的装载,在定时器0里面需要重新装回原来的数据。

八位自动重装的好处是 在溢出的时候,提前把 写在TL1中的数据(初值)存放到TH1,由硬件完成,比软件(中断函数重新赋初值)效率高,更精确
在这里插入图片描述

第二个TMOD 0010 0000
左往右数 第三位,第四位决定了定时器的工作方式。

  • TH1和TL1 8位自动重装 TH1 =TL1相等
    TH1 和TL1体现在 定时器1的溢出率公式里面
    选择定时器1的工作方式在这里插入图片描述
  • 计算 8位UART下的TH1
   TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值

计算的思路:
9600 =2的SMOD次方/32*(定时器1的溢出率)

可计算出TH1的值  FD
计算TH0

SCON 选择好串口的工作方式后,即一般选择方式1,涉及到定时器1的使用。需要选择定时器1的工作方式。通过定时器1来使用系统的时钟。系统时钟是波特率的发生器。

移位等后面所有动作的发起,都需要依靠TX CLOCK和RX CLOCK,这些靠系统的晶振,通过定时器1来访问。定时器1的工作模式也需要进行配置。

在这里插入图片描述
作为波特率发生器的时候,我们选用定时器1的8位自动重载模式。为什么要选这个?以前我们是通过软件修改(在发生中断的时候)TH0和TL0。需要往寄存器中写一些东西。往寄存器里面放东西需要耗时间的。

  • 而选择8位自动重装是靠硬件完成的,溢出时由硬件自动的将 TH1存放的值装入TL1里面。需要配置TH1和TL1,是一样的。

1. 配置好串口的工作模式,一般选择01模式

2.配置串口工作模式下 的 定时器1的8位自动重载工作模式

在这里插入图片描述

  • 定时器1配置成10模式,而不影响定时器0的正常工作模式。
    高四位清零,低四位保持不变。
    选择10模式,低四位保持不变。
    0000 1111 &0x0F
    0010 0000 |0x20
  • 注意定时器1在使用的时候,要注意打开定时器1的运行控制位。TR1 =1 ;

3.配置好串口工作模式 ,配置好定时器1的工作模式,,然后在他们的基础上来计算TL0

TL1 = 0xFD;
TH1 = 0xFD; //根据公式算出初始值。

在这里插入图片描述

X = 253 HEX:FD

4.外加一个AUXR

  • 当用到晶振的时候,会产生辐射。配置AUXR寄存器里的ALEOFF标志。并且在涉及到AUXR时,必须 加一个 sbit AUXR = 0x8E;
    在这里插入图片描述

自己动手实现串口初始化

串口选择 工作模式 方式101模式)
定时器1 8位自动重载的模式
void UartInit()
{
  AUXR = 0x01;
  SCON = 0X40;      //选择串口工作方式1,不使能REN,不TI和RI
  TMOD &= 0X0F;
  TMOD |= 0X20;  //定时器1工作在8位自动重载
  
  TL1 = 0XFD;
  TH1 = 0XFD;  //9600波特率根据公式算出的初值
  TR1 = 1;
  //ES = 1;
  //EA = 1;
}

通过 串口定时器实现代码 直接粘贴即可。

1.发送一个字符串

  • 批量的一个字节一个字节的发,不断的调用发送一个字节的函数即可。
  • 字符串的最后隐藏着一个,结束标志 /0,即在while(*str !’ \0’ ),退出while()循环。
  • 在串口中的换行
    在串口中 \r\n相当于换行,并且都是从头开始对其模式。
    sendstring(“yyds!\r\n”);
小问题: 发送序列的紊乱

移位寄存器的操作过程也耗时间。第一组yyds,当第一组的第一个y发过去的时候,移位寄存器需要一定的时间。这段时间内会,因为此时仍然在不断的调用这void sendByte (char data_msg)这个函数会出现第一组的 y d s,但此时移位寄存器无法捕获到第一组的 y d s.捕获到第二组的y,重复以上的操作。

会成 串口助手上 y y y y y y d d d d d d d d d s s s ,发送序列紊乱的问题

在这里插入图片描述

解决发送序列紊乱的方法

利用软件层面的延时 或者是 智能延时,但是使用软件层面的延时会导致 刷屏的影响。

void sendData( char send_msg)
{
   SBUF = send_msg;
	Delay10ms();
	// while(!TI);
	// TI = 0;
}

软件层面的延时

定义一个软件层面的延迟。这个软件层面


void sendData( char send_msg)
{
   SBUF = send_msg;
	Delay10ms();
	 //while(!TI);
	// TI = 0;
}

软件层面延时。解决了发送紊乱,但存在高刷屏的问题

在这里插入图片描述

void sendData( char send_msg)
{
   SBUF = send_msg;
	//Delay10ms();
    while(!TI);
	 TI = 0;
}

@@@@TI发送中断请求标志(智能延时) 发送紊乱+不刷屏

虽然我们未使用中断,但是他仍然 当发送完数据的时候,会由内部硬件置1.
即是否打开中断,不影响硬件对TI的硬件上的置1,并且需要使用软件清0.(TI = 0)

  • 无论是否打开中断,当发送完数据的时候,TI都会被硬件置1
  • 未发送的时候TI=0;借助电平跳跃标志( while(TI ==0) );,不满足条件说明已经发送完数据了,并且需要软件的清零 TI =0;

#include "reg52.h"
#include "intrins.h"

                         /*   配置串口波特率等 Uatr_Init()实现单片机向外界发送数据
												     1. 借助串口助手 得到Uatr_Init() ,8位自动重载,定时器1
														 2.while(1) 实现发送字符串
						 2.1 调用字符串函数() sendString();
						 2.2 进而调用字符函数   sendData();(未使用中断也可以用)使用TI发送请求中断标志,很好用
                  
                          */
													
													
													
													
													
sbit AUXR = 0x8E;																		
			
void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
			
	void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
}
		

void sendData( char send_msg)
{
   SBUF = send_msg;
	 while(!TI);
	 TI = 0;
}

void sendString( char * send_msg )
{
   while(*send_msg != '\0'){
            sendData(*send_msg);
		        send_msg++;
}
}
 

void main()
{ 
	 char * send_msg = "yyds!\r\n";
	 UartInit();
	 while(1){
		  Delay1000ms();
		  sendString("yyds!\r\n");
		  }
      
		
    }
   

与C语言有关的小问题 字符常量
  • 特别的注意,在单片机里面不要使用 char *p = “YYDS!\r\n”

单片机是一位一位的发送的,*p 解引用操作 得到的是一大窜串 字符。
不可能用
p++来拆分成 一位一位的字母。

特别注意。 特别注意,特别注意。

传输字符串的时候一般使用下面的方法:
这样写即可

sendString("yyds!\r\n");

void sendString( char * send_msg )
{
   while(*send_msg != '\0'){
            sendData(*send_msg);
		        send_msg++;
}

利用串口点灯

单片机想要接收数据 ,必须 打开REN

让PC端通过串口,给单片机发送电灯的指令,单片机收到指令后点亮单片机对应的灯。

  • 通过SCON寄存器,SCON寄存器支持配置串口的工作模式,同时还有一个关键的作用REN = 1,允许/禁止串行接收控制位,允许串行口的接收状态。即,单片机允许 收到PC端的消息 的意思。想通过PC(串口助手)给单片机发数据,REN必须打开。

单片机 SBUF PC的关系

在这里插入图片描述

怎么知道 单片机发送完一个数据?怎么知道 单片机收到一个数据?

利用TI RI 当满足条件的时候,硬件自动置1.
另外 不使用中断 也可以使用这两个标志位(即使他们叫接收中断请求标志位),这两个东西 是靠硬件提供的。

  • RI 和TI理论上需配合中断使用,但无中断也可以使用。,借助硬件底层的电气特性,灵活的使用。
查询的方式

怎么知道收到数据,查询RI的值,如果RI是1(收到数据后由硬件置1),一旦被硬件置1,必须使用软件清0.

if(RI == 1){
RI = 0;
cmd =SBUF;
if(cmd == ‘c’){
D5 = 1;
}
}

串口实现开灯关灯代码

一切问题的出现 都是 你自己代码的造成的。不太灵敏。

 while(1){
		  Delay1000ms();
		  sendString("yyds!\r\n");
		
		  if(RI == 1)
				
			{   RI = 0;
				   cmd = SBUF;   //往MCU内核里读数据
				  if(cmd == 'c'){
            D6 = 1;
          }
          if(cmd == 'o'){
            D6 = 0;
          }

       }
		  }
存在一个小问题 ,发送指令不太灵敏(Delay1000ms导致的)

即当单片机 收到数据后RI = 1,(被硬件置1).只用当使用 软件清零的方式 RI = 0c才会重新恢复。故这个TI = 1的条件持续时间比较长的。造成的原因是 可能CPU正在数数,没能及时的响应条件的判断。

CPU正在 做梦 Delay1000ms();

解决不太灵的方法:利用中断的方式

####### Uart中断

  • 存在发送中断请求标志位和接收中断请求标志位。
    但是中断 系统 没有去特意的区分是 发送请求中断 还是 接收请求中断。

  • 故需要去人为的判断是 什么引起的中断:

RI == 1 证明是 接收中断请求标志位,一个中断系统,逻辑上必须区分是发送导致的还是接收导致的中断。




void Uart_Handler ()interrupt 4
{

    if(RI == 1){
       RI = 0;
			 cmd = SBUF;   //往MCU内核里读数据
				  if(cmd == 'c'){
            D6 = 1;
          }
          if(cmd == 'o'){
            D6 = 0;
          }
    }
		if(TI == 1){



   }


  
}		


  • 发送和接收引起的串口中断,使用的都是同一个函数。
    实现真正的全双工,不受影响。收发自如
利用串口中断解决不太灵敏 的代码
  • 无论是使用什么中断,当想用的时候都要打开两个中断允许位,一个总开关,一个分支开关。
    如 串口中断+总中断

  • 通过手册查到,配置相应的位即可



#include "reg52.h"
#include "intrins.h"

                         /*   配置串口波特率等 Uatr_Init()实现单片机向外界发送数据
												     1. 借助串口助手 得到Uatr_Init() ,8位自动重载,定时器1
														 2.while(1) 实现发送字符串
						 2.1 调用字符串函数() sendString();
						 2.2 进而调用字符函数   sendData();(未使用中断也可以用)使用TI发送请求中断标志,很好用
                  
                          */
													
													
													
													
													
sbit AUXR = 0x8E;	
sbit D6  = P3^6;
 char cmd;
			
void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
		
	void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
	
	ES =1 ;         //打开串口中断+总中断
	EA = 1;     
}
		

void sendData( char send_msg)
{
   SBUF = send_msg;
//	Delay10ms();
	 while(!TI);
	 TI = 0;
}

void sendString( char * send_msg )
{
   while(*send_msg != '\0'){
            sendData(*send_msg);
		        send_msg++;
}
}
 

void main()
{  
	 
	  D6 = 1;
	  UartInit();
	
	 while(1){
		  Delay1000ms();
		  sendString("yyds!\r\n");	  		
    }
   
		
	}	
void Uart_Handler ()interrupt 4
{

    if(RI == 1){
       RI = 0;
			 cmd = SBUF;   //往MCU内核里读数据
				  if(cmd == 'c'){
            D6 = 1;
          }
          if(cmd == 'o'){
            D6 = 0;
          }
    }
		if(TI == 1){



   }


  
}		
		
		
		
		
		
		




总结中断的触发条件

  • 中断类似于多线程,即main()中发生心跳包证明单片机还在,业务逻辑(开关灯等操作)等放在中断里面进行实现(如串口中断函数 处理 点灯)
    类似于多线程的能力

  • 一旦发生数据,立马让CPU先去处理这件紧急的事情,然后CPU在返回做原本不着急的事情。

  • 中断 的思想: 触发条件+ 操作 +错发条件恢复

  • 利用中断一定要打开串口开关
    如串口中的ES和EA开关
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

定时器0 16位一般用去舵机 使用中断 触发条件 :规格爆表 恢复操作:定时器1重新赋初值(TH0和TL0)

定时器1 16位 1一般在 超声波中 当成计时器1使用,不使用 中断,使用计时作用。即
但也存在恢复操作: 为下一次计时做准备,TH0 = 0; TLO = 0

定时器1 16位 使用中断 ???

定时器1 用于串口波特率 8位 自动重载模式 不使用中断,仅使用计时模式。而且是自动重载的方式。不用手动去恢复 定时器1的初值

外部中断 垃圾桶震动传感器使用 P3^2口 触发条件:低电平 或 低跳变触发

串口Uart中断 以及中断处理函数 硬件置1,需要软件清0
两个情况; 发送中断请求标志位 和 接收中断请求标志位
1个中断系统 ,故需要 去逻辑代码上去区分判断 到底是发送还是接受引起的
串口中断处理函数中:存在触发条件+操作+触发条件恢复

小白玩串口躲避大坑

区分 数字1 还是 字符1(引号1)
文本模式下的1是一个字符。

在这里插入图片描述

在这里插入图片描述

  • 对数字而言 ,上位机选择文本模式的时候。
  • 文本模式的1 是字符,ASSII码:49
  • 文本模式的0 是字符,ASII :48
    即 上位机 写的0是文本模式的 0,故代码查询满足条件使用(if (cmd == 48)).
    上位机 文本0 对于 程序而言是ASII码48
电脑处理是处理 对应字符的ASCII码
电脑写程序的时候   数字 110进制的,但是在数值上与HEX 16进制的1相等(即16进制的9以下 都和 10进制对应的数相等)
电脑写程序的时候   字符 1  对应的ASCII
上位机须选HEX模式下的0,单片机必须这样写。
if(cmd ==0)
{
 D5 = 1;
}


-------------
上位机选文本模式0,单片机必须写ASCII码(10进制的形态)
if(cmd == 48)
{
D5 = 1;

}
单词指令亮灯(很基础C语言解决方案,对于,长多指令不适用)

open cloese
cmd- open
cmd:open
c:o
c:c

  • 子字符串点亮 。
  • open可以点亮
  • askdakfkk en 虽然也包括en但无法点亮
cmd命令数组来确定
Status 静态局部变量

没有这个Status int i = 0,每次进入这个函数的时候,都会去执行。而当有了这个Status的时候,就执行一次。仅仅被初始化1次,不在函数调用的堆栈区。在静态变量区。

cmd数组格式
  if(RI == 1){
			
       RI = 0;
			 cmd[i] = SBUF;   //往MCU内核里读数据
       i++;
			
			if(i == SIZE){
         i =0;
       }          
			 
cmd字符串比较
if(strstr(cmd,"en") ){
         D6 = 0;
				 i = 0;
				 memset(cmd,'\0',SIZE);    
      }
  • strstr 需要头文件 #inlcude <string.h> C语言 用<>即可
    数据下来之后,可能会发生数据的紊乱。如何比较,即关心获得的子字符串里面是否有en或op或cl等。
    C语言能力泽中去写这个解决方法:
    #define SIZE 12; //宏的方式定义数组的大小,方便下次改变

if(strstr(cmd," se")){
D5 = 1;
I = 0;
memset(memset,‘\0’,SIZE);
}

  • 子字符串点亮 。
  • open可以点亮
  • askdakfkk en 虽然也包括en但无法点亮

子字符串点灯en开灯(对于超过12数组大小不适用,两个子字符串优先检测到的先执行)

#include "reg52.h"
#include "intrins.h"
#include <string.h>


                         /*   配置串口波特率等 Uatr_Init()实现单片机向外界发送数据
												     1. 借助串口助手 得到Uatr_Init() ,8位自动重载,定时器1
														 2.while(1) 实现发送字符串
						 2.1 调用字符串函数() sendString();
						 2.2 进而调用字符函数   sendData();(未使用中断也可以用)使用TI发送请求中断标志,很好用
                  
                          */
													
													
													
													
													
sbit AUXR = 0x8E;	
sbit D6  = P3^6;
#define SIZE 12

char cmd[SIZE];

			
void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
		
	void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
	
	ES =1 ;         //打开串口中断+总中断
	EA = 1;     
}
		

void sendData( char send_msg)
{
   SBUF = send_msg;
//	Delay10ms();
	 while(!TI);
	 TI = 0;
}

void sendString( char * send_msg )
{
   while(*send_msg != '\0'){
            sendData(*send_msg);
		        send_msg++;
}
}
 

void main()
{  
	 
	  D6 = 1;
	  UartInit();
	
	 while(1){
		  Delay1000ms();
		  sendString("yyds!\r\n");	  		
    }
   
		
	}	
void Uart_Handler ()interrupt 4
{
    static int i = 0;
    if(RI == 1){
			
       RI = 0;
			 cmd[i] = SBUF;   //往MCU内核里读数据
       i++;
			
			if(i == SIZE){
         i =0;
       }          
			 
			 if(strstr(cmd,"se") ){
         D6 = 1;
				 i = 0;
				 memset(cmd,'\0',SIZE);    
      }
			
			 if(strstr(cmd,"en") ){
         D6 = 0;
				 i = 0;
				 memset(cmd,'\0',SIZE);    
      }
    
    
		if(TI == 1){



   }


  
}		
		
	}	
		
		
		
		

串口原理协议(串行输入 输出经历了什么过程)

  • 使用串口的时候需要配置 校验位和停止位
  • 单片机接收来自外界的信息的时候,必须打开REN,此时数据可以收到了,但是为了使数据序列不紊乱的输出和不刷屏的输出 需要借助 串口的中断系统(其中的RI标志位,)。
  • SBUF 数据是一位一位的传输接收的。
  • 带入9600,根据公式算出了TL0(定时器1 8位自动重载模式)。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

蓝牙模块手机上点灯

TX和RX 单片机的 和 蓝牙模块 交叉接线
但是 VCC—5V
GND —GND
手机上,下载一个蓝牙助手。点击配对即可。

典型的蓝牙透传

单片机发的信息,原本被PC助手捕获,现在被蓝牙透传到手机上了。

  • 原本发到PC机上的信息 被蓝牙捕获了,同时PC助手上也会显示,手机上也会显示。

手机通过蓝牙助手 发数据给蓝牙模块 蓝牙模块通过TX传到单片机 单片机通过RX收到对应的AT指令 点灯

蓝牙透传

蓝牙比WIFI简单,因为WIFI还需要单片机给WIFI发送AT指令才能驱动联网

在这里插入图片描述

CH340 转TTL 电脑USB接口 蓝牙模块是TTL电平的
交叉接线。
PC端接上蓝牙 -----------手机 实现手机与上位机的通信。

即 在手机上发数据 数据到蓝牙 蓝牙TX传到上位机的RX口 上位机收到数据
(人眼看到了数据)

修改蓝牙名称

不能在手机上修改蓝牙的名称,最好在电脑上的串口助手里面写指令即可。
不

在这里插入图片描述

AT指令 AT+NAME = lanya
在这里插入图片描述

WIFI模块 学习重点(之后使用安信可的串口助手)

通过WIFI模块让单片机上网
ESP8266 (RTOS,WIFI协议栈,目前阶段不需要学习内部的东西,先学会51即可)
模块的出现,是为了方便使用

  • 蓝牙,ESP-01,等通信模块都是基于AT指令的设计,
  • C51单片机(MCU)向 中端设配器(网卡)或者数据终端设备发送AT指令,发送指令请求,目的是 驱动终端适配器干活,比如联网,数据传输等
  • AT指令驱动 一般都是基于串口的,ESP-8266一般出厂的波特率是115200.
    在这里插入图片描述

WIFI -CH340- PC端

  • 115200或者9600去连接
  • 通过PC端的串口调试助手,在串口助手上发AT指令,去驱动WIFI模块联网,去做服务器的一些访问。
  • ESP8266的技术支持是安信可公司做的,故推荐借助安信可 的调试助手即可。
  • AT指令从终端设备(数据终端设备)向终端适配器(数据电路终端设备)发送的。换句话说(电脑或则单片机) 通过115200的波特率 向网卡发送一系列的AT指令,来驱动网卡联网等操作。
常见的劝退的问题

基本上所有的AT指令,都需要 勾选发送新行。
电脑关闭防火墙的准备 否则WIFI模块无法连接到服务器,但可以连接到网络
1.搜defender防火墙
2.找到相应的内容关闭即可
在这里插入图片描述

3.未发勾选新行,在发送AT指令的时候,导致模块连接不上网络
在这里插入图片描述

现成的AT指令
AT+CWJAP_CUR="FAST_8274","20050505"
AT+CIPSTART="TCP","192.168.1.102",8888
AT+CIPMODE=1
AT+CIPSEND

ESP8266 使用前的准备

接线方式如下面的所示:

TTL转串口工具                          ESP8266
5.0(3.3)V                             VCC
GND                                      GND

剩下的交叉接线即可                      

打开安信可调试助手在这里插入图片描述
上电以后 发送重启指令的初始化消息。
显示了WIFI内部固件的版本,网卡的唯一物理地址。

  • 注意把防火墙关闭。否则模块无法连接上去。
  • 打开安信可调试助手,注意是否COM口选择对了,通过电脑的设备管理器来进行查看
  • 注意选择波特率 9600 或者是115200(虽然都是WIFI模块,但他们的波特率可能是不相同的)
详细的AT指令学习

查用方法论

入网设置
  • 设置工作模式(和手机一样的功能)
    注意指令是否写错,注意是否是半角英文符号,注意是否发送AT指令的时候勾选新行
AT+CWMODE=1   station设备模式
AT+CWMODE=2   AP路由器模式
AT+CWMODE=3  双模模式(前面的两种)


ESP8266做设备端,连接家里的路由器
让ESP8266连上家里路由器的指令
AT+CWJAP="TP-LINK_3E30","66666" 

看到下面的结果,表示连接成功。
在这里插入图片描述

查询WIFI模块的IP地址
AT+CIFSR

电脑路由器的IP地址即网关: 192.168.4.1

ESP8266
ESP8266模块的IP地址 192.168.1.104

ESP8266双模模式下的:
在这里插入图片描述

设置为 设备端单模式下:
在这里插入图片描述

笔记本电脑的IP地址 192.168.1.102 1

现成的网络调试助手

模块是通过TCP协议与电脑连接

  • ESP8266发送数据 给路由器 ,路由器中转给电脑。
  • 电脑这边设置一个TCP服务器
    通信的基础是 笔记本电脑连上了家里的路由器。
    ESP8266也连上了家里的路由器。
    即两者在同一个网段内是个前提条件,然后笔计本电脑自己主动请求作为一个同一网段里的 服务器。
  • 笔记本作为TCP服务器,提前输入自己的IP地址和端口号(在同一网段下,笔记本电脑作为TCP的服务端)
    -单片机(串口助手)给 ESP8266发送AT指令(在同一网段下,ESP8266作为客户端接入笔记本电脑的服务器)

两者通过以上的方式,实现在同一路由器下,两者能够有联系,并进行双方的通信。数据交换。
1.通过ipconfig查到PC笔记本电脑的IP地址,打开TCP服务器
2.TCP服务器本机地址写 笔记本电脑的IP地址
2.1 ESP8266在连接上路由器后(同一个局域网里面找到 笔记本IP自己的服务器)

  • PC1 与ESP8266相连,用于发送AT指令
  • 笔记本电脑 负责创建一个服务器 注意选择TCP服务器
  • ESP8266连上笔记本在同一网段下的服务器,即可实现线通信

在这里插入图片描述

两者连上了,进行通信

AT+CIPSEND=4   设置即将发送的数据长度
  • 安信可串口调试助手,相当于 ESP8266模块。
  • 在安可信串口调试助手里发送数据,即串口调试助手将 内容 通过USB-TTL发给ESP8266,ESP8266在同一网关下,内容转给笔记本TCP服务器。简单的理解成 给安信可串口发信息,相当于是 ESP8266给服务器发送 内容。
固定n个长度数字的发送
  • 在发送数据的时候,一定要注意 把发送新行去掉
  • 在发送命令的时候,一定要先勾选新行。
  • 在数据的时候,必须在方框里面写入数据,同时一定要去掉新行。
设置成透传模式
AT+CIPMODE=1
AT+CIPSEND

发送内容:(记住发送数据,去掉新行) +++。

在这里插入图片描述

ESP8266连上网过,下次上电会自动的联网。

单片机与ESP8266连接

单片机与ESP8266连接,通过同意网关下的手机或者笔记本建立一个服务器,ESP8266作为客户端连上服务器。即ESP8266(单片机,因为透传的模式,即数据同时发到单片机里面)。从手机TCP或者笔记本TCP,给ESP8266发数据,相当于给单片机发数据。即可以实现 手机借助同一WIFI实现控制灯的效果。
在这里插入图片描述

两条路

一个是基于串口的
一个是基于网络的

单片机发送AT指令实现联网

在这里插入图片描述

在这里插入图片描述

单片机与ESP8266相连。单片机给ESP8266发AT指令。模块可以接收数据,像蓝牙一样。

  • 服务器发送数据,数据被ESP8266接收数据,因为单片机和ESP相连,故数据会透传到单片机,实现基于WiFi模块的远程点灯。
  • 就是谁接ESP8266模块,来自服务器发送的数据就会被 谁 给接收走。
单片机串口通信给ESP8266自动发指令,驱动ESP联网,连服务器。
AT指令的数组

\ 表示转译,即保留这个字符串原来的意义。在引号前加\表示保留引号的原来意思。
指令必须发送换行

  • 定义的字符串太占空间了,指定一个code ,关键字。
    在这里插入图片描述
    在这里插入图片描述
AT+CWJAP_CUR="FAST_8274","20050505"
AT+CIPSTART="TCP","192.168.1.102",8888
AT+CIPMODE=1
AT+CIPSEND

1.连接网络指令

code char LJWL[] = "AT+CWJAP_CUR=\"TP-LINK_ZT\",\"95279527\"\r\n "

2.连接服务器指令

code char LJFWQ = "AT+CIPSTART=\"TCP\",\"192.168.1.102\",8880\r\n"

3.开启透传模式指令


char TCMS[] = "AT+CIPMODE=1\r\n"

4.开始传输数据指令

char SJCS[] = "AT+CIPSEND\r\n"
打开串口SPI助手,查看一下是否指令有误。

在这里插入图片描述

ESP8266执行指令成功后,会返回给服务器(单片机)一些数据

在这里插入图片描述

验证单片机把指令发给ESP8266模块,模块能够正常执行返回这些指令(返回给单片机,虽然服务器也有能力,但此时服务器未连接,相当于 ESP模块透传给了单片机,无法给到服务器数据)

黑盒测试

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

黑盒测试的时候出现问题及解决

在这里插入图片描述
根本问题是联网导致的,即无闹的延时不起作用。我们应该合适的时间去等待联网,如果没有联网成功,发送重启的指令(包含联网指令)重新进行联网,必须解决联网的问题,下面的每一步才有用。
在这里插入图片描述

解决方法 即后面的优化检测AT指令返回值

把人眼看到的问题所在,让单片机去解决。通过预先设定好程序,让单片机去帮忙检测模块的返回值。即检测指令的运行结果。

  • 优化8266使用,检测AT执行结果
    如果发送AT指令,通过不靠谱的延时方式,会出错。因为不知道 ESP8266在当下的连接网络需要多少秒。即固定延时,导致准确度不太好。可能导致很多的无效操作。
  • 固定的延时5s 可能对于连接网络而言,过快。即,ESP8266未连上网络就进行下面的AT指令输入
  • 固定的延时5s可能对于下面其他连接指令过慢,即可能那些透传,数据传输指令只需要1s,照成时间浪费

指令执行成功返回值
在这里插入图片描述

标志位解决问题
发送AT指令延时问题
  • 数据发送中断标志位,当数据放送成功以后,硬件拉高,需要软件清零。
  • 类似上面的思想,自己创建一个标志位。如下面所示。使用,使用之后清零。连接服务器使用OK,同时开启透传也使用OK,故第一次OK完之后,要清空标志位,以便下一次使用。
char AT_OK_Flag = 0;
char AT_Connect_Net_Flag = 0;

在一个地方完成一件事情后,赋予一个标志位,在另一个地方,完成对应事情后,清空这个标志位的方法

  • 标志位 发送AT指令获得一个标志位
  • 该标志位改变时,才允许发送下一个指令,即该标志位改变就相当于,AT指令发送成功,并且 模块完成了相对应的操作
  • 该标志位,只有在检测到对应成功返回值时,才会被改变

图片。。。。。。。。

串口初始化上电以后也会打印一些信息

故在打开串口之后,等待一些时间 即串口休眠以后 ,在发送AT指令
UartInit();
Delay1000ms(); //给ESP模块上电的时间
sendString(LJWL)

观察开发板上灯的状态 来知道是否连接成功

在这里插入图片描述

利用最后的标志位进行点灯,即到最后面了 保留被改变的标志位,用于点灯。
if(AT_Connect_Net_Flag){
D5 = 0;
}

观察成功返回值,在返回的内容中检测 关键字段

在这里插入图片描述

返回值可能时 一连贯发很多,故需要先引起注意。即检测到w就把w放到buffer的首位。
首字符引起注意:

当有w来了,必须保证w放到数组的头,至于后面的先不管,先引起注意。

  • 同时注意 buf的位置预设 返回值首位的位置预设
    当有数据的时候,定义一个临时变量,完成首位的位置预设。
    即,当检测当第一个字符时候,强制的将该字母下标转化为0.以WIFI_GOTIP和WIFI_Connect ,即当检测到w的时候,就引起注意,不管后面是什么字符

问题解析:
在这里插入图片描述

方法 当检测到第一个需要的关键字母的时候,就紧张起来了。
在这里插入图片描述

在这里插入图片描述

标志位满足条件改变 与自主清0
  • 标志位满足条件改变

在这里插入图片描述

buferf有效数据清零,承接模块的成功返回值

在这里插入图片描述

关于代码那些事

1.0代码


模拟ESP8266返回值 给单片机

在这里插入图片描述

一步一步调试,即通过给单片机发送指令来模拟模块的返回值。

图片。。。。。.

2.0代码 (优化,捕获联网失败状态,重新发联网指令)

可以通过单片机的灯来知道是否联网成功,连接服务器等一系列成功。灯一亮,就可以在服务器里面给单片机写指令控制其他外设了。

为了解决出现的fail,可以捕获联网失败状态。重新发联网指令。即当联网不成功,会有fail关键字,当检测到fail时重新联网即可。

  • 当出现fail的时候应该如何做。
    当连接失败的时候,可以用灯的闪烁来告诉用户。捕获fail失败的关键字,类似于前面的方法。
    提示:错误出现的时候,灯闪烁。但是在中断的时候最好不要去加延时函数。失败之后发送:重启的指令。
  • 在中断的时候实现灯闪烁尽量不使用延时函数,最好不要用d延时。但这个延时是必要的,即当该灯闪烁的时候告诉用户故障来了,即信号发多少都无无法捕获。故这里可以使用延时,因为外部不会去发其他的指令了。
for(i = 0; i<5;i++){
 D6 = 0
 Delay1000ms();
 D6 = 1;
 Delay1000ms();
}
sendString(RESET);
memset(buffer,'\0',12)  //这个也可以不用












。。。。。。。

两种方式

  • 单片机先发AT指令给 SPI串口助手,进行AT指令的初步人眼排查

在这里插入图片描述

  • 进一步判断 通过模块的返回值来判断

在这里插入图片描述

通过网络TCP通信控制LED代码 傻等的问题(不太成熟的代码,即可能某个地方出错,无法连上)
#include "reg52.h"
#include "intrins.h"
#include <string.h>


                         /*   配置串口波特率等 Uatr_Init()实现单片机向外界发送数据
												     1. 借助串口助手 得到Uatr_Init() ,8位自动重载,定时器1
														 2.while(1) 实现发送字符串
						 2.1 调用字符串函数() sendString();
						 2.2 进而调用字符函数   sendData();(未使用中断也可以用)使用TI发送请求中断标志,很好用
                  
                          */
													
													
													
													
													
sbit AUXR = 0x8E;	
sbit D6  = P3^6;
#define SIZE 12

char cmd[SIZE];


			
void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
		
	void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
	
	ES =1 ;         //打开串口中断+总中断
	EA = 1;     
}
		

void sendData( char send_msg)
{
   SBUF = send_msg;
//	Delay10ms();
	 while(!TI);
	 TI = 0;
}

void sendString( char * send_msg )
{
   while(*send_msg != '\0'){
            sendData(*send_msg);
		        send_msg++;
}
}
 

void main()
{  
	  code char LJWL[] = "AT+CWJAP_CUR=\"FAST_8274\",\"20050505\"\r\n";
	  code char LJFWQ[] = "AT+CIPSTART=\"TCP\",\"192.168.1.102\",8888\r\n";
	  char TCMS[] = "AT+CIPMODE=1\r\n";
	  char SJCS[] = "AT+CIPSEND\r\n";
	   char mark = 0;
	  D6 = 1;
	  UartInit();
	
	 while(1){
		  if(mark == 0){
		 
		   Delay1000ms();
		  Delay1000ms();
		  Delay1000ms();
		  sendString(LJWL);
		  Delay1000ms();
		  Delay1000ms();
		  Delay1000ms();
			 Delay1000ms();
				 Delay1000ms();
				 Delay1000ms();
				 Delay1000ms();
				 Delay1000ms();
		  Delay1000ms();
		  sendString(LJFWQ);
		  Delay1000ms();
		  Delay1000ms();
		  Delay1000ms();
		  Delay1000ms();
				 Delay1000ms();
				 Delay1000ms();
				 Delay1000ms();
      sendString(TCMS);
		  Delay1000ms();
		  Delay1000ms();
		  Delay1000ms();
		  Delay1000ms();
      sendString(SJCS);	
      Delay1000ms();
		  Delay1000ms();
		  Delay1000ms();
		  Delay1000ms();		 
		  mark =1;
    }else{

     Delay1000ms();	
		  sendString( "yyds\r\n");
    }
   
		
	}
}	
void Uart_Handler ()interrupt 4
{

    if(RI == 1){
			 static int i = 0;
       RI = 0;
			 cmd[i] = SBUF;   //往MCU内核里读数据
       i++;
			
			if(i == SIZE){
         i =0;
       }          
			 
			 if(strstr(cmd,"se") ){
         D6 = 1;
				 i = 0;
				 memset(cmd,'\0',SIZE);    
      }
			
			 if(strstr(cmd,"en") ){
         D6 = 0;
				 i = 0;
				 memset(cmd,'\0',SIZE);    
      }
    
    
		if(TI == 1){



   }


  
}		
		
	}	
		
		
		
		

优化检测AT指令执行结果(解决傻等问题,但存在若没连上网,不会自动重启联网指令)
最终版本即上面的2.0版本代码
#include "reg52.h"
#include "intrins.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>


                         /*   配置串口波特率等 Uatr_Init()实现单片机向外界发送数据
												     1. 借助串口助手 得到Uatr_Init() ,8位自动重载,定时器1
														 2.while(1) 实现发送字符串
						 2.1 调用字符串函数() sendString();
						 2.2 进而调用字符函数   sendData();(未使用中断也可以用)使用TI发送请求中断标志,很好用
                  
                          */
													
													
													
													
													
sbit AUXR = 0x8E;	
sbit D6  = P3^6;
sbit D7 = P3^7;
#define SIZE 12

code char LJWL[] = "AT+CWJAP_CUR=\"FAST_8274\",\"20050505\"\r\n";
code char RESET[] = "AT+RST\r\n";

code char LJFWQ[] = "AT+CIPSTART=\"TCP\",\"192.168.1.102\",8888\r\n";
char TCMS[] = "AT+CIPMODE=1\r\n";
char SJCS[] = "AT+CIPSEND\r\n";
char buffer[SIZE];
char tmp;
char Connect_Flag = 0;
char flag        = 0;
			
void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
		
	void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
	
	ES =1 ;         //打开串口中断+总中断
	EA = 1;     
}
		

void sendData( char send_msg)
{
   SBUF = send_msg;
//	Delay10ms();
	 while(!TI);
	 TI = 0;
}

void sendString( char * send_msg )
{
   while(*send_msg != '\0'){
            sendData(*send_msg);
		        send_msg++;
}
}
 

void main()
{  
	
	  D6 = 1;
	  D7 = 1;
	  UartInit();
	  Delay1000ms();//串口输出,上电稳定,最开始的乱码

		  
		  sendString(LJWL);
			while(!Connect_Flag); 
	   	while(!flag);
      flag = 0;
	                   
				
	
		  sendString(LJFWQ);
			while(!flag);
	        flag = 0;
				
				
      sendString(TCMS);	
			while(!flag);
		     
    
		
      sendString(SJCS);	
			
				
				
	    if(Connect_Flag ==1){
				
         D6 = 0;
       }
			 
			if( flag== 1){
				
         D7= 0;
       }
			 
		 			 
			 
			 
  while(1){

      Delay1000ms();	
			 Delay1000ms();	
			 Delay1000ms();	
			 Delay1000ms();	
		  sendString( "yyds\r\n");
    }
   
	}	
	void UartHandler() interrupt 4
	{
		
		 static int i = 0;
	   if(RI)
	   {
		   RI = 0;
		   tmp = SBUF;
		   if(  tmp =='W'|| tmp == 'O' || tmp == 'L' ||tmp == 'E'){
         i = 0;
     }
		
		 buffer[i] = tmp;
		 i++;
		 
		 		 
		 
		
		  
		 if(buffer[0] == 'E'&& buffer[1] == 'R'&&buffer[2] =='R'){
         for(i = 0; i<2;i++){
                D6 = 0;
                Delay1000ms();
                 D6 = 1;
                 Delay1000ms();
              }
               sendString(RESET);
         memset(buffer,'\0',12); 
			
     }
		 if(buffer[0] == 'W'&& buffer[5] == 'G'){
      Connect_Flag = 1;
			memset(buffer,'\0',12);
     }
		 
		 
		 
     if(buffer[0] == 'O'&& buffer[1] == 'K'){
      flag = 1;
			memset(buffer,'\0',12);
    }
		 
		
		if(buffer[0] == 'L' && buffer[1] =='1'){
         D7 = 0;
			  memset(buffer,'\0',12);
     
    }
   if(buffer[0] == 'L'&& buffer[1] =='0' ){
        D7 = 1;
			  memset(buffer,'\0',12);
     
    }
    if( i == 12){
      i = 0;
    }  

    }
   if(TI){

   }
	}

		
		
		
		
		
		
		
		
		
		
		

			
	




2.0版本文字解释
我的错误代码
  • 造成的原因是 由于 发指令的时候 应该是 && 写成了 ||
  • 注意 在TCP客户端 连接的时候 要选则对应的客户端。。。或者直接选择总的客户端即可。
void Uart_Handler ()interrupt 4
{
      static int i = 0;
    if(RI == 1){
       RI = 0;                                      //将引起警示的字母放tmp里
			 tmp[0] = SBUF;   //往MCU内核里读数据
		}
			 if(tmp[0]=='W'||tmp[0]=='O'||tmp[0]=='L'){
          buffer[0] = tmp[0];                         //每次都将警示字母放到 buffer的首位    
				  memset(tmp,'\0',1);
				  i=1;
        }  
       if(buffer[0]=='W'||buffer[0]=='O'||buffer[0]=='L'){
        buffer[i] = SBUF;                                     //判断首位。并接着顺序写入其他即可
				i++;
			 }
					 
				if(buffer[0]=='W'&& buffer[5]=='G'){
           Connect_Flag = 1;
           i = 0;
           memset(buffer,'\0',SIZE);			                  //检测到对应指令, 改变标志位状态
      }		
       	if(buffer[0]=='O'&& buffer[1]=='K'){
           OK_Flag   = 1;
           i = 0;
           memset(buffer,0,SIZE);			
      }

				if(i == 12){
              i=0;                                           //不能写过长的指令
           }
			
			 if(buffer[0]=='L'&& buffer[1]==':'&& buffer[2]=='1'){
				   i = 0;
				   D6 = 0;
           memset(buffer,0,12);			                             //服务器亮灯的操作
      }
			if(buffer[0]=='L'&& buffer[1]==':'&& buffer[2]=='0'){
				   i = 0;
				   D6 = 1;
           memset(buffer,'\0',12);			
      }
    
    
		if(TI == 1){



   }
 }
	

ESP8266 当设备 最终牛逼的代码

#include "reg52.h"
#include "intrins.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>


                         /*   配置串口波特率等 Uatr_Init()实现单片机向外界发送数据
												     1. 借助串口助手 得到Uatr_Init() ,8位自动重载,定时器1
														 2.while(1) 实现发送字符串
						 2.1 调用字符串函数() sendString();
						 2.2 进而调用字符函数   sendData();(未使用中断也可以用)使用TI发送请求中断标志,很好用
                  
                          */
													
													
													
													
													
sbit AUXR = 0x8E;	
sbit D6  = P3^6;
sbit D7 = P3^7;
#define SIZE 12

code char LJWL[] = "AT+CWJAP_CUR=\"FAST_8274\",\"20050505\"\r\n";
code char RESET[] = "AT+RST\r\n";

code char LJFWQ[] = "AT+CIPSTART=\"TCP\",\"192.168.1.102\",8888\r\n";
char TCMS[] = "AT+CIPMODE=1\r\n";
char SJCS[] = "AT+CIPSEND\r\n";
char buffer[SIZE];
char tmp;
char Connect_Flag = 0;
char flag        = 0;
			
void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
		
	void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
	
	ES =1 ;         //打开串口中断+总中断
	EA = 1;     
}
		

void sendData( char send_msg)
{
   SBUF = send_msg;
//	Delay10ms();
	 while(!TI);
	 TI = 0;
}

void sendString( char * send_msg )
{
   while(*send_msg != '\0'){
            sendData(*send_msg);
		        send_msg++;
}
}
 

void main()
{  
	
	  D6 = 1;
	  D7 = 1;
	  UartInit();
	  Delay1000ms();//串口输出,上电稳定,最开始的乱码

		  
		  sendString(LJWL);
			while(!Connect_Flag); 
	   	while(!flag);
      flag = 0;
	                   
				
	
		  sendString(LJFWQ);
			while(!flag);
	        flag = 0;
				
				
      sendString(TCMS);	
			while(!flag);
		     
    
		
      sendString(SJCS);	
			
				
				
	    if(Connect_Flag ==1){
				
         D6 = 0;
       }
			 
			if( flag== 1){
				
         D7= 0;
       }
			 
		 			 
			 
			 
  while(1){

      Delay1000ms();	
			 Delay1000ms();	
			 Delay1000ms();	
			 Delay1000ms();	
		  sendString( "yyds\r\n");
    }
   
	}	
	void UartHandler() interrupt 4
	{
		
		 static int i = 0;
	   if(RI)
	   {
		   RI = 0;
		   tmp = SBUF;
		   if(  tmp =='W'|| tmp == 'O' || tmp == 'L' ||tmp == 'E'){
         i = 0;
     }
		
		 buffer[i] = tmp;
		 i++;
		 
		 		 
		 
		
		  
		 if(buffer[0] == 'E'&& buffer[1] == 'R'&&buffer[2] =='R'){
         for(i = 0; i<2;i++){
                D6 = 0;
                Delay1000ms();
                 D6 = 1;
                 Delay1000ms();
              }
               sendString(RESET);
         memset(buffer,'\0',12); 
			
     }
		 if(buffer[0] == 'W'&& buffer[5] == 'G'){
      Connect_Flag = 1;
			memset(buffer,'\0',12);
     }
		 
		 
		 
     if(buffer[0] == 'O'&& buffer[1] == 'K'){
      flag = 1;
			memset(buffer,'\0',12);
    }
		 
		
		if(buffer[0] == 'L' && buffer[1] =='1'){
         D7 = 0;
			  memset(buffer,'\0',12);
     
    }
   if(buffer[0] == 'L'&& buffer[1] =='0' ){
        D7 = 1;
			  memset(buffer,'\0',12);
     
    }
    if( i == 12){
      i = 0;
    }  

    }
   if(TI){

   }
	}

		
		
		
		

ESP8266AT指令

AT+CWJAP_CUR="FAST_8274","20050505"
AT+CIPSTART="TCP","192.168.1.102",8888
AT+CIPMODE=1
AT+CIPSEND

ESP 工作为AP模式并当成服务器

  • ESP当成设备 接到了路由器 去访问电脑上的服务器。即ESP相当于客户端 与服务器单维的交互。
  • ESP当成路由器 服务器 AP模式下。

ESP +安心可调试助手

  • 第一步,AT+RST
  • 第二步,AT+CIPMUX=3设置成双模模式,即此时的ESP作为路由器,具有网关的IP地址。
  • 查看AP模式下即ESP8266路由器的网关地址。指令:AT+CIFSR
第一步,AT+CWMODE=3 ,第一步配置成双模模式
第二步,AT+CIFSR 查看ESP路由器的地址

用笔记本电脑连上ESP8266路由器
利用ipconfig查看笔记本的作为设备端的ip地址

第三步, AT+CIPMUX=1  打开使能多连接
第四步,AT+CIPSERVER=1建立TCPserver 建立ESP tcp服务器

用网络调试助手,笔记本以设备的方式接到ESP路由器上


第五步,数据交互
连上以后,此时的客户端 马上可以发数据给ESP8266,
ESP在路由器模式下,无法进入透传模式,利用指令发数据:
AT+CIPSEND=0,8  往通道0发数据,数据大小为8.


第六步,如何断开连接。
AT+CIPCLOSE=0

第一张图片
在这里插入图片描述

第二张图片

在这里插入图片描述

第三张图片
在这里插入图片描述
第四张图片
在这里插入图片描述

单片机当路由器和服务器模式控制

  • 电脑的串口调试助手让ESP8266工作在双模的模式下。即ESP8266此时是路由器,故需要发用电脑的串口发送一个指令去建立一个ESP8266TCP 服务器。
1。设置成路由器的模式。  AT+CWMODE = 2;   
char SZMS[] = "AT+CWMODE=2"

4G模块

初始LCD1602

DHT11温湿度传感器初始

在这里插入图片描述

  • 即能测温度,又能测湿度。
  • 全部校准,直接输出数字。
  • 超低能耗,休眠。
  • 直接输出结果,不用转化
    单片机是个机器,DHT11模块也是个机器,机器之间的对话只能是0101序列。DHT11看到这个序列后开始测温。测出结果后还是通过0101序列返回单片机,然后单片机驱动外设。
  • 数据格式 40位
  • 8位温度整数+8位温度小数 温度 16个字节
  • 8位湿度整数+8位湿度小数 湿度16个字节
  • 最后8位 校验位

DHT11时序图

  • C51想要DHT11进行测温,必须给DHT11发一些010101的序列驱动指令。
  • 看时序图 围绕 开始 结束 转折
驱动模块&&检测模块是否存在

单片机 把主机电平拉高了,而模块把主机信号拉低了。故在合适的时间检测被拉低,即代表模块存在,且成功被驱动。

  • 单片机发送序列 去驱动DHT11模块
  • 模块在被驱动之后,会做出反应,即将原本的主机上的电平拉低。通过检测该段时间 主机上的电平就知道是否 模块存在,并被成功的驱动了。由于前面主机在(单片机发送的驱动模块的序列里)主机被拉高持续20-40us,该段时间不确定,即导致后面检测 DHT11模块是否存在 带来了很大的困难,故20-40us利用while(!dht)的方式
  • 无法准确的去判断检测主机电平的时间 ,故将持续20-40us(区间)转换成了这样的条件 ,检测那段时间电平的跳变规律
时序逻辑分析

非标准协议去看时序,把时序换成代码。
在这里插入图片描述

在这里插入图片描述

如果检测到模块存在,那么单片机让灯亮(Delay60ms()后检测if(DHT == 0))

口诀:注意一般都是下坡到底端的值,以及上坡到顶端的值。

在这里插入图片描述

驱动模块,模块存在并拉低主机电平 的检验 代码实现

单片机负责驱动模块,将他们之间的主机线 拉高。
而当模块成功被驱动后,只有模块才能使在第一次的时候 把主机电平拉低。

  • 故 在合适的时间检测到主机电平是低电平,即证明模块被成功驱动
#include <regx52.h>
#include <intrins.h>    

                                   /*单片机驱动DHT11模块,模块存在且成功被驱动后,将主机电平(那条信号线上的)拉低
																	  通过在合适的时间(一般采用卡点方式确定,不采用延时的方式确定)
																		        1.配置串口波特率等
																					  2.发送01序列驱动时序,单片机将主机信号线(他们相连用的Data线)
																						3.检测 主机信号线上是否为低电平
																	 */
sbit DHT_Line  =  P3^3;
sbit D6        =  P3^6;
sbit AUXR      =  0x8E;
	void Delay1000ms()		//@11.0592MHz
{
	unsigned char i, j, k;

	_nop_();
	i = 8;
	j = 1;
	k = 243;
	do
	{
		do
		{
			while (--k);
		} while (--j);
	} while (--i);
}
	void Delay20us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 6;
	while (--i);
}

void Delay60us()		//@11.0592MHz
{
	unsigned char i;

	i = 25;
	while (--i);
}

	void UartInit(void)		//9600bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	AUXR &= 0xBF;		//定时器1时钟为Fosc/12,即12T
	AUXR &= 0xFE;		//串口1选择定时器1为波特率发生器
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFD;		//设定定时初值
	TH1 = 0xFD;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
}

void DHT11_Init()
{
    Delay1000ms();
	
	  DHT_Line =1;
	  DHT_Line =0;
	  Delay20us();
	  DHT_Line =1;
	  Delay60us();
}
	  

void main()
{
	 UartInit();
	 DHT11_Init();
	 if(DHT_Line == 0){
     D6 = 0;

    }
	while(1);



}
																	 
																	 
													

解决时间区间无法准确找到D点问题

在这里插片描述

通过卡电平的方式找到D点,即被模块拉低的最初的第一状态。即如果该目标点前面是高电平就用while(dh1)…,否则用while(!dh1)
d点前面时间区域电平是高电平。
找到D点:while(dh1)

  • 通过卡点的方式找到该点。即把该点前面的状态作为条件即可。
    e点前面是80us的低电平
    找到e点 :while(!dh1) 由低电平卡到高电平,即卡成功了,即找到了目标点。
    f点前面是拉高的80us电平
    找到f点 while(dh1);
    g点 之后一定是高电平,因为DHT11传输数据内容 都是以高电平传输的,内容(0或则1)不同,高电平持续的时间不同。
  • 单片机向DHT11发送01系列后,DHT11模块开始工作了,即图中 箭头开始传送数据
    在这里插入图片描述

之后,都是高电平,只不过是高电平持续的时间不同。代表的数据内容(0或者1)不同罢了。

  • 有了0和1的区别,就可以通过把内容转换成01系列来发送了。DHT11只是识别01序列,01序列单片机识别,单片机检索(01序列对应的操作)进行外设的操作。

g点发送数据系列01

g点之后的电平被拉高,电平持续的时间不同,用来代表是0或者1.记住万物01和这个一个道理。
图片
在这里插入图片描述

  • g点50us之后读
    找到g点后,50us之后读,如果低电平,说明是数据0。因为表示数据0持续的高电平的时间是26-28us,即50的时候检测到的已经是低电平了(越到下一轮去了)。而数据1相比数据0持续的时间比较长,即在g点之后的50us的时刻,依然保持原来的高电平。故通过检测g点50us之后主机线上的电平 就可以来区分是代表0还是代表1了。

g点之后读5轮

在这里插入图片描述

  • 数据一位一位的发送,反复上面9点发送数据系列01的步骤,得到了一个完整的数据序列。包括40个位,
  • 故需要这样的反复操作 需要读5轮,每一轮读8次(数据位),每8次形成一个数据(1字符8个字节),就可以把温湿度全部获取出来

DHT读取温湿度数据

DHT11_Start( ) 找到F点

在这里插入图片描述

void DHT11_Start( ) 最终是为了卡到f点,找到F点。

void DHT11_Start()
{








}

void Read_Data_From_DHT() 在G点开始发送数据

每次都需要重启驱动序列

在这里插入图片描述

每次读的时候都需要做 DHT11_Start( ) 重启驱动序列

  • 因为DHT11是低功耗,休眠的,故每次读的时候,都需要发送驱动序列
  • 每次读的时候,主机都要重启驱动序列(每一次,一次指的是发送40位数位,即一次完整的温湿度显示。
    在这里插入图片描述
卡g点之后Delay60ms() 检测主机的电平

在这里插入图片描述

定义一个容器,数组去存放数据位。
移位操作,tmp移位
  • tmp<<=1; (tmp = tmp<<1)

  • tmp |= flag;尾巴或上flag

  • mp8位初始值,不关心系统默认给他分配的内存原始数据。我读出高位后,向右移动,并且最高位消失。右边红色的盒子用于存放有效数据位

  • 原本蓝色的格子没了,剩下了红色的格子,即相当于我们读出了一个温湿度的其中的一个数据(例如温度的整数)

  • tmp用于移位然后或上最低位。

标志位flag检测到低电平就用0来表示。

为了说明截取了主要的代码(不完整的)
if(dht == 1){
flag =1;
}

Delay60us后面 找到下一个bit开始的位置

在这里插入图片描述

Delay60us() 后flag = 1下面的一条while(dht)等待语句

  • 注意时间要控制好,如果要是序列代表的是1,他持续高电平的时间比较久
    -需要等着1 变成0
  • 序列代表的是0的话,也需要等着高电平变成低电平
  • 借助while(dth)卡点的方式找到 下一个bit开始的位置。
  • 对于1来说,高电平持续时间是 70us,比较久 ,我们需要借助while卡一下。当dht主线上的高电平没了之后,while就退出了。而0暂且不用(教程里面说不用)
 for(j = 0;j<8;j++){  每轮读8while(!dth);
             Delay60us();
             
          if(dht == 1){
             flag =1;
            while(dht);       注意这个,注意这个。当高电平持续时间没了以后就退出了。
             }else{
                flag =0;
            }
重要理解(未用注释符号)
for(i = 0; i<5;i++){5for(j = 0;j<8;j++){  每轮读8while(!dth);
             Delay60us();
             
          if(dht == 1){
             flag =1;
            while(dht);       注意这个,注意这个。当高电平持续时间没了以后就退出了。
             }else{
                flag =0;
            }
           tmp=tmp<<1;  将盒子里的数据整体向左移动一位,最后一个格子里空出一位。
           tmp |= flag;   获取了一个位,即填到了最后面的一个空格子里。重复这俩个操作 ,使格子里面的所有内容被替换了一遍,。
         }   //tmp是一个8个格子的长盒子。通过for(j = 8)的循环,长盒子里面的8位被全部替换了。
         
   datas[i] = tmp;  for(i =5)循环  将这长盒子(8位)转换成一个数据(一个字符)。将每轮的tmp放到数据数组里面去承接,即此时已经是 一个 一个的DHT11显示的最小组成部分了
}
}

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值