嵌入式入门之51单片机

前言

本篇文章属于学习笔记,来源于B站教学视频,相关代码工程请从源地址自行下载。这位Up讲解得很好,适合同学们一起学习,在这里推荐给大家。本文为个人学习笔记,只能做参考,细节方面建议观看视频,肯定受益匪浅。
51单片机入门教程-2020版 程序全程纯手打 从零开始入门

一.环境搭建

1.开发软件:keil5C51。可自行到官网下载。(注意要下51单片机版本)
请添加图片描述
2、烧录软件stc-isp
嵌入式入门之51单片机_第1张图片
3.开发板:普中A2标准板。嵌入式入门之51单片机_第2张图片

二.单片机介绍

单片机(Single-Chip Microcomputer)是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU、随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计数器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域广泛应用。从上世纪80年代,由当时的4位、8位单片机,发展到现在的300M的高速单片机。

历史

单片机的发展先后经历了4位、8位、16位和32位等阶段。8位单片机由于功能强,被广泛用于工业控制、智能接口、仪器仪表等各个领域,8位单片机在中、小规模应用场合仍占主流地位,代表了单片机的发展方向,在单片机应用领域发挥着越来越大的作用。 80年代初,Intel公司推出了8位的MCS-51系列的单片机。

51单片机的部件

MCS-51单片机的逻辑部件,包括一个8位CPU及片内振荡器、 80514B掩膜ROM、87514KBEPROM、8031无ROM、特殊功能寄存器SFR128BRAM、定时器/计数器T0及T1、并行I/O接口:P0、P1、P2、P3;串行接口:TXD、RXD;中断系统:INT0,INT1。

51单片机的命名规则

嵌入式入门之51单片机_第3张图片

51单片机结构

嵌入式入门之51单片机_第4张图片
嵌入式入门之51单片机_第5张图片

STC89C52系列单片机最小系统

嵌入式入门之51单片机_第6张图片

二.操作LED

1.LED的硬件原理图

通过下面的图,我们可以看到LED正极接着电源,那我们可以控制P2的八根引脚的高低电压来控制电路的导通从而控制LED的亮灭。并且通过串联电阻来限流,避免LED烧坏。而单片机需要通过CPU控制寄存器的值,进而通过驱动器加大控制力度,由控制电路输出高低电平(对应寄存器1/0)。因此,程序需要在对应的寄存器上写1或0,即可控制LED的亮灭。

嵌入式入门之51单片机_第7张图片
嵌入式入门之51单片机_第8张图片

2.控制LED代码。

//LED D1亮
#include 

void main()
{
	//P2=0xEE;//1111 1110
	while()
	{
		P2=0xEE;//1111 1110
	}
}
//延时500ms控制LED灯闪烁
#include 
#include 
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();
	}
}
//流水灯
#include 
#include 
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;//1111 1110
		Delay500ms();
		P2=0xFD;//1111 1101
		Delay500ms();
		P2=0xFB;//1111 1011
		Delay500ms();
		P2=0xF7;//1111 0111
		Delay500ms();
		P2=0xEF;//1110 1111
		Delay500ms();
		P2=0xDF;//1101 1111
		Delay500ms();
		P2=0xBF;//1011 1111
		Delay500ms();
		P2=0x7F;//0111 1111
		Delay500ms();
	}
}
//LED流水灯升级(控制延时时间)
#include 
#include 
void Delay1ms(unsigned int xms)		//@11.0592MHz
{
	while(xms){
		unsigned char i, j;

		_nop_();
		_nop_();
		_nop_();
		i = 11;
		j = 190;
		do
		{
			while (--j);
		} while (--i);
		xms--;
 }
}

void main()
{
	while(1)
	{
		P2=0xFE;//1111 1110
		Delay1ms(500);
		P2=0xFD;//1111 1101
		Delay1ms(500);
		P2=0xFB;//1111 1011
		Delay1ms(500);
		P2=0xF7;//1111 0111
		Delay1ms(500);
		P2=0xEF;//1110 1111
		Delay1ms(500);
		P2=0xDF;//1101 1111
		Delay1ms(500);
		P2=0xBF;//1011 1111
		Delay1ms(500);
		P2=0x7F;//0111 1111
		Delay1ms(500);
	}
}

//按位与控制LED循环亮灭
#include 
#include 
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=0xFF;
		Delay500ms();
		while(P2!=0)
		{
			
			P2=(P2<<1);
			Delay500ms();
		}
		//P2=0xFF;
    }
}

嵌入式入门之51单片机_第9张图片
嵌入式入门之51单片机_第10张图片

三.独立按键控制LED灯亮灭

1.独立按键原理图

四个按键都接地,按下时导通,四根引线是则处于低电位。
嵌入式入门之51单片机_第11张图片

2.按键的抖动。

因为按键的抖动,消除抖动可以在编写代码是延时抖动时间,使他处于稳点位置。
嵌入式入门之51单片机_第12张图片

3.按键控制led的代码。

//按键控制LED
#include 
void main()
{
	//P2=0xFE;
	//P2_0=1;
	while(1)
	{
		if(P3_1==0){
			P2_0=0;
		}
		else{
			P2_0=1;
		}
	}
	
}
//按键控制led状态
#include 

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

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

void main()
{
	while(1)
	{
		if(P3_1==0)
		{
			Delay1ms(20);
			while(P3_1==0);
			Delay1ms(20);
			
			P2_0=~P2_0;
		}
	}
}
//按键控制led二进制亮灭
#include 
void Delay(unsigned int xms)		//@11.0592MHz
{
	while(xms--)
	{
		unsigned char i, j;
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
    }
}

void main(){
	
	unsigned char LEDNum = 0;
	while(1)
	{
		if(P3_1==0)
		{
			Delay(20);
			while(P3_1==0);
			Delay(20);
			LEDNum++;
			P2 = ~LEDNum;
		}
	}	
}
//按键控制led移位
#include 
void Delay(unsigned int xms)		//@11.0592MHz
{
	while(xms--)
	{
		unsigned char i, j;
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
    }
}

void main(){
	
	unsigned char LEDNum = 0;
	P2 = ~0x01;
	//unsigned char LEDNum2 = 0;
	while(1)
	{
		if(P3_1==0)
		{
			Delay(20);
			while(P3_1==0);
			Delay(20);
			  LEDNum++;
			if(LEDNum>7)
				LEDNum=0;
			 //LEDNum++;
			P2 = ~(0x01<<LEDNum);
			//LEDNum1++;
		}
		if(P3_0==0)
		{
			Delay(20);
			while(P3_0==0);
			Delay(20);
			if(LEDNum==0)
				LEDNum=7;
			else
				 LEDNum--;
			P2 = ~(0x01<<LEDNum);
		}
	}	
}

四.静态数码管

1.单个数码管引脚定义

数码管的接法,有共阳和共阴之分。共阴时,拉高电压即可点亮。共阳时,拉低电平点亮。
嵌入式入门之51单片机_第13张图片

2.开发板四位一体的数码管引脚

嵌入式入门之51单片机_第14张图片

3.74HC138译码器原理图

嵌入式入门之51单片机_第15张图片

4.动态数码管模块原理图

嵌入式入门之51单片机_第16张图片

5.静态数码管模块代码

//
#include 
const unsigned char NixieTable[]={0x3F,0x06,0x5B,
	                           0x4F,0x66,0x6D,
		                       0x7D,0x07,0x7F,0x6F};

