51单片机学习笔记

51单片机学习笔记

一、开发板各功能介绍

alt text

序号 板载资源 说明
1 数码管模块 2个四位一体共阴数码管,可显示8位数字字符
2 LCD1602接口 可兼容LCD6602/LCD9648液晶屏,可显示数字字符等
3 LCD12864接口 兼容LCD9648/MiniLCD12864,可显示汉字/图像
4 8*8LED点阵 可显示数字/字符等
5 LED模块 8个LED,可实现流水灯等显示
6 矩阵按键 4*4矩阵按键,输入装置
7 红外接收头 NEC协议,可实现遥控
8 DS18B20温度传感器 可实现温度采集
9 NRF24L01接口 可实现2.4G远程遥控通信
10 独立按键 输入装置
11 MicroUSB接口 电源输入,程序下载
12 USB转TTL模块 CH340C芯片,可作电脑USB与单片机串口下载和通信
13 3.3V电源模块 ASM1117-3.3芯片,将5V转为3.3V
14 电源开关 控制系统开关
15 AD/DA 可采集外部模拟信号和输出模拟电压
16 EEPROM模块 可储存256字节数据,掉电不丢失
17 复位按键 系统复位
18 蜂鸣器 可作报警提示
19 DS1302时钟模块 可作时钟发生
20 步进电机驱动模块 ULN2003芯片,可作直流电机、28BYJ48步进电机驱动
21 STC89C52单片机座及IO 固定单片机,并将IO引出
22 晶振 提供基准频率
23 74HC595 扩展IO,控制LED点阵
24 74HC245 驱动数码管段选显示
25 74HC138 驱动数码管段选显示

二、LED

2-1 点亮LED

alt text

从该单片机的LED模块原理图可以看到是由 P2 接口控制。

#include <REGX52.H>//调用芯片头文件

int main(){
	P2=0x55;//0101 0101(1为高电平,0为低电平)
    //一般写为十六进制
}

由 Keil5 生成 .hex 文件,再通过 stc-isp 下载到单片机中成功点亮。

alt text

2-2 LED闪烁

LED闪烁需要用到延时函数,可用 stc-isp 的软件延时计算器生成。

#include <REGX52.H>
#include <INTRINS.H>//_nop_()函数所需头文件

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){
		P2=0xFE;
		Delay500ms();
		P2=0xFF;
		Delay500ms();
	}
}

延时Plus版

//先生成延时1ms的函数,再进行循环xms次,达到延时目的
#include <INTRINS.H>

void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms--){
		_nop_();
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
	}
	
}

2-3 LED流水灯

通过循环加位运算实现

#include <REGX52.H>
#include <INTRINS.H>

void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms--){
		_nop_();
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
	}
}

int i;

void main(){
	while(1){
		for(i=0;i<8;i++){
			P2=0xFF;
			P2=P2^(1<<i);
			Delay1ms(200);
		}
	}
}

三、独立按键

单片机上电的时候所有io口默认都是高电平

3-1 独立按键控制LED

由独立按键原理图可以看到其接口。

alt text

实现按下 K1 使 D1 亮起,松开熄灭。

//P3-1代表P31
//P2-0代表P20
#include <REGX52.H>

void main(){
	while(1){
		if(P3_1==0){
			P2_0=0;
		}else{
			P2_0=1;
		}
	}
}

3-2 消除独立按键的抖动

alt text

使用 Delay 函数去消除按键抖动

#include <REGX52.H>
#include <INTRINS.H>

void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	
	while(xms--){
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
	}
	
}

void main(){
	while(1){
		if(P3_1==0){
			Delay1ms(20);//按下延时20ms
			while(P3_1==0){}
			Delay1ms(20);//松开延时20ms
			P2_0=~P2_0;
		}
	}
}

3-3 独立按键控制LED显示二进制

#include <REGX52.H>
#include <INTRINS.H>

void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	
	while(xms--){
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
	}
	
}

void main(){
	int i,j;
	unsigned int lnum=0xFF;
	while(1){
		if(P3_1==0){
			Delay1ms(20);
			while(!P3_1){}
			Delay1ms(20);
			lnum--;
            //因为该单片机D1在左侧,所以需要进行高低位转换
			for(i=0,j=7;i<8;i++,j--){
				if(((P2>>j)&1)!=((lnum>>i)&1)) P2=P2^(1<<j);
			}
		}
	}
}

3-4 独立按键控制LED移位

长按 K1 向左移动

长按 K2 向右移动

#include <REGX52.H>
#include <INTRINS.H>

