前言

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

一.环境搭建

1.开发软件:keil5C51。可自行到官网下载。(注意要下51单片机版本)

2、烧录软件stc-isp

3.开发板:普中A2标准板。

二.单片机介绍

单片机(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单片机结构


STC89C52系列单片机最小系统

二.操作LED

1.LED的硬件原理图

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


2.控制LED代码。

//LED D1亮
#include <REGX52.H>void main()
{//P2=0xEE;//1111 1110while(){P2=0xEE;//1111 1110}
}
//延时500ms控制LED灯闪烁
#include <REGX52.H>
#include <INTRINS.H>
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 <REGX52.H>
#include <intrins.h>
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 1110Delay500ms();P2=0xFD;//1111 1101Delay500ms();P2=0xFB;//1111 1011Delay500ms();P2=0xF7;//1111 0111Delay500ms();P2=0xEF;//1110 1111Delay500ms();P2=0xDF;//1101 1111Delay500ms();P2=0xBF;//1011 1111Delay500ms();P2=0x7F;//0111 1111Delay500ms();}
}
//LED流水灯升级(控制延时时间)
#include <REGX52.H>
#include <intrins.h>
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 1110Delay1ms(500);P2=0xFD;//1111 1101Delay1ms(500);P2=0xFB;//1111 1011Delay1ms(500);P2=0xF7;//1111 0111Delay1ms(500);P2=0xEF;//1110 1111Delay1ms(500);P2=0xDF;//1101 1111Delay1ms(500);P2=0xBF;//1011 1111Delay1ms(500);P2=0x7F;//0111 1111Delay1ms(500);}
}
//按位与控制LED循环亮灭
#include <REGX52.H>
#include <intrins.h>
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;}
}


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

1.独立按键原理图

四个按键都接地,按下时导通,四根引线是则处于低电位。

2.按键的抖动。

因为按键的抖动,消除抖动可以在编写代码是延时抖动时间,使他处于稳点位置。

3.按键控制led的代码。

//按键控制LED
#include <REGX52.H>
void main()
{//P2=0xFE;//P2_0=1;while(1){if(P3_1==0){P2_0=0;}else{P2_0=1;}}}
//按键控制led状态
#include <REGX52.H>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 <REGX52.H>
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 <REGX52.H>
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;elseLEDNum--;P2 = ~(0x01<<LEDNum);}}
}

四.静态数码管

1.单个数码管引脚定义

数码管的接法,有共阳和共阴之分。共阴时,拉高电压即可点亮。共阳时,拉低电平点亮。

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

3.74HC138译码器原理图

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

5.静态数码管模块代码

//
#include <REGX52.H>
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 <REGX52.H>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.模块化编程


模块化编程框架


C语言预编译

2.LCD1602调试工具

六.矩阵键盘

1.矩阵键盘介绍

2.扫描的概念

3.准双向口输出简图

4.矩阵键盘模块原理图

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

5.代码块示例

MartixKey.c

#include <REGX52.H>
#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 <REGX52.H>
#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 <REGX52.H>
#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,则正确elseLCD_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位定时器用。

4.定时器框架

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

5.定时器工作模式

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

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

工作模式1框图:

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

定时器时钟

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


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


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

定时器计数单元

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

中断系统

中断程序流程

STC89C52中断资源

中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)
中断优先级个数:4个
中断号:

注意:中断的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的中断资源,
例如中断源个数不同、中断优先级个数不同等等

定时器和中断系统


定时器相关寄存器

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

寄存器相当于一个复杂机器的“操作按钮”


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

main:

