MCU-Study

MCU-Study

jrl Lv3

Contents

[TOC]

Part1–初识MCU

一、Preparation

1. Two applications

Keil5(编写程序)

(1)从官网下载Keil5

(2)从License Magement中复制你的CID并将其输入keygen中激活

注意,以管理员身份运行程序,否则权限不够。 AND 在打开Keygen或者下载前,关闭防火墙,否则有一定概率误杀

(3)点击Generate,完成激活

Stc-isp(下载程序)

这个自行搜索下载

在成功下载之后,一般来说无法直接读取或识别外接的C51,这个时候需要安装相应的driver:

这时候如果在任务管理器中能够能看到USB-SERIAL,说明驱动安装成功,C51已被读取。

x def phase5(n1, c1, n2, c2, n3, c3):    r = CRT([c1, c2, c3], [n1, n2, n3])    m = int(r)^(1/3)    print(hex(m)[2:])    n1 = 78642188663937191491235684351005990853149481644703243255021321296087539054265733392095095639539412823093600710316645130404423641473150336492175402885270861906530337207734106926328737198871118125840680572148601743121884788919989184318198417654263598170932154428514561079675550090698019678767738203477097731989c1 = 23419685303892339080979695469481275906709035609088426118328601771163101123641599051556995351678670765521269546319724616458499631461037359417701720430452076029312714313804716888119910334476982840024696320503747736428099717113471541651211596481005191146454458591558743268791485623924245960696651150688621664860n2 = 98174485544103863705821086588292917749386955237408645745685476234349659452606822650329076955303471252833860010724515777826660887118742978051231030080666542833950748806944312437614585352818344599399156268450521239843157288915059003487783576003027303399985723834248634230998110618288843582573006048070816520647c2 = 72080679612442543693944655041130370753964497034378634203383617624269927191363529233872659451561571441107920350406295389613006330637565645758727103723546610079332161151567096389071050158035757745766399510575237344950873632114050632573903701015749830874081198250578516967517980592506626547273178363503100507676n3 = 91638855323231795590642755267985988356764327384001022396221901964430032527111968159623063760057482761918901490239790230176524505469897183382928646349163030620342744192731246392941227433195249399795012672172947919435254998997253131826888070173526892674308708289629739522194864912899817994807268945141349669311c3 = 22149989692509889061584875630258740744292355239822482581889060656197919681655781672277545701325284646570773490123892626601106871432216449814891757715588851851459306683123591338089745675044763551335899599807235257516935037356212345033087798267959242561085752109746935300735969972249665700075907145744305255616​phase5(n1,c1,n2,c2,n3,c3)# 464c41477b325e3872736138633566336366663462633039353334396665633635666332323633653837387dpython

二、About MCU

1. Brief introduction

单片机,英文Micro ControIIer Unit , 简称MCU

  1. 内部集成了CPU 、RAM 、ROM 、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能
  2. 单片机的任务是信息采集( 依靠传感器) 、处理( 依靠CPU) 和硬件设备( 例如电机, LED 等) 的控制
  3. 单片机跟计算机相比, 单片机算是一个袖珍版计算机, 一个芯片就能构成完整的计算机系统。但在性能上, 与计算机相差甚远, 但单片机成本低、体积小、结构简单, 在生活和工业控制领域大有所用。同时, 学习使用单片机是了解计算机原理与结构的最佳选择

51单片机:指80年代Intel开发的8051内核的单片机的统称。

*类比电脑内存条*:RAM(Random Access Memory,随机存取存储器)是计算机中的一种主要存储设备,用于临时存储和快速访问数据。它是计算机的一个重要组成部分,用于存储正在执行的程序和数据。

RAM与计算机的硬盘驱动器(或固态驱动器)不同,后者用于长期存储数据,而RAM是一种易失性存储器,意味着其存储的数据在断电或重新启动后会被清除。这是因为RAM是基于电子器件和电路构建的,需要持续的电源供应来保持存储的数据。

RAM被分为主存储器(Main Memory)和高速缓存(Cache Memory)。主存储器是计算机直接访问的存储区域,用于存储正在运行的程序和数据。它的存取速度比硬盘驱动器等次要存储设备要快得多。高速缓存是位于CPU内部的一种更快速的存储器,用于临时存储处理器频繁访问的数据,以提高计算机的性能。

