51单片机学习笔记(三)

51单片机学习笔记(三)

目录

51单片机学习笔记(三)

AT24C02

AT24C02的概况​编辑

存储器类型​编辑

RAM和ROM各自的特点

RAM种类

ROM种类

存储器模型​编辑

I2C总线的时序

I2C总线介绍

I2C电路规范​编辑

I2C时序结构

AT24C02的结构和数据帧

AT24C02的结构​编辑

AT24C02的数据帧

AT24C02的应用示例

定时器扫描

DS18B20

DS18B20概况​编辑

DS18B20的结构​编辑

1-Wire介绍

控制DS18B20寄存器

ROM命令

DS18B20功能命令

温度分辨率配置

​编辑

DS18B20的应用示例

LCD1602

LCD1602概况

LCD1602的结构

引脚及应用电路​编辑

内部结构框图​编辑

时序结构​编辑

控制LCD1602

LCD1602的应用示例

pwm

pwm介绍及其原理

直流电机及其驱动

电机

电机驱动电路​编辑

使用pwm制作呼吸灯和调速电机

AD/DA转换器

AD/DA介绍

集成运算放大器

集成运算放大器内部结构​编辑

集成运算放大器构成电路

负反馈

基本运算电路

DA原理

T型电阻网络DA转换器​编辑

AD原理

逐次逼近型AD转换器​编辑

xpt2046和DAC模块​编辑

AD/DA使用示例

红外遥控

红外遥控介绍

硬件电路

发送与接收

外部中断

红外遥控调节直流电机示例


AT24C02

AT24C02的概况

AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息

存储介质:E2PROM

通讯接口:I2C

总线容量:256字节

工作电压:1.8V~5.5V

内部写周期:最大5ms

可按字节,随机,序列读

擦写次数:100万次

数据保持时间:100年

引脚功能
VCC、GND电源(1.8V~5.5V )
WP写保护(高电平有效)
SCL、SDAI2C接口
A0、A1、A2I2C地址

由于我所使用的单片机中的WP接GND所以,写保护解除。

SCL和SDA需要接两个阻值为4.7K的上拉电阻(配置I2C协议规范)。

存储器类型

RAM和ROM各自的特点

RAM:

RAM,全称为Random Access Memory,中文名为随机存取存储器。它可以随时进行读写操作,具有非常快的访问速度,通常被用作操作系统或其他正在运行中的程序的临时存储空间。

其主要特性是易失性,即当电源关闭时,其中存储的数据会立即消失。这种类型的存储器的性能优秀,但容量相对较小。

ROM:

ROM,全称为Read-Only Memory,中文名为只读存储器。ROM主要用于存放不需要更改的程序,如微程序、固定子程序和字母符号等。

它的特性是断电不会丢失数据,同时它不能随时进行读写操作,访问速度相对较慢。具有结构简单、集成度高、造价低、功耗小、可靠性高等优点。常用于嵌入电脑主板或做移动存储介质。

RAM种类

SRAM:

静态随机存取存储器,是随机存取存储器的一种。所谓的“静态”,是指这种存储器只要保持通电,里面储存的数据就可以恒常保持。其内部存储结构为一个D触发器,用电路来存储数据。SRAM的速度相对较快,这使其非常适合作为计算机或其他设备的高速缓存(Cache),因为它可以快速地存取数据。

在我所使用的单片机中,定义的变量,函数,特殊功能寄存器都存储在SRAM中。

特点:速度快,容量小,成本高。

DRAM:

动态随机存取存储器,是一种半导体存储器,主要的作用原理是利用电容内存储动态随机存取,即利用电容内存储电荷的多少来代表一个二进制比特(bit)是1还是0。

由于在现实中晶体管会有漏电电流的现象,导致电容上所存储的电荷数量并不足以正确的判别数据,而导致数据毁损。DRAM最为常见的系统内存,但只能将数据保持很短的时间。为了保持数据,DRAM使用电容存储,所以必须隔一段时间刷新。

电脑内存条,手机的运行内存都是使用DRAM。

特点:相比SRAM成本更低,容量更大。

ROM种类

Mask ROM:

掩膜编程的只读存储器MROM(Mask-programmed ROM),MROM是最早出现的ROM类型,其内容在生产过程中就被写入,无法再次修改。PROM允许用户一次性写入信息,写入后就无法更改,适合小批量生产。

PROM:

可编程只读存储器PROM(Programmable Read-Only Memory),这是一种可以用程序操作的只读内存。它允许使用称为PROM编程器的硬件将数据写入设备中。一旦PROM被编程后,就只能专用那些数据,并且不能再进行编程。因此,这种存储器最主要特征是只允许数据写入一次,如果数据烧入错误只能报废。

EPROM:

可擦除可编程只读存储器EPROM(Erasable Programmable Read Only Memory)。EPROM是一种基于浮栅晶体管单元的存储器,其特点是可以通过紫外线照射来擦除原有数据(紫外线照射器),然后重新写入新的数据。这种特性使得EPROM具有很高的灵活性,可以多次修改和删除其中的数据。

E2PROM:

电可擦除可编程只读存储器E2PROM(Electrically Erasable Programmable Read-Only Memory),这是一种可以通过电子方式多次复写的半导体存储设备。与EPROM相比,E2PROM不需要使用紫外线照射器来擦除和重编程,而是可以通过高于普通电压的作用来擦除和重编程(重写)。这使得E2PROM的操作更为便捷,无需从计算机中取出即可修改。在一个EEPROM中,当计算机在使用的时候可以频繁地反复编程,因此其寿命主要取决于写入次数

E2PROM有很多种类,例如:AT24C02是一个2K位串行CMOS E2PROM,内部含有256个8位字节,该器件通过IIC总线接口进行操作

缺点:读写速度慢,容量小,在清除和编辑设备时所花费的时间较长。

Flash:

闪存,全称快闪存储器(Flash Memory),是一种电子式可清除程序化只读存储器的形式,允许在操作中被多次擦或写的存储器。

闪存结合了ROM和RAM的长处。不仅具备电子可擦除可编辑(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据。它于EEPROM的最大区别是,FLASH按扇区(block)操作,而EEPROM按照字节操作。FLASH的电路结构较简单,同样容量占芯片面积较小,成本自然比EEPROM低,因此适合用于做程序存储器。

U盘,内存卡,单片机程序存储器,电脑的固态硬盘等设备都是应用了Flash。

硬盘:

机械硬盘,它们是由电机、磁盘和读写头等部件组成,通过磁性原理来存储数据。然而,由于其运动部件的存在,机械硬盘在运行时会产生噪音,并且相对较慢。

软盘:

软盘,又称为(Floppy Disk),是个人计算机(PC)中最早使用的可移动存储介质。软盘的读写是通过软盘驱动器完成的,这种驱动器设计能接收可移动式软盘。常用的软盘就是容量为1.44MB的3.5英寸软盘,它曾经盛极一时。

软盘是一种可移动的外部存储器,主要用于存储和传输小文件。虽然其容量相对较小,读写速度也较慢,但它具有可装可卸的优点,给用户带来了一定的便利。然而,随着科技的发展,尤其是U盘的出现,软盘的应用逐渐衰落,最终被淘汰。

光盘:

一种光学存储介质,利用激光原理进行读、写操作。它可以存放各种文字、声音、图形、图像和动画等多媒体数字信息。光盘的种类多样,包括CD、DVD、蓝光等。

CD光盘的容量一般为650MB到700MB,主要用于存储音频文件。DVD光盘的容量则达到了4.7GB,可以存储视频文件。而蓝光光盘(BD)是下一时代的高画质影音储存光盘媒体,其利用波长较短的蓝色激光读取和写入数据,因此得名“蓝光”。蓝光光盘的存储容量非常大,单层盘的存储容量就达到了25GB或50GB,双层盘更是可以达到50GB或100GB。然而,随着科技的发展,尤其是云存储的出现,光盘的使用逐渐减少。

存储器模型

存储器内部是一个电路的网状结构,在网状电路的行和列分别是地址总线和数据总线。通过检索地址总线(地址一次只能选中一行),并在对应地址写入数据,就能从数据总线读取数据。

其中Mask ROM结点处的电路连接方式如图片右上方,未选中的结点导线不连接,选中的结点处加上一个二极管以防止行串联。

而PROM结点处的连接方式如图右下方。未选中的结点处用两个方向相反的二极管连接,所以正常情况下是断路状态。但是,其中蓝色的二极管是一个容易被击穿的二极管。当接入高电流时,这个二极管被击穿,变成短路,之后就变成了选中结点的电路。因为击穿是一次性的,所以PROM也只能被编程一次。因为早期单片机需要外接一个高电压,用于击穿二极管,在存储过程中要烧毁二极管,所以单片机的下载程序也被称为“烧录程序”,这个叫法也一直沿用至今。

I2C总线的时序

I2C总线介绍

I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线

两根通信线:SCL(Serial Clock)、SDA(Serial Data)

同步、半双工,带数据应答

通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度

支持I2C通信的设备有:

1:0.96寸OLED屏幕是一种显示设备,其尺寸为0.96英寸,分辨率为128×64像素。这种屏幕采用了SSD1306驱动芯片,支持I2C接口方式进行通讯。

2:DS3231是一款低成本、高精度的I²C实时时钟(RTC),包含电池输入端,即使在断开主电源的情况下,也能保持精确的计时。

3:MPU6050是一种整合性6轴运动处理组件,具有高精度、低功耗和成本低廉的特点。由全球首例的三轴MEMS加速度计和三轴陀螺仪传感器组成,它还配备了数字运动处理器(DMP)引擎,以及主I2C和SPI串行通信接口。

除此以外,还有很多支持I2C通信的设备,这里不再举例。

I2C电路规范

1、所有I2C设备的SCL连在一起,SDA连在一起

(体现I2C作为总线的功能:总线上可同时挂载多个设备)

2、设备的SCL和SDA均要配置成开漏输出模式

(开漏输出:

当需要输出0时,引脚直接接地;

当需要输出1时,引脚浮空,相当于此端口在默认情况下什么都不接,呈高阻态;)

3、SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右

4、开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题

(虽然开漏输出和外接上拉电阻的共同作用可以实现IO口的双向功能,即可以读取外部输入电平的变化,但是其输出状态受外部电路影响较大。此外,使用开漏输出模式时还需要考虑到电阻的选择问题。因为不同的外部电路可能需要不同的上拉电阻值才能正常工作。)

接受设备内部电路:

1:MOS管:接低电平时导通,接高电平时断开

2:检测SCLK的电平情况,可理解为电压表

I2C时序结构

起始条件:SCL高电平期间,SDA从高电平切换到低电平

终止条件:SCL高电平期间,SDA从低电平切换到高电平

发送字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节

接收字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA,即将SDA置1)

