51单片机
- 相关链接
- Cx51编译器 用户指南 可理解为C语言的额外修改版本
- Keil 官网 Keil下载后需找渠道激活
- STC-ISP 下载链接
基础知识
- 开发工具:keil5 编写程序、STC-ISP 下载程序、烧录软件-找厂商要
- 说明:本文档基于 STC89C52 单片机
51单片机
- 单片机、简称 MCU
- 将CPU、存储器【RAM、ROM】、输出输出、定时器、中断系统、通讯接口等集成在一个芯片上的微型计算机。成本低、体积小、结构简单、易于编程。
- 单片机的任务是:信息采集(依靠传感器)、处理(依靠CPU)、控制(电机、LED等)
- 51单片机指所有 兼容Inter 8031指令系统的单片机的统称。 实现8051指令集?
STC89C52
- 位数:8位单片机;寄存器、数据总线、计算逻辑单元等也都是8位,一次能处理8位数据。
- RAM:512字节 - 掉电丢失
- ROM:8kb(Flash)- 掉电不丢失 1k=1024b 1b=8字节
- 工作频率/速度:不同板子不一定相同(单片机晶振决定?)
- 命名规则:
引脚
- 在单片机中有寄存器(存储器)、且8个为一组、每个寄存器都通过一个驱动器(驱动增大电流)连接一个IO口
- CPU可以直接访问并控制寄存器中的值,从而控制IO的输出
- 通过CPU控制寄存器、完成对硬件电路的控制
- 在51中一般使用16进制进行控制
最小系统
三级管
软件准备
STC89C52支持
- 下载并激活keil【略】
- 下载STC-ISP
- 配置STC-ISP【将我们的芯片database 导入到 keil 】
- 关闭 keil
- 坐上角 芯片型号 - 选择 STC89C52/LE52RC
- 右上 keil仿真设置 - 选择 “添加型号和头文件到keil中...”
- 弹出框中选择keil安装路径
- 使用 keil 创建项目
- Poject - new Poject - 设置存放的目录
- 选择使用的芯片,下图例:
STC89C52RC
- 确定添加启动文件
- 在单片机复位时执行,清除数据+初始化操作
中文乱码解决
- Edit - Configuration - Encoding - 选择支持中文的字符集即可 GB2312
- 如果报错,可能是没有修改的权限,需用管理员身份运行
编译配置(魔法棒)
- Output
- 勾选 Create HEX File , 生成HEX文件
编译
- 左上角编译按钮 3个
- 只编译当前文件,不会触及其他文件的链接【用于检查当前文件】
- 差量化编译, 编译当前Target目录下修改过的所有文件,产生链接【生成可执行文件】
- 完全重新编译,【生成可执行文件】
- 注意
- 能被开发板识别的可执行文件为 hex 后缀
- 编译后的文件可通过,项目文件夹 - Objects 文件夹下查看
demo
- 使用kile5新建项目
- 第一步:设置项目名及保存的路径
- 第二步:配置·单片机型号;stc89c52
- 第三步:新建main.c,开写
c
#include <REGX52.H>
void main(){
P2 = 0xFE; // 设置P2组的引脚值,控制高低电平 1111 1110
}
#include <STC89C5xRC.H> // 包含STC89C52的头文件
void main(){
P00 = 0; // 让P00引脚输出低电平
while(1); // 死循环,让程序持续运行,从而使单片机持续工作
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
代码烧录
- 核心板默认不支持USB协议进行数据传输,支持传统串口协议
- 在开核心板上,存在 CH340 芯片,实现 USB转串口 的协议
- 注意
- windows可能需要安装 CH340驱动
使用VSCode
- Embedded IDE 官方文档
- VSCode 插件,用来开发单片机项目,比如:
8051
,stm8
,stm32
,other cortex-m mcus
...
- VSCode 插件,用来开发单片机项目,比如:
- 使用步骤
- 安装插件
- 创建项目- 空项目-根据需要选择
- 打开项目-并切换为工作区
- 插件配置 - 设置工具链 - 配置相关工具的路径
- 插件配置 - 项目属性 - 包含目录 - 引入相关INC目录
- 编译代码 - 成功后就可以使用【后续使用STC-ISP烧录】
- VSCODE中需要安装 python,借助 py脚本实现
知识点
头文件
头文件重复处理
- 同一份头文件可能被不同文件引用,避免重复被执行,应使用
#ifndef #define #endif
做处理
c
#ifndef 模块名称
#define 模块名称
// .h 头定义内容...
#endif
1
2
3
4
5
6
2
3
4
5
6
STC89C5xRC.H
包含STC89C52的头文件,keil 代码中 鼠标右键 open document xxx 查看具体内容
- sfr(Special Function Register)
- keil C51编译器提供的扩展数据类型,用于声明单片机中的特殊功能寄存器
- 特殊功能寄存器:用于控制、配置单片机中的各种硬件设备,如GPIO端口、定时器、串口等
- 每组GPIO端口均有一个对应的8为寄存器
- P0 的
0x80
表示第0组的寄存器地址,不能修改,可通过单片机手册查看sfr P0 = 0x00
含义:向 P0 所指向的寄存器赋值为 0x00
- sbit
- sbit 表示 特殊功能寄存器中的一位
- keil C51编译器提供的扩展数据类型,取值为
0/1
零星知识
c
sfr P0 = 0x80; // p0从此仅表示对应寄存器上的8位,可通过 p0 = 0x01 对其8位赋值
sbit P00 = P0^0; // p00 仅表示p0上的最低位,可通过 p0 = 1 对这个位赋值
P00 = ~P00; // ~ 取反符号
/* 合理改值【位运算 运用】
- P1 有8位,如何修改其中的某3位; P15 P14 P13
- 思路:先把指定的几位改为0;将要修改的数据移动到对应二进制位;两数相或;
*/
positon <<= 3;
P1 &= 0xC7;
P1 |= postion;
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
C89变量语法
- 所有的变量都,必须写在当前作用域的最前方
数据类型
模块化编程
将不同的功能、模块拆分到不同文件,再用头文件相互引用。
变量命名
g_
前缀标识全局变量,例g_system_state
s_
前缀标识静态变量,例s_internal_counter
st_
前缀标识静态变量,例st_data
p_
前缀标识指针,例p_num
p_st
前缀标识结构体指针,例p_st_data
- 常量和宏定义使用全大写字母,并用下划线分割单词,例
MAX_VALUE
- 变量通常用小写字母,单词间使用下划线连接,例如
sensor_value
- 结构体类型声明使用
_Struct
后缀,例Date_Struct
- 枚举类型名称使用大写字母开始,例
ColorType
- 枚举值使用全大写字母,并用下划线分割单词,
COLOR_RED
- 内部变量都要加
static
关键字,只能当前文件访问的变量
函数命名
- 采用
{分层}_{模块}_{功能}
作为函数名称, - 返回布尔值的函数,名称应是一个问题或断言,
IsButtonPressend()
- 获取属性/变量值的函数使用
Get
前缀 - 设置属性/变量值的函数使用
Set
前缀 - 内部函数都要加
static
关键字
文件命名
- 使用
{分层}_{模块}作为文件名称
- 如果项目规模较大,建议头文件和源文件分开
代码分层规范
- 工具函数和常规宏定义
- 特定算法、工具函数、常规宏定义
- 目录 COM
- 前缀 COM_
- 驱动层
- 所有与自身芯片硬件直接交互的代码,例如 GPIO开关、硬件UART、ADC驱动、计时器等
- 目录 Dri/
- 前缀 Dri_
- 接口层
- 位于驱动之上,通过标准接口(GPIO、UART、IIC、SPI等)驱动外部硬件的代码,没有外部硬件可不用这一层
- 目录 Int/
- 前缀 int_
- 中间层
- 提供更高级的服务,如操作系统、文件系统、通信协议栈等。通常用于复杂单片机项目,简单项目不涉及这一层
- 目录:Mid/
- 前缀:Mid_
- 应用层
- 包含应用程序的主要逻辑,只与 中间层/接口层 进行交互,尽量不直接访问驱动层
- 目录:App/
- 前缀:App_
中断系统
中断系统:单片机CPU用于处理外部紧急事件的机制,使单片机能够实时响应外部事件,提高系统的灵活性和响应能力
概念
- 中断源:能够引发中断的事件
- 中断标志位
- 用于标识某个中断是否发生,每个中断都有一个与之对应的中断标识位
- 某个中断发生时,相应中断标志位就会置为1
- 当CPU检测到标志位为 1 时,就处理相应的中断
- CPU处理完,中断标志位需复位 0【自动复位 / 手动复位 - 参看文档】
- 中断服务程序:中断后要处理的逻辑
- 中断优先级
- 高优先级可以打断低优先级的中断
STC89C52RC
中断源共8个
- 外部中断
- 引脚:
INFT0 INFT1
- 由单片机外部紧急事件触发的中断,通过特定引脚发送特定的信号触发
- 特定信号:51单片机支持两种触发方式:低电平触发、下降沿触发
- 引脚:
- 定时器中断
- 定时器:
Timer0 Timer1 Timer2
,每个定时器都有对应的中断 - 由单片机内部的定时器触发的中断
- 定时器是单片机自身具备的功能模块,用于实现定时任务,设置指定时间后触发回调
- 定时器:
- 串口中断【全双工】
- 串口引脚:
TxD 发送数据 RxD 接收数据
- 由单片机串口触发的中断,当串口接收到数据或者发送完整数据后都会触发相应的中断
- 串口引脚:
- 外部中断
中断服务程序语法
interrupt
关键字,表示当前函数为中断服务程序0
中断号- 每个中断都有唯一的中断号,标识当前中断服务程序处理的中断 - 中断号定义查手册
c// 函数名-无特殊要求 void 函数名 interrupt 0 { // 编写中断处理逻辑 }
1
2
3
4中断优先级
- STC89C52RC共有四个中断优先级,每个中断都可单独设置优先级
- 优先级高的先被处理,中断优先级相同时,中断号小先执行
- 中断嵌套:高优先级中断 可打断 低优先级中断
- STC89C52 仅支持两级中断嵌套,更多的会被搁置等待
外部中断
- 使用步骤
- 默认CPU会屏蔽所有中断请求,必须先启用再使用。由单片机内的两个特殊功能寄存器控制 - 中断控制
- IE 中断允许寄存器
EA=1 EX0=1
- XICON 辅助中断控制寄存器
- IE 中断允许寄存器
- 配置外部中断触发方式 - 终端控制位
- STC89C52RC 支持两种触发方式:下降沿触发、低电平触发
- 每个外部中断各需要一个控制位设置触发方式,分别位于
XICON
和TCON
寄存器
- 中断优先级【可选】
- 支持两级/四级优先级,每个中断源的优先级都需通过1个/2个控制位进行配置,可能位于
IPH-中断优先级高位 IP-中断优先级地位 XICON-辅助中断控制
- 支持两级/四级优先级,每个中断源的优先级都需通过1个/2个控制位进行配置,可能位于
- 默认CPU会屏蔽所有中断请求,必须先启用再使用。由单片机内的两个特殊功能寄存器控制 - 中断控制
c
// K3按键按下后触发中断,k3接的外部中断INT0
#include <STC89C5xRC.H> // 包含STC89C52的头文件
void INT0_Init()
{
EA = 1; // 总中断使能
EX0 = 1; // 外部中断0使能
IT0 = 1; // 外部中断 - 下降沿触发
}
void main()
{
INT0_Init();
while (1) {
}
}
// 中断处理程序
void INT0_CallBack() interrupt 0
{
delay_10us(1000); // 消斗
if(KEY3==0) P20 = ~P20; // 再次判断K3键是否按下,并翻转LED状态
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
定时器中断
定时器相比软件延时,具有更高的精确度
- 工作原理
- 使用一个n位的脉冲计数器,对时钟信号的脉冲进行计数,每个脉冲+1,当计数器达到最大值时溢出,触发定时器中断
- 脉冲:信号在短时间内
从低到高,再返回低电平
的过程 - 周期:两个相邻脉冲的时间间隔
- 频率:单位时间内脉冲重复的次数
1s/周期
- 脉冲:信号在短时间内
- 定时时长的影响因素
- 脉冲计数器的位数
- 分为8、16、32位
- 脉冲计数器的初始值
- 时钟信号的频率
- 脉冲计数器的位数
- 使用一个n位的脉冲计数器,对时钟信号的脉冲进行计数,每个脉冲+1,当计数器达到最大值时溢出,触发定时器中断
- 使用步骤
- 启动定时器中断
ET0 ET1
- 定时器的启动可被单片机 内部寄存器控制 或 外部引脚控制
- 选择
计数/定时
方式【脉冲来源不同】TMOD
- 定时方式-用于产生精确的时间延迟 -
系统时钟的脉冲信号
- 计数方式-用于统计外部脉冲信号的个数 -
单片机外部引脚的脉冲信号
- 每个定时器都有一个控制位,用于设置工作模式
TMOD
不支持按位寻址,需整体赋值
- 定时方式-用于产生精确的时间延迟 -
- 选择工作模式
TMOD
- 定时器0共有四种工作模式
- 13位【兼容】:最大8192,TL0和TH0两个8位寄存器用于存储脉冲计数器数值,TL0仅用低5位
- 16位:最大65536,TL0和TH0两个8位寄存器用于存储脉冲计数器数值
- 8位自动重装载
- 13位、16位,一次执行完,需开发者重新为脉冲计数器设置初始值
- 自动重装载能够再脉冲计数器溢出时,自动重新设置初始值,适用于执行周期性任务
- TL0 用于存储脉冲计算器的初始值,TH0 用于存储脉冲计数器的初始值,每次溢出后自动恢复到TH0的值
- 双8位
- 该模式下,TL0、TH0分别用作一个脉冲计数器,适用于脉冲计数器不够时拆分
- 定时器0共有四种工作模式
- 启动定时器中断
脉冲计数器初始值
- 以16位工作模式为例,实现1ms定时
- 已知:最大65536,12分频,时钟频率11.0592MHz = 11059.2KHz = 11059200hz
12分频后的频率 = 计数脉冲频率 = 11059200/12
12分频后的时间 = 1/计数脉冲频率 = 12/11059200s ≈ 1.08us
- 1ms所需脉冲数:
0.001/(12/11059200) ≈ 922
- 脉冲应设初始值:
65536-922 = 64614
c
// 赋值
TL0 = 64614; TH0 = 64614 >> 8; // 直接赋值,简单
TL0 = 0x66; TH0 = 0xFC; // 使用传统十六进制,高效
1
2
3
2
3
定时器中端解耦demo
c
// 16位定时器实现led灯 1s闪烁1次 单文件版
// main.c
#include "Dri_Timer0.h"
void main()
{
Dri_Timer0_Init();
while (1) {
}
}
// Dri_Timer0.c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include "Dri_Timer0.h"
void Dri_Timer0_Init(){
// 1.启用终端
ET0 = 1; // 使能定时器0中断
EA = 1; // 总中断开启
// 2.工作模式:GATE=0;C/T=0;M1=0;M0=1;
TMOD = (TMOD & 0xF0) | 0x01; // 设置定时器0:16位定时模式
// 3.设置初始值
TL0 = 64614; // 设置定时器0低8位的初值
TH0 = 64614 >> 8; // 设置定时器0高8位的初值
// 4.启动定时器 0-开始计数 0-停止
TR0 = 1;
}
void Dri_Timer0_Handler() interrupt 1
{
// 局部静态变量,
// - 静态变量生命周期与程序一致,整个程序结束前都不会被销毁重建
// - 只能被当前函数访问,避免被外部意外修改
static unsigned int temp = 0;
// 重装载脉冲计数器-初始值
TL0 = 64614;
TH0 = 64614 >> 8;
temp++;
if (temp >= 1000) {
temp = 0;
P20 = ~P20; // 翻转P20引脚的电平
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
c
// 解耦版
// main.c
// main.c
// 16位定时器实现led灯 1s闪烁1次
#include <STC89C5xRC.H>
#include "Dri_Timer0.h"
void LED_Blink(void)
{
// 局部静态变量,
// - 静态变量生命周期与程序一致,整个程序结束前都不会被销毁重建
// - 只能被当前函数访问,避免被外部意外修改
static unsigned int temp = 0;
temp++;
if (temp >= 1000) {
temp = 0;
P20 = ~P20; // 翻转P20引脚的电平
}
}
void main()
{
Dri_Timer0_Init();
Dri_Timer0_RegisterCallBack(LED_Blink); // 注册回调函数
while (1) {
}
}
// Dri.Timer0.h
// Dri.Timer0.h
#ifndef __DIR_TIMER0_H__
#define __DIR_TIMER0_H__
#include "Com_Util.h"
void Dri_Timer0_Init();
/**
* @brief: 注册Timer0的中断回调函数,参数FUN是一个函数指针,指向将要注册的回调函数
* @return bit 0-失败 1-成功
*/
bit Dri_Timer0_RegisterCallBack(FUN callback);
/**
* @brief: 取消Timer0的中断回调函数,参数FUN是一个函数指针,指向将要注册的回调函数
* @return bit 0-失败 1-成功
*/
bit Dri_Timer0_DelCallBack(FUN callback);
#endif
// Dri.Timer0.c
// Dri.Timer0.c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include "Dri_Timer0.h"
#define MAX_CALLBACK_NUM 4
#define NULL ((void *)0) // 定义NULL
static FUN s_callbacks[MAX_CALLBACK_NUM]; // 定义一个静态数组,用于存放回调函数指针
void Dri_Timer0_Init()
{
unsigned char i;
// 1.启用终端
ET0 = 1; // 使能定时器0中断
EA = 1; // 总中断开启
// 2.工作模式:GATE=0;C/T=0;M1=0;M0=1;
TMOD = (TMOD & 0xF0) | 0x01; // 设置定时器0:16位定时模式
// 3.设置初始值
TL0 = 64614; // 设置定时器0低8位的初值
TH0 = 64614 >> 8; // 设置定时器0高8位的初值
// 4.启动定时器 0-开始计数 0-停止
TR0 = 1;
// 初始化回调数组
for (i = 0; i < MAX_CALLBACK_NUM; i++) {
s_callbacks[i] = NULL;
}
}
bit Dri_Timer0_RegisterCallBack(FUN callback)
{
unsigned char i;
for (i = 0; i < MAX_CALLBACK_NUM; i++) {
if (s_callbacks[i] == callback) return 1;
if (s_callbacks[i] == NULL) {
s_callbacks[i] = callback;
return 1;
}
}
return 0;
}
bit Dri_Timer0_DelCallBack(FUN callback)
{
unsigned char i;
for (i = 0; i < MAX_CALLBACK_NUM; i++) {
if (s_callbacks[i] == callback) {
s_callbacks[i] = NULL;
return 1;
};
}
return 0;
}
void Dri_Timer0_Handler() interrupt 1
{
unsigned char i;
// 重装载脉冲计数器-初始值
TL0 = 64614;
TH0 = 64614 >> 8;
for (i = 0; i < MAX_CALLBACK_NUM; i++) {
if (s_callbacks[i] != NULL) s_callbacks[i]();
}
}
// Com_Util.h
// Com_Util.h
#ifndef __COM_UTIL_H__
#define __COM_UTIL_H__
typedef unsigned char u8;
typedef unsigned long u32;
typedef void (*FUN)(void);
void Com_Util_Delay100us(void);
void Com_Util_Delay1000ms(void);
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
通讯
概念
- 串行通讯:设备间的数据 通过一条数据线 逐位传输
- 并行通讯:设备间数据 通过多条数据线 同时传输
- 单双工:只允许数据在一个方向上传输,只能从发送端到接收端
- 半双工:数据方向不限制,但同一时刻,数据只能在一个方向上传输
- 全双工:允许数据同时在两个方向上传输,效率最高
- 发送方/接收方 对数据的协调统一
- 同步通讯:发送方发送数据时,同时发送自己的时钟信号;接收方在时钟周期的
下降沿/上升沿
时接收数据(使用相同的时钟信号) - 异步:统一发送方发送数据和接收方接收数据的频率,不需要额外的时钟信号
- 同步通讯:发送方发送数据时,同时发送自己的时钟信号;接收方在时钟周期的
UART
- 全双工通讯
- 通用异步收/发(Universal Asynchronous Receiver/Transmitter):异步、全双工的串行通信接口,常用于微控制器与计算机、其他微控制器或外部设备之间的数据交换
数据格式
- 数据 逐帧Frame 发送,每个数据帧通常包括:起始位、数据位、校验位、停止位
- 空闲状态:没有数据传输时,应为高电平
- 起始位:起始位用低电平
- 数据位:传输的主体内容,长度5-9位,一般为8位,低电平0,高电平1
- 校验位(可选):校验当前帧的数据正确性
- 奇校验:数据位1的数目位偶数,校验位为1,否则0
- 偶校验:数据位1的数目位偶数,校验位为0,否则1
- 停止位:数据帧的结束,通常为1位或2位,应为高电平
- 其他约定
- 波特率(Baud Rate)
- 表示数据传输速率,每秒传输的基本符号
symbol
数,二进制的基本符号为0/1
,因此二进制中 波特率与比特率相等,1位就是1个符号 - 四进制中,1个符号等于2bit位
- 表示数据传输速率,每秒传输的基本符号
- 波特率(Baud Rate)
工作模式
方式 | 功能(数据位+校验位) | 波特率 | SM0 | SM1 | |
---|---|---|---|---|---|
0 | 同步移位串行:移位寄存器 | SYSclk/12 | 半双工同步通讯,时钟信号TxD,数据信号RxD | 0 | 0 |
1 | 8位UART,无校验位,波特率可变 | 定时器1溢出率+SMOD | 0 | 1 | |
2 | 9位UART,波特率固定,8数据位+1校验位 | SYSclk系统时钟+SMOD | 1 | 0 | |
3 | 9位UART,波特率可变 | 定时器1溢出率+SMOD | 1 | 1 |
波特率设置
常见波特率:4800、9600、19200、38400、57600、115200;9600≈1.17kb/s
- 方式1,波特率受SMOD控制位、定时器1的溢出频率
- 设置波特率,9600为例
- SMOD位于
PCON 电源控制寄存器
,不可按位寻址,设置为0,波特率=定时器1溢出率/32
- SMOD位于
demo
c
1
常用功能
时间控制
软件延时
通过执行无效命令,达到计时效果,拖住程序的运行
- 借助工具生成,特定的软件延时函数
- 定时长度(一般使用1ms,然后通过传参形式复用任意时间长度)
- 选择系统频率(查单片机配置的晶振频率)
c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include <INTRINS.H> // _nop_() 所在头文件
void Delay500ms(void) //@11.0592MHz
{
unsigned char data i, j, k;
_nop_();
i = 4;
j = 129;
k = 119;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main(){
do{
P20 = ~P20;
Delay500ms();
}while(1); // 死循环,让它不会执行结束
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
延时原理
- 软件延时函数,生成为ASM汇编代码;芯片数据手册中,指令系统描述了每条汇编指令执行所需的时钟周期
- 默认使用的是 12个时钟周期作为一个机器周期,可开启烧录工具的 双倍速模式-使用6个时钟周期作为一个机器周期
- 注意
- 开启 6T模式后,由于时钟周期缩短,延时函数对应减半
学习demo
流水灯
c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include <INTRINS.H>
void Delay100ms(void) //@11.0592MHz
{
unsigned char data i, j;
i = 180;
j = 73;
do{
while (--j);
} while (--i);
}
unsigned char tmp = 0x01;
void main(){
do{
if(tmp == 0x00 ){ tmp=0x01; }
P2 = ~tmp;
Delay100ms();
tmp<<=1;
}while(1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
数码管
- 概念
- 段选:确定显示的内容
- 位选:确定显示的数字位置
- 扩展
- 优化电路:使用 74HC38译码器 较少引脚使用-但同时只能展示一个数字
- 驱动灯管:74HC245N驱动器,提升输出电压,让数码管亮
- 注意
- 切换前显示的数字为置空,会导致下一位在动态切换时存在略微重影
c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include <INTRINS.H>
void Delay100us(void) //@11.0592MHz
{
unsigned char data i, j;
_nop_();
_nop_();
i = 2;
j = 15;
do {
while (--j);
} while (--i);
}
typedef unsigned char u8;
unsigned int tmp = 0;
static u8 arr[10] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f};
/**
* @brief 数码管 指定位置->指定数字 显示
* @param{u8} position 片选[0-7]
* @param{u8} num 要显示的数字[0-9]
*/
void showNum(u8 position, u8 num)
{
P0 = 0x00; // 置空,避免设置时,旧的数据保存在P0中切换时存在重影
// 控制哪个数字亮
P22 = position % 2;
P23 = position / 2 % 2;
P24 = position / 2 / 2 % 2;
P0 = arr[num]; // 控制数字的哪个位灭-默认输出高/亮
}
void main()
{
do {
// 循环显示 76543210
if (tmp >= 8) {
tmp = 0;
}
showNum(tmp, tmp);
Delay100us();
tmp += 1;
} while (1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
74HC138译码器
作用:将3位二进制的输入转换为8位的二进制输出,同一时刻只有一个为低电平;例如:74HC138
- 用途
- 数码管使用时,位提升GPIO的利用率,将两个数码管的阴极接到38译码器,实现同一时刻只点亮一个数字
245驱动器
245驱动芯片:由于51单片机高电平的驱动能力很微弱【为保证输入模式时-输入后引脚内部能感知-因此内部默认给出的高电平较小】,不足以点亮数码管,因此可使用 74HC245N作为驱动芯片
- 74HC245N驱动器
- 官方定义:双向的8路总线收发器
74HC595?
直接代替 74HC245N驱动器 和 74HC38译码器
按键
- 介绍
- 相当于开关,按下接通/断开,松开断开/接通
- 12导通,34导通的目的:避免按键长期使用后出现松动
Demo
c
/* 点击按键控制led灯的亮灭 - 采用按键检测 与 逻辑分离 */
// mian.c
#include <STC89C5xRC.H> // 锟斤拷锟斤拷STC89C52锟斤拷头锟侥硷拷
#include "Int_Key.h"
void main(){
do {
if (Int_Key_IsDownP30()) P20 = ~P20;
} while (1);
}
// Int_Key.h
#ifndef _INT_KEY_H
#define _INT_KEY_H
bit Int_Key_IsDownP30();
...
#endif /* _INT_KEY_H */
// Int_Key.c
#include <STC89C5xRC.H> // 锟斤拷锟斤拷STC89C52锟斤拷头锟侥硷拷
#include "Com_Util.h"
bit Int_Key_IsDownP30(){
if (P30 == 0) {
Com_Util_Delay10ms();
if (P30 == 0) {
while (P30 == 0);
return 1; // 1: Key down, 0: Key up
}
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
消抖
- 问题
- 由按键的物理特性限制,按键内部弹簧和触电具有弹性,当按键被按下或释放是,弹性材料发生震动,从而导致触电在短时间内反复接触和断开,因此一次按键实际按下多次,从而看起来像“失灵”。
- 独立按键-在使用时,独立按键通过其中的金属弹片控制电路。在按下和抬起过程中,硬件接触之间不是瞬间完成的,存在抖动变化的过程。
延时消抖
c
// 利用延时10ms后的状态做判断依据,10ms后是低-表示按下-翻转led,10ms后是高-表示抬起-无需操作
void main()
{
do {
if (P31 == 0) {
Com_Util_Delay10ms();
if (P31 == 0) {
while (P31 == 0);
P20 = ~P20;
}
}
} while (1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
矩阵按键
思路:将P20引脚置0,分别获取 24、25、26、27谁为1表示被按下,再一次类推将21、22、23分别置0检测
c
// Int_KeyMatrix.c文件
#include "Int_KeyMatrix.h"
u8 Int_KeyMatrix_CheckSW()
{
u8 num1, num2;
int i;
num2 = 0;
for (i = 0; i < 4; i++) {
P1 |= 0xf0;
if (i == 0) P17 = 0;
if (i == 1) P16 = 0;
if (i == 2) P15 = 0;
if (i == 3) P14 = 0;
num1 = P1 & 0x0F; // 0000 1111
if (num1 == 0x0e) num2 = 4;
if (num1 == 0x0d) num2 = 3;
if (num1 == 0x0b) num2 = 2;
if (num1 == 0x07) num2 = 1;
if (num2) return (num2 + 4 * i);
}
return 0;
}
// main.c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include <INTRINS.H>
#include "Int_KeyMatrix.h"
void main()
{
u8 key;
while (1) {
key = Int_KeyMatrix_CheckSW();
// 数码管展示指定数字
showNum(0, key % 10); // 个位
if(key / 10 % 10) showNum(1, key / 10 % 10); // 十位
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
蜂鸣器
- 分类
- 有源蜂鸣器 - 自带震荡电路
- 震荡频率固定,声音固定
- 无源蜂鸣器 - 不自带震荡电路
- PWM可调节
- 有源蜂鸣器 - 自带震荡电路
无源蜂鸣器
- 蜂鸣器旁边二极管的作用:释放断电时的瞬时高压,保护三极管防击穿!
c
// Int_Buzzer.c
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include "Int_Buzzer.h"
#include "Com_Util.h"
void Int_Buzzer_Buzz(void)
{
u8 count = 100;
while (count > 0) {
P25 = ~P25;
Com_Util_Delay1ms();
count -= 1;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
点阵LED
74HC238译码器
类似于
74HC138
,与其相反,将3位二进制的输入转换为8位的二进制输出,同一时刻只有一个为高电平
74HC595
常用的 8位
串行输入 转 并行输出
芯片
- SCK 移位寄存器 Shift Register
- 用于接收串行数据输入,芯片会根据SCK时钟信号(上升沿)将串行输入逐位移入移位寄存器
- RCK 存储寄存器 Stroage Register
- 与输出引脚相连,因此存储寄存器上的数据就是芯片的输出数据
- 会根据RCK时钟信号(上升沿),将移位寄存器接收的串行数据一次性加载到存储寄存器并进行输出
- 脚G非(OE)
- 74HC595开关,输出使能端,低电平开启,默认高电平
- SRCLR非
- 移位寄存器清零端,低电平有效,默认高电平
- QH`
- 串行数据输出脚,由于移位寄存器移入数据导致 被移除的数据
c
// demo - 未封装处理【略】
#include <STC89C5xRC.H> // 包含STC89C52的头文件
#include "Com_Util.h"
void main()
{
unsigned int temp;
int i;
do {
P36 = 0;
temp = 0x01;
for (i = 0; i < 8; i++) {
/* code */
P34 = 1; // 切换扫描行
// 触发移位+存储寄存器
P36 = ~P36;
P36 = ~P36;
P35 = ~P35;
P35 = ~P35;
// 设置要显示的灯光
P0 = 0x00;
// 设置延时
Com_Util_Delay1000ms();
}
} while (1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27