RAM的容量通常以兆字节(MB)或千兆字节(GB)进行衡量。较大容量的RAM可以容纳更多的程序和数据,从而提供更好的性能和多任务处理能力。计算机的RAM容量可以根据需要进行扩展或升级。

总之,RAM是一种临时存储设备,用于存储正在执行的程序和数据,提供计算机的实时访问能力。它在计算机的性能和多任务处理方面起着重要的作用。

*类比电脑硬盘*:ROM(Read-Only Memory)是一种计算机芯片或存储设备中的一种存储器类型。与随机访问存储器(RAM)相比,ROM在断电后能够保持其存储的数据不变,因此也被称为非易失性存储器。ROM的数据一般是由制造厂商在生产过程中预先写入的,并且用户无法对其进行修改。这意味着ROM中存储的内容是只读的,无法被擦除或重写。

ROM有多种不同类型,包括:

  1. PROM(Programmable Read-Only Memory,可编程只读存储器):这种ROM允许用户在一次性编程之前将数据写入其中。一旦编程完成,数据将永久固化在芯片中,不可更改。
  2. EPROM(Erasable Programmable Read-Only Memory,可擦除可编程只读存储器):这种ROM允许用户通过使用特定设备将其数据擦除,然后再次编程。擦除通常通过使用紫外线光线或电子擦除器来进行。
  3. EEPROM(Electrically Erasable Programmable Read-Only Memory,电可擦除可编程只读存储器):EEPROM与EPROM类似,但是擦除操作可以通过电源供电而不需要其他特殊设备。EEPROM的擦除和编程可以在特定的操作条件下进行。

ROM的应用非常广泛,它被用于存储启动引导程序、固件、芯片内的固定数据以及其他一些需要在断电后保持不变的数据。有些游戏机、手机和电脑主板中也使用ROM存储固件或操作系统。总之,ROM在计算机和电子设备中起到了重要的作用,并且因其稳定性和数据不易丢失的特点而受到广泛使用。

2. Application field

单片机的使用领域已十分广泛, 如智能仪表、实时工控、通讯设备、导航系统、家用电器等。各种产品一旦用上了单片机, 就能起到使
产品升级换代的功效, 常在产品名称前冠以形容词——“智能型”,如智能型洗衣机等。

3. Naming rules

4. Structure

单片机内部结构图:

单片机管脚图

例如 Vcc代表 + 极,Gnd代表 - 极

单片机的最小系统:

电容——过滤因为电源不稳定产生的电磁波

复位电路(中间的)——复位电路通常用来清除存储器中的数据、关闭所有开关和重置相关的逻辑电路。它可以在系统启动时,或在错误发生时,通过将相关电路重新初始化,确保系统处于可控状态。

晶振(左下角)——用于时钟电路和振荡电路中,以稳定电子设备的工作频率。晶振的主要原理是利用压电效应,在晶体振荡器中产生稳定的振荡信号。推动程序往下进行的关键。

Part2–编写MCU

一、Light up LED

LED,全称为Light Emitting Diode(发光二极管),是一种固体电子器件,可以将电能直接转化为光能。相对于传统的光源,如白炽灯和荧光灯,LED具有更高的能效、更长的使用寿命和更大的可靠性。

LED的工作原理是基于半导体材料的特性。当电流通过LED时,电子和空穴在半导体材料中重新组合,产生能量释放,从而产生可见光。不同半导体材料的能带结构决定了LED发出的光的颜色。

LED具有许多优点。首先,LED保存能量且效率高,相比传统光源,LED产生的光功率更大,但消耗的电能更少。其次,LED寿命长,通常可以达到数万个小时,远远超过传统光源。此外,LED具有快速开启和关闭的特性,并且可以根据需要调节亮度,因此被广泛应用于照明和显示领域。

LED在各个领域有广泛的应用。在照明领域,LED被用于家庭照明、商业照明和街道照明等。由于其高效节能的特性,LED也被用于太阳能照明和绿色建筑项目中。在电子显示领域,LED用于制造各种显示屏,如电视、计算机显示器和手机屏幕。此外,LED还常用于车辆照明、室内装饰、电子设备指示灯等。

1. 点亮一个LED

首先观察MCU里的LED模块

右边图片中的RP9、RP10是两个电阻 102=1k(10*10^2)元器件上的标号以此类推

