proteus虚拟串口的实现:https://mp.csdn.net/console/editor/html/107251649

一、使用proteus绘制简单的电路图,用于后续仿真

二、编写程序

/********************************************************************************************************************
----    @Project:   USART
----    @File:  main.c
----    @Edit:  ZHQ
----    @Version:   V1.0
----    @CreationTime:  20200720
----    @ModifiedTime:  20200720
----    @Description:   实现功能:
----    显示和独立按键部分根据数码管显示的程序来改编,用S1,S5,S9,S13作为独立按键。
----         一共有4个窗口。每个窗口显示一个参数。有两种更改参数的方式:
----    第一种:按键更改参数:
----        第8,7,6,5位数码管显示当前窗口,P-1代表第1个窗口,P-2代表第2个窗口,P-3代表第3个窗口,P-4代表第1个窗口。
----        第4,3,2,1位数码管显示当前窗口被设置的参数。范围是从0到9999。S1是加按键,按下此按键会依次增加当前窗口的参数。S5是减按键,按下此按键会依次减少当前窗口的参数。S9是切换窗口按键,按下此按键会依次循环切换不同的窗口。S13是启动发送数据和复位按键,当系统处于待机状态时,按下此按键会启动发送数据;当通讯超时蜂鸣器报警时,可以按下此键清除报警,返回到待机的状态。
----
----    第二:通过串口把更改的参数发送给从机。
----    波特率是:9600.
----    通讯协议:EB 00 55  GG 00 02 XX XX  CY
----    其中第1,2,3位EB 00 55就是数据头
----    其中第4位GG就是数据类型。01代表更改参数1,02代表更改参数2,03代表更改参数3,04代表更改参数4,
----    其中第5,6位00 02就是有效数据长度。高位在左,低位在右。
----    其中从第7,8位XX XX是被更改的参数。高位在左,低位在右。
----    第9位CY是累加和,前面所有字节的累加。
----    一个完整的通讯必须发送完4串数据,每串数据之间的间隔时间不能超过10秒钟,否则认为通讯超时主机会重发数据,如果连续三次都没有返回,则引发蜂鸣器报警。如果接收到得数据校验正确,主机继续发送新的一串数据,直到把4串数据发送完毕为止。
----
----       系统处于待机状态时,LED灯一直亮,
----       系统处于非待机状态时,LED灯闪烁,
----       系统处于出错状态时,LED灯闪烁,并且蜂鸣器间歇鸣叫报警。
----
----    通过电脑的串口助手来模拟从机,返回不同的应答
----    从机返回校验正确应答:eb 00 55 f5 00 00 35
----    从机返回校验出错应答:eb 00 55 fa 00 00 3a
----    单片机:AT89C52
********************************************************************************************************************/
#include "reg52.h"
/*——————宏定义——————*/
#define FOSC 11059200L
#define BAUD 9600
#define T1MS (65536-FOSC/12/500)   /*0.5ms timer calculation method in 12Tmode*/#define const_key_time1 9   /*按键去抖动延时的时间*/
#define const_key_time2 9   /*按键去抖动延时的时间*/
#define const_key_time3 9   /*按键去抖动延时的时间*/
#define const_key_time4 9   /*按键去抖动延时的时间*/
#define const_led_0_5s 32   /*大概0.5秒的时间*/
#define const_led_1s 64 /*大概1秒的时间*/
#define const_send_time_out 640 /*通讯超时出错的时间 大概10秒*/
#define const_rc_size 20    /*接收串口中断数据的缓冲区数组大小*/
#define const_receive_time 5    /*如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小*/
#define const_send_size 10  /*串口发送数据的缓冲区数组大小*/#define const_voice_short 20  /*蜂鸣器短叫的持续时间*//*——————变量函数定义及声明——————*/
/*蜂鸣器的驱动IO口*/
sbit BEEP = P2^7;
/*LED*/
sbit LED = P3^5;/*按键*/
sbit Key_S1 = P0^0; /*对应S1键,加键*/
sbit Key_S2 = P0^1; /*对应S5键,减键*/
sbit Key_S3 = P0^2; /*对应S9键,切换窗口*/
sbit Key_S4 = P0^3; /*对应S13键,复位*/
sbit Key_Gnd = P0^4;/*数码管*/
sbit Dig_Hc595_Sh = P2^0;
sbit Dig_Hc595_St = P2^1;
sbit Dig_Hc595_Ds = P2^2;unsigned char ucSendregBuf[const_send_size];   /*发送的缓冲区数组*/
unsigned int uiSendCnt = 0; /*用来识别串口是否接收完一串数据的计时器*/
unsigned char ucSendLock = 1;   /*串口服务程序的自锁变量,每次接收完一串数据只处理一次*/
unsigned int uiRcregTotal = 0;  /*代表当前缓冲区已经接收了多少个数据*/
unsigned char ucRcregBuf[const_rc_size];    /*接收串口中断数据的缓冲区数组*/
unsigned int uiRcMoveIndex = 0; /*用来解析数据协议的中间变量*/
unsigned char ucSendCntLock = 0;    /*串口计时器的原子锁*/
unsigned char ucRcType = 0; /*数据类型*/
unsigned int uiRcSize = 0; /*数据长度*/
unsigned char ucRcCy = 0;   /*校验累加和*/
unsigned char ucLedLock = 0;    /*原子锁*/
unsigned int uiLedCnt = 0;  /*控制Led闪烁的延时计时器*/
unsigned int uiSendTimeOutCnt = 0;  /*用来识别接收数据超时的计时器*/
unsigned char ucSendTimeOutLock = 0;    /*原子锁*/unsigned char ucStatus = 0; /*当前状态变量 0代表待机 1代表正在通讯过程 2代表发送出错*/
unsigned char ucSendStep = 0;   /*发送数据的过程步骤*/
unsigned char ucErrorCnt = 0;   /*累计错误总数*/
unsigned char ucSendTotal = 0;  /*记录当前已经发送了多少串数据*/
unsigned char ucReceiveStatus = 0;  /*返回的数据状态 0代表待机 1代表校验正确 2代表校验出错*/unsigned char ucKeySec = 0; /*被触发的按键编号*/
unsigned int uiKeyTimeCnt1 = 0; /*按键去抖动延时计数器*/
unsigned char ucKeyLock1 = 0;   /*按键触发后自锁的变量标志*/
unsigned int uiKeyTimeCnt2 = 0; /*按键去抖动延时计数器*/
unsigned char ucKeyLock2 = 0;   /*按键触发后自锁的变量标志*/
unsigned int uiKeyTimeCnt3 = 0; /*按键去抖动延时计数器*/
unsigned char ucKeyLock3 = 0;   /*按键触发后自锁的变量标志*/
unsigned int uiKeyTimeCnt4 = 0; /*按键去抖动延时计数器*/
unsigned char ucKeyLock4 = 0;   /*按键触发后自锁的变量标志*/unsigned int uiVoiceCnt = 0;    /*蜂鸣器鸣叫的持续时间计数器*/
unsigned char ucVoiceLock = 0;  /*蜂鸣器鸣叫的原子锁*/unsigned char ucDigShow8;   /*第8位数码管要显示的内容*/
unsigned char ucDigShow7;   /*第7位数码管要显示的内容*/
unsigned char ucDigShow6;   /*第6位数码管要显示的内容*/
unsigned char ucDigShow5;   /*第5位数码管要显示的内容*/
unsigned char ucDigShow4;   /*第4位数码管要显示的内容*/
unsigned char ucDigShow3;   /*第3位数码管要显示的内容*/
unsigned char ucDigShow2;   /*第2位数码管要显示的内容*/
unsigned char ucDigShow1;   /*第1位数码管要显示的内容*/unsigned char ucDigDot8;   /*数码管8的小数点是否显示的标志*/
unsigned char ucDigDot7;   /*数码管7的小数点是否显示的标志*/
unsigned char ucDigDot6;   /*数码管6的小数点是否显示的标志*/
unsigned char ucDigDot5;   /*数码管5的小数点是否显示的标志*/
unsigned char ucDigDot4;   /*数码管4的小数点是否显示的标志*/
unsigned char ucDigDot3;   /*数码管3的小数点是否显示的标志*/
unsigned char ucDigDot2;   /*数码管2的小数点是否显示的标志*/
unsigned char ucDigDot1;   /*数码管1的小数点是否显示的标志*/unsigned char ucDigShowTemp = 0;  /*临时中间变量*/
unsigned char ucDisplayDriveStep = 1; /*动态扫描数码管的步骤变量*/unsigned char ucWd1Update = 1;    /*窗口1更新显示标志*/
unsigned char ucWd2Update = 0;  /*窗口2更新显示标志*/
unsigned char ucWd3Update = 0;  /*窗口3更新显示标志*/
unsigned char ucWd4Update = 0;  /*窗口4更新显示标志*/
unsigned char ucWd = 1; /*本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。*/
unsigned int uiSetData1 = 0;    /*本程序中需要被设置的参数1*/
unsigned int uiSetData2 = 0;    /*本程序中需要被设置的参数2*/
unsigned int uiSetData3 = 0;    /*本程序中需要被设置的参数3*/
unsigned int uiSetData4 = 0;    /*本程序中需要被设置的参数4*/unsigned char ucTemp1 = 0; /*中间过渡变量*/
unsigned char ucTemp2 = 0;  /*中间过渡变量*/
unsigned char ucTemp3 = 0;  /*中间过渡变量*/
unsigned char ucTemp4 = 0;  /*中间过渡变量*/void Dig_Hc595_Drive(unsigned char, unsigned char);/*根据原理图得出的共阴数码管字模表*/
code unsigned char Dig_Table[] =
{0x3f,  /*0       序号0*/0x06,  /*1       序号1*/0x5b,  /*2       序号2*/0x4f,  /*3       序号3*/0x66,  /*4       序号4*/0x6d,  /*5       序号5*/0x7d,  /*6       序号6*/0x07,  /*7       序号7*/0x7f,  /*8       序号8*/0x6f,  /*9       序号9*/0x00,  /*不显示  序号10*/0x40,  /*-      序号11*/0x73,  /*P       序号12*/
};/**
* @brief  定时器0初始化函数
* @param  无
* @retval 初始化T0
**/
void Init_T0(void)
{TMOD = 0x01;                    /*set timer0 as mode1 (16-bit)*/TL0 = T1MS;                     /*initial timer0 low byte*/TH0 = T1MS >> 8;                /*initial timer0 high byte*/
}/**
* @brief  串口初始化函数
* @param  无
* @retval 初始化T0
**/
void Init_USART(void)
{SCON = 0x50;TMOD = 0x21;                    TH1=TL1=-(FOSC/12/32/BAUD);
}/**
* @brief  外围初始化函数
* @param  无
* @retval 初始化外围
* 让数码管显示的内容转移到以下几个变量接口上,方便以后编写更上一层的窗口程序。
* 只要更改以下对应变量的内容,就可以显示你想显示的数字。
**/
void Init_Peripheral(void)
{ucDigDot8 = 0;  ucDigDot7 = 0; ucDigDot6 = 0; ucDigDot5 = 0;   ucDigDot4 = 0;ucDigDot3 = 0;   ucDigDot2 = 0;  ucDigDot1 = 0; ET0 = 1;/*允许定时中断*/TR0 = 1;/*启动定时中断*/TR1 = 1;ES = 1;   /*允许串口中断*/EA = 1;/*开总中断*/
}/**
* @brief  初始化函数
* @param  无
* @retval 初始化单片机
**/
void Init(void)
{LED  = 0;BEEP = 1;Key_Gnd = 0;Dig_Hc595_Drive(0x00, 0x00); /*关闭所有经过另外两个74HC595驱动的LED灯*/Init_T0();Init_USART();
/*
* 为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
* 这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
*/  IP = 0x10;  /*把串口中断设置为最高优先级,必须的。*/
}
/**
* @brief  延时函数
* @param  无
* @retval 无
**/
void Delay_Long(unsigned int uiDelayLong)
{unsigned int i;unsigned int j;for(i=0;i<uiDelayLong;i++){for(j=0;j<500;j++)  /*内嵌循环的空指令数量*/{; /*一个分号相当于执行一条空语句*/}}
}
/**
* @brief  延时函数
* @param  无
* @retval 无
**/
void Delay_Short(unsigned int uiDelayShort)
{unsigned int i;for(i=0;i<uiDelayShort;i++){; /*一个分号相当于执行一条空语句*/}
}/**
* @brief  串口发送函数
* @param  ucSendData
* @retval 在发送一串数据中,每个字节之间必须添加一个延时,用来等待串口发送完成。
* 不增加延时,单单靠发送完成标志位来判断还是容易出错,在51,PIC单片机中都是这么做。
* 在stm32单片机中,可以不增加延时,直接靠单片机自带的标志位来判断就很可靠。
**/
void eusart_send(unsigned char ucSendData)
{ES = 0;    /*关串口中断*/   TI = 0; /*清零串口发送完成中断请求标志*/SBUF = ucSendData;    /*发送一个字节*/Delay_Short(400); /*每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整*/TI = 0;    /*清零串口发送完成中断请求标志*/  ES = 1; /*允许串口中断*/
}/**
* @brief  一发一收的通讯服务程序
* @param  无
* @retval 无
**/
void communication_service(void)
{unsigned int i;if(ucStatus == 1)   /*处于正在通讯的过程中*/{switch(ucSendStep){case 0:   /*通讯过程0  发送一串数据*/switch(ucSendTotal)    /*根据当前已经发送到第几条数据来决定发送哪些参数*/{case 0: /*发送参数1*/ucSendregBuf[0] = 0xeb;ucSendregBuf[1] = 0x00;ucSendregBuf[2] = 0x55;ucSendregBuf[3] = 0x01;ucSendregBuf[4] = 0x00;ucSendregBuf[5] = 0x02;ucSendregBuf[6] = uiSetData1 >> 8; /*把int类型的参数分解成两个字节的数据*/ucSendregBuf[7] = uiSetData1;break;case 1:   /*发送参数2*/ucSendregBuf[0] = 0xeb;ucSendregBuf[1] = 0x00;ucSendregBuf[2] = 0x55;ucSendregBuf[3] = 0x02;ucSendregBuf[4] = 0x00;ucSendregBuf[5] = 0x02;ucSendregBuf[6] = uiSetData2 >> 8; /*把int类型的参数分解成两个字节的数据*/ucSendregBuf[7] = uiSetData2;break;case 2:   /*发送参数3*/ucSendregBuf[0] = 0xeb;ucSendregBuf[1] = 0x00;ucSendregBuf[2] = 0x55;ucSendregBuf[3] = 0x03;ucSendregBuf[4] = 0x00;ucSendregBuf[5] = 0x02;ucSendregBuf[6] = uiSetData3 >> 8; /*把int类型的参数分解成两个字节的数据*/ucSendregBuf[7] = uiSetData3;break;case 3:   /*发送参数4*/ucSendregBuf[0] = 0xeb;ucSendregBuf[1] = 0x00;ucSendregBuf[2] = 0x55;ucSendregBuf[3] = 0x04;ucSendregBuf[4] = 0x00;ucSendregBuf[5] = 0x02;ucSendregBuf[6] = uiSetData4 >> 8; /*把int类型的参数分解成两个字节的数据*/ucSendregBuf[7] = uiSetData4;break;}ucSendregBuf[8] = 0x00;for(i = 0; i < 8; i ++)    /*最后一个字节是校验和,是前面所有字节累加,溢出部分不用我们管,系统会有规律的自动处理*/{ucSendregBuf[8] += ucSendregBuf[i];}for(i = 0; i < 9; i ++){eusart_send(ucSendregBuf[i]);    /*把一串完整的数据发送给下位机*/}ucSendCntLock = 1;   /*原子锁加锁*/uiSendTimeOutCnt = 0;  /*超时计时器计时清零*/ucSendCntLock = 0; /*原子锁解锁*/ucReceiveStatus = 0;   /*返回的数据状态清零*/ucSendStep = 1;    /*切换到下一个步骤,等待返回的数据*/break;case 1:    /*通讯过程1  判断返回的指令*/if(ucReceiveStatus == 1)  /*校验正确*/{ucErrorCnt = 0;    /*累计校验错误总数清零*/ucSendTotal ++;   /*累加当前发送了多少串数据*/if(ucSendTotal >= 4){ucStatus = 0;   /*切换到结束时的待机状态*/}else    /*还没发送完4串数据,则继续发送下一串新数据*/{ucSendStep = 0;    /*返回上一个步骤,继续发送新数据*/}}else if((ucReceiveStatus == 2) || uiSendTimeOutCnt > const_send_time_out)    /*校验出错或者超时出错*/{ucErrorCnt ++;   /*累计错误总数*/if(ucErrorCnt >= 3)    /*累加重发次数3次以上,则报错*/{ucStatus = 2; /*切换到出错报警状态*/}else  /*重发还没超过3次,继续返回重发*/{ucSendStep = 0;  /*返回上一个步骤,重发一次数据*/}}break;}}
}
/**
* @brief  状态显示的应用程序
* @param  无
* @retval 无
**/
void status_service(void)
{if(ucStatus != 0)   /*处于非待机的状态,Led闪烁*/{if(uiLedCnt < const_led_0_5s)   /*大概0.5秒*/{LED = 1;    /*前半秒亮*/if(ucStatus == 2)   /*处于发送数据出错的状态,则蜂鸣器间歇鸣叫报警*/{ucVoiceLock = 1;    /*原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt*/uiVoiceCnt = const_voice_short; /*按键声音触发,滴一声就停。*/ucVoiceLock = 0;    /*原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt*/}}else if(uiLedCnt < const_led_1s){LED = 0;}else{ucLedLock = 1;  /*原子锁加锁*/uiLedCnt = 0;   /*延时计时器清零,让Led灯处于闪烁的反复循环中*/ucLedLock = 0;   /*原子锁加锁*/}}else{LED = 1;    /*处于待机状态,Led一直亮*/}
}/**
* @brief  串口服务程序
* @param  无
* @retval 在main函数里
**/
void usart_service(void)
{unsigned int i;if(uiSendCnt >= const_receive_time && ucSendLock == 1)  /*说明超过了一定的时间内,再也没有新数据从串口来*/{ucSendLock = 0; /*处理一次就锁起来,不用每次都进来,除非有新接收的数据*//*下面的代码进入数据协议解析和数据处理的阶段*/uiRcMoveIndex = 0;  /*由于是判断数据头,所以下标移动变量从数组的0开始向 最尾端移动*/while(uiRcregTotal >= 5 && uiRcMoveIndex <= (uiRcregTotal - 5)){if(ucRcregBuf[uiRcMoveIndex + 0] == 0xeb && ucRcregBuf[uiRcMoveIndex + 1] == 0x00 && ucRcregBuf[uiRcMoveIndex + 2] == 0x55){ucRcType = ucRcregBuf[uiRcMoveIndex + 3];   /*数据类型  一个字节*/uiRcSize = ucRcregBuf[uiRcMoveIndex + 4];   /*数据长度  两个字节*/uiRcSize = uiRcSize << 8;uiRcSize += ucRcregBuf[uiRcMoveIndex + 5];ucRcCy = ucRcregBuf[uiRcMoveIndex + 6 + uiRcSize];   /*记录最后一个字节的校验*/ucRcregBuf[uiRcMoveIndex + 6 + uiRcSize] = 0;   /*清零最后一个字节的累加和变量*/for(i = 0; i < (3 + 1 + 2 + uiRcSize); i++) /*计算校验累加和*/{ucRcregBuf[uiRcMoveIndex + 6 + uiRcSize] +=  ucRcregBuf[uiRcMoveIndex + i];}if(ucRcCy == ucRcregBuf[uiRcMoveIndex + 6 + uiRcSize])  /*如果校验正确则进入以下数据处理*/{switch (ucRcType)   /*根据不同的数据类型来做不同的数据处理*/{case 0xf5:  /*返回的是正确的校验指令*/ucReceiveStatus = 1;    /*代表校验正确*/break;                    case 0xfa: ucReceiveStatus = 2; /*代表校验错误*/break;}}  break;  /*退出循环*/                }uiRcMoveIndex ++;  /*因为是判断数据头,游标向着数组最尾端的方向移动*/} uiRcregTotal = 0;   /*清空缓冲的下标,方便下次重新从0下标开始接受新数据*/    }
}/**
* @brief  显示数码管字模的驱动函数
* @param  无
* @retval   动态驱动数码管的原理
* 在八位数码管中,在任何一个瞬间,每次只显示其中一位数码管,另外的七个数码管
* 通过设置其公共位com为高电平来关闭显示,只要切换画面的速度足够快,人的视觉就分辨不出来,感觉八个数码管
* 是同时亮的。以下dig_hc595_drive(xx,yy)函数,其中第一个形参xx是驱动数码管段seg的引脚,第二个形参yy是驱动
* 数码管公共位com的引脚。
**/
void Display_Drive(void)
{switch(ucDisplayDriveStep){case 1: /*显示第1位*/ucDigShowTemp = Dig_Table[ucDigShow1];if(ucDigDot1 == 1){ucDigShowTemp = ucDigShowTemp | 0x80; /*显示小数点*/}Dig_Hc595_Drive(ucDigShowTemp, 0xfe);break;case 2:    /*显示第2位*/ucDigShowTemp = Dig_Table[ucDigShow2];if(ucDigDot2 == 1){ucDigShowTemp = ucDigShowTemp | 0x80; /*显示小数点*/}Dig_Hc595_Drive(ucDigShowTemp, 0xfd);break;case 3:    /*显示第3位*/ucDigShowTemp = Dig_Table[ucDigShow3];if(ucDigDot3 == 1){ucDigShowTemp = ucDigShowTemp | 0x80; /*显示小数点*/}Dig_Hc595_Drive(ucDigShowTemp, 0xfb);break;case 4:    /*显示第4位*/ucDigShowTemp = Dig_Table[ucDigShow4];if(ucDigDot4 == 1){ucDigShowTemp = ucDigShowTemp | 0x80; /*显示小数点*/}Dig_Hc595_Drive(ucDigShowTemp, 0xf7);break;case 5:    /*显示第5位*/ucDigShowTemp = Dig_Table[ucDigShow5];if(ucDigDot5 == 1){ucDigShowTemp = ucDigShowTemp | 0x80; /*显示小数点*/}Dig_Hc595_Drive(ucDigShowTemp, 0xef);break;case 6:    /*显示第6位*/ucDigShowTemp = Dig_Table[ucDigShow6];if(ucDigDot6 == 1){ucDigShowTemp = ucDigShowTemp | 0x80; /*显示小数点*/}Dig_Hc595_Drive(ucDigShowTemp, 0xdf);break;case 7:    /*显示第7位*/ucDigShowTemp = Dig_Table[ucDigShow7];if(ucDigDot7 == 1){ucDigShowTemp = ucDigShowTemp | 0x80; /*显示小数点*/}Dig_Hc595_Drive(ucDigShowTemp, 0xbf);break;case 8:    /*显示第8位*/ucDigShowTemp = Dig_Table[ucDigShow8];if(ucDigDot8 == 1){ucDigShowTemp = ucDigShowTemp | 0x80; /*显示小数点*/}Dig_Hc595_Drive(ucDigShowTemp, 0x7f);break;}ucDisplayDriveStep ++;    /*逐位显示*/if(ucDisplayDriveStep > 8)   /*扫描完8个数码管后,重新从第一个开始扫描*/{ucDisplayDriveStep = 1;}
}
/**
* @brief  数码管的595驱动函数
* @param  无
* @retval
* 如果直接是单片机的IO口引脚驱动的数码管,由于驱动的速度太快,此处应该适当增加一点delay延时或者
* 用计数延时的方式来延时,目的是在八位数码管中切换到每位数码管显示的时候,都能停留一会再切换到其它
* 位的数码管界面,这样可以增加显示的效果。但是,由于是间接经过74HC595驱动数码管的,
* 在单片机驱动74HC595的时候,dig_hc595_drive函数本身内部需要执行很多指令,已经相当于delay延时了,
* 因此这里不再需要加delay延时函数或者计数延时。
**/
void Dig_HC595_Drive(unsigned char ucDigStatusTemp16_09, unsigned char ucDigStatusTemp08_01)
{unsigned char i;unsigned char ucTempData;Dig_Hc595_Sh = 0;Dig_Hc595_St = 0;    ucTempData = ucDigStatusTemp16_09;  /*先送高8位*/for(i = 0; i < 8; i ++){if(ucTempData >= 0x80){Dig_Hc595_Ds = 1;}else{Dig_Hc595_Ds = 0;}/*注意,此处的延时delay_short必须尽可能小,否则动态扫描数码管的速度就不够。*/Dig_Hc595_Sh = 0;    /*SH引脚的上升沿把数据送入寄存器*/Delay_Short(1); Dig_Hc595_Sh = 1;Delay_Short(1);    ucTempData = ucTempData <<1;}ucTempData = ucDigStatusTemp08_01;   /*再先送低8位*/for(i = 0; i < 8; i ++){if(ucTempData >= 0x80){Dig_Hc595_Ds = 1;}else{Dig_Hc595_Ds = 0;}Dig_Hc595_Sh = 0;   /*SH引脚的上升沿把数据送入寄存器*/Delay_Short(1); Dig_Hc595_Sh = 1;Delay_Short(1);    ucTempData = ucTempData <<1;}Dig_Hc595_St = 0;    /*ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来*/Delay_Short(1);Dig_Hc595_St = 1;Delay_Short(1);Dig_Hc595_Sh = 0;   /*拉低,抗干扰就增强*/Dig_Hc595_St = 0;Dig_Hc595_Ds = 0;
}
/**
* @brief  显示的窗口菜单服务程序
* @param  无
* @retval
*凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,
*每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。
*局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,
*表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。
**/
void Display_Service(void)  /*显示的窗口菜单服务程序*/
{switch(ucWd){case 1:   /*显示P--1窗口的数据*/if(ucWd1Update == 1) /*窗口1要全部更新显示*/{ucWd1Update = 0; /*及时清零标志,避免一直进来扫描*/ucDigShow8 = 12;  /*第8位数码管显示P*/ucDigShow7 = 11;   /*第7位数码管显示-*/ucDigShow6 = 1;        /*第6位数码管显示1*/ucDigShow5 = 10;   /*第5位数码管不显示*/
/*
* 此处为什么要多加4个中间过渡变量ucTemp?是因为uiSetData1分解数据的时候
* 需要进行除法和求余数的运算,就会用到好多条指令,就会耗掉一点时间,类似延时
* 了一会。我们的定时器每隔一段时间都会产生中断,然后在中断里驱动数码管显示,
* 当uiSetData1还没完全分解出4位有效数据时,这个时候来的定时中断,就有可能导致
* 显示的数据瞬间产生不完整,影响显示效果。因此,为了把需要显示的数据过渡最快,
* 所以采取了先分解,再过渡显示的方法。
*//*先分解数据*/ucTemp4 = uiSetData1 / 1000;ucTemp3 = uiSetData1 % 1000 / 100;ucTemp2 = uiSetData1 % 100 / 10;ucTemp1 = uiSetData1 %10;/*再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好*//*就是在这里略作修改,把高位为0的去掉不显示。*/if(uiSetData1 < 1000){ucDigShow4 = 10;}else{ucDigShow4 = ucTemp4;   /*第4位数码管要显示的内容*/                        }if(uiSetData1 < 100){ucDigShow3 = 10;}else{ucDigShow3 = ucTemp3;    /*第3位数码管要显示的内容*/                        }if(uiSetData1 < 10){ucDigShow2 = 10;}else{ucDigShow2 = ucTemp2; /*第2位数码管要显示的内容*/                        }ucDigShow1 = ucTemp1;  /*第1位数码管要显示的内容*/                    }break;case 2:  /*显示P--2窗口的数据*/if(ucWd2Update == 1) /*窗口2要全部更新显示*/{ucWd2Update = 0; /*及时清零标志,避免一直进来扫描*/ucDigShow8 = 12;  /*第8位数码管显示P*/ucDigShow7 = 11;   /*第7位数码管显示-*/ucDigShow6 = 2;        /*第6位数码管显示2*/ucDigShow5 = 10;   /*第5位数码管不显示*//*先分解数据*/ucTemp4 = uiSetData2 / 1000;ucTemp3 = uiSetData2 % 1000 / 100;ucTemp2 = uiSetData2 % 100 / 10;ucTemp1 = uiSetData2 %10;if(uiSetData2 < 1000){ucDigShow4 = 10;}else{ucDigShow4 = ucTemp4;   /*第4位数码管要显示的内容*/                        }if(uiSetData2 < 100){ucDigShow3 = 10;}else{ucDigShow3 = ucTemp3;    /*第3位数码管要显示的内容*/                        }if(uiSetData2 < 10){ucDigShow2 = 10;}else{ucDigShow2 = ucTemp2; /*第2位数码管要显示的内容*/                        }ucDigShow1 = ucTemp1;  /*第1位数码管要显示的内容*/                                    }break; case 3: /*显示P--3窗口的数据*/if(ucWd3Update == 1) /*窗口3要全部更新显示*/{ucWd3Update = 0; /*及时清零标志,避免一直进来扫描*/ucDigShow8 = 12;  /*第8位数码管显示P*/ucDigShow7 = 11;   /*第7位数码管显示-*/ucDigShow6 = 3;        /*第6位数码管显示3*/ucDigShow5 = 10;   /*第5位数码管不显示*//*先分解数据*/ucTemp4 = uiSetData3 / 1000;ucTemp3 = uiSetData3 % 1000 / 100;ucTemp2 = uiSetData3 % 100 / 10;ucTemp1 = uiSetData3 %10;if(uiSetData3 < 1000){ucDigShow4 = 10;}else{ucDigShow4 = ucTemp4;   /*第4位数码管要显示的内容*/                        }if(uiSetData3 < 100){ucDigShow3 = 10;}else{ucDigShow3 = ucTemp3;    /*第3位数码管要显示的内容*/                        }if(uiSetData3 < 10){ucDigShow2 = 10;}else{ucDigShow2 = ucTemp2; /*第2位数码管要显示的内容*/                        }ucDigShow1 = ucTemp1;  /*第1位数码管要显示的内容*/                            }break;         case 4: /*显示P--4窗口的数据*/if(ucWd4Update == 1) /*窗口4要全部更新显示*/{ucWd4Update = 0; /*及时清零标志,避免一直进来扫描*/ucDigShow8 = 12;  /*第8位数码管显示P*/ucDigShow7 = 11;   /*第7位数码管显示-*/ucDigShow6 = 4;        /*第6位数码管显示4*/ucDigShow5 = 10;   /*第5位数码管不显示*//*先分解数据*/ucTemp4 = uiSetData4 / 1000;ucTemp3 = uiSetData4 % 1000 / 100;ucTemp2 = uiSetData4 % 100 / 10;ucTemp1 = uiSetData4 %10;if(uiSetData4 < 1000){ucDigShow4 = 10;}else{ucDigShow4 = ucTemp4;   /*第4位数码管要显示的内容*/                        }if(uiSetData4 < 100){ucDigShow3 = 10;}else{ucDigShow3 = ucTemp3;    /*第3位数码管要显示的内容*/                        }if(uiSetData4 < 10){ucDigShow2 = 10;}else{ucDigShow2 = ucTemp2; /*第2位数码管要显示的内容*/                        }ucDigShow1 = ucTemp1;  /*第1位数码管要显示的内容*/                            }break;                 }
}/**
* @brief  扫描按键
* @param  无
* @retval 放在定时中断里
**/
void key_scan(void)
{if(Key_S1 == 1)    /*IO是高电平,说明按键没有被按下,这时要及时清零一些标志位*/ {ucKeyLock1 = 0;    /*按键自锁标志清零*/uiKeyTimeCnt1 = 0;  /*按键去抖动延时计数器清零*/}else if(ucKeyLock1 == 0)   /*有按键按下,且是第一次被按下*/{uiKeyTimeCnt1 ++; /*累加定时中断次数*/if(uiKeyTimeCnt1 > const_key_time1){uiKeyTimeCnt1 = 0;ucKeyLock1 = 1;ucKeySec = 1;   /*触发1号键*/}}if(Key_S2 == 1)  /*IO是高电平,说明按键没有被按下,这时要及时清零一些标志位*/ {ucKeyLock2 = 0;    /*按键自锁标志清零*/uiKeyTimeCnt2 = 0;  /*按键去抖动延时计数器清零*/}else if(ucKeyLock2 == 0)   /*有按键按下,且是第一次被按下*/{uiKeyTimeCnt2 ++; /*累加定时中断次数*/if(uiKeyTimeCnt2 > const_key_time2){uiKeyTimeCnt2 = 0;ucKeyLock2 = 1;ucKeySec = 2;   /*触发2号键*/}}if(Key_S3 == 1)  /*IO是高电平,说明按键没有被按下,这时要及时清零一些标志位*/ {ucKeyLock3 = 0;    /*按键自锁标志清零*/uiKeyTimeCnt3 = 0;  /*按键去抖动延时计数器清零*/}else if(ucKeyLock3 == 0)   /*有按键按下,且是第一次被按下*/{uiKeyTimeCnt3 ++; /*累加定时中断次数*/if(uiKeyTimeCnt3 > const_key_time3){uiKeyTimeCnt3 = 0;ucKeyLock3 = 1;ucKeySec = 3;   /*触发3号键*/}}if(Key_S4 == 1)  /*IO是高电平,说明按键没有被按下,这时要及时清零一些标志位*/ {ucKeyLock4 = 0;    /*按键自锁标志清零*/uiKeyTimeCnt4 = 0;  /*按键去抖动延时计数器清零*/}else if(ucKeyLock4 == 0)   /*有按键按下,且是第一次被按下*/{uiKeyTimeCnt4 ++; /*累加定时中断次数*/if(uiKeyTimeCnt4 > const_key_time4){uiKeyTimeCnt4 = 0;ucKeyLock4 = 1;ucKeySec = 4;   /*触发4号键*/}}
}/**
* @brief  按键服务的应用程序
* @param  无
* @retval 无
**/
void key_service(void)
{switch(ucKeySec)   /*按键服务状态切换*/{case 1:    /*加按键*/switch(ucWd) /*在不同的窗口下,设置不同的参数*/{case 1:uiSetData1 ++;if(uiSetData1 > 9999){uiSetData1 = 9999;}ucWd1Update = 1;    /*窗口1更新显示*/break;case 2:uiSetData2 ++;if(uiSetData2 > 9999){uiSetData2 = 9999;}ucWd2Update = 1;  /*窗口1更新显示*/break;case 3:uiSetData3 ++;if(uiSetData3 > 9999){uiSetData3 = 9999;}ucWd3Update = 1;  /*窗口1更新显示*/break;case 4:uiSetData4 ++;if(uiSetData4 > 9999){uiSetData4 = 9999;}ucWd4Update = 1;  /*窗口1更新显示*/break;               }ucVoiceLock = 1;uiVoiceCnt = const_voice_short;ucVoiceLock = 0;ucKeySec = 0;break;case 2:  /*减按键*/switch(ucWd) /*在不同的窗口下,设置不同的参数*/{case 1:uiSetData1 --;if(uiSetData1 > 9999){uiSetData1 = 0;}ucWd1Update = 1;   /*窗口1更新显示*/break;case 2:uiSetData2 --;if(uiSetData2 > 9999){uiSetData2 = 0;}ucWd2Update = 1; /*窗口1更新显示*/break;case 3:uiSetData3 --;if(uiSetData3 > 9999){uiSetData3 = 0;}ucWd3Update = 1; /*窗口1更新显示*/break;case 4:uiSetData4 --;if(uiSetData4 > 9999){uiSetData4 = 0;}ucWd4Update = 1; /*窗口1更新显示*/break;               }ucVoiceLock = 1;uiVoiceCnt = const_voice_short;ucVoiceLock = 0;ucKeySec = 0;       break;case 3:   /*切换窗口按键*/ucWd ++;  /*切换窗口*/if(ucWd > 4){ucWd = 1;}switch(ucWd){case 1:ucWd1Update = 1;break;case 2:ucWd2Update = 1;break;case 3:ucWd3Update = 1;break;case 4:ucWd4Update = 1;break;}ucVoiceLock = 1;uiVoiceCnt = const_voice_short;ucVoiceLock = 0; ucKeySec = 0;   break;  case 4: /*启动发送数据和复位按键*/switch(ucStatus) /*在不同的状态下,进行不同的操作*/{case 0 : /*待机*/ucErrorCnt = 0;   /*累计错误总数清零*/ucSendTotal = 0;    /*已经发送串数据总数清零*/ucSendStep = 0;  /*发送数据的过程步骤清零,返回开始的步骤待命*/ucStatus = 1;  /*启动发送数据,1代表正在通讯过程*/break;case 1:    /*处于正在通讯的过程*/break;case 2:  /*发送数据出错,比如中间超时没有接收到数据*/ucStatus = 0;    /*切换回待机的状态*/break;}ucVoiceLock = 1;uiVoiceCnt = const_voice_short;ucVoiceLock = 0;  ucKeySec = 0;break; }
}/**
* @brief  定时器0中断函数
* @param  无
* @retval 无
**/
void ISR_T0(void)   interrupt 1
{TF0 = 0;  /*清除中断标志*/TR0 = 0; /*关中断*//* * 此处多增加一个原子锁,作为中断与主函数共享数据的保护*/if(ucSendCntLock == 0){ucSendCntLock = 1;if(uiSendCnt < const_receive_time){uiSendCnt ++;ucSendLock = 1;}ucSendCntLock = 0;}if(ucVoiceLock == 0)    /*原子锁判断*/{if(uiVoiceCnt != 0){uiVoiceCnt --;BEEP = 0;}else{;BEEP = 1;}      }if(ucStatus != 0)  /*处于非待机的状态,Led闪烁*/{if(ucLedLock == 0){uiLedCnt ++;  }       }if(ucStatus == 1)  /*处于正在通讯的状态*/{if(ucSendTimeOutLock == 0){uiSendTimeOutCnt ++;}} key_scan();Display_Drive(); /*数码管字模的驱动函数*/TL0 = T1MS;                     /*initial timer0 low byte*/TH0 = T1MS >> 8;                /*initial timer0 high byte*/TR0 = 1; /*开中断*/
}/**
* @brief  串口接收数据中断
* @param  无
* @retval 无
**/
void usart_receive(void)    interrupt 4
{if(RI == 1){RI = 0;++ uiRcregTotal;if(uiRcregTotal > const_rc_size){uiRcregTotal =  const_rc_size;}ucRcregBuf[uiRcregTotal - 1] = SBUF;if(ucSendCntLock == 0){ucSendCntLock = 1;uiSendCnt = 0;  /*及时喂狗,虽然在定时中断那边此变量会不断累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个串口接收中断它都被清零。*/ucSendCntLock = 0;}}else{TI = 0;}
}/*————————————主函数————————————*/
/**
* @brief  主函数
* @param  无
* @retval 实现LED灯闪烁
**/
void main()
{/*单片机初始化*/Init();/*延时,延时时间一般是0.3秒到2秒之间,等待外围芯片和模块上电稳定*/Delay_Long(100);/*单片机外围初始化*/   Init_Peripheral();while(1){key_service();   /*按键服务的应用程序*/usart_service();    /*串口服务程序*/ communication_service();    /*一发一收的通讯服务程序*/       Display_Service();    /*显示的窗口菜单服务程序*/        status_service();    /*状态显示的应用程序*/   }
}

