【51单片机系列笔记三】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

参考资料:江协科技
参考资料:stc89c52官方手册
51单片机蜂鸣器、ATC24C02-I2C总线、DS18B20温度传感器-1Wire、LCD1602液晶显示学习笔记。


一、蜂鸣器

蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器。源指的是振荡源。
有源蜂鸣器内部自带振荡源,通电即可发声,但是频率固定。
无源蜂鸣器需要外部提供振荡源才可发声,可以控制发出不同频率的声音。
在这里插入图片描述
此处使用了ULN2003芯片(可以不使用)。ULN2003是一个单片高电压、高电流的达林顿晶体管阵列集成电路。内部由7对NPN达林顿晶体管组成。
ULN2003芯片逻辑狂徒如下。当输入为高时,输出为低。输入和输出电平相反。
在这里插入图片描述
假设周期为1000ms,包含高电平和低电平各500ms。蜂鸣器翻转I/O频率为周期频率的2倍。
在这里插入图片描述

实验现象:蜂鸣器播放小星星片段。

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

sbit BUZZER = P2^5;
//以4分音符为基准,500ms
#define SPEED 500

//音符对应的频率
unsigned int code FreqTable[]=
{
	0,63628,63731,63835,63928,64021,64103,64185,64260,64331,
	64400,64463,64524,64580,64633,64684,64732,64777,64820,
	64860,64898,64934,64968,65000,65030,65058,65085,65110,
	65134,65157,65178,65198,65217,65235,65252,65268,65283
};

unsigned char music[] = {
	13,4,
	13,4,
	20,4,
	20,4,
	22,4,
	22,4,
	20,4+4,
	18,4,
	18,4,
	17,4,
	17,4,
	15,4,
	15,4,
	13,4+4,
	0xff
};

unsigned char freqIndex,musicIndex;

void Buzzer_Timer0_Init()
{
	TMOD &= 0XF0;
	TMOD |= 0X01;
	TR0 = 1;
	TL0 = 64536 % 256;
	TH0 = 64536 / 256;
	TF0 = 0;
	
	EA = 1;
	ET0 = 1;
}

void main()
{
	Buzzer_Timer0_Init();
	while(1)
	{
		//判断是否播放完了
		if(music[musicIndex] != 0xff)
		{
			freqIndex = music[musicIndex++];
			//每个音符的维持时长
			DelayXms(SPEED / 4 * music[musicIndex++]);
			//按下后抬手动作
			TR0 = 0;
			DelayXms(5);
			TR0 = 1;
		}
		else
		{
			//循环播放
			musicIndex = 0;	
		}
	}
}

void Timer0_Routine() interrupt 1
{
	TL0 = FreqTable[freqIndex] % 256;
	TH0 = FreqTable[freqIndex] / 256;
	BUZZER = !BUZZER;
}

二、AT24C02存储器-I2C

I2C总线

I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线。
两根通信线:SCL时钟线(Serial Clock)、SDA数据线(Serial Data)
同步、半双工,带数据应答、高位先行。
SCL和SDA都配置成开漏输出模式,并添加上拉电阻。开漏输出模式下,低电平具有很强的电流驱动能力。开漏输出和上拉电阻共同实现“线与”功能:I2C设备中,有一个输出低电平,总线为低电平;只有所有输出高电平,总线才为高电平。
在这里插入图片描述
空闲状态:SCL和SDA都为高电平。
起始条件:SCL为高电平,SDA下降沿(电平由高到低)
停止条件:SCL为高电平,SDA上升沿(电平由低到高)
在这里插入图片描述
发送字节:SCL为低电平,主机将数据按位依次放到SDA总线上(高位在前)。SCL为高电平,从机读取SDA总线上的数据。依次循环8次
在这里插入图片描述
接收字节:SCL为低电平,从机将数据按位依次SDA总线上(高位在前)。SCL为高电平,主机读取SDA总线上的数据。依次循环8次。
注意,主机在接收数据之前,必须先释放SDA总线,否则从机无法正确发送数据。
在这里插入图片描述
发送应答:主机在接收完一个字节之后,在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。
接收应答:主机在发送完一个字节之后,在下一个时钟接收一位数据,判断从机是否应答。注意,主机在接收应答之前,必须先释放SDA总线,否则从机无法正确发送应答。
发送应答是发送字节的特例(发送字节的1位),接收应答是接收字节的特例(接收字节的1位)
最后,软件模拟I2C通信过程中,所有操作都是针对主机的,从机的操作是不需要我们关心的。后续在编写代码时,建议除了停止条件外,其他状态(如起始条件、发送/接收字节、应答)都保持SCL为低电平。
在这里插入图片描述