P2口下的寄存器控制LED: 1–5V 不亮 0–0V 亮


  1. 创建一个Project,在列表里的Atmel下选择AT89C52
  1. 在该文件夹下创建一个C的文件,以便进一步编写程序

3.编写控制LED的代码

1
2
3
4
5
6
#include <REGX52.H>

void main()
{
P2=0xFE;
}

4.将文件保存为hex格式,以便烧入

5.用stc-isp将程序烧入进MCU中,尝试运行

选择相应的单片机型号与串口号,并打开已写好的程序文件

然后点击下载,重启单片机,发现成功点亮LED

2. LED闪烁

重复(1)中的操作步骤,新建一个Project

其实要完成闪烁只需要写一个while循环就可以了

1
2
3
4
5
6
7
8
9
10
11
12
#include <REGX52.H>

void main()
{

while(1)
{
P2=0xFE;
P2=0xFF;
}

}

但这个时候发现LED闪烁周期过快,肉眼无法观察到明显现象,这个时候需要加大周期,即加入延时函数。

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
#include <REGX52.H>
#include <INTRINS.H>

void Delay500ms() //@12.000MHz
{
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; //0xAA 左右闪烁
Delay500ms();
P2=0xFF; //0x55
Delay500ms();
}

}

3. LED流水灯

有了“前车之鉴”,再写流水灯其实就很简单了,逻辑是一样的。

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
#include <REGX52.H>
#include <INTRINS.H>

void Delay250ms() //@12.000MHz
{
unsigned char i, j, k;

_nop_();
i = 2;
j = 231;
k = 91;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}


void main()
{
while(1)
{
P2=0xFE;
Delay250ms();
P2=0xFD;
Delay250ms();
P2=0xFB;
Delay250ms();
P2=0xF7;
Delay250ms();
P2=0xEF;
Delay250ms();
P2=0xDF;
Delay250ms();
P2=0xBF;
Delay250ms();
P2=0x7F;
Delay250ms();
}
}

Pro版本:

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
#include <REGX52.H>

int n=50;
void Delay1ms(unsigned int xms) //@12.000MHz
{
unsigned char i,j;
while(xms)
{
i=2;
j=239;
do
{
while(--j);
} while(--i);
xms--;
}
}


void main()
{
while(1)
{
P2=0xFE;
Delay1ms(n);
P2=0xFD;
Delay1ms(n);
P2=0xFB;
Delay1ms(n);
P2=0xF7;
Delay1ms(n);
P2=0xEF;
Delay1ms(n);
P2=0xDF;
Delay1ms(n);
P2=0xBF;
Delay1ms(n);
P2=0x7F;
Delay1ms(n);
}
}

二、Individual key

C51上的独立按键:底座+金属弹片+ +

不按下时4个引脚两两连接,按下去时4个引脚同时连接

独立按键:

4个独立按键一端接 -极,另一端接I/O口(I/O口默认高电平

寄存器检测I/O口电平

1. 独立按键控制LED亮灭

sfr:整个8位的寄存器

寄存器中P2表示8位一体,不可单独控制单独位次

发现sbit可控制单独位次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <REGX52.H>

void main()
{
//P2=0xFE;

while(1) //单独控制P2的第0根管脚
{
if(P3_1==0)
{
P2_0=0; //读取K1口寄存器
}
else
{
P2_0=1;
}
}

}

2. 独立按键控制LED状态

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
#include <REGX52.H>

void Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}


void main()
{
while(1)
{
if(P3_1==0) //检测按键是否按下,按下不操作,松手才开始操作(类比于电脑主表操作原理)
{
Delay(20);
while(P3_1==0);
Delay(20);

P2_0=~P2_0; //按位取反 P2_0只有一位,按位取反在0,1间变化
}
}
}


3. 独立按键控制LED显示二进制

项目的创建以及准备工作就不再赘述

主要是利用 unsigned char 类型(0—255)与寄存器同样为8位的特性,来间接表示LED二进制

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
#include <REGX52.H>

void Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 12;
j = 169;
do
{
while (--j);
} while (--i);
}
}


void main()
{
unsigned char LEDNum=0;
while(1)
{
if(P3_1==0)
{
Delay(20);
while(P3_1==0);
Delay(20);
LEDNum++; //P2++;不行
P2=~LEDNum;
}
}
}

P2=1111 1111 //P2口默认高电平

P2++ 会使端口溢出,P2变成 0000 0000

再次取反P2将保持原样,即P2=1111 1111 ,表现为D1—D8全灭