void Nixie(unsigned char Location,Number)
{
	switch(Location)
	{
		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 = NixieTable[Number];
	
}



void main()
{
//	const unsigned char NixieTable[]={0x3F,0x06,0x5B,
//	                           0x4F,0x66,0x6D,
//		                       0x7D,0x07,0x7F,0x6F};
//注意:函数传参都是拷贝过去的。
	
	while(1)
	{
		Nixie(3,2);
	}
	
	
}

6.动态数码管模块代码

消影是因为位选和段选显示数据窜位。加了一个延时函数并且位选清零。

#include 

const unsigned char NixieTable[]={0x3F,0x06,0x5B,
	                           0x4F,0x66,0x6D,
		                       0x7D,0x07,0x7F,0x6F};

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

void Nixie(unsigned char Location,Number)
{
	switch(Location)
	{
		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 = NixieTable[Number];
	Delay(1);
	P0=0x00;
	
}



void main()
{
//	const unsigned char NixieTable[]={0x3F,0x06,0x5B,
//	                           0x4F,0x66,0x6D,
//		                       0x7D,0x07,0x7F,0x6F};
//注意:函数传参都是拷贝过去的。
	
	while(1)
	{
		Nixie(1,1);
		//Delay(100);
		Nixie(2,2);
		//Delay(100);
		Nixie(3,3);
		//Delay(100);
		
	}
	
	
}

7.数码管驱动方式。

单片机直接扫描: 硬件设备简单,但会耗费大量的单片机CPU时间。
专用驱动芯片: 内部自带显存、扫描电路,单片机只需告诉它显示什么即可。

五.模块化编程和LCD1602调试工具

1.模块化编程

嵌入式入门之51单片机_第17张图片
模块化编程框架
请添加图片描述
嵌入式入门之51单片机_第18张图片
C语言预编译
嵌入式入门之51单片机_第19张图片

2.LCD1602调试工具

嵌入式入门之51单片机_第20张图片

六.矩阵键盘

1.矩阵键盘介绍

嵌入式入门之51单片机_第21张图片

2.扫描的概念

嵌入式入门之51单片机_第22张图片

3.准双向口输出简图

嵌入式入门之51单片机_第23张图片

4.矩阵键盘模块原理图

读取第一行然后快速循环扫描,也可以按列扫描。
例:当P1_7 = 0是相当于接地,当S1按下时候,强上拉使P1_3低电压输入数据为0,即P1_3=0。
嵌入式入门之51单片机_第24张图片

5.代码块示例

MartixKey.c

#include 
#include "Delay.h"

unsigned char MartixKey()
{
	unsigned char KeyNum=0;
	
		P1 = 0xFF;
		P1_7=0;
	    if(P1_3==0){Delay(20);while(P1_3==0);Delay(20);KeyNum=1;}
		if(P1_2==0){Delay(20);while(P1_2==0);Delay(20);KeyNum=2;}
		if(P1_1==0){Delay(20);while(P1_1==0);Delay(20);KeyNum=3;}
		if(P1_0==0){Delay(20);while(P1_0==0);Delay(20);KeyNum=4;}
		
		P1 = 0xFF;
		P1_6=0;
	    if(P1_3==0){Delay(20);while(P1_3==0);Delay(20);KeyNum=5;}
		if(P1_2==0){Delay(20);while(P1_2==0);Delay(20);KeyNum=6;}
		if(P1_1==0){Delay(20);while(P1_1==0);Delay(20);KeyNum=7;}
		if(P1_0==0){Delay(20);while(P1_0==0);Delay(20);KeyNum=8;}
		
		P1 = 0xFF;
		P1_5=0;
	    if(P1_3==0){Delay(20);while(P1_3==0);Delay(20);KeyNum=9;}
		if(P1_2==0){Delay(20);while(P1_2==0);Delay(20);KeyNum=10;}
		if(P1_1==0){Delay(20);while(P1_1==0);Delay(20);KeyNum=11;}
		if(P1_0==0){Delay(20);while(P1_0==0);Delay(20);KeyNum=12;}
		
		P1 = 0xFF;
		P1_4=0;
	    if(P1_3==0){Delay(20);while(P1_3==0);Delay(20);KeyNum=13;}
		if(P1_2==0){Delay(20);while(P1_2==0);Delay(20);KeyNum=14;}
		if(P1_1==0){Delay(20);while(P1_1==0);Delay(20);KeyNum=15;}
		if(P1_0==0){Delay(20);while(P1_0==0);Delay(20);KeyNum=16;}
	return KeyNum;
}

main.c

#include 
#include "LCD1602.h"
#include "Delay.h"
#include "MartixKey.h"

unsigned char KeyNum=0;
void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"hello world!");
	while(1)
	{
		KeyNum = MartixKey();
		if(KeyNum)
		{
			LCD_ShowNum(2,1,KeyNum,2);
		}
	}
}

6.矩阵按键密码锁代码

main.c

#include 
#include "LCD1602.h"
#include "Delay.h"
#include "MartixKey.h"

unsigned char KeyNum = 0;
unsigned int Password ,count;
void main()
{
	LCD_Init();
	LCD_ShowString(1,1,"Password!");
	while(1)
	{
		
		KeyNum = MartixKey();
		if(KeyNum)
		{
			if(KeyNum<=10)//如果按键s1-s10,输入密码。
			{
				if(count<4)
				{
					Password *= 10; //密码左移一位
				    Password += KeyNum%10;//输入一位密码
				}
				count++;//计数加1
			}
			if(KeyNum==11)//如果按下S11,确认
			{
				if(Password==1234)
					LCD_ShowString(1,11,"Right!");//如果密码等于1234,则正确
				else
					LCD_ShowString(1,11,"Error!");
			}
			if(KeyNum==12)//如果按下S12,重新输入
			{
				Password=0;
				count=0;
			}
			LCD_ShowNum(2,1,Password,4);//更新显示
		}
	}
}

七.定时器。

1.定时器介绍

51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。
我们之前控制的led,按键矩阵按键等都是单片机IO口控制的外设

2.定时器作用:

(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
(2)替代长时间的Delay,提高CPU的运行效率和处理速度
(3)操作系统的任务切换。
一些高级的单片机,它里面就有专门的一个系统的滴答定时器,用来计时让操作系统来执行多任务。
CPU的多任务就是把多个任务分成一段一段的时间片,把他们交叉组合在一起,然后只要一条线的执行
就能实现多个任务的同时执行。
在切换某个任务的执行,这个过程就是定时器实现的。

3.STC89C52定时器资源

  • 定时器个数:3个(T0、T1、T2),T0和T1与传统的51单片机兼容,T2是此型号单片机增加的资源
  • 注意:定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,
    但一般来说,T0和T1的操作方式是所有51单片机所共有的。

STC89C52系列单片机的定时器0和定时器1,与传统8051的定时器完全兼容
当在定时器1做波特率发生器时,定时器0可以当两个8位定时器用。

嵌入式入门之51单片机_第25张图片

4.定时器框架

定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号,每隔“一秒”,计数单元的数值就增加一,
当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请,
产生“响铃提醒”,使程序跳转到中断服务函数中执行。

嵌入式入门之51单片机_第26张图片

5.定时器工作模式

STC89C52的T0和T1均有四种工作模式:

模式0:13位定时器/计数器
模式1:16位定时器/计数器(常用)
模式2:8位自动重装模式
模式3:两个8位计数器

工作模式1框图:
嵌入式入门之51单片机_第27张图片

此模式下,定时器配置为16位定时器/计数器,由TLO的8位和THO的8位所构成。
TLO的8位溢出向THO进位,THO计数溢出置位TCON中的溢出标志位TFO。

定时器时钟

SYSclk:系统时钟,即晶振周期 本开发板上的晶振为11.0592MHz

嵌入式入门之51单片机_第28张图片
嵌入式入门之51单片机_第29张图片

12Mhz: 时钟周期:
1/12Mhz,1单位是秒所以12Mhz要转为秒为12000000hz 1/12000000≈0.00000008s
机器周期:
12×时钟周期=0.00000008s×12=0.000001s
转为us就是1us

11.0592Mhz:
时钟周期:1/11.0592Mhz,1单位是秒所以11.0592Mhz要转为秒为11059200hz

1/11059200≈0.00000009s

机器周期:12×时钟周期=0.00000009s×12=0.00000109s
转为us就是1.09us

嵌入式入门之51单片机_第30张图片
请添加图片描述

T0 Pin是单片机的外部引脚,由这个引脚来提供时钟的时候,这个定时器就是个计数器。请添加图片描述

定时器计数单元

嵌入式入门之51单片机_第31张图片

此模式下,定时器配置为16位定时器/计数器,由TL0的8位和TH0的8位所构成。
TL0的8位溢出向TH0进位,TH0计数溢出置位TCON中的溢出标志位TF0。

请添加图片描述

中断系统

嵌入式入门之51单片机_第32张图片

中断程序流程
嵌入式入门之51单片机_第33张图片
STC89C52中断资源

中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)
中断优先级个数:4个
中断号:
嵌入式入门之51单片机_第34张图片
注意:中断的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的中断资源,
例如中断源个数不同、中断优先级个数不同等等

定时器和中断系统
嵌入式入门之51单片机_第35张图片
嵌入式入门之51单片机_第36张图片
定时器相关寄存器

单片机通过配置寄存器来控制内部线路的连接,通过内部线路的不同连接方式来实现不同电路,不同电路完成不同功能
寄存器是连接软硬件的媒介
在单片机中寄存器就是一段特殊的RAM存储器,一方面,寄存器可以存储和读取数据,
另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式

寄存器相当于一个复杂机器的“操作按钮”
嵌入式入门之51单片机_第37张图片
嵌入式入门之51单片机_第38张图片
嵌入式入门之51单片机_第39张图片

代码:通过独立按键控制流水灯模式,并由定时器执行流水灯。