void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	
	while(xms--){
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
	}
	
}

void main(){
	int l=0;//标记LED的位置
	P2=0xFE;
	while(1){
		if(P3_1==0){
			Delay1ms(20);
			while(!P3_1){//实现长按
				P2|=(1<<l);
				l--;
				if(l<0) l=7;
				P2&=~(1<<l);
				Delay1ms(200);
			}
			Delay1ms(20);
		}
		if(P3_0==0){
			Delay1ms(20);
			while(!P3_0){//实现长按
				P2|=(1<<l);
				l++;
				if(l>7) l=0;
				P2&=~(1<<l);
				Delay1ms(200);
			}
			Delay1ms(20);
		}
	}
}

四、数码管

四位数码管的引脚

alt text

alt text

该数码管为共阴极接法

接法检测方法

  • COM共阴极,若是GRN就是共阳极
  • 供电平测试

4-1 74HC138译码器

alt text

使用 P22 P23 P24 三个I口控制八个输出端,从而控制数码管共极的高低电平。

控制方法

  • 3位的二进制数转为十进制控制哪个输出端为低电平
  • C控制高位,B控制中位,A控制低位

例如

C B A
P24 P23 P22
1 0 1

则 Y5 所接的 LED6 为0,其余都为1。

4-2 数码管动态显示

由原理图上的 P0 接口和 P22 P23 P24 便可控制单个数码管显示。

而实现动态显示只需要不停扫描即可。

#include <REGX52.H>

unsigned char Nixienum[15]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};//初始化

void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms--){
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
	}
}

void Nixie_tube(unsigned char location,number){
	
	location=(8-location);
	P2_2=(location>>0)&1;
	P2_3=(location>>1)&1;
	P2_4=(location>>2)&1;
	P0=Nixienum[number];
	Delay1ms(1);//消影
	P0=0x00;
}

void main(){
	int i;
	while(1){
		for(i=1;i<=8;i++){
			Nixie_tube(i,i);
		}
	}
}

五、LCD1602调试工具

alt text

用封装好的 LED1602.c 进行调试

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

void main(){
	LCD_Init();
	LCD_ShowString(1,1,"Hello World!");
	while(1){}
}

其他函数与用法请看 LED1602.c

六、矩阵键盘

alt text

当按键按下时,接通两个接口。

可以进行扫描来确认哪个按键按下。

  • 逐行扫描

  • 逐列扫描

主函数

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

unsigned char Keynum;

void main(){
	LCD_Init();
	LCD_ShowString(1,1,"MatrixKey!");
	while(1){
		Keynum=MatrixKey();
		if(Keynum){//如果按下则输出
			LCD_ShowNum(2,1,Keynum,2);
		}
	}
}

MatrixKey 模块(这里采用逐列扫描)

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

unsigned char MatrixKey(){
	unsigned char i,j,Keynumber=0;
	for(i=0;i<4;i++){
		P1=0xFF;//重置接口
		P1=P1&(~(1<<i));//让第i列为0
		for(j=4;j<8;j++){
			if(!(P1>>j&1)){//检测(i,j)按键是否按下
				Delay(20);
				while(!(P1>>j&1)){}
				Delay(20);
				Keynumber=(4-i)+4*(7-j);//获得按键值
				break;
			}
		}
	}
	return Keynumber;
}

七、定时器

7-1 定时器原理

待补

7-2 定时器使用

定时器一般可直接用 stc-isp 生成

按键控制LED流水方向

主函数

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

unsigned int LEDMode;

void main(){
	Timer0Init();
	P2=0xFE;
	while(1){
		unsigned int Keynum=Key();
		if(Keynum==1){//按K1向左
			LEDMode=1;
		}
		if(Keynum==0){//按K2向右
			LEDMode=0;
		}
	}
}


void Timer0_Routine() interrupt 1{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=200){
		T0Count=0;
		if(LEDMode==0){//按K2向右
			P2=_crol_(P2,1);
		}
		if(LEDMode==1){//按K1向左
			P2=_cror_(P2,1);
		}
	}
}

Timer0 模块

#include <REGX52.H>

void Timer0Init(void)		//1毫秒@11.0592MHz
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;			//进入中断
	EA=1;
	PT0=0;
}

/*
void Timer0_Routine() interrupt 1{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=1000){
		T0Count=0;
	}
}

*/

Key 模块

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

unsigned char Key(){
	
	if(P3_0==0){Delay(20);while(!P3_0){}Delay(20);return 0;}
	if(P3_1==0){Delay(20);while(!P3_1){}Delay(20);return 1;}
	if(P3_2==0){Delay(20);while(!P3_2){}Delay(20);return 2;}
	if(P3_3==0){Delay(20);while(!P3_3){}Delay(20);return 3;}
	
	return 4;
}