AT24C02存储器

AT24C02是掉电不丢失的存储器,属于E2PROM,容量为256字节,共32页,每页8字节。采用I2C通信。
在这里插入图片描述
AT24C02内部结构图
在这里插入图片描述
AT24C02通信时,通常使用字节写和随机读写单个字节/数据,也可以采用页的方式读写多个数据(当读写完一个字节/数据时,内部字地址自动加1,读写后续数据)。其中,器件地址确定当前与主机通信的设备,字地址确定待读写数据的地址。I2C通信,读写数据是高位MSB先行。
在这里插入图片描述
在这里插入图片描述
实验现象:往存储器中读写数字33。

#include <REGX52.H>
#include "delay.h"
#include "LCD1602.h"

sbit SCL = P2^1;
sbit SDA = P2^0;
#define DEVICE_ADDR 0xa0

void I2C_Start()
{
	SDA = 1;
	SCL = 1;
	SDA = 0;
	SCL = 0;
}

void I2C_Stop()
{
	SDA = 0;
	SCL = 1;
	SDA = 1;
}

void I2C_ByteSend(unsigned char Data)
{
	unsigned char i;
	for(i = 0;i<8;i++)
	{
		//主机放数据
		SDA = (Data << i) & 0x80;
		//从机接收数据
		SCL = 1;
		//等待主机放数据
		SCL = 0;
	}
}

unsigned char I2C_ByteReceive()
{
	unsigned char i,Data = 0x00;
	//主机释放SDA,从机才可以发送数据
	SDA = 1;
	for(i = 0;i<8;i++)
	{
		//等待主机接收数据
		SCL = 1;
		//主机接收数据
		if(SDA)
		{
			Data |= (0x80 >> i);
		}
		//从机放数据
		SCL = 0;
	}
	return Data;
}

void I2C_AckSend(unsigned char Ack)
{
	SDA = Ack;
	SCL = 1;
	SCL = 0;
}

unsigned char I2C_AckReceive()
{
	unsigned char Ack;
	SDA = 1;
	SCL = 1;
	Ack = SDA;
	SCL = 0;
	return Ack;
}

void AT24C02_DataWriteByAddr(unsigned char RegAddr,Data)
{
	I2C_Start();
	I2C_ByteSend(DEVICE_ADDR);
	I2C_AckReceive();
	I2C_ByteSend(RegAddr);
	I2C_AckReceive();
	I2C_ByteSend(Data);
	I2C_AckReceive();
	I2C_Stop();
}

unsigned char AT24C02_DataReadByAddr(unsigned char RegAddr)
{
	unsigned char Data;
	
	I2C_Start();
	I2C_ByteSend(DEVICE_ADDR);
	I2C_AckReceive();
	I2C_ByteSend(RegAddr);
	I2C_AckReceive();
	
	I2C_Start();
	I2C_ByteSend(DEVICE_ADDR | 0x01);
	I2C_AckReceive();
	Data = I2C_ByteReceive();
	I2C_AckSend(1);
	I2C_Stop();
	return Data;
}

void main()
{
	unsigned char Data;
	LCD_Init();
	LCD_ShowString(1,1,"hello,world!");
	
	AT24C02_DataWriteByAddr(1,33);
	DelayXms(5);
	Data = AT24C02_DataReadByAddr(1);
	LCD_ShowNum(2,1,Data,2);
	
	while(1)
	{}
}

三、DS18B20温度传感器-1Wire

1-Wire总线

单总线(1-Wire BUS)是由Dallas公司开发的一种通用数据总线。
单总线通信线:DQ数据线
设备的DQ均要配置成开漏输出模式加上拉电阻,与I2C类似。
若此总线的从机采取寄生供电,则主机还应配一个强上拉输出电路。
在这里插入图片描述
初始化时序:控制器(主机)将DQ总线拉低超过480us,后释放总线。从机(DS18B20)检测到上升沿后,等待15~60us后,再将总线拉低5~60us,释放总线。
注意,控制器发送复位脉冲和接收都需要超过480us
在这里插入图片描述
写1时序:控制器将DQ总线拉低,在15us之内释放总线。从机在15us~60us之间读取数据(采样)。
写0时序:控制器将DQ总线拉低超过60us。从机在15us~60us之间读取数据(采样)。
读1/0时序:控制器将DQ总线拉低超过1us,后释放总线。从机将总线拉高表示发送1,拉低表示发送0,后释放总线。从机释放总线后,控制器读取数据(采样)控制器从拉低总线到读取数据必须在15us内完成
注意,写1时序、写0时序、读1/0时序持续时长都必须超过60us。循环8次,即为读写字节。
在这里插入图片描述