4. 独立按键控制LED移位

项目预期:按下P3_1(左1),实现LED从左向右移位1

​ 按下P3_0(左2),实现LED从右向左移位1

[^如图所示]:

实现移位的代码如下,主要使用C语言中的位运算:**<< 按位左移** 与 >>按位右移

P3_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
#include <REGX52.H>

void Delay(unsigned int xms);
unsigned char LEDNum=0;

void main()
{

while(1)
{
if(P3_1==0)
{
Delay(20);
while(P3_1==0);
Delay(20);

if(LEDNum>7)
{
LEDNum=0;
}
P2=~(0x01<<LEDNum);
LEDNum++;
}
}
}


void Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 12;
j = 169;
do
{
while (--j);
} while (--i);
}
}

与左移逻辑一样,加上右移,完善代码,实现项目预期

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
#include <REGX52.H>

void Delay(unsigned int xms); //申明函数
unsigned char LEDNum=0; //8位的全局变量

void main()
{
P2=~0x01; //点亮D1
while(1)
{
if(P3_1==0) //判断按键是否按下
{
Delay(20);
//检测按键状态,即手是否在按下按键后抬起。若未抬起,则按键处于闭合状态,执行while死循环,防止误触
while(P3_1==0);
Delay(20);

LEDNum++;
if(LEDNum>=8)
{
LEDNum=0;
}
P2=~(0x01<<LEDNum);
}

if(P3_0==0) //判断按键是否按下
{
Delay(20);
while(P3_0==0);
Delay(20);

if(LEDNum==0)
{
LEDNum=7;
}
else
{
LEDNum--; //自减,实现右移
}
P2=~(0x01<<LEDNum);
}
}
}

//延时函数
void Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 12;
j = 169;
do
{
while (--j);
} while (--i);
}
}

三、Static digitron display

(1)数码管介绍

控制数码管显示:

138译码器和数码管,两者共同作用下控制数码管的显示

(2)数码管引脚定义:

1. 静态数码管显示

目标是使数码管的第三位单独显示数字“6”

步骤为:

1.控制138译码器选中LED6(标号从右到左) Y5—101

2.给予P0口段码数据,经过信号缓冲,再传输至公共段码端

总共有以下几种实现方式:

1.共阴极连接

2.共阳极连接:

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//第三位数码(LED6)管显示“6”
#include <REGX52.H>

void main()
{
P2_4=1; // 101=5 LED6由Y5口控制,通过P_2,P_3,P_4口控制Y5输出低电平来点亮LED6
P2_3=0;
P2_2=1;
P0=0x7D;
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
#include <REGX52.H>

unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};

void Nixie(unsigned char Location,Number); //首先控制P2口选中LED1~8中之一,再给P0口断码数据,选择显示的数字
void main()
{
Nixie(2,3);
while(1)
{

}
}

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];
}

2. 动态数码管显示

利用辉光现象造成的人眼视觉残留,不断扫描数码管,来动态显示多位数字。

可看成静态数码管的快随迭代显示。

控制原理

LED1–8的一端直接连在138译码器的输出端

(1) 138译码器

实际上就是用3个输入口控制8个输出口,以达到减少I/O口的目的,进而提高工作效率

P2_3口,P2_4 口,P2_5口,是”二进制“

(2) 74HC245:

74HC245(双向数据缓冲器)其实是芯片,筛选弱信号通过芯片,过滤掉强信号

因为低电平的驱动能力强(LED在低电平驱动时远亮与被高电平驱动时)

排阻:


动态显示原理

控制数码管单独静态显示:

动态数码管显示就是轮流“静态”显示,利用人的视觉残留从而实现动态效果

消影

即使能够利用视觉残留达成动态显示的效果,实际上依旧存在”窜位“问题

视觉上”1、2、3“,依旧在闪烁

这时就需要消影

综述

单片机直接扫描数码管:

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
#include <REGX52.H>

// 段码表
unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};

// 动态数码管显示函数
void Nixie(unsigned char Location,Number);
// 延时函数
void Delay(unsigned int xms);

void main()
{

while(1)
{
Nixie(1,1);
//Delay(0.002);
Nixie(2,2);
//Delay(0.002);
Nixie(3,3);
//Delay(0.002);
}
}


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; //消影,即在段选和位选之间加入清零操作 排除窜位影响,即使窜位也没关系
}


void Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}



四、模块化编程及LCD1602调试工具

1.模块化编程

(1) 介绍

模块化的编程可以提升效率,避免主函数显得冗长,使代码容易阅读。

(2) 框图表示

模块化编程即使就是一个预编译的过程,例如在C语言中经常使用的

You can't use 'macro parameter character #' in math mode #include<stido.h> 头文件

实际上,就是把函数的定义与声明分开来,使接口和实现分离开来

(3) 预编译

一般来说,预编译的实现如下:

有两个文件

一个是以结尾的头文件(用于声明函数)

还有一个是以结尾的C语言文件(用于定义函数)

.h

1
2
3
4
5
6
7
// 头文件声明Delay函数
#ifndef __DELAY_H__ // if not define
#define __DELAY_H__ // define #ifndef和#define的意义:防止重复包含,使函数只被定义一次,提高效率

void Delay(unsigned int xms);

#endif

.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// C定义Delay函数
void Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}

(4) 注意事项

例如:

调用的函数一定要声明,否则会报错

1
2
3
4
5
6
7
#ifndef __NIXIE_H__
#define __NIXIE_H__

void Nixie(unsigned char Location,Number);


#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <REGX52.H>
#include "Delay.h" // 在数码管Nixie函数的定义中使用了Delay函数,那么在定义时必须引用"Delay.h"

unsigned char NixieTable[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,0x00};

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; //消影,即在段选和位选之间加入清零操作 排除窜位影响,即使窜位也没关系
}

2. LCD1602调试工具

在插入LCD显示屏后由于引脚的冲突,数码管将不再亮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <REGX52.H>
#include "LCD1602.h"

void main()
{
LCD_Init(); //初始化
LCD_ShowChar(1,1,'A'); //在几行几列显示单个字符
LCD_ShowString(1,3,"Hello"); //显示字符串
LCD_ShowString(1,9,"ZZH");

LCD_ShowNum(2,1,123,3); //显示3位 若显示4位,则为0123,2位为23
LCD_ShowSignedNum(2,5,-66,2); //符号位不算在内
while(1)
{

}


}

每秒加1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"

int a=0;

void main()
{
LCD_Init(); //初始化
while(1)
{
a++;
Delay(1000);
LCD_ShowNum(1,1,a,3); //每秒+1
}

}

五、 矩阵键盘

1. MatrixKey简介

(1) 矩阵按键

这种矩阵按键能很好的节省I/O口,提升工作效率。

工作方式

若有多行/列会无法判断

I/O口原理

C51的I/O口是“弱上拉,强下拉”模式

手绘简图介绍:

(2) 扫描

数码管快速扫描,但是会占用CPU的时间

显示器/手机屏幕:(利用矩阵的方式)对横竖交错的像素点进行扫描

(3) MatrixKey代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <REGX52.H>
#include "Delay.h"

unsigned char MatrixKey()
{
unsigned char KeNumber=0;

// 第一列置“0”
P1=0xFF;
P1_3=0;
//s1的检测
if(P1_7==0) //判断s1是否按下
{
Delay(20);
while(P1_7==0) //消抖,判断s1是否抬起 为“1”时才跳出死循环
{
}
Delay(20);
KeyNumber=1;
}
return KeyNumber;
}
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
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"

/**
* @brief 矩阵键盘读取按键键码
* @param 无
* @retval KeyNumber 按下按键的键码值
如果按键按下不放,程序会停留在此函数,松手的一瞬间,返回按键键码,没有按键按下时,返回0
*/

unsigned char KeyNumber;

void main()
{
LCD_Init();
LCD_ShowString(1,1,"Hello ZZH");
while(1)
{
KeyNumber=MatrixKey();
if(KeyNumber)
{
LCD_ShowNum(2,1,KeyNumber,2);
}
}

}

2. 矩阵键盘密码锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Password:");
while(1)
{
KeyNumber=MatrixKey();
if(KeyNumber)
{
if(KeyNumber<=10) //如果按键S1~S10按键按下,输入密码
{
Password*=10; //密码左移一位
Password+=KeyNumber%10; //对10取余,目的是使10被定义为0 获取一位密码
}
LCD_ShowNum(2,1,Password,4);
}
}

}

// 第一位 0001 乘10 0010 实现左移

简易的密码锁代码L:

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
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"

unsigned char KeyNumber;
unsigned int Password,Count;