时钟

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

unsigned char hr,mins,sec;

void main(){
	Timer0Init();
	LCD_Init();
	LCD_ShowString(1,1,"Clock:");
	LCD_ShowString(2,1,"  :  :");
	while(1){
		LCD_ShowNum(2,1,hr,2);
		LCD_ShowNum(2,4,mins,2);
		LCD_ShowNum(2,7,sec,2);
	}
}

void Timer0_Routine() interrupt 1{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=1000){
		T0Count=0;
		sec++;
		if(sec>=60) sec=0,mins++;
		if(mins>=60) mins=0,hr++;
		if(hr>=24) hr=0;
	}
}

八、串口

8-1 串口通信原理

待补

8-2 串口与电脑数据传输

依然可以使用 stc-isp 去生成 UART_Iint 函数

电脑通过串口控制LED与发送数据

主函数

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

void main(){
	UART_Init();
	while(1){
	}
}

void UART_Routine() interrupt 4
{
	if(RI==1){
		P2=SBUF;
		UART_SendByte(SBUF);
		RI=0;
	}
}

UART 模块

#include <REGX52.H>

void UART_Init(void)		//4800bps@11.0592MHz
{
	PCON &= 0x7F;		//波特率不倍速
	SCON = 0x50;		//8位数据,可变波特率
	TMOD &= 0x0F;		//清除定时器1模式位
	TMOD |= 0x20;		//设定定时器1为8位自动重装方式
	TL1 = 0xFA;		//设定定时初值
	TH1 = 0xFA;		//设定定时器重装值
	ET1 = 0;		//禁止定时器1中断
	TR1 = 1;		//启动定时器1
	EA=1;			//进入中断
	ES=1;
}

void UART_SendByte(unsigned char Byte){
	SBUF=Byte;
	while(TI==0);
	TI=0;
}

//串口中断函数模板
/*
void UART_Routine() interrupt 4
{
	if(RI==1){

		RI=0;
	}
}
*/

九、LED点阵屏

alt text

  • LED点阵屏结构类似于数码管,只不过数码管把每列像素以“8”字排列
  • LED点阵屏有共阴和共阳两种接法(此LED点阵屏为共阴接法)
  • LED点阵屏需要进行逐行或逐列扫描,才能使所以LED同时显示

9-1 74HC595模块

alt text

alt text

  • 74H595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据,多片级联后,可输出更多位,常用于IO口的扩展。
  • 内部实现类似于栈,一直向栈顶输入,再并行输出。

9-2 LED点阵屏实现图像&动画

使用LED点阵屏时,需将单片机上 J24 中的 GND 和 OE 短接

输出 Hello!

主函数

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

unsigned char i=0,Offset=0;//Offset为偏移量
//code可以把数据存在flash里,防止占用过多内存
unsigned char code Animation[]={
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	0xFF,0x08,0x08,0x08,0xFF,0x00,0x0E,0x15,0x15,0x15,0x08,0x00,0xFE,0x01,0x02,0x00,
	0xFE,0x01,0x02,0x00,0x0E,0x11,0x11,0x0E,0x00,0x00,0x7D,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};//通过取模软件获得图像数据

void main(){
	MatrixLED_Init();
	Timer0_Init();
	while(1){
		for(i=0;i<8;i++){
			MatrixLED_ShowColum(i,Animation[i+Offset]);
		}
	}
}

void Timer0_Routine() interrupt 1{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=50){
		T0Count=0;
		Offset++;
		if(Offset>44) Offset=0;
	}
}

MatrixLED 模块

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

/*
为方便记住各接口功能,这里采用重新定义
*/
sbit RCK=P3^5; //RCLK
sbit SCK=P3^6; //SRCLK
sbit SER=P3^4;

#define MATRIX_LED_PORT P0

void MatrixLED_Init(){
	SCK=0;
	RCK=0;
}

void _74H595_WriteByte(unsigned char Byte){
	unsigned char i;
	for(i=0;i<8;i++){
		SER=Byte&(0x80>>i);
		SCK=1;
		SCK=0;
	}
	RCK=1;
	RCK=0;
}

/**
   *  @brief LED点阵屏显示一列数据
   *  @param Column 选择的列,范围0~7,0在左边
   *  @param Data 选择列显示的数据,高位在上,1为亮
   *  @retval 无
   */