发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

接受应答:在发送完一个字节之后,主机在下一个时钟接收一位数据, 判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA,即将SDA置1)

AT24C02的结构和数据帧

AT24C02的结构

1:EEPROM,用于存储数据

2、9:译码器

4:数据恢复

5:数据地址寄存器:每写入或读出一个数,寄存器地址加一;若读出不指定地址,默认为该寄存器内地址

6:器件地址比较器

7:串行数据逻辑

8:开始,停止逻辑

10:数据输出

AT24C02的数据帧

其中,AT24C02的固定地址为1010,可配置地址本开发板上为000 所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1。

字节写:在WORD ADDRESS处写入数据DATA

页写:

在输入一个数据后不发送终止条件,而是在AT24C02应答之后,继续输入7个数据,那么后面输入的数据将继续存储在EEPROM里面。EEPROM每接受到一个字节就产生一次应答,最终由主机发送终止条件结束写序列。

接收到每个数据后,字地址的低3位内部自动加1,高位地址位不变,维持在当前页内。当内部产生的字地址达到该页边界地址时, 随后的数据将写入该页的页首。

由于AT24C02中的EEPROM一共有8字节页,如果超过8个数据传送给了EEPROM,字地址将回转到该页的首字节,先前的字节将会被覆盖。

随机读:读出在WORD ADDRESS处的数据DATA

顺序读:

顺序读可通过“当前位置读”或“随机读”启动,主机接受后发送应答“0”,只要EEPROM接受到ACK,就自动增加字地址并随时钟发送后面的数据。若存储器地址达到末尾,地址自动回转到“0”,并继续顺序读。

主机不发送应答“0”,并发送终止条件时,停止顺序读。

AT24C02的应用示例

首先是将I2C模块化,代码如下:

#include <REGX52.H>
​
sbit SCL=P2^1;  //将引脚重命名
sbit SDA=P2^0;
​
void I2C_START()    //定义I2C起始条件函数,标志开始传输数据
{
    SDA=1;  //将SDA,SCL置高电平
    SCL=1;
    SDA=0;  //将SDA,SCL置低电平,
    SCL=0;
}
​
void I2C_STOP()     //定义I2C终止条件函数,标志结束传输数据
{
    SDA=0;  //将SDA置低电平
    SCL=1;  //将SDA,SCL置高电平
    SDA=1;
    
}
​
void I2C_SEND(unsigned char date)   //定义函数传输date
{
    unsigned char i;    //定义计数器
    for(i=0;i<8;i++)    //循环输入
    {
        SDA=date&(0x80>>i);     //将date的第i位赋值给SDA,高位在前
        SCL=1;  //将SCL置高电平,从机接受数据
        SCL=0;  //将SCL恢复低电平
    }
}
​
unsigned char I2C_READ()    //定义函数接受date
{
    unsigned char i,date=0x00;  //定义计数器,返回值
    SDA=1;      //主机释放SDA
    for(i=0;i<8;i++)    //循环接收
    {
        SCL=1;  //将SCL置高电平,主机接受数据
        if(SDA==1) date=date|(0x80>>i);     //将第i次的SDA数据赋值给date的第i位,高位在前
        SCL=0;  //将SCL恢复低电平
    }
    return date;    //返回date
}
​
void I2C_SENDACK(unsigned char a)   //定义函数发送应答
{
    SDA=a;  //将a赋值给SDA
    SCL=1;  //从机接受数据
    SCL=0;  //将SCL恢复低电平
}
​
unsigned char I2C_READACK() //定义函数接受应答
{
    unsigned char a;    //定义返回值
    SDA=1;  //主机释放总线
    SCL=1;  //将SCL置高电平,主机接受数据
    a=SDA;  //a接受SDA
    SCL=0;  //将SCL恢复低电平
    return a;   //返回a
}

接下来是将AT24C02模块化

#include <REGX52.H>
#include "I2C.H"
#include "delay.h"
void AT24C02_SEND(unsigned char address,unsigned char date)
//定义函数写入数据到AT24C02
{
    I2C_START();    //发送起始条件
    I2C_SEND(0xa0);     //选中设备AT24C02,选择写入模式
    I2C_READACK();      //接受从机应答
    I2C_SEND(address);  //写入地址
    I2C_READACK();      //接受从机应答
    I2C_SEND(date);     //写入数据
    I2C_READACK();      //接受从机应答
    I2C_STOP();     //发送终止条件
    Delay(5);   //由AT24C02芯片手册得写周期为5ms,所以延时5ms
}
​
unsigned char AT24C02_READ(unsigned char address)
//定义函数读取AT24C02
{
    unsigned char date;     //定义返回值
    I2C_START();    //发送起始条件
    I2C_SEND(0xa0);     //选中设备AT24C02,选择写入模式
    I2C_READACK();      //接受从机应答
    I2C_SEND(address);      //写入地址
    I2C_READACK();      //接受从机应答
    I2C_START();        //发送起始条件
    I2C_SEND(0xa1);     //选中设备AT24C02,选择读取模式
    I2C_READACK();      //接受从机应答
    date=I2C_READ();    //date接受数据
    I2C_SENDACK(1);     //发送应答给从机
    I2C_STOP();     //发送终止条件
    return date;    //返回date
}

接下来是用于测试以上两个模块得主函数

#include <REGX52.H>
#include "I2C.H"
#include "DELAY.H"
#include "AT24C02.H"
#include "LCD1602.H"
void main()
{
    unsigned char a=0;
    LCD_Init();
    while(1)
    {
        if(P3_1==0)     //按下按键1,a加一
        {
            while(P3_1==0);
            a++;
        }
        if(P3_0==0)     //按下按键2,将a保存到AT24C02
        {
            while(P3_0==0);
            AT24C02_SEND(1,a);
        }
        if(P3_2==0)     //按下按键3,读取保存值
        {
            while(P3_2==0);
            a=AT24C02_READ(1);
        }
        LCD_ShowNum(1,8,a,1);   //显示a
    }
}

定时器扫描

制作一个秒表(使用定时器扫描数码管和按键,使用AT24C02保存数据)

首先,为了实现用定时器扫描数码管,于是将数码管的扫描代码改写如下:

#include <REGX52.H>
#include "Delay.h"
code unsigned char x[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x40};
unsigned char y[]={0,0,0,0,0,0,0,0,0},i=1;  //用数列y来保存对应数码管显示的内容(索引为位置,值为显示内容)
//数列y为外部可调用变量。
​
void light(unsigned char postion,unsigned char value)   //数码管扫描函数
{
        P0=0x00;
    switch(postion)
    {
        case 1:P2_4=1;P2_3=1;P2_2=1;break;
        case 2:P2_4=1;P2_3=1;P2_2=0;break;
        case 3:P2_4=1;P2_3=0;P2_2=1;break;
        case 4:P2_4=1;P2_3=0;P2_2=0;break;
        case 5:P2_4=0;P2_3=1;P2_2=1;break;
        case 6:P2_4=0;P2_3=1;P2_2=0;break;
        case 7:P2_4=0;P2_3=0;P2_2=1;break;
        case 8:P2_4=0;P2_3=0;P2_2=0;break;
    }
    P0=x[value];
}
​
void light1()   //定义函数逐次扫描数码管
{
    light(i,y[i]);
    i++;
    if(i>=9) i=1;
}

接下来,定义检测按键按下的独立按键扫描函数(由于之后使用定时器扫描独立按键,所以可以省略延时消抖)代码如下:

#include <REGX52.H>
unsigned char order()
{
    unsigned char a=0;
    if(P3_1==0){
//      Delay(5);
        //while(P3_1==0);
        //Delay(20);
        a=1;
    }
    if(P3_0==0){
//      Delay(5);
        //while(P3_0==0);
        //Delay(20);
        a=2;
    }
    if(P3_2==0){
//      Delay(5);
        //while(P3_2==0);
        //Delay(20);
        a=3;
    }
    if(P3_3==0){
//      Delay(5);
        //while(P3_3==0);
        //Delay(20);
        a=4;
    }
    return a;
}
​
unsigned char order1()
{
    unsigned char a,last_a,state;   //定义变量检测按键状态和按键上一个状态
    a=order();
    if(a==1&&last_a==0) state=1;    //但按键状态非0,且上一个状态为0时,返回该状态
    if(a==2&&last_a==0) state=2;
    if(a==3&&last_a==0) state=3;
    if(a==4&&last_a==0) state=4;
    
    last_a=a;
    return state;
}
​