三、仿真实现

51单片机实现主机的串口收发

单片机学习笔记————51单片机实现主机的串口收发相关推荐

  1. 单片机学习笔记————51单片机实现带数码管显示的象棋比赛专用计时器

    一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...

  2. 单片机学习笔记————51单片机实现数码管中的倒计时程序

    一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...

  3. 单片机学习笔记————51单片机实现用LED灯和按键来模拟工业自动化设备的运动控制

    一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...

  4. 单片机学习笔记————51单片机实现带数码管显示的加法简易计算器

    一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...

  5. 单片机学习笔记————51单片机实现在数码管中实现iphone4S开机密码锁的程序

    一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...

  6. 单片机学习笔记————51单片机实现矩阵键盘的组合按键触发

    一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...

  7. 单片机学习笔记————51单片机实现按住一个独立按键不松手的加速匀速触发

    一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...

  8. C51汇编语言寻址方式,单片机学习:51单片机寻址方式详解

    原标题:单片机学习:51单片机寻址方式详解 51单片机是对所有兼容Intel 8031指令系统的单片机的统称.该系列单片机的始祖是Intel 8031单片机,后来随着Flash rom 技术的发展,8 ...

  9. 单片机位寻址举例_单片机学习:51单片机寻址方式详解

    51单片机是对所有兼容Intel 8031指令系统的单片机的统称.该系列单片机的始祖是Intel 8031单片机,后来随着Flash rom 技术的发展,8031单片机取得了长足的发展,成为了应用最广 ...

