一、 STC89C52
单片机:Micro Controller Unit。
51内核:
C6、C5相当于蓄水池,保持水流稳定。
X1晶振电路,程序一条条执行,晶振产生单片机所必须的时钟频率,让程序一步步往下走。晶振的提供的时钟频率越高,那单片机的运行速度也就越快。32.768K晶振通常用于时间显示,16MHZ、26MHZ等用于传输信号。C2、C3起振电容作用:稳定频率。R2是为了电路稳定工作。
R1复位电路。高电平复位,因为刚接高电平电容没有电,电容相当于短路,电流直接过去。充满之后电容相当于断路,9线变为低电平。
s1-s16:矩阵按键。
k1-4:独立按键。
DS1302:时钟芯片(闹钟、定时控制)。
AD/DA:模数转换器,将0、5v转为0-5v,数字信号转为模拟信号(单片机LED可以渐进亮度)。
单片机电平是TTL 电平,高电平+5v,低电平0v。
GR1:光敏电阻,103电位器(滑动变阻器)。
NTC1:热敏电阻
U15:触摸屏芯片。
U14:步进电机:精确控制角度、速度。
138译码器:驱动数码管的。
24C02:E方PROM,掉电不丢失。
74HC245:驱动数码管。
PR9和PR10:限流电阻,102=10 00=1K电阻,473=47 000=47K.
二、LED发光二极管
LED发光二极管:Light Emitting Diode
CPU通过控制寄存器来控制硬件电路。
D1-D8电路图:
P20接高电平LED灯不亮,接低电平才亮,控制引脚输出高低电平,P2是从STC搭线过来的。
跑马灯程序:
Delay函数延迟1ms:
#include <REGX52.H>
void Delay500ms(unsigned int num) //stc-isp软件延时计算器长度1ms、指令集为STC-Y1
{
unsigned char i, j;
while(num)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
num--;
}
}
void main()
{
while(1)
{
P2=0xFE;
Delay500ms(600);
P2=0xFD;
Delay500ms(500);
P2=0xFB;
Delay500ms(400);
P2=0xF7;
Delay500ms(300);
P2=0xEF;
Delay500ms(200);
P2=0xDF;
Delay500ms(100);
P2=0xBF;
Delay500ms(100);
P2=0x7F;
Delay500ms(100);
}
}
三、独立按键
独立按键内部结构:
在<REGX52.H>头文件中,sfr P0 = 0x80;表示字节寄存器,8位为一组的。 sbit P0_0=0x80;表示位寄存器。
独立按键电路图:
单片机上电,所有IO口默认都是高电平,在开漏输出下,默认输出电平被上拉电阻拉成高电平。
对于机械开关,机械触点断开、闭合时,一个开关在闭合时不会马上接通、断开也不立即断开而是会有抖动。
#include<REGX52.H>
void Delay(unsigned int x)
{
unsigned char i,j;
while(x)
{
i=2;
j=239;
do
{
while(j--);
}while(i--);
x--;
}
}
//单独按键控制LED
void main(){
while(1)
{
if(P3_1==0)
{
Delay(10);
while(P3_1==0);
Delay(10);
P2_0=~P2_0;
}
}
}
//独立按键控制LED显示二进制
void main(){
unsigned char num=0;
while(1)
{
if(P3_1==0)
{
while(P3_1==0);
//unsigned int 类型超出最大值时他会从0开始
num++;
P2=~num;
}
}
}
//独立按键控制LED移位
void main(){
unsigned char num=0;
P2=~0x01;
while(1)
{
if(P3_1==0)
{
Delay(10);
while(P3_1==0);
Delay(10);
num++;
if(num>=8)
{
num=0;
}
P2=~(0x01<<num);
}
}
}
每更新一次文件,都要重新导入文件然后烧录。
四、LED数码管
LED数码管电路图:
COM:接地线。
74HC245:双向数据缓冲器。作用是提高驱动能力。如果没有缓冲器,那么P的数据要直接驱动LED显示,有了缓冲器,数据就是信号了,因为缓冲器接了VCC,驱动能力就强。
读二进制数是从高位P07开始读。
OE其实是CE(chip enable),低电平有效,不接地就不工作。
DIR是方向,是送数据还是读数据。如果接高电平,就把左边数据送到右边。
J21是跳线帽,两个插槽,让他们短路。如果跳线帽在LE和VCC上,就是把LE接在VCC,那么左边一直是高电平。
单片机高电平驱动能力有限,输出的最大电流不能太大,低电平驱动能力强。
104是电容,100K pF=100nF。pF,nF,uF,mF,F,用来稳定电源,电源滤波。
RP4是排阻,100R=100欧,限流电阻,4位一体。
五、138译码器
G1、G2A、G2B是使能端,G1要接高电平1,G2A、G2B接低电平0才能工作。
3位ABC控制8个LED。
共阴极连接,3、8接Gnd。
共阳极连接,A段阳极连接到3、8引脚,阴极连接到7引脚。
位选:7、6、4、2、1、9、10、5端。
定义段,正好8位一一对应(51单片机是8位单片机)。
就近原则:段离哪个引脚近,就从哪个引脚引出。
//静态数码管显示
#include<REGX52.H>
unsigned char numarr[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};
void display(unsigned char led,unsigned char num)
{
switch(led)
{
case 1:P2_2=1;P2_3=1;P2_4=1;break;
case 2:P2_2=1;P2_3=1;P2_4=0;break;
case 3:P2_2=1;P2_3=0;P2_4=1;break;
case 4:P2_2=1;P2_3=0;P2_4=0;break;
case 5:P2_2=0;P2_3=1;P2_4=1;break;
case 6:P2_2=0;P2_3=1;P2_4=0;break;
case 7:P2_2=0;P2_3=0;P2_4=1;break;
case 8:P2_2=0;P2_3=0;P2_4=0;break;
}
P0=numarr[num];
}
void main()
{
display(7,5);
}
六、模块化编程
函数模块化编程:.c(函数、变量的定义)+.h文件(函数、变量的声明)。
LCD1602液晶屏调试窗口:
LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)。
LCD_ShowString(unsigned char Line,unsigned char Column,char *String)。
LCDShowChar ( unsigned char Line, unsigned char Column, char Char)。
LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)。
LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)。
LCD1602原理图:
当RS和R/W共同为低电平时可以写入指令或显示地址;当RS为低电平,R/W为高电平时,可以读忙信号;当 RS为高电平,R/W为低电平时,可以写入数据。
E端为使能端,当E端由高电平跳变为低电平时(下降沿有效),液晶模块执行命令。
#include <REGX52.H>
#include "LCD1602.h"
void main()
{
LCD_Init();
LCD_ShowChar(1,1,'A');
while(1){};
}
七、矩阵键盘
矩阵按键模块原理图:
检测逻辑:逐行扫描是把上面4个赋值,比如扫描s1行,那就P17为0(引脚默认为高电平),P16、P15、P14为1,如果P13为0那就是S1按下。逐列扫描是把下面4个赋值。
单片机I/O口工作模式:弱上拉强下拉(准双向口):输出1能力比较弱,输出0能力比较强。
上拉电阻是把一个信号通过一个电阻接到电源(Vcc),下拉电阻是一个信号通过一个电阻接到地(GND)。高电平容易被外部信号拉低。
芯片的管脚有三个类型,输出(Output,简称O)、输入(Input,简称I)和输入输出(Input/Output,简称I/O)。
芯片的输入管脚,输入的状态有三个:高电平、低电平和高阻状态。高阻状态,即管脚悬空,很可能造成输入的结果是不定状态,引起输出震荡。有些应用场合不希望出现高阻状态,可以通过上拉电阻或下拉电阻使管脚稳定状态。
高阻输入:没有上拉也没有下拉,仅做输入,减少上拉电阻对输入的影响。
推挽输出:没有上拉电阻,高电平直接接Vcc,低电平直接接Gnd。
数码管扫描(输出扫描):显示第1位->显示第2位->显示第3位->······,快速循环,同时显示。
矩阵键盘(输入扫描):读取第1行(列)->读取第2行(列)->······快速循环,同时显示。
按下矩阵键盘显示相应数字:
MatrixKey.c
#include <REGX52.H>
#include "Delay.h"
unsigned int MatrixKey(){
unsigned int num=0;
P1=0xff;
P1_3=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);num=1;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);num=5;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);num=9;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);num=13;}
P1_2=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);num=2;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);num=6;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);num=10;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);num=14;}
P1_1=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);num=3;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);num=7;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);num=11;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);num=15;}
P1_0=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);num=4;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);num=8;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);num=12;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);num=16;}
return num;
}
MatrixKey.h
#ifndef _MATRIXKEY_H_
#define _MATRIXKEY_H_
unsigned int MatrixKey();
#endif
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned int number;
void main(){
LCD_Init();
LCD_ShowString(1,1,"hello world");
while(1){
number= MatrixKey();
if(number)
{
LCD_ShowNum(2,1,number,2);//最后是显示数据长度2
}
}
}
矩阵键盘密码锁:
Delay.h
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int num);
#endif
Delay.c
void Delay(unsigned int num)
{
unsigned char i, j;
while(num)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
num--;
}
}
LCD1602.C
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay()
{
unsigned char i, j;
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}
/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}
/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}
/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}
LCD1602. H
#ifndef __LCD1602_H__
#define __LCD1602_H__
//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
#endif
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned int number;
unsigned int password;
unsigned int count=0;
void main(){
LCD_Init();
LCD_ShowString(1,1,"Password");
while(1){
number= MatrixKey();
if(number)
{
if(number<=10)
{
if(count<4)
{
password=password*10;//左移一位
password+=number%10;//10当作0
count++;
}
LCD_ShowNum(2,1,password,4);
}
if(number==11)//11当作确认键
{
if(password==1234)//不是“1234”
{
LCD_ShowString(1,14,"OK");//一共16列从倒数第三个开始
password=0;
count=0;
LCD_ShowNum(2,1,password,4);
}
else
{
LCD_ShowString(1,14,"ERR");
password=0;
count=0;
LCD_ShowNum(2,1,password,4);
}
}
if(number==12)//12取消键
{
LCD_ShowString(1,14,"CANCEL");
password=0;
count=0;
LCD_ShowNum(2,1,password,4);
}
}
}
}
出现requires ANSI-style prototype的问题,要么是没有导入头文件,要么方法的形参或者名称引用时有错误。
八、定时器
定时器:内部资源,可实现软件计时,可替换长时间Delay。
定时器个数:T0、T1、T2 。T0、T1与传统51单片机兼容,T2是89C52新增的资源。
定时器根据时钟信号,每隔一个时间段,计数单元的值+1,增加到“设定的闹钟提醒时间”,计数单元就会向中断系统发出中断申请。
STC89C52的T0和T1均有四种工作模式:
模式0:13位定时器/计数器。
模式1:16位定时器/计数器(常用)。
模式2:8位自动重装模式。
模式3:两个8位计数器。
TL0:time or low。
TH0:time or high。
TF0:time or flag,标志位,超出65535申请中断。
T0Pin:单片机外部的接口,外部引脚T0。
SYSclk(system clock):系统时钟,即晶振周期,89C52晶振为12MHz。
C/T头上-:如果一位配置为高电平,它就是C功能(counter),给0就是timer(定时器),给1是计数器。
➗12:进12分频是1MHz,1MHz的一个周期是1ms,每个1ms就要计一次数。
INT0:外部中断引脚。
GATE:打开定时器/计数器,由TR0单独控制或者INT0+TR0联合控制。联合控制就是两者都为高才打开定时器/计数器。后面跟的是非门、或门(有1计1)、与门(有0计0)。
STC89C52:4个外部中断,3个定时器中断,1个串口中断。
单片机通过配置寄存器控制内部电路连接->控制不同电路->完成不同功能。比如寄存器可以控制开关往哪里开。寄存器是连接软硬件的媒介。
time or control:
TR1:定时器开始。
IE1:interrupt enable,定时器1的中断使能。
TF:溢出标志位。
IE、IT和INT0有关。
可位寻址:可以对每一位单独赋值。
不可位寻址:只能整体赋值。
定时器的相关寄存器:
中断寄存器:
EA:总开关。
ET:中断允许位。
PT:控制优先级。
初始化之后,从while中断到中断函数。
#include <REGX52.H>
void Timer0_Init(){
// TMOD=0x01;//0000 0001
//假如高4位配置好了,低4位要配置0001,解决配置低4位的时候覆盖高4位的问题
TMOD&=0XF0;//高4位保持不变,低4位清0
TMOD|=0X01;
//配置TCON
TF0=0;
TR0=1;
//计数脉冲在12MHz情况下每隔1us计数+1,加到最大值产生中断。
//计数器可以从0计数到65535,总共时间65ms,可以每隔1ms产生一次中断,1000次中断就是1s,此时TF0=1;
//123/100=1,存一位;123%100=23;存两位,寄存器8位,两个寄存器拼接的65535,所以但看寄存器0要分开。
//把高八位拿出来
TH0=64535/256;//0xFC
//把低八位拿出来
TL0=64535%256;//0x17,stp生成是0x18,应该是64535%256+1
ET0=1;
EA=1;
//PT0默认为0
PT0=0;
}
void main()
{
Timer0_Init();
while(1)
{
}
}
unsigned int T0count;
//中断服务程序
void Timer0_Routine() interrupt 1
{
//TH、TL执行初始化后会变成0,要修改回来
TH0=64535/256;
TL0=64535%256;
T0count++;
if(T0count>=1000)
{
T0count=0;
P2_0=~P2_0;
}
}
可以使用stp生成定时器初始化的代码。
6T和12T指的是12分频和6分频。
删除AUXR,增加ET0=1;EA=1;
LED流水灯
#include<REGX52.H>
#include "delay.h"
#include "LCD1602.h"
#include <INTRINS.H>
void Time0_Init()
{
TMOD &= 0xF0;
TMOD |= 0x01;
TL0 = 0x18;
TH0 = 0xFC;
TF0 = 0;
TR0 = 1;
ET0=1;
EA=1;
PT0=0;
}
unsigned int Tcount;
unsigned int cou;
void main()
{
P2=0xFE;
Time0_Init();
while(1)
{
}
}
void Timer0_Routine() interrupt 1
{
TL0 = 0x18;
TH0 = 0xFC;
Tcount++;
if(Tcount>=1000)
{
Tcount=0;
cou++;
P2=_crol_(P2,1);
}
}
液晶屏显示计时:
#include<REGX52.H>
#include "delay.h"
#include "LCD1602.h"
#include <INTRINS.H>
void Time0_Init()
{
TMOD &= 0xF0;
TMOD |= 0x01;
TL0 = 0x18;
TH0 = 0xFC;
TF0 = 0;
TR0 = 1;
ET0=1;
EA=1;
PT0=0;
}
unsigned int Tcount,min,hour;
unsigned int sec;
void main()
{
LCD_Init();
Time0_Init();
LCD_ShowString(1,1," : :");
while(1)
{
LCD_ShowNum(1,1,hour,2);
LCD_ShowNum(1,4,min,2);
LCD_ShowNum(1,7,sec,2);
}
}
void Timer0_Routine() interrupt 1
{
TL0 = 0x18;
TH0 = 0xFC;
Tcount++;
if(Tcount>1000)
{
Tcount=0;
sec++;
if(sec>=60)
{
min++;
sec=0;
if(min>=60)
{
min=0;
hour++;
if(hour>=24)
{
hour=0;
}
}
}
}
}
九、串口
串口是通讯接口,实现两个设备互相通信。
51单片机自带UART(通用异步收发器),可实现单片机串口通信。
TXD:transmit exchange data
TXD与RXD要交叉连接。
当电平标准不一致时,需要加电平转换芯片。
串口常用的电平标准有如下三种:
TTL电平▏:+5V表示1,0V表示0。单片机使用TTL电平。
RS232电平:-3~-15V表示1,+3~+15V表示0。
RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号即两线压差,不相对于Gnd)。差分传输,CAN总线、USB使用该电平,传输距离一千多米,RS232和TTL只能传输十几米,其相对于Gnd。
通信接口比较:
点对点通信:只能两个设备互相通信,不能多个设备一起通信。
单片机板子上的24C02,是存储数据的芯片,数据读出写入靠I方C实现。
单片机板子上的DS1302通信方式 类似SPI协议。
单片机板子上的DS18B202 1-Wire。
STC89C52有1个UART
STC89C52的UART有四种工作模式:
模式0:同步移位寄存器。
模式1:8位UART,波特率可变(常用)。
模式2:9位UART,波特率固定。
模式3:9位UART。
波特率:采样的速率,串口通信的速率,发送和接收数据位的间隔时间。传送多少数据帧,比特率是传送多少位。
时序图:
奇偶校验:奇校验:1是奇数,如果原数据1是偶数再加1位。
先发低位后发高位,0011 0101,从右往左发。
串口集成了UART收发器,所以不需要了解时序图,收发就好了。
串口模式图:内部怎么收发。
SBUF:串口数据缓存寄存器,物理上是两个独立的寄存器,但占用相同的地址。写操作时,写入的是发送寄存器,读操作时,读出的是接收寄存器。
TI:transmit interrupt,发送中段,RI:接收中断。
÷2是2分频。控制波特率,控制收发器的采样时间。
串口中断:
串行口相关寄存器:
SMOD控制串口栏,POF、GF、PD、IDL电源控制。SM0/FE(frame error):检测帧错误,不用管。
IPH、IP是因为89C52有4个优先级。
串口工作:配置SCON,PCON,+配置中断的EA,ES+配置定时器T1。
在串口数据手册中:
REN:是否允许接收 。
T1=1,发送完了置1,然后用软件置0,还用这个判断是发送还是接收。
设置SCON=0100000。
SMOD波特率是否加倍,给1加倍。SMOD0检测帧错误。设置PCON=0。
IE不需要配置,默认是不开启中断。
串口是对应定时器1的,定时器初始化函数需要修改。
十六位记的数多(0-65535),但每次都需要自己写的代码赋初值,浪费时间。双八位就是将十六位分开,一个计数,另一个存放初值,每次计数完成后AR(Auto Reload)会自动将值赋给CNT(计数器),不用代码处理,比较快,但只有八位所以记的数少了(0-255)。串口中需要使用双8位的模式(把两个定时器分开,自动重装)。
波特率加倍是为了分频,如果不加倍时钟变慢 就不能匹配了,会有较大误差 。
当我溢出的时候是用来计算波特率的,并不是判断中断的标志,在讲定时器的时候溢出后令ET0=1执行中断函数,但这里定时器是用来计算波特率的,串口这里接收中断和发送中断应该和定时器没关系(自己理解)
不开启中断,是因为需要我们手动清零。(课本有讲)
由于定时器T1的优先级高于串口中断的优先级,因此在定时器初始化的时候,不要打开定时器T1的中断允许标志,只需要打开定时器T1的计数功能即可,否则可能会出现无法进入串口中断的情况!
串口向电脑发送数据:
UART.c
#include <REGX52.H>
void UART_Init()
{
PCON=0X80;//波特率加倍
SCON=0X50;//0X40和0x50区别在于REN位不同
TMOD&=0x0f;//清除定时器1模式位
TMOD|=0X20;
TL1=0XF4;//设定定时器1初值,0XF3打开串口调试会出现E6
TH1=0XF4;//设定定时器1重装值
TR1=1;//启动定时器1
ET1=0;//关闭定时器1中断
}
void UART_SendByte(unsigned int byte)
{
SBUF=byte;//串口模式图中,配置好定时器和波特率,然后byte给SBUF就可自动发
while(TI==0);//检测是不是发送出去
TI=0;//TI必须软件复位
}
UART.h
#ifndef _UART_H_
#define _UART_H_
void UART_Init();
void UART_SendByte(unsigned int byte);
#endif
main.c
#include<REGX52.H>
#include "delay.h"
#include "UART.h"
unsigned int num;
void main()
{
UART_Init();
while(1)
{
UART_SendByte(num);//波特率越低,误差越稳定
num++;
Delay(1000);
}
}
效果:
电脑通过串口控制:
发送是只需要单片机发送,收是需要中断系统,电脑发过来就使用中断需要把REN位=1.即SCON=0X50。需要中断查询号。
注意:之前不用的中断是时钟中断,这里使用的中断是串口中断.
当你在发送区发送数据时,电脑端才会接受到中断,才会执行.
main.c
#include<REGX52.H>
#include "delay.h"
#include "UART.h"
unsigned int num;
void main()
{
UART_Init();
while(1)
{
}
}
//中断服务子函数
void UART_Routine() interrupt 4
{
if(RI==1)
{
P2=~SBUF;
RI=0;
UART_SendByte(SBUF);
}
}
F3=243,每隔256溢出一次,相当于计13个数溢出一次,12T的晶振在12T的模式下每隔1us计一次数,所以每隔13us溢出一次,溢出的频率就是1/13us MHz=0.07692,也就是T1溢出率。在串口模式图中,走1和÷16那么1/13us/16=0.004807MHz=4807Hz就是波特率,误差就是7/4800。
文本模式/字符模式:将原始数据编码(ASCII码)后显示。
十、LED点阵屏
引脚乱序排列。
逐行或者逐列扫描。比如9给1,其余行给0,扫描列。
74H595:
74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。恒压输出,如果多个灯点亮就可能亮度不均匀,当然也有恒流输出的。
如果OE(autoput enable,输出使能)上面加一个横线,代表低电平有效或者下降沿有效。
RCLK:register clock,寄存器时钟。默认低电平。
SERCLR:serial clear,串行清零端,把里面数据清空,接VCC代表不清空。默认低电平。
SER:串行数据。
QH‘:控制多片级联.QH’可能接其他的74HC595,如果寄存器数据满了没有RCLK移位,溢出的数据就给QH‘,QH’放到其他的74HC595的移位寄存器。
左边是移位寄存器,右边是输出缓存。
RCLK用于移位,当一位数据进来,有一个上升沿进来,这位数据就下降一位,当八位数据满了,RCLK就把寄存器的8位移到输出缓存。
单片机IO口一上电是高电平,所以需要给SERCLK设置低电平初始化。而且IO口是弱上拉, 输出低电平可以接受很大电流,输出高电平电流小相当于接一个电阻再接VCC。
LED点阵原理图:
开发板引脚和对应关系:
IO口驱动LED能力弱,可以加一个三极管驱动,这时候IO口相当于控制信号。
C51的sfr、sbit。
sfr(special function register):特殊功能寄存器声明,例:sfr P0= 0x80;声明P0口寄存器,物理地址为0x80。
sbit(special bit):特殊位声明例:sbit P0_1=0x81;或 sbit p0_1= P0_1;声明PO寄存器的第1位。
可位寻址/不可位寻址:在单片机系统中,操作任意寄存器或者某一位的数据时,必须给出其物理地址,又因为一个寄存器里有8位,所以位的数量是寄存器数量的8倍,单片机无法对所有位进行编码,故每8个寄存器中,只有一个是可以位寻址的。对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“1=”、“^=”的方法进行位操作。
74HC595电路:
让LED显示内容:
#include <REGX52.H>
#include "Delay.h"
//不管前面的名称是什么,地址赋值过去就可以
sbit RCK=P3^5;//从P3_0开始,往后5位的地址
sbit SRK=P3^6;
sbit SER=P3^4;
#define LED_PORT P0//自己设计电路P0对应可能改变。P0是寄存器,不能用sbit
void WriteByte(unsigned int byte)
{
unsigned char i;//不能在for里面用int i=0;
for(i=0;i<8;i++)
{
SER=byte&(0x80>>i);//非0就是1
SRK=1;
SRK=0;
}
RCK=1;
RCK=0;
}
void LEDshow(unsigned char column,Data)//Data是断码,不能是data
{
WriteByte(Data);
LED_PORT=~(0x80>>column);
Delay(1);
LED_PORT=0xFF;//位清零
}
void main()
{
SRK=0;
RCK=0;
while(1)
{
LEDshow(1,0x80);
LEDshow(2,0x80);
LEDshow(3,0x60);
}
}
让LED实现动态效果:
Animation[]数组是存放在RAM里面,如果里面内容数据大,可以放FLASH程序存储器里面,里面的数据是不能更改的。声明:unsigned char code Animation[] = {}。
#include <REGX52.H>
#include "Delay.h"
//不管前面的名称是什么,地址赋值过去就可以
sbit RCK=P3^5;//从P3_0开始,往后5位的地址
sbit SRK=P3^6;
sbit SER=P3^4;
#define LED_PORT P0//自己设计电路P0对应可能改变。P0是寄存器,不能用sbit
unsigned char Animation[] = {
0x30,0x78,0x7C,0x3E,0x7C,0x78,0x30,0x00,0x30,0x78,
0x7C,0x3E,0x7C,0x78,0x30,0x00,
};
void WriteByte(unsigned int byte)
{
unsigned char i;//不能在for里面用int i=0;
for(i=0;i<8;i++)
{
SER=byte&(0x80>>i);//非0就是1
SRK=1;
SRK=0;
}
RCK=1;
RCK=0;
}
void LEDshow(unsigned char column,Data)//Data是断码,不能是data
{
WriteByte(Data);
LED_PORT=~(0x80>>column);
Delay(1);
LED_PORT=0xFF;//位清零
}
unsigned char i,count,offset;
void main()
{
SRK=0;
RCK=0;
while(1)
{
for(i=0;i<8;i++)
{
LEDshow(i,Animation[i+offset]);
if(i>=8) i=0;
count++;
if(count>=8)//count防止越界,灯亮8遍才到下一帧
{count=0;offset++;if(offset>8) offset=0;}
}
Delay(20);
}
}
十一、DS1302实时时钟
DS1302是由美国DALLAS公司推出的具有涓细电流充电能力(Vcc有电给备用电池充电,没电使用备用电池)的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时且具有闰年补偿等多种功能。SO。引脚有DIP和SO,DIP直插封装,SO贴片封装。
RTC(Real TimeClock):实时时钟,是一种集成电路,通常称为时钟芯片。
晶振通过内部电路处理生成稳定的频率,精度非常高。全称叫石英晶体增长器。
WP:写保护,置1写入无效。TCS、DS、RS用于涓流充电。
寄存器定义:
7位固定为1,6位置1操作RAM,置0操作CK(clock时钟)。RD是读,WR是写。
在时钟上升沿,IO口时钟将会被写入,数据写入时钟芯片 。时钟下降沿,DS1302的数据会输出。
以BCD码存储,BCD码(BinaryCoded Decimal),用4位二进制数来表示1位十进
制数。例:00010011表示13,10000101表示85,00011010不合法。在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法。
BCD码转十进制:DEC=BCD/16*10+BCD%16;(2位BCD)。
十进制转BCD码:BCD=DEC/1O*16+DEC%10;(2位BCD)。
通信接口:
时序表:
都先要读命令字。 命令字解决了‘读写?在哪里?去哪里?’的问题。读出read用到下降沿。
read中,上升沿给IO口赋值,下降沿读出。
原理图:
LCD1602 显示时间:
DS1302.c
#include <REGX52.H>
sbit DS1302_SCLK=P3^6;
sbit DS1302_IO=P3^4;
sbit DS1302_CE=P3^5;//P3_6可行的前提是P3可位寻址,而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[]={25,3,8,18,06,36,6};//不能是03,08
void DS1302_Init()
{
DS1302_SCLK=0;
DS1302_CE=0;
}
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;
}
unsigned DS1302_ReadByte(unsigned char Command,Data)
{
unsigned char i;Data=0x00;
Command|=0x01;//不要放第一句
DS1302_CE=1;
for(i=0;i<8;i++)
{
DS1302_IO=Command&(0x01<<i);
DS1302_SCLK=0;
DS1302_SCLK=1;//如果是10那就是在Command范围内读出data
}
for(i=0;i<8;i++)
{
DS1302_SCLK=1;
DS1302_SCLK=0;//write是16个周期,read是15个周期,重复置1抵消1个周期
if(DS1302_IO){Data|=(0x01<<i);}
}
DS1302_IO=0;
DS1302_CE=0;
return Data;
}
void DS1302_SetTime()
{
DS1302_WriteByte(DS1302_WP,0x00);
DS1302_WriteByte(DS1302_SECOND,DS1302_Time[0]/10*16+DS1302_Time[0]%10);
DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
DS1302_WriteByte(DS1302_HOUR,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
DS1302_WriteByte(DS1302_WP,0x80);//开启写保护
}
void DS1302_ReadTime()
{
unsigned char temp;
temp=DS1302_ReadByte(DS1302_SECOND);
DS1302_Time[0]=temp/16*10+temp%16;
temp=DS1302_ReadByte(DS1302_MINUTE);
DS1302_Time[1]=temp/16*10+temp%16;
temp=DS1302_ReadByte(DS1302_HOUR);
DS1302_Time[2]=temp/16*10+temp%16;
}
DS1302.h
#ifndef _DS1302_H_
#define _DS1302_H_
unsigned char DS1302_Time[];//变量声明为外部变量,必须加上extern
void DS1302_Init();
void DS1302_WriteByte(unsigned char Command,Data);
unsigned DS1302_ReadByte(unsigned char Command,Data);
void DS1302_SetTime();
void DS1302_ReadTime();
#endif
main.c
#include<REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
unsigned char Second;
void main()
{
LCD_Init();
DS1302_Init();
DS1302_SetTime();
while(1)
{
DS1302_ReadTime();
LCD_ShowNum(1,1, DS1302_Time[0],2);
LCD_ShowNum(1,3, DS1302_Time[1],2);
LCD_ShowNum(1,5, DS1302_Time[2],2);
}
}
可调时钟代码:
加入定时器、按键模块。
main.c
#include<REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "key.h"
#include "Timer0.h"
unsigned char KeyNum,MODE,TimeFlash,TimeSelect;//两个模块:时钟显示、时钟设置
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);
}
void TimeSet()
{
if(KeyNum==2)
{
TimeSelect++;
TimeSelect%=3;//越界清零
}
if(KeyNum==3)
{
DS1302_Time[TimeSelect]++;
DS1302_SetTime();
if(DS1302_Time[0]>60){DS1302_Time[0]=0;}
if(DS1302_Time[1]>60){DS1302_Time[1]=0;}
if(DS1302_Time[2]>24){DS1302_Time[2]=0;}
}
if(KeyNum==4)
{
DS1302_Time[TimeSelect]--;
DS1302_SetTime();
if(DS1302_Time[0]<0){DS1302_Time[0]=59;}
if(DS1302_Time[1]<0){DS1302_Time[1]=59;}
if(DS1302_Time[2]<0){DS1302_Time[2]=23;}
}
if(TimeSelect==0&&TimeFlash==1){ LCD_ShowString(1,1," ");}
else { LCD_ShowNum(1,1, DS1302_Time[0],2);}
if(TimeSelect==1&&TimeFlash==1){ LCD_ShowString(1,4," ");}
else { LCD_ShowNum(1,4, DS1302_Time[1],2);}
if(TimeSelect==2&&TimeFlash==1){ LCD_ShowString(1,7," ");}
else { LCD_ShowNum(1,7, DS1302_Time[2],2);}
}
void main()
{
LCD_Init();
DS1302_Init();
DS1302_SetTime();
LCD_ShowString(1,1," : : ");
Timer0Init();
while(1)
{
KeyNum=key();
if(KeyNum==1)
{
if(MODE==0){MODE=1;}
else if(MODE==1)
{MODE=0;}
}
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)
{
T0Count=0;
TimeFlash=!TimeFlash;//!是逻辑取反0取反是1,~是按位取反0取反是0xff
}
}
十二、蜂鸣器
蜂鸣器按驱动方式可分为有源蜂鸣器和无源蜂鸣器。有显示正极的或者长脚是正极。
有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定。
无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲(时钟周期)的频率,可发出不同频率的声音。
蜂鸣器样式:
蜂鸣器原理图:
单片机IO口不能直接驱动蜂鸣器。所以经过ULN2003芯片。
ULN2003是一个单片高电压、高电流的达林顿晶体管阵列集成电路。它是由7对NPN达林顿管组成的。
蜂鸣器接在其中一路。给0经非门后没有驱动能力,是一种高阻态,相当于断开。LED起到测试作用。
51单片机蜂鸣器是无源蜂鸣器,所以P15要提供固定频率。不能一直通电。默认通电是有电流的。
蜂鸣器播放提示音代码:
Buzzer.c
#include "Delay.h"
#include <REGX52.H>
#include <INTRINS.H>
sbit buzzer=P2^5;
unsigned int i;
void Buzzer_Delay500ms()
{
unsigned char i;
_nop_();//延时1个机器周期,12MHz的单片机机器周期为1us
i = 247;
while (--i);
}
void Buzzer_Time(unsigned int ms)
{
for(i=0;i<ms*2;i++)
{
buzzer=!buzzer;
Buzzer_Delay500ms();//1000HZ,
}
}
main.c
#include <REGX52.H>
#include "Delay.h"
#include "key.h"
#include "Nixie.h"
#include "Buzzer.h"
unsigned char KeyNum;
sbit Buzzer=P2^5;//原理图上是P1^5,其实是P2^5
void main()
{
while(1)
{
KeyNum=key();
if(KeyNum)
{
Buzzer_Time(1000);
}
Nixie(1,KeyNum);
}
}
蜂鸣器播放音乐:
Timer0Init()中改TH0、TL0是无关紧要的,因为这是第一次中断的时间。TH0、TL0构成16位计数器,只能溢出申请中断然后归0,但不会自己清零,所以设一个初值,比如初值从中间开始计时,所以设置中断函数的TH0、TL0。把TH0从0XFC改为0XFD,重装值变大,溢出时间就变短,频率升高。
需要在半个周期翻转一次,取整是计数器计这么多就产生中断,重装载值就是中间值。
乐谱:#升音,b降音,半音是白旁边黑的,全音是白旁边的白的。