最终,该代码的主函数如下:

#include <REGX52.H>
#include "timer0.h"
#include "order.h"
#include "AT24C02.H"
#include "smg.h"
#include "i2c.h"
#include "delay.h"
​
int cnt=0,cnt0=0;
unsigned char us=0,second=0,minute=0;
unsigned char us1=0,second1=0,minute1=0;
unsigned char mod=0,mode=1;
void main()
{
Timer0Init();   
    while(1)
    {
                    if(us>=100)
                    {
                        us=0;
                        second++;
                        if(second>=60)
                        {
                            second=0;
                            minute++;
                            if(minute>=60)
                            {
                                minute=0;
                            }
                        }
                    }
        
        if(mod==2)
        {
                    us=0;
            second=0;
            minute=0;
        }
        if(mod==3)
        {
            AT24C02_SEND(1,us);
            AT24C02_SEND(3,minute);
            AT24C02_SEND(2,second);
        }
    if(mod==4)
        {
            us=AT24C02_READ(1);
            second=AT24C02_READ(2);
            minute=AT24C02_READ(3);
        }
        y[8]=us%10;
        y[7]=us/10;
        y[6]=10;
        y[5]=second%10;
        y[4]=second/10;
        y[3]=10;
        y[2]=minute%10;
        y[1]=minute/10;
    }
}
​
void Timer0_Rountine()   interrupt 1
{
    
    TL0 = 0x66; 
    TH0 = 0xFC;
    cnt++;
    if(cnt%10==0)
    {
        mod=order1();
        if(mod==1) mode++;
    }
    if(cnt%2==0)
        light1();
    if(cnt==1000)
        cnt=0;
    if(cnt%10==0&&mode%2==0)
    us++;
}

DS18B20

DS18B20概况

DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点

测温范围:-55°C 到 +125°C。

通信接口:1-Wire(单总线)。

其它特征:内置温度报警功能。

供电范围:3.0V至5.5V。

可形成总线结构:每片DS18B20都有一个独一无二的64位序列号,所以一个1-Wire总线上可连接多个DS18B20设备。

温度转换时间在转换精度为12-Bits时达到最大值750ms。

内部温度采集精度可以由用户自定义为9-Bits至12-Bits。

引脚功能
VDD电源(3.0V ~ 5.5V)
GND电源地
DQ单总线接口

DS18B20的结构

64-BIT ROM:作为器件地址,用于总线通信的寻址

SCRATCHPAD(暂存器):用于总线的数据交互

EEPROM:用于保存温度触发阈值和配置参数

DS18B20的SCRATCHPAD(暂存器)组织结构如上图所示。该存储器包含了SRAM暂存寄存器和存储着过温和低温(TH和TL)温度报警寄存器及配置寄存器的非易失性EEPROM。值得注意的是当DS18B20的温度报警功能没有用到的时候,过温和低温(TH和TL)温度报警寄存器可以当做通用功能的存储单元。

EEPROM寄存器:在EEPROM寄存器中的数据在设备断电后是不会丢失的;在设备上电后EEPROM的值将会重新装载至相对应的暂存寄存器中。当然,在任何其他时刻EEPROM寄存器中的数据也可以通过重新装载EEPROM命令[B8h]将数据装载至暂存寄存器中。主设备可以在产生读时序后,紧跟着发送重新装载EEPROM命令,则如果DS18B20正在进行重新装载将会响应0电平,若重新装载已经完成则会响应1电平。

DS18B20的供电

DS18B20可以由外部供电,或者可以由“寄生电源”供电,这使得DS18B20可以不采用当地的外部电源供电而实现其功能。“寄生电源”供电方式在远程温度检测或空间比较有限制的地方有很大的应用。

寄生电源供电方式:

下图展示的就是DS18B20的“寄生电源”控制电路,其由DQ口拉高时向其供电。总线拉高的时候为内部电容(C pp)充电,当总线拉低是由该电容向设备供电。当DS18B20为“寄生电源”供电模式时,该VDD引脚必须连接到地。

在“寄生电源”供电模式下,只要工作在指定的时序下,则该1-Wire总线和Cpp可以提供给DS18B20足够的电流来完成各种工作以及满足供电电压。然而,当DS18B20正在进行温度转换或正将暂存寄存器中的值拷贝至EEPROM时,其工作电流将会高至1.5mA。通过1-Wire总线上的上拉电阻提供的电流将会引起不可接受的电压跌落,同时将会有很大部分电流由Cpp提供。为了保证DS18B20有足够的电流供应,有必要在1-Wire总线上提供一个强有力的上拉,不管此时在进行温度转换还是正将暂存寄存器中的值拷贝至EEPROM中。值得注意的是,1-Wire总线必须在温度转换命令[44h]或暂存寄存器拷贝命令[48h]下达10uS后提供一个强有力的上拉,同时在整个温度转换期间(Tconv)或数据传送(Twr=10ms)期间总线必须一直强制拉高。当强制拉高时该1-Wire总线上不允许有任何其他动作。

“寄生电源”供电方式在温度超过+100℃时不推荐使用,因为在超过该温度下时将会有很大的漏电流导致不能进行正常的通信。实际应用中,在类似的温度状态下强烈推荐该DS18B20由外部供电电源供电。

在某些情况下,总线上的主设备可能不知道连接到该总线上的DS18B20是由“寄生电源”供电还是由外部电源供电。为了得到这些信息,主设备可以在发送一个跳过ROM命令[CCh]之后再发送一个读取供电方式命令[B4h]再紧跟一个“读取数据时序”。在读取数据时序中,“寄生电源”供电方式的DS18B20将会将总线拉低,但是,由外部供电方式的DS18B20将会让该总线继续保持高。所以,如果总线被拉低,主设备就必须要在温度转换期间将总线强制拉高。

外部电源供电方式


1-Wire介绍

单总线电路规范

设备的DQ均要配置成开漏输出模式

DQ添加一个上拉电阻,阻值一般为4.7KΩ左右

单总线时序结构

初始化:主机将总线拉低至少480us,然后释放总线,等待15~60us后,存在的从机会拉低总线60~240us以响应主机,之后从机将释放总线

发送一位:主机将总线拉低60~120us,然后释放总线,表示发送0;主机将总线拉低1~15us,然后释放总线,表示发送1。从机将在总线拉低30us后(典型值)读取电平,整个时间片应大于60us

接收一位:主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us的末尾),读取为低电平则为接收0,读取为高电平则为接收1 ,整个时间片应大于60us

发送一个字节:连续调用8次发送一位的时序,依次发送一个字节的8位(低位在前)

接收一个字节:连续调用8次接收一位的时序,依次接收一个字节的8位(低位在前)

控制DS18B20寄存器

访问DS18B20的事件序列如下所示:

第一步:初始化

从机复位,主机判断从机是否响应

第二步:ROM命令(紧跟任何数据交换请求)

ROM指令+本指令需要的读写操作

第三步:DS18B20功能命令(紧跟任何数据交换请求)

功能指令+本指令需要的读写操作

ROM**指令**功能指令
SEARCH ROM [F0h]CONVERT T [44h]
READ ROM [33h]WRITE SCRATCHPAD [4Eh]
MATCH ROM [55h]READ SCRATCHPAD [BEh]
SKIP ROM [CCh]COPY SCRATCHPAD [48h]
ALARM SEARCH [ECh]RECALL E2 [B8h]
READ POWER SUPPLY [B4h]
ROM命令

当总线上的主设备检测到了存在脉冲后,就可以执行ROM命令。这些命令是对每个设备独一无二的64位ROM编码进行操作的,当总线上连接有多个设备时,可以通过这些命令识别各个设备。这些命令同时也可以使主设备确定该总线上有多少个什么类型的设备或者有温度报警信号的设备。总共包含有5种ROM命令,每个命令的长度都是8 Bit。

搜索ROM[F0h]

当系统上电初始化后,主设备必须识别该总线上所有的从设备的ROM编码,这样就可以使得主设备确定总线上的从设备的类型及数量。主设备学习ROM编码是一个清除的过程,则主设备要根据需要循环地发送搜索ROM[F0h]命令(搜索ROM命令跟随着数据交换)来确定总线上所有的从设备。

搜索ROM的搜索算法如下(来自csdn上的资料)

ROM搜索算法的核心规则, 是在搜索中重复进行一个简单的三步操作

步骤1: 读一次: 得到一位的值 总控读取1个bit. 这时每个设备都会将ROM当前这一位的bit值放到总线上, 如果这位是0, 就会对总线写0(拉低总线), 如果这位是1, 则会对总线写1, 允许总线保持高电平. 如果两者都存在, 总控读取的是0(低电平).

步骤2: 再读一次: 得到这位的补码 总控继续读一个bit, 这时候每个设备会将ROM当前这一位的bit的补码放到总线上, 如果这位是0就会写1, 如果这位是1则会写0, 如果两者都存在, 总控会读到一个0, 这样总控就会知道存在多个设备, 并且它们的ROM在这一位上的值不同.

步骤3: 写一次: 指定这一位的目标值 总控写入一个bit, 比如写入0, 表示在后面的搜索中选择这一位为0的设备, 屏蔽掉这一位为1的设备