void main()
{
LCD_Init();
LCD_ShowString(1,1,"Password:");
while(1)
{
KeyNumber=MatrixKey();
if(KeyNumber)
{
if(KeyNumber<=10&&Count<4) //如果按键S1~S10按键按下,输入密码 控制密码为4位
{
Password*=10; //密码左移一位
Password+=KeyNumber%10; //对10取余,目的是使10被定义为0 获取一位密码
Count++; //计次加一
}
LCD_ShowNum(2,1,Password,4); //更新显示

if(KeyNumber==11) //如果s11按下,确认密码
{
if(Password==521)
{
LCD_ShowString(1,14,"OK ");
//密码和计次清0

}
else{LCD_ShowString(1,14,"ERR");}
Password=0;
Count=0;
LCD_ShowNum(2,1,Password,4); //更新显示
}

//按下取消
if(KeyNumber==12)
{
Password=0;
Count=0;
LCD_ShowNum(2,1,Password,4); //更新显示
}
}

}

}

六、 定时器

1. 定时器

(1)简介

(2)工作原理及模块

工作模式
工作模块

时钟

计数系统和中断系统

整体

定时器时钟

定时器0的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
void Timer0Init(void)		//1毫秒@12.000MHz
{
AUXR &= 0xF0; //定时器时钟1T模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;
EA=1;
PT0=0;
}

中断系统

流程图如下:

单片机中的中断资源


注意:

1.中断函数一般都比较“短”,执行的都是一些简单的自增或是赋值操作,因为时间较长的话会影响main函数,即不分主次

2.中断函数不能有形参和返回值

1
2
3
4
5
//中断子程序,加上“interrupt+中断号” 普通子函数就变成了终端服务子函数
void Timer0_Routine() interrupt 1
{

}

2. 按键控制LED流水灯模式&定时器时钟

按键控制LED流水灯模式

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
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include <INTRINS.H>

unsigned char KeyNum,LEDMode;

void main()
{
P2=0xFE; //给初值,最低位给0,点亮最低位LED
Timer0Init();
while(1)
{
KeyNum=Key(); //获取独立按键键码
if(KeyNum) //如果按键按下
{
if(KeyNum==1) //如果K1按键按下
{
LEDMode++; //模式切换,进行0,1循环
if(LEDMode>=2)LEDMode=0;
}
}
}
}

void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值,清空计时器
TH0 = 0xFC; //设置定时初值
T0Count++; //T0Count计次,对中断频率进行分频
if(T0Count>=500)//分频500次,500ms
{
T0Count=0;
if(LEDMode==0) //模式判断
P2=_crol_(P2,1); //LED输出 crol--循环左移 cror--循环右移
if(LEDMode==1)
P2=_cror_(P2,1);
}
}

注意

1
2
_crol_( , )  
这两个函数均为循环移位函数,可循环(左/右)移动二进制位 0x01 》0x02》0x04 若移到最高位则循环

定时器时钟代码实现

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
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"

unsigned char Sec=58,Min=56,Hour=22;

void main()
{
LCD_Init();
Timer0Init();
LCD_ShowString(1,1,"Clock:");
LCD_ShowString(2,1," : :");

while(1)
{
LCD_ShowNum(2,7,Sec,2);
LCD_ShowNum(2,4,Min,2);
LCD_ShowNum(2,1,Hour,2);
}

}


//定时器中断函数模板
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=1000)
{
T0Count=0;
Sec++;
if(Sec>=60)
{
Sec=0;
Min++;
}
if(Min>=60)
{
Min=0;
Hour++;
}
if(Hour>=24)
{
Hour=0;
}
}
}

七、串口

1.串口通信

2.串口向电脑发送数据&电脑通过串口控制LED

串口每隔一秒向电脑发送数据

串口初始化函数模块
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
#include <REGX52.H>

/**
* @brief 串口初始化
* @param 无
* @retval 无
*/
void UART_Init() //4800bps@11.0592MHz
{
SCON=0x40; //使能波特率倍速位SMOD
PCON|=0x80; //8位数据,可变波特率
TMOD &= 0x0F; //设置定时器模式,清除定时器1模式位
TMOD |= 0x20; //设置定时器模式,设置定时器1为8位自动重装方式
TL1 = 0xF4; //设置定时初值
TH1 = 0xF4; //设置定时初值
ET1= 0; //禁止定时器1中断
TR1=1; //启动定时器1

}