最新文章

  1. php中perl配置,配置Apache及运行perl,php
  2. springboot 配置多线程
  3. for in在python中什么意思_python for in中的in
  4. 运维常说的 5个9、4个9、3个9 的可靠性,到底是什么???
  5. java影院购票系统开题报告,开题报告-网上电影院购票系统的设计与实现.doc
  6. 查看虚拟机cpu型号_虚拟机管理器(Virtual Machine Manager)简介 | Linux 中国
  7. 小米12 Ultra相机参数曝光:1.92亿像素主摄+1英寸超大底
  8. 高可用,完全分布式Hadoop集群HDFS和MapReduce安装配置指南
  9. node 更新_更新应用时,如何实现 K8s 零中断滚动更新?
  10. redhat linux系统下查看Emulex HBA卡基本命令
  11. android毕业论文结论,毕业论文经典结束语
  12. android svg 线条动画教程,SVG技术入门:线条动画实现原理
  13. AT绑定句柄无效和拒绝访问
  14. 如何有效率的学习马克思主义基本原理概论
  15. 技术文摘12 yun jia 技术 资料 截图工具 美容
  16. 习题:一圆型游泳池如图所示,现在需在其周围建一圆型过道,并在其四周围上栅栏。栅栏价格为35元/米,过道造价为20元/平方米。过道宽度为3米,游泳池半径由键盘输入。要求编程计算并输出过道和栅栏的造价。
  17. 《浪潮之巅》——吴军
  18. 计算机三级网络技术资料分享
  19. vm-tools 安装
  20. Kotlin-面向对象

热门文章

  1. 在Python中,输出格式:%d , %6d , %-6d, %06d , %.6f的一些区分
  2. 105.液体流加载特效
  3. 什么是OOM?如何解决OOM问题!
  4. java 使用File 创建文件和文件夹
  5. 2021年亚马逊测评是否是割韭菜项目?是一个不是一个靠谱的项目?到底赚钱吗?
  6. java时间计算的方法
  7. Netty教程(九)——解码器
  8. linux 查看crond服务器,Linux中crond服务与crontab用法
  9. PAT B1052 卖个萌 (20 分)
  10. pytorch 实现 SE Block