循环 总线控制端在8字节ROM的每一位上执行这个三步操作后, 就能知道一个 DS18B20 的 8字节 ROM 值, 如果总线上有多个 DS18B20, 则需要重复多次.

搜索示例 示例数据 下面的例子假设总线上有4个设备, 对应的ROM值分别为

ROM1 00110101… ROM2 10101010… ROM3 11110101… ROM4 00010001… 示例搜索过程 搜索步骤如下

单线总线控制端(以下简称总控)执行 RESET, 所有的 DS18B20设备(以下简称设备)响应这个RESET 总控执行 Search ROM 命令 总控读取1个bit. 这时每个设备都会将自己的ROM的第一个bit放到总线上, ROM1 和 ROM4 会对总线写0(拉低总线), 而 ROM2 和 ROM3 则会对总线写1, 允许总线保持高电平. 这时候总控读取的是0(低电平). 总控继续读下一个bit, 每个设备会将第一个bit的补码放到总线上, 这时候 ROM1 和 ROM4 写1, 而 ROM2 和 ROM3 写0, 因此总控依然读到一个0, 这时候总控会知道存在多个设备, 并且它们的ROM在这一位上的值不同. (说明)从每次的两步读取中观察到的值分别有以下的含义 00 有多个设备, 且在这一位上值不同 01 所有设备的 ROM在这一位上的值是0 10 所有设备的 ROM在这一位上的值是1 11 总线上没有设备 总控写入一个bit, 比如写入0, 表示在后面的搜索中屏蔽 ROM2 和 ROM3, 仅留下 ROM1 和 ROM4 总控再执行两次读操作, 读到的值为0,1, 这表示总线上所有设备在这一位上的值都是0 总控写入一个bit, 因为值是确定的, 这次写入的是0 总控再执行两次读操作, 读到的值为0,0, 这表示总线上还有多个设备, 在这一位上的值不同 总控写入一个bit, 这次写入0, 这将屏蔽 ROM1, 仅留下 ROM4 总控重复进行三步操作, 读出 ROM4 剩余的位, 完成第一次搜索 总控再次重复之前的搜索直到第7位 总控写入一个bit, 这次写入1, 将屏蔽 ROM4, 仅保留 ROM1 总控通过重复三步操作, 读出 ROM1 剩余的位 总控再次重复之前的搜索直到第3位 总控写入一个bit, 这次写入1, 将屏蔽 ROM1 和 ROM4 仅保留 ROM2 和 ROM3 重复之前的逻辑, 当所有00读数都被处理, 说明设备的ROM已经全部被读取. 总控通过单线总线读取所有设备, 每个设备需要的时间为960 µs + (8 + 3 x 64) 61 µs = 13.16 ms, 识别速度为每秒钟75个设备.

代码逻辑 使用代码实现时, 整体的逻辑是按一个固定的方向(先0后1)深度优先遍历一个二叉树. ———————————————— 版权声明:本段文字为CSDN博主「IOsetting」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:DS18B20数字温度计 (三) 1-WIRE总线 ROM搜索算法和实际测试_ds18b20 搜索使用_IOsetting的博客-CSDN博客

读取ROM[33h]

该命令在总线上仅有一个从设备时才能使用。该命令使得总线上的主设备不需要搜索ROM命令过程就可以读取从设备的64位ROM编码。当总线上有超过一个从设备时,若再发送该命令,则当所有从设备都会回应时,将会引起数据冲突。

匹配ROM[55h]

该匹配ROM命令之后跟随发送64位的ROM编码使得总线上的主设备能够匹配特定的从设备。只有完全匹配该64位ROM编码的从设备才会响应总线上的主设备发出的功能命令;总线上的其他从设备将会等待下下一个复位脉冲。

跳过ROM[CCh]

主设备可以使用该命令来同时向总线上的所有从设备发送不要发送任何的ROM编码命令。例如,主设备通过向总线上所有的DS18B20发送跳过ROM命令后再发送温度转换[44h]命令,则所有设备将会同时执行温度转。

警报搜索[ECh]

该命令的操作与跳过ROM命令基本相同,但是不同的是只有警报标志置位的从设备才会响应。该命令使得主设备确定在最近一次温度转换期间是否有DS18B20有温度报警。当所有的报警搜索命令循环执行后,总线上的主设备必须回到事件序列中的第一步(初始化)。

DS18B20功能命令

温度转换[44h]

该命令为初始化单次温度转换。温度转换完后,温度转换的数据存储在暂存寄存器的2个字节长度的温度寄存器中,之后DS18B20恢复到低功耗的闲置状态。如果该设备是采用的“寄生电源”供电模式,在该命令执行10uS(最大)后主设备在温度转换期间必须强制拉高数据线。如果该设备是采用的外部供电模式,主设备在温度转换命令之后可以执行读取数据时序,若DS18B20正在进行温度转换则会响应0电平,温度转换完成则响应1电平。

写入暂存寄存器[4Eh]

该命令使得主设备向DS18B20的暂存寄存器写入3个字节的数据。第一个字节的数据写入TH寄存器(暂存寄存器的 Byte 2),第二个字节的数据写入TL寄存器(Byte 3),第三个字节的数据写入配置寄存器(Byte 4)。所有的数据必须是以低位先发的原则。所有的三个字节的数据在写入之前主设备必须先对从设备复位,否则数据将会损坏。

读取暂存寄存器[BEh]

该命令使得主设备可以读取暂存寄存器中存储的值。数据从Byte 0的低位开始传送直到第9个字节(Byte 8 - CRC)读取完毕。主设备若只需要暂存寄存器中的部分数据,则可以在读取数据中通过复位来终止。

拷贝暂存寄存器[48h]

该命令为将暂存寄存器中的TH、TL及配置寄存器(Byte 2,Byte 3和Byte 4)的值拷贝至EEPROM中。如果该设备采用的“寄生电源”供电模式,在该命令发送后10us(最大)内主设备必须强制拉高1-Wire总线超过10ms。

召回EEPROM[B8h]

该命令将温度报警触发值(TH和TL)及配置寄存器的数据从EEPROM中召回至暂存寄存器中的Byte 2,Byte 3和Byte4中。主设备可以在召回EEPROM命令之后执行读取数据时序,若DS18B20正在进行召回EEPROM则会响应0电平,召回EEPROM完成则响应1电平。召回数据操作在上电初始化后会自动执行一次,所以设备在上电期间暂存寄存器中一直会有有效的数据。

读取供电模式[B4h]

主设备通过执行该命令之后再执行读取数据时序来确定总线上的DS18B20是否是由“寄生电源”供电。在读取数据时序中,“寄生电源”供电的DS18B20将会拉低总线,外部电源独立供电模式的DS18B20则会释放总线让其保持在高电平。

温度分辨率配置

暂存寄存器中的Byte 4包含着配置寄存器;如图8所示。用户通过改变表2中R0和R1的值来配置DS18B20的分辨率。上电默认为R0=1及R1=1(12位分辨率)。需要注意的是,转换时间与分辨率之间是有制约关系的。Bit 7和Bit 0至Bit 4作为内部使用而保留使用,不可被写入。

温度存储格式:

DS18B20的应用示例

延时:

由于我所使用的单片机时钟晶振为11.0592MHZ,采用12分频。所以,计算可得单片机执行一条语句所耗费的时间为:

t=1/(11.0592 * 1000000) * 12=1.08506微秒

所以只要让单片机在需要延时时陷入由延时时间除以每次执行语句的时间次数的循环,就能完成延时指定时间。

用数码管显示温度示例:

首先是将1-Wire总线的代码模块化

#include <REGX52.H>
​
sbit onewire_dq=P3^7;   //将引脚重命名
​
unsigned char onewire_init()    //定义函数初始化总线
{
    unsigned char i;    //定义计数器
    unsigned char ack;      //定义应答返回值
    EA=0;       //关闭中断总开关
    onewire_dq=1;   //释放总线
    onewire_dq=0;   //将总线拉低500us
    i = 227;
    while (--i);
    onewire_dq=1;   //将总线拉高70us
    i = 29;
    while (--i);
    ack=onewire_dq;     //接受应答
    i = 227;        //延时500us
    while (--i);
    EA=1;       //打开中断总开关
    return ack;     //返回ack
}
​
void onewire_send(unsigned char date)   //定义函数发送一个比特
{
    unsigned char i;    //定义计数器
    EA=0;       //关闭中断总开关
    onewire_dq=0;   //将总线拉低
    if(date)    //判断发送1还是0,若发送1,执行下列代码
    {
        i = 3;      //延时6us
    while (--i);
        onewire_dq=1;   //将数据放入onewire_dq
        i = 24;     //延时54us
    while (--i);
    }
    else    //若发送0,执行下列代码
    {
        i = 27;     //延时60us
    while (--i);
    }
    onewire_dq=1;   //释放总线
    EA=1;   //打开中断总开关
}
​
unsigned char onewire_read()    //定义函数接受一个比特
{
    unsigned char i;    //定义计数器
    unsigned char date;     //定义返回值
    EA=0;       //关闭中断总开关
    onewire_dq=0;   //将总线拉低5us
    i = 2;
    while (--i);    
    onewire_dq=1;   //释放总线5us
    i = 2;
    while (--i);
    date=onewire_dq;    //采集onewire_dq的数值
    i = 24;
    while (--i);
    EA=1;   //打开中断总开关
    return date;    //返回date
}
​
void onewire_sendbyte(unsigned char Byte)   //定义函数发送一个字节
{
    unsigned char i;    //定义计数器
    for(i=0;i<8;i++)    //循环发送8个比特
    {
        onewire_send(Byte&(0x01<<i));
    }
}
​
​
unsigned char onewire_receivebyte(void)     //定义函数接受一个字节
{
    unsigned char i;    //定义字节
    unsigned char Byte=0x00;    //定义返回值Byte初始值为0x00
    for(i=0;i<8;i++)    //循环8次接受8个比特
    {
        if(onewire_read()){Byte|=(0x01<<i);}
    }
    return Byte;    //返回Byte
}

