单片机入门学习1

51单片机入门学习记录(1)

教程:江科协

目录:

[LED](#1.4 LED)

[独立按键](#1.5 独立按键)

[数码管](#1.10 数码管)

模块化编程

[矩阵键盘](#1.12 矩阵键盘)

[定时器](#1.14 定时器)

[串口](#1.18 串口)

1.1

安装kail和stc-isp 买了块板子

1-2视频还没有连接板子(板子刚下单

1.4 LED

AT89C52!!!

单片机到了 连接串口安装驱动程序CH341ser 一开始一直显示预安装 搜了很多帖子也没找到解决方案

在等官方客服回复中摸鱼了会 一个小时后再次回来连接惊喜的连接成功了

1.12回顾:应该是usb线没有连接好的问题,后续很多时候都是正常的,只有在usb未完全插入电脑的时候会提示检测未知

听了很多的硬件内部介绍 听不懂

要照明第一个LED灯:

点亮第一个LED

image-20230104210911586

1
P2 = 1111 1110

0赋给P20口(最低位)——操作LED 写代码的时候要转化为十进制或者十六进制

1
P2=0xFE //1111 1110

代码下载入单片机中 如下操作:

image-20230104210631295

image-20230104210749530

但是P2命令会一直被执行 我们需要让他持续 而不是重复执行命令

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

void main(){
P2=0xFE; //1111 1110
while(1){

}

亮不同的灯:

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

void main(){
P2 = 0xFE; 1111 1110
P2=0xA5;//1010 0101
while(1){
}
}

以1s为周期闪烁

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

void main(){

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

此时单片机并没有按预期闪烁 而是减小了亮度 原因为单片机的速度是Mhz 速度很快 肉眼无法分辨

所以我们需要在两个代码里执行一定的延时

image-20230104212517191

这里选错了 应该选毫秒…

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 <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;
Delay500ms();
P2 = 0xFF;
Delay500ms();
}
}

成功咯!

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 <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;//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();
}
}

以200ms为变化间隔 实现

1.5 独立按键

独立按键

按下点亮 弹起熄灭

电子开关 按下时接通 送开始断开 通过内部的金属弹片受力弹动

按下为低电平(与GND相连)

image-20230105112118646

源代码 P2 = 0xFE;要同时给八个赋值 只想操控最低位

image-20230105113023929

sbit可以操纵位寄存器

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

void main(){
// P2 = 0xFE;
P2_0 = 0;
while(1){

}
}

要实现电子元件控制:

注意 K1和K2 的接脚不是按照顺序 K1是接在P3_1的口上

image-20230105114234639

image-20230105114319582

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

void main(){

while(1){
if(P3_1 == 0){//按下K1
P2_0 = 0;//点亮LED1
}
else{
P2_0 = 1;//熄灭LED1
}
}
}

按键的抖动

由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地连接,在断开时也不会一下子断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动

image-20230105122610824

实现按键的消抖:硬件的过滤、软件程序处理(检测)

以及检测松手

按下弹起一个周期后在执行命令
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
#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);
}
}

void main(){
while(1){
if(P3_1==0){
Delay(20);//过滤按下抖动
while(P3_1==0);//检测松手
Delay(20);//过滤松开抖动

P2_0 =~P2_0;
}
}
}

但是刚刚不小心打成了P3_0==0 灯是按下后不停闪烁 其实这是一个无效语句 真正实现的内核是:

1
2
3
4
5
6
7
8
9
10
void main(){
while(1){
if(P3_1==0){
Delay(20);//过滤按下抖动
Delay(20);//过滤松开抖动

P2_0 =~P2_0;
}
}
}

只要按下P2_0会不停地取反 灯就不停地闪烁

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

P2上电默认都是高电平

方案一:

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>

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


void main(){
//unsigned char LEDnumber;
while(1){
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);


P2--;
}
}
}

方案二:

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>

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


void main(){
unsigned char LEDnumber=0;//一般用unsigned char表示寄存器(八位)
while(1){
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);

//P2--;
LEDnumber++;
P2 =~LEDnumber;
}
}
}

独立按键控制LED移位

一个开关左右移动

原本想要更简洁一些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main(){
P2 = ~0x01;
int i;
while(1){
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);
}

for(i=0;i<8;i++){
P2=~(0x01<<i);
}
}
}

发现运行后 LED全灯都是小亮度亮的 只有最后一个灯在按下按钮的时候是常亮的 发现是在一次while循环里 立马就执行了移位 延时函数失效

修改程序

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 = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}

void main(){
unsigned char LEDnumber;
P2 =~0x01;
while(1){
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);

LEDnumber++;
if(LEDnumber>=8){
LEDnumber=0;
}
P2=~(0x01<<LEDnumber);
}
}
}
实现两个开关左右移动:
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>

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

unsigned char LEDnumber;//最好定义全局变量
void main(){
P2 =~0x01;
while(1){//右移动
if(P3_1==0){
Delay(20);
while(P3_1==0);
Delay(20);

LEDnumber++;
if(LEDnumber>=8){
LEDnumber=0;
}
P2=~(0x01<<LEDnumber);
}
if(P3_0==0){//左移动
Delay(20);
while(P3_0==0);
Delay(20);
if(LEDnumber==0){
LEDnumber = 7;
}
else{
LEDnumber--;
}
P2=~(0x01<<LEDnumber);
}
}
}

