C51单片机学习(一):基础外设入门指南
参考
51单片机入门教程
1. 单片机简介
1.1 定义
1.2 STC89C52 单片机
STC 公司 51 单片机系列,8 位,RAM(512 字节),ROM(8K,Flash),工作频率 12MHz
51 单片机为什么叫 51?
是因为这种单片机最初采用的是英特尔公司的 8051 指令系统。随着时间的推移,有多家公司生产了兼容 8051 指令系统的单片机,它们虽然功能各异,但是核心架构相同,因此都被统称为 51 单片机 “51” 在这里代表了 8051 架构,也就是 Intel 公司发明的一种早期的单片控制器架构
命名规则
1.3 单片机内部结构图

1.4 单片机管脚图
1.5 单片机最小系统
1.6 单片机核心原理图
2. LED
2.1 简介

2.2 LED 原理图
单片机 TTL 电平:高电平 5V,低电平 0V,LED 具有单向导电性,当 LED 的正端接了高电位,负端连接了低电位,且正负端电位差超过 1.8V 以上时,LED 就会亮起来
2.3 进制转换
2.4 C51 数据类型
2.5 示例代码
#include <REGX52.H> // 定义寄存器和端口(识别 P2 口)
void main() {
P2 = 0xFE; // 1111 1110,0x 前缀表示 十六进制,引脚配置为 “低电平有效”
// P2 = 0x55; // 0101 0101,8 个 LED 灯间隔点亮
while (1) {
}
}
#include <REGX52.H>
#include <INTRINS.H> // _nop_(); 需要的头文件
void Delay500ms() { // @12.000 MHz
unsigned char i, j, k;
_nop_(); // 空操作命令,确保编译器不会对后续的循环优化
i = 4;
j = 205;
k = 187;
// 外层循环的条件是 i != 0
// 内层两层循环的条件分别是 j != 0 && k != 0
do {
do {
while (--k); // 循环耗时操作
} while (--j); // 嵌套循环耗时操作
} while (--i);
}
void main() {
while(1) {
P2 = 0xFE; // 1111 1110
Delay500ms(); // 单片机当中每次都是以 MHZ 速度运行,闪烁太快人眼看不出,因此要加延迟函数
P2 = 0xFF; // 1111 1111
Delay500ms();
}
}
#include <REGX52.H>
#include <INTRINS.H>
void Delay500ms() { // @12.000 MHz
unsigned char i, j, k;
_nop_();
i = 4;
j = 205;
k = 187;
do {
do {
while (--k);
} while (--j);
} while (--i);
}
void main() {
while (1) {
P2 = 0xFE; // 1111 1110
Delay500ms();
P2 = 0xFD; // 1111 1101
Delay500ms();
P2 = 0xFB; // 1111 1011
Delay500ms();
P2 = 0xF7; // 1111 0111
Delay500ms();
P2 = 0xEF; // 1110 1111
Delay500ms();
P2 = 0xDF; // 1101 1111
Delay500ms();
P2 = 0xBF; // 1011 1111
Delay500ms();
P2 = 0x7F; // 0111 1111
Delay500ms();
}
}
#include <REGX52.H>
void Delay1ms(unsigned int xms); // @12.000MHz
void main() {
while (1) {
P2 = 0xFE; // 1111 1110
Delay1ms(100);
P2 = 0xFD; // 1111 1101
Delay1ms(100);
P2 = 0xFB; // 1111 1011
Delay1ms(100);
P2 = 0xF7; // 1111 0111
Delay1ms(100);
P2 = 0xEF; // 1110 1111
Delay1ms(100);
P2 = 0xDF; // 1101 1111
Delay1ms(100);
P2 = 0xBF; // 1011 1111
Delay1ms(100);
P2 = 0x7F; // 0111 1111
Delay1ms(100);
}
}
void Delay1ms(unsigned int xms) { // @12.000MHz
unsigned char i, j;
while (xms) {
i = 2;
j = 239;
do {
while (--j);
} while (--i);
xms--;
}
}
3. 独立按键
3.1 按键介绍

3.2 独立按键原理图

3.3 C51 数据运算
3.4 C51 基本语句
3.5 按键的抖动