void MatrixLED_ShowColum(unsigned char Colum,Data){
	_74H595_WriteByte(Data);
	MATRIX_LED_PORT=~(0x80>>Colum);
	Delay(1);
	MATRIX_LED_PORT=0xFF;
}

十、DS1302实时时钟

alt text

引脚名 作用 引脚名 作用
VCC2 主电源 CE 芯片使能
VCC1 备用电源 IO 数据输入/输出
GND 电源地 SCLK 串行时钟
X1,X2 32.768KHz晶振

寄存器定义

alt text

数据存储

DS1302数据存储为 BCD 码

  • BCD码(Binary Coded Decimal),用4位二进制数来表示1位十进制数
  • 在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法
  • BCD码转十进制:DEC=BCD/16*10+BCD%16; (2位BCD)
  • 十进制转BCD码:BCD=DEC/10*16+DEC%10; (2位BCD)

时钟

主函数

#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
#include "Key.h"
#include "Timer0.h"

unsigned char Second;

void main(){
	LCD_Init();
	DS1302_Init();
	DS1302_SetTime();
	LCD_ShowString(1,1,"20  /  /");
	LCD_ShowString(2,1,"  :  :");
	while(1){
		DS1302_ReadTime();
		LCD_ShowNum(1,3,DS1302_Time[0],2);
		LCD_ShowNum(1,6,DS1302_Time[1],2);
		LCD_ShowNum(1,9,DS1302_Time[2],2);
		LCD_ShowNum(2,1,DS1302_Time[3],2);
		LCD_ShowNum(2,4,DS1302_Time[4],2);
		LCD_ShowNum(2,7,DS1302_Time[5],2);
	}
}

DS1302 模块

#include <REGX52.H>

sbit DS1302_CE=P3^5;
sbit DS1302_IO=P3^4;
sbit DS1302_SCLK=P3^6;

#define DS1302_SECOND 	0x80
#define DS1302_MINUTE 	0x82
#define DS1302_HOUR 	0x84
#define DS1302_DATE 	0x86
#define DS1302_MONTH 	0x88
#define DS1302_DAY 		0x8A
#define DS1302_YEAR 	0x8C
#define DS1302_WP 		0x8E

unsigned char DS1302_Time[]={24,11,2,15,50,0,6};

void DS1302_Init()
{
	DS1302_CE=0;
	DS1302_SCLK=0;
}

/**
   *  @brief 按命令写入数据
   *  @param Command命令,Data写入的数据	
   *  @retval 无
   */
void DS1302_WriteByte(unsigned char Command,Data)
{
	unsigned char i;
	DS1302_CE=1;
	for(i=0;i<8;i++){
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=1;
		DS1302_SCLK=0;
	}
	for(i=0;i<8;i++){
		DS1302_IO=Data&(0x01<<i);
		DS1302_SCLK=1;
		DS1302_SCLK=0;
	}
	DS1302_CE=0;
}

/**
   *  @brief 按命令读取数据
   *  @param Command命令
   *  @retval 读出的数据
   */
unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i,Data=0;
	Command|=0x01;
	DS1302_CE=1;
	for(i=0;i<8;i++){
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=0;//为使时序对上,这里先赋0 再赋1
		DS1302_SCLK=1;
	}
	for(i=0;i<8;i++){
		DS1302_SCLK=1;
		DS1302_SCLK=0;
		if(DS1302_IO){Data|=(0x01<<i);}
	}
	DS1302_CE=0;
	DS1302_IO=0;
	return Data;
}


/**
   *  @brief 时钟的初始设定
   *  @param 无
   *  @retval 无
   */
void DS1302_SetTime()
{
	DS1302_WriteByte(DS1302_WP,0x00);//写入保护关
	DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);
	DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
	DS1302_WriteByte(DS1302_DATE,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
	DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
	DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
	DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
	DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
	DS1302_WriteByte(DS1302_WP,0x80);//写入保护开
}

/**
   *  @brief 读出时间
   *  @param 无
   *  @retval 无
   */
void DS1302_ReadTime()
{
	unsigned char Temp;
	Temp=DS1302_ReadByte(DS1302_YEAR);
	DS1302_Time[0]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_MONTH);
	DS1302_Time[1]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_DATE);
	DS1302_Time[2]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_HOUR);
	DS1302_Time[3]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_MINUTE);
	DS1302_Time[4]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_SECOND);
	DS1302_Time[5]=Temp/16*10+Temp%16;
	Temp=DS1302_ReadByte(DS1302_DAY);
	DS1302_Time[6]=Temp/16*10+Temp%16;
}

十一、蜂鸣器

蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器

  • 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定

  • 无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音

alt text

该蜂鸣器借用了 ULN2003D 的一个接口

alt text

需注意ULN2003D中有取反,既输入 1 输出 0

按键提示音

主函数

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

unsigned char KeyNum;

void main(){
	while(1){
		KeyNum=Key();
		if(KeyNum){
			Buzzer_Time(1000);
		}
		KeyNum=0;
	}
}  

Buzzer 模块

#include <REGX52.H>
#include <INTRINS.H>

sbit Buzzer=P2^5;

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

void Buzzer_Time(unsigned int ms){
	unsigned int i;
	for(i=0;i<ms*2;i++){
		Buzzer=!Buzzer;
		Buzzer_Delay500us();
	}
}

十二、AT24C02 (I2C总线)

  • AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
  • 存储介质:E2PROM
  • 通讯接口:I2C总线
  • 容量:256字节

alt text

12-1 I2C总线

  • I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线
  • 两根通信线:SCL(Serial Clock)、SDA(Serial Data)
  • 同步、半双工,带数据应答

I2C时序结构

alt text

alt text

alt text

alt text

I2C数据帧

alt text

alt text

alt text

AT24C02数据存储

AT24C02数据帧

alt text

AT24C02写入与读取数据

主函数

#include <REGX52.H>
#include "Key.h"
#include "LCD1602.h"
#include "AT24C02.h"
#include "Delay.h"

unsigned char Data,KeyNum;
unsigned int Num;

void main(){
	LCD_Init();
	LCD_ShowNum(1,1,Num,5);
	while(1){
		KeyNum=Key();
		if(KeyNum==1){
			Num++;
			LCD_ShowNum(1,1,Num,5);
		}
		if(KeyNum==2){
			Num--;
			LCD_ShowNum(1,1,Num,5);
		}
		if(KeyNum==3){
			AT24C02_WriteByte(0,Num%256);
			Delay(5);
			AT24C02_WriteByte(1,Num/256);
			LCD_ShowString(2,1,"Write OK");
			Delay(1000);
			LCD_ShowString(2,1,"        ");
		}
		if(KeyNum==4){
			//需要用Data进行转接,不然会出现错误
			Data=AT24C02_ReadByte(0);
			Num=Data;Data=AT24C02_ReadByte(1);
			Num|=Data<<8;
			LCD_ShowNum(1,1,Num,5);
			LCD_ShowString(2,1,"Read OK");
			Delay(1000);
			LCD_ShowString(2,1,"        ");
		}
	}
}

I2C 模块

#include <REGX52.H>

sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;

void I2C_Start(){
    I2C_SDA=1;
    I2C_SCL=1;
    I2C_SDA=0;
    I2C_SCL=0;
}

void I2C_Stop(){
    I2C_SDA=0;
    I2C_SCL=1;
    I2C_SDA=1;
}

void I2C_SendByte(unsigned char Byte){
    unsigned char i;  
    for(i=0;i<8;i++){
        I2C_SDA=Byte&(0x80>>i);
        I2C_SCL=1;
        I2C_SCL=0;
    }
}

unsigned char I2C_ReceiveByte(){
    unsigned char Byte=0x00,i;
    I2C_SDA=1;
    for(i=0;i<8;i++){
        I2C_SCL=1;
        if(I2C_SDA){Byte|=(0x80>>i);}
        I2C_SCL=0;
    }
    return Byte;
}

void I2C_SendAck(unsigned char AckBit){
    I2C_SDA=AckBit;
    I2C_SCL=1;
    I2C_SCL=0;
}

unsigned char I2C_ReceiveAck(){
    unsigned char AckBit;
    I2C_SDA=1;
    I2C_SCL=1;
    AckBit=I2C_SDA;
    I2C_SCL=0;
    return AckBit;
}

AT24C02 模块

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

#define AT24C02_ADDRESS 0xA0

void AT24C02_WriteByte(unsigned char WordAddress,Data){
	I2C_Start();
    I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAck();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	I2C_SendByte(Data);
	I2C_ReceiveAck();
	I2C_Stop();
}

unsigned char AT24C02_ReadByte(unsigned char WordAddress){
	unsigned char Data;
	I2C_Start();
    I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAck();
    I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
    I2C_Start();
    I2C_SendByte(AT24C02_ADDRESS|0x01);
	I2C_ReceiveAck();
    Data=I2C_ReceiveByte();
    I2C_SendAck(1);
    I2C_Stop();
	return Data;
}

十三、DS18B20温度传感器