main:

#include 
#include "Timer0.h"
#include "Key.h"
#include 
 
unsigned char KeyNum,LEDMode;
 
void main()
{
	P2=0xFE;
	Timer0Init();
	while(1)
	{
		KeyNum=Key();		//获取独立按键键码
		if(KeyNum)			//如果按键按下
		{
			if(KeyNum==1)	//如果K1按键按下
			{
				LEDMode++;	//模式切换,按1下按键是模式1,按2下是模式0,默认模式0
				if(LEDMode>=2)LEDMode=0;
			}
		}
	}
}
 
void Timer0_Routine() interrupt 1  //中断函数标识,含优先级
{
	static unsigned int T0Count;  //静态变量,拥有局部作用域,全局生命周期
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;		//T0Count计次,对中断频率进行分频
	if(T0Count>=500)//分频500次,500ms
	{
		T0Count=0;
		if(LEDMode==0)			//模式判断
			P2=_crol_(P2,1);	//LED输出(循环左移函数,即使流水灯循环左移)
		if(LEDMode==1)
			P2=_cror_(P2,1);
	}
}

Timer0.c

#include 
 
 
/**
  * @brief  定时器0初始化,1毫秒@12.000MHz
  * @param  无
  * @retval 无
  */
void Timer0Init(void)
{
	TMOD &= 0xF0;		//设置定时器模式,只改变T0,避免T1改变
	TMOD |= 0x01;		//设置定时器模式
	TL0 = 0x18;		//高位设置定时初值 65535/256
	TH0 = 0xFC;		//低位设置定时初值 65535%256
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;
	EA=1;
	PT0=0;
}
 
/*定时器中断函数模板
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;  //静态变量,拥有局部作用域,全局生命周期
	TL0 = 0x18;		//设置定时初值,像沙漏,重置沙漏时间
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=1000)
	{
		T0Count=0;
		
	}
}
*/

定时器时钟代码

main:

#include 
#include "LCD1602.h"
#include "Delay.h"
#include "Timer0.h"

unsigned char Sec, Min=18, Hour=0;

void main()
{
	LCD_Init();
	Timer0Init();
	LCD_ShowString(1,1,"abc");
     while(1)
     {
        LCD_ShowNum(2,1,Hour,2);
		LCD_ShowString(2,3,":");
		LCD_ShowNum(2,4,Min,2);
		LCD_ShowString(2,6,":");
		LCD_ShowNum(2,7,Sec,2);

     }

}

void Timer0_Routie () interrupt 1
 {
	 static unsigned int T0count;
	 T0count++;
	 TL0 = 0x66;		
	 TH0 = 0xFC;
	 if(T0count>1000)
	 {
	 
	   T0count=0;
		Sec++;
		if(Sec>=60)
		{
			Sec=0;
			Min++;
			if(Min>=60)
			{
				Min=0;
				Hour++;
				if(Hour>=24)
				{
					Hour=0;
				}
			}
		}

	 }
 }

八.串口通讯。

1.串口介绍

串口,即串行接口,串行通信接口或串行通讯接口(通常指COM接口),是采用串行通信方式的扩展接口。
串口、UART口、COM口、USB口是指的物理接口形式(硬件)。
与之相对应的另一种接口叫并口,并行接口。
串口:串口是一个泛称,UART,TTL,RS232,RS485,I2C,SPI都遵循类似的通信时序协议,因此都被通称为串口。

两者的区别是,传输一个字节(8个位)的数据时,串口是将8个位排好队,逐个地在1条连接线上传输,
而并口则将8个位一字排开,分别在8条连接线上同时传输。

串口、RS-232与TTL

前面讲过,RS-232是一个串行通信接口标准,它规定了逻辑“1”为-3 ~ -15V,逻辑“0”为+3 ~+15V,
符合该标准的串口也叫RS-232串口,比如电脑的COM口。
那么,还有不符合RS-232标准的串口?答案是肯定的,那就是单片机(如stm32)的UART/USART,
这个也叫串口,但它不遵循RS-232标准,使用的是TTL电平(Transistor-TransistorLogic)
该电平的逻辑“1”为+5V,逻辑“0”为0V,称为TTL串口。

  **需要注意的是,串口、UART/USART通常指的是硬件接口,而RS-232指的是属于物理层范畴的串行通信接口标准,简而言之,RS-232就是个标准。**

UART接口:通用异步收发器(Universal Asynchronous Receiver/Transmitter),UART是串口收发的逻辑电路,这部分可以独立成芯片,也可以作为模块嵌入到其他芯片里,单片机、SOC、PC里都会有UART模块。
COM口:特指台式计算机或一些电子设备上的D-SUB外形(一种连接器结构,VGA接口的连接器也是D-SUB)的串行通信口,应用了串口通信时序和RS232的逻辑电平。
USB口:通用串行总线,和串口完全是两个概念。虽然也是串行方式通信,但由于USB的通信时序和信号电平都和串口完全不同,因此和串口没有任何关系。USB是高速的通信接口,用于PC连接各种外设,U盘、键鼠、移动硬盘、当然也包括“USB转串口”的模块。(USB转串口模块,就是USB接口的UART模块)

2.串口通讯。

串口通讯(Serial Communication),是指外设和计算机间,通过数据信号线、地线等,按位进行传输数据的一种通讯方式。
串口是一种接口标准,它规定了接口的电气标准,没有规定接口插件电缆以及使用的协议。

3.UART

通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,通常称作UART)
是一种串行异步收发协议,应用十分广泛。UART工作原理是将数据的二进制位一位一位的进行传输。
在UART通讯协议中信号线上的状态位高电平代表’1’低电平代表’0’。
当然两个设备使用UART串口通讯时,必须先约定好传输速率和一些数据位。

51单片机内部自带UART(Universal Asynchronous Receiver Transmitter,通用异步收发器),可实现单片机的串口通信。

  • 简单双向串口通信有两根通信线(发送端TXD和接收端RXD)
  • TXD与RXD要交叉连接
  • 当只需单向的数据传输时,可以直接一根通信线
  • 当电平标准不一致时,需要加电平转换芯片
    请添加图片描述

电平标准

嵌入式入门之51单片机_第40张图片

接口及引脚定义

嵌入式入门之51单片机_第41张图片

常见接口比较

嵌入式入门之51单片机_第42张图片

嵌入式入门之51单片机_第43张图片

串口参数与时序图

嵌入式入门之51单片机_第44张图片
嵌入式入门之51单片机_第45张图片

串口模式图

嵌入式入门之51单片机_第46张图片

相关寄存器

嵌入式入门之51单片机_第47张图片

串口和中断系统

嵌入式入门之51单片机_第48张图片

4.串口向电脑发送数据代码

main:

#include 
#include "Delay.h"
#include "UART.h"
 
unsigned char Sec;
 
void main()
{
	UartInit();
	while(1)
	{
		UART_SendByte(Sec);
		Sec++;
		Delay(1);  // 必要的延时,避免误差导致乱码,没误差的时候可以不需要
	}
}
#include 

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


/**
  * @brief  向缓冲区赋一个数据
  * @param  Byte 赋的值
  * @retval 无
  */
void UART_SENT(unsigned char Byte)
{
	SBUF = Byte;
	while(TI==0);
	TI=0;
}

5.电脑通过串口控制LED

#include 
#include "Delay.h"
#include "Uart.h"

unsigned int set;
void main()
{
	UartInit();
     while(1)
     {
       
     }

}


void UART_Routine() interrupt 4
{
	if(RI!=0)
	{
		P2 = ~SBUF;
		UART_SENT(SBUF);
		RI=0;
	}
	
}

九.LED点阵屏

1.LED点阵屏介绍

嵌入式入门之51单片机_第49张图片

2.显示原理

嵌入式入门之51单片机_第50张图片

3.74HC595模块原理图

嵌入式入门之51单片机_第51张图片

4.74HC595介绍

74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,
8根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。
嵌入式入门之51单片机_第52张图片

5.C51的sfr、sbit

嵌入式入门之51单片机_第53张图片

6.LED点阵屏显示图形代码

#include 
#include "Delay.H"
 
sbit RCK=P3^5;  //RCLK
sbit SCK=P3^6;  //SRCLK
sbit SER=P3^4;	//SER
 
#define MATRIX_LED_PORT  P0
 
void _74HC595_WriteByte(unsigned char Byte)
{
//	SER=Byte&0x80;  //一般是0、1赋值,不过,如果非0,都会当作1
//	SCK=1;
//	SCK=0;
//	SER=Byte&0x60;
//	SCK=1;
//	SCK=0;
	unsigned char i;
	for(i=0;i<8;i++)
	{
		SER=Byte&(0x80>>i);
		SCK=1;
		SCK=0;
	}
		RCK=1;
		RCK=0;
}
 