接下来是把DS18B20模块化的代码

#include <REGX52.H>
#include "onewire.h"
​
void ds18b20_convert()  //定义函数使DS18B20温度变换
{
    onewire_init();     //初始化总线
    onewire_sendbyte(0xcc);     //跳过rom
    onewire_sendbyte(0x44);     //开始温度变换
    
}
​
​
float ds18b20_read()    //定义函数读取DS18B20的温度
{
    
    float t;    //定义温度返回值
    unsigned char TLSB=0x00,TMSB=0x00;      //定义TLSB,TMSB接受温度
    int temp=0;     //定义暂存值
    onewire_init();     //初始化总线
    onewire_sendbyte(0xcc);     //跳过rom
    onewire_sendbyte(0xbe);     //读取暂存器温度
    TLSB=onewire_receivebyte( );
    TMSB=onewire_receivebyte( );
    temp=TMSB<<8|TLSB;
    t=temp/16.0;    //将读取的数据转换成float格式并返回t
    return t;
}

最后是主函数:

#include <REGX52.H>
#include "Delay.h"
#include "smg.h"
#include "timer0.h"
#include "ds18b20.h"
#include "onewire.h"

float temperature = 0;
int cnt=0;
unsigned char b[]={48,49,50,51,52,53,54,55,56,57,45,45};//定义数组来表示acill编码

void main()
{
    unsigned char tem[8] = {0, 0, 0, 0, 0, 0, 0, 0};	//定义数组tem来表示温度的每一位数字
    unsigned char c = 0;

    Timer0Init();	//初始化定时器0

	UART_INIT();	//初始化串口
    while (1)
    {
        ds18b20_convert();		//循环温度变换
        temperature = ds18b20_read();	//循环读取温度
			
        
if (temperature < 0)
        {
            temperature = -temperature;
            y[1] = 11;
        }
        else y[1] = 10;

        for (c = 0; c < 10; c++)
        {
            if (temperature - c * 100 < -0.00001)
            {
                tem[1] = c - 1; break;
            }
        }

        for (c = 0; c < 10; c++)
        {
            if (temperature - 100 * tem[1] - c * 10 < -0.00001)
            {
                tem[2] = c - 1; break;
            }
        }

        for (c = 0; c < 10; c++)
        {
            if (temperature - 100 * tem[1] - 10 * tem[2] - c * 1 < -0.00001)
            {
                tem[3] = c - 1; break;
            }
        }

        for (c = 0; c < 10; c++)
        {
            if (temperature - 100 * tem[1] - 10 * tem[2] - tem[3] - c * 0.1 < -0.00001)
            {
                tem[4] = c - 1; break;
            }
        }

        for (c = 0; c <= 10; c++)
        {
            if (temperature - 100 * tem[1] - 10 * tem[2] - tem[3] - tem[4] * 0.1 - c * 0.01 < -0.00001)
            {
                tem[5] = c - 1; break;
            }
        }

        for (c = 0; c <= 10; c++)
        {
            if (temperature - 100 * tem[1] - 10 * tem[2] - tem[3] - tem[4] * 0.1 - tem[5] * 0.01 - c * 0.001 < -0.00001)
            {
                tem[6] = c - 1; break;
            }

        }

        for (c = 0; c < 10; c++)
        {
            if (temperature - 100 * tem[1] - 10 * tem[2] - tem[3] - tem[4] * 0.1 - tem[5] * 0.01 - tem[6] * 0.001 - c * 0.0001 <= -0.00001)
            {
                tem[7] = c - 1; break;
            }
        }
				
				UART_SEND(b[tem[2]]);	//发送温度并发送小数点和空格
				UART_SEND(b[tem[3]]);
				UART_SEND(46);
				UART_SEND(b[tem[4]]);
				UART_SEND(b[tem[5]]);
				UART_SEND(b[tem[6]]);
				UART_SEND(b[tem[7]]);
				UART_SEND(32);
				UART_SEND(32);
				UART_SEND(32);
				
        y[2] = tem[1];	//在数码管显示温度
        y[3] = tem[2];
        y[4] = tem[3];
        y[5] = tem[4];
        y[6] = tem[5];
        y[7] = tem[6];
        y[8] = tem[7];
    }	
	}



void Timer0_Rountine()   interrupt 1
{
	TL0 = 0x66;		//配置定时器每1毫秒发生一次中断
	TH0 = 0xFC;
	cnt++;
	cnt=cnt%1000;
	
	if(cnt%2)	//定时器扫描数码管
		light1();	
	}

LCD1602

LCD1602概况

LCD1602(Liquid Crystal Display)液晶显示屏是一种字符型液晶显示模块,可以显示ASCII码的标准字符和其它的一些内置特殊字符,还可以有8个自定义字符

显示容量:16×2个字符,每个字符为5*7点阵

由于LCD1602背后集成了控制电路的芯片和扫描电路的芯片,所以不需要像数码管一样频繁通过扫描来使其点亮

还有另一种液晶显示屏是LCD12864,是一种字符型的液晶显示屏,可显示任意的图形和字符

LCD1602的结构

引脚及应用电路
引脚功能
VSS
VDD电源正极(4.5~5.5V)
VO对比度调节电压
RS数据/指令选择,1为数据,0为指令
RW读/写选择,1为读,0为写
E使能,1为数据有效,下降沿执行命令
D0~D7数据输入/输出
A背光灯电源正极
K背光灯电源负极
内部结构框图

屏幕:16 * 2 用于显示内容

字模库:CGRAM和CGRAM

CGRAM可用于用户自定义字符。

CGROM内存储了不可更改的字符:0x20~0x7F 为标准的 ASCII 码,0xA0~0xFF 为日文字符和希腊文字符,其 余字符码(0x10~0x1F 及 0x80~0x9F)没有定义。

DDRAM:数据显示区(40 * 2)

通过将需要显示的内容对应的编码写在DDRAM使得在DDRAM的前16列通过在字模组里寻找对应显示方式在屏幕中显示。

当调用移屏指令使显示屏显示内容在DDRAM里面移动的话,当移动DDRAM使其数据移出DDRAM时,溢出的数据会重新来到DDRAM的末尾,可实现流动字幕。

控制电路:

光标:AC(address counter),指向DDRAM中的地址

时序结构

控制LCD1602

初始化:

发送指令0x38 //八位数据接口,两行显示,5*7点阵

发送指令0x0C //显示开,光标关,闪烁关

发送指令0x06 //数据读写操作后,光标自动加一,画面不动

发送指令0x01 //清屏

显示字符:

发送指令0x80|AC //设置光标位置

发送数据 //发送要显示的字符数据

发送数据 //发送要显示的字符数据

LCD1602的应用示例

#include <REGX52.H>
#include "DELAY.H"
sbit LCD_RS=P2^6;	//重命名
sbit LCD_EN=P2^7;
sbit LCD_WR=P2^5;
#define LCD_D P0

void lcd1602_writecommand(unsigned char command)	//定义函数写入一个指令
{
	LCD_RS=0;
	LCD_WR=0;
	LCD_D=command;
	LCD_EN=1;
	Delay(1);
	LCD_EN=0;
	Delay(1);
}

void lcd1602_writedate(unsigned char date)		//定义函数写入一个数据
{
	LCD_RS=1;
	LCD_WR=0;
	LCD_D=date;
	LCD_EN=1;
	Delay(1);
	LCD_EN=0;
	Delay(1);
}


void lcd1602_init()		//定义初始化函数
{
		lcd1602_writecommand(0x38);	//八位数据接口,两行显示,5*7点阵
		lcd1602_writecommand(0x0c);	//显示开,光标关,闪烁关
		lcd1602_writecommand(0x06);	//数据读写操作后,光标自动加一,画面不动
		lcd1602_writecommand(0x01);	//清屏
}


void lcd1602_read()
{
		lcd1602_writecommand(0x80);	//设置光标位置
		lcd1602_writedate('a');	//设置显示内容
}

void lcd1602_char(unsigned char x,y,date)	//定义函数显示
{
	unsigned char address;
	switch(x)	//选择光标在第一行还是第二行
	{
		case 1:{address=0x80;address=address+y-1;break;}
		case 2:{address=0xc0;address=address+y-1;break;}
	}
		lcd1602_writecommand(address);	//设置光标位置
		lcd1602_writedate(date);	//设置显示内容
}

void lcd1602_string(unsigned char x,y,unsigned char date[])
{
	unsigned char address;
	unsigned char *p=&date[0];	//定义指针指向字符串数组date[]
	
	switch(x)	//选择光标在第一行还是第二行
	{
		case 1:{address=0x80;address=address|(y-1);break;}
		case 2:{address=0xc0;address=address|(y-1);break;}
	}
	while(*p!='\0')		//当字符不为'\0'时
	{
		lcd1602_writecommand(address);
		lcd1602_writedate(*p);
		p++;
		address++;
	}
}

int asd(int x, int y)	//定义函数计算次方
{
	int date=1;
	for (; y > 0; y--)
		date = x * date;
	return date;
}