13-1 DS18B20介绍

  • DS18B20是一种常见的数字温度传感器
  • 测温范围:-55℃到+125℃
  • 通信接口:1-Wire(单总线)
  • 其他特征:可形成总线结构、内置温度报警器、可寄生供电

alt text

储存器结构

alt text

13-2 单总线介绍

  • 一根通信线:DQ
  • 异步,半双工
  • 单总线只需一根线便可实现数据双向传输

时序

alt text

alt text

alt text

接收与发送一个字节时,都是低位在前

13-3 操作DS18B20

  • 初始化:从机复位,主机判断从机是否响应
  • ROM操作:ROM指令+本指令需要的读写操作
  • 功能操作:功能指令+本指令需要的读写操作
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]

注:h代表16进制

数据帧

alt text

温度储存格式

由16位整数组成

alt text

前5位为符号位

alt text

温度显示

主函数

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

unsigned char Ack;
float T;

void main(){
	//先进行温度检测
	DS18B20_ConverT();
	Delay(500);
	LCD_Init();
	LCD_ShowString(1,1,"Temperature:");
	while(1){
		DS18B20_ConverT();
		T=DS18B20_ReadT();
		if(T<0){
			LCD_ShowChar(2,1,'-');
			T=-T;
		}else
		LCD_ShowChar(2,1,'+');
		LCD_ShowNum(2,2,T,3);//显示整数部分
		LCD_ShowChar(2,5,'.');
		LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);//显示小数部分
	}
}

OneWire 模块

#include <REGX52.H>
#include <INTRINS.H>

sbit OneWire_DQ=P3^7;

unsigned char OneWire_Init(){
	unsigned char i,Ack;
	OneWire_DQ=1;
	OneWire_DQ=0;
	_nop_();i = 227;while (--i); //500us
	OneWire_DQ=1;
	i = 29;while (--i); //70us
	Ack=OneWire_DQ;
	_nop_();i = 135;while (--i); //300us
	return Ack;
}

void OneWire_SendBit(unsigned char Bit){
	unsigned char i;
	OneWire_DQ=1;
	OneWire_DQ=0;
	_nop_();i = 3;while (--i); //10us
	OneWire_DQ=Bit;
	i = 25;while (--i); //55us
	OneWire_DQ=1;
}

unsigned char OneWire_ReceiveBit(){
	unsigned char i,Bit;
	OneWire_DQ=1;
	OneWire_DQ=0;
	i = 2;while (--i); //5us
	OneWire_DQ=1;
	i = 2;while (--i); //5us
	Bit=OneWire_DQ;
	i = 25;while (--i); //55us
	return Bit;
}

void OneWire_SendByte(unsigned char Byte){
	unsigned char i;
	for(i=0;i<8;i++){
		OneWire_SendBit(Byte&(0x01<<i));
	}
}

unsigned char OneWire_ReceiveByte(){
	unsigned char i,Byte=0x00;
	for(i=0;i<8;i++){
		if(OneWire_ReceiveBit()) Byte|=0x01<<i;
	}
	return Byte;
}

DS18B20 模块

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

//ROM指令
#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_SEARCH_ROM 0xF0
#define DS18B20_READ_ROM 0x33
#define DS18B20_MATCH_ROM 0x55
#define DS18B20_ALARM_SEARCH 0xEC
//功能指令
#define DS18B20_CONVERT_T 0x44
#define DS18B20_WRITE_SCRATCHPAD 0x4E
#define DS18B20_READ_SCRATCHPAD 0xBE
#define DS18B20_COPY_SCRATCHPAD 0x48
#define DS18B20_RECALL_E2 0xB8
#define DS18B20_READ_POWER_SUPPLY 0xB4

void DS18B20_ConverT(){
	OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
	OneWire_SendByte(DS18B20_CONVERT_T);
}

float DS18B20_ReadT(){
	unsigned char TLSB,TMSB;
	int Temp;
	float T;
	OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
	OneWire_SendByte(DS18B20_READ_SCRATCHPAD);
	TLSB=OneWire_ReceiveByte();
	TMSB=OneWire_ReceiveByte();
	Temp=(TMSB<<8)|TLSB;
	T=Temp/16.0;//因为最低位是2^-4
	return T;
}

温度报警器

主函数

#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
#include "Buzzer.h"
#include "AT24C02.h"
#include "Key.h"
#include "Timer0.h"

unsigned char Ack,KeyNum;
float TShow,T;
char Tlow,Thigh;

