单片机时的实验。
单片机实验
运行环境
- 单片机:STC89C52开发板,某宝有售
- KEIL:用于编写程序
- STC-ISP:程序写入
1. 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
// 延时函数
void delay(){
uchar i,j;
for(i=0;i<255;i++)
for(j=0;j<255;j++);
}
// 主函数
int main(){
// 初始化
uchar i,temp;
// 循环运行
while(1){
// 循环前进移动
temp = 0x01;
for(i=0;i<8;i++){
P1 = ~temp;
delay();
temp = temp <<1;
}
// 循环返回移动
temp = 0x80;
for(i=0;i<8;i++){
P1= ~temp;
delay();
temp =temp >>1;
}
}
}
2. 航标灯
模拟航标灯工作:在白天熄灭,在夜晚断续点亮,时间间隔2秒,即亮2秒,灭2秒,周期循环进行。(8个LED灯一起闪烁,用按键开关1模拟光线传感器,按下表示白天,松开表示夜晚)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sbit FLAG = P3^0; // 设置P3_0
void delay(){ // 延时函数
unsigned int i,j; // 此处由于超出255,不能再使用uchar
for(i=0;i<2000;i++)
for(j=0;j<110;j++);
}
int main(){ // 主函数
uchar temp;
temp = 0x00;
while(1){
if(FLAG==0){ // 白天
P1 = 0xff; // 无闪烁
}else{ // 晚上
P1= ~temp; // 不断闪烁
temp = ~temp;
delay();
}
}
}
3. LED灯前进
上电时初始状态为LED灯D1亮其他熄灭,之后每按下一次S4按键,LED灯移动一位,每按下一次S5键,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
sbit S4 = P3^2;
sbit S5 = P3^3;
void delay(){ // 延时
uchar i,j;
for(i=0;i<255;i++)
for(j=0;j<255;j++);
}
void delay10ms(){ // 延时
uchar i,j;
for(i=0;i<30;i++)
for(j=0;j<110;j++);
}
int main(){
uchar i,temp;
i=1;
temp = 0x01;
P1 = ~temp;
while(1){
if(S4==0){ // 按下一次LED灯前进一位
delay10ms(); // 延时
if(S4==0){
while(S4==0); // 按键释放检测
temp = 0x01;
temp = temp <<(i%8);
P1 = ~temp;
delay();
i++;
}
}
if(S5==0){ // 复位
delay10ms();
if(S5==0){
while(S5==0);
i=1;
P1 = ~(0x01);
}
}
}
}
4. 动态刷新-显示学号
动态刷新程序,让数码管显示学号后8位
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
sbit WEI = P2^7;
sbit DUAN = P2^6;
// 阴极0-9对应端码
unsigned char segment_code[] ={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
// 要显示的数字
unsigned char stu_id[] = {3,1,0,5,4,2,0,5};
// 延时函数
void delay(){
unsigned long i = 0x30;
while(i--);
}
// 主函数
int main(){
unsigned long i;
while(1){
long i; // 此处不能为unsigned
unsigned long temp;
for(i=7;i>=0;i--){
P0=~(0x01<<i); // 每次移动一位
WEI=1; WEI=0; // 打开位锁存
P0 = segment_code[stu_id[i]]; // 每次每一位学号
DUAN=1;DUAN=0; // 打开端锁存
delay(); // 延时显示
P0 = 0x0; // 以下三句用于消隐
DUAN = 1;
DUAN=0;
}
}
}
5. 动态显示000000001->99999999
动态刷新程序封装为一个函数,由main函数调用,在此基础上,让数码管循环加1显示,从0000 0001开始,一直加到9999 9999(检查实验结果)
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
sbit WEI = P2^7;
sbit DUAN = P2^6;
// 阴极0-9对应端码
unsigned char segment_code[] ={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
// 延时函数
void delay(){
unsigned long i = 0x30;
while(i--);
}
// 动态刷新显示指定的数值
void segDisplay(long number){
long i; // 不能为unsigned
unsigned long temp;
for(i=7;i>=0;i--){ // 遍历每一个LED
P0=~(0x01<<i); // 每次访问一个LED
WEI=1; WEI=0;
temp=number%10; // 每次获取一个数字
number/=10; // 砍掉获取到的数字
P0 = segment_code[temp];// 显示获取到的数字
DUAN=1;DUAN=0;
delay(); // 延时显示
P0 = 0x0; // 消隐
DUAN = 1;
DUAN=0;
}
}
// 主函数
int main(){
unsigned long i;
while(1){
for(i=1;i<=99999999;i++){
unsigned long k = 5;
while(k--){ // 此处为了防止过快显示
segDisplay(i);
}
}
}
}
6. 矩阵键盘扫描
编写矩阵键盘扫描程序,扫描矩阵按键,每次按下按键后,用数码管显示矩阵键值。
原理
原理:
P3口高4位通高电,依次给P3低4位(每一行)通低电位。
通完电之后,读取P3口的高4位,如果通低电位的所在行,有按键被按下,则P3口的高4位必定有一位不是高电位。
假如S12被按下,则扫描到第三行的时候,读取P3口的高4位,则会读到:0xc0
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
sbit WEI = P2^7;
sbit DUAN = P2^6;
unsigned char segment_code[] ={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
unsigned char last_key = 0; // 用于保存上一次按下的按键
// 延时
void delay(){
unsigned long i = 0x10;
while(i--);
}
// 延时10ms
void delay10s(){
unsigned int i = 1*110;
while(i--);
}
// 显示传入参数函数
void segDisplay(long number){
long i;
unsigned long temp;
for(i=7;i>=0;i--){
P0=~(0x01<<i);
WEI=1; WEI=0;
temp=number%10;
number/=10;
P0 = segment_code[temp];
DUAN=1;DUAN=0;
delay();
P0 = 0x0;
DUAN = 1;
DUAN=0;
}
}
// 扫描矩阵键盘,并返回指定的值。
int getKey(){
unsigned char temp;
unsigned char key=0;
unsigned char l;
unsigned char i;
for(i=0;i<4;i++){
P3 = ~(0x01<<i); // 循环设置P3口低4位为低电平,同时给P3口高4位通电
temp = P3; // 读取P3口
temp &= 0xf0; // 去除读取到的低4位
if(temp!=0xf0){ // 消除抖动判断是否有键被按下
delay10s();
if(temp!=0xf0){
switch(temp){// 依次判断按下的是"那一列?"
case 0xe0:
key = 4*i+1;
break;
case 0xd0:
key = 4*i+2;
break;
case 0xb0:
key = 4*i+3;
break;
case 0x70:
key = 4*i+4;
break;
}
}
}
}
if(key!=0){ // 如果有键按下,更新矩阵按下的值last_key
last_key = key;
}
return last_key;
}
// 主函数
int main(){
unsigned char key;
while(1){
key = getKey();
segDisplay(key);
}
}
7. 中断控制数码管计数
用中断控制数码管计数,上电后数码管显示000000001,按下中断按键后数码管开始递增计时,再次按下时暂停计时,以此类推
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
sbit WEI = P2^7;
sbit DUAN = P2^6;
unsigned char segment_code[] ={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};
unsigned long num= 1; // 用于递增变量
unsigned char flag = 0; // 暂停还是递增的标志
void delay(){
unsigned long i = 0x10;
while(i--);
}
void segDisplay(long number){ // 显示指定数值函数
long i;
unsigned long temp;
for(i=7;i>=0;i--){
P0=~(0x01<<i);
WEI=1; WEI=0;
temp=number%10;
number/=10;
P0 = segment_code[temp];
DUAN=1;DUAN=0;
delay();
P0 = 0x0;
DUAN = 1;
DUAN=0;
}
}
// 中断函数
void intr() interrupt 0 using 0 {
if(flag==1){// 如果是递增,这将标志位设置为暂停
flag=0;
}else{ // 如果是暂停,这将标志位设置为递增
flag=1;
}
}
int main(){
EA= 1; // 总中断允许
EX0=1; // 允许外部中断0中断
IT0=1; // 选择外部中断0为跳延触发方式
while(1){
if(flag==0){ // 暂停
segDisplay(num);
}else{ // 递增
unsigned char i = 0x05;
while(i--){ // 此处减缓显示管的递增速度
segDisplay(num);
}
num++; // 数字递增
}
}
}
8. 蜂鸣器输出不同音调
编写定时器程序,实现输出特定精确频率的方波给蜂鸣器,实现不同的音调
步骤
- 计算机器周期:(1/单片机频率)*12
- 计算次数:(需要时间/机器周期)
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
sbit beep = P2^3; // 蜂鸣器所在端口
double tones[] = {523,587,659,698,783,880,987}; // 中音的7个音调
unsigned char flag;
unsigned int i;
// "两个老虎"的简单音谱
unsigned int tigers[] = {1,2,3,1,1,2,3,1,3,4,5,3,4,5,5,6,5,6,4,3,2,7,5,6,4,3,2,7,1,5,1,1,5,1};
// 中断主程序
void timer() interrupt 1 {
TH0 = (unsigned int)(65536-11059200/tones[flag]/24)/256;
TL0 = (unsigned int)(65536-11059200/tones[flag]/24)%256;
if(i==34){ // 循环一遍之后,将蜂鸣器置空一段时间
beep = 1;
}else{
beep = ~beep;
}
}
// 延时
void delay(){
unsigned int i;
unsigned char j;
for(i=0;i<300;i++)
for(j=0;j<110;j++);
}
void main(){
unsigned char k =5;
TMOD = 0x01;
flag = 0;
TH0 = (unsigned int)(65536-11059200/tones[flag]/24)/256;
TL0 = (unsigned int)(65536-11059200/tones[flag]/24)%256;
beep = 0;
EA = 1;
ET0 = 1;
TR0 = 1;
while(1){
for(i=0;i<34;i++){ // 遍历“两个老虎”音调谱
flag = tigers[i]-1;
delay();
}
while(k--){
delay();
}
}
}
9. 串口编程入门
步骤
- 初始化
- 串口初始化:SCON->REN
- 定时器初始化:PCON->TMOD
- 初始化初值:TMOD->比特率
- 启动定时器
- 开中断
- 中断接收
- 初始化
SCON:串口控制寄存器
每个位 SM0 SM1 SM2 REN TB8 RB8 TI RI 功能 方式选择 方式选择 - 允许串行接收位 - - - - 0 1 0 1 - SM0、SM1(最常用)
- 方式1:8位异步收发,波特率可变
- REN
- 允许:1
- 禁止:0
- SCON常用赋值:0x50
- SM0、SM1(最常用)
PCON:特殊功能寄存器
SMOD - - - GF1 GF0 PD IDL PCON仅有最高位与串行口相关,其余是电源相关,因此使用是需要谨慎。
PCON仅有最高位与串行口相关,其余是电源相关,因此使用是需要谨慎。
PCON最高位:波特率
- 1比0翻倍。
此处为了不干扰其他选项:PCOM|=(0x01<<7);
TMOD:定时、计数器寄存器
TMOD GATE C/T M1 M0 GATE C/T M1 M0 T1 T1 T1 T1 T0 T0 T0 T0 - GATE:门控位,是否计数
- M1、M0工作方式:此处选择8位自动填装 M1= 1,M0=0,GATE
- TMOD = 0x20;
将以上初始化代码封装为函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14void init(){ // 使用该函数的前提是导入了 reg52.h
SCON = 0x40;
PCON |= 0x80;
TMOD &= 0x0F; // 将高4位置0
TMOD |= 0x10;
// 赋初值 19.2kbit
TH1 = 0xFD;
TL1 = 0xFD;
// 启动定时器
TR1 = 1;
// 开中断
EA = 1;
ES = 1;
}定义发送字符串函数
1
2
3
4
5
6
7void sendchar(char data){
// 给发送缓冲区写值
SBUF = data;
// 判断是否读取完毕
while(TI==0);
TI=0;
}定义中断的方式接收字符
1
2
3
4
5
6
7void getChar() interrupt 4 {
if(RI){
RI = 0;
ch = SBUF;
read_flag = 1; // 读取一个字节的标志位(全局)
}
}
10. 串口通信-发送与接收
在PC端通过串口工具发送字符,在单片机一侧实现接收字符,再把接收到的字符发送回PC
代码
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
bit read_flag = 0; // 读取的标志位
unsigned char ch; // 存放读取到的字符
// 串口通信的初始化
void init_serialcom( void ) {
SCON = 0x50 ; //SCON: serail mode 1, 8-bit UART, enable ucvr
TMOD |= 0x20 ; //TMOD: timer 1, mode 2, 8-bit reload
PCON |= 0x80 ; //SMOD=1;
TH1 = 0xFA ; //Baud:9600 fosc=11.0592MHz
IE |= 0x90 ; //Enable Serial Interrupt
TR1 = 1 ; // timer 1 run
TI=1;
}
// 发送字符
void send_char_com( unsigned char ch) {
SBUF = ch;
while (TI== 0);
TI= 0 ;
}
// 串口监听字符
void serial () interrupt 4 using 3 {
if (RI) {
RI = 0 ;
ch=SBUF;
read_flag= 1 ;
}
}
void main(){
init_serialcom();
while(1){
if(read_flag==1){ // 判断是否读取到数据
send_char_com(ch); // 将读取到的数据发送出去
read_flag=0; // 将读取标志清零
}
}
}
11. 串口通信-pc控制单片机
PC端通过串口发送命令,控制单片机外设工作,如PC端用十六进制发送0x01,点亮单片机上第一个LED灯,发送0x02,点亮第二个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
bit read_flag = 0; // 读取的标志位
unsigned char ch; // 存放读取到的字符
// 串口通信的初始化
void init_serialcom( void )
{
SCON = 0x50 ; //SCON: serail mode 1, 8-bit UART, enable ucvr
TMOD |= 0x20 ; //TMOD: timer 1, mode 2, 8-bit reload
PCON |= 0x80 ; //SMOD=1;
TH1 = 0xFA ; //Baud:9600 fosc=11.0592MHz
IE |= 0x90 ; //Enable Serial Interrupt
TR1 = 1 ; // timer 1 run
TI=1;
}
// 发送字符
void send_char_com( unsigned char ch) {
SBUF = ch;
while (TI== 0);
TI= 0 ;
}
// 串口监听字符
void serial () interrupt 4 using 3 {
if (RI) {
RI = 0 ;
ch=SBUF;
read_flag= 1 ;
}
}
void main(){
init_serialcom();
while(1){
if(read_flag==1){ // 判断是否读取到数据
switch(ch){
case 0x01:
P1 = ~(0x01);
break;
case 0x02:
P1 = ~(0x01<<1);
break;
case 0x03:
P1 = ~(0x01<<2);
break;
case 0x04:
P1 = ~(0x01<<3);
break;
case 0x05:
P1 = ~(0x01<<4);
break;
case 0x06:
P1 = ~(0x01<<5);
break;
case 0x07:
P1 = ~(0x01<<6);
break;
default:
P1 = ~(0x01<<7);
break;
}
read_flag=0;
}
}
}
12. 串口通信-多命令的读取与判断
制定一个简单的串口通信协议,如下表,编写单片机的串口中断程序,解析以下命令并执行
第一个字节 第二个字节 第三个字节 0xff:命令头部 0x01:点亮 0x02:熄灭 0x01:第一个LED 0x02:第二个LED … 例如:当PC端向单片机发送0xff 0x01 0x02 命令 则点亮第二个LED
当PC端向单片机发送0xff 0x02 0x05 命令 则熄灭第五个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
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
unsigned char read_flag; // 命令标志位
unsigned char ch; // 读取来自PC端的字符
unsigned char is_light; // 灯亮标志位
// 串口通信的初始化
void init_serialcom( void ) {
SCON = 0x50 ; //SCON: serail mode 1, 8-bit UART, enable ucvr
TMOD |= 0x20 ; //TMOD: timer 1, mode 2, 8-bit reload
PCON |= 0x80 ; //SMOD=1;
TH1 = 0xFA ; //Baud:9600 fosc=11.0592MHz
IE |= 0x90 ; //Enable Serial Interrupt
TR1 = 1 ; // timer 1 run
TI=1;
}
// 监听中断
void serial () interrupt 4 using 3 {
if (RI){ // 每当RI读取1个字节,就会置1 。
RI=0; // 读取标志位清零
ch=SBUF; // 从缓冲区获取数据
if(ch==0xff){ // 读取到第一个命令(标志命令的开端)
read_flag = 1;// 判断读到第一个命令
}else{
if(read_flag==1){ // 读取到第2个命令(灯亮的标志位)
read_flag=2; // 设置标志位为2
if(ch==0x01){
is_light = 1; // 设置灯亮
}else if(ch==0x02){
is_light = 0; // 设置灯灭
}
}else if(read_flag==2){ // 读取到第3个命令(灯控位)
read_flag =0; // 将标志位清空
if(is_light){ // 灯亮(将该位,置0,其他位不变)
switch(ch){
case 0x01:
P1 &= ~(0x01);
break;
case 0x02:
P1 &= ~(0x01<<1);
break;
case 0x03:
P1 &= ~(0x01<<2);
break;
case 0x04:
P1 &= ~(0x01<<3);
break;
case 0x05:
P1 &= ~(0x01<<4);
break;
case 0x06:
P1 &= ~(0x01<<5);
break;
case 0x07:
P1 &= ~(0x01<<6);
break;
case 0x08:
P1 &= ~(0x01<<7);
break;
}
}else{ // 灯灭(将该位,置1,其他位不变)
switch(ch){
case 0x01:
P1 |= (0x01);
break;
case 0x02:
P1 |= (0x01<<1);
break;
case 0x03:
P1 |= (0x01<<2);
break;
case 0x04:
P1 |= (0x01<<3);
break;
case 0x05:
P1 |= (0x01<<4);
break;
case 0x06:
P1 |= (0x01<<5);
break;
case 0x07:
P1 |= (0x01<<6);
break;
case 0x08:
P1 |= (0x01<<7);
break;
}
}
}
}
}
}
void main(){
init_serialcom(); // 初始化参数
while(1); // 死循环等待读取完毕中断产生
}注意:
- 本题难点不在于代码逻辑的实现,而是在于通信之间数据的交互
串口助手
注意:
在使用该助手的时候,在PC发送数据给单片机的时候,在选择HEX模式(十六进制)的时候,发送数据时不要加0x前缀或者在前面补0,直接写对应的数值,例如:ff 2 5不要写成0xff 0x02 0x05或者ff 02 05。否则出现的逻辑错误调试调到怀疑人生
每次使用时记得打开串口,而且使用前,波特率记得要选对,串口在左上角串口号可以看到当前使用的是哪一个串口。
已经完结了!!!!发现有什么错误,欢迎留言指出。