3.6 示例代码
#include <REGX52.H>
void main() {
while (1) {
if (P3_1 == 0 || P3_0 == 0) { // 如果 K1 按键(RXD,P3_1)或 K2 按键(TXD,P3_0)按下
P2_0 = 0; // LED1 输出 0,点亮
} else {
P2_0 = 1; // LED1 输出 1,熄灭
}
}
}
#include <REGX52.H>
void Delay(unsigned int xms) {
unsigned char i, j;
while (xms) {
i = 2;
j = 239;
do {
while (--j);
} while (--i);
xms--;
}
}
void main() {
while (1) {
if (P3_1 == 0) { // 如果 K1 按键按下
Delay(20); // 延时,以消除按键抖动带来的影响
while (P3_1 == 0); // 判断 K1 按键是否仍处于按下状态,松手检测
Delay(20); // 延时,以消除按键抖动带来的影响
P2_0 = ~P2_0; // LED1 取反
}
}
}
#include <REGX52.H>
void Delay(unsigned int xms) {
unsigned char i, j;
while (xms--) {
i = 2;
j = 239;
do {
while (--j);
} while (--i);
}
}
void main() {
unsigned char LEDNum = 0; // 无符号字符型(所占1字节 = 8bit位)刚好对应着 8 位二进制的数据
while (1) {
if (P3_1 == 0) { // 如果 K1 按键按下
Delay(20); // 延时消抖
while (P3_1 == 0); // 判断 K1 按键是否仍处于按下状态,松手检测
Delay(20); // 延时消抖
LEDNum++; // 变量自增,用于切换 LED 灯的状态
// P2 口上电之后和单片机的 IO 上电一样都是默认的是高电平:1111 1111
P2 = ~LEDNum; // 变量取反输出给 LED,控制 LED 灯的亮灭
}
}
}
// K1 = P3_1;K2 = P3_0;K3 = P3_2;K4 = P3_3
#include <REGX52.H>
void Delay(unsigned int xms);
unsigned char LEDNum; // 全局变量定义默认为 0
void main() {
P2 = ~0x01; // 上电默认 LED1 点亮
while (1) {
if (P3_1 == 0) { // 如果 K1 按键按下
Delay(20);
while (P3_1 == 0);
Delay(20);
LEDNum++; // LEDNum 自增
if (LEDNum >= 8) // 限制 LEDNum 自增范围
LEDNum = 0;
P2 = ~(0x01 << LEDNum); // LED 的第 LEDNum 位点亮
}
if (P3_0 == 0) { // 如果 K2 按键按下
Delay(20);
while (P3_0 == 0);
Delay(20);
if (LEDNum == 0) // LEDNum 减到 0 后变为 7
LEDNum = 7;
else // LEDNum 未减到 0,自减
LEDNum--;
P2 = ~(0x01 << LEDNum); // LED 的第 LEDNum 位点亮
}
}
}
void Delay(unsigned int xms) {
unsigned char i, j;
while (xms--) {
i = 2;
j = 239;
do {
while (--j);
} while (--i);
}
}
4. 数码管
4.1 简介

4.2 数码管的引脚定义
以共阴极为例(下图右上),若要显示数字 6

以共阴极为例(下图右上),若要在第三位数码管显示数字 1

4.3 数码管原理图
LED1~LED8 都是接到 138 译码器上的输出端,138 译码器原理如下
74HC245 芯片作用:也称双向数据缓冲器,用来提高芯片驱动能力
电容 CC2 作用:起到电源滤波作用,使得芯片的供电更加稳定
RP4 电阻:限流作用,100R 单位为 Ω
位选
段选
4.3 C51 数组 & 子函数
数组:把相同类型的一系列数据统一编制到某一个组别中,可以通过数组名 + 索引号简单快捷的操作大量数据
int x[3]; // 定义一组变量(3个)
int x[]={1,2,3}; // 定义一组变量并初始化
x[0]; //引用数组的第0个变量
x[1]; //引用数组的第1个变量
x[2]; //引用数组的第2个变量
// 引用 x[3] 时,数组越界,读出的数值不确定,应避免这种操作
子函数:将完成某一种功能的程序代码单独抽取出来形成一个模块,在其它函数中可随时调用此模块,以达到代码的复用和优化程序结构的目的
void Function(unsigned char x, y) {
}
返回值 函数名(形参){
函数体
}
4.4 数码管段码表
4.5 数码管驱动方式

