单片机学习笔记————51单片机实现主机的串口收发
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单片机实现主机的串口收发相关推荐
- 单片机学习笔记————51单片机实现带数码管显示的象棋比赛专用计时器
一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...
- 单片机学习笔记————51单片机实现数码管中的倒计时程序
一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...
- 单片机学习笔记————51单片机实现用LED灯和按键来模拟工业自动化设备的运动控制
一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...
- 单片机学习笔记————51单片机实现带数码管显示的加法简易计算器
一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...
- 单片机学习笔记————51单片机实现在数码管中实现iphone4S开机密码锁的程序
一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...
- 单片机学习笔记————51单片机实现矩阵键盘的组合按键触发
一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...
- 单片机学习笔记————51单片机实现按住一个独立按键不松手的加速匀速触发
一.使用proteus绘制简单的电路图,用于后续仿真 二.编写程序 /***************************************************************** ...
- C51汇编语言寻址方式,单片机学习:51单片机寻址方式详解
原标题:单片机学习:51单片机寻址方式详解 51单片机是对所有兼容Intel 8031指令系统的单片机的统称.该系列单片机的始祖是Intel 8031单片机,后来随着Flash rom 技术的发展,8 ...
- 单片机位寻址举例_单片机学习:51单片机寻址方式详解
51单片机是对所有兼容Intel 8031指令系统的单片机的统称.该系列单片机的始祖是Intel 8031单片机,后来随着Flash rom 技术的发展,8031单片机取得了长足的发展,成为了应用最广 ...
最新文章
- php中perl配置,配置Apache及运行perl,php
- springboot 配置多线程
- for in在python中什么意思_python for in中的in
- 运维常说的 5个9、4个9、3个9 的可靠性,到底是什么???
- java影院购票系统开题报告,开题报告-网上电影院购票系统的设计与实现.doc
- 查看虚拟机cpu型号_虚拟机管理器(Virtual Machine Manager)简介 | Linux 中国
- 小米12 Ultra相机参数曝光:1.92亿像素主摄+1英寸超大底
- 高可用,完全分布式Hadoop集群HDFS和MapReduce安装配置指南
- node 更新_更新应用时,如何实现 K8s 零中断滚动更新?
- redhat linux系统下查看Emulex HBA卡基本命令
- android毕业论文结论,毕业论文经典结束语
- android svg 线条动画教程,SVG技术入门:线条动画实现原理
- AT绑定句柄无效和拒绝访问
- 如何有效率的学习马克思主义基本原理概论
- 技术文摘12 yun jia 技术 资料 截图工具 美容
- 习题:一圆型游泳池如图所示,现在需在其周围建一圆型过道,并在其四周围上栅栏。栅栏价格为35元/米,过道造价为20元/平方米。过道宽度为3米,游泳池半径由键盘输入。要求编程计算并输出过道和栅栏的造价。
- 《浪潮之巅》——吴军
- 计算机三级网络技术资料分享
- vm-tools 安装
- Kotlin-面向对象
热门文章
- 在Python中,输出格式:%d , %6d , %-6d, %06d , %.6f的一些区分
- 105.液体流加载特效
- 什么是OOM?如何解决OOM问题!
- java 使用File 创建文件和文件夹
- 2021年亚马逊测评是否是割韭菜项目?是一个不是一个靠谱的项目?到底赚钱吗?
- java时间计算的方法
- Netty教程(九)——解码器
- linux 查看crond服务器,Linux中crond服务与crontab用法
- PAT B1052 卖个萌 (20 分)
- pytorch 实现 SE Block