一、模块化编程
1、把各个模块的代码放在.c文件中(注意为.c文件)
2、在.h文件里提供外部可调用的函数的声明(注意为.h文件)
3、其他.c文件想使用其中代码时,只需要#include"xxx.h"文件即可(#include< >与#include" "的区别在于前者是在目录中查找,而后者是在自己的程序的文件查找,因此需要提前把所应用的函数放进本文件夹中,否则会找不到而出现错误)
4、define语句:#define PI 3.14的意思为将PI代替为3.14;#define ABC的意思为定义ABC
5、.h程序的格式:(外部可调用的函数放在.h文件内)
#ifndef__xxx_H__ //xxx为文件名
#define__xxx_H__
void xxx(); //函数声明
#endif
二、LCD1602调试工具
1、该单片机的液晶显示屏。会与数码管和LED所用的管脚冲突,因此使用该工具会导致数码管乱码,LED的前三个灯无法使用
2、(提供的LCD1602代码属于模块化代码)
LCD_Init(); //初始化是必须的
LCD_ShowChar(, ,); //显示一个字符
LCD_ShowSrting(, ,); //显示字符串
LCD_ShowNum(, , ,); //显示十进制数字
LCD_ShowSignedNum(, , ,); //显示带有符号的十进制数字
LCD_ShowHexNum(, , ,); //显示十六进制数字
LCD_ShowBinNum(, , ,); //显示二进制
(注意每个函数的变量查看模块里的注释)
三、矩阵键盘
1、扫描:矩阵键盘为输入扫描:过程大致为读取第1位(列)——第2位(列)——......——迅速循环这个过程。(可以逐行扫描也可以逐列扫描,但由于线路问题,所以首选逐列扫描)
2、示例
P1=0xFF;
P1_3=0;//这两步相当于将矩阵清零后,选择第一列
if(P1_7==0){Delay1ms(20);while(P1_7==0);Delay1ms(20);keynum=1;}
if(P1_6==0){Delay1ms(20);while(P1_6==0);Delay1ms(20);keynum=5;}
if(P1_5==0){Delay1ms(20);while(P1_5==0);Delay1ms(20);keynum=9;}
if(P1_4==0){Delay1ms(20);while(P1_4==0);Delay1ms(20);keynum=13;}
(1)第一行将P1矩阵清零;第二行P1_3=0表示选择了图中P13这一列。
(2)第三行P1_7=0表示选择了图中P17这一行,结合第一列,也就是图中S1,后面将1赋给了kuynum。
(3)剩下几行同理,按下S5,S9,S13,都将相应的值赋给了keynum。
(将其包装为模块keyboard,方便以后直接运用它)
3、
#include <REGX52.H>
#include "Delay1ms.h"
#include "LCD1602.h"
#include "keyboard.h"
unsigned char keynum;
void main()
{
char a[16]="input a number";
LCD_Init();
LCD_ShowString(1,1,a);
while(1)
{
keynum=keyboard();//将keyboard这个函数的返回值赋予keynum
if(keynum)//即为keynum!=0
{
LCD_ShowNum(2,1,keynum,2);//末尾为输入两位数,若不足两位,自动补为0
}
}
}
(1)我们这里运用了四个文件,分别是系统文件、Delay1ms文件(延迟)、LCD1602文件(液晶显示屏)、keyboard文件(矩阵键盘)
(2)LCD_Init();为初始化LCD函数,方可运用LCD1602
(3)这里运用了LCD函数中的LCD_Showstring()模块。LCD_Showstring(1,1,a),表示在液晶显示屏的第一行,第一位数开始显示字符a,前面定义了字符a为input a number。
(4)while函数内将keyboard的值赋给了前面定义的keynum,紧接着if语句,if(keynum)表示只要kuynum有数值,不等于0,就进入if函数,然后运用LCD_ShowNum然后根据keynum的值来显示数值,(2,1,keynum,2)的意思是液晶屏第二行第一格开始显示keynum,占两格。
四、定时器
1、属于单片机的内部资源,其电路的连接和运转均在单片机内部完成
2、作用:(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
(2)代替长时间的Delay,提高CPU的运行效率和处理速度(使用Delay短暂的时间时,是无法使用单片机的其他功能的)
3、个数:该单片机有3个定时器(T1、T2、T3),T0与T1与传统的51单片机兼容,T2是此型号单片机增加的资源
4、STC89C52的T0和T1均有四个工作模式
模式0:13位定时器/计数器
模式1:16位定时器/计数器
模式2:8位自动重装模式
模式3:两个8位计数器
5、模式——组成:时钟&计数&中断
(1)时钟给计数器一个脉冲,使其加一
(2)计数器最大内存为65535,溢出+1会变回0,进而申请中断
(3)系统时钟:SYSclk,即晶振周期,本开发板为12MHZ,时钟会将12MHZ进行12分频变为1MHZ(1微秒)
(4)C/T:当给1(高电频),为C——计数器;当给0(低电频),为T——定时器。
(5)中断请求:总是先处理优先级别高的
(6)中断嵌套:正在处理一个中断请求时,发生了另一个级别比它还高的中断源请求,CPU你能够暂停转而去处理优先级更高的中断请求,处理完之后再继续处理原来的。
(7)中断资源:有8个中断源,有4个中断优先级
6、寄存器TMOD各位功能:
TOMD地址:89H 复位值:00H
(1)M1,M0控制:(0,0)13位定时器;(0,1)16位定时器;
(1,0)8位定时器;(1,1)定时器/计数器无效(即为停止)
(2)GATE(控制端):
(注意电路中的或、与逻辑语句)
(3)不可位寻址:只能整体赋值(TMOD)
可位寻址:可给其中的每一位单独赋值(TCON)
7、STC中的定时器计算器配置
(1)频率12.000MHZ
(2)定时长度1毫秒
(3)定时器0
(4)定时器模式16位
(5)定时器时钟12T
8、设置定时器:
void Timer0Init()
{
/*TMOD=0x01; 0000 0001->使得定时器M0口为1,其他为0。此时定时器0的M1为0、M0为1,C/T为C(定时器),因此为16位定时器;
GATE端为0,由于电路结构可得到单纯由TR0控制输出*/
TMOD=TMOD&0xF0;//把TMOD低四位清零,高四位不变
TMOD=TMOD|0x01;//把TMOD的最低位变1,高四位不变
/*当TMOD为1010 0001,->1010 0000,->1010 0001*/
TF0=0;//控制定时器工作,确保其初始值为0,让其工作
TR0=1; //当TR0=1时,才允许T0开始计数;为0会禁止
//(TF和TR0为TCON的一部分,其为可寻位址,因此可以给其中每一位单独赋值)
TH0=0xFC;/*64535/256*/
TL0=0x18;/*64535%256*///用TH0和TL0分别存储64535,前者为高位,后者为低位
/*使用64535使得时钟每记一次即增加到达65535,时间为1000微秒,即为1毫秒 */
ET0=1;
EA=1;
PT0=0;//这三行是为了打通中断系统的通路
}
(注意当函数遇到中断时,会跳转到中断函数内。中断函数的特征:函数名后有相应的中断号:interrupt x)
void Timer0_Routine() interrupt 1 //中断服务函数
{
unsigned int T0count;
TH0=0xFC;
TL0=0x18;//每次中断完后,TH0和TR0都会为溢出为0,因此需要重新赋值
T0count++;
if(T0count>=1000) //一次为1毫秒,一千次为1秒
{
T0count=0;
P2_0=~P2_0;
}
}
(这里的中断函数内,P20LED灯会以一秒为周期闪烁)
9、定时器的其他运用
(1)时钟:
#include <REGX52.H>
#include "Delay1ms.h"
#include "Time0.h"
#include "LCD1602.h"
unsigned char sec,min,hour;
void main()
{
LCD_Init();//这个也是初始化!!!
LCD_ShowString(1,1,"CLOCK:");
LCD_ShowString(2,3,":");
LCD_ShowString(2,6,":");
Timer0Init();//注意要初始化.h文件的声明!!!
while(1)
{
LCD_ShowNum(2,1,hour,2);
LCD_ShowNum(2,4,min,2);
LCD_ShowNum(2,7,sec,2);
}
}
void Timer0_Routine() interrupt 1 //中断服务函数
{
unsigned int T0count;
TH0=0xFC;
TL0=0x18;//每次中断完后,TH0和TR0都会为溢出为0,因此需要重新赋值
T0count++;
if(T0count>=1000) //一次为1毫秒,一千次为1秒
{
T0count=0;
sec++;
if(sec>=60)
{
sec=0;
min++;
if(min>=60)
{
min=0;
hour++;
}
}
}
}
(需要注意的是运用模块时,要将程序放进该程序文件夹中,并且添加到该程序中。)
(2)定时器控制LED灯流动:
#include <REGX52.H>
#include "Time0.h"
#include "keyboard_2.h"
#include <INTRINS.H>//系统带的函数
unsigned char keynum;
unsigned char LEDMode;
void main()
{
P2=0xFE;
Timer0Init();//应用闹钟,然后跳转到下面一个函数void Timer0_Routine() interrupt 1中
while(1)
{
keynum=key();
if(keynum)
{
if(keynum==1)
{
LEDMode++;//按一次P3_1使得LEDMode改变,一开始为0,按一下后为1,再按一下后为2,2经过if语句变为0
if(LEDMode>=2)LEDMode=0;//使得Mode0101变化
}
}
}
}
void Timer0_Routine() interrupt 1 //中断服务函数
{
unsigned int T0count;
TH0=0xFC;
TL0=0x18;//每次中断完后,TH0和TR0都会为溢出为0,因此需要重新赋值
T0count++;
if(T0count>=500) //一次为1毫秒,一千次为1秒
{
T0count=0;
if(LEDMode==0)
P2=_crol_(P2,1);
if(LEDMode==1)
P2=_cror_(P2,1);
}
}
·注意到的是该程序的头文件多了#include <INTRINS.H>文件,该文件是程序自带的
·我们运用到了P2=_crol_(P2,1)和P2=_cror_(P2,1),_crol_表示向左移动一格,_cror_表示向右移动一格。与<<和>>不一样,<<和>>移动到最高位后会溢出,而_crol_和_crol_到达最高位后会跳到最低位,也就是说不需要判断重新赋值。P2=_crol_(P2,1)表示将P2这列数整体向左移动1格
五、串口通信
1、串口通信的介绍:
(1)应用广泛的通讯接口,实现两个设备互相通信,成本低,线路简单。
(2)单片机与单片机、单片机与电脑,与各种模块互相通行
(3)51单片机内部自带UART
2、简单双向串口通信有两根通信线:发送端TXD和接收端RXD(其中TXD与RXD要交叉连接)
(其中VCC为供电接口,如有电源可不连接;GND为对地接口)
3、电平标准:(数据1和数据0的表达方式)
(1)TTL电平:+5V表示1,0V表示0
(2)RS232电平:-3~-15V表示1,+3~+15V表示0
/*(1)和(2)为对地电压*/
(3)RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号即两线电压)
4、术语:
(1)全双工:双方可以在同一时刻互相传输数据(两根线)
(2)半双工:通信双方可以互相传输数据,但必须分时复用一根数据线
(3)单工:不能反向运输
(4)异步:双方各自约定通信速率
(5)同步:靠一根时钟线来约定通信速率
(6)总线:连接各个设备的数据传输路线
5、参数:
(1)波特率:串口通信的速率(发送与接接收的间隔时间)
(2)比特率:传送位数
(3)检验位(9位数据格式才有):用于数据验位(奇偶校验)
实际上是检查数据中1的个数:如采用奇校验:0000 0011 1(这个1是我们人为添加上去的,和系统约定好的)->0000 0011 1检验1的个数为奇数,为正确;若为偶数,则系统重新发送,这样有利于传输的数据是正确的。
(4)串口助手的配置
选择:12.000MHZ,4800波特率,串口1,8位数据,定时器1(8位自动重载),定时器时钟12T,选择加倍
6、控制寄存器
#include <REGX52.H>
void UartInit(void) //4800bps@12.000MHz
{
//串口配置
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
//定时器配置
TMOD &= 0x0F; //清除定时器1模式位
TMOD |= 0x20; //设定定时器1为8位自动重装方式
TL1 = 0xF3; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
void UART_SendByte(unsigned char Byte)//该函数是单片机发送端向接收端发送数据,能显示出来
{
SBUF=Byte;//我们输入Byte给到SBUF即可
while(TI==0);//检测是否完成,
TI=0;//可以使函数完整运行
}
(1)SCON中的SM0,SM1确定串口工作方式:(0,1)为方式1,是常用的工作方式
(2)SCON中的REN用于允许或者禁止接收控制位(REN=1->允许)
(3)TB8;RB8
(4)TI:用来复位,完成一段指令后必须用其来复位为0;RI:同TI
7、中断跳转位函数名可以自己取,但需要在末尾加上相应的中断号“interrupt +数字”
8、发送中断与接收中断:
9、波特率计算:
(1)12MHZ晶振在12T环境下每1微秒记1次数,记了13个数,即每隔13微秒溢出一次
(2)定时器溢出率=1/13微秒≈0.07692MHZ(微秒/次)
(3)波特率:0.0769/16≈0.00480769MHZ=4807.69HZ
(4)误差:7.69/4800*100%=0.16%
10、数据显示模式
(1)HEX模式(十六进制模式/二进制模式):以原始数据的形式显示
(2)文本模式:以原始数据编码后的形式显示,如0x41显示为'A'
六、LED点阵屏
1、介绍:由若干个独立的LED组成,以矩阵排列
分类:(1)颜色:单色、双色、全彩(2)像素:8*8,16*16(可拼接)
电路:与数码管相似,也有共阴极与共阳极。LED点阵屏需要进行逐行或者逐列扫描才能使所有LED同时显示。
2、74HC595:
3、特殊功能寄存器(sfr)(给相应名称变量地址)
sfr P0= 0x80;声明P0口寄存器,物理地址为0x80
特殊位声明(sbit)
sbit P0_1=0x80或sbit P0_1=P0^1;声明P0寄存器第一位
4、unsigned char code Animation[]={}
这是个存放LED图像的代码数组,由于内存大,应放在code指代的flash空间内,不占主要内存。(但是注意放在flash之后,在主函数内无法更改,只能读取)
5、LED点阵屏的函数模块:
#include <REGX52.H>
#include "Delay1ms.h"
sbit RCK=P3^5;//RCLK
sbit SCK=P3^6;//SRCLK
sbit SER=P3^4;//SER
#define MATRIX_LED_PORT P0//用其他来代替P0
void _74HC595_WriteByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
SER=Byte&(0x80>>i);//写入数据
SCK=1;//上升
SCK=0;//下降
}//放在以为寄存器中
RCK=1;//上升
RCK=0;//下降
//给到一个寄存器时钟中
}
void MatrixLED_ShowColumn(unsigned char Column,Date)
{
_74HC595_WriteByte(Date);//选择列显示的数据,高位在上,1为亮,0为灭(共阳极连接)
MATRIX_LED_PORT=~(0x80>>Column);//选定列:0~7
Delay1ms(1);//消影
MATRIX_LED_PORT=0xFF;//清零
}
void MatrixLED_Init()
{
SCK=0;
RCK=0;//初始定义
}
/*void main()
{
SCK=0;
RCK=0;//初始定义
while(1)
{
MatrixLED_ShowColumn(0,0xF0);
MatrixLED_ShowColumn(1,0xF0);
MatrixLED_ShowColumn(2,0xF0);
MatrixLED_ShowColumn(3,0xF0);
MatrixLED_ShowColumn(4,0xF0);
MatrixLED_ShowColumn(5,0xF0);
MatrixLED_ShowColumn(6,0xF0);
MatrixLED_ShowColumn(7,0xF0);
}
}*/
LED点阵屏显示函数展示
#include <REGX52.H>
#include "Delay1ms.h"
#include "MATRIX_LED_PORT.h"
sbit RCK=P3^5;//RCLK
sbit SCK=P3^6;//SRCLK
sbit SER=P3^4;//SER
unsigned char code Animation[]={/*所要显示的图像的代码*/
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//填补前面的空隙
0x81,0x81,0xFF,0x81,0x81,0x00,0x70,0x88,
0x84,0x82,0x41,0x21,0x41,0x82,0x84,0x88,
0x70,0x00,0x80,0x40,0x20,0x1F,0x20,0x40,
0x80,0x00,0x00,0x00,0xFD,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//填补后面的空隙
};
void main()
{
unsigned char i,offset,count;
MatrixLED_Init();
while(1)
{
for(i=0;i<8;i++)
{
MatrixLED_ShowColumn(i,Animation[i+offset]);//i+offset使LED流动
}
count++;//记录扫描的次数
if(count>10)
{
count=0;
offset++;//流动到下一位
if(offset>40)//防止尽头处出现乱码
{
offset=0;
}
}
}
}
/*MatrixLED_ShowColumn(0,Animation[0]);
MatrixLED_ShowColumn(1,Animation[1]);
MatrixLED_ShowColumn(2,Animation[2]);
MatrixLED_ShowColumn(3,Animation[3]);
MatrixLED_ShowColumn(4,Animation[4]);
MatrixLED_ShowColumn(5,Animation[5]);
MatrixLED_ShowColumn(6,Animation[6]);
MatrixLED_ShowColumn(7,Animation[7]);*/
其中获取图像代码数列的方式为使用取模软件:
(1)模拟动画:放大格点到一定程度
(2)基本操作:新建图像,选择合适的点阵大小
(3)取模方式:选择C51
(4)设计好图案后,点击点阵生成区,即可复制代码,粘贴至数列中