void MatrixLED_ShowColumn(unsigned char Column, Data)
{
	_74HC595_WriteByte(Data);
//	if(Column==0){P0=~0x80;}
//	if(Column==1){P0=~0x40;}
	MATRIX_LED_PORT=~(0x80>>Column);
	Delay(1);
	MATRIX_LED_PORT=0xFF;
	
}
 
void main()
{
	SCK=0;
	RCK=0;
	while(1)
	{
//		_74HC595_WriteByte(0xAA);
		MatrixLED_ShowColumn(0,0x80);
		MatrixLED_ShowColumn(1,0x40);
		MatrixLED_ShowColumn(2,0x20);
		MatrixLED_ShowColumn(3,0x10);
	}
}

7.LED点阵屏显示动画

#include 
#include "MatrixLED.h"

	
unsigned char Animation[]={
	                       0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
	                       0x00,0x42,0x46,0x4A,0x52,0x62,0x42,0x00,
	                       0x00,0x3C,0x42,0x42,0x42,0x42,0x3E,0x02,
                           0x00,0x7E,0x08,0x08,0x08,0x08,0x7E,0x00,
						   0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,}
void main()
{
	//MatrixLED_Init();
	//_74HC595_WriteByte(0x12);
    unsigned char i, Offset=2, Count=0;
	MatrixLED_Init();
	while(1)
	{
	    for(i=0 ;i<8 ;i++)
		{
			MatrixLED_ShowColumn(i,Animation[i+Offset]);
		}
		Count++;
		if(Count>10)//扫描十遍
		{
			Count=0;
			Offset++;
			if(Offset>32)
			{
			Offset=0; //防止数组溢出
			}
		}
	}

}

十.DS1302实时时钟

1.DS1302介绍

嵌入式入门之51单片机_第54张图片

2.引脚定义和应用电路

嵌入式入门之51单片机_第55张图片

3.内部结构框图

嵌入式入门之51单片机_第56张图片

4.晶振工作原理

晶振结构示意图
嵌入式入门之51单片机_第57张图片
嵌入式入门之51单片机_第58张图片
我们给石英晶体电压时,石英晶体就会产生机械形变,形变时候又会产生电压,以此不断重复给石英晶体交变的电压
就能产生稳定的震动频率。嵌入式入门之51单片机_第59张图片

当输入的信号频率等于谐振频率时候,LC串联阻抗看作0
嵌入式入门之51单片机_第60张图片
嵌入式入门之51单片机_第61张图片
反相器兼具把信号放大的功能
嵌入式入门之51单片机_第62张图片
嵌入式入门之51单片机_第63张图片
嵌入式入门之51单片机_第64张图片
感兴趣的可以点击去看原视频介绍。
晶振工作原理

5.寄存器

嵌入式入门之51单片机_第65张图片
WP写保护位

6.命令字

嵌入式入门之51单片机_第66张图片

7.时序定义

嵌入式入门之51单片机_第67张图片

CE 与时钟控制

驱动CE为高启动所有数据传输。
CE输入有两个功能
第一:CE打开控制逻辑,允许访问地址/命令序列的移位寄存器。
第二,CE信号提供了终止单字节或多字节CE数据传输的方法。
对于数据输入,数据必须在时钟上升沿有效,数据位在时钟下降沿输出。
如果CE输入低,所有的数据传输终止,I/O引脚进入高阻抗状态。
在上电时, CE必须为逻辑0直到 VCC大于2.0V,同样,  SCLK 必须为逻辑0当 CE 变成逻辑1状态。

数据输入

在输入一个写命令字节的8个SCLK周期之后,在接下来的8个SCLK周期的上升边缘输入一个数据字节。
额外的SCLK周期被忽略,如果他们无意中发生。数据从第0位开始输入。

数据输出

在输入一个read命令字节的8个SCLK周期之后
一个数据字节被输出到下一个8个SCLK周期的下降边缘。
注意,要传输的第一个数据位发生在命令字节的最后一位写入后的第一个下降沿上。
只要CE保持高,额外的SCLK周期就会重新传输数据字节。

时钟停止标志

秒寄存器的第7位被定义为时钟停止(CH)标志。
当此位设置为逻辑1时。时钟振荡器停止,DS1302被置于低功率待机模式,电流漏小于100nA。
当此位写入逻辑0时,时钟开始计时。 也即上电状态。

写保护

控制寄存器的第7位是写保护位。前7(06)被迫为0,在读时总是读0。
在对时钟或RAM进行任何写操作之前,第7位必须是0。
当值高时,写保护位阻止对任何其他寄存器的写操作也即初始上电状态。因此,WP位应该在试图写入设备之前被清除。
电路上电的初始态WP是1,这时是不能改写上面任何一个时间寄存器的,只有首先将WP改写为0,才能进行其它寄存器的写操作。

8.BCD码

嵌入式入门之51单片机_第68张图片

9.DS1302时钟代码

main:

#include 
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"