DS18B20温度传感器

DS18B20 数字温度传感器采用单总线(1-Wire)通信,内置温度过低/过高报警功能,可以寄生供电。
在这里插入图片描述
DS18B20内部结构。
64-BIT ROM:作为器件地址,用于总线通信的寻址
SCRATCHPAD(暂存器):用于总线的数据交互
EEPROM:用于保存温度触发阈值和配置参数
在这里插入图片描述
温度寄存器。温度数据以二进制补码数存储。其中位0~3确定测量精度。位4~10为有效数据。符号标志位(S)温度的正负极性:正数则 S=0,负数则 S=1。
在这里插入图片描述
在这里插入图片描述

配置寄存器,配置温度测量精度,分别有:0.5、0.25、0.125、0.0625。注意温度转换时间,精度越大,转换时间越长。
在这里插入图片描述
访问DS18B20步骤:

  • 第一步:初始化
  • 第二步:ROM 命令(紧跟任何数据交换请求)
  • 第三步:DS18B20 功能命令(紧跟任何数据交换请求)
    ROM 命令:DS18B20设备都有唯一的64 位 ROM 编码。因此,ROM 命令确定和主机通信的设备。共有5种命令:搜索 ROM[F0h]、读取 ROM[33h]、匹配 ROM[55h]、跳过 ROM[CCh]、警报搜索[ECh]。
    DS18B20 功能命令:确定了通信设备后,主机通过功能命令向 DS18B20 执行具体操作。功能命令有:温度转换[44h]、写入暂存寄存器[4Eh]、读取暂存寄存器[BEh]、拷贝暂存寄存器[48h]、召回 EEPROM[B8h]、读取供电模式[B4h]。
    在这里插入图片描述
    实验现象1:温度变换和温度读取
    实验现象2:配置温度过低、过高警报、拷贝暂存寄存器、读取暂存寄存器
#include <REGX52.H>
#include "delay.h"
#include "LCD1602.h"
#include <intrins.h>

sbit DQ = P3^7;

void Delay500us()		//@11.0592MHz
{
	unsigned char i;
	_nop_();
	i = 227;
	while (--i);
}

void Delay10us()		//@11.0592MHz
{
	unsigned char i;
	i = 2;
	while (--i);
}

void Delay60us()		//@11.0592MHz
{
	unsigned char i;
	i = 25;
	while (--i);
}

void Delay80us()		//@11.0592MHz
{
	unsigned char i;
	i = 34;
	while (--i);
}

unsigned char DS18B20_1Wire_Init()
{
	unsigned char Ack;
	DQ = 1;
	DQ = 0;
	Delay500us();
	DQ = 1;
	Delay80us();
	Ack = DQ;
	Delay500us();
	return Ack;
}

void DS18B20_1Wire_Write1()
{
	DQ = 0;
	Delay10us();
	DQ = 1;
	Delay60us();
}

void DS18B20_1Wire_Write0()
{
	DQ = 0;
	Delay60us();
	DQ = 1;
}

void DS18B20_1Wire_ByteSend(unsigned char Data)
{
	unsigned char i,temp;
	for(i = 0;i<8;i++)
	{
		temp = (Data >> i) & 0x01;
		if(temp)
		{
			DS18B20_1Wire_Write1();
		}
		else
		{
			DS18B20_1Wire_Write0();
		}
	}
}

unsigned char DS18B20_1Wire_ByteRead()
{
	unsigned char i,Data = 0x00;
	for(i = 0;i<8;i++)
	{
		DQ = 0;
		_nop_();
		_nop_();
		DQ = 1;
		Delay10us();
		if(DQ)
		{
			Data |= (0x01 << i);
		}
		Delay60us();
	}
	
	return Data;
}

void DS18B20_1Wire_TempConvert()
{
	DS18B20_1Wire_Init();
	DS18B20_1Wire_ByteSend(0xcc);
	DS18B20_1Wire_ByteSend(0x44);
}

