1、单片机概述
1.1. 单片机的定义与分类
定义:
单片机(Microcontroller Unit,简称MCU)是一种将微处理器、存储器(包括程序存储器和数据存储器)、输入/输出接口和其他必要的功能模块集成在单个芯片上的微型计算机。它通常用于控制应用,可以在没有外部支持芯片的情况下独立运行。
分类:
-
按处理能力分类:
- 低端单片机:处理能力较弱,适用于简单的控制应用。
- 中端单片机:具有一定的处理能力,适用于中等复杂度的控制系统。
- 高端单片机:处理能力强,适用于复杂的控制系统和计算密集型应用。
-
按指令集分类:
- 复杂指令集单片机(CISC):指令丰富,操作复杂,适用于多种应用场景。
- 精简指令集单片机(RISC):指令简单,执行速度快,功耗低。
-
按位数分类:
- 4位单片机:处理能力有限,适用于简单的控制任务。
- 8位单片机:应用最广泛,如8051系列。
- 16位单片机:处理能力更强,适用于较为复杂的控制系统。
- 32位单片机:具有更高的处理能力和更多的功能,适用于高端应用。
-
按用途分类:
- 通用型单片机:适用于多种场合。
- 专用型单片机:针对特定应用设计,如汽车电子、家电控制等。
1.2. 单片机的发展历程
早期阶段(1970年代):
- 1971年,Intel推出了世界上第一款微处理器4004,随后推出了8008,为单片机的发展奠定了基础。
发展阶段(1980年代):
- 1981年,Intel推出了8051单片机,成为历史上最成功的单片机之一。
- 其他公司如Motorola、Zilog、Atmel等也相继推出了自己的单片机产品。
成熟阶段(1990年代):
- 单片机的性能不断提升,应用领域不断扩展。
- 出现了基于Flash存储技术的单片机,使得程序更新更加方便。
多元化发展阶段(2000年代至今):
- 单片机开始集成更多的功能,如USB、网络、CAN总线等。
- 32位单片机逐渐成为市场的主流。
- 出现了基于ARM架构的嵌入式处理器,进一步推动了单片机技术的发展。
1.3. 单片机的应用领域
-
消费电子:
- 家用电器(如洗衣机、空调、电视)
- 音响设备
- 游戏机
-
工业控制:
- 自动化生产线
- 机器人控制
- 电力系统监控
-
汽车电子:
- 发动机控制单元(ECU)
- 车身电子控制
- 车载娱乐系统
-
医疗设备:
- 监护仪器
- 医疗诊断设备
- 智能假肢
-
通信设备:
- 路由器
- 交换机
- 无线通信模块
-
物联网(IoT):
- 智能家居
- 智能城市
- 工业互联网
-
其他:
- 安全监控
- 环境监测
- 仪器仪表
2、单片机硬件基础
2.1. 单片机的内部结构
内部结构图
单片机的内部结构通常包括以下几个主要部分:
- 中央处理器(CPU): 执行程序指令,进行算术和逻辑运算。
- 程序存储器(ROM): 存储程序代码。
- 数据存储器(RAM): 存储运行时的数据和变量。
- 特殊功能寄存器(SFR): 控制和状态寄存器,用于特定功能的配置和状态反馈。
- 输入/输出端口(I/O): 与外部设备进行数据交换。
- 定时器/计数器: 用于定时或计数功能。
- 中断控制器: 处理中断请求,实现多任务处理。
- 串行通信接口: 实现与其他设备或单片机的串行通信。
2.2. 单片机的引脚功能
以8051单片机为例,它通常有40个引脚,以下是部分引脚的功能:
- VCC: 电源正极。
- GND: 电源负极。
- P0.0 - P0.7: 端口0,可以作为通用I/O口,也可以作为地址/数据总线。
- P1.0 - P1.7: 端口1,通用I/O口。
- P2.0 - P2.7: 端口2,通用I/O口,也可用于外部存储器地址线。
- P3.0 - P3.7: 端口3,通用I/O口,部分引脚具有第二功能,如P3.0和P3.1用于串行通信。
- RST: 复位引脚,高电平有效。
- ALE/PROG: 地址锁存使能/程序存储器编程。
- PSEN: 程序存储器选通信号。
- EA/VPP: 外部访问使能/编程电压。
2.3. 单片机的时钟与复位
- 时钟: 单片机的时钟用于提供CPU操作的基本时间节拍。8051单片机通常需要一个外部晶振或陶瓷振荡器来提供时钟信号。
- 复位: 当RST引脚接收到高电平时,单片机会执行复位操作,将CPU和寄存器恢复到初始状态。
2.4. 单片机的存储器结构
2.4.1 程序存储器(ROM)
程序存储器用于存储单片机的程序代码。在8051单片机中,程序存储器通常是只读存储器(ROM)或闪存(Flash)。汇编代码将被存储在程序存储器的特定地址中。
assembly ORG 0000H ; //程序起始地址 MOV A,
55H ; //将55H加载到累加器A
2.4.2 数据存储器(RAM)
数据存储器用于存储程序运行时的数据和变量。在8051单片机中,数据存储器是随机存取存储器(RAM)。
char data_var = 0xAA; // 定义一个字符变量并初始化为0xAA
data_var
变量将被存储在数据存储器中的一个地址。
2.4.3 特殊功能寄存器(SFR)
特殊功能寄存器是CPU内部的一些寄存器,它们具有特定的功能,如控制I/O端口、定时器、串行通信等。P1
是一个特殊功能寄存器,用于控制端口1的状态。
SFR P1 = 0x90; // 定义P1端口的地址
P1 = 0xFF; // 将P1端口的所有引脚设置为高电平
3. 汇编语言
(1)指令系统
汇编语言的指令系统是指单片机可以理解和执行的指令集合。每个指令对应一个特定的操作,如数据传送、算术运算、逻辑运算、跳转等。以下是一些常见的8051单片机汇编指令:
- 数据传送指令: MOV A, #data(将立即数data传送到累加器A)
- 算术运算指令: ADD A, Rn(将寄存器Rn的内容加到累加器A,结果存储在A中)
- 逻辑运算指令: ANL A, #data(将累加器A的内容与立即数data进行逻辑与运算)
- 跳转指令: JMP label(无条件跳转到label标签处执行)
(2)汇编程序结构
- 起始地址: 指定程序开始执行的地址。
- 数据定义: 定义程序中使用的变量和数据。
- 代码段: 包含实际的指令代码。
- 子程序: 可被主程序调用的代码块。
- 中断服务程序: 处理中断事件的代码。
assembly
ORG 0000H ; 程序起始地址
START: MOV A, #55H ; 将立即数55H传送到累加器A
ADD A, R0 ; 将寄存器R0的内容加到累加器A
MOV P1, A ; 将累加器A的内容输出到端口P1
SJMP START ; 无条件跳转到START标签处继续执行
(3) 汇编语言编程实例
例子1: 将P1端口的值翻转到P2端口
assembly
ORG 0000H
START: MOV A, P1 ; 将P1端口的值读取到累加器A
CPL A ; 将累加器A的内容取反
MOV P2, A ; 将取反后的值输出到P2端口
SJMP START ; 无限循环
3.1C语言基础
C语言的基础概念:
- 变量: 用于存储数据的标识符。
- 数据类型: 定义变量可以存储的数据种类,如int、char、float等。
- 控制结构: 如if、for、while等,用于控制程序流程。
3.2 C语言编程实例
例子1: 将P1端口的值翻转到P2端口
#include <reg51.h> // 包含8051寄存器定义的头文件
void main() {
while (1) { // 无限循环
P2 = ~P1; // 将P1端口的值取反后输出到P2端口
}
}
reg51.h
头文件包含了8051单片机的特殊功能寄存器定义,P1
和P2
是特殊功能寄存器,分别代表端口1和端口2。程序中的while
循环会不断执行,将P1端口的值取反后输出到P2端口。
4、单片机I/O接口
4.1、I/O口的结构与功能
I/O口(输入/输出端口)是单片机与外部设备进行数据交换的接口。在8051单片机中,通常有P0、P1、P2、P3四个8位并行I/O端口。
-
结构:
- 锁存器: 用于存储数据输出。
- 缓冲器: 用于数据输入。
- 驱动器: 提供足够的电流来驱动外部设备。
-
功能:
- 输入模式: 可以读取外部设备的状态(如按键、传感器等)。
- 输出模式: 可以控制外部设备(如LED、继电器等)。
4.2、I/O口的编程方法
-
设置I/O口为输入模式:
- 对于P0、P1、P2、P3端口,可以通过设置相应的数据(定义)方向寄存器(如P1DIR)来控制每个位是输入还是输出。
-
设置I/O口为输出模式:
- 同样,通过设置数据方向寄存器来控制端口的方向。
-
读取输入:
- 直接读取I/O口寄存器即可。
-
写入输出:
- 直接向I/O口寄存器写入数据即可。
4.3、I/O口的应用实例
例子1: 控制一个连接在P1.0的LED亮灭( LED控制)
#include <reg51.h>
void delay(unsigned int ms) {
unsigned int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 123; j++); // 简单的延时函数
}
void main() {
P1 = 0xFF; // 初始化P1口,所有LED熄灭
while (1) {
P1_0 = 0; // 点亮LED
delay(1000); // 延时
P1_0 = 1; // 熄灭LED
delay(1000); // 延时
}
}
例子2: 检测连接在P1.0的按键是否被按下(按键检测)
#include <reg51.h>
void delay(unsigned int ms) {
unsigned int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 123; j++);
}
void main() {
P1 = 0xFF; // 将P1口设置为输入模式,并启用内部上拉电阻
while (1) {
if (P1_0 == 0) { // 如果P1.0为低电平,表示按键被按下
delay(20); // 延时消抖
if (P1_0 == 0) { // 再次检测,确保按键确实被按下
while (P1_0 == 0); // 等待按键释放
P1_1 = ~P1_1; // 切换P1.1连接的LED状态
}
}
}
}
例子3: 控制一个连接在P1.0的继电器开关(继电器控制)
#include <reg51.h>
void delay(unsigned int ms) {
unsigned int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 123; j++);
}
void main() {
P1 = 0xFF; // 初始化P1口,所有继电器断开
while (1) {
P1_0 = 0; // 继电器闭合
delay(5000); // 延时
P1_0 = 1; // 继电器断开
delay(5000); // 延时
}
}
``
`
5、单片机定时器/计数器
5.1、定时器/计数器的工作原理
定时器/计数器是单片机内部的一个重要模块,它可以用于计时或者计数。在8051单片机中,通常有两个定时器/计数器:定时器/计数器0和定时器/计数器1。
5.1.1 工作原理:
- 定时器模式: 当定时器/计数器被配置为定时器模式时,它会对单片机的内部时钟信号进行计数。当计数达到设定值时,定时器溢出,可以触发中断或改变I/O端口的状态。
- 计数器模式: 当定时器/计数器被配置为计数器模式时,它会对外部信号(通常是T0或T1引脚上的脉冲)进行计数。
5.2、定时器/计数器的编程方法
初始化定时器/计数器:
- 设置定时器/计数器模式。
- 设置定时器/计数器的初值。
- 启用或禁用定时器/计数器。
- 配置中断(如果需要)。
5.3、定时器/计数器的应用实例
例子1: 使用定时器0实现1秒的延时(延时)
#include <reg51.h>
void Timer0_Init() {
TMOD &= 0xF0; // 清除定时器0模式位
TMOD |= 0x01; // 设置定时器0为模式1(16位定时器模式)
TH0 = 0xFC; // 设置定时器初值(1秒延时,具体值取决于晶振频率)
TL0 = 0x18;
ET0 = 1; // 启用定时器0中断
EA = 1; // 启用全局中断
TR0 = 1; // 启动定时器0
}
void Timer0_ISR() interrupt 1 {
TH0 = 0xFC; // 重新加载定时器初值
TL0 = 0x18;
// 这里可以执行需要延时的代码
}
void main() {
Timer0_Init();
while (1) {
// 主循环代码
}
}
例子2: 使用定时器1对外部脉冲进行计数(脉冲计数)
#include <reg51.h>
void Timer1_Init() {
TMOD &= 0x0F; // 清除定时器1模式位
TMOD |= 0x10; // 设置定时器1为模式1(16位计数器模式)
ET1 = 1; // 启用定时器1中断
EA = 1; // 启用全局中断
TR1 = 1; // 启动定时器1
}
void Timer1_ISR() interrupt 3 {
// 这里可以读取TL1和TH1的值,以获取脉冲计数
// 注意:在中断服务程序中,通常不进行复杂操作
}
void main() {
Timer1_Init();
while (1) {
// 主循环代码
}
}
例子3: 使用定时器0产生PWM信号(PWM输出)
#include <reg51.h>
void Timer0_Init() {
TMOD &= 0xF0; // 清除定时器0模式位
TMOD |= 0x02; // 设置定时器0为模式2(8位自动重装载模式)
TH0 = 0xFF; // 设置PWM周期(具体值取决于PWM频率)
TL0 = TH0; // 初始化TL0
ET0 = 1; // 启用定时器0中断
EA = 1; // 启用全局中断
TR0 = 1; // 启动定时器0
}
void Timer0_ISR() interrupt 1 {
static unsigned char pwm_width = 0; // PWM占空比变量
if (TF0) { // 检查定时器是否溢出
TR0 = 0; // 停止定时器
TH0 = 0xFF; // 重新加载PWM周期
TL0 = TH0;
P1_1 = 1; // 开始PWM周期
TR0 = 1; // 重新启动定时器
} else if (TL0 == pwm_width) {
P1_1 = 0; // 设置PWM占空比
}
}
void main() {
Timer0_Init();
6、单片机中断系统
6.1中断系统的组成
-
中断源:中断源是指能够触发中断的事件或信号。中断源可以是硬件事件(如定时器溢出、外部引脚变化)或软件事件(如软件中断指令)。
-
中断控制器:中断控制器负责管理中断请求,确定中断的优先级,以及选择哪个中断服务程序(ISR)来响应。
-
中断向量表:中断向量表是一个存储中断服务程序入口地址的表格。当中断发生时,CPU通过中断向量表找到对应的中断服务程序。
-
中断服务程序(ISR):中断服务程序是一段用于处理特定中断事件的代码。当中断被触发时,CPU会暂停当前任务,跳转到ISR执行。
6.2中断处理流程
-
中断请求:中断源发出中断请求信号。
-
中断识别:中断控制器识别中断请求,并判断其优先级。
-
中断响应:如果中断被允许,CPU会暂停当前任务,保存当前程序状态(如程序计数器、寄存器等),然后跳转到对应的中断服务程序。
-
执行中断服务程序:CPU执行中断服务程序来处理中断事件。
-
恢复执行:中断服务程序执行完毕后,CPU恢复之前保存的程序状态,并返回到中断发生前的位置继续执行。
6.3中断优先级和嵌套
中断系统通常支持中断优先级,以决定多个中断同时发生时哪个中断将被首先处理。中断嵌套是指一个中断服务程序在执行时可以被另一个更高优先级的中断打断。
6.4程实践
#include <reg51.h>
// 外部中断0服务程序
void External0_ISR(void) interrupt 0 {
// 执行外部中断0的处理代码
}
// 定时器0中断服务程序
void Timer0_ISR(void) interrupt 1 {
// 执行定时器0的处理代码
}
void main() {
EA = 1; // 全局中断使能
EX0 = 1; // 外部中断0使能
ET0 = 1; // 定时器0中断使能
// 其他初始化代码...
while(1) {
// 主循环代码
}
}
7、单片机串行通信
单片机的串口通信是一种常用的数据传输方式,它允许单片机与其他设备(如计算机、传感器、其他单片机等)进行数据交换。
串口通信的基本概念
串口通信(Serial Communication)是一种按照位序列进行数据传输的方式,通常使用串行通信接口
串口通信的主要参数
-
波特率(Baud Rate):表示每秒钟传送的位数,单位是bps(bits per second)。
-
数据位(Data Bits):表示每个数据帧中数据位的数量,通常为7或8位。
-
停止位(Stop Bits):表示每个数据帧结束时的停止位的数量,通常为1、1.5或2位。
-
校验位(Parity Bit):用于错误检测的位,可以是奇校验、偶校验或无校验。
-
流控制(Flow Control):用于防止数据丢失的技术,如硬件流控制(RTS/CTS)或软件流控制(XON/XOFF)。
串口通信的硬件接口
- RX(接收):用于接收数据。
- TX(发送):用于发送数据。
- GND(地):用于信号参考点。
单片机串口通信的步骤
-
初始化串口:设置波特率、数据位、停止位、校验位等参数。
-
配置中断(可选):如果使用中断方式进行数据接收和发送,需要配置相关的中断服务程序。
-
数据发送:编写代码将数据写入串口发送缓冲区。
-
数据接收:编写代码从串口接收缓冲区读取数据。
示例1:8051单片机串口通信
#include <reg51.h>
```c
```c
```bash
```bash
// 假设使用11.0592MHz的晶振
void Serial_Init() {
SCON = 0x50; // 设置为模式1,8位数据,可变波特率
TMOD |= 0x20; // 定时器1使用模式2(自动重装载)
TH1 = 0xFD; // 设置波特率为9600
TL1 = 0xFD; // 与TH1相同
TR1 = 1; // 启动定时器1
TI = 1; // 设置TI位,准备发送
}
void Serial_SendByte(unsigned char dat) {
SBUF = dat; // 将数据放入发送缓冲区
while (!TI); // 等待发送完成
TI = 0; // 清除发送完成标志
}
void main() {
Serial_Init(); // 初始化串口
while (1) {
Serial_SendByte('A'); // 发送字符'A'
// 可以添加延时,控制发送速度
}
}
首先初始化串口,然后在一个无限循环中发送字符’A’。Serial_Init
函数设置了串口的工作模式和波特率,而Serial_SendByte
函数负责发送一个字节的数据。
8、单片机外围设备扩展
单片机连接到各种外围设备的详细插图