void main()
{
	 LCD_Init();
	 DS1301_Init();
	 //DS1302_WriteByte(0x80,0x03);
	DS1302_SetTime();
     while(1)
     {
		// Second = DS1302_ReadByte(0x81);
		// LCD_ShowNum(1,2,Second/16*10+Second%16,3);
		DS1302_ReadTime();
		LCD_ShowNum(1,1,DS1302_TIME[0],2);
		LCD_ShowNum(1,4,DS1302_TIME[1],2);
		LCD_ShowNum(1,7,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.c:

#include 


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


#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[]={22,12,31,23,59,55,1};

/**
  * @brief  初始化函数,初始化芯片使能和串行时钟线
  * @param  无
  * @retval 无
  */
void DS1301_Init(void)	
{
	DS1302_SCK = 0;
	DS1302_CE  = 0;
}

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

/**
  * @brief  读数据
  * @param  命令字/地址
  * @retval Date
  */

unsigned char DS1302_ReadByte(unsigned char Command )
{
	unsigned char i,Date=0x00;
	Command|=0x01; 
	DS1302_CE = 1;
	for(i=0;i<8;i++)   
	{
		DS1302_IO=Command & (0x01<<i);
		DS1302_SCK = 0;
		DS1302_SCK = 1;
	}
	for(i=0;i<8;i++)   
	{
		DS1302_SCK = 1;
		DS1302_SCK = 0;
		if(DS1302_IO)  
			Date |= (0x01<<i);
	}
	DS1302_CE = 0;
	DS1302_IO = 0;
	return Date;
}
/**
  * @brief  转为BCD码并且设置数据,
  * @param  无
  * @retval 无
  */

void DS1302_SetTime(void)
{
	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(void)
{
	
	unsigned char Temp = 0x00;
	Temp=DS1302_ReadByte(DS1302_YEAR);
	DS1 302_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;

	
}

10.DS1302可调时钟

main:

#include 
#include "LCD1602.h"
#include "DS1302.h"
#include "Delay.h"
#include "Key.h"
#include "Timer0.h"

char KeyNum,MODE,TimeSetSelect,TimeSetFlashFlag;

void TimeShow()
{
	    DS1302_ReadTime();
		LCD_ShowNum(1,1,DS1302_TIME[0],2);
		LCD_ShowNum(1,4,DS1302_TIME[1],2);
		LCD_ShowNum(1,7,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);
}

void TimeSet()
{
	if(KeyNum==2)//按键2按下
	{
		TimeSetSelect++;//设置选择位加1
		TimeSetSelect%=6;//越界清零
	}
	if(KeyNum==3)//按键3按下
	{
		DS1302_TIME[TimeSetSelect]++;//时间设置位数值加1
		if(DS1302_TIME[0]>99){DS1302_TIME[0]=0;}//年越界判断
		if(DS1302_TIME[1]>12){DS1302_TIME[1]=1;}//月越界判断
		if( DS1302_TIME[1]==1 || DS1302_TIME[1]==3 || DS1302_TIME[1]==5 || DS1302_TIME[1]==7 || 
			DS1302_TIME[1]==8 || DS1302_TIME[1]==10 || DS1302_TIME[1]==12)//日越界判断
		{
			if(DS1302_TIME[2]>31){DS1302_TIME[2]=1;}//大月
		}
		else if(DS1302_TIME[1]==4 || DS1302_TIME[1]==6 || DS1302_TIME[1]==9 || DS1302_TIME[1]==11)
		{
			if(DS1302_TIME[2]>30){DS1302_TIME[2]=1;}//小月
		}
		else if(DS1302_TIME[1]==2)
		{
			if((DS1302_TIME[0]%4==0 && DS1302_TIME[0]%100!=0) || (DS1302_TIME[0]%400))
			{
				if(DS1302_TIME[2]>29){DS1302_TIME[2]=1;}//闰年2月
			}
			else
			{
				if(DS1302_TIME[2]>28){DS1302_TIME[2]=1;}//平年2月
			}
		}
		if(DS1302_TIME[3]>23){DS1302_TIME[3]=0;}//时越界判断
		if(DS1302_TIME[4]>59){DS1302_TIME[4]=0;}//分越界判断
		if(DS1302_TIME[5]>59){DS1302_TIME[5]=0;}//秒越界判断
	}

	if(KeyNum==4)//按键4按下
	{
		DS1302_TIME[TimeSetSelect]--;//时间设置位数值减1
		if(DS1302_TIME[0]<0){DS1302_TIME[0]=99;}//年越界判断
		if(DS1302_TIME[1]<1){DS1302_TIME[1]=12;}//月越界判断
		if( DS1302_TIME[1]==1 || DS1302_TIME[1]==3 || DS1302_TIME[1]==5 || DS1302_TIME[1]==7 || 
			DS1302_TIME[1]==8 || DS1302_TIME[1]==10 || DS1302_TIME[1]==12)//日越界判断
		{
			if(DS1302_TIME[2]<1){DS1302_TIME[2]=31;}//大月
			if(DS1302_TIME[2]>31){DS1302_TIME[2]=1;}
		}
		else if(DS1302_TIME[1]==4 || DS1302_TIME[1]==6 || DS1302_TIME[1]==9 || DS1302_TIME[1]==11)
		{
			if(DS1302_TIME[2]<1){DS1302_TIME[2]=30;}//小月
			if(DS1302_TIME[2]>30){DS1302_TIME[2]=1;}
		}
		else if(DS1302_TIME[1]==2)
		{
			if((DS1302_TIME[0]%4==0 && DS1302_TIME[0]%100!=0) || (DS1302_TIME[0]%400))
			{
				if(DS1302_TIME[2]<1){DS1302_TIME[2]=29;}//闰年2月
				if(DS1302_TIME[2]>29){DS1302_TIME[2]=1;}
			}
			else
			{
				if(DS1302_TIME[2]<1){DS1302_TIME[2]=28;}//平年2月
				if(DS1302_TIME[2]>28){DS1302_TIME[2]=1;}
			}
		}
		if(DS1302_TIME[3]<0){DS1302_TIME[3]=23;}//时越界判断
		if(DS1302_TIME[4]<0){DS1302_TIME[4]=59;}//分越界判断
		if(DS1302_TIME[5]<0){DS1302_TIME[5]=59;}//秒越界判断
	}
	//更新显示,根据TimeSetSelect和TimeSetFlashFlag判断可完成闪烁功能
	if(TimeSetSelect==0 && TimeSetFlashFlag==1){LCD_ShowString(1,1,"  ");}
	else {LCD_ShowNum(1,1,DS1302_TIME[0],2);}
	if(TimeSetSelect==1 && TimeSetFlashFlag==1){LCD_ShowString(1,4,"  ");}
	else {LCD_ShowNum(1,4,DS1302_TIME[1],2);}
	if(TimeSetSelect==2 && TimeSetFlashFlag==1){LCD_ShowString(1,7,"  ");}
	else {LCD_ShowNum(1,7,DS1302_TIME[2],2);}
	if(TimeSetSelect==3 && TimeSetFlashFlag==1){LCD_ShowString(2,1,"  ");}
	else {LCD_ShowNum(2,1,DS1302_TIME[3],2);}
	if(TimeSetSelect==4 && TimeSetFlashFlag==1){LCD_ShowString(2,4,"  ");}
	else {LCD_ShowNum(2,4,DS1302_TIME[4],2);}
	if(TimeSetSelect==5 && TimeSetFlashFlag==1){LCD_ShowString(2,7,"  ");}
	else {LCD_ShowNum(2,7,DS1302_TIME[5],2);}

}


void main()
{
	 LCD_Init();
	 Timer0Init();
	 DS1301_Init();
	LCD_ShowString(1,1,"  -  -  ");//静态字符初始化显示
	LCD_ShowString(2,1,"  :  :  ");
	 //DS1302_WriteByte(0x80,0x03);
	DS1302_SetTime();
			 //TimeShow();
     while(1)
     {
		// TimeShow();
		 KeyNum = Key();
		 if(KeyNum == 1)
		 {
				if(MODE==0){MODE=1;TimeSetSelect=0;}//功能切换
			     else if(MODE==1){MODE=0;DS1302_SetTime();}
		 }
		 switch(MODE)
		 {
			 case 0: TimeShow();break;
			 case 1: TimeSet();break;
		 }
			
     }

}


void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=500)//每500ms进入一次
	{
		T0Count=0;
		TimeSetFlashFlag=!TimeSetFlashFlag;//闪烁标志位取反
	}

}

十一.蜂鸣器播放

1.蜂鸣器介绍

嵌入式入门之51单片机_第69张图片

2.驱动电路

嵌入式入门之51单片机_第70张图片

3.ULN2003

嵌入式入门之51单片机_第71张图片

嵌入式入门之51单片机_第72张图片

4.蜂鸣器播放提示音代码

main:

#include 
#include "Key.h"
#include "Delay.h"
#include "Nixie.h"
#include "Buzzer.h"



unsigned char KeyNum;


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

}

Buzzer:

#include 
#include 

sbit  Buzzer = P2^5;//取别名

/**
  * @brief  蜂鸣器专用延时:500us
  * @param  无
  * @retval 无
  */
void Buzzer_Delay500us()		//@11.0592MHz
{
	unsigned char i;

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

/**
  * @brief  蜂鸣器响铃时间
  * @param  unsigned int ms:响铃时间参数
  * @retval 无
  */

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

5.蜂鸣器播放音乐代码

#include 

#include "Delay.h"
#include "Timer0.h"
 
//蜂鸣器端口定义
sbit Buzzer=P2^5;
 
//播放速度,值为四分音符的时长(ms)
#define SPEED	500
 
//音符与索引对应表,P:休止符,L:低音,M:中音,H:高音,下划线:升半音符号#
#define P	0
#define L1	1
#define L1_	2
#define L2	3
#define L2_	4
#define L3	5
#define L4	6
#define L4_	7
#define L5	8
#define L5_	9
#define L6	10
#define L6_	11
#define L7	12
#define M1	13
#define M1_	14
#define M2	15
#define M2_	16
#define M3	17
#define M4	18
#define M4_	19
#define M5	20
#define M5_	21
#define M6	22
#define M6_	23
#define M7	24
#define H1	25
#define H1_	26
#define H2	27
#define H2_	28
#define H3	29
#define H4	30
#define H4_	31
#define H5	32
#define H5_	33
#define H6	34
#define H6_	35
#define H7	36
 
//索引与频率对照表
unsigned int FreqTable[]={
	0,
	63628,63731,63835,63928,64021,64103,64185,64260,64331,64400,64463,64528,
	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 code Music[]={
	//音符,时值,
	
	//1
	P,	4,
	P,	4,
	P,	4,
	M6,	2,
	M7,	2,
	
	H1,	4+2,
	M7,	2,
	H1,	4,
	H3,	4,
	
	M7,	4+4+4,
	M3,	2,
	M3,	2,
	
	//2
	M6,	4+2,
	M5,	2,
	M6, 4,
	H1,	4,
	
	M5,	4+4+4,
	M3,	4,
	
	M4,	4+2,
	M3,	2,
	M4,	4,
	H1,	4,
	
	//3
	M3,	4+4,
	P,	2,
	H1,	2,
	H1,	2,
	H1,	2,
	
	M7,	4+2,
	M4_,2,
	M4_,4,
	M7,	4,
	
	M7,	8,
	P,	4,
	M6,	2,
	M7,	2,
	
	//4
	H1,	4+2,
	M7,	2,
	H1,	4,
	H3,	4,
	
	M7,	4+4+4,
	M3,	2,
	M3,	2,
	
	M6,	4+2,
	M5,	2,
	M6, 4,
	H1,	4,
	
	//5
	M5,	4+4+4,
	M2,	2,
	M3,	2,
	
	M4,	4,
	H1,	2,
	M7,	2+2,
	H1,	2+4,
	
	H2,	2,
	H2,	2,
	H3,	2,
	H1,	2+4+4,
	
	//6
	H1,	2,
	M7,	2,
	M6,	2,
	M6,	2,
	M7,	4,
	M5_,4,
	
	
	M6,	4+4+4,
	H1,	2,
	H2,	2,
	
	H3,	4+2,
	H2,	2,
	H3,	4,
	H5,	4,
	
	//7
	H2,	4+4+4,
	M5,	2,
	M5,	2,
	
	H1,	4+2,
	M7,	2,
	H1,	4,
	H3,	4,
	
	H3,	4+4+4+4,
	
	//8
	M6,	2,
	M7,	2,
	H1,	4,
	M7,	4,
	H2,	2,
	H2,	2,
	
	H1,	4+2,
	M5,	2+4+4,
	
	H4,	4,
	H3,	4,
	H3,	4,
	H1,	4,
	
	//9
	H3,	4+4+4,
	H3,	4,
	
	H6,	4+4,
	H5,	4,
	H5,	4,
	
	H3,	2,
	H2,	2,
	H1,	4+4,
	P,	2,
	H1,	2,
	
	//10
	H2,	4,
	H1,	2,
	H2,	2,
	H2,	4,
	H5,	4,
	
	H3,	4+4+4,
	H3,	4,
	
	H6,	4+4,
	H5,	4+4,
	
	//11
	H3,	2,
	H2,	2,
	H1,	4+4,
	P,	2,
	H1,	2,
	
	H2,	4,
	H1,	2,
	H2,	2+4,
	M7,	4,
	
	M6,	4+4+4,
	P,	4,
	
	0xFF	//终止标志
};
 
unsigned char FreqSelect,MusicSelect;
 
void main()
{
	Timer0Init();
	while(1)
	{
		if(Music[MusicSelect]!=0xFF)	//如果不是停止标志位
		{
			FreqSelect=Music[MusicSelect];	//选择音符对应的频率
			MusicSelect++;
			Delay(SPEED/4*Music[MusicSelect]);	//选择音符对应的时值
			MusicSelect++;
			TR0=0;
			Delay(5);	//音符间短暂停顿
			TR0=1;
		}
		else	//如果是停止标志位
		{
			TR0=0;
			while(1);
		}
	}
}
 
void Timer0_Routine() interrupt 1
{
	if(FreqTable[FreqSelect])	//如果不是休止符
	{
		/*取对应频率值的重装载值到定时器*/
		TL0 = FreqTable[FreqSelect]%256;		//设置定时初值
		TH0 = FreqTable[FreqSelect]/256;		//设置定时初值
		Buzzer=!Buzzer;	//翻转蜂鸣器IO口
	}
}

6.code关键字

code是keil C51里面的关键字,一般用于定义常量数组,意思是告诉编译说把这个数组放在ROM存储。

code的作用是告诉单片机,定义的数据要放在ROM(程序存储区)里面,写入后就不能再更改。

因为C语言中没办法详细描述存入的是ROM还是RAM(寄存器),所以在软件中添加了这一个语句起到代替汇编指令的作用,

对应的还有data是存入RAM的意思。

程序可以简单的分为code(程序)区,和data (数据)区,
code区在运行的时候是不可以更改的,data区放全局变量和临时变量,是要不断的改变的,
cpu从code区读取指令,对data区的数据进行运算处理。因此code区存储在什么介质上并不重要,象以前的计算机程序存储在卡片上,code区也可以放在rom里面,也可以放在ram里面,也可以放在flash里面(但是运行速度要慢很多,主要读flash比读ram要费时间),因此一般的做法是要将程序放到flash里面,然后load到 ram里面运行的;DATA区就没有什么选择了,肯定要放在RAM里面,放到rom里面改动不了。

unsigned char code array[];
//表示分配一个指向code区的指针,指针在默认存储区
code unsigned char  array[];
//表示分配一个指向默认存储区的指针,指针在code区

c51中的存储类型:

  • code :程序存储区(64KB)
  • data :可直接寻址的内部数据存储区(128B)
  • idata:不可直接寻址的内部数据存储区(256B)
  • bdata:可位寻址内部数据存储区(16B)
  • xdata:外部数据存储区(64KB)
  • pdata:分页的外部数据存储区

十二.AT24C02(I2C总线)

1.存储器的介绍

嵌入式入门之51单片机_第73张图片

SRAM速度非常快,是目前读写最快的存储设备了,但是它也非常昂贵,所以只在要求很苛刻的地方使用,譬如CPU的一级缓冲,二级缓冲。另一种称为动态RAM(Dynamic RAM/DRAM),DRAM保留数据的时间很短,速度也比SRAM慢,不过它还是比任何的ROM都要快,但从价格上来说DRAM相比SRAM要便宜很多,计算机内存就是DRAM的。

FLASH存储器又称闪存,它结合了ROM和RAM的长处,不仅具备电子可擦除可编程(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据(NVRAM的优势),U盘和MP3里用的就是这种存储器。在过去的20年里,嵌入式系统一直使用ROM(EPROM)作为它们的存储设备,然而近年来Flash全面代替了ROM(EPROM)在嵌入式系统中的地位,它用作存储Bootloader以及操作系统或者程序代码,或者直接当硬盘使用(U盘)。

ROM、RAM、DRAM、SRAM和FLASH的区别

2.存储器简化模型

嵌入式入门之51单片机_第74张图片

注意:地址总线和数据总线不是连接交叉的,他们之间有个高度差。

3.AT24C02介绍

嵌入式入门之51单片机_第75张图片

4.引脚及应用电路

嵌入式入门之51单片机_第76张图片

I2C硬件接口是开漏模式,这个接口只能输出低电平,要实现高电平就要靠上拉电阻去拉高。

5.内部结构框图

嵌入式入门之51单片机_第77张图片

6.I2C总线介绍

嵌入式入门之51单片机_第78张图片

7. I2C电路规范

嵌入式入门之51单片机_第79张图片

上拉电阻是将总线的拉成高电平,当连接在总线上的任意一个设备输出低电平时,总线被拉低就是输出了低电平
“线与”的意思就是连接在总线上的设备只要有一个输出低电平(0)总线就为低电平(0),只有全部设备都为高阻态时总线才是高电平(1)

嵌入式入门之51单片机_第80张图片

8.I2C时序结构

嵌入式入门之51单片机_第81张图片

嵌入式入门之51单片机_第82张图片
嵌入式入门之51单片机_第83张图片
嵌入式入门之51单片机_第84张图片

9.I2C数据帧

嵌入式入门之51单片机_第85张图片

嵌入式入门之51单片机_第86张图片
嵌入式入门之51单片机_第87张图片

10.AT24C02数据帧

嵌入式入门之51单片机_第88张图片


11.程序代码

main:

#include 
#include "Key.h"
#include "LCD1602.h"
#include "AT24C02.h"
#include "Delay.h"


unsigned char KeyNum;
unsigned int Num;

void main()
{
	LCD_Init();
	LCD_ShowNum(1,1,Num,5);
     while(1)
     {
		 KeyNum=Key();
        if(KeyNum == 1)//K1按键,Num自增
		{
			Num ++;
			LCD_ShowNum(1,1,Num,5);
			
		}
		if(KeyNum == 2)//K2按键,Num自减
		{
			Num --;
			LCD_ShowNum(1,1,Num,5);
			
		}
		if(KeyNum == 3)//K3按键,向AT24C02写入数据

		{
			AT24C02_WriteByte(0,Num%256);
			Delay(5);
			AT24C02_WriteByte(1,Num/256);
			Delay(5);
			LCD_ShowString(2,1,"Write OK");
			Delay(1000);
			LCD_ShowString(2,1,"        ");			
		}
		if(KeyNum == 4)//K4按键,向AT24C02读取数据

		{
			Num=AT24C02_ReadByte(0);
			Num|=AT24C02_ReadByte(1)<<8;
			LCD_ShowNum(1,1,Num,5);
			LCD_ShowString(2,1,"Read OK ");
			Delay(1000);
			LCD_ShowString(2,1,"        ");
		}
     }

}

AT24C02:

#include 
#include "I2C.h"


#define AT24C02_ADDRESS		0xA0


/**
  * @brief  AT24C02写入一个字节
  * @param  WordAddress 要写入字节的地址
  * @param  Data 要写入的数据
  * @retval 无
  */
void AT24C02_WriteByte(unsigned char WordAddress, Date)
{
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAck();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	I2C_SendByte(Date);
	I2C_ReceiveAck();
	I2C_Stop();
	
}

/**
  * @brief  AT24C02读取一个字节
  * @param  WordAddress 要读出字节的地址
  * @retval 读出的数据
  */
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
	unsigned char Date;
	
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS);
	I2C_ReceiveAck();
	I2C_SendByte(WordAddress);
	I2C_ReceiveAck();
	I2C_Start();
	I2C_SendByte(AT24C02_ADDRESS+1);
	I2C_ReceiveAck();
	Date = I2C_ReceiveByte();
	I2C_SendAck(1);
	I2C_Stop();
	
	return Date;
}

I2C:

#include 
  
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
 
/**
  * @brief  I2C开始
  * @param  无
  * @retval 无
  */
void I2C_Start(void)
{
	I2C_SDA=1;
	I2C_SCL=1;
	I2C_SDA=0;
	I2C_SCL=0;
}
 
/**
  * @brief  I2C停止
  * @param  无
  * @retval 无
  */
void I2C_Stop(void)
{
	I2C_SDA=0;
	I2C_SCL=1;
	I2C_SDA=1;
}
 
/**
  * @brief  I2C发送一个字节
  * @param  Byte 要发送的字节
  * @retval 无
  */
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;
	}
}
 