void lcd1602_intnum(unsigned char x,y,unsigned int date,unsigned char length)
//定义函数显示int类型的数字
{
	unsigned char address;
	unsigned char show;	//定义显示内容
	unsigned char i;	//定义计数器

	switch(x)
	{
		case 1:{address=0x80;address=address|(y-1);break;}
		case 2:{address=0xc0;address=address|(y-1);break;}
	}
	for(i=length;i>0;i--)	//将逐个位上的数字取出并输出
	{
		lcd1602_writecommand(address++);
		show=date/asd(10,i-1)%10+48;
		lcd1602_writedate(show);
	}
	
}

void lcd1602_signed_intnum(unsigned char x,y,int date,unsigned char length)
//定义函数显示带有符号的int类型数字
{
	unsigned char address;
	unsigned char show;
	unsigned char i;

	switch(x)
	{
		case 1:{address=0x80;address=address|(y-1);break;}
		case 2:{address=0xc0;address=address|(y-1);break;}
	}
	if(date<0)	//判断数值小于0,输出‘-’号
	{
		lcd1602_writecommand(address++);
		lcd1602_writedate('-');
	}
	else if(date<0)	//判断数值大于0,输出‘+’号
	{
		lcd1602_writecommand(address++);
		lcd1602_writedate('+');
	}
	date=-date;
	for(i=length;i>0;i--)
	{
		lcd1602_writecommand(address++);
		show=date/asd(10,i-1)%10+48;
		lcd1602_writedate(show);
	}
	
}


void lcd1602_hexnum(unsigned char x,y,unsigned char date)	//定义函数输出16进制的char型数据
{
	unsigned char address;
	unsigned char show;
	unsigned char i;

	switch(x)
	{
		case 1:{address=0x80;address=address|(y-1);break;}
		case 2:{address=0xc0;address=address|(y-1);break;}
	}
	lcd1602_writecommand(address++);
	lcd1602_writedate('0');
	lcd1602_writecommand(address++);
	lcd1602_writedate('x');
	for(i=2;i>0;i--)
	{
			lcd1602_writecommand(address++);

			if((date/asd(16,i-1)%16)>9)
			show=date/asd(16,i-1)%16+87;
			else
			show=date/asd(16,i-1)%16+48;
		lcd1602_writedate(show);
	}
	
}

    void lcd1602_binnum(unsigned char x,y,unsigned char date)	//定义函数输出2进制的char型数据
{
	unsigned char address;
	unsigned char show;
	unsigned char i;

	switch(x)
	{
		case 1:{address=0x80;address=address|(y-1);break;}
		case 2:{address=0xc0;address=address|(y-1);break;}
	}
	
	for(i=8;i>0;i--)
	{
		lcd1602_writecommand(address++);
		show=date/asd(2,i-1)%2+48;
		lcd1602_writedate(show);
	}
}

pwm

pwm介绍及其原理

PWM(Pulse Width Modulation)即脉冲宽度调制,脉冲宽度调制是通过将有效的电信号分散成离散形式从而来降低电信号所传递的平均功率的一种方式。在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速、开关电源等领域

PWM重要参数: 频率 = 1 / TS 占空比 = TON / TS 精度 = 占空比变化步距

惯性系统:例如LED,当立即变为低电平时,LED并不会瞬间熄灭,而是具有惯性,在短时间内逐渐熄灭。在这种情况下,PWM就可以发挥作用,通过改变脉冲的宽度,我们可以控制电流的平均大小,从而有效地控制LED的亮度。

在非惯性系统中,不可以使用pwm进行调制。

直流电机及其驱动

电机

直流电机是一种将电能转换为机械能的装置。一般的直流电机有两个电极,当电极正接时,电机正转,当电极反接时,电机反转。

直流电机主要由永磁体(定子)、线圈(转子)和换向器组成除直流电机外,常见的电机还有步进电机、舵机、无刷电机、空心杯电机等

步进电机:步进电机是一种能将脉冲信号转换为机械运动的电机,步进电机每次转动的角度是已知的,从而可以精确控制电机的运动。其中,永磁式步进电机由两相或多相永磁体和两个线圈组成,通过改变电流的方向来改变磁场的强度和方向,从而实现转子的旋转。

舵机:舵机的工作原理主要是利用电流控制舵机的角度。一般来说,舵机接收到一个脉冲信号后,会转动一定的角度。脉冲信号的频率越高,舵机转动的速度就越快。通过改变脉冲信号的占空比,可以控制舵机转动的角度。此外,舵机还可以通过PWM(脉冲宽度调制)来实现更精确的角度控制。

无刷电机:无刷电机的特点是不使用机械的电刷装置,而是采用方波自控式永磁同步电机,以霍尔传感器取代碳刷换向器,以钕铁硼作为转子的永磁材料。这种设计消除了有刷电机的缺点,如电刷磨损、电磁干扰等。此外,无刷电机还具有高效率、低能耗、低噪音、超长寿命、高可靠性的优点。当是其缺点是价格较高,且维护难度较大。

空心杯电机:空心杯电机是一种特殊的直流电机,其特殊性在于其转子结构采用了无铁芯转子,也被称为空心杯型转子。这种独特的转子结构完全去除了铁芯,从而显著减小了电机的体积和重量,同时也提高了电机的效率。

电机驱动电路

由于从单片机的引脚处直接输出的高电平的驱动能力很弱,所以需要经过达林顿管对电流进行放大(如下图为电机驱动芯片ULN2003的逻辑框图)

红框内的二极管为续流二极管。由于电机为电感性负载,所以当电流发生较大变化时,由于电感的作用,电路内会产生与原来电流方向相同的电流,这个电流产生的突变电压可能会使电路上的三级管被击穿。为了避免这种情况发生,在电路上与电感性负载并联一个续流二极管,可以让电感性负载在产生感应电动势时,及时将电流通过二极管泄放。总之,泄流二极管在电路中起到了避免在电感性负载断电时产生高电压损伤零件的情况。

H桥驱动:

便于控制电机的正转反转,但由于电流方向可变,所以无法接上续流二极管,对三极管的抗压能力要求较高。

使用pwm制作呼吸灯和调速电机

调速电机代码

#include <REGX52.H>
#include "smg.h"
#include "timer0.h"
#include "order.H"
unsigned char cnt=0,compare=0;	//定义cnt计数器,cmopera为pwm调速挡位
	unsigned char state;	//定义state表示按键状态
void main()
{
	unsigned char a;		//定义a表示模式
	Timer0Init();
	while(1)
	{
		state=order1();		//检测按键状态
		if(state==1) a++;
		a=a%4;
		switch(a)	//
		{
			case 0:{compare=0;break;}
			case 1:{compare=50;break;}
			case 2:{compare=75;break;}
			case 3:{compare=100;break;}
		}
		light(1,a);		//数码管显示模式
	}
}

void Timer0_Rountine()   interrupt 1
{
	TL0 = 0x66;		//定时器定时发送脉冲,模拟电压
	TH0 = 0xFC;
	cnt++;
	cnt=cnt%100;
	if(cnt<compare) P1_0=1;
	else P1_0=0;

}

呼吸灯代码

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

unsigned char cnt=0,compare;	//定义cnt计数器,cmopera为亮度
void main()
{
	unsigned char i,j;		//定义延时变量
	Timer0Init();	//定时器初始化
	while(1)
	{
		for(compare=0;compare<100;compare++)
		{
		i = 9;		//延时500微秒
	j = 244;
	do
	{
		while (--j);
	} while (--i);
		}
		for(compare=100;compare>0;compare--)
		{
		i = 9;		//延时500微秒
	j = 244;
	do
	{
		while (--j);
	} while (--i);
		}
	}
}

void Timer0_Rountine()   interrupt 1
{
	TL0 = 0x91;		//每100微秒扫描一次
	TH0 = 0xFf;
	cnt++;
	cnt=cnt%100;
	if(cnt<compare) 	//判断亮灭
	{	
		P2_0=1;
	}
	else 
	{
		P2_0=0;
	}
}

AD/DA转换器

AD/DA介绍

AD(Analog to Digital):模拟-数字转换,将模拟信号转换为计算机可操作的数字信号

DA(Digital to Analog):数字-模拟转换,将计算机输出的数字信号转换为模拟信号

AD/DA转换打开了计算机与模拟信号的大门,极大的提高了计算机系统的应用范围,也为模拟信号数字化处理提供了可能

AD/DA性能指标:

1、分辨率:指AD/DA数字量的精细程度,通常用位数表示。例如,对于5V电源系统来说,8位的AD可将5V等分为256份,即数字量变化最小一个单位时,模拟量变化5V/256=0.01953125V,所以,8位AD的电压分辨率为0.01953125V,AD/DA的位数越高,

2、分辨率就越高转换速度:表示AD/DA的最大采样/建立频率,通常用转换频率或者转换时间来表示,对于采样/输出高速信号,应注意AD/DA的转换速度

我的51单片机上的AD/DA模块如下:

1、热敏电阻

2、光敏电阻

3、滑动变阻器

4、DA输出LED

AD转换通常有多个输入通道,用多路选择开关连接至AD转换器,以实现AD多路复用的目的,提高硬件利用率

AD/DA与单片机数据传送可使用并口(速度快、原理简单),也可使用串口(接线少、使用方便)

可将AD/DA模块直接集成在单片机内,这样直接写入/读出寄存器就可进行AD/DA转换,单片机的IO口可直接复用为AD/DA的通道

PWM在很大成程度上可以替代DA。

上述两个模块在之后会详细介绍

集成运算放大器