/**
* @brief 串口发送一个字节数据
* @param Byte 要发送的一个字节数据
* @retval 无
*/
void UART_SendByte(unsigned char Byte)
{
SBUF=Byte; //只需要将数据写入SBUF,她就会自动发送
//检测是否完成
while(TI==0);
TI=0;
}

主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"

unsigned char Sec;

void main()
{
UART_Init(); //循环之前进行初始化

while(1)
{
UART_SendByte(Sec);
Sec++;
Delay(1000);
}

}

电脑通过串口控制LED

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
#include <REGX52.H>
#include "Delay.h"
#include "UART.h"



void main()
{
UART_Init(); //循环之前进行初始化

while(1)
{

}

}


void UART_Routine() interrupt 4
{
//判断是否是“读SBUF”,即接受中断 因为发送也会触发中断,为了区分开发送与接受
if(RI==1)
{
P2=~SBUF;
UART_SendByte(SBUF);
RI=0; //软件检测1后清零
}

}

八、LED点阵屏

点阵屏显示笑脸:

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
#include <REGX52.H>
#include "Delay.h"


sbit RCK=P3^5; //RCLK
sbit SCK=P3^6; //SRCLK
sbit SER=P3^4; //SER

#define MATRIX_LED_PORT P0 //定义P0口,因为它是一个寄存器(不能进行sbit声明)便于后续统一操作(只需修改串口,如P0)
/**
* @brief 74HC595写入一个字节
* @param 要写入的一个字节
* @retval 无
*/

//将参数写入8个引脚,给其赋值
void _74HC595_WriteByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
SER=Byte&(0x80>>i); //最高位为1时赋值为0x80......
SCK=1; //给高电平上推,上升沿移位
SCK=0; //清零
}
RCK=1; //8位数据移位
RCK=0;

}

/**
* @brief LED点阵屏显示一列
* @param Column 要选择的列,范围:0~7,0在最左边
* @retval Data 选择的列所显示的数据,高位在上,1为亮,0为灭
*/

void MatrixLED_ShowColumn(unsigned char Column,Date)
{
_74HC595_WriteByte(Date);
MATRIX_LED_PORT=~(0x80>>Column); //取反,保持0亮,1灭
Delay(1);
MATRIX_LED_PORT=0xFF; //位选清零,防止串位
}


int main()
{
SCK=0;
RCK=0;
while(1)
{
MatrixLED_ShowColumn(0,0x3C);
MatrixLED_ShowColumn(1,0x42);
MatrixLED_ShowColumn(2,0xA9);
MatrixLED_ShowColumn(3,0x85);
MatrixLED_ShowColumn(4,0x85);
MatrixLED_ShowColumn(5,0xA9);
MatrixLED_ShowColumn(6,0x42);
MatrixLED_ShowColumn(7,0x3C);

}


}

使用文字取模软件来读取点阵数据

横向取模,高位在上(不用选取字节倒序)

C51自动生成

动态动画显示:

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
#include <REGX52.H>
#include "Delay.h"
#include "MatrixLED.h"

// unsigned char code Animation 这里加上code后会把数组存储在Flash中,内存更大,但是,不可写入,只能读取(原来存储在RAM中,有溢出的风险)
unsigned char Animation[]={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFF,0x18,0x18,0x18,0xFF,0x00,0x0E,0x15,
0x15,0x15,0x09,0x00,0x7E,0x01,0x02,0x00,
0x7E,0x01,0x02,0x00,0x0E,0x11,0x11,0x0E,
0x00,0x7D,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
//存放动画的数据,逐帧偏移即可实现动态效果



int main()
{
unsigned char i,Offset,Count=0;
MatrixLED_Init();
while(1)
{
//显示静态图像
for(i=0;i<8;i++)
{
MatrixLED_ShowColumn(i,Animation[i+Offset]);
}
Count++;
//延时
if(Count>10)
{
Count=0;
Offset++; //逐帧移动
if(Offset>40)
{
Offset=0;
}
}

}

}

九、DS1302实时时钟

1
111
  • Title: MCU-Study
  • Author: jrl
  • Created at: 2023-07-15 21:33:22
  • Updated at: 2023-10-03 15:45:47
  • Link: https://jrl777.github.io/2023/07/15/MCU-Study/
  • License: This work is licensed under CC BY-NC-SA 4.0.
 Comments