zl程序教程

您现在的位置是:首页 >  工具

当前栏目

《单片机串口通信及测控应用实战详解》——6.2 单片机端程序设计

应用单片机通信 详解 实战 程序设计 串口 6.2
2023-09-11 14:17:47 时间

本节书摘来自异步社区《单片机串口通信及测控应用实战详解》一书中的第6章,第6.2节,作者 李江全,聂晶,梁习卉子,刘新英,更多章节内容可以访问云栖社区“异步社区”公众号查看。

6.2 单片机端程序设计 6.2.1 使用查询方式C51程序设计

串口在发送数据和接收数据完成时均会引起串口中断,从而使接收标志位RI和发送标志位TI置1。查询方式和中断方式的区别就在于CPU查看RI和TI方式不同,以及处理数据的效率不同。

查询方式是指通过CPU定时的查询SCON串口控制寄存器中的接收标志位RI和发送标志位TI来接收和发送数据。此种方式下,当串口发送数据或接收数据完成时,仅仅将相应的标志位置1而不会以任何形式通知主程序。主程序只能通过定时查询发现标志位状态的改变,从而进行相应的处理,如标志位的清0。这种方式下数据的发送和接收是半双工的,占用CPU时间长,工作效率低。

中断方式是在接收和发送数据时,CPU不必连续地查询接收标志位RI和发送标志位TI。当串口发送数据或接收数据完成时,CPU自动转入中断服务程序对接收到的数据进行处理,只需要在中断服务程序中通过查看是RI还是TI来判断数据是接收还是发送,从而跳转至相应的处理部分。这种方式下可以实现全双工通信,CPU可以腾出时间处理其他任务,效率高,速度快。

查询方式通信流程:当串口接收到数据时,硬件系统将RI置1。在主程序中当CPU首次查询到RI = 1时,首先判断接收的首字节是否为本机地址,如果不是则将接收缓冲区里的数据清0返回重新查询接收;如果是则驱动继电器动作、通过数码管显示数据和地址,并将数据返回给上位机;然后继续下一次循环。

各个单片机开发板C51程序基本相同,只是地址不同,在常量声明“#define”语句中体现。

#include reg51.h 

#include string.h 

#define addr 01 //02号单片机板C51程序addr为02;03号单片机板C51程序addr为03

#define uint unsigned int 

#define uchar unsigned char 

sbit jdq1 = P2^0; //继电器1

sbit jdq2 = P2^1; //继电器2 

/**********************数码显示 键盘接口定义**********************/ 

sbit PS0 = P2^4; //数码管个位 

sbit PS1 = P2^5; //数码管十位 

sbit PS2 = P2^6;//数码管百位 

sbit PS3 = P2^7;//数码管千位

sfr P_data = 0x80;//P0口为显示数据输出口

sbit P_K_L = P2^2;//键盘列 

//字段转换表

uchar tab[] = {0xfc,0x60,0xda,0xf2,0x66,0xb6,0xbe,0xe0,0xfe,0xf6,

 0xee,0x3e,0x,0x,0x9e,0x8e}; 

uchar data_buf[2];

void init_serial(void);

bit recv_data(void);

void display(uchar a,uchar c);

void sw_out(unsigned char b);//开关量输出

void delay(unsigned int delay_time);

void main(void)