集成运算放大器简称运放,它以半导体体单晶硅为芯片,把整个电路中的元件如三极管,二极管,电阻等制作在一起,是具有很高放大倍数的放大电路单元。内部集成了差分放大器、电压放大器、功率放大器三级放大电路,是一个性能完备、功能强大的通用放大电路单元,由于其应用十分广泛,现已作为基本的电路元件出现在电路图中

运算放大器可构成的电路有:电压比较器、反相放大器、同相放大器、电压跟随器、加法器、积分器、微分器等

运算放大器电路的分析方法:虚短、虚断(负反馈条件下)

集成运放的符号如下图所示

两个输入端分别是同相端UP和反向端UN。同相端输入电压与输出电压同相,即当集成运放工作在线性区时,同相端输入电压UP增大,输出电压U0也增大,它们保持同相关系。反向端与同相端相反,输入电压UN越大,输出电压U0越小,他们保持相反关系。

集成运放的电压传输特性是指输出电压U0与同相电压和反向电压之差(差模信号)之间的关系,其曲线如下图所示:

由图可知,当电压处于Ua到Ub时,集成运放的输出电压与输入信号成比例关系,这个区域称为线性区,此时输出电压与输入信号有以下关系:

U0=Aod*(UP-UN)

其中,Aod是开环电压放大倍数,它的数值很大,表示传输特性曲线的斜率。

当电压不再线性区时,输出电压只有两种数值:+Uom或-Uom。如果同相电压大于反向电压,则输出+Uom;反之输出-Uom。Uom的具体值由集成运放的内部参数和外接电呀决定。

集成运算放大器本质上是一种电压放大器,理想化的集成运放的参数指标如下:

开环电压放大倍数:Aod=∞;

输入电阻Ri=∞;

输出电阻R0=0;

通频带fbw=∞;

集成运算放大器内部结构

运算放大器通常由四个基本部分组成,即输入级,中间放大级,输出级和偏置电路,如上图所示。

输入级提供与输出同相和反相的两个输入端,并具有较高的输入电阻和抑制干扰及零点漂移的能力,因而采用差分放大电路。

中间放大电路是运放的主放大器,其主要作用是提供较高的电压放大倍数,通常由二、三级直接耦合的共射极放大电路组成。另外,中间放大级还具有将双端输出转换为单端输出的作用。

输出级应具有输出电压线性范围宽、带负载能力强、非线性失真小等特点,一般采用互补对称放大电路。

偏置电路的主要作用是为各级放大电路提供稳定适合的静态工作电流,集成运放的偏置电路一般由各种恒流源组成。

如下图uA741集成运放的内部电路结构为:(有关该电路的原理及作用不再此处说明)

其中它一共由8个引出端:

2端为反相输入端

3端为同相输入端

6端为输出端

7端和8端分别接正负电源

1端与5端之间接调零电阻

8端悬空,不接

集成运算放大器构成电路
负反馈

由于理想运放的Aod很大,只要有很小的差模输入电压,则输出很高的电压,使运放进入非线性区。因此,引入负反馈就可以使运放工作在线性区,稳定输出信号

虚短:

由上面的U0的公式可得,因为Aod的值非常大,所以要是理想运放的输出电压在一定范围内变化,则输入的电压差必须非常小,因此可以近似认为Up-Un=0,即Up=Un。此时,同相端与反相端的电位近似相等,称为虚短。

虚断

由于理想运放的输入电阻非常大(Ri=∞),因此

在近似计算时,可以认为ip=0,in=0,即同相端与反相端的输入电流都为0,称为虚断。

基本运算电路

同相比例运算电路:

特殊情况:当R2的值为无穷大时,U0的值接近与Ui,即当R2为断路时,输入电压等于输出电压,此时该电路称为电压跟随器,如下图

反相比例运算器

反相加法运算电路

减法运算器

积分运算器

微分运算器

除此之外,运放还可以和电容和电阻组合形成有源滤波电路,如:低通滤波电路,高通滤波电路,带通滤波电路,全通滤波电路等等。(此处不再画出电路图及其公式)

DA原理

T型电阻网络DA转换器

如上图:

在运放前的T型电阻网络中,一共有D0到D78个开关,这8个开关都有0和1两个接口,分别接向同相输入端和反相输入端。同相输入端接地,反相输入端上接入负反馈网络,由此构成DA转换器。

在该T型电阻网络中当开关接1时,接入反相输入端D0端的电阻流过的电流最小,所以它就在表示该电压的二进制的最低位,所以依次从右到左位数逐渐升高。

并且由上图中的运放组成的基本电路得,这个电路为反相比例放大器,所以得到其输出电压的公式如下:

输出电压 Vo=(D7~D0)/256×VREF×Rfb/R

当Rfb=R时,Vo=(D7~D0)/256×VREF

PWM型DA转换器

PWM型DA转换器就是由PWM信号输出经过低通滤波器和电压跟随器,将信号输出成稳定的电压值。

输出电压 Vo=(PWM占空比)×Vh

AD原理

逐次逼近型AD转换器

逐次逼近型AD转换器的核心是一个运算放大器用来放大差分信号。当通道选择的模拟信号接入运放的输入端,另一个输入端给一个DA转换器,通过逐次尝试不同的电压值后,输出端的电压大小来判断尝试值与模拟信号的大小,从而逐次逼近模拟信号的值,并将读取得到的值传入缓冲器。

xpt2046和DAC模块

名称说明
BUSY忙时信号线。当CSA为高电平时,为高祖状态
DIN串行数据输入端。当CS为低电平时,数据在DCLK上升沿锁存进来
CS片选信号。控制转换时序和使能串行输入输出寄存器,高电平时ADC掉电
DCLK外部时钟信号输入
VCC电源输入端
X+X+位置输入端
Y+Y+位置输入端
X-X-位置输入端
Y-Y-位置输入端
GND接地
VBAT电池监视输入端
AUXADC辅助输入通道
VREF参考电压输入/输出
IOVDD数字电源输入端
PENIRO笔接触中断引脚
DOUT串行数据输出端。数据在DCLK的下降沿移出,当CS高电平时为高祖状态

xpt2046的时序为SPI通信如下:

前 8 个时钟用来通过DIN引脚输入控制字节。当转换器获取有关下一次转换的足够信息后,接着根据 获得的信息设置输入多路选择器和参考源输入,并进入采样模式,接着的 12 个时钟周期将完成真正的模数转换。第 13 个时钟将输出转换结果的最后一 位。剩下的 3 个多时钟周期将用来完成被转换器忽略的最后字节(DOUT置低)

起始位——第一位,即 S 位。控制字的首位必须是 1,即 S=1。在 XPT2046 的 DIN 引脚检测到起始 位前,所有的输入将被忽略。

地址——接下来的 3 位(A2、A1 和 A0)选择多路选择器的现行通道

MODE——模式选择位,用于设置 ADC 的分辨率。MODE=0,下一次的转换将是 12 位模式; MODE=1,下一次的转换将是 8 位模式。

SER/DFR——SER/DFR位控制参考源模式,选择单端模式(SER/DFR=1),或者差分模式 (SER/DFR=0)。在X坐标、Y坐标和触摸压力测量中,为达到最佳性能,首选差分工作模式。参考电 压来自开关驱动器的电压。在单端模式下,转换器的参考电压固定为VREF相对于GND引脚的电压

PD0 和 PD1——掉电和内部参考电压配置的关系。

通过时序发送命令之后,接受来自xpt2046的数据,就能读出模拟信号转换后的数字信号。

在该模块中,我们只需按照PWM输出到P2_1口,就能输出模拟信号。

AD/DA使用示例

首先是将xpt2046的控制代码模块化

#include <REGX52.H>
#include <INTRINS.H>
sbit xpt2046_DIN=P3^4;	//将引脚重命名
sbit xpt2046_CS=P3^5;
sbit xpt2046_DCLK=P3^6;
sbit xpt2046_DOUT=P3^7;

unsigned int xpt2046_read(unsigned char command)	//定义函数发送指令并读取xpt2046的某个值
{
	unsigned char i;	//定义计数器
	unsigned int date=0;	//定义返回值
	xpt2046_DCLK=0;		//初始化时钟线
	xpt2046_CS=0;	//打开使能
	for(i=0;i<8;i++)	//循环8次从高位到低位一次将指令输入到xpt2046_DIN
	{
		xpt2046_DIN=command&(0x80>>i);
		xpt2046_DCLK=1;		//上升沿将数据锁存进xpt2046
		xpt2046_DCLK=0;		//恢复低电平
	}
	for(i=0;i<16;i++)
	{
		xpt2046_DCLK=1;	//恢复高电平
		xpt2046_DCLK=0;	//下降沿让xpt2046将数据放到xpt2046_DOUT
		if(xpt2046_DOUT)	//判断输出并赋值date
		date=date|(0x8000>>i);
	}
	xpt2046_CS=1;	//将使能线拉回高电平,结束时序
	if(command&0x08)	//判断指令中的精度大小,确认移动位数
		return (date>>8);
	else return (date>>4);
}

unsigned int xpt2046_read_AD1_8()	//定义读取AD1的值,精度为8位的函数;
{
	return xpt2046_read(0x9c);
}

unsigned int xpt2046_read_AD1_12()	//定义读取AD1的值,精度位12位的函数;
{
	return xpt2046_read(0x94);
}

unsigned int xpt2046_read_NTC1_8()	//定义读取NTC1的值,精度为8位的函数;
{
	return xpt2046_read(0xDc);
}
unsigned int xpt2046_read_NTC1_12()	//定义读取AD1的值,精度位12位的函数;
{
	return xpt2046_read(0xD4);
}