/**
  * @brief  I2C接收一个字节
  * @param  无
  * @retval 接收到的一个字节数据
  */
unsigned char I2C_ReceiveByte(void)
{
	unsigned char i,Byte=0x00;
	I2C_SDA=1;
	for(i=0;i<8;i++)
	{
		I2C_SCL=1;
		if(I2C_SDA){Byte|=(0x80>>i);}
		I2C_SCL=0;
	}
	return Byte;
}
 
/**
  * @brief  I2C发送应答
  * @param  AckBit 应答位,0为应答,1为非应答
  * @retval 无
  */
void I2C_SendAck(unsigned char AckBit)
{
	I2C_SDA=AckBit;
	I2C_SCL=1;
	I2C_SCL=0;
}
 
/**
  * @brief  I2C接收应答位
  * @param  无
  * @retval 接收到的应答位,0为应答,1为非应答
  */
unsigned char I2C_ReceiveAck(void)
{
	unsigned char AckBit;
	I2C_SDA=1;
	I2C_SCL=1;
	AckBit=I2C_SDA;
	I2C_SCL=0;
	return AckBit;
}

12.秒表代码

main

#include 
#include "Timer0.h"
#include "Key.h"
#include "Nixie.h"
#include "Delay.h"
#include "AT24C02.h"
 