4.6 示例代码
#include <REGX52.H>
// 数码管段码表
unsigned char NixieTable[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
// Location:数码管的位置,Number:显示数码管的数字
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]; // 段码输出
}
void main() {
Nixie (2, 3); // 在数码管的第 2 位置显示 3
while (1) {}
}
#include <REGX52.H>
// 数码管段码表
unsigned char NixieTable[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
//延时子函数
void Delay(unsigned int xms) {
unsigned char i, j;
while (xms--) {
i = 2;
j = 239;
do {
while (--j);
} while (--i);
}
}
// Location:数码管的位置,Number:显示数码管的数字
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,消影
}
void main() {
while (1) {
Nixie(1, 1); // 在数码管的第 1 位置显示 1
// Delay(20);
Nixie(2, 2); // 在数码管的第 2 位置显示 2
// Delay(20);
Nixie(3, 3); // 在数码管的第 3 位置显示 3
// Delay(20);
}
}
5. 模块化编程和 LCD 调试工具
5.1 模块化编程

5.2 C 预编译

5.3 LCD1602 调试工具

5.4 使用示例
#include <REGX52.H>
#include <LCD1602.H>
int main(void) {
unsigned int Number = 51;
signed int negative = -1;
LCD_Init();
while (1) {
LCD_ShowChar(1, 1, 'W');
LCD_ShowString(1, 2, "XH");
LCD_ShowNum(1, 4, Number,2);
LCD_ShowSignedNum(1, 7, negative, 1);
LCD_ShowHexNum(2, 1, 0xFF, 2);
LCD_ShowBinNum(2, 4, 0x00, 8);
}
}
6. 矩阵键盘
6.1 简介
在键盘中按键数量较多时,为了减少 I/O 口的占用,通常将按键排列成矩阵形式,采用逐行或逐列的 “扫描”,就可以读出任何位置按键的状态
扫描
以上两种扫描方式的共性:节省 I/O 口
单片机 IO 口模式
为什么单片机它的 IO 口是默认为高电平呢?
6.2 矩阵键盘原理图
6.3 使用示例
#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__
unsigned char MatrixKey();
#endif
#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;
}
#include <REGX52.H>
#include "Delay.h" // 包含 Delay 头文件
#include "LCD1602.h" // 包含 LCD1602 头文件
#include "MatrixKey.h" // 包含矩阵键盘头文件
unsigned char KeyNum;
void main() {
LCD_Init(); // LCD 初始化
LCD_ShowString(1, 1, "MatrixKey:"); // LCD 显示字符串
while (1) {
KeyNum = MatrixKey(); // 获取矩阵键盘键码
if (KeyNum) { // 如果有按键按下
LCD_ShowNum(2, 1, KeyNum, 2); // LCD 显示键码
}
}
}
6.4 矩阵键盘密码锁
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned char KeyNum;
unsigned int Password, Count;
void main() {
LCD_Init();
LCD_ShowString(1, 1, "Password:");
while (1) {
KeyNum = MatrixKey();
if (KeyNum) {
if (KeyNum <= 10) { // 如果 S1~S10 按键按下,输入密码
if (Count<4) { // 如果输入次数小于 4
Password *= 10; // 密码左移一位
Password += KeyNum % 10; // 获取一位密码
Count++; // 计次加一
}
LCD_ShowNum(2, 1, Password, 4); // 更新显示
}
if (KeyNum == 11) { // 如果 S11 按键按下,确认
if (Password == 2345) { // 如果密码等于正确密码
LCD_ShowString(1, 14, "OK "); // 显示 OK
Password = 0; // 密码清零
Count = 0; // 计次清零
LCD_ShowNum(2, 1, Password, 4); // 更新显示
} else {
LCD_ShowString(1, 14, "ERR"); // 显示 ERR
Password = 0; // 密码清零
Count = 0; // 计次清零
LCD_ShowNum(2, 1, Password, 4); // 更新显示
}
}
if (KeyNum == 12) { // 如果 S12 按键按下,取消
Password = 0; // 密码清零
Count = 0; // 计次清零
LCD_ShowNum(2, 1, Password, 4); // 更新显示
}
}
}
}