呜呜呜还是有很多代码的小细节要注意的

注意:unsigned char LEDnumber;//最好定义全局变量默认是0 在main里定义可能不是0 在初始定义时好像不能赋初始值 会报错 呜呜呜已经把这些细碎知识点忘光了

1.10 数码管

数码管:多个发光二极管封装在一起组成“ 8 ”字型的器件

在数码管上任意位置显示任意数据(静态显示)

image-20230110195522620

引脚就近原则

要点亮一个数字 以“6”为例子

无标题

开发板上有两个四位一体的数码管

1

138编码器

134

注意Y0对应LED1(有错位)

数码管

嘿嘿

74HC254是一个双向数据缓冲,为了提高单片机的负载能力 (能够带动很多LED灯)

DIR(连接LE)的输入为高电平时,从左向右缓冲数据,反之,从右向左读取数据

而左边部分与138编码器相连接,目的是为了使得单片机提供的为控制信号,缓冲到右边,由电源VCC直接提供能量控制LED灯

主要是因为高电平驱动能力弱(低电平驱动能力强)

使某一位显示一个数据:

111111111222222222333333333333

以LED6显示6为例

123

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

void main(){
P2_4 = 1;
P2_3 = 0;
P2_2 = 1;
P0 = 0x7D;
while(1){

}
}

07a4e4660eb9751ed31e6b99baeaf86

LED模块因为引脚冲突所以也会亮

把数码管的某一位显示某一个数字进行封装,抽取出变成子函数以达到代码的复用和优化程序结构的目的

参数:位置数字

一般用数组显示段码

段码表:

1
2
3
0  1  2  3  4  5  6  7  8  9  

0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,

优化程序结构:

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

unsigned char NixieTable[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};

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(1,1);
while(1){

}
}

image-20230110215647695

数码管的动态显示 任意位置 多显示(动态扫描)

不断地扫描

不添加延时的话会出现这样的情况:

故而数码管需要 消影

位选—>段选—>位选—>段选—>位选—>段选—>位选—>段选

前一个段选到下一个位选很短的时刻 上一个数据会窜到下一个数据上!!!(段没变 位变了)

所以要

位选—>段选—>清零—>位选

但不能立马清零 立马清零数码管会变得比较暗

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>

