【51单片机】基于普中科技HC6800-EM3 V3.0开发板的51单片机学习记录

普中科技HC6800-EM3

理论知识

周期

在51单片机中存在时钟周期、状态周期、机器周期、指令周期

  • 时钟周期: 时钟周期也称振荡周期或晶振周期,即晶振在单位时间发出的脉冲数,在一般情况下使用外部晶振源,在本套单片机中提供了11.0592MHz、12MHz、24MHz三个晶振可供选择。
  • 状态周期: 在8051单片机中将两个时钟周期定义为一个状态周期。
  • 机器周期: 在计算机中往往将一条指令的执行过程划分成多个阶段,其中完成一个阶段的操作所需的时间成为机器周期,在8051系列单片机中一个机器周期 = 12个时钟周期。
  • 指令周期: 完成一条指令所需的时间。在STC单片机下载程序中有12T和6T的选择,在12T模式下指令周期 = 12个时钟周期,在6T模式下指令周期 = 6个时钟周期。此选项会影响串口的波特率。

中断

中断简单的来说就是在主程序执行过程中突然有人叫CPU紧急去做另外一件事。

中断原理

在基本型8051单片机中存在5个中断源分别是外部中断:INT0、INT1和定时计数器中断:T0、T1以及串口中断。

其中断的启用与否由内部特殊寄存器IE控制

IE位(中断使能寄存器) 7 6 5 4 3 2 1 0
字节地址:A8H EA - - ES ET1 EX1 ET0 EX0
  • EX0: 外部中断0使能,EX0 = 1,允许外部中断0中断,EX0 = 0,禁止外部中断0中断。

  • ET0: T0溢出中断使能,ET0 = 1,允许T0中断,ET0 = 0,禁止T0中断。

  • EX1: 外部中断1使能,EX1 = 1,允许外部中断1中断,EX1 = 0,禁止外部中断1中断。

  • ET1: T1溢出中断使能,ET1 = 1 ,允许T1中断,ET1 = 0,禁止T1中断。

  • E S: 串行中断使能,ES = 1,允许串行口中断,ES = 0 ,禁止串行口中断。

  • E A: 中断允使能,EA = 1,允许CPU处理中断请求,EA = 0,禁止CPU处理中断请求。

IP位(优先级寄存器) 7 6 5 4 3 2 1 0
- - - - PS PT1 PX1 PT0 PX0
  • P S: 串口优先级切换使能,PS = 1,将串口中断优先级设为高级,PS = 0,将串口中断优先级设为低级。
  • PT1: T1优先级切换使能,PT1 = 1,将T1中断优先级设为高级,PT1 = 0,将T1中断优先级设为低级。
  • PX1: 外部中断1优先级切换使能,PX1 = 1,将外部中断1中断优先级设为高级,PX1 = 0,将外部中断1中断优先级设为低级。
  • PT0: T0优先级切换使能,PT0 = 1,将T0中断优先级设为高级,PT0 = 0,将T0中断优先级设为低级。
  • PX0: 外部中断0优先级切换使能,PX0 = 1,将外部中断0中断优先级设为高级,PX0 = 0,将外部中断0中断优先级设为低级。

自然优先级:当未设置中断优先级(IP)时,程序处理中断按照自然优先级进行排序,自然优先级为 外部中断0 > T0 > 外部中断1 > T1 > 串口中断。当设置中断优先级(IP)时,按照高级/低级中存在的中断源再次按自然优先级进行。

终端结构

TCON位(中断模式寄存器) 7 6 5 4 3 2 1 0
字节地址:88H TF1 TR1 TF0 TR0 IE1 IT1 IE0 IT0
所属模块 T1 T1 T0 T0 中断 中断 中断 中断
  • IT0: 外部中断0触发模式控制位,IT0 = 1,外部中断0为下降沿触发,IT0 = 0,外部中断0为低电平触发。
  • IE0: 外部中断0中断请求标志位,当外部中断0发出中断请求时,IE0置1。
  • IT1: 外部中断1触发模式控制位,IT1 = 1,外部中断1为下降沿触发,IT1 = 1,外部中断1为低电平触发。
  • IE1: 外部中断1中断请求标志位,当外部中断1发出中断请求时,IE1置1。
  • TR0: 定时\计数器T0使能位,TR0 = 1,T0工作,TR0 = 0,T0不工作。
  • TF0: 定时\计数器T0溢出标志位,T0溢出时,TF0置1,并发出中断请求。
  • TR1: 定时\计数器T1使能位,TR1 = 1,T1工作,TR1 = 0,T1不工作。
  • TF1: 定时\计数器T1溢出标志位,T1溢出时,TF1置1,并发出中断请求。