void main(){
	Thigh=AT24C02_ReadByte(1);
	Tlow=AT24C02_ReadByte(0);
	if(Thigh>125||Tlow<-55||Thigh<=Tlow){
		Thigh=25;Tlow=0;
	}
	DS18B20_ConverT();
	Delay(500);
	Timer0Init();
	LCD_Init();
	LCD_ShowString(1,1,"T:");
	LCD_ShowString(2,1,"TH:");
	LCD_ShowString(2,10,"TL:");
	LCD_ShowSignedNum(2,4,Thigh,3);
	LCD_ShowSignedNum(2,13,Tlow,3);
	while(1){
		KeyNum=Key();
		if(KeyNum){
			if(KeyNum==1){
				Thigh++;
				if(Thigh>125){Thigh=125;}
			}
			if(KeyNum==2){
				Thigh--;
				if(Thigh<=Tlow){Thigh++;}
			}
			if(KeyNum==3){
				Tlow++;
				if(Tlow>=Thigh){Tlow--;}
			}
			if(KeyNum==4){
				Tlow--;
				if(Tlow<-55){Thigh=-55;}
			}
			LCD_ShowSignedNum(2,4,Thigh,3);
			LCD_ShowSignedNum(2,13,Tlow,3);
			AT24C02_WriteByte(1,Thigh);
			AT24C02_WriteByte(0,Tlow);
		}
	}
}


void Timer0_Routine() interrupt 1{
	static unsigned int T0Count;
	TL0 = 0x66;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=500){
		//温度显示
		DS18B20_ConverT();
		T=DS18B20_ReadT();
		TShow=T;
		if(T<0){
			LCD_ShowChar(1,3,'-');
			TShow=-TShow;
		}else
		LCD_ShowChar(1,3,'+');
		LCD_ShowNum(1,4,TShow,3);
		LCD_ShowChar(1,7,'.');
		LCD_ShowNum(1,8,(unsigned long)(TShow*100)%100,2);

		//检测是否超出范围
		if(T>Thigh){
			LCD_ShowString(1,13,"OV:H");
			Buzzer_Time(50);
		}
		else if(T<Tlow){
			LCD_ShowString(1,13,"OV:L");
			Buzzer_Time(50);
		}else LCD_ShowString(1,12,"     ");

		T0Count=0;
	}
}

十四、直流电机

alt text

14-1 PWM介绍

  • PWM(Pulse Width Modulation)即脉冲宽度调制,在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速、开关电源等领域
  • TS 为周期
  • 频率 = 1TS
  • 占空比 = TONTS
  • 精度 = 占空比变化步距

(既通过开关的频率来控制亮度或速度)

14-2 LED呼吸灯

可以将呼吸放到定时器里

LED呼吸灯

#include <REGX52.H>

sbit LED=P2^0;

void Delay(unsigned int t){
	while(t--);
}

void main(){
	unsigned int Time,i;
	while(1){
		for(Time=0;Time<100;Time++){
			for(i=0;i<20;i++){
				LED=0;
				Delay(100-Time);
				LED=1;
				Delay(Time);
			}
		}
		for(Time=100;Time>0;Time--){
			for(i=0;i<20;i++){
				LED=0;
				Delay(100-Time);
				LED=1;
				Delay(Time);
			}
		}
	}
}

14-3 电机调速

电机调速

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

sbit Motor = P1^0;

unsigned int Compare,KeyNum,Speed;

void main(){
	Timer0Init();
	while(1){
		KeyNum=Key();
		if(KeyNum==1){
			Speed++;
			Speed%=4;
		}
		if(Speed==0) Compare=0;
		if(Speed==1) Compare=40;
		if(Speed==2) Compare=80;
		if(Speed==3) Compare=100;
	}
}

void Timer0_Routine() interrupt 1{
	static unsigned int T0Count;
	TL0 = 0xA4;		//设置定时初值
	TH0 = 0xFF;		//设置定时初值
	T0Count++;
	T0Count%=100;
	if(T0Count>=Compare){
		Motor=0;
	}else{
		Motor=1;
	}
}

十五、AD/DA

15-1 AD/DA介绍

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

待补

十六、红外遥控器

16-1 红外遥控器

  • 通信方式:单工,异步
  • 红外LED波长:940nm
  • 通信协议标准:NEC标准

alt text

alt text

Repeat为重复发送,比如一直按着按键不放

alt text

16-2 外部中断

发送红外信号时,需要触发外部中断进行接受信号

alt text

INT0和INT1为中断引脚

  • STC89C52有4个外部中断
  • STC89C52的外部中断有两种触发方式:
    下降沿触发和低电平触发

外部中断寄存器与定时器类似

alt text

16-3 红外遥控

