文章目录
一、补充
1.1 模块化编程
原代码:
#include <REGX52.H>
unsigned char NixieTable[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; //ÊýÂë¹Ü¶ÎÂë±í
void Delay(unsigned int xms) //
{
unsigned char i, j;
while(xms--)
{
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()
{
while(1)
{
Nixie(2,3); //第二个数码管显示3
Delay(1);
Nixie(3,7);
Delay(1);
Nixie(6,6);
Delay(1);
}
}
烧录结果:
代码多少有些冗长,我们可以对一些功能代码进行模块化
比如延时(Delay):
建立Delay.c:
void Delay(unsigned int xms) //
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
建立Delay.h:(建头文件拿来用)
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int xms);
#endif
于是,只需要一个“ #include “Delay.h” ”就能随意使用Delay()
数码管(Nixie)同理:
建立Nixie.c:
#include <REGX52.H>
#include "Delay.h"
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];
Delay(1);
P0=0x00;
}
建立Nixie.h:
#ifndef __NIXIE_H__
#define __NIXIE_H__
void Nixie(unsigned char Location, Number);
#endif
于是,主文件就变成了这样,只需引用Delay与Nixie模块,就可以极简达成原来的效果:
#include <REGX52.H>
#include "Delay.h"
#include "Nixie.h"
void main()
{
while(1)
{
Nixie(2,3);
Delay(1);
Nixie(3,7);
Delay(1);
Nixie(6,6);
Delay(1);
}
}
1.2 LCD1602调试工具
讲LCD1602插入槽口:
头文件:LCD1602.h(调用.c中函数)
#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 "LcD1602.h"
void main()
{
LCD_Init(); //初始化LCD
LCD_ShowChar(1,1,'A'); //在第一行第一列显示“A”(字符串)
LCD_ShowString(1,3,"Hello"); //从第一行第三列开始显示“Hello”(一个字母一格)
LCD_ShowNum(1,9,123,1); //从第一行第九列开始显示“123”(只显示一位,最终显示“1”)
LCD_ShowSignedNum(1,13,-66,2); //从第一行第十三列开始显示“-66”(“-”占一位,最终显示“-66”)
LCD_ShowHexNum(2,1,0xA8,2);
LCD_ShowBinNum(2,4,0xAA,8);
{
}
}
实现效果:
小应用:
#include <REGX52.H>
#include "LcD1602.h"
int Result;
void main()
{
LCD_Init();
Result=1+1;
LCD_ShowNum(1,1,Result,2);
while(1)
{
}
}
显示结果:
二、矩阵键盘
原理图:
2.1 简单使用
矩阵键盘又称为行列式键盘,它是用4条I/O线作为行线,4条I/O线作为列线组成的键盘。
在行线和列线的每一个交叉点上,设置一个按键。这样键盘中按键的个数是4×4个。
这种行列式键盘结构能够有效地提高单片机系统中I/O口的利用率。由于单片机IO端口具有线与的功能,因此当任意一个按键按下时,行和列都有一根线被线与,通过运算就可以得出按键的坐标从而判断按键键值。
矩阵键盘,像是把独立按键和数码管杂糅在了一起?通过读取引脚变化引起的电平变化来扫描判断按键的位置。
矩阵键盘可以作用在数码管,作用在LCD,也可以作用在串口。下面我们以LCD为例介绍矩阵键盘的使用。
将按键与数值的互通进行模块化:
#include <REGX52.H>
#include "Delay1.h"
unsigned char MatrixKey()
{
unsigned char KeyNumber=0;
P1=0xFF;
P1_3=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}
P1=0xFF;
P1_2=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;}
P1=0xFF;
P1_1=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;}
P1=0xFF;
P1_0=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}
return KeyNumber;
}
凭此,我们得到了MatrixKey.h
进行简单的调用:
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned char KeyNum; //定义全局变量KeyNum
void main()
{
LCD_Init();
LCD_ShowString(1,1,"MatrixKey:");
while(1)
{
KeyNum=MatrixKey(); //将按键值赋予KeyNum
if(KeyNum)
{
LCD_ShowNum(2,1,KeyNum,2); //显示KeyNum
}
}
}
按下s12按键,结果如下:
2.2 制作简易的密码锁
代码如下:
#include <REGX52.H>
#include "Delay1.h"
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned char KeyNum;
unsigned int Password,Count; //定义密码、轮数
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Password:");
while(1)
{
KeyNum=MatrixKey();
if(KeyNum)
{
if(KeyNum<=10)
{
if(Count<4)
{
Password*=10; //密码左移
Password+=KeyNum%10; //获取密码
Count++;
}
LCD_ShowNum(2,1,Password,4);
}
if(KeyNum==11) //判断正误
{
if(Password==2345)
{
LCD_ShowString(1,14,"OK ");
Password=0;
Count=0;
}
else
{
LCD_ShowString(1,14,"ERR");
Password=0;
Count=0;
}
}
if(KeyNum==12) //取消
{
Password=0;
Count=0;
LCD_ShowNum(2,1,Password,4); //更新显示
}
}
}
}
结果如下:(以2345作为密码)
三、定时器
3.1 原理
本51单片机有三个定时器T0、T1与T2(可能有些单片机没有T1)。
定时器实质是加一计数器,大致框图如下:
当计数达到一定条件时,向中断系统发送中断申请,以执行定时任务。(最大可记到65535)
T0、T1定时器的工作模式均包括以下四种:
模式0(13位定时器)
模式1(16位定时器)(常用)
模式2(8位自动重装)
模式3(两个8位定时器)
上图为工作模式1的连接图:
SYSclk:系统时钟,即晶振周期。
中断系统
突然发生紧急情况,需要cpu停下当前任务去解决的情况叫做中断,这些引起中断的突发状况被称作中断源。一个中断系统可以有多个中断源,根据轻重缓急进行先后处理。如果cpu在处理一个中断源请求时,发生了一个优先级更高的请求,那么cpu会优先处理该请求,处理完再回到低级中断服务程序,这个过程被称作中断嵌套。
没有中断嵌套系统的中断系统被称为单级中断系统,有的则称为多级中断系统。
示意图:
本单片机结构示意图:
中断资源:(与单片机型号相关)
中断源(8个):外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、外部中断2、定时器2中断、外部中断3
中断优先级数:4
中断号:
寄存器
寄存器是连接软硬件的媒介。
单片机通过配置寄存器来控制内部电路的连接,控制不同电路实现不同功能。
TCON:定时器/计时器T0、T1的控制寄存器。
TMOD:定时器/计时器的工作模式寄存器。
符号代表对应寄存器的状态,具体含义及功能可以说明书或网上查询。
3.2 小应用
定时器模块的代码可以通过stc-isp快速实现,不作过多说明。
如此配置,得到相应代码
时钟6T模式可在应用左侧勾选。本版单片机时钟只有6T、12T两种模式,所以可以直接删去第一行代码:
void Timer0_Init(void) //1毫秒@11.0592MHz
{
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x66; //设置定时初始值
TH0 = 0xFC; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1; //启用了定时器0的中断,当Timer0溢出会产生中断
EA = 1; //启用全局中断
PT0 = 0; //将定时器0设置为非倒计时模式。在倒计时模式下,当计数到零时,Timer0会自动重新加载初值并继续计数。设置为非倒计时模式后,需要手动重新加载初值以继续计数。
}
定时器控制流水灯
创建独立按键模块:
#include <REGX52.H>
#include "Delay.h"
/**
* @brief 获取独立按键键码
* @param 无
* @retval 按下按键键码,范围:0~4,无按键按下时返回值为0
*/
unsigned char Key()
{
unsigned char KeyNumber=0;
if(P3_1==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=1;}
if(P3_0==0){Delay(20);while(P3_0==0);Delay(20);KeyNumber=2;}
if(P3_2==0){Delay(20);while(P3_2==0);Delay(20);KeyNumber=3;}
if(P3_3==0){Delay(20);while(P3_3==0);Delay(20);KeyNumber=4;}
return KeyNumber;
}
正文:
#include <REGX52.H>
#include "Timer0.h"
#include "key.h"
#include <INTRINS.H> //调用系统定义的函数模块_crol_、_cror_
unsigned char KeyNum,LEDMode;
void main()
{
P2=0xFE;
Timer0_Init();
while(1)
{
KeyNum=Key();
if(KeyNum)
{
if(KeyNum==1)
{
LEDMode++;
if(LEDMode>=2)
LEDMode=0;
}
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count; //静态局部变量,防止变量值丢失
TL0 = 0x66;
TH0 = 0xFC;
T0Count++;
if(T0Count>=500)
{
T0Count=0;
if(LEDMode==0)
P2=_crol_(P2,1); //循环左移
if(LEDMode==1)
P2=_cror_(P2,1); //循环右移
}
}
至此,已实现流水灯和定时器的结合,按下第一个独立按键可以实现转向。
简易时钟
实现代码:
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"
unsigned char Sec,Min,Hour;
void main()
{
LCD_Init();
Timer0_Init();
LCD_ShowString(1,1,"Clock:");
LCD_ShowString(2,3,":");
LCD_ShowString(2,6,":"); //可写成LCD_ShowString(" : :");
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 //中断函数
{
static unsigned int T0Count; //静态局部变量,防止变量值丢失
TL0 = 0x66;
TH0 = 0xFC;
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
Sec++; //中断函数一般只进行简单任务
if(Sec>=60)
{
Sec=0;
Min++;
if(Min>=60)
{
Min=0;
Hour++;
}
}
}
}
效果:
四、串口
4.1 基本概念
串行通信与并行通信
串行通信是指使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度。
并行通信通常是将数据字节的各位用多条数据线同时进行传送。
虽然,并行通信的效率高,传输速度快,但是要想实现同时接收十分困难。顾及成本与可行性,我们一般会选择串口通信。
串口是一种应用十分广泛的通讯接口,单片机的串口可以使其与单片机、电脑等各种模块相互通信。
51单片机内部自带UART,可实现单片机的串口通信。
串口连接:
模式图:
简单双向串口通信有两根通信线(发送端TXD与接收端RXD,可以不止两根),TXD与RXD要交叉连接。
如果只是单向的数据传输,可以直接连一根通信线。
当电平标准不一致,需要加电平转换芯片转换为一致标准。(强行传输会造成设备的损坏)
电平标准(数据1与数据0的表达方式)
常用的三种电平标准如下:
TTL电平:+5V表示1,0V表示0
RS232电平:-3 ~ -15V表示1,+3 ~ +15V表示0
RS485电平:两线压差+2~+6V表示1,-2 ~ -6V表示0(差分信号,传输距离长)
常见的通信接口:
以及CAN、USB等。
通信方式
(1)、单工通信
单工是指数据传输仅能沿一个方向,不能实现反向传输。
(2)、半双工通信
半双工是指数据传输可以沿两个方向,但需要分时进行。(复用一根数据线)
(3)、全双工通信
全双工是指数据可以同时进行双向传输。
(4)、异步通信
指通信双方各自约定通信速率
(5)、同步通信
通信双方靠一根时钟线来约定通信速率
本51单片机原理图:
寄存器
波特率:串口通信的速率(发送与接收各数据位的间隔时间)
检验位:用于数据验证
停止位:用于数据帧间隔
时序图:
串口相关寄存器:
其中,SCON为串口控制寄存器:
SMOD为电源控制寄存器:
SBUF为串口数据寄存器,还有TMOD等诸多寄存器,不一一展示。
代码,可以使用stc-isp的串口助手协助完成。
与中断系统
可以类比定时器。
4.2 简单任务
关于配置串口的代码,可以使用stc-isp进行编写:
void Uart1_Init(void) //4800bps@11.0592MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
AUXR &= 0xBF; //定时器时钟12T模式
AUXR &= 0xFE; //串口1选择定时器1为波特率发生器
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xF4; //设置定时初始值
TH1 = 0xF4; //设置定时重载值
ET1 = 0; //禁止定时器中断
TR1 = 1; //定时器1开始计时
}
由于版本问题,AUXR没有被正确定义,可以删去。
串口向电脑传输数据
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte; //写入数据
while(TI==0);
TI=0; //检查写入
}
完整代码(模块化前):
#include <REGX52.H>
#include "Delay.h"
unsigned char sec;
void Uart1_Init(void) //4800bps@11.0592MHz
{
PCON |= 0x80; //使能波特率倍速位SMOD
SCON = 0x50; //8位数据,可变波特率
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xF4; //设置定时初始值
TH1 = 0xF4; //设置定时重载值
ET1 = 0; //禁止定时器中断
TR1 = 1; //定时器1开始计时
}
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte; //写入数据
while(TI==0);
TI=0; //检查写入
}
void main()
{
Uart1_Init();
while(1)
{
UART_SendByte(sec);
sec++;
Delay(1000);
}
}
现象:
电脑通过串口控制LED
在串口传输模块中加入与中断系统的关联:
EA=1;
ES=1;
构造中断服务子函数:(中断号见3.1)
void UART_Routine() interrupt 4
{
if(RI==1)
{
P2=~SBUF;
UART_SendByte(SBUF);
RI=0;
}
}
代码如下:
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
void main()
{
UART_Init();
while(1)
{
}
}
void UART_Routine() interrupt 4
{
if(RI==1)
{
P2=~SBUF;
UART_SendByte(SBUF);
RI=0;
}
}
现象:
发送”11“:
五、LED点阵屏
本单片机原理图:
5.1 背景知识
点阵屏的结构类似数码管。(数码管就像其中的单元结构组成”8“字)
74HC595
74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据。
常用于IO口扩展。
5.2 显示图形及动画
代码:
#include <REGX52.H>
#include "Delay.h"
sbit RCK=P3^5; //RCLK
sbit SCK=P3^6; //SRCLK
sbit SER=P3^4; //SER
/**
* @brief 74H595写入一个字节
* @param Byte 要写入的字节
* @retval 无
*/
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;
}
/**
* @brief LED点阵屏显示一列数据
* @param Column 要选择的列,范围:0~7,0在最左边
* @param Data 选择列显示的数据,高位在上,1为亮,0为灭
* @retval 无
*/
void MatrixLED_ShowColumn(unsigned char Column,Data)
{
_74HC595_WriteByte(Data);
P0=~(0x80>>Column);
Delay(1); //消位选与段选间的延时
P0=0xFF;
}
void main()
{
SCK=0;
RCK=0;
while(1)
{
MatrixLED_ShowColumn(0,0x3C);
MatrixLED_ShowColumn(1,0x42);
MatrixLED_ShowColumn(2,0xA9);
MatrixLED_ShowColumn(3,0x85);
MatrixLED_ShowColumn(4,0x85);
MatrixLED_ShowColumn(5,0xA9);
MatrixLED_ShowColumn(6,0x42);
MatrixLED_ShowColumn(7,0x3C);
}
}
可以显示一个笑脸:(拙劣的照相技术)
也可以显示流动的图形,可以适当使用工具。
通过文字取模工具得到想要的图案的代码数组。
实现流动的代码:
#include <REGX52.H>
#include "Delay.h"
#include "MatrixLED.h"
unsigned char Animation[]={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFF,0x10,0x10,0x10,0xFF,0x07,0x78,0x90,0x78,0x07,0xFF,0x90,0x90,0x90,0x60,0xFF,
0x90,0x90,0x90,0x60,0x80,0x40,0x3F,0x40,0x80,0x00,0xFF,0x91,0x91,0x91,0x6E,0x81,
0xFF,0x81,0xFF,0x98,0x94,0x92,0x61,0x80,0x80,0xFF,0x80,0x80,0xFF,0x10,0x10,0x10,
0xFF,0xFF,0x81,0x81,0x42,0x3C,0x07,0x78,0x90,0x78,0x07,0x80,0x40,0x3F,0x40,0x80,
};
void main()
{
unsigned char i,Offset=3,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>72) //数组元素数
{
Offset=0;
}
}
}
}
可以得到流动的“HAPPY BIRTHDAY”。
P23-24后续再补