{ uint a;

 init_serial();

 EA = 0;

 while(1)

 if(recv_data() == 0)

 { data_buf[0] = 0;

 data_buf[1] = 0;

 continue;

 sw_out(data_buf[1]);

 TI = 0;

 SBUF = data_buf[0];

 while(!TI);

 TI = 0;

 TI = 0;

 SBUF = data_buf[1];

 while(!TI);

 TI = 0;

 for(a = 0;a a++)//显示,兼有延时的作用

 display(data_buf[1],data_buf[0]);

/**************************串口初始化函数**************************/

/*函数原型:void init_serial(void)

/*函数功能:设置串口通信参数及方式

/******************************************************************/ 

void init_serial(void)

{ TMOD = 0X20;//定时器1方式2

 TH1 = 0XFA;

 TL1 = 0XFA;

 PCON = 0X80;

 SCON = 0X50;//串口方式1,允许接收,波特率9600bit/s

 TR1 = 1; //开始计时

 /**************************数据接收函数**************************/

/*函数原型:void recv_data(uint temp)

/*函数功能:数据发送

/*输入参数:temp

/******************************************************************/ 

 bit recv_data(void)

 { uchar c0 = 0;

 uchar tmp,i = 0;

 while(c0 2)

 { RI = 0;

 while(!RI);

 tmp = SBUF;

 RI = 0;

 data_buf[i] = tmp;

 i++;

 c0++;

 if(data_buf[0]! = addr)

 return 0;

 return 1;

/**************************数码管显示函数**************************/

/*函数原型:void display(void)

/*函数功能:数码管显示

/*调用模块:delay()

/******************************************************************/ 

void display(uchar a,uchar c)

 bit b = P_K_L;

 P_K_L = 1;//防止按键干扰显示

 P_data = tab[a 0x0f]; //显示数据一位

 PS0 = 0; 

 PS1 = 1;

 PS2 = 1; 

 PS3 = 1;

 delay(200);

 P_data = tab[(a 4) 0x0f]; //显示数据十位

 PS0 = 1; 

 PS1 = 0;

 delay(200); 

 P_data = tab[c]; //显示地址一位

 PS1 = 1;

 PS2 = 0;

 delay(200);

 P_data = tab[0]; //显示地址十位

 PS2 = 1; 

 PS3 = 0;

 delay(200);

 PS3 = 1;

 P_K_L = b; //恢复按键

 P_data = 0xff; //恢复数据口

/**************************数据输出函数**************************/

/*函数原型:void sw_out(uchar a)

/*函数功能:数据采集

/******************************************************************/ 

void sw_out(unsigned char b)

 if(b = = 0x00)

 jdq1 = 1; //接收到PC发来的数据00,关闭继电器1和2

 jdq2 = 1;

 else if(b == 0x01)

 jdq1 = 1; //接收到PC发来的数据01,继电器1关闭,继电器2打开

 jdq2 = 0;

 else if(b == 0x10)

 jdq1 = 0; //接收到PC发来的数据10,继电器1打开,继电器2关闭

 jdq2 = 1;

 else if(b == 0x11)

 jdq1 = 0; //接收到PC发来的数据11,打开继电器1和2

 jdq2 = 0;

/*******************************延时函数*********************************/

/*函数原型:delay(unsigned int delay_time)

/*输入参数:delay_time (输入要延时的时间)

/**********************************************************************/

void delay(unsigned int delay_time) //延时子程序

{for(;delay_time delay_time--)

 }

将C51程序编译生成HEX文件,然后采用STC-ISP软件将HEX文件下载到单片机中。

打开“串口调试助手”程序(ScomAssistant.exe),首先设置串口号COM1、波特率9600、校验位NONE、数据位8、停止位1等参数(注意:设置的参数必须与单片机设置的一致),选择“十六进制显示”和“十六进制发送”,打开串口。

PC通过串行口将十六进制数发送给多个单片机,驱动地址吻合的单片机继电器动作,并在数码管显示接收的数。单片机接收到数据后,返回原数据给PC。

如PC发送十六进制数据“01 11”,驱动1号单片机板继电器1和2打开,单片机返回十六进制数据“01 11”。如图6-2所示。


screenshot

6.2.2 使用查询方式汇编程序设计

各个单片机开发板汇编程序基本相同,只是地址不同。

/******************************************************************

** 1号从机机程序(单片机与多个单片机串口通信) 

** 晶 振 频 率:11.0592MHz

** 线 路:单片机实验开发板B

******************************************************************/ 

 A_BYTE EQU 40H

 B_BYTE EQU 41H 

 C_BYTE EQU 42H

 D_BYTE EQU 43H

 MCU_DATA EQU 45H

 MCU_ADDR EQU 01H 

 PS0 BIT P2.4 //数码管个位 

 PS1 BIT P2.5 //数码管十位 

 PS2 BIT P2.6 //数码管百位 

 PS3 BIT P2.7 //数码管千位 

 jdq1 BIT P2.0 //继电器1

 jdq2 BIT P2.1 //继电器2 

 ORG 0000H

 SJMP MAIN

 ORG 0030H

MAIN:MOV SP,#60H

 ACALL init_serial

LOOP:CLR RI 

 JNB RI,$

 MOV A,SBUF

 CLR RI

 CJNE A,#MCU_ADDR,LOOP 

 JNB RI,$ //是本机地址

 MOV MCU_DATA,SBUF

 CLR RI

 CLR TI

 MOV SBUF,#MCU_ADDR

 JNB TI,$

 CLR TI

 MOV SBUF,MCU_DATA

 JNB TI,$

 CLR TI

 MOV A,MCU_DATA

 SWAP A

 ANL A,#0FH

 MOV B_BYTE,A

 MOV A,MCU_DATA

 ANL A,#0FH

 MOV A_BYTE,A

 ACALL SW_OUT

 MOV C_BYTE,#MCU_ADDR

 MOV D_BYTE,#0H

 MOV R3,#10H 

RR1:ACALL DISPLAY

 DJNZ R3,RR1

 SJMP LOOP

SW_OUT:MOV A,MCU_DATA //数据输出

 CJNE A,#00H,SS1

 SETB jdq1 //接收到PC发来的数据00,关闭继电器1和2

 SETB jdq2

 SJMP SS0

SS1:CJNE A,#01H,SS2

 SETB jdq1 //接收到PC发来的数据01,继电器1关闭,继电器2打开

 CLR jdq2

 SJMP SS0

SS2:CJNE A,#10H,SS3

 CLR jdq1 //接收到PC发来的数据10,继电器1打开,继电器2关闭

 SETB jdq2

 SJMP SS0

SS3:CJNE A,#11H,SS0

 CLR jdq1 //接收到PC发来的数据11,打开继电器1和2

 CLR jdq2

SS0:RET

init_serial: //串口初始化函数 

 MOV SCON,#50H ;设置成串口1方式 

 MOV TMOD,#20H ;波特率发生器T1工作在模式2上 

 MOV TH1,#0FAH ;预置初值(按照波特率4800bit/s预置初值) 

 MOV TL1,#0FAH ;预置初值(按照波特率4800bit/s预置初值) 

 ORL PCON, #80H;波特率加倍 现在的波特率为9600bit/s

 SETB TR1 ;启动定时器T1

DISPLAY: MOV A,R0

 PUSH ACC

 MOV DPTR,#NUMTAB ;指定查表起始地址

 MOV R0,#4

DPL1: MOV R1,#25 ;显示1000次

DPLOP: MOV A,A_BYTE ;取个位数

 MOVC A,@A+DPTR ;查个位数的7段代码

 MOV P0,a ;送出个位的7段代码

 CLR PS0 

 SETB PS1

 ACALL D1MS ;显示1ms

 MOV A,B_BYTE ;取十位数

 MOVC A,@A+DPTR ;查十位数的7段代码

 MOV P0,A ;送出十位的7段代码

 SETB PS0 

 CLR PS1

 ACALL D1MS ;显示1ms 

 MOV A,C_BYTE ;取百位数

 MOVC A,@A+DPTR ;查百位数的7段代码

 MOV P0,A ;送出百位的7段代码

 SETB PS1 

 CLR PS2

 ACALL D1MS ;显示1ms

 MOV A,D_BYTE ;取千位数

 MOVC A,@A+DPTR ;查千位数的7段代码

 MOV P0,A ;送出千位的7段代码

 SETB PS2 

 CLR PS3

 ACALL D1MS ;显示1ms

 SETB PS3 

 DJNZ R1,DPLOP ;100次没完循环

 DJNZ R0,DPL1 ;4个100次没完循环

 POP ACC

 MOV R0,A

D1MS: MOV R7,#80 ;1ms延时

 DJNZ R7,$

;实验板上的7段数码管0~9数字的共阴显示代码

numtab: DB 0FCH,60H,0DAH,0F2H,66H,0B6H,0BEH,0E0H,0FEH,0F6H 

 END

将汇编程序编译生成HEX文件,然后采用STC-ISP软件将HEX文件下载到单片机中。

打开“串口调试助手”程序(ScomAssistant.exe),首先设置串口号COM1、波特率9600、校验位NONE、数据位8、停止位1等参数(注意:设置的参数必须与单片机设置的一致),选择“十六进制显示”和“十六进制发送”,打开串口。

PC通过串行口将十六进制数发送给多个单片机,驱动地址吻合的单片机继电器动作,并在数码管显示接收的数。单片机接收到数据后,返回原数据给PC。

如PC发送十六进制数据“01 11”,驱动1号单片机板继电器1和2打开,单片机返回十六进制数据“01 11”,如图6-3所示。


screenshot

6.2.3 使用中断方式C51程序设计

中断方式通信流程:当串口接收到数据时,硬件系统将RI置1,触发程序进入中断服务程序。由中断服务程序接收串口数据并将其保存至接收缓冲区。中断服务程序首先判断接收的首字节是否为本机地址,如果不是则清空接收缓冲区,计数变量清0,直接退出中断服务程序从新等待数据接收;否则继续接收数据,计数变量C0加1。当接收完数据后,计数变量清0以便于下一次数据的接收,驱动继电器动作并将数据返回给上位机。然后退出中断服务程序,在主程序中通过数码管显示缓冲区的内容。

各个单片机开发板C51程序基本相同,只是地址不同,在常量声明“#define”语句中体现。

#include reg51.h 

#include string.h 

#define addr 01 //02号单片机板C51程序addr为02;03号单片机板C51程序addr为03

#define uint unsigned int 

#define uchar unsigned char 

sbit jdq1 = P2^0;//继电器1

sbit jdq2 = P2^1;//继电器2 

/***********************数码显示 键盘接口定义********************/ 

sbit PS0 = P2^4;//数码管个位 

sbit PS1 = P2^5;//数码管十位 

sbit PS2 = P2^6;//数码管百位 

sbit PS3 = P2^7;//数码管千位

sfr P_data = 0x80;//P0口为显示数据输出口

sbit P_K_L = P2^2;//键盘列 

uchar tab[] = {0xfc,0x60,0xda,0xf2,0x66,0xb6,0xbe,0xe0,

 0xfe,0xf6,0xee,0x3e,0x,0x,0x9e,0x8e};//字段转换表

uchar data_buf[2];

void init_serial(void);

void display(uchar a,uchar c);

void sw_out(unsigned char b);//开关量输出

void delay(unsigned int delay_time);

void main(void)

{ uint a;

 init_serial();

 RI = 0;

 while(1)

 { for(a = 0;a a++)//显示,兼有延时的作用

 display(data_buf[1],addr);

/**************************串口初始化函数**************************/

/*函数原型:void init_serial(void)

/*函数功能:设置串口通信参数及方式

/******************************************************************/ 

void init_serial(void)

{ TMOD = 0X20;//定时器1方式2

 TH1 = 0XFA;

 TL1 = 0XFA;

 PCON = 0X80;

 SCON = 0X50;//串口方式1,允许接收,波特率9600bit/s

 TR1 = 1; //开始计时

 ES = 1;

 EA = 1; 

//串口中断处理函数

void serial_int() interrupt 4 

{ uchar c0,i;

 if(RI == 1)

 { data_buf[i] = SBUF;

 RI = 0;

 i++;

 c0++; 

 if(data_buf[0]! = addr)

 { data_buf[0] = 0;

 data_buf[1] = 0;

 c0 = 0;

 i = 0;

 return;

 if( c0 == 2)

 { c0 = 0; 

 sw_out(data_buf[1]);

 TI = 0;

 SBUF = data_buf[0];

 while(!TI);

 TI = 0;

 TI = 0;

 SBUF = data_buf[1];

 while(!TI);

 TI = 0;

 i = 0;

/**************************数码管显示函数**************************/

/*函数原型:void display(void)

/*函数功能:数码管显示

/*调用模块:delay()

/******************************************************************/ 

void display(uchar a,uchar c)

 bit b = P_K_L;

 P_K_L = 1;//防止按键干扰显示

 P_data = tab[a 0x0f];//显示数据一位

 PS0 = 0; 

 PS1 = 1;

 PS2 = 1; 

 PS3 = 1;

 delay(200);

 P_data = tab[(a 4) 0x0f]; //显示数据十位

 PS0 = 1; 

 PS1 = 0;

 delay(200); 

 P_data = tab[c];//显示地址一位

 PS1 = 1;

 PS2 = 0;

 delay(200);

 P_data = tab[0];//显示地址十位

 PS2 = 1; 

 PS3 = 0;

 delay(200);

 PS3 = 1;

 P_K_L = b;//恢复按键

 P_data = 0xff;//恢复数据口

/**************************数据输出函数**************************/

/*函数原型:void sw_out(uchar a)

/*函数功能:数据采集

/******************************************************************/ 

void sw_out(unsigned char b)

 if(b = = 0x00)

 jdq1 = 1; //接收到发来的数据00,关闭继电器1和2

 jdq2 = 1;

 else if(b = = 0x01)

 jdq1 = 1; //接收到发来的数据01,继电器1关闭,继电器2打开

 jdq2 = 0;

 else if(b = = 0x10)

 jdq1 = 0; //接收到发来的数据10,继电器1打开,继电器2关闭

 jdq2 = 1;

 else if(b = = 0x11)

 jdq1 = 0; //接收到发来的数据11,打开继电器1和2

 jdq2 = 0;

/*******************************延时函数*********************************/

/*函数原型:delay(unsigned int delay_time)

/*函数功能:延时函数

/*输入参数:delay_time (输入要延时的时间)

/**********************************************************************/

void delay(unsigned int delay_time) //延时子程序

{for(;delay_time delay_time--)

 }
6.2.4 使用中断方式汇编程序设计

各个单片机开发板汇编程序基本相同,只是地址不同。

/******************************************************************

** 1号从机机程序(多个单片机与PC串口通信) 

** 晶 振 频 率:11.0592MHz

** 线 路:单片机实验开发板B

******************************************************************/ 

 A_BYTE EQU 40H

 B_BYTE EQU 41H 

 C_BYTE EQU 42H

 D_BYTE EQU 43H

 RECV_NUM EQU 44H

 MCU_DATA EQU 45H

 MCU_ADDR EQU 01H 

 PS0 BIT P2.4 //数码管个位 

 PS1 BIT P2.5 //数码管十位 

 PS2 BIT P2.6 //数码管百位 

 PS3 BIT P2.7 //数码管千位 

 jdq1 BIT P2.0 //继电器1

 jdq2 BIT P2.1 //继电器2 

 ORG 0000H

 SJMP 

 ORG 0023H

 AJMP UART

 ORG 0030H

MAIN:MOV SP,#60H

 MOV RECV_NUM,#0

 ACALL init_serial

LOOP:MOV A,MCU_DATA

 SWAP A

 ANL A,#0FH

 MOV B_BYTE,A

 MOV A,MCU_DATA

 ANL A,#0FH

 MOV A_BYTE,A

 ACALL SW_OUT

 MOV C_BYTE,#MCU_ADDR

 MOV D_BYTE,#0H

 ACALL DISPLAY

 SJMP 

SW_OUT:MOV A,MCU_DATA //数据输出

 CJNE A,#00H,SS1

 SETB jdq1 //接收到PC发来的数据00,关闭继电器1和2

 SETB jdq2

 SJMP SS0

SS1:CJNE A,#01H,SS2

 SETB jdq1 //接收到PC发来的数据01,继电器1关闭,继电器2打开

 CLR jdq2

 SJMP SS0

SS2:CJNE A,#10H,SS3

 CLR jdq1 //接收到PC发来的数据10,继电器1打开,继电器2关闭

 SETB jdq2

 SJMP SS0

SS3:CJNE A,#11H,SS0

 CLR jdq1 //接收到PC发来的数据11,打开继电器1和2

 CLR jdq2

SS0:RET

init_serial: //串口初始化函数 

 MOV SCON,#50H ;设置成串口1方式 

 MOV TMOD,#20H ;波特率发生器T1工作在模式2上 

 MOV TH1,#0FAH ;预置初值(按照波特率4800bit/s预置初值) 

 MOV TL1,#0FAH ;预置初值(按照波特率4800bit/s预置初值) 

 ORL PCON, #80H;波特率加倍 现在的波特率为9600bit/s

 SETB TR1 ;启动定时器T1

 SETB ES

 SETB EA

DISPLAY: MOV A,R0

 PUSH ACC

 MOV DPTR,#NUMTAB ;指定查表起始地址

 MOV R0,#4

DPL1: MOV R1,#25 ;显示1000次

DPLOP: MOV A,A_BYTE ;取个位数

 MOVC A,@A+DPTR ;查个位数的7段代码

 MOV P0,a ;送出个位的7段代码

 CLR PS0 

 SETB PS1

 ACALL D1MS ;显示1ms

 MOV A,B_BYTE ;取十位数

 MOVC A,@A+DPTR ;查十位数的7段代码

 MOV P0,A ;送出十位的7段代码

 SETB PS0 

 CLR PS1

 ACALL D1MS ;显示1ms 

 MOV A,C_BYTE ;取百位数

 MOVC A,@A+DPTR ;查百位数的7段代码

 MOV P0,A ;送出百位的7段代码

 SETB PS1 

 CLR PS2

 ACALL D1MS ;显示1ms

 MOV A,D_BYTE ;取千位数

 MOVC A,@A+DPTR ;查千位数的7段代码

 MOV P0,A ;送出千位的7段代码

 SETB PS2 

 CLR PS3

 ACALL D1MS ;显示1ms

 SETB PS3 

 DJNZ R1,DPLOP ;100次没完循环

 DJNZ R0,DPL1 ;4个100次没完循环

 POP ACC

 MOV R0,A

D1MS: MOV R7,#80 ;1ms延时

 DJNZ R7,$

UART:CLR TI

 CLR RI

 MOV A,RECV_NUM

 CJNE A,#0,UU1

 MOV A,SBUF

 CJNE A,#MCU_ADDR,UU

 INC RECV_NUM

 SJMP UU

UU1:CJNE A,#1,UU

 MOV MCU_DATA,SBUF

 ACALL SW_OUT

 MOV RECV_NUM,#0 

 CLR TI

 MOV SBUF,#MCU_ADDR

 JNB TI,$

 CLR TI

 MOV SBUF,MCU_DATA

 JNB TI,$

 CLR TI

UU: RETI 

 ;实验板上的7段数码管0~9数字的共阴显示代码

numtab: DB 0FCH,60H,0DAH,0F2H,66H,0B6H,0BEH,0E0H,0FEH,0F6H 

 END

异步社区 异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。