unsigned int xpt2046_read_GR1_8()	//定义读取GR1的值,精度为8位的函数;
{
	return xpt2046_read(0xac);
}

unsigned int xpt2046_read_GR1_12()	//定义读取AD1的值,精度位12位的函数;
{
	return xpt2046_read(0xa4);
}

接下来是的PWM模块的DA输出DA1以呼吸灯形式亮灭的主函数代码:

该代码实现的功能是让单片机以DA1的一次呼吸周期为标志,读取一次AD1,NTC1.GR1的数值并输出在LCD1602上。

#include <REGX52.H>
#include "xpt2046.h"
#include "LCD1602.H"
#include "timer0.h"
#include "DELAY.H"

unsigned int compare,cnt;	//定义计数器,和比较值

void main()
{
	unsigned int a,b,c;
	LCD_Init();		//初始化LCD
	Timer0Init();		//初始化定时器0
	while(1)
	{
		
		a=xpt2046_read_AD1_12();	//a=AD1的值
		LCD_ShowNum(1,1,a,4);
		b=xpt2046_read_NTC1_12();	//b=NTC1的值
		LCD_ShowNum(1,8,b,4);
		c=xpt2046_read_GR1_12();	//c=GR1的值
		LCD_ShowNum(2,1,c,4);
		
		for(compare=0;compare<100;compare++)	//使compare周期性递增
		Delay(5);
		for(compare=100;compare>0;compare--)	//使compare周期性递减
		Delay(5);
	}
}

void Timer0_Rountine()   interrupt 1
{
	TL0 = 0x91;		
	TH0 = 0xFf;
	cnt++;
	cnt=cnt%100;
	if(cnt<compare)  P2_1=0;	//cnt小于compare时,P2_1亮
	else P2_1=1;	//cnt大于compare时,P2_1灭
}

红外遥控

红外遥控介绍

红外遥控是利用红外光进行通信的设备,由红外LED将调制后的信号发出,由专用的红外接收头进行解调输出。

通信方式:单工,异步红外。

LED波长:940nm。

通信协议标准:NEC标准。

硬件电路

发射红外信号电路:

IN为输入的信号源,Q1为三级管,从VCC开始的电流经过Q1,电流被调制成与信号源相同的波形,LED1为发射红外光的LED光源,R2为限流电阻。

接受红外信号电路:

IR1内部接有一个红外接收LED 用于滤掉其他自然光,同时还有一个滤波器用于滤掉由其它光源发出的不符合规定频率的红外光

发送与接收

空闲状态:红外LED不亮,接收头输出高电平。

发送低电平:红外LED以38KHz频率闪烁发光,接收头输出低电平。

发送高电平:红外LED不亮,接收头输出高电平。

NEC编码

发送数据前先进行初始化,之后从低位到高位输出地址码,地址码反码,命令码和命令码反码。当按键重复按下时,发送端持续发送重复标识。

外部中断

由于红外信号发送的持续时间很短,因此需要通过外部中断来使单片机能够灵敏地触发接收信号的程序。由上方的红外接收电路可得,该接受信号的引脚接在P3_2上,即与外部中断INT0共用同一个引脚。所以只需要让外部中断INT0通过下降沿触发,并在每一次都记录上一次的触发中断的时间,就可以来判断信号的内容。并且需要将INT0的中断优先级配置较高,确保能接受到红外信号。

其外部中断号如下:

void Int0_Routine(void)   interrupt 0;

配置上述开关,并将EA中断总开关打开就能由P3_2口接受红外信号了。

红外遥控调节直流电机示例

首先是将定时器函数改造

#include <REGX52.H>
void Timer0Init()		//初始化定时器0
{
	TMOD &= 0xF0;	
	TMOD |= 0x01;	
	TL0 = 0;		
	TH0 = 0;		
	TF0 = 0;		
	TR0 = 0;		
}

void Timer0_set()	//重置定时器
{
	TL0 = 0x00;		
	TH0 = 0x00;
}

unsigned int Timer0_read()	//读取定时器数据
{
	return (TH0<<8)|TL0;
}

void Timer0_run(unsigned char flag)		//控制定时器开关
{
	TR0=flag;
}

接下来是控制外部中断的模块化代码

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

unsigned char state=0,address=0,command=0,repeat=0;	//定义状态,地址,命令,重复标志
unsigned char temp[4]={0,0,0,0};	//定义缓存数组
unsigned char i=0,j,k;	//定义计数器
	
void int0_init()	//配置外部中断INTO,初始化。
{
	EA=1;
	EX0=1;
	PX0=1;
	IT0=1;
	IE0=0;
}


void IR_init()	//初始化外部中断和定时器
{
	int0_init();
	Timer0Init();
}

void Int0_Routine(void)   interrupt 0	//中断函数
{
	int cnt;	//定义计数器
	repeat=0;	//初始化重复标志
	switch(state){
		case 0:		//空闲状态
		{
			Timer0_set();	//重置计数器
			Timer0_run(1);	//打开定时器
			state=1;	//使状态变为1
			break;
		}
		case 1:		//搜索初始化时序状态
		{
			cnt=Timer0_read();	//读取定时器数值
			Timer0_set();	//重置计时器
			if(cnt>12442-500&&cnt<12442+500) {state=2;}
			//若符合初始化时序,则使状态变为2
			else if(cnt>10368-500&&cnt<10368+500) {repeat=1;Timer0_run(0);state=0;}
			//若符合发送重复标识时序,则状态变为0
			else state=1;
			//若时序均不符合目标,则停留在状态1,继续搜索起始时序
			break;
		}
		case 2:
		{	
			cnt=Timer0_read();	//读取定时器数值
			Timer0_set();	//重置计时器
			if(cnt>1032-500&&cnt<1032+500) {i++;}
			//若接受的值为0,则计数器i加一(由于初始化的temp的值为0,所以可以直接跳过该位)
			else if(cnt>2074-500&&cnt<2074+500) {k=i%8;j=i/8;temp[j]=temp[j]|(0x01<<k);i++;}			//若接受的值为1,则计数器i+1,temp的第i位赋值1
			else {state=1;i=0;}
			//若时序不符合目标,则返回到状态1,继续搜索起始时序
			if(i==32)	//当填充位数到达32时,进行校验
			{
				if((temp[1]==~temp[0])&&(temp[2]==~temp[3]))
				//若校验成功
				{
					address=temp[0];
					command=temp[2];
					temp[0]=0;	//将temp的所有值置0,恢复初始化
					temp[1]=0;
					temp[2]=0;
					temp[3]=0;
					Timer0_run(0);	//关闭计时器
					state=0;	//返回空闲状态
					i=0;	//将i清零
				}
				i=0;	//将i清零
			}
			break;
		}
	}
}

unsigned char IR_address()	//定义函数获取地址码
{
	return address;
}

unsigned char IR_command()	//定义函数获取命令码
{
	return command;
}

unsigned char IR_repeat()	//定义函数获取重复标识,并每获取一次将重复标识清零
{
	if(repeat)
	{
		repeat=0;
		return 1;
	}
	else return 0;
}

及其模块的声明如下:

#ifndef __int0_H__
#define __int0_H__

#define IR_0 0x16
#define IR_1 0x0c
#define IR_2 0x18
#define IR_3 0x5e
#define IR_4 0x08
#define IR_5 0x1c
#define IR_6 0x5a
#define IR_7 0x42
#define IR_8 0x52
#define IR_9 0x4a
#define IR_RPT 0x19
#define IR_SD 0x0D
#define IR_EQ 0x07
#define IR_VOLN 0x15
#define IR_VOLP 0x09
#define IR_STOP 0x44
#define IR_FORWARD 0x43
#define IR_BACK 0x40
#define IR_MODE 0x46
#define IR_KEY 0x45
#define IR_SOUND 0x47


void int0_init();
void IR_init();
unsigned char IR_address();
unsigned char IR_command();
unsigned char IR_repeat();

#endif

最后是通过红外遥控调节直流电机的案例的主函数代码:

#include <REGX52.H>
#include "int0.h"
#include "lcd1602.h"
#include "timer1.h"

unsigned char cnt,compare=0;	//定义计数器,比较值

void main()
{
	unsigned char date,num=0;	//定义date接收红外信号
	IR_init();	//初始化红外遥控
	Timer1Init();	//初始化计时器
	LCD_Init();	//初始化LCD
	while(1)
	{
		date=IR_command();	//循环读取红外信号
		compare=compare%201;	//设置compare的上限
		if(date==IR_0) compare=0;	//根据接受的信号不同,改变compare的大小
		if(date==IR_1) compare=100;
		if(date==IR_2) compare=150;
		if(date==IR_3) compare=200;
		if(date==IR_RPT)	//当按下逐渐增加转速按钮时
		if(IR_repeat()) compare=compare+1;		//每收到一个重复信号,compare加一
		LCD_ShowNum(1,1,compare,3);	//输出compare的值
	}
}

void Timer1_Rountine()   interrupt 3	//定时器1中断,控制输出PWM信号
{
	TL1 = 0x66;		
	TH1 = 0xFC;
	cnt++;
	cnt=cnt%200;	//设置cnt的上限
	if(cnt<compare) P1_0=1;		//判断cnt与compare的大小并改变P1_0的值
	else P1_0=0;
}

我的有关51单片机的学习到此为止,本笔记中许多内容来自江协科技。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值