unsigned char NixieTable[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};

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

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 main(){

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

image-20230110221227515

数码管驱动方式:

单片机直接扫描:硬件设备简单,但会耗费大量的单片机CPU时间;

专用驱动芯片:内部自带显存、扫描电路,单片机只需告诉它显示什么即可 GM1640

1.11模块化编程

模块化编程

避免所有函数都放在main函数里

要将不同模块的代码放在不同的c文件里

在.h文件里提供外部可调用函数的声明

预编译:

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

void Delay(unsigned int xms);

#endif

•此外还有#ifdef,#if,#else,#elif,#undef

代码模块化实例

image-20230111221933504

include优先到标准库中去搜索模块。

include"file"优先到自定义库中去搜索模块。

main.c

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

void main(){
while(1)
{
Nixie(1,1);
Nixie(2,2);
Nixie(3,3);
}
}

Delay.h

1
2
3
4
5
6
#ifndef __DELAY_H__
#define __DELAY_H__

void Delay(unsigned int xms);

#endif

Delay.c

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

Nixie.h

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

void Nixie(unsigned char location,number);

#endif

Nixie.c

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

unsigned char NixieTable[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};

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

要注意继承问题 在数码管模块里也用到了Delay函数 所以要在模块里引用Delay的头文件

感觉每次调用要复制这些模块到同一个文件夹下面好麻烦

有空的时候要专门做一个自建库 然后修改C51的路径

LCD1602调试

连接好后 数码管会乱码

image-20230111223140765

使用LCD1602液晶屏作为调试窗口,提供类似printf函数的功能,可实时观察单片机内部数据的变换情况,便于调试和演示。

只需要知道所提供函数的作用和使用方法

有报错:

image-20230111224227603

requires ANSI-style prototype搜了一下:

需要要把调用的子函数放到main前面,或者在main函数前面先把需要调用的子函数定义声明一下。

也不行

把文件里的函数名字复制过来就好了

然后自己再重新写也好了 好迷惑···

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

void main(){
LCD_Init();
LCD_ShowChar(1,1,'A');
LCD_ShowString(1,3,"Hello");
LCD_ShowNum(1,9,123,3);
LCD_ShowSignedNum(1,13,-66,1);
LCD_ShowHexNum(2,1,0xA8,2);
LCD_ShowBinNum(2,4,0xAA,8);
while(1){

}
}

a8b8007a5727ab7a0a59d73c9d4f779

方便地显示字符串/数字:

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

int result =0;

void main(){
LCD_Init();
while(1){
result++;
Delay(500);
LCD_ShowNum(1,1,result,3);
}
}

1.12 矩阵键盘

数码管扫描(输出扫描

原理:显示第1位→显示第2位→显示第3位→……,然后快速循环这个过程,最终实现所有数码管同时显示的效果

矩阵键盘扫描(输入扫描

原理:读取第1行(列)→读取第2行(列) →读取第3行(列) → ……,然后快速循环这个过程,最终实现所有按键同时检测的效果

以上两种扫描方式的共性:节省I/O口

读取按键显示

扫描法
逐行扫描

image-20230112152213216

如果P17,P16,P15,P14全部给高电平1 无论按键按不按下 下面的都是高电平

而将这几个口连接低电平(GND),可以查看P13,12,11,10,如果是0的话说明按键被按下了

所以想要扫描第二行的话只需要将第二行P16置0,其他三行给1,然后判断P13,12,11,10

相当于把键盘逐行扫描了一遍

⚠但逐行扫描的话,P15有可能会驱动五线四相步进电机(引脚冲突),导致无源蜂鸣器自己响应,如下图

image-20230112152427114

image-20230112152510088

故采用

逐列扫描

即给P13,12,11,10进行赋值,读取P17,P16,P15,P14

image-20230112153239203

单片机配置是准双向接口输出配置,当口线输出为1时驱动能力很弱,允许外部装置将其拉低,而当引脚输出为低时,它的驱动能力很强,可吸收相当大的电流。

P1,P2,P3都是“弱上拉”,P0是一种开漏输出

弱上拉:当输出高电平时,能够输出的电流很小,很容易被别的强下拉拉低

开漏输出:输出端相当于三极管的集电极. 高电平状态需要上拉电阻才行.

上拉:将不确定的信号,固定在高电平,电源到器件引脚上的电阻叫上拉电阻,作用是平时使用该引脚为高电平,上拉是对器件注入电流,即灌电流

image-20230112154900369

下拉:将不确定的信号,固定到地点平,地到器件引脚的电阻叫下拉电阻,作用是平时使该引脚为低电平,下拉是从器件输出电流,即拉电流

image-20230112154915834

参考博客

💡想在他编程之前创建自建库 呜呜好像要在一个project下 类似 下次再看看惹

up主的代码是一条条写下去的 类似这样

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

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

P1 = 0xFF;
P1_3 = 0;
if(P1_7 ==0){Delay(20);while(P1_7==0);/*检测松手*/Delay(20);key=1;}
if(P1_6==0){Delay(20);while(P1_6==0);/*检测松手*/Delay(20);key=5;}
if(P1_5 ==0){Delay(20);while(P1_5==0);/*检测松手*/Delay(20);key=9;}
if(P1_4 ==0){Delay(20);while(P1_4==0);/*检测松手*/Delay(20);key=13;}
//···

return key;
}

感觉有点麻烦 想用循环 参考一个博主的代码进行修改和理解

得到扫描法代码:
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
#include <REGX52.H>
#include "Delay.h"
#include "intrins.h"
#define keyboard P1 //四条行线四条列线所连接的IO口,指定标识符keyboard来代替表达式P1

unsigned char MatrixKey()
{
unsigned char row_scan_code=0x01; //行扫描码0000 0001
unsigned char col_scan_code=0xEF;//列扫描码 1110 1110 ;0000 0000
unsigned char keycode; //按键键值
unsigned char i,x,j;
//可以做一个消抖处理
for(i=0;i<4;i++)//逐列扫描,将列线逐列拉低
{
keycode=i+1;//对应关键列的第一行
keyboard=col_scan_code; //P1_3=0->P1_2=0->P1_1=0->P1_0=0按列顺次拉低
x=keyboard; //读取行线状态
for(j=0;j<4;j++)//逐行扫描
{
if(!(x&row_scan_code))//说明对应行的按键按下,使行线被拉低
{
keycode+=4*j;
Delay(20);
//如果按键还未释放,则仍有行线被拉至低电平
while((keyboard&0x0f)!=0x0f);//等待按键释放(释放的话低四位是F)
Delay(20);
P1=0x0F; //恢复原状态,为下次按键做准备
return keycode; //已检测到按键键码,返回
}
else
row_scan_code=_crol_(row_scan_code,1);//左移一位,逐行扫描,将行线逐列拉低
}
col_scan_code=_crol_(col_scan_code,1);//左移一位,将下一列线拉低,准备扫描下一列
row_scan_code=0x01;//重置行扫描码,为下一行扫描作准备
}
keycode=0;//没有按键按下,键值记为0
return keycode;
}

_crol_函数

真实测试后跳转回来,博主的代码好像和我实际的东西是反的,如果从1开始编号,原始状态的列应该是1111 0111P1_3~P1_0是低四位),而博主是 1110 1111,是先把P1_4置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
35
36
37
38
39
40
41
42
43
44
45
#include <REGX52.H>
#include "Delay.h"
#include "intrins.h"
#define keyboard P1 //四条行线四条列线所连接的IO口,指定标识符keyboard来代替表达式P1

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

unsigned char MatrixKey()
{
unsigned char row_scan_code=0x80; //行扫描码1000 0000
unsigned char col_scan_code=0xF7;//列扫描码 1111 0111 ;0000 0000;
unsigned char keycode; //按键键值
unsigned char i,x,j;
//可以做一个消抖处理
for(i=0;i<4;i++)//逐列扫描,将列线逐列拉低
{
keycode=i+1;//对应关键列的第一行
keyboard=col_scan_code; //P1_3=0->P1_2=0->P1_1=0->P1_0=0按列顺次拉低
x=keyboard; //读取行线状态
for(j=0;j<4;j++)//逐行扫描
{
if(!(x&row_scan_code))//说明对应行的按键按下,使行线被拉低
{
keycode+=4*j;
Delay(20);
//如果按键还未释放,则仍有行线被拉至低电平
while((keyboard&0xF0)!=0xF0);//等待按键释放(释放的话高四位是F)
Delay(20);
P1=0xF0; //恢复原状态,为下次按键做准备
return keycode; //已检测到按键键码,返回
}
else
row_scan_code=_cror_(row_scan_code,1);//右移一位,逐行扫描,将行线逐列拉低
}
col_scan_code=_cror_(col_scan_code,1);//右移一位,将下一列线拉低,准备扫描下一列
row_scan_code=0x80;//重置行扫描码,为下一行扫描作准备
}
keycode=0;//没有按键按下,键值记为0
return keycode;
}

耶✌

线反法

原理:

首先使P1口的高四位输出高电平,P1口低四位输出低电平,这时键盘的行线被拉高,列线被拉低。如果有按键按下,则某一条行线将被拉低,此时读取P1口高四位,读取到的将不再全为高电平,说明有按键按下。(在判断是否有按键按下这一点上,线反法与逐行逐列扫描法是一致的)根据读取到0值的I/O口所连接的行线,就可以判断出按下的按键位于哪一行。接下来使P1口的高四位输出低电平,P1口低四位输出高电平(即与上次输出的电平相反,因此称为线反法)。如果有按键按下,此时读取P1口低四位,读取到的将不再全为高电平,根据读取到0值的I/O口所连接的列线,就可以判断出按下的按键位于哪一列。综合按键所在的行线与列线,即可唯一确定按键所在位置,进而获取按键的键值。

第一步:P1.0–P1.3输出为0(4行),P1.4–P1.7做输入(4列)。
第二步:读取P1.4–P1.7的数据并保存。
第三步:将第二步中保存的数据从P1.4–P1.7输出,此时P1.0–P1.3做输入。
第四步:读取P1的数据,若无按键按下,此时读取的数据为0xff,若有按键按下,P1.0–P1.3中必定有一个为0(对应着被按下按键的行),P1.4–P1.7中必定有一个为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
35
36
37
38
39
40
//@brief:判断4*4矩阵键盘是否有键可靠按下,高4位口接行线,低四位口接列线
//@retval:当有键可靠按下时返回1-16的键值,否则返回0
#define keyboard P1
unsigned char Check_Keydown()
{
unsigned char KeyValue=0;
keyboard=0x0f;
if(keyboard!=0x0f)//如果按键按下
{
delay_ms(10);//延时10ms消抖
if(keyboard!=0x0f)//按键确实按下
{
//判断按键所在列,以所在列的第一行的按键键值赋给KeyValue
keyboard=0X0F;
switch(keyboard)
{
case(0X07): KeyValue=1;break; //第一列按下
case(0X0b): KeyValue=2;break; //第二列按下
case(0X0d): KeyValue=3;break; //第三列按下
case(0X0e): KeyValue=4;break; //第四列按下
}
//判断按键所在行
keyboard=0XF0;
switch(keyboard)
{
case(0X70): KeyValue=KeyValue;break; //第一行按下
case(0Xb0): KeyValue=KeyValue+4;break; //第二行按下
case(0Xd0): KeyValue=KeyValue+8;break; //第三行按下
case(0Xe0): KeyValue=KeyValue+12;break; //第四行按下
}
while(keyboard!=0xf0); //按键松手后退出
return KeyValue;
}
else //否则认为是信号干扰导致
{
return 0; //认为没有按键按下
}
}
return 0; //如果没有按键按下返回零
}

version2 参考博主

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

#define SEGPORT P0
#define KEYPORT P1

typedef unsigned char u8;
typedef unsigned int u16;

//数码管显示按键号,段码
unsigned char code LEDCODE[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x40};
//查找按键号的数组,行和列与电路图一致
unsigned char KEYCODE[]=
{0XEE,0XDE,0XBE,0X7E,
0XED,0XDD,0XBD,0X7D,
0XEB,0XDB,0XBB,0X7B,
0XE7,0XD7,0XB7,0X77};

//函数声明
void delayxms(u16 s); //延时函数
u8 KEY_Scan(void); //按键扫描

//主函数
void main (void)
{
while(1)
{
SEGPORT=~LEDCODE[KEY_Scan()];
}
}

void delayxms(u16 s)
{
u16 i,j;
for(i=0;i<s;i++)
{
for(j=0;j<120;j++)
{}
}
}

u8 KEY_Scan(void)
{
u8 temp,num;
KEYPORT=0xf0; //P1.0--P1.3输出0,P1.4--P1.7输出为1,为输入做准备
temp=KEYPORT; //读入P1的值
temp&=0xf0; //保留P1.4--P1.7的数据
if(temp!=0xf0) //若P1.4--P1.7输入不全为1,代表有按键被按下
{
delayxms(5); //延时消抖
KEYPORT=0xf0; //再次判断是否真有按键按下
temp=KEYPORT; //读入P1的值
temp&=0xf0; //保留P1.4--P1.7的数据
if(temp!=0xf0) //真有按键按下
{
temp|=0x0f;
KEYPORT=temp; //将读取的P1.4--P1.7数据从P1.4--P1.7输出,此时P1.0--P1.3做输入
temp=KEYPORT; //再次读取P1的值,上述原理中的第四步
for(num=0;num<16;num++) //查找按键号,
{
if(temp==KEYCODE[num])
break;
}
return num; //对应按键号
}
return 16; //表示无按键按下
}
else
return 16;
}

version3 参考博主

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 <reg52.h>
typedef unsigned char uchar;
typedef unsigned int uint;
uchar code KEY_TABLE[] =
{
0xEE, 0xDE, 0xBE, 0x7E,
0xED, 0xDD, 0xBD, 0x7D,
0xEB, 0xDB, 0xBB, 0x7B,
0xE7, 0xD7, 0xB7, 0x77
};
uchar code TABLE[] =
{
0x3F, 0x06, 0x5B, 0x4F,
0x66, 0x6D, 0x7D, 0x07,
0x7F, 0x6F, 0x77, 0x7C,
0x39, 0x5E, 0x79, 0x71,
};
void Delay(uchar m)
{
--m;
}
void main()
{
uchar temp, key, i;
while(1)
{
P3 = 0xF0;
if (P3 != 0xF0)
{
Delay(2000);
if (P3 != 0xF0)
{
temp = P3;
P3 = 0x0F;
key = temp | P3;
for (i = 0; i < 16; ++i)
if (key == KEY_TABLE[i])
break;
P2 = TABLE[i];
}
}
}
}

实现形式有很多 有空的时候对比一下这三个方案 今天单片机花了很久的时间 就先用扫描法继续推进吧~

main.c

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

unsigned char keynumber;

void main(){
LCD_Init();
while(1){
keynumber = MatrixKey();
if(keynumber){
LCD_ShowNum(1,1,keynumber,2);
}
}
}

!!得要有if(keynumber)的判断

如果没有的话:1.会显示00 2.按下按键后松开由于进入下一次循环过快会立马刷新为00 无法看清01 会感觉一直是00 所以要判断!!

电子密码锁

~矩阵键盘的应用

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

unsigned char keynumber;
unsigned int password,count;
unsigned int truenumber = 3060;

/**
* S1-S9为对应数字
* S10为对应数字0
* S11为确认键,S12为取消键
*/

void main(){
LCD_Init();
LCD_ShowString(1,1,"Password:");
while(1){
keynumber = MatrixKey();
//由于while的存在 可以接二连三地往后更新密码 不用担心按键下一次要执行是否会清零的问题
if(keynumber){ //输入密码
if(keynumber<=10){
if(count<4){ //防止超过int范围
password = password*10+keynumber%10;
count++;
}
else{ //计次达到4也可以始终读取!
if(password>=1000){
password = password%(password/1000*1000)*10+keynumber%10;
}
else{
password = password*10+keynumber%10;
}
}
LCD_ShowNum(2,1,password,4); //更新显示
}
if(keynumber==11){ //确认
if(password == truenumber){
LCD_ShowString(1,11," OK!");
}
else{
LCD_ShowString(1,11,"Wrong!");
password = 0;
count = 0;
LCD_ShowNum(2,1,password,4); //更新显示
}
}
if(keynumber==12){ //一键清零
password = 0;
count = 0;
LCD_ShowNum(2,1,password,4); //更新显示
}
}
}
}

由于while的存在 可以接二连三地往后更新密码 不用担心按键下一次要执行是否会清零的问题

但是按到第六位左右会超过int的范围 于是要有预防措施 添加count变量来计次 在up主的功能上多加了一个计次超过4也可以始终不断读取的功能(那这样好像S12就没有用了哦hhh 作为一键清零吧~)

基础上加一个撤销的功能 只删除一个数字 比较简单就不写下去了~

有一个bug 密码不能为0

726f76a3a3b9f54837f4be4c8769209

今日总结:

好家伙!一个矩阵键盘写的字比之前几天加起来的都多!三千多个字 也可能因为代码贴的太多了hhh

一个功能的代码实现可以多样化 耐心尝试与探索 仔细理解 遇到不会的即使搜索相关帖子解决 可以有很大收获

美中不足今天花了好多时间几乎下午三四点开始今天单片机的学习 除了晚饭和饭后散步 一直搞到现在十点

但是今天学习大家有趣的代码以及一些方便的函数 真的会很开心 主动性吸收知识真的很取悦自己

1.14 定时器

独立按键与流水灯的联动! 直接加在一起的话会有很长时间的delay延迟 用定时器使其灵敏

之前都是单片机控制的L口外部实现的,而定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成

(1)用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作
(2)替代长时间的Delay,提高CPU的运行效率和处理速度(原来的Delay会耗CPU 效率低)

定时器内部工作原理

image-20230114193718207

STC89C52的T0和T1四种工作模式:

模式0:13位定时器/计数器

模式1:16位定时器/计数器(常用)

模式2:8位自动重装模式

模式3:两个8位计数器

以模式1为例

image-20230114200130072

image-20230114201934901

中断系统

image-20230114203251888

流程

image-20230114203555716

内部结构图

image-20230114204532170

PT0为1 走高优先级 PT0为0 走低优先级

单片机通过配置寄存器来控制内部线路的连接—>完善不同的功能

image-20230114213718274

image-20230114213755711

中断程序设置

应用定时器0

先配置工作模式寄存器TMOD:

image-20230114214905474

结合流程图👇中的门电路可以看出GATE0是为了让INT1单独控制 C/T0是为了工作在计时器模式 M1M00、1是为了工作在工作模式1(16位)

image-20230114215013184

接下来配置TCON

image-20230114215801600

由于gate给了0 所以IE0IT0不用配置了

不可位寻址寄存器只能整体赋值,可位寻址寄存器可以对其中某一位单独赋值

1.17 定时器(2)

摸鱼了两天+昨天出去玩 停了三天才继续上次的内容,年前应该是跟不完up主的内容了,看大纲轴真的可以很清晰地看出摸鱼的日子,SAD,不要太摆了惹~

接下来配置TH0TL0

选了12分频原默认值是:每隔1us+1 计数到最大值(65535)后中断 总定时间为65535us

想要实现的目标:每隔1s发生中断,上面的默认达不到1s,所以要不断地累计中断

image-20230114220358917

配置中断路径

具体内部结构图

image-20230117092124919

image-20230117092036540

1
2
3
ET0=1;
EA=1;
PT0=0;
中断后的程序

image-20230117092623227

test中断程序

以1ms中断为例子

实现手段:赋初值

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>

/*配置寄存器*/
void timer0_Init(){
/*配置TMOD*/
TMOD=0x01; //0000 0001
/*配置TCON*/
/*要求中断要使TF0=1,即TCON_5=1*/
/*TR=0时禁止计数,即TCON_4=0*/
TF0=0;
TR0=1;//控制是否计数
/*隔1ms中断*/
TH0=64535/256;
TL0=64535%256;
/*配置中断系统*/
ET0=1;
EA=1;
PT0=0;//默认等于0 低优先级
}

void main(){
timer0_Init();
while(1){

}
}

void Timer0_Rountine() interrupt 1{
P2_0=0;
}

IMG_2089

以1s为目标

实现手段:赋初值+循环

注意:计数器溢出后会TH0TL0会变成0,而不是原来给的初值,所以进入中断要重新赋初值

TMOD=0x01;会把定时器1的状态默认刷新为0000,所以要改成TMOD&=0xF0;TMOD|=0X01;,注意不能直接TMOD&=0xF1;如果原来最低位为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
35
36
37
38
39
#include <REGX52.H>

/*配置寄存器*/
void timer0_Init(){ //1ms @12.000Mhz

/*配置TMOD*/
TMOD &= 0xF0; //保持高四位,低四位清零
TMOD |= 0X01;
/*配置TCON*/
/*要求中断要使TF0=1,即TCON_5=1*/
/*TR=0时禁止计数,即TCON_4=0*/
TF0 = 0;
TR0 = 1;//控制是否计数
/*隔1ms中断*/
TH0 = 64535/256;
TL0 = 64535%256+1;
/*配置中断系统*/
ET0 = 1;
EA = 1;
PT0 = 0;//默认等于0 低优先级
}

void main(){
timer0_Init();
while(1){

}
}

unsigned int count;
void Timer0_Rountine() interrupt 1{
TH0 = 64535/256;
TL0 = 64535%256;
count++;
if(count>=1000){
count = 0;
P2_0 = ~P2_0;//会以间隔1s闪烁
}
}

软件里也有现成的代码配置:

image-20230117095057767

但是中断系统配置要自己写一下:

1
2
3
ET0 = 1;
EA = 1;
PT0 = 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
35
36
#include <REGX52.H>

/*配置寄存器*/
void timer0_Init(){

// AUXR &= 0x7F; //定时器时钟12T模式 已经有默认了不需要设置
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时

/*配置中断系统*/
ET0 = 1;
EA = 1;
PT0 = 0;//默认等于0 低优先级
}

void main(){
timer0_Init();
while(1){

}
}

unsigned int count;
void Timer0_Rountine() interrupt 1{
TH0 = 0xFC;
TL0 = 0x18;
count++;
if(count>=1000){
count = 0;
P2_0 = ~P2_0;//会以间隔1s闪烁
}
}
将1s中断程序模块化

Timer0.c

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

/**
* @brief 定时器0初始化,1ms 12.000Mhz
* @param 无
* @retval 无
*/

void timer0_Init(){

// AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0x01; //设置定时器模式
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时

/*配置中断系统*/
ET0 = 1;
EA = 1;
PT0 = 0;//默认等于0 低优先级
}

/*定时器中断模板 一般放在main函数中 不易模块化
void Timer0_Rountine() interrupt 1{
static unsigned int count;
TH0 = 0xFC;
TL0 = 0x18;
count++;
if(count>=1000){
count = 0;
}
}
*/

Timer0.h

1
2
3
4
5
6
#ifndef __Timer0_H__
#define __Timer0_H__

void timer0_Init();

#endif

main.c

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

void main(){
timer0_Init();
while(1){

}
}


void Timer0_Rountine() interrupt 1{
static unsigned int count;
TH0 = 0xFC;
TL0 = 0x18;
count++;
if(count>=1000){
count = 0;
P2_0 = ~P2_0;//会以间隔1s闪烁
}
}

按键控制LED流水灯模式

获取独立按键的模块化编程:

Key.c

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

/**
* @brief 获取独立按键键码
* @param 无
* @retval 按键键码0~4,无按键按下时返回值为0
*/

unsigned char key(){
unsigned char keynumber=0;
if(P3_1==0){Delay(20);while(P3_1==0);Delay(20);keynumber=1;}
if(P3_0==0){Delay(20);while(P3_0==0);Delay(20);keynumber=2;}
if(P3_2==0){Delay(20);while(P3_2==0);Delay(20);keynumber=3;}
if(P3_3==0){Delay(20);while(P3_3==0);Delay(20);keynumber=4;}
return keynumber;
}

Key.h

1
2
3
4
5
6
#ifndef __Key_H__
#define __Key_H__

unsigned char key();

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

unsigned char keynum,ledmode;

void main(){
P2 = 0xFE;
timer0_Init();
while(1){
keynum = key();
if(keynum==1){
ledmode++;
if(ledmode>=2)ledmode = 0;
}
}
}


void Timer0_Rountine() interrupt 1{
static unsigned int count;
TH0 = 0xFC;
TL0 = 0x18;
count++;
if(count>=500){
count = 0;
if(ledmode==0)
P2=_crol_(P2,1);
else
P2=_cror_(P2,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
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"

unsigned char Sec,Min,Hour;

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

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

void Timer0_Rountine() interrupt 1{
static unsigned int count;
TH0 = 0xFC;
TL0 = 0x18;
count++;
if(count>=1000){
count = 0;
Sec++;
if(Sec>=60)
{
Sec = 0;
Min++;
if(Min>=60)
{
Min = 0;
Hour++;
if(Hour>=24){
Hour = 0;
}
}
}
}
}

其他的事情:

关于中断系统up主讲的比较少 要另外找视频再学一下

不能再摸鱼了 剩下还有10个章节 春节前是看不完了 争取在二月前看完哇!

1.18 串口

串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信

单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大的扩展了单片机的应用范围,增强了单片机系统的硬件实力。

51单片机内部自带UART(Universal Asynchronous Receiver Transmitter,通用异步收发器),可实现单片机的串口通信。

单片机-电脑使用usb转换接口

image-20230118110540374

电平标准:人为规定的电压与数据的对应关系

TTL电平:+5V表示1,0V表示0 (单片机)

RS232电平:-3-15V表示1,+3~+15V表示0

RS485电平:两线压差+2+6V表示1,-2~-6V表示0(差分信号)

名称 引脚定义 通信方式 特点
UART TXD、RXD 全双工、异步 点对点通信
I²C SCL、SDA 半双工、同步 可挂载多个设备
SPI SCLK、MOSI、MISO、CS 全双工、同步 可挂载多个设备
1-Wire DQ 半双工、异步 可挂载多个设备

I²C :单片机写入读出依靠的通信口

通信方式:
  • 全双工:通信双方可以在同一时刻互相传输数据
  • 半双工:通信双方可以互相传输数据,但必须分时复用一根数据线
  • 单工:通信只能有一方发送到另一方,不能反向传输(例如:遥控器)
  • 异步:通信双方各自约定通信速率(双方要自己计时)
  • 同步:通信双方靠一根时钟线来约定通信速率
  • 总线:连接各个设备的数据传输线路(类似于一条马路,把路边各住户连接起来,使住户可以相互交流)

单片机的UART

只有一个,四种工作模式

与电脑的通信接口:

image-20230118151951283

RXD、TXD(串口)和P3口(IO口)共用

MCU发送信号到usb自动下载电路 转化为usb信号传给电脑(串口通信)

串口参数及时序图

image-20230118153108056

image-20230118160904942

到中断后:

image-20230118161202525

串口相关寄存器

image-20230118161705814

SMOD和SMOD0是属于串口的

串口向电脑发送数据

配置控制寄存器SCON

应用模式一

image-20230118162843820

image-20230118162954177

配置PCON

image-20230118191441248

up主说中断不用配置?不太明白欸

似乎是说中断默认打开,相当于已经配置好了,不用自己配置

选择波特率

串口只能用定时器1,且是8位自动重装载定时器

image-20230118192549066

用软件的波特率计算器

image-20230118194647648

要选波特率倍速,波特率加倍是因为分频了

程序

要发送信息到单片机,需要写信息到缓存SBUF

连续发会出错,所以要通过发送控制器的标志位TI检测

当串行在停止位开始发送时由内部硬件自动置位1,必须由软件来复位

image-20230118160904942

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 "Delay.h"

void UART_Init(){ //4800bps@12.000MHz
/*配置串口部分*/
SCON = 0x40;
PCON |= 0x80;
/*配置定时器部分*/
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xF3; //设定定时初值
TH1 = 0xF3; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
// TF0 = 0; //清除TF0标志
// TR0 = 1; //定时器0开始计时
TR1 = 1; //启动定时器1

}

void UART_SendByte(unsigned char Byte){
SBUF = Byte; //写入缓冲区
while(TI==0);//判断是否等于1
TI = 0;
}

void main(){
UART_Init();
UART_SendByte(0x66);
while(1){

}
}

打开单片机,发现一直是00,原来是软件的波特率没有改,要改成4800

按照原来的9600的话,发出去一个数据会被采集成两个数据

但是又E6和66交错,可能是因为我的单片机晶振是11.0596,修改定时器

1
2
TL1 = 0xF4;		//设定定时初值
TH1 = 0xF4; //设定定时器重装值
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 "Delay.h"

void UART_Init(){ //4800bps@12.000MHz
/*配置串口部分*/
SCON = 0x40;
PCON |= 0x80;
/*配置定时器部分*/
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
// TF0 = 0; //清除TF0标志
// TR0 = 1; //定时器0开始计时
TR1 = 1; //启动定时器1

}

void UART_SendByte(unsigned char Byte){
SBUF = Byte; //写入缓冲区
while(TI==0);//判断是否等于1
TI = 0;
}

void main(){
UART_Init();
UART_SendByte(0x11);
while(1){

}
}

如果把UART_SendByte(0x11);放到while里可能晶振会有误差,可以用以下delay

只发送不接收
1
2
3
4
5
6
7
8
9
void main(){
UART_Init();
while(1){
UART_SendByte(Sec);
Sec++;
Delay(1000);

}
}

image-20230118201728456

将串口模块化

UART.h

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

void UART_Init();
void UART_SendByte(unsigned char Byte);

#endif

UART.c

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>

/**
* @brief 串口初始化
* @param 无
* @retval 无
*/

void UART_Init(){ //4800bps@11.0596MHz
/*配置串口部分*/
SCON = 0x40;
PCON |= 0x80;
/*配置定时器部分*/
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1

}

/**
* @brief 串口发送一个字节数据
* @param Byte 要发送的一个字节数据
* @retval 无
*/

void UART_SendByte(unsigned char Byte){
SBUF = Byte; //写入缓冲区
while(TI==0);//判断是否等于1
TI = 0;
}

电脑控制LED流水灯

由于不知道电脑发送信号的时间,所以单片接收需要中断系统,在中断系统中进行数据处理与提取

SCON中一个REN(接收位),置1

还要打开串口的中断

image-20230118205620282

image-20230118205901441

防止单片机发送数据的时候进入中断,要先做一下测试

image-20230118210736914

将单片机的信息发送回电脑

image-20230118212713998

UART.c

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

/**
* @brief 串口初始化
* @param 无
* @retval 无
*/

void UART_Init(){ //4800bps@11.0596MHz
/*配置串口部分*/
SCON = 0x50;
PCON |= 0x80;
/*配置定时器部分*/
TMOD &= 0x0F; //设置定时器模式
TMOD |= 0x20; //设置定时器模式
TL1 = 0xF4; //设定定时初值
TH1 = 0xF4; //设定定时器重装值
ET1 = 0; //禁止定时器1中断
TR1 = 1; //启动定时器1
/*配置中断系统*/
EA = 1;
ES = 1;
}

/**
* @brief 串口发送一个字节数据
* @param Byte 要发送的一个字节数据
* @retval 无
*/

void UART_SendByte(unsigned char Byte){
SBUF = Byte; //写入缓冲区
while(TI==0);//判断是否等于1
TI = 0;
}

/*串口中断函数模板
void UART_Routine() interrupt 4{
if(RI==1)//防止单片机发送数据的时候进入中断
{

RI = 0;
}
}
*/

main.c

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

unsigned char Sec;

void main(){
UART_Init();
while(1){

}
}

void UART_Routine() interrupt 4{
if(RI==1)//防止单片机发送数据的时候进入中断
{
RI = 0;
}
}

计算波特率

发现选择11.0596MHz后

1
2
TL1 = 0xF4;		//设定定时初值
TH1 = 0xF4; //设定定时器重装值

0xF4244,每隔256溢出一次,计数值位12,每计数12就溢出一次(每隔12us)

所以定时器的溢出率为1/12us=0.08333Mhz

真正到发送控制器(SMOD为1)的频率为0.08333/16=0.005208Mhz=5208hz

8.33/5200约等于0.16%的误差

如果不选倍数

真正到发送控制器(SMOD为1)的频率为0.08333/2/16=0.002604Mhz=2604hz

也可以让UART发送以编码形式呈现(要转化为ASCII码)

例如发送UART_SendByte(0x41)以编码形式呈现就是A

也可以直接发字符UART_SendByte('A')

但是如果发送A的时候选的是HEX编码 则是不可见的(在ASCII码中0x0a是不可见的)

(因为这里是8位重整模式,一次给寄存器赋值最高只能存储8位二进制数据,而8位二进制数据正好对应一个字符,所以只能一个一个发送字符)

嗯发现哪止定时器,串口也是很迷糊的,说是郭天祥老师讲的也不错,到时候可以查漏补缺一下┭┮﹏┭┮


单片机入门学习1
http://gigiboo.github.io/2023/02/14/单片机1/
作者
Gigiboo
发布于
2023年2月14日
许可协议