中断号

中断源符号 名称 中断原因 中断号
INT0 外部中断0 P3^2引脚低电平或下降沿信号 0
T0 定时器0中断 定时/计数器0计数回0溢出 1
INT1 外部中断1 P3^3引脚低电平或下降沿信号 2
T1 定时器1中断 定时/计数器1计数回0溢出 3
TI/RI 串行口中断 串行通讯完成一帧数据发送或接收引起中断 4

定时\计数器

在中断篇幅中有一个TCON寄存器其中高4位为定时/计数器部分。

TMOD(定计器模式寄存器) 7 6 5 4 3 2 1 0
字节地址:89H GATE C/ T M1 M0 GATE C/T M1 M0
  • C/T: 定时器/计数器切换使能,C/T = 1,为计数器模式,C/T = 0,为定时器模式。

  • GATE: 门控位,GATE = 0,只需设置TR0/1即可启动定时/计数器,GATE = 1,需要当外部中断引脚INT0/1为高电平时,才可设置TR0/1启动定时/计数器。

  • M1 M0 工作方式 说明
    0 0 方式0 13位定时/计数器
    0 1 方式1 16位定时/计数器
    1 0 方式2 8位自动重装定时/计数器
    1 1 方式3 T0分为两个独立的8位定时/计数器,T1此方式停止计数。

计时器工作方式

串口

串口工作

相关寄存器:

SCON(串口控制寄存器) 7 6 5 4 3 2 1 0
字节地址:98H SM0/FE SM1 SM2 REN TB8 RB8 T1 R1
  • SM2: 允许方式2或方式3多机通讯控制位。

  • REN: 允许/禁止串行接受控制位,REN = 1 ,串行接受状态,串行接收器RxD启用,开始接收信息,REN = 0 ,串行禁止状态,停止接收数据。

  • TB8: 在方式2/方式3,他是要发送的第9位数据。

  • TR8: 在方式2/方式3,他是接收到的地9位数据。

  • TI: 发送中断请求标志位,当串行数据发送结束时,硬件自动置为,即TI = 1,响应中断后必须软件复位。

  • RI: 接收中断请求标志位,当串行数据接收结束时,硬件自动置为,即RI = 1,响应中断后必须软件复位。

SM0 SM1 工作方式 功能说明 波特率
0 0 方式0 同步移位寄存器方式,8 位数据。数据通过 RxD(输入)和 TxD(输出)移位,类似 SPI 波特率 = fosc / 12
0 1 方式1 10 位异步串行通信:1 起始位、8 数据位、1 停止位。 波特率可变,通常由定时器 1 决定(常用于标准 UART 通讯)
1 0 方式2 11 位异步串行通信:1 起始位、9 数据位(包括可编程的第 9 位)、1 停止位。 波特率为固定的 fosc / 64 或 fosc / 32(SMOD=1 时)
1 1 方式3 11 位异步串行通信,跟模式 2 类似。 区别是波特率可变,通常由定时器 1 决定。
PCON(电源控制寄存器) 7 6 5 4 3 2 1 0
字节地址:87H SMOD SMOD0 - POF GF1 GF0 PD IDL
  • SMOD: 波特率选择位,SOMD = 1 ,方式1、2、3的波特率加倍,SMOD = 0,波特率不加倍。
  • SMOD0: 帧错误检测有效控制位,SMOD0 = 1,SCON寄存器中SM0/EF位用于EF(帧错误检测)功能,SMOD0 = 0,SCON寄存器中SM0/EF位用于SM0。

51单片机串口通讯

1
2
3
4
5
6
7
8
9
10
11
void UsartInit()
{
SCON=0X50; //设置为工作方式1
TMOD=0X20; //设置计数器工作方式2
PCON=0X80; //波特率加倍
TH1=0XF3; //计数器初始值设置,注意波特率是4800的
TL1=0XF3;
ES=1; //打开接收中断
EA=1; //打开总中断
TR1=1; //打开计数器
}
1
2
3
4
5
6
7
8
9
void Usart() interrupt 4
{
u8 receiveData;
receiveData=SBUF; //出去接收到的数据
RI = 0; //清除接收中断标志位
SBUF=receiveData; //将接收到的数据放入到发送寄存器
while(!TI); //等待发送数据完成
TI=0; //清除发送完成标志位
}

}

