一、STC89C52单片机
STC89C52是一款常见的51系列单片机。
使用Keil进行开发时,通常通过:
#include <reg52.h>
来使用单片机内部的寄存器和IO端口。
单片机程序和Linux C程序最大的区别之一是:
Linux程序主要操作操作系统提供的资源,而单片机程序主要直接控制硬件。
二、GPIO
1. 什么是GPIO
GPIO就是通用输入输出接口(General Purpose Input/Output)。
STC89C52主要有4组IO口:
P0
P1
P2
P3
每组有8个引脚:
P0.0 ~ P0.7
P1.0 ~ P1.7
P2.0 ~ P2.7
P3.0 ~ P3.7
因此可以通过操作这些IO口控制LED、数码管、按键、蜂鸣器等外围设备。
2. 使用寄存器控制IO
例如:
P2 = 0xFF;
表示给P2口写入:
1111 1111
也可以单独控制某一位:
P2^0 = 0;
表示控制P2.0。
三、LED控制
1. LED的基本原理
LED本质上是发光二极管。
单片机通过GPIO输出高低电平控制LED。
在目前使用的开发板中,LED通常采用低电平有效:
P2.x = 0 → LED亮
P2.x = 1 → LED灭
因此不能简单地认为:
输出1 = LED亮
具体还要看开发板的硬件连接方式。
2. LED初始化
为了让LED上电后默认关闭,可以:
void led_init(void)
{
P2 = 0xFF;
}
因为当前实验板是低电平点亮,所以:
P2 = 0xFF
↓
1111 1111
↓
所有LED关闭
3. 控制LED亮灭
可以封装成函数:
void led_on(unsigned char on)
{
P2 = ~on;
}
例如:
led_on(0x01);
表示点亮第1个LED。
如果:
led_on(0x05);
二进制为:
0000 0101
取反后输出:
1111 1010
对应相应的LED亮起。
四、LED流水灯
LED最常见的练习就是流水灯。
例如:
LED1 → LED2 → LED3 → LED4
→ LED5 → LED6 → LED7 → LED8
程序的核心就是不断改变P2的输出值。
例如:
P2 = 0xFE;
P2 = 0xFD;
P2 = 0xFB;
P2 = 0xF7;
由于LED采用低电平有效,因此可以实现依次点亮。
1. 延时函数
LED流水灯需要加入延时,否则变化速度太快,人眼无法观察。
例如:
void delay(unsigned int t)
{
while(t--);
}
使用:
delay(0x3FFF);
让程序暂停一段时间。
五、数码管
1. 什么是数码管
数码管是一种用于显示数字的器件。
常见的数码管由:
a
b
c
d
e
f
g
dp
组成。
其中:
-
a~g:组成数字 -
dp:小数点
例如显示数字:
a
---
f| |b
-g-
e| |c
---
d
六、数码管段选
显示不同数字,本质上就是控制a~g这些段的亮灭。
例如常见的段码表:
unsigned char code seg_table[] =
{
0x3F, // 0
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F // 9
};
例如:
P0 = 0x06;
就可以让数码管显示:
1
七、数码管的位选和段选
如果开发板上有多个数码管,就需要解决两个问题:
段选
决定:
当前显示什么数字。
例如:
seg_select(1);
可以理解为选择数字1对应的段码。
位选
决定:
哪一个数码管显示。
例如:
bit_select(1);
表示选择第1位。
因此可以记成:
位选 → 选择第几个数码管
段选 → 选择显示什么数字
八、数码管动态显示
多个数码管通常不是同时真正点亮,而是采用动态扫描。
例如有4位数码管,要显示:
1 2 3 4
程序快速执行:
选择第1位 → 显示1
↓
选择第2位 → 显示2
↓
选择第3位 → 显示3
↓
选择第4位 → 显示4
↓
重新开始
扫描速度足够快时,人眼看到的就是:
1234
1. 动态扫描的核心
可以封装:
void display_num(unsigned int num)
{
// 依次进行位选和段选
}
例如:
display_num(1234);
就可以让数码管显示:
1234
九、数码管模块化编程
之前练习中使用了:
digitaltube.h
digitaltube.c
delay.h
delay.c
main.c
这种方式比较适合单片机项目。
例如:
digitaltube.h
#ifndef __DIGITALTUBE_H__
#define __DIGITALTUBE_H__
void digitaltube_init(void);
void bit_select(unsigned char b);
void seg_select(unsigned char s);
void display_num(unsigned int num);
#endif
主函数中:
#include <reg52.h>
#include "digitaltube.h"
#include "delay.h"
void main(void)
{
unsigned int num = 0;
digitaltube_init();
while(1)
{
display_num(num++);
delay(0x3FFF);
}
}
这样主函数只负责:
调用功能。
具体的数码管控制代码放到对应的 .c 文件中。
十、定时器
1. 为什么需要定时器
前面的延时函数:
while(t--);
属于软件延时。
这种方法简单,但是CPU一直在执行循环。
定时器则可以利用单片机内部的硬件计时功能。
常见用途:
-
定时
-
延时
-
LED闪烁
-
数码管扫描
-
产生周期信号
-
串口波特率产生
十一、STC89C52定时器
STC89C52中常用:
Timer0
Timer1
也就是:
定时器0
定时器1
定时器工作时会不断计数。
当计数达到溢出条件后,可以产生中断。
基本过程:
设置定时器
↓
设置初值
↓
启动定时器
↓
不断计数
↓
溢出
↓
产生中断
↓
执行中断函数
十二、定时器相关寄存器
学习定时器时经常遇到:
TMOD
TH0
TL0
TH1
TL1
TR0
TR1
TF0
TF1
其中:
TMOD
用于设置定时器的工作模式。
TH0、TL0
定时器0的高8位和低8位。
TH1、TL1
定时器1的高8位和低8位。
TR0
控制定时器0启动。
TR0 = 1;
表示启动定时器0。
TR1
控制定时器1启动。
TF0
定时器0溢出标志。
TF1
定时器1溢出标志。
十三、16位定时器
定时器0可以工作在16位模式。
此时:
TH0 + TL0
共同组成16位计数器。
可以理解为:
TH0 TL0
高8位 低8位
↓ ↓
┌────────┬────────┐
│ 8 bit │ 8 bit │
└────────┴────────┘
16 bit
例如之前练习中使用:
TH0 = 0x4C;
TL0 = 0x00;
设置定时器初值。
十四、定时器中断
如果只使用定时器而不使用中断,可以一直查询:
while(TF0 == 0);
但是使用中断以后,程序可以:
主程序
↓
正常运行
↓
定时器溢出
↓
CPU暂停当前程序
↓
执行定时器中断函数
↓
中断结束
↓
返回主程序继续运行
这就是中断机制。
十五、什么是中断
中断可以理解为:
CPU正在执行当前程序时,突然有一个事件需要处理,于是CPU暂时停止当前程序,去处理这个事件,处理完成后再回来继续执行。
例如:
main()
↓
LED正常运行
↓
按键按下
↓
产生中断
↓
执行中断函数
↓
返回main()
十六、外部中断
STC89C52常见的外部中断包括:
INT0
INT1
其中:
INT0 → P3.2
INT1 → P3.3
例如使用INT0:
按键
↓
INT0
↓
CPU产生中断
↓
执行中断函数
十七、中断相关寄存器
学习中断时需要重点掌握:
IE
TCON
IE------中断允许寄存器
IE用于控制中断是否开启。
其中:
EA
是总中断允许位。
可以理解为:
EA = 0 → 关闭总中断
EA = 1 → 开启总中断
所以使用中断时通常需要:
EA = 1;
十八、外部中断初始化
例如使用INT0,可以进行相应初始化:
void eint0_init(void)
{
EA = 1; // 开启总中断
EX0 = 1; // 开启外部中断0
IT0 = 1; // 设置INT0触发方式
}
之后等待外部中断发生。
十九、中断服务函数
当INT0产生中断后,需要执行中断服务函数。
Keil C51中可以使用:
void eint0_isr(void) interrupt 0
{
// 中断处理代码
}
这里:
interrupt 0
表示这个函数对应外部中断0。
例如:
unsigned int count = 0;
void eint0_isr(void) interrupt 0
{
count++;
}
每产生一次INT0中断:
count = count + 1
二十、数码管显示中断计数
可以把前面学习的内容结合起来。
例如:
按键
↓
INT0中断
↓
count++
↓
数码管显示count
主程序:
void main(void)
{
unsigned int count = 0;
digitaltube_init();
eint0_init();
while(1)
{
display_num(count);
}
}
中断函数:
void eint0_isr(void) interrupt 0
{
count++;
}
这样每按一次按键:
0
↓
1
↓
2
↓
3
↓
4
...
数码管上的数字也会随之变化。
二十一、定时器与中断的区别
这两个概念很容易混淆。
定时器
负责:
计时、计数。
中断
负责:
通知CPU立即处理某个事件。
两者可以结合:
定时器
↓
计数
↓
溢出
↓
产生中断
↓
执行中断函数
所以:
定时器不等于中断,但是定时器可以通过溢出产生中断。
二十二、GPIO、LED、数码管、定时器、中断之间的关系
把本篇内容串起来:
STC89C52
│
┌───────────┼───────────┐
↓ ↓ ↓
GPIO 定时器 外部中断
│ │ │
┌──┴──┐ │ 按键
↓ ↓ ↓ │
LED 数码管 定时溢出 ↓
│ │ │ 中断函数
│ │ ↓ │
└─────┴──────→ 显示 ←──────┘
通过这些模块,就可以完成比较完整的单片机控制程序。
二十三、本篇重点总结
1. GPIO
P0
P1
P2
P3
用于控制外部硬件。
2. LED
重点理解:
低电平有效
0 → 亮
1 → 灭
通过P2可以控制LED。
3. 数码管
记住两个概念:
位选 → 选择第几位
段选 → 显示什么数字
多个数码管一般采用动态扫描。
4. 定时器
重点:
Timer0
Timer1
TMOD
TH0
TL0
TH1
TL1
TR0
TR1
作用:
实现硬件计时。
5. 中断
重点:
IE
TCON
EA
EX0
IT0
基本流程:
开启中断
↓
等待事件
↓
产生中断
↓
执行中断服务函数
↓
返回主程序
GPIO负责控制硬件,LED和数码管是GPIO的具体应用;定时器负责计时,中断负责及时响应事件。