void DS18B20_1Wire_TempRead(unsigned char *Data)
{
	DS18B20_1Wire_Init();
	DS18B20_1Wire_ByteSend(0xcc);
	DS18B20_1Wire_ByteSend(0xbe);
	Data[0] = DS18B20_1Wire_ByteRead();
	Data[1] = DS18B20_1Wire_ByteRead();
	Data[2] = DS18B20_1Wire_ByteRead();
	Data[3] = DS18B20_1Wire_ByteRead();
}

void DS18B20_1Wire_RegConfig()
{
	DS18B20_1Wire_Init();
	DS18B20_1Wire_ByteSend(0xcc);
	DS18B20_1Wire_ByteSend(0x4e);
	DS18B20_1Wire_ByteSend(25);
	DS18B20_1Wire_ByteSend(20);
	DS18B20_1Wire_ByteSend(0x7f);
}

void DS18B20_1Wire_SaveDataToEEPROM()
{
	DS18B20_1Wire_Init();
	DS18B20_1Wire_ByteSend(0xcc);
	DS18B20_1Wire_ByteSend(0x48);
}

void DS18B20_1Wire_Test1()
{
	int Temp;
	float T;
	unsigned char Data[4];
	LCD_Init();
	LCD_ShowString(1,1,"temp");
	
	LCD_ShowString(2,5,".");
	DS18B20_1Wire_TempConvert();
	DelayXms(1000);
	
	while(1)
	{
		DS18B20_1Wire_TempConvert();
		DelayXms(1000);
		DS18B20_1Wire_TempRead(Data);
		Temp = (Data[1] << 8) | Data[0];
		T = Temp / 16.0;
		if(T > 0)
		{
			LCD_ShowString(2,1,"+");
		}
		else
		{
			LCD_ShowString(2,1,"-");
			T = -T;
		}
		LCD_ShowNum(2,2,T,3);
		LCD_ShowNum(2,6,(unsigned long)(T * 10000) % 10000 ,4);
	}
}

void DS18B20_1Wire_Test2()
{
	unsigned char Data[4];
	LCD_Init();
	LCD_ShowString(1,1,"temp config");
	LCD_ShowString(2,1,"th:");
	LCD_ShowString(2,7,"tl:");
	
	DS18B20_1Wire_RegConfig();
	DS18B20_1Wire_SaveDataToEEPROM();
	while(1)
	{
		DS18B20_1Wire_TempRead(Data);
		LCD_ShowNum(2,4,Data[2],2);
		LCD_ShowNum(2,10,Data[3],2);
	}
}

void main()
{
	DS18B20_1Wire_Test1();
//	DS18B20_1Wire_Test2();
}

四、LCD1602液晶显示

LCD1602(Liquid Crystal Display)液晶显示屏是一种字符型液晶显示模块,可以显示ASCII码的标准字符和其它的一些内置特殊字符,还可以有8个自定义字符。
显示容量:16×2个字符,每个字符为5*8点阵。
在这里插入图片描述
LCD1602内部结构图。类似数码管,字模库相当于段码表,数据显示区相当于位选。
在这里插入图片描述
数据显示区比屏幕大,可以通过移屏形成滚动字幕。
在这里插入图片描述
LCD1602指令集,初始化配置输入方式、显示开关、功能设置、清屏等。
光标相当于内部地址,会自动增加/减少,往右/左读写数据。
在这里插入图片描述LCD1602写数据/指令时序
在这里插入图片描述
实验现象1:LCD1602显示数字、字符、字符串等。
实验现象2:LCD1602显示自定义字符

#include <REGX52.H>
#include <intrins.h>

sbit RS = P2^6;
sbit RW = P2^5;
sbit CE = P2^7;
#define IO P0

//此函数定义八个字符分别写入 CGRAM 的八个地址
unsigned char code pic[8][8]={ 
/*-- 调入了四幅图像:向上 --*/ 
/*-- 宽度 x 高度=5x8 --*/ 
/*-- 宽度不是 8 的倍数,现调整为:宽度 x 高度=8x8 --*/ 
{0x04,0x0E,0x15,0x04,0x04,0x04,0x04,0x00},//↑
{0x00,0x04,0x04,0x04,0x04,0x15,0x0E,0x04},//↓
{0x00,0x04,0x08,0x1F,0x08,0x04,0x00,0x00},//←
{0x00,0x04,0x02,0x1F,0x02,0x04,0x00,0x00},//→
{0x04,0x04,0x0A,0x1F,0x1F,0x0A,0x04,0x04},//占位符
{0,0,0,0,0,0,0,0}, 
{0,0,0,0,0,0,0,0}, 
{0,0,0,0,0,0,0,0} 
};

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

	_nop_();
	i = 2;
	j = 199;
	do
	{
		while (--j);
	} while (--i);
}