SPI协议

在标准SPI (Serial Peripheral interface) 通讯中,往往使用4根数据线进行通讯,其分别为SSEL(片选信号)、SCKL(时钟信号)、MOSI(主机to从机数据信号)、MISO( 从机to主机数据信号)。

别称

SSEL:NSS、CE、CS

SCKL:SCK

MOSI:SOMI、DIN、DI、SDI

MISO:SIMO、DOUT、DO、SDO

使用51单片机模拟SPI通讯

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

void SPI_write(u8 dat){
u8 i;
CLK = 0;
for(i = 0 ; i < 8 ; i ++){
DIN = dat & 0x80 ? 1 : 0 ;
CLK = 0;
CLK = 1;
dat <<= 1;
}
}

u16 SPI_read(){
u8 j;
u16 dat_read = 0x0000;
CLK = 0;
for(j = 0 ; j < 12 ; j ++){
dat_read <<= 1;
CLK = 1 ;
CLK = 0;
dat_read |= DOUT;
}
return dat_read;
}

u16 SPI_read_value(u8 a){
u8 i = 0;
u16 value = 0 ;
CS = 0;
CLK = 0;
SPI_write(a);
for(i=6; i>0; i--);
CLK = 1;
_npo_();
CLK = 0;
value = SPI_read();
CS = 1;
return value;
}

实验1 :关闭和开启一个LED灯

编程

1
#include "reg52.h"

引用51单片机特殊寄存器

1
2
sbit led1 = P0^0;
sbit led2 = P0^1;

定义引脚P0.0

1
2
led1 = 0;
led2 = 1;

令P0.0引脚为低电平,P0.1引脚为高电平,开发板单片机引脚有上拉电阻。

1
2
3
4
5
6
7
8
9
10
11
#include "reg52.h"
sbit led1=P0^0;
sbit led2=P0^1;
void main()
{
while(1)
{
led1=0;
led2=1;
}
}

while(1) 使单片机循环持续执行,在希望程序一直运行应该加上。

实验

将开发板的 JP10.1和 SIP8.1、JP10.2和 SIP8.2进行跳线连接,将 J21的1、2叫使用跳线帽进行短接。

编译下载程序。

发现D18关闭,D17开启。

实验2:LED灯闪烁

编程

1
typedef unsigned int u16;

将u16定义为无符号整数类型。

1
2
3
4
void delay(u16 i)
{
while(i--);
}

定义了一个延时函数,通过单片机执行代码的延迟进行延时,在当前平台下 while 每次循环耗时约10u。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "reg52.h"
typedef unsigned int u16;
sbit led=P0^0;
void delay(u16 i)
{
while(i--);
}
void main()
{
while(1)
{
led=0;
delay(50000);
led=1;
delay(50000);
}
}

delay(50000) 约能提供450ms的延时。

实验

将开发板的 JP10.1和 SIP8.1进行跳线连接,将 J21的1、2叫使用跳线帽进行短接。

编译下载程序。

发现D18闪烁。

实验3:LDE流水灯

编程

1
#include<intrins.h>

[intrins.h](函数库 | LiQiWen) 是特定于 Keil C51 的扩展库,无法跨平台使用。

1
#define led P0

定义led为P0.0-P0.7引脚。

1
led=0x01;

0x01为16进制数据其转换为二进制数为0b00000001,相应的也是就是使P0.0为高电平,P0.1-P0.7为低电平。

1
led=_crol_(led,1);

将变量led左移一位。