unsigned char KeyNum;
unsigned char Min,Sec,MiniSec;
unsigned char RunFlag;
 
void main()
{
	Timer0_Init();
	while(1)
	{
		KeyNum=Key();
		if(KeyNum==1)			//K1按键按下
		{
			RunFlag=!RunFlag;	//启动标志位翻转
		}
		if(KeyNum==2)			//K2按键按下
		{
			Min=0;				//分秒清0
			Sec=0;
			MiniSec=0;
		}
		if(KeyNum==3)			//K3按键按下
		{
			AT24C02_WriteByte(0,Min);	//将分秒写入AT24C02
			Delay(5);
			AT24C02_WriteByte(1,Sec);
			Delay(5);
			AT24C02_WriteByte(2,MiniSec);
			Delay(5);
		}
		if(KeyNum==4)			//K4按键按下
		{
			Min=AT24C02_ReadByte(0);	//读出AT24C02数据
			Sec=AT24C02_ReadByte(1);
			MiniSec=AT24C02_ReadByte(2);
		}
		Nixie_SetBuf(1,Min/10);	//设置显示缓存,显示数据
		Nixie_SetBuf(2,Min%10);
		Nixie_SetBuf(3,11);
		Nixie_SetBuf(4,Sec/10);
		Nixie_SetBuf(5,Sec%10);
		Nixie_SetBuf(6,11);
		Nixie_SetBuf(7,MiniSec/10);
		Nixie_SetBuf(8,MiniSec%10);
	}
}
 
/**
  * @brief  秒表驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Sec_Loop(void)
{
	if(RunFlag)
	{
		MiniSec++;
		if(MiniSec>=100)
		{
			MiniSec=0;
			Sec++;
			if(Sec>=60)
			{
				Sec=0;
				Min++;
				if(Min>=60)
				{
					Min=0;
				}
			}
		}
	}
}
 
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count1,T0Count2,T0Count3;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count1++;
	if(T0Count1>=20)
	{
		T0Count1=0;
		Key_Loop();	//20ms调用一次按键驱动函数
	}
	T0Count2++;
	if(T0Count2>=2)
	{
		T0Count2=0;
		Nixie_Loop();//2ms调用一次数码管驱动函数
	}
	T0Count3++;
	if(T0Count3>=10)
	{
		T0Count3=0;
		Sec_Loop();	//10ms调用一次数秒表驱动函数
	}
}

Nixie:

#include 
#include "Delay.h"
 
//数码管显示缓存区
unsigned char Nixie_Buf[9]={0,10,10,10,10,10,10,10,10};
 
//数码管段码表
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x00,0x40};
 
/**
  * @brief  设置显示缓存区
  * @param  Location 要设置的位置,范围:1~8
  * @param  Number 要设置的数字,范围:段码表索引范围
  * @retval 无
  */
void Nixie_SetBuf(unsigned char Location,Number)
{
	Nixie_Buf[Location]=Number;
}
 
/**
  * @brief  数码管扫描显示
  * @param  Location 要显示的位置,范围:1~8
  * @param  Number 要显示的数字,范围:段码表索引范围
  * @retval 无
  */
void Nixie_Scan(unsigned char Location,Number)
{
	P0=0x00;				//段码清0,消影
	switch(Location)		//位码输出
	{
		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=NixieTable[Number];	//段码输出
}
 
/**
  * @brief  数码管驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Nixie_Loop(void)
{
	static unsigned char i=1;
	Nixie_Scan(i,Nixie_Buf[i]);
	i++;
	if(i>=9){i=1;}
}

Key:

#include 
#include "Delay.h"
 
unsigned char Key_KeyNumber;
 
/**
  * @brief  获取按键键码
  * @param  无
  * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下
  */
unsigned char Key(void)
{
	unsigned char Temp=0;
	Temp=Key_KeyNumber;
	Key_KeyNumber=0;
	return Temp;
}
 
/**
  * @brief  获取当前按键的状态,无消抖及松手检测
  * @param  无
  * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下
  */
unsigned char Key_GetState()
{
	unsigned char KeyNumber=0;
	
	if(P3_1==0){KeyNumber=1;}
	if(P3_0==0){KeyNumber=2;}
	if(P3_2==0){KeyNumber=3;}
	if(P3_3==0){KeyNumber=4;}
	
	return KeyNumber;
}
 
/**
  * @brief  按键驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Key_Loop(void)
{
	static unsigned char NowState,LastState;
	LastState=NowState;				//按键状态更新
	NowState=Key_GetState();		//获取当前按键状态
	//如果上个时间点按键按下,这个时间点未按下,则是松手瞬间,以此避免消抖和松手检测
	if(LastState==1 && NowState==0)
	{
		Key_KeyNumber=1;
	}
	if(LastState==2 && NowState==0)
	{
		Key_KeyNumber=2;
	}
	if(LastState==3 && NowState==0)
	{
		Key_KeyNumber=3;
	}
	if(LastState==4 && NowState==0)
	{
		Key_KeyNumber=4;
	}
}

十三.DS18B20温度传感器与温度读取

1.DS18B20介绍

嵌入式入门之51单片机_第89张图片

2.引电路脚及应用

嵌入式入门之51单片机_第90张图片

3.内部结构框图

嵌入式入门之51单片机_第91张图片

4.存储器结构

嵌入式入门之51单片机_第92张图片

5.单总线介绍

嵌入式入门之51单片机_第93张图片

6.单总线电路规范

嵌入式入门之51单片机_第94张图片

7.单总线时序结构

嵌入式入门之51单片机_第95张图片

嵌入式入门之51单片机_第96张图片
嵌入式入门之51单片机_第97张图片
嵌入式入门之51单片机_第98张图片

8.DS18B20操作流程

嵌入式入门之51单片机_第99张图片

9.DS18B20数据帧

嵌入式入门之51单片机_第100张图片

10.温度存储格式

嵌入式入门之51单片机_第101张图片

11.温度读取储存

main

#include 
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
#include "AT24C02.h"
#include "Key.h"
#include "Timer0.h"
 
float T,TShow;
char TLow,THigh;
unsigned char KeyNum;
 
void main()
{
	DS18B20_ConvertT();		//上电先转换一次温度,防止第一次读数据错误
	Delay(1000);			//等待转换完成
	THigh=AT24C02_ReadByte(0);	//读取温度阈值数据
	TLow=AT24C02_ReadByte(1);
	if(THigh>125 || TLow<-55 || THigh<=TLow)
	{
		THigh=20;			//如果阈值非法,则设为默认值
		TLow=15;
	}
	LCD_Init();
	LCD_ShowString(1,1,"T:");
	LCD_ShowString(2,1,"TH:");
	LCD_ShowString(2,9,"TL:");
	LCD_ShowSignedNum(2,4,THigh,3);
	LCD_ShowSignedNum(2,12,TLow,3);
	Timer0_Init();
	
	while(1)
	{
		KeyNum=Key();
		
		/*温度读取及显示*/
		DS18B20_ConvertT();	//转换温度
		T=DS18B20_ReadT();	//读取温度
		if(T<0)				//如果温度小于0
		{
			LCD_ShowChar(1,3,'-');	//显示负号
			TShow=-T;		//将温度变为正数
		}
		else				//如果温度大于等于0
		{
			LCD_ShowChar(1,3,'+');	//显示正号
			TShow=T;
		}
		LCD_ShowNum(1,4,TShow,3);		//显示温度整数部分
		LCD_ShowChar(1,7,'.');		//显示小数点
		LCD_ShowNum(1,8,(unsigned long)(TShow*100)%100,2);//显示温度小数部分
		
		/*阈值判断及显示*/
		if(KeyNum)
		{
			if(KeyNum==1)	//K1按键,THigh自增
			{
				THigh++;
				if(THigh>125){THigh=125;}
			}
			if(KeyNum==2)	//K2按键,THigh自减
			{
				THigh--;
				if(THigh<=TLow){THigh++;}
			}
			if(KeyNum==3)	//K3按键,TLow自增
			{
				TLow++;
				if(TLow>=THigh){TLow--;}
			}
			if(KeyNum==4)	//K4按键,TLow自减
			{
				TLow--;
				if(TLow<-55){TLow=-55;}
			}
			LCD_ShowSignedNum(2,4,THigh,3);	//显示阈值数据
			LCD_ShowSignedNum(2,12,TLow,3);
			AT24C02_WriteByte(0,THigh);		//写入到At24C02中保存
			Delay(5);
			AT24C02_WriteByte(1,TLow);
			Delay(5);
		}
		if(T>THigh)			//越界判断
		{
			LCD_ShowString(1,13,"OV:H");
		}
		else if(T<TLow)
		{
			LCD_ShowString(1,13,"OV:L");
		}
		else
		{
			LCD_ShowString(1,13,"    ");
		}
	}
}
 
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=20)
	{
		T0Count=0;
		Key_Loop();	//每20ms调用一次按键驱动函数
	}
}