#include <STC89C5xRC.H>
#include "Timer0.h"
#include "Key.h"
#include <INTRINS.H>unsigned char KeyNum,LEDMode;void main()
{P2=0xFE;Timer0Init();while(1){KeyNum=Key();      //获取独立按键键码if(KeyNum)            //如果按键按下{if(KeyNum==1)    //如果K1按键按下{LEDMode++; //模式切换,按1下按键是模式1,按2下是模式0,默认模式0if(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 <STC89C5xRC.H>/*** @brief  定时器0初始化,1毫秒@12.000MHz* @param  无* @retval 无*/
void Timer0Init(void)
{TMOD &= 0xF0;     //设置定时器模式,只改变T0,避免T1改变TMOD |= 0x01;      //设置定时器模式TL0 = 0x18;       //高位设置定时初值 65535/256TH0 = 0xFC;        //低位设置定时初值 65535%256TF0 = 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 <REGX52.H>
#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要交叉连接
  • 当只需单向的数据传输时,可以直接一根通信线
  • 当电平标准不一致时,需要加电平转换芯片

电平标准

接口及引脚定义

常见接口比较

串口参数与时序图


串口模式图

相关寄存器

串口和中断系统

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

main:

#include <STC89C5xRC.H>
#include "Delay.h"
#include "UART.h"unsigned char Sec;void main()
{UartInit();while(1){UART_SendByte(Sec);Sec++;Delay(1);  // 必要的延时,避免误差导致乱码,没误差的时候可以不需要}
}
#include <REGX52.H>/*** @brief  初始化函数* @param  无* @retval 无*/
void UartInit(void)     //9600bps@11.0592MHz
{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;     //启动定时器1EA = 1;ES = 1;}/*** @brief  向缓冲区赋一个数据* @param  Byte 赋的值* @retval 无*/
void UART_SENT(unsigned char Byte)
{SBUF = Byte;while(TI==0);TI=0;
}

5.电脑通过串口控制LED

#include <REGX52.H>
#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点阵屏介绍

2.显示原理

3.74HC595模块原理图

4.74HC595介绍

74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,
8根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。

5.C51的sfr、sbit

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

#include <STC89C5xRC.H>
#include "Delay.H"sbit RCK=P3^5;  //RCLK
sbit SCK=P3^6;  //SRCLK
sbit SER=P3^4; //SER#define MATRIX_LED_PORT  P0void _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 <REGX52.H>
#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介绍

2.引脚定义和应用电路

3.内部结构框图

4.晶振工作原理

晶振结构示意图


我们给石英晶体电压时,石英晶体就会产生机械形变,形变时候又会产生电压,以此不断重复给石英晶体交变的电压
就能产生稳定的震动频率。

当输入的信号频率等于谐振频率时候,LC串联阻抗看作0


反相器兼具把信号放大的功能



感兴趣的可以点击去看原视频介绍。
晶振工作原理

5.寄存器


WP写保护位

6.命令字

7.时序定义

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

8.BCD码

9.DS1302时钟代码

main:

#include <REGX52.H>
#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 <REGX52.H>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 0x8eunsigned 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 <REGX52.H>
#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++;//设置选择位加1TimeSetSelect%=6;//越界清零}if(KeyNum==3)//按键3按下{DS1302_TIME[TimeSetSelect]++;//时间设置位数值加1if(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]--;//时间设置位数值减1if(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.蜂鸣器介绍

2.驱动电路

3.ULN2003

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

main:

#include <REGX52.H>
#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 <REGX52.H>
#include <INTRINS.H>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 <REGX52.H>#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[]={//音符,时值,//1P,  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,//2M6,    4+2,M5,    2,M6, 4,H1, 4,M5,   4+4+4,M3, 4,M4,   4+2,M3,    2,M4,   4,H1,   4,//3M3,    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,//4H1,    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,//5M5,    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,//6H1,  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,//7H2,    4+4+4,M5, 2,M5,   2,H1,   4+2,M7,    2,H1,   4,H3,   4,H3,   4+4+4+4,//8M6,   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,//9H3,    4+4+4,H3, 4,H6,   4+4,H5,    4,H5,   4,H3,   2,H2,   2,H1,   4+4,P, 2,H1,   2,//10H2,   4,H1,   2,H2,   2,H2,   4,H5,   4,H3,   4+4+4,H3, 4,H6,   4+4,H5,    4+4,//11H3,    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.存储器的介绍

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.存储器简化模型

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

3.AT24C02介绍

4.引脚及应用电路

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

5.内部结构框图

6.I2C总线介绍

7. I2C电路规范

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

8.I2C时序结构



9.I2C数据帧


10.AT24C02数据帧


11.程序代码

main:

#include <REGX52.H>
#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 <REGX52.H>
#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 <REGX52.H>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 <REGX52.H>
#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;                //分秒清0Sec=0;MiniSec=0;}if(KeyNum==3)            //K3按键按下{AT24C02_WriteByte(0,Min);  //将分秒写入AT24C02Delay(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 <REGX52.H>
#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 <REGX52.H>
#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介绍

2.引电路脚及应用

3.内部结构框图

4.存储器结构

5.单总线介绍

6.单总线电路规范

7.单总线时序结构



8.DS18B20操作流程

9.DS18B20数据帧

10.温度存储格式

11.温度读取储存

main

#include <REGX52.H>
#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 <REGX52.H>
//引脚定义
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 500usOneWire_DQ=1;i = 32;while (--i);         //Delay 70usAckBit=OneWire_DQ;i = 247;while (--i);        //Delay 500usreturn AckBit;
}/*** @brief  单总线发送一位* @param  Bit 要发送的位* @retval 无*/
void OneWire_SendBit(unsigned char Bit)
{unsigned char i;OneWire_DQ=0;i = 4;while (--i);          //Delay 10usOneWire_DQ=Bit;i = 24;while (--i);            //Delay 50usOneWire_DQ=1;
}/*** @brief  单总线接收一位* @param  无* @retval 读取的位*/
unsigned char OneWire_ReceiveBit(void)
{unsigned char i;unsigned char Bit;OneWire_DQ=0;i = 2;while (--i);            //Delay 5usOneWire_DQ=1;i = 2;while (--i);            //Delay 5usBit=OneWire_DQ;i = 24;while (--i);         //Delay 50usreturn 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 <REGX52.H>
#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介绍

2.引脚及应用电路

3.内部结构框图

4.存储器结构

5.时序结构

6.LCD1602指令集

7.LCD1602操作流程

8.编写代码

详见LCD1602模块(第五章)。

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

1.直流电机介绍

2.电机驱动电路

嵌入式入门之51单片机相关推荐

  1. 嵌入式入门-32位单片机简介

    一.单片机 参见单片机_百度百科 这是单片机的基本介绍,千篇一律搬运工的工作就不打算浪费时间了,就从目前学习到的知识面总结一下单片机吧,当然,入行不到两个月菜狗一枚,不正之处请指正. 单片机,也就是微 ...

  2. 嵌入式电路设计(51单片机电路设计)

    [ 声明:版权所有,请勿用于商业用途. 联系信箱:feixiaoxing @163.com] 读书的时候,其实学校没有教学51单片机.后来是看了郭天祥的书,并且买了杜洋工作室的板子,这才知道有51单片 ...

  3. 嵌入式设计 | 基于51单片机的TEA5767 FM收音机

    我们现在想要实现,51单片机控制TEA5767收音模块,自动搜台,功放发声,并在1602液晶上面显示 还是那句话,要学会看芯片手册.这个项目无非通过就是I²C写数据输出,喇叭发声,然后把变化的数据显示 ...

  4. 51单片机入门教程(3)——数码管显示学号

    目录 1.数码管简介 2.静态显示 3.动态显示 4.小结 相信经过了流水灯的实现,大家已经弄清楚了Keil和Proteus是如何联动使用的,并且对51单片机也有了一定的认识,在这一章里,我带大家利用 ...

  5. 最简单DIY基于51单片机的舵机控制器

    51单片机物联网智能小车系列文章目录 第一篇:最简单DIY的51蓝牙遥控小车设计方案 第二篇:最简单DIY串口蓝牙硬件实现方案 第三篇:最简单DIY蓝牙PS2遥控器控制蓝牙智能小车 第四篇:最简单DI ...

  6. 最简单DIY基于蓝牙、51单片机和舵机的钢铁爱国者机关枪控制器

    51单片机物联网智能小车系列文章目录 第一篇:最简单DIY的51蓝牙遥控小车设计方案 第二篇:最简单DIY串口蓝牙硬件实现方案 第三篇:最简单DIY蓝牙PS2遥控器控制蓝牙智能小车 第四篇:最简单DI ...

  7. 嵌入式单片机入门(51)

    1.嵌入式的简单了解 2.单片机和嵌入式的关系 3.单片机的学习 4.单片机的分类及51单片机的基本构造 一,嵌入式的简单了解 嵌入式的方向:纯硬件和软硬件结合.纯硬件包含很多种,例如硬件电路.PCB ...

  8. 【51单片机快速入门指南】4.3.1: MPU6050调用DMP库获取四元数和欧拉角

    目录 相关介绍 DMP库相关 DMP加载步骤: DMP设置数据写入 更新DMP DMP数据包结构 程序实现 DMP.c DMP.h 测试程序 四元数 实验现象 欧拉角的获取 普中51-单核-A2 ST ...

  9. 嵌入式开发之路,从51单片机开始

    关注.星标公众号,直达精彩内容 来源:技术让梦想更伟大 作者:李肖遥 嵌入式开发入门之路 我相信很多朋友第一次接触的单片机应该就是51单片机,8位的mcu,丰富的教程,可以做很多小玩意,让初学者基本掌 ...

最新文章

  1. 实战篇:一个核心系统 3 万多行代码的重构之旅
  2. tomcat+SSH中遇到中文乱码的解决方法
  3. 代码单元测试:gtest
  4. java getdelay_java中DelayQueue的一个使用陷阱分析
  5. jquery-属性操作
  6. snmp是什么层协议_率先拥抱TSN——CC-Link发布新一代网络协议CC-Link IE TSN
  7. LeetCode 一题多解
  8. 怎样安装php5_如何安装php5.3
  9. c++ PDFium pdf转为图片
  10. 使用d3.js绘制曲线图
  11. SQL Server - 设置主键自增
  12. 这特么是啥系列之----HSF求求你别秀了
  13. 谷歌自动驾驶正式入华,能否掀起“鲶鱼效应”?
  14. Ubuntu 安装 SSH 服务
  15. 解决vimdiff ‘E97: Cannot create diffs‘错误的一种方法
  16. 5万块钱的笔记本,没能让苹果“炸场”
  17. 用Python实现拨打电话
  18. “熬夜”的地道英文说法
  19. matlab使用Copula仿真优化市场风险
  20. 天线基本性能参数介绍

热门文章

  1. Android插件化之DroidPlugin原理解析
  2. 形容词的比较级和最高级
  3. 岗位热招 | 微软企业商用事业部喊你加入啦~
  4. 今天是端午节,工作有进展了
  5. 技能高考c语言程序填空题,技能练习题
  6. 这30个生活小技巧,不看后悔死……
  7. python求极限函数_SymPy库常用函数
  8. 【计算机二级Python】阶段性总结版
  9. html css超链接字体颜色,css 字体颜色(css color)
  10. JS中的可枚举属性与不可枚举属性的学习以及扩展