51单片机原理与应用
51单片机介绍
准备工作
首先需要安装几个软件
开始使用安装 Keil 集成开发环境
Keil 是用来写 C 语言程序的, 它还可以将程序编译成单片机可以识别的指令集.
STC-ISP 安装
STC-ISP 可以将编译好的程序下载到单片机中, 以让程序能在单片机运行.
USB驱动
安装了串口驱动后 STC-ISP 才能识别单片机.
由于我已经安装的时候没有截图写教程, 所以先就不写详细的安装步骤了(
51 单片机简介
单片机(MicrocontrollerUnit, MCU)算一种袖珍版计算机, 一个芯片就能构成完整的计算机系统.
单片机成本低, 体积小, 结构简单. 在学习使用单片机的过程中, 可以快速了解计算机原理与其结构.
Intel 公司推出了 8051 单片机内核, 因此这种类似于 8051 内核的单片机, 我们都统称为 51 单片机.
51 单片机的
位数: 8位.
RAM: 512字节.
ROM: 8K (Flash).
工作频率: 12Mhz.
RAM: 随机存取存储器, 用于存储程序和数据(会丢失数据, 相当于工作台).
ROM: 只读存储器, 用于存储程序(长期存储, 相当于仓库).
单片机内部结构图
乱七八糟的我也不爱看
LED 灯控制
新建一个 Keil 工程
为了方便导入和管理代码:
首先, 在电脑中新建一个文件夹, 作为所有 Keil 工程的存放位置.
然后, 打开 Keil 软件, 新建第一个工程文件夹.
如果不这么做, 文件就会散成一团(
接着, 如图所示.
然后在跳出的对话框中选中 Atmel 下的 AT89C52.
会有一个对话框询问是否自动创建开始文件.
推荐选 否
,选了是影响也不大.
接着, 我们要在项目中添加一个 .c 的文件, 作为程序的入口.
由于这个版本的 keil 不能直接新建, 所以我们得先在 系统的文件管理器 中新建文件.
在文件管理器中找到项目文件夹, 右键新建一个文件, 命名为 main.c
.
然后在 keil 中将这个 main.c
文件添加到工程中.
这样, 工程就新建完成了!
接下来就可以开始编写代码了.
为了能在程序编译好后能直接得到 .hex 文件, 我们还得在 Keil 中设置一下编译选项.
在 Output
选项卡中, 勾选 Create HEX file
.
LED 灯控制
LED 灯是指发光二极管, 它只允许电流从一个方向通过.
当我们把电池的正极接到P型材料这一端, 负极接到N型材料这一端时, 电流就可以顺利通过LED.
因为电子和空穴可以分别从N型和P型材料流向对方, 然后在中间的PN结区域相遇并复合, 发出光来.
反之, 电子和空穴都被推向相反的方向, 就无法发光了.
在开发板中, LED模块连接如图所示:
如图所示, LED 模块有八个 LED 灯. 右边接在 VCC ,也就是电源的正极, 左边与单片机的 P2(P2.0~P2.7) 引脚相连.
在 LED 与 电源正极 之间 有两个 1K 的电阻, 起到限流的作用, 防止 LED 灯发光过强而烧毁.
如何点亮 LED 灯?
为了让 LED 灯点亮, 需要在 LED 的阳极和阴极之间形成电流的通路.
已知 LED 灯的一侧接通的是电源正极, 我们只要能控制另一端引脚的电压即可.
1 | void main() { |
但程序并不认识 P2
这个寄存器, 这时只需要导入头文件即可.
1 |
|
在这个头文件中就有 P2
这个寄存器的定义.
接着, 将写好的程序编译成 .hex 文件, 下载到单片机中.
LED 流水灯
让 8 个 LED 灯依次点亮, 然后再依次熄灭.
1 |
|
单独操作位寄存器
在头文件 regx52.h
中, 我们可以看到寄存器的定义.
1 | sbit P2_0 = 0xA0; |
我们可以直接操作这些寄存器.
注意在
reg52.h
中并没有定义, 需要自己声明.
独立按键
按钮在单片机中的接线如图所示:
按钮一端接地(电源负极), 另一端接接在单片机的 IO 口上. 当单片机接电时, IO 口为高电平, 按钮按下, 电路导通, IO 口变为低电平.
即 按钮按下, IO 口为 ‘0’.
按钮松开, IO 口为 ‘1’.
按键的抖动
对于机械开关,当机械触点断开, 闭合时,由于机械触点的弹性作用, 一个开关在闭合时不会马上稳定地接通, 在断开时也不会立马完全断开, 所以在开关闭合及断开的瞬间会伴随一连串的抖动.
最方便的解决方法就是检测到按下时让程序延时一段时间即可.
用按钮控制 LED 灯亮灭:
1 |
|
定时器/计数器
在之前, 我们使用的是 Delay()
函数来延时, 但这种方式很不精确, 而且会占用主程序, 无法在 Delay()
函数执行时执行其他任务.
定时器就能用来实现精确的延时, 其电路的连接和运转均在单片机内部完成.
在 STC89C52 中, 有 T0, T1, T2 这三个定时器.
T0 和 T1 与传统的 51单片机 兼容, T2 则是此型号单片机增加的资源.
除了用作定时, 它也可用作计数器.
工作原理
定时器在单片机内部就像一个小闹钟. 根据时钟的输出信号, 每隔 一个时间段
, 计数单元的数值就会增加一.
当计数单元数值增加到 设定的提醒时间
时, 计数单元就会向 中断系统
发出中断申请, 使程序跳转到中断服务函数中执行.
中断系统
类似于定时器
, 也是单片机内部的资源.
STC89C52 的 定时器/计数器
T0, T1 都有如下几种工作模式:
- 模式0: 13 位定时器/计数器
- 模式1: 16 位定时器/计数器 (常用)
- 模式2: 8 位自动重装模式
- 模式3: 两个 8 位计数器
工作模式 1 的框图:
计数器
如图, 在计数器里有
- TL(Timer Low) : 低字节
- TH(Timer High): 高字节
两个字节, 它们总共可以存储 65536 个不同的数值.
时钟模块
稳定地给 计数器
脉冲. 每来一次脉冲, 计数器就会增加 1.
直到 计数数到 65536 , 计数器溢出, 置一个标志位, 并向 中断系统
发出中断申请.
定时器时钟
系统时钟, 即晶振周期.
中断系统
中断系统是为使 CPU 具有对外界紧急事件的实时处理能力而设置的.
请示 CPU 中断的请求称为中断源, 中断源的轻重缓急称之为中断优先级.
高优先级的中断可以打断低优先级的中断.
不同型号的单片机拥有不同的中断资源.
在 STC89C52 中, 中断源共有 8 个.
不使用定时器依然可以实现延时, 可以通过查询 TF0 标志位.
就像你一直需要检查你的钟一样.
中断号:
中断源 | 中断服务函数 | 中断号 |
---|---|---|
外部中断0 | Int0_Routine(void) | interrupt 0; |
定时器0 | Timer0_Routine(void) | interrupt 1; |
外部中断1 | Int1_Routine(void) | interrupt 2; |
定时器1 | Timer1_Routine(void) | interrupt 3; |
串口中断 | UART_Routine(void) | interrupt 4; |
定时器2 | Timer2_Routine(void) | interrupt 5; |
外部中断2 | Int2_Routine(void) | interrupt 6; |
外部中断3 | Int3_Routine(void) | interrupt 7; |
定时器相关的寄存器
寄存器是连接软硬件的桥梁, 相当于一个复杂机器的操作按钮.
在单片机中寄存器就是一段特殊的 RAM 存储器.
一方面, 寄存器可以存储和读取数据,
另一方面, 每一个寄存器背后都连接了一根导线, 控制着电路的连接方式.
TCON 定时器控制寄存器
TCON(Timer Control) 为定时器/计数器 T0, T1 的控制寄存器, 同时也锁存 T0, T1 溢出中断源和外部请求中断源等.
TF1, IE1, IT1等, 控制定时器 T1
;IE0, TF0, IT0等 控制定时器 T0
.
TCON 可位寻址, 因此能单独操作 TCON 寄存器的各个位.
TCON 格式如下:
SFR name | Address | bit | B7 | B6 |
---|---|---|---|---|
TCON | 88H | name | TF1 | TR1 |
B5 | B4 | B3 | B2 | B1 | B0 |
---|---|---|---|---|---|
TF0 | TR0 | IE1 | IT1 | IE0 | IT0 |
- TF(Timer Flag):
定时器/计数器
溢出标志. 当定时器
的最高位高位产生溢出时, 会由硬件将 TF 置为 1 ,然后向 CPU 请求中断. 直到 CPU 响应 此中断后, 才由硬件将 TF1 复位为 0. - TR(Timer Run):
定时器/计数器
的运行控制位, 该位由软件置位和清零. 0 时停止计数, 1 时开始计数. - IE(Interrupt Enable):
定时器/计数器
的 中断使能位. IE1 = 1 时外部中断向 CPU 请求中断, 当 CPU 响应该中断时, 由硬件清 0. - IT(Interrupt Type):
定时器/计数器
的 中断类型位. 0 时为边沿触发, 1 时为电平触发.
TMOD 定时器工作模式寄存器
定时和计数功能由特殊功能寄存器 TMOD(Timer Mode) , 控制着 C/~T 进行选择.
TMOD 不可位寻址, 只能整体赋值.
7 | 6 | 5 | 4 |
---|---|---|---|
GATE | C/~T | M1 | M0 |
3 | 2 | 1 | 0 |
---|---|---|---|
GATE | C/~T | M1 | M0 |
其中, 03 是定时器0的工作模式, 47 是定时器1的工作模式.
可以看出, 两个定时器各有四种操作模式, 分别是:
位 | 符号 | 功能 |
---|---|---|
TMOD.7/ TMOD.3/ |
GATE | 控制定时器 T1, 当 GATE=1 , 需 INT1脚 为高 且 TR1=1 时, 才可打开 定时器T1. 控制定时器 T0, 当 GATE=1 , 需 INT0脚 为高 且 TR0=1 时, 才可打开 定时器T0. |
TMOD.6/ TMOD.2/ |
C/~T | 控制模式为 定时(= 0) 还是 计数(= 1). |
TMOD.5 & TMOD.4 | M1 & M0 | 控制定时器 T1 的工作模式. |
TMOD.1 & TMOD.0 | M1 & M0 | 控制定时器 T0 的工作模式. |
M1, M0 共两位, 能用来选择定时器的工作模式.
具体的工作模式如下:
M1 | M0 | 工作模式 | 说明 |
---|---|---|---|
0 | 0 | 模式0: 13位定时器/计数器 |
兼容 8084 定时模式, TL只用低 5 位参与分频, TH1 整个 8 位全用. |
0 | 1 | 模式1: 16位定时器/计数器 |
TL,TH 整个 16 位全用. |
1 | 0 | 模式2: 8位自动重装模式 |
当溢出时将 TH1 存放的值自动装入 TL1. |
1 | 1 | 模式3: 无效 |
停止计数. |
使用定时器实现 LED 闪烁
接下来实践一下, 用定时器实现每隔 一秒 闪烁一次 LED 灯.
已知 计时器最大只能为 65535 ,我们先让计时器计数 1000 次时就溢出, 也就是先计 1微秒.
将计时器的初值设置为 64535 即可.
然后将这个 1微秒 的计时器重复 1000 次, 就是一个一秒的计时器了.
1 |
|
TMOD 缺陷
当需要使用两个定时器时, 又由于 TMOD 只能整体赋值, 所以当给其中一个定时器赋值时, 会对另一个定时器造成影响.
因此, 我们这样修改代码:
1 | // 旧代码: TMOD = 0x01; 0000 0001, 会影响 T1 的工作模式. |
在数字电路中, 任何数 & 1,都等于自身, 任何树 & 0, 都等于 0.
利用这个特征, 我们可以只操作需要赋值的定时器.
让其只操作需要赋值的定时器即可.
在 STC-ISP 软件, 可以很方便地配置定时器:
- 89C52 是没有 16位自动重载的. 只有 16位 或 8位自动重载.
- 定时器时钟选中 12T
- 生成的代码没有配置中断系统, 记得加上:
- 生成的代码针对新系列单片机, 会有
AUXR
寄存器, 但是我们在学习的单片机没有, 因此记得删除.
1 | EA = 1; // 打开总中断 |
数码管
LED 数码管是一种简单, 廉价的显示器, 是由多个发光二极管封装在一起组成 “8” 自型的器件.
LED 连接方式有两种
- 共阴极
- 共阳极
也就只是一种连接方式而已, 了解即可.
想要使单个 数码管 显示数字, 根据其接线口, 可以很轻易推出显示数字的值.
例如想要显示数字 1, 则将 b, c 赋值为1.
赋值时注意 高位是从 DP 开始. 例如 1 就该赋值为 0000 0110.
其他的数字同理:
显示数字 | 二进制 | 十六进制 |
---|---|---|
0 | 0011 1111 | 0x3F |
1 | 0000 0110 | 0x06 |
2 | 0101 1011 | 0x5B |
3 | 0100 1111 | 0x4F |
4 | 0110 0110 | 0x66 |
5 | 0110 1101 | 0x6D |
6 | 0111 1101 | 0x7D |
7 | 0000 0111 | 0x07 |
8 | 0111 1111 | 0x7F |
9 | 0110 1111 | 0x6F |
A | 0111 1011 | 0x77 |
B | 0111 1110 | 0x7C |
C | 0011 1001 | 0x39 |
D | 0101 1110 | 0x5E |
E | 0111 1001 | 0x79 |
F | 0111 1111 | 0x71 |
空 | 1111 1111 | 0x00 |
四位一体数码管在单片机上接线如图所示:
如图, 数码管的一端连接着 74HC254 , 能进行数据缓冲.
由于高电平的驱动能力有限, 因此信号传输都用低电平完成. 但是直接用低电平点亮LED, 效果并不理想.
因此, 这个数据缓冲就是用来提高驱动能力的.
我们用低电平发送信号, 然后由这个数据缓冲器把信号提高到高电平来驱动 LED.
数码管的另一边接的就是 138 译码器, 是一种3线-8线译码器:
138 译码器 只用三个 IO 端(A/P2_2, B/P2_3, C/P2_4) , 就可以控制八个输出端.
它的工作原理是:
A, B, C | 输出 |
---|---|
000 | 0 (LED 8) |
001 | 1 (LED 7) |
010 | 2 (LED 6) |
011 | 3 (LED 5) |
100 | 4 (LED 4) |
101 | 5 (LED 3) |
110 | 6 (LED 2) |
111 | 7 (LED 1) |
选中的输出端, 就会输出0.
点亮一个 LED 数码管
那么一个 LED 数码管, 究竟是怎么样被点亮的呢?
第一步就是通过 138 译码器, 通过这三个口, 使某一位输出为 0 ,选中要显示的LED.
接下来通过缓冲送来的信号, 驱动 LED 点亮.
例如, 点亮 LED6, 使其显示数字 6, :
代码实现:
1 |
|
设计一个数码管显示数字的函数:
1 | // 设计一个数组存储每个数字对应的二进制数 |
多位数码管动态显示
由于其设计, 数码管无法做到同时显示多个数字.
但是我们可以快速切换显示的数字, 只要切换的足够迅速, 利用人眼的视觉暂留效应, 就可以看到”同时”显示多个数字.
不过如果我们直接这样写
1 | // 使用刚刚设计的函数 displayNum() |
会出现 数码管的残影:
影响数字辨认.
这是由于数码管显示数字时, 会先选位选(选择要在哪个数码管上显示), 然后再段选(选择要显示的数字).
位选 –> 段选 –> 位选 –> 段选 –> …
由于这个过程十分迅速, 就会导致数码管串位显示, 数字显示在了错误的位置上.
为了解决这个问题, 可以这样做:
位选 –> 段选 –> 清零 –> 位选 –> 段选 –> 清零 –> …
代码层面, 我们可以优化一下 displayNum() 函数:
1 |
|
模块化编程
众所周知, 程序员最不喜欢一直做重复的事. 像之前提到的 Delay(), 我们可以把它封装成一个函数, 然后在其他地方调用, 就不用每次都要写一遍.
C预编译
C语言的预编译以 # 开头, 作用是在珍珠的编译开始之前, 对代码做一些预处理(预编译)
预编译 | 作用 |
---|---|
#include | 包含头文件 |
#define | 定义常量 |
#ifndef | 防止头文件重复包含 |
#endif | 结束头文件包含 |
接下来, 尝试一下将之前写的 Delay() 函数模块化.
首先创建工程, 完成一些基本操作:
1 |
|
然后在工程目录下创建 delay.h 和 delay.c 文件:
delay.c 存放函数实现:
1 | // 定义一个延时函数 |
delay.h 存放函数声明:
1 |
|
矩阵键盘
- 标题: 51单片机原理与应用
- 作者: Wreckloud_雲之残骸
- 此记初现于 : 2024-09-30 12:41:49
- 此记变迁于 : 2024-11-22 12:07:31
- 链接: https://www.wreckloud.com/2024/09/30/猎识印记-领域/嵌入式/51单片机/
- 版权声明: 本幽影记采用 CC BY-NC-SA 4.0 进行许可。