OneWrite.c

#include 
//引脚定义
sbit OneWire_DQ=P3^7;
 
/**
  * @brief  单总线初始化
  * @param  无
  * @retval 从机响应位,0为响应,1为未响应
  */
unsigned char OneWire_Init(void)
{
	unsigned char i;
	unsigned char AckBit;
	OneWire_DQ=1;
	OneWire_DQ=0;
	i = 247;while (--i);		//Delay 500us
	OneWire_DQ=1;
	i = 32;while (--i);			//Delay 70us
	AckBit=OneWire_DQ;
	i = 247;while (--i);		//Delay 500us
	return AckBit;
}
 
/**
  * @brief  单总线发送一位
  * @param  Bit 要发送的位
  * @retval 无
  */
void OneWire_SendBit(unsigned char Bit)
{
	unsigned char i;
	OneWire_DQ=0;
	i = 4;while (--i);			//Delay 10us
	OneWire_DQ=Bit;
	i = 24;while (--i);			//Delay 50us
	OneWire_DQ=1;
}
 
/**
  * @brief  单总线接收一位
  * @param  无
  * @retval 读取的位
  */
unsigned char OneWire_ReceiveBit(void)
{
	unsigned char i;
	unsigned char Bit;
	OneWire_DQ=0;
	i = 2;while (--i);			//Delay 5us
	OneWire_DQ=1;
	i = 2;while (--i);			//Delay 5us
	Bit=OneWire_DQ;
	i = 24;while (--i);			//Delay 50us
	return Bit;
}
 
/**
  * @brief  单总线发送一个字节
  * @param  Byte 要发送的字节
  * @retval 无
  */
void OneWire_SendByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		OneWire_SendBit(Byte&(0x01<<i));
	}
}
 
/**
  * @brief  单总线接收一个字节
  * @param  无
  * @retval 接收的一个字节
  */
unsigned char OneWire_ReceiveByte(void)
{
	unsigned char i;
	unsigned char Byte=0x00;
	for(i=0;i<8;i++)
	{
		if(OneWire_ReceiveBit()){Byte|=(0x01<<i);}
	}
	return Byte;
}

OneWrite.h

#ifndef __ONEWIRE_H__
#define __ONEWIRE_H__
 
unsigned char OneWire_Init(void);
void OneWire_SendBit(unsigned char Bit);
unsigned char OneWire_ReceiveBit(void);
void OneWire_SendByte(unsigned char Byte);
unsigned char OneWire_ReceiveByte(void);
 
#endif

12.温度报警器代码

main:

#include 
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
#include "AT24C02.h"
#include "Key.h"
#include "Timer0.h"
 
float T,TShow;
char TLow,THigh;
unsigned char KeyNum;
 
void main()
{
	DS18B20_ConvertT();		//上电先转换一次温度,防止第一次读数据错误
	Delay(1000);			//等待转换完成
	THigh=AT24C02_ReadByte(0);	//读取温度阈值数据
	TLow=AT24C02_ReadByte(1);
	if(THigh>125 || TLow<-55 || THigh<=TLow)
	{
		THigh=20;			//如果阈值非法,则设为默认值
		TLow=15;
	}
	LCD_Init();
	LCD_ShowString(1,1,"T:");
	LCD_ShowString(2,1,"TH:");
	LCD_ShowString(2,9,"TL:");
	LCD_ShowSignedNum(2,4,THigh,3);
	LCD_ShowSignedNum(2,12,TLow,3);
	Timer0_Init();
	
	while(1)
	{
		KeyNum=Key();
		
		/*温度读取及显示*/
		DS18B20_ConvertT();	//转换温度
		T=DS18B20_ReadT();	//读取温度
		if(T<0)				//如果温度小于0
		{
			LCD_ShowChar(1,3,'-');	//显示负号
			TShow=-T;		//将温度变为正数
		}
		else				//如果温度大于等于0
		{
			LCD_ShowChar(1,3,'+');	//显示正号
			TShow=T;
		}
		LCD_ShowNum(1,4,TShow,3);		//显示温度整数部分
		LCD_ShowChar(1,7,'.');		//显示小数点
		LCD_ShowNum(1,8,(unsigned long)(TShow*100)%100,2);//显示温度小数部分
		
		/*阈值判断及显示*/
		if(KeyNum)
		{
			if(KeyNum==1)	//K1按键,THigh自增
			{
				THigh++;
				if(THigh>125){THigh=125;}
			}
			if(KeyNum==2)	//K2按键,THigh自减
			{
				THigh--;
				if(THigh<=TLow){THigh++;}
			}
			if(KeyNum==3)	//K3按键,TLow自增
			{
				TLow++;
				if(TLow>=THigh){TLow--;}
			}
			if(KeyNum==4)	//K4按键,TLow自减
			{
				TLow--;
				if(TLow<-55){TLow=-55;}
			}
			LCD_ShowSignedNum(2,4,THigh,3);	//显示阈值数据
			LCD_ShowSignedNum(2,12,TLow,3);
			AT24C02_WriteByte(0,THigh);		//写入到At24C02中保存
			Delay(5);
			AT24C02_WriteByte(1,TLow);
			Delay(5);
		}
		if(T>THigh)			//越界判断
		{
			LCD_ShowString(1,13,"OV:H");
		}
		else if(T<TLow)
		{
			LCD_ShowString(1,13,"OV:L");
		}
		else
		{
			LCD_ShowString(1,13,"    ");
		}
	}
}
 
void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=20)
	{
		T0Count=0;
		Key_Loop();	//每20ms调用一次按键驱动函数
	}
}

十四.LCD1602液晶显示屏

1.LCD1602介绍

嵌入式入门之51单片机_第102张图片

2.引脚及应用电路

嵌入式入门之51单片机_第103张图片

3.内部结构框图

嵌入式入门之51单片机_第104张图片

4.存储器结构

嵌入式入门之51单片机_第105张图片

5.时序结构

嵌入式入门之51单片机_第106张图片

6.LCD1602指令集

嵌入式入门之51单片机_第107张图片

7.LCD1602操作流程

嵌入式入门之51单片机_第108张图片

8.编写代码

详见LCD1602模块(第五章)。

十五.直流电机驱动(PWM)与LED呼吸灯

1.直流电机介绍

嵌入式入门之51单片机_第109张图片

2.电机驱动电路

你可能感兴趣的:(单片机,51单片机)