1
led=_cror_(led,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
#include "reg52.h"
#include<intrins.h>
typedef unsigned int u16;
typedef unsigned char u8;
#define led P0
void delay(u16 i)
{
while(i--);
}
void main()
{
u8 i;
led=0x01;
delay(50000);
while(1)
{
for(i=0;i<7;i++)
{
led=_crol_(led,1);
delay(50000);
}
for(i=0;i<7;i++)
{
led=_cror_(led,1);
delay(50000);
}
}
}

实验

实验4:蜂鸣器

编程

1
sbit beep=P1^5;	

定义引脚P1.5。

1
beep=~beep;

将beep变量进行取反。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "reg52.h"
#include<intrins.h>
typedef unsigned int u16;
typedef unsigned char u8;
sbit beep=P1^5;
void delay(u16 i)
{
while(i--);
}
void main()
{
while(1)
{
beep=~beep;
delay(10); //延时大约100us 通过修改此延时时间达到不同的发声频率
}
}

实验

实验5:继电器

编程

1
sbit relay=P1^4;

定义引脚P1.4。

1
while(1);

while(1);没有包裹函数单独放在主函数后面时,当单片机运行到此处时,会进入死循环,可以实现函数只执行一次的效果。

1
2
3
4
5
6
7
8
9
#include "reg52.h"
typedef unsigned int u16;
typedef unsigned char u8;
sbit relay=P1^4;
void main()
{
relay=0;
while(1);
}

实验6:多个数码管0-9循环

编程

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 "reg52.h"
typedef unsigned int u16;
typedef unsigned char u8;
#define LED P1
sbit L_A = P0^2 ;
sbit L_B = P0^1 ;
sbit L_C = P0^0 ;

int NUM[10] = {0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x3F};
void dealy(){
int d_i = 105000;
while(d_i){
d_i --;
}
}
void main()
{

while(1){
int i = 0;
int j = 0;
//ABCDEFG 1 0110000 0x30 | 2 1101101 0x6D | 3 1111001 0x79 | 4 0110011 0x66 | 5 1011011 0x6D | 6 1011111 0x7D | 7 1110000 0x07 | 8 1111111 0x7F | 9 1111011 0x6F | 0 1111110 0x3E
for(j = 0 ; j < 8 ; j ++){
switch(j){
case 0: L_A = 0 ; L_B = 0 ; L_C = 0 ; break ;
case 1: L_A = 0 ; L_B = 0 ; L_C = 1 ; break ;
case 2: L_A = 0 ; L_B = 1 ; L_C = 0 ; break ;
case 3: L_A = 0 ; L_B = 1 ; L_C = 1 ; break ;
case 4: L_A = 1 ; L_B = 0 ; L_C = 0 ; break ;
case 5: L_A = 1 ; L_B = 0 ; L_C = 1 ; break ;
case 6: L_A = 1 ; L_B = 1 ; L_C = 0 ; break ;
case 7: L_A = 1 ; L_B = 1 ; L_C = 1 ; break ;
}
for(i = 0 ; i < 10 ; i ++){
P1 = NUM[i];
dealy();
};
}
};
}

通过控制单片机P1通道使其输出0-9对应的二进制值,并通过实验箱内的138译码器实现不同数码管之间的循环。

实验

将J12和JP8进行跳线连接,将P0.0-P0.2和138译码器区域内的ABC进行连接。

下载程序。

实验6

实验7:点阵屏显示图案(X 逐行扫描,Y IO直出)

本实验实现了在点阵屏上输出一个图案,但是因为点阵屏的Y轴直接连接单片机IO,故造成单片机IO大量占用,并非最优解。

编程

写一个小程序将图片转换为一个16进制二维数组

小程序使用python编写。

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
# -*- coding: gbk -*-
from PIL import Image


def process_image_to_decimal(image_path, output_path):
# 加载图片并转换为RGB模式
image = Image.open(image_path).convert('RGB')

with open(output_path, "w", encoding="utf-8") as file:
# 遍历图片的每一行(共16行)
for y in range(16):
# 每行初始化一个16位的二进制数组
binary_arr = [0] * 16

# 遍历该行的每个像素列(共16列)
for x in range(16):
r, g, b = image.getpixel((x, y))

# 判断是否为黑色像素(使用逻辑运算符and)
if r == 0 and g == 0 and b == 0:
binary_arr[x] = 1
else:
binary_arr[x] = 0

# 分割为前8位和后8位
first_byte = binary_arr[:8]
second_byte = binary_arr[8:]

# 转换为十进制数值
decimal1 = int("".join(map(str, first_byte)), 2)
decimal2 = int("".join(map(str, second_byte)), 2)

hex1 = f"{decimal1:02X}"
hex2 = f"{decimal2:02X}"
file.write(f"{{0x{hex1},0x{hex2}}},\n")

# 使用示例
if __name__ == "__main__":
input_image = r"C:\Users\22979\Desktop\img.png"
output_file = r"C:\Users\22979\Desktop\example.txt"

try:
process_image_to_decimal(input_image, output_file)
print("文件转换完成")
except Exception as e:
print(f"错误发生: {str(e)}")

将生成文件中的数组,粘贴到 unsigned char code SER_XIN[16][2]

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

#define J17 P1
#define J18 P0

const unsigned char code SER_XIN[16][2] = {
{0x10,0x08},
{0x38,0x1C},
{0x7C,0x3E},
{0xEE,0x7F},
{0xEF,0xF7},
{0xEF,0xF7},
{0xE0,0x07},
{0xEF,0xE7},
{0x6F,0xEE},
{0x3F,0xDC},
{0x1F,0xF8},
{0x0F,0xF0},
{0x07,0xE0},
{0x03,0xC0},
{0x01,0x80},
{0x00,0x00}
};

sbit SER = P3^4 ; //595串行输入
sbit RCK = P3^5 ; //储存寄存器时钟
sbit SCK = P3^6 ; //位移寄存器时钟

// int SER_TEST[36] = {0,1,0,1,1,0,0,1 , 1,1,0,0,0,1,1,1 , 0,0,0,0,0,0,0,0 , 0,0,0,0,0,0,0,0};

int m_i = 0;

void VGA(int a){
J18 = SER_XIN[a][0];
J17 = SER_XIN[a][1];
}

void main(){
SER = 0;
RCK = 0;
SCK = 0;
while(1){
for(m_i = 0 ; m_i < 32 ; m_i ++){
if((m_i%32) == 0 || (m_i%32) == 31){
SER = 0;
}else{
SER = 1;
}

if((m_i%2) == 0){
VGA(m_i/2);
SCK = 1;
RCK = 0;
}else{
SCK = 0;
RCK = 1;
}

}
}
}

实验

将J18和P0、J17和P1相连,将JP595使用跳线帽短接,下载程序

观察到点阵屏出现图案

实验8:点阵屏显示图案(X 逐行扫描,XY 595输出)

本实验和试验7相同均是实现点阵屏图案显示,但实验8仅使用3个IO,相比实验7使用了11个IO可以算是极其节省了。

编程

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
#include "reg51.h"
#include <intrins.h>

sbit SER = P3^4 ; //595串行输入
sbit RCK = P3^5 ; //储存寄存器时钟
sbit SCK = P3^6 ; //位移寄存器时钟

char move = 0 ;

short int list[16] = {
{0xAAAA},
{0xCCCC},
{0xF0F0},
{0xFF00},
{0xDB6C},
{0xE38E},
{0x5B55},
{0x8800},
{0x4894},
{0x3E3C},
{0x4F72},
{0x05A6},
{0x31C0},
{0x473C},
{0x7CDE},
{0xC400},
};

short int row[16] = {
{0xFFFE},
{0xFFFD},
{0xFFFB},
{0xFFF7},
{0xFFEF},
{0xFFDF},
{0xFFBF},
{0xFF7F},
{0xFEFF},
{0xFDFF},
{0xFBFF},
{0xF7FF},
{0xEFFF},
{0xDFFF},
{0xBFFF},
{0x7FFF},
};

void Serial_OUT(int dat){ //数据串行输出 IO 数据 位数
int j = 0;
for( ; j < 16 ; j ++){
SCK = 0;
SER = dat & 0x8000 ? 1 : 0;
SCK = 1;
dat <<= 1;
}

}

void VGA(short int Y[16] , short int X[16] ){
for(move = 0 ; move < 16 ; move ++){
Serial_OUT(X[move]); //数据帧
Serial_OUT(Y[move]); //数据帧
RCK = 0;
RCK = 1;
}
}

void main(){
int move = 0;

SER = 0;
RCK = 0;
SCK = 0;
while(1){
VGA(list,row);
}
}

实验

本次实验完全不需要使用跳线,仅需将JP595使用跳线帽进行短接即可。

下载程序。

实验9:ADC模数转换

本开发板使用XPT2046,其使用了SPI进行数据通讯,并配有4路12bitADC电路。

编程

此次程序设计使用了多文件工程。

xpt2046的通讯程序

首先定义头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#ifndef __xpt2046_H__
#define __xpt2046_H__

#include "reg51.h"

typedef unsigned char u8;
typedef unsigned short int u16;
typedef unsigned int u32;

sbit DOUT = P3^7;
sbit DIN = P3^6;
sbit CS = P3^5;
sbit CLK = P3^4;

void xpt2046_delay(u16 sleep);
void xpt2046_write(u8 dat);
u16 xpt2046_read(void);
u16 xpt2046_read_adc(u8 a);

#endif

其次编写xpt2046的通讯函数。

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
#include "xpt2046.h"


void xpt2046_delay(u16 sleep){
u16 x,y;
for(x = sleep ; x > 0 ; x --){
for(y = 18 ; y > 0 ; y --){

}
}
}

void xpt2046_write(u8 dat){
u8 i;
CLK = 0;
for(i = 0 ; i < 8 ; i ++){
DIN = dat & 0x80 ? 1 : 0 ;
CLK = 0;
CLK = 1;
dat <<= 1;
}
}

u16 xpt2046_read(){
u8 j;
u16 dat_read = 0x0000;
CLK = 0;
for(j = 0 ; j < 12 ; j ++){
dat_read <<= 1;
CLK = 1 ;
CLK = 0;
dat_read |= DOUT;
}
return dat_read;
}

u16 xpt2046_read_adc(u8 a){
u16 adc = 0 ;
CS = 0;
CLK = 0;
xpt2046_write(a);
xpt2046_delay(1);
CLK = 1;
xpt2046_delay(1);
CLK = 0;
adc = xpt2046_read();
CS = 1;
return adc;
}

最后编写主函数。

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
#include "reg51.h"
#include "xpt2046.h"

sbit hc138_a = P1^0;
sbit hc138_b = P1^1;
sbit hc138_c = P1^2;

typedef unsigned int uint;
typedef unsigned char uchar;

#define LED P0

u8 NUM[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};

void GMS_display(u8 a[] , u8 sum){
u8 GMS_i = 0;
for(GMS_i = 0 ; GMS_i < 4 ; GMS_i ++){
switch(GMS_i){
case 0:hc138_a = 0 ; hc138_b = 0 ; hc138_c = 0; break;
case 1:hc138_a = 0 ; hc138_b = 0 ; hc138_c = 1; break;
case 2:hc138_a = 0 ; hc138_b = 1 ; hc138_c = 0; break;
case 3:hc138_a = 0 ; hc138_b = 1 ; hc138_c = 1; break;
}
LED = NUM[a[GMS_i]];
xpt2046_delay(100);
LED = 0x00;
}
}
void main(){
u16 adc = 0;
u8 i = 0;
u8 sum[4];
while (1)
{
if(i == 20){
adc = xpt2046_read_adc(0xD4);//将此处的值更改为0x94为可调电阻、0xA4为光敏电阻、0xD4为热敏电阻。
i = 0;
}
i ++;
sum[0] = adc / 1000;
sum[1] = adc % 1000 /100;
sum[2] = adc % 1000 %100 /10;
sum[3] = adc % 1000 %100 %10;
GMS_display(sum,4);
}
}

实验

将单片机P1.0-P1.2使用跳线连接至74HC138的ABC端口,将P0口与J12使用跳线连接,将P3.4-P3.7与AD/DA区域内的CLK、CS、DIN、DOUT使用跳线进进行连接。

下载程序。

ADC实验

实验10:ADC读取可调电阻电压并使用LCD显示电压,并使用串口向上位机传输电压书记

编程

XPT2046的程序在实验9已经编写过,可以直接使用,在此处不再编写。

定义LCD头文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef __LCD_H__
#define __LCD_H__

#include "reg51.h"

#define BD P0

sbit RS = P2^6;//RS = 1 Ñ¡ÔñÊý¾Ý¼Ä´æÆ÷£»RS = 0 Ñ¡ÔñÖ¸Áî¼Ä´æÆ÷
sbit RW = P2^5;//RW = 1 ¶ÁÐÅÏ¢£»RW = 0дÐÅÏ¢¡£
sbit EN = P2^7;//ʹÄÜÐźţ¬µ±ENΪ0ʱִÐÐÖ¸Áî¡£

void lcd_dealy(u16 sleep);
u8 lcd_read();
u8 lcd_busy();
void lcd_write_cmd(u8 cmd);
void lcd_write_data(u8 dat);
void lcd_inti();
void lcd_display(u8 dat[],u8 sum);


#endif

编写LCD函数

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
#include "LCD.h"

void lcd_dealy(u16 sleep){ //LCD专用延时函数
u16 x,y;
for(x = sleep ; x > 0 ; x --){
for(y = 18 ; y > 0 ; y --){
}
}
}

u8 lcd_read(){ //LCD读函数
u8 dat = 0x00;
RS = 0;
RW = 1;
EN = 0;
lcd_dealy(1);
EN = 1;
dat = BD;
return dat;
}

u8 lcd_busy(){ //LCD忙检测
u8 temp = 0x01 & lcd_read();
return temp;
}

void lcd_write_cmd(u8 cmd){ //LCD写命令
RS = 0;
RW = 0;
EN = 0;
BD = cmd;
lcd_dealy(2);
EN = 1;
lcd_dealy(2);
EN = 0;
}

void lcd_write_data(u8 dat){ //LCD写数据
RS = 1;
RW = 0;
EN = 0;
BD = dat;
lcd_dealy(2);
EN = 1;
lcd_dealy(5);
EN = 0;
}

void lcd_inti(){ //LCD复位
lcd_write_cmd(0x38);
lcd_write_cmd(0x0c);
lcd_write_cmd(0x60);
lcd_write_cmd(0x01);
}

void lcd_display(u8 dat[],u8 sum){ //LCD显示函数
u8 i;
for(i = 0 ; i < sum ; i ++){
lcd_write_cmd(0x80+i);
lcd_write_data(dat[i]);
}
}

编写主函数同样可以在实验9的主函数上略加修改即可

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
#include "reg51.h"
#include "xpt2046.h"
#include "LCD.h"

sbit hc138_a = P1^0;
sbit hc138_b = P1^1;
sbit hc138_c = P1^2;

void UsartInit(){ //串口复位函数
SCON = 0X50;
TMOD &= 0X0F;
TMOD |= 0X20;
PCON &= 0X7F;
TH1 = 0XF3;
TL1 = 0XF3;
// ES = 1;
// EA = 1;
TR1 = 1;
}

void Usart_white(u8 dat[],u8 sum){ //串口发送函数
u8 i;
for(i = 0 ; i < sum ; i ++){
SBUF = dat[i];
while(!TI);
TI = 0;
xpt2046_delay(10);
}
xpt2046_delay(200);
}

void main(){ //主函数
u16 adc = 0;
u8 i = 0;
u8 sum[4];
u8 HEX_H[6];
UsartInit();
lcd_inti();
while (1)
{
if(i == 20){
adc = xpt2046_read_adc(0x94);
i = 0;
adc = adc*12/10;
sum[0] = adc / 1000;
sum[1] = adc % 1000 /100;
sum[2] = adc % 1000 %100 /10;
sum[3] = adc % 1000 %100 %10;
}
i ++;

HEX_H[0] = '0' + sum[0] ;
HEX_H[1] = '.' ;
HEX_H[2] = '0' + sum[1] ;
HEX_H[3] = '0' + sum[2] ;
HEX_H[4] = '0' + sum[3] ;
HEX_H[5] = 'V' ;

lcd_display(HEX_H,6);
Usart_white(HEX_H,6);
xpt2046_delay(100);
}


}

实验

将P3.4-P3.7与AD/DA区域内的CLK、CS、DIN、DOUT使用跳线进进行连接;将LCD1602液晶屏幕,插入J1602。

下载程序。

LCD实验

串口信息

使用node-red写一个小程序,将单片机获取到的电压数据使用图形化的方式显示出来。

node-red界面

实验11:使用定时器中断产生PWM波形

编程

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

sbit pwm = P1^0;

u16 i = 0;

void Timer0Init(void)
{
TCON |= 0X10;
TMOD &= 0xF0;
TMOD |= 0x02;
TL0 = 0xFE;
TH0 = 0xFE;
TF0 = 0;
TR0 = 1;
EA = 1;
ET0 = 1;

}

void User() interrupt 1{
i = i < 100 ? i : 0 ;
pwm = i < 42 ? 1 : 0;
i ++;
}

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

}
}

实验

下载程序,使用示波器测试P1.0口输出波形。

示波器

观察到占空比为42%的波形。