51单片机:硬件原理与软件编程
51单片机概述
一、课程简介
1、硬件设备
51单片机开发板
Win电脑
2、软件设备
Keil5:编写程序代码
STC-ISP:下载程序
有道词典
福昕阅读器
二、开发工具介绍
1、Keil5
keil.com => 下载C51版本 => 使用破解程序
2、STC-ISP
绿色版:直接运行
3、驱动
开发板用usb连接电脑
安装驱动
三、单片机及开发板介绍
1、单片机概述
单片机(MCU):内部继承了电脑常有的硬件功能
单片机任务:信息采集(传播器)=> 处理(CPU)=> 硬件设备的控制(电机、LED)
单片机相当于微小的电脑,成本低、体积小、结构简单、性能低。
2、STC89C52
所述为51单片机(与8051内核相同)
STC公司
8位
512字节 RAM
8K ROM(Flash)
工作频率:12MHz
3、单片机最小系统
- 电源电路
- VCC:正极
- 接地符号:负极
- 电容:稳压
- 晶振电路
- 复位电路
4、开发板介绍
- 单片机:不要插反,中间有缺口,缺口左侧为P10接口
- LED
- 按键:矩阵按键、独立按键
- 红外接收头:接受遥控信号
- AT下载:用于AT芯片下载程序
- 无线模块:2.4Ghz
- USB自动下载
- 时钟芯片
- 复位键:手动复位,相当于断电重开
- 数模转换器(AD/DA)
- 74H595:扩展IO口
- 步进电机:精确控制的电机
- 超声波:测距
- 蜂鸣器:输出信号、音乐
- 138译码器
- 24C02:ROM(单片机的ROM只能存储程序)
- 温度传感器
- 74HC245:驱动器数码管
- 排座:用于连接液晶屏
- 点阵屏:LED 8*8 阵列
LED与独立按键
一、点亮一个LED
1、LED介绍
LED:发光二极管
补:电阻读数
102 => 10 00 = 1k
473 => 47 000
2、Keil的使用
1、新建工程:Project => New Project
Ctrl + Shift + N :新建文件夹
2、选型号:Atmel-AT89C52
3、xxx提示?选否
4、Source Group => 右键 => Add New Item ... => C文件"main.c"
5、设置勾选创建HEX File文件
6、编译
3、单片机控制硬件的原理
CPU通过对寄存器的控制,寄存器通过驱动器、引脚控制硬件设备
4、代码的编写
#include <REGX52.H>
void main() {
while(1){
P2=0xFE; //1111 1110
}
}5、程序的下载
STC-ISP软件
单片器型号:STC89C52
串口号:USB...
打开程序文件:*.hex
下载进单片机
二、LED的闪烁
利用STC-ISP软件生成Delayxxxms()延时函数
#include <REGX52.H>
void main() {
while(1){
P2=0xFE; //1111 1110
Delay500ms();
P2=0xFF; //1111 1111
Delay500ms();
}
}三、 独立按键实现LED亮灭
1、轻触按键
独立按键:按下接通、松开断开
2、代码实现
1、
P2 = 0xFE相当于P2_0 = 02、独立按键对应顺序
- K1:P3_1
- K2:P3_0
- K3:P3_2
- K4:P3_3
#include <REGX52.H>
void main() {
while(1){
if(P3_1 == 0) {
P2_0 = 0;
} else {
P2_0 = 1;
}
}
}四、独立按键控制LED状态
1、按键的抖动
机械开关断开和闭合时会产生抖动
消抖:硬件过滤、软件算法(Delay)
2、代码实现
#include <REGX52.H>
void main() {
while(1){
if(P3_1 == 0) {
Delay(20); // 消除前抖动
while(P3_1 == 0); // 等待松手
Delay(20); // 消除后抖动
P2_0 = ~P2_0;
}
}
}五、独立按键控制LED显示二进制
#include <REGX52.H>
void main() {
unsigned char LEDNum = 0;
while(1){
if(P3_1 == 0) {
Delay(20); // 消除前抖动
while(P3_1 == 0); // 等待松手
Delay(20); // 消除后抖动
LEDNum++;
P2_0 = ~LEDNum;
}
}
}六、独立按键控制LED位移
#include <REGX52.H>
void main() {
P2 = ~0x01;
unsigned char LEDNum = 0;
while(1){
if(P3_1 == 0) {
Delay(20); // 消除前抖动
while(P3_1 == 0); // 等待松手
Delay(20); // 消除后抖动
LEDNum++;
if(LEDNum >= 8) LEDNum = 0;
P2 = ~(0x01 << LEDNum);
}
if(P3_0 == 0) {
Delay(20); // 消除前抖动
while(P3_1 == 0); // 等待松手
Delay(20); // 消除后抖动
if(LEDNum == 0) LEDNum = 7;
else LEDNum--;
P2 = ~(0x01 << LEDNum);
}
}
}矩阵键盘与定时器
一、矩阵键盘
1、矩阵键盘概述
在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式
采用逐行或逐列的“扫描”,就可以读出任何位置按键的状态
2、扫描的概念
数码管扫描(输出扫描):显示第1位→显示第2位→显示第3位→……,然后快速循环这个过程,最终实现所有数码管同时显示的效果
矩阵键盘扫描(输入扫描):读取第1行(列)→读取第2行(列) →读取第3行(列) → ……,然后快速循环这个过程,最终实现所有按键同时检测的效果
扫描的特点:节省I/O口
3、代码实现
#include <REGX52.H>
#include "Delay.h"
/**
* @brief 矩阵键盘读取按键键码
* @param 无
* @retval KeyNumber 按下按键的键码值
如果按键按下不放,程序会停留在此函数,松手的一瞬间,返回按键键码,没有按键按下时,返回0
*/
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;
}二、定时器原理
1、定时器概述
定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成
定时器作用: 用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作。替代长时间的Delay,提高CPU的运行效率和处理速度
2、STC89C52定时器资源
定时器个数:3个(T0、T1、T2)
T0和T1与传统的51单片机兼容,T2是此型号单片机增加的资源
3、定时器框图
每隔"一秒",计数单元的数值就增加一,当计数单元数值增加到"设定的闹钟提醒时间"时,计数单元就会向中断系统发出中断申请
4、定时器的工作模式
模式1:16位定时器/计数器
SYSclk:系统时钟,即晶振周期,本开发板上的晶振为12MHz
12T mod:每1µs计数
5、中断系统概念
中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的。
当中央处理机CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这个紧急事件,处理完以后,再回到原来被中断的地方,继续原来的工作,这样的过程称为中断。实现这种功能的部件称为中断系统,请示CPU中断的请求源称为中断源。微型机的中断系统一般允许多个中断源,当几个中断源同时向CPU请求中断,要求为它服务的时候,这就存在CPU优先响应哪一个中断源请求的问题。通常根据中断源的轻重缓急排队,优先处理最紧急事件的中断请求源,即规定每一个中断源有一个优先级别。CPU总是先响应优先级别最高的中断请求。
当CPU正在处理一个中断源请求的时候(执行相应的中断服务程序),发生了另外一个优先级比它还高的中断源请求。如果CPU能够暂停对原来中断源的服务程序,转而去处理优先级更高的中断请求源,处理完以后,再回到原低级中断服务程序,这样的过程称为中断嵌套。这样的中断系统称为多级中断系统,没有中断嵌套功能的中断系统称为单级中断系统。
- 高优先级中断可以打断低优先级中断
- 中断对外界紧急事件的实时处理能力
- 中断源、中断优先级
6、中断系统流程
7、STC89C52中断资源
中断源个数:8个(外部中断0、定时器0中断、外部中断1、定时器1中断、串口中断、定时器2中断、外部中断2、外部中断3)
中断优先级个数:4个
中断号:
8、定时器和中断系统
为了方便讲解,这里使用的中断系统图是传统51单片机的图
9、定时器相关寄存器
寄存器是连接软硬件的媒介
在单片机中寄存器就是一段特殊的RAM存储器,一方面,寄存器可以存储和读取数据,另一方面,每一个寄存器背后都连接了一根导线,控制着电路的连接方式
寄存器相当于一个复杂机器的“操作按钮”
三、计时器的使用
1、LED闪烁
计数器:0~65535
每隔1µs计数加,总共定时时间65535µs
64535离计数器溢出差值1000,所以计时时间为1ms
#include <REGX52.H>
void Timer0_Init() {
//TMOD = 0x01; //0000 0001 => 影响前四位
TMOD &= 0xF0; //把TMOD低四位清零、高四位保留
TMOD |= 0x01; //把TMOD最低位置1
TF0 = 0;
TR0 = 1;
TH0 = 64535 / 256;
TL0 = 65535 % 256 + 1;
ET0 = 1; //允许中断
EA = 1;
PT0 = 0;
}
void main() {
Timer0_Init();
while (1) {
}
}
void Timer0_Routine() interrupt 1 {
static unsigned int T0Cnt = 0;
TH0 = 64535 / 256;
TL0 = 65535 % 256 + 1;
T0Cnt++;
if(T0Cnt >= 1000) {
T0Cnt = 0;
P2_0 = ~P2_0;
}
}2、LED流水灯
#include <REGX52.H>
#include <INTRINS.H>
void Delay(unsigned int xms) {
unsigned char i, j;
while (xms--) {
i = 2;
j = 239;
do {
while (--j);
} while (--i);
}
}
/**
* @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;
}
/**
* @brief 定时器0初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0Init() {
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
EA = 1;
PT0 = 0;
}
unsigned char KeyNum, LEDMode;
void main() {
P2 = 0xFE;
Timer0Init();
while (1) {
KeyNum = Key(); //获取独立按键键码
if (KeyNum) //如果按键按下
{
if (KeyNum == 1)//如果K1按键按下
{
LEDMode++; //模式切换
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);
}
}3、定时器时钟
main.c
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"
unsigned char Sec = 55, Min = 59, Hour = 23;
void main() {
LCD_Init();
Timer0Init();
LCD_ShowString(1, 1, "Clock:"); //上电显示静态字符串
LCD_ShowString(2, 1, " : :");
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 = 0x18;//设置定时初值
TH0 = 0xFC;//设置定时初值
T0Count++;
if (T0Count >= 1000)//定时器分频,1s
{
T0Count = 0;
Sec++;//1秒到,Sec自增
if (Sec >= 60) {
Sec = 0;//60秒到,Sec清0,Min自增
Min++;
if (Min >= 60) {
Min = 0;//60分钟到,Min清0,Hour自增
Hour++;
if (Hour >= 24) {
Hour = 0;//24小时到,Hour清0
}
}
}
}
}Timer0.c
#include <REGX52.H>
/**
* @brief 定时器0初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0Init(void) {
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
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;
}
}
*/串口与LED点阵屏
一、串口
1、串口的概述
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信。
单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大的扩展了单片机的应用范围,增强了单片机系统的硬件实力。
51单片机内部自带UART(Universal Asynchronous Receiver Transmitter,通用异步收发器),可实现单片机的串口通信。
2、硬件电路
简单双向串口通信有两根通信线(发送端TXD和接收端RXD)
TXD与RXD要交叉连接
当只需单向的数据传输时,可以直接一根通信线
当电平标准不一致时,需要加电平转换芯片
3、电平标准
电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种
- TTL电平:+5V表示1,0V表示0
- RS232电平:-3~-15V表示1,+3~+15V表示0
- RS485电平:两线压差+2~+6V表示1,-2~-6V表示0(差分信号)
4、常见通信接口
5、相关术语
全双工:通信双方可以在同一时刻互相传输数据
半双工:通信双方可以互相传输数据,但必须分时复用一根数据线
单工:通信只能有一方发送到另一方,不能反向传输
异步:通信双方各自约定通信速率
同步:通信双方靠一根时钟线来约定通信速率
总线:连接各个设备的数据传输线路
6、51单片机的UART
STC89C52有1个UART
STC89C52的UART有四种工作模式:
- 模式0:同步移位寄存器
- 模式1:8位UART,波特率可变(常用)
- 模式2:9位UART,波特率固定
- 模式3:9位UART,波特率可变
7、串口参数及时序图
波特率:串口通信的速率(发送和接收各数据位的间隔时间)
检验位:用于数据验证
停止位:用于数据帧间隔
8、串口模式图
SBUF:串口数据缓存寄存器,物理上是两个独立的寄存器,但占用相同的地址。写操作时,写入的是发送寄存器,读操作时,读出的是接收寄存器
9、串口相关寄存器
数据显示模式
- HEX模式/十六进制模式/二进制模式:以原始数据的形式显示
- 文本模式/字符模式:以原始数据编码后的形式显示
二、串口的使用
uart.c
#include <REGX52.H>
/**
* @brief 串口初始化,4800bps@12.000MHz
* @param 无
* @retval 无
*/
void UART_Init() {
SCON=0x40;
PCON |= 0x80;
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xF3; //设定定时初值
TH1 = 0xF3; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
}
/**
* @brief 串口发送一个字节数据
* @param Byte 要发送的一个字节数据
* @retval 无
*/
void UART_SendByte(unsigned char Byte) {
SBUF=Byte;
while(TI==0);
TI=0;
}main.c
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"
unsigned char Sec;
void main() {
UART_Init(); //串口初始化
while(1) {
UART_SendByte(Sec); //串口发送一个字节
Sec++; //Sec自增
Delay(1000); //延时1秒
}
}三、LED点阵屏
1、LED点阵屏介绍
LED点阵屏由若干个独立的LED组成,LED以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等
LED点阵屏与数码管一样,有共阴和共阳两种接法,不同的接法对应的电路结构不同
LED点阵屏需要进行逐行或逐列扫描,才能使所有LED同时显示
2、74HC595
74HC595是串行输入并行输出的移位寄存器,可用3根线输入串行数据,8根线输出并行数据,多片级联后,可输出16位、24位、32位等,常用于IO口扩展。
3、开发板引脚对应关系
4、C的sfr、sbit
sfr(special function register):特殊功能寄存器声明 例:sfr P0 = 0x80; 声明P0口寄存器,物理地址为0x80
sbit(special bit):特殊位声明 例:sbit P0_1 = 0x81; 或 sbit P0_1 = P0^1; 声明P0寄存器的第1位
可位寻址/不可位寻址:
在单片机系统中,操作任意寄存器或者某一位的数据时,必须给出其物理地址,又因为一个寄存器里有8位,所以位的数量是寄存器数量的8倍,单片机无法对所有位进行编码,故每8个寄存器中,只有一个是可以位寻址的。
对不可位寻址的寄存器,若要只操作其中一位而不影响其它位时,可用“&=”、“|=”、“^=”的方法进行位操作
四、LED点阵屏的使用
1、显示笑脸
#include <REGX52.H>
#include "Delay.h"
sbit RCK=P3^5; //RCLK
sbit SCK=P3^6; //SRCLK
sbit SER=P3^4; //SER
#define MATRIX_LED_PORT P0
/**
* @brief 74HC595写入一个字节
* @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);
MATRIX_LED_PORT=~(0x80>>Column);
Delay(1);
MATRIX_LED_PORT=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);
}
}2、显示动画
MatrixLED.c
#include <REGX52.H>
#include "Delay.h"
sbit RCK=P3^5; //RCLK
sbit SCK=P3^6; //SRCLK
sbit SER=P3^4; //SER
#define MATRIX_LED_PORT P0
/**
* @brief 74HC595写入一个字节
* @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 点阵屏初始化
* @param 无
* @retval 无
*/
void MatrixLED_Init()
{
SCK=0;
RCK=0;
}
/**
* @brief LED点阵屏显示一列数据
* @param Column 要选择的列,范围:0~7,0在最左边
* @param Data 选择列显示的数据,高位在上,1为亮,0为灭
* @retval 无
*/
void MatrixLED_ShowColumn(unsigned char Column,Data)
{
_74HC595_WriteByte(Data);
MATRIX_LED_PORT=~(0x80>>Column);
Delay(1);
MATRIX_LED_PORT=0xFF;
}main.c
#include <REGX52.H>
#include "Delay.h"
#include "MatrixLED.h"
//动画数据
unsigned char code Animation[]={
0x3C,0x42,0xA9,0x85,0x85,0xA9,0x42,0x3C,
0x3C,0x42,0xA1,0x85,0x85,0xA1,0x42,0x3C,
0x3C,0x42,0xA5,0x89,0x89,0xA5,0x42,0x3C,
};
void main()
{
unsigned char i,Offset=0,Count=0;
MatrixLED_Init();
while(1)
{
for(i=0;i<8;i++) //循环8次,显示8列数据
{
MatrixLED_ShowColumn(i,Animation[i+Offset]);
}
Count++; //计次延时
if(Count>15)
{
Count=0;
Offset+=8; //偏移+8,切换下一帧画面
if(Offset>16)
{
Offset=0;
}
}
}
}数码管与LCD1602
一、静态数码管
1、数码管介绍
LED数码管:有多个发光二极管封装在一起的"8"字型的显示器
2、数码管引脚定义
分为:共阴极连接(多)、共阳极连接
图片仅供参考,以图纸为准
公共端:接地 => 供电
位选端:用来选择哪个需要点亮 => 段码
3、八位一体数码管
- 控制P22、P23、P24控制138译码器
- 控制P00~P07,指定段码数据
11个引脚(8+3)控制8个数码管 => 同一时刻只有一个被选中
公共端(3) => 138译码器(3 -> 8) => 控制点亮哪个数码管
位选端(8) => 控制数码管哪个二极管被点亮
74HC245(双向数据缓冲器):控制数据传输方向,将控制信号变为供电信号。
准双向口输出配置:"弱上拉、强上拉"
准双向口输出类型可用作输出和输入功能而不需重新配置口线输出状态。这是因为当口线输出为1时驱动能力很弱,允许外部装置将其拉低。当引脚输出为低时,它的驱动能力很强,可吸收相当大的电流。准双向口有3个上拉品体管适应不同的需要。
4、138译码器
G1、G2A、G2B:使能端,G2A、G2B低电平有效,G1高电平有效
A、B、C:译码输入端,高电平有效
Y0-Y7:译码输出端,低电平有效
5、代码实现
#include <REGX52.H>
void main() {
while(1) {
P0=0x3F;
}
}二、动态数码管
1、动态数码管原理
利用余晖效应快速扫描
2、数码管的消影
理想:「位选 段选」 「位选 段选」 「位选 段选」 ...
现实:位选 「段选 位选」 段选 ... => 产生重影
解决方法:Delay() + 段选清零
3、数码管的驱动方式
1、单片机直接扫描:硬件设备简单,但会耗费大量的单片机CPU时间
2、专用驱动芯片(TM1640):内部自带显存、扫描电路,单片机只需告诉它显示什么即可
4、代码实现
#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; //段码清0,消影
}三、模块化编程
1、模块化编程简介
传统方式编程:所有的函数均放在main.c里,若使用的模块比较名。则一个文件内会有很多的代码,不利于代码的组织和管理,而且很影响编程者的思路
模块化编程:把各个模块的代码放在不同的.c文件里,在h文件里提供外部可调用函数的声明,其它.c文件想使用其中的代码时,只需要#include "xxx.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等
2、模块化编程框图
3、C预编译
C语言的预编译以#开头,作用是在真正的编译开始之前,对代码做一些处理(预编译)
| 预编译 | 含义 |
|---|---|
#include <REGX52 .H> | 把REGX52.H文件的内容搬到此处 |
#define PI 3.14 | 定义PI,将I替换为3.14 |
#define ABC | 定义ABC |
#ifndef __XXX_H__ | 如果没有定义__XXX_H__ |
#endif | 与#ifndef,#if匹配,组成"括号" |
4、注意事项
- c文件:函数、变量的定义
- h文件:可被外部调用的函数、变量的声明
- 任何自定义的变量、函数在调用前必须有定义或声明(同一个.c),使用到的自定义函数的.c文件必须添加到工程参与编译
- 使用到的.h文件必须要放在编译器可寻找到的地方 (工程文件夹根目录、安装目录、自定义)
四、LCD1602调试工具
1、LCD1602概述
使用LCD1602液晶屏作为调试窗口,提供类似printf函数的功能,可实时观察单片机内部数据的变换情况,便于调试和演示。
提供的LCD1602代码属于模块化的代码,使用者只需要知道所提供函数的作用和使用方法就可以很容易的使用LCD1602
LCD1602会与LED和数码管冲突,属于正常行为
2、LCD1602使用
导入<LCD1602.h>头文件,调用api接口即可
#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>
//引脚配置:
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');
}
}DS1302时钟
一、DS1302时钟
1、DS1302介绍
DS1302是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能
RTC(Real Time Clock):实时时钟,是一种集成电路,通常称为时钟芯片
2、引脚定义
| 引脚名 | 作用 | 引脚名 | 作用 |
|---|---|---|---|
| VCC2 | 主电源 | CE | 芯片使能 |
| VCC1 | 备用电池 | IO | 数据输入/输出 |
| GND | 电源地 | SCLK | 串行时钟 |
| X1、X2 | 32.768KHz晶振 |
3、应用电路
4、内部结构框图
5、寄存器
6、时序
7、BCD码
BCD码(Binary Coded Decimal):用4位二进制数来表示1位十进制数
0001 0011表示13,1000 0101表示85,0001 1010不合法
在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法
BCD码转十进制:
十进制转BCD码:
二、实时时钟
ds1302.c
#include <REGX52.H>
//引脚定义
sbit DS1302_SCLK=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
//时间数组,索引0~6分别为年、月、日、时、分、秒、星期
unsigned char DS1302_Time[]={19,11,16,12,59,55,6};
/**
* @brief DS1302初始化
* @param 无
* @retval 无
*/
void DS1302_Init(void)
{
DS1302_CE=0;
DS1302_SCLK=0;
}
/**
* @brief DS1302写一个字节
* @param Command 命令字/地址
* @param Data 要写入的数据
* @retval 无
*/
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;
}
/**
* @brief DS1302读一个字节
* @param Command 命令字/地址
* @retval 读出的数据
*/
unsigned char DS1302_ReadByte(unsigned char Command)
{
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;
}
for(i=0;i<8;i++)
{
DS1302_SCLK=1;
DS1302_SCLK=0;
if(DS1302_IO){Data|=(0x01<<i);}
}
DS1302_CE=0;
DS1302_IO=0; //读取后将IO设置为0,否则读出的数据会出错
return Data;
}
/**
* @brief DS1302设置时间,调用之后,DS1302_Time数组的数字会被设置到DS1302中
* @param 无
* @retval 无
*/
void DS1302_SetTime(void)
{
DS1302_WriteByte(DS1302_WP,0x00);
DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);//十进制转BCD码后写入
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 DS1302读取时间,调用之后,DS1302中的数据会被读取到DS1302_Time数组中
* @param 无
* @retval 无
*/
void DS1302_ReadTime(void)
{
unsigned char Temp;
Temp=DS1302_ReadByte(DS1302_YEAR);
DS1302_Time[0]=Temp/16*10+Temp%16;//BCD码转十进制后读取
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;
}main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
void main()
{
LCD_Init();
DS1302_Init();
LCD_ShowString(1,1," - - ");//静态字符初始化显示
LCD_ShowString(2,1," : : ");
DS1302_SetTime();//设置时间
while(1) {
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);//显示秒
}
}蜂鸣器
一、蜂鸣器
1、蜂鸣器介绍
鸣器是一种将电信号转换为声音信号的器件,常用来产生设备的按键音、报警音等提示信号
- 有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定
- 无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音
2、驱动电路
1)三极管驱动
2)集成电路驱动
3、ULN2003
二、乐理知识
1、键盘与音符对照
2、音符与频率对照
3、简谱
#升音
b降音
-增加一拍
_减少半拍
·增加半拍
三、蜂鸣器案例
1、按键提示音
main.c
#include <REGX52.H>
#include "Key.h"
#include "Nixie.h"
#include "Buzzer.h"
unsigned char KeyNum;
void main()
{
Nixie(1,0);
while(1)
{
KeyNum=Key();
if(KeyNum)
{
Buzzer_Time(100);
Nixie(1,KeyNum);
}
}
}Buzzer.c
#include <REGX52.H>
#include <INTRINS.H>
//蜂鸣器端口:
sbit Buzzer=P1^5;
/**
* @brief 蜂鸣器私有延时函数,延时500us
* @param 无
* @retval 无
*/
void Buzzer_Delay500us() //@12.000MHz
{
unsigned char i;
_nop_();
i = 247;
while (--i);
}
/**
* @brief 蜂鸣器发声
* @param ms 发声的时长,范围:0~32767
* @retval 无
*/
void Buzzer_Time(unsigned int ms) {
unsigned int i;
for(i=0;i<ms*2;i++) {
Buzzer=!Buzzer;
Buzzer_Delay500us();
}
}2、小星星
太多了,哭哭~ 放一下老师的源码咯
#include <REGX52.H>
#include "Delay.h"
#include "Timer0.h"
//蜂鸣器端口定义
sbit Buzzer=P1^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口
}
}PID控制算法
一、PID概要
1、引入
流量稳定、调整流量
2、适用系统
二阶以内的线性系统
线性系统:满足齐次性和叠加性
齐次性:
则 叠加性:
则
一阶系统:
二阶系统:
3、宏观意义
简单、无需精确建模
二、PID基本知识
1、控制系统概述
1)开环控制系统
一般开环控制系统
前馈控制系统
2)闭环控制系统
单闭环
双闭环
3)复合控制系统
前馈-反馈复合控制系统
2、连续与离散信号
1)图形表示
2)信号算式表示
三、PID公式
1、抽象解释
2、形象解释
1)单P控制
2)PID控制
3、其余相关知识
1)积分限幅
对于积分的最大值进行限制
2)积分分离
对于误差的范围进行限制,超出范围不进行积分
3)微分先行
消除期望高度突变的影响




















