主函数

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

unsigned char Num;
unsigned char Command;
unsigned char Address;


void main(){
	IR_Init();
	LCD_Init();
	LCD_ShowString(1,1,"ADDR  CMD  NUM");
	LCD_ShowString(2,1,"00    00   000");
	while(1){
		if(IR_GetDataFlag()||IR_GetRepeatFlag()){
			Address=IR_GetAddress();
			Command=IR_GetCommand();
			
			LCD_ShowHexNum(2,1,Address,2);
			LCD_ShowHexNum(2,7,Command,2);
			if(Command==IR_VOL_MINUS){
				Num--;
			}
			if(Command==IR_VOL_ADD){
				Num++;
			}
			LCD_ShowNum(2,12,Num,3);
		}
	}
}

Timer0 模块

#include <REGX52.H>

void Timer0_Init(void)		//1毫秒@11.0592MHz
{
	TMOD &= 0xF0;		//设置定时器模式
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0;		//设置定时初值
	TH0 = 0;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 0;		//定时器0不计时
}

void Timer0_SetCounter(unsigned int Value){
	TH0=Value/256;
	TL0=Value%256;
}

unsigned int Timer0_GetCounter(){
	return (TH0<<8)|TL0;
}

void Timer0_Run(unsigned char Flag){
	TR0=Flag;
}

Int0 模块

#include <REGX52.H>

void Int0_Init(){
	//启用外部中断
	IT0=1;
	IE0=0;
	EX0=1;
	EA=1;
	PX0=1;
}

/*
void Int0_Routine() interrupt 0
{
	
}

*/

IR 模块

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

unsigned int IR_Time;
unsigned char IR_State;
unsigned char IR_Data[4],IR_pData;
unsigned char IR_DataFlag;
unsigned char IR_RepeatFlag;
unsigned char IR_Address;
unsigned char IR_Command;


void IR_Init(){
	Int0_Init();
	Timer0_Init();
}

unsigned char IR_GetDataFlag(){
	if(IR_DataFlag){
		IR_DataFlag=0;
		return 1;
	}
	return 0;
}

unsigned char IR_GetRepeatFlag(){
	if(IR_RepeatFlag){
		IR_RepeatFlag=0;
		return 1;
	}
	return 0;
}

unsigned char IR_GetAddress(){
	return IR_Address;
}

unsigned char IR_GetCommand(){
	return IR_Command;
}

void Int0_Routine(void) interrupt 0
{
	if(IR_State==0){
		Timer0_SetCounter(0);
		Timer0_Run(1);
		IR_State=1;
	}
	else if(IR_State==1){
		IR_Time=Timer0_GetCounter();
		Timer0_SetCounter(0);
		if(IR_Time>12442-500&&IR_Time<12442+500){
			IR_State=2;
		}
		else if(IR_Time>10368-500&&IR_Time<10368+500){//判断Repeat
			IR_RepeatFlag=1;
			Timer0_Run(0);
			IR_State=0;
		}
		else {//防止出错
			IR_State=1;
		}
	}
	else if(IR_State==2){
		IR_Time=Timer0_GetCounter();
		Timer0_SetCounter(0);
		if(IR_Time>1032-500&&IR_Time<1032+500){
			IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8));
			IR_pData++;
		}    
		else if(IR_Time>2074-500&&IR_Time<2074+500){
			IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8));
			IR_pData++;
		}
		else {//防止出错
			IR_pData=0;
			IR_State=1;
		}
		if(IR_pData>=32){
			IR_pData=0;
			if((IR_Data[0]==~IR_Data[1])&&(IR_Data[2]==~IR_Data[3])){
				IR_Address=IR_Data[0];
				IR_Command=IR_Data[2];
				IR_DataFlag=1;
			}
			Timer0_Run(0);
			IR_State=0;
		}
	}
}
#ifndef __IR_H__
#define __IR_H__

#define IR_POWER		0x45
#define IR_MODE			0x46
#define IR_MUTE			0x47
#define IR_START_STOP	0x44
#define IR_PREVIOUS		0x40
#define IR_NEXT			0x43
#define IR_EQ			0x07
#define IR_VOL_MINUS	0x15
#define IR_VOL_ADD		0x09
#define IR_0			0x16
#define IR_RPT			0x19
#define IR_USD			0x0D
#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

void IR_Init();
unsigned char IR_GetDataFlag();
unsigned char IR_GetRepeatFlag();
unsigned char IR_GetAddress();
unsigned char IR_GetCommand();
#endif

十七、LCD1602显示屏

posted @   xingke233  阅读(149)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示