void LCD1602_WriteCommand(unsigned char Command)
{
	RS = 0;
	RW = 0;
	IO = Command;
	CE = 1;
	Delay1ms();
	CE = 0;
	Delay1ms();
}

void LCD1602_WriteData(unsigned char Data)
{
	RS = 1;
	RW = 0;
	IO = Data;
	CE = 1;
	Delay1ms();
	CE = 0;
	Delay1ms();
}

void LCD1602_Init()
{
	LCD1602_WriteCommand(0x38);
	LCD1602_WriteCommand(0x0c);
	LCD1602_WriteCommand(0x06);
	LCD1602_WriteCommand(0x01);
}

unsigned int LCD1602_Pow(unsigned char x,y)
{
	unsigned int mul = 1;
	while(y--)
	{
		mul *= x;
	}
	return mul;
}

void LCD1602_ShowChar(unsigned char row,column,Data)
{
	if(row == 1)
	{
		LCD1602_WriteCommand(0x80 | (column - 1));
	}
	if(row == 2)
	{
		LCD1602_WriteCommand(0xc0 | (column - 1));
	}
	LCD1602_WriteData(Data);
}
 
void LCD1602_ShowNum(unsigned char row,column,unsigned int Data,unsigned char length)
{
	unsigned char i;
	if(row == 1)
	{
		LCD1602_WriteCommand(0x80 | (column - 1));
	}
	if(row == 2)
	{
		LCD1602_WriteCommand(0xc0 | (column - 1));
	}
	for(i = 0;i<length;i++)
	{	
		LCD1602_WriteData('0' + Data / LCD1602_Pow(10,length - 1 - i) % 10);
	}
}

void LCD1602_ShowStr(unsigned char row,column,unsigned char *str)
{
	if(row == 1)
	{
		LCD1602_WriteCommand(0x80 | (column - 1));
	}
	if(row == 2)
	{
		LCD1602_WriteCommand(0xc0 | (column - 1));
	}
	while(*str != '\0')
	{
		LCD1602_WriteData(*str++);
	}
}

//add 是 CGRAM 的地址(0~7)
//*pic_num 是指向一个 8 位数组的首地址
void LCD1602_WritePic(unsigned char add,unsigned char *pic_num) 
{ 
	unsigned char i; 
	add <<= 3; 
	for(i=0;i<8;i++) 
	{ 
		LCD1602_WriteCommand(0x40|(add+i)); 
		LCD1602_WriteData(*pic_num++); 
	} 
}

void LCD1602_Test2_CustomShow()
{
	unsigned char i;
	LCD1602_Init();
	//写八个自定义字符到CGRAM的0~7地址中
	for(i=0;i<8;i++) 
	{
		LCD1602_WritePic(i,pic[i]);
	} 
	 //显示八个自定义字符从第1行第1列开始
	LCD1602_WriteCommand(0x80); 
	for(i=0;i<0x08;i++) 
	{
		LCD1602_WriteData(i);
	}	
}

void LCD1602_Test1()
{
	LCD1602_Init();
	LCD1602_ShowChar(1,1,'>');
	LCD1602_ShowNum(2,1,65200,4);
	LCD1602_ShowStr(1,5,"hello,world!");
}

void main()
{
//	LCD1602_Test1();
	LCD1602_Test2_CustomShow();
	while(1)
	{}
}

问题

1、关于DS18B20温度传感器,设置温度过高/低警报后,执行警报搜索ROM命令,如何接收警报置位标志位?还是说必须使用代码比较温度高低,再搭配蜂鸣器模拟温度报警?
2、关于LCD1602显示自定义字符,只能按行取模吗?将8个自定义字符存储在CGRAM的0~7地址中,字符的段码表存放的地址?
每个自定义字符使用取模软件,按行取模,需要8行来存储。每行用十六进制表示,需要一个地址来存储,也就是每个自定义字符需要8个地址来存储。LCD1602中CGRAM地址0~3FH,共有64个地址。因此,LCD1602最多可自定义8(64/8)字符。
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值