历史上的今天
今天是:2024年11月08日(星期五)
2021年11月08日 | 51单片机实现通过串口用计数延时方式发送一串数据
2021-11-08 来源:eefocus
一、使用proteus绘制简单的电路图,用于后续仿真

二、编写程序
/********************************************************************************************************************
---- @Project: USART
---- @File: main.c
---- @Edit: ZHQ
---- @Version: V1.0
---- @CreationTime: 20200714
---- @ModifiedTime: 20200714
---- @Description:
---- 波特率是:9600 。
---- 按一次按键S1,发送EB 00 55 01 00 00 00 00 41
---- 按一次按键S5,发送EB 00 55 02 00 00 00 00 42
---- 按一次按键S9,发送EB 00 55 03 00 00 00 00 43
---- 按一次按键S13,发送EB 00 55 04 00 00 00 00 44
---- 单片机: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_send_time 100 /*累计主循环次数的计数延时 根据项目实际情况来调整此数据大小*/
#define const_send_size 20 /*串口发送数据的缓冲区数组大小*/
#define const_Message_size 10 /*环形消息队列的缓冲区数组大小*/
#define const_key_time1 20 /*按键去抖动延时的时间*/
#define const_key_time2 20 /*按键去抖动延时的时间*/
#define const_key_time3 20 /*按键去抖动延时的时间*/
#define const_key_time4 20 /*按键去抖动延时的时间*/
#define const_voice_short 40 /*蜂鸣器短叫的持续时间*/
/*——————变量函数定义及声明——————*/
/*蜂鸣器的驱动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;
unsigned char ucSendregBuf[const_send_size]; /*接收串口中断数据的缓冲区数组*/
unsigned char ucMessageBuf[const_Message_size]; /*环形消息队列的缓冲区数据*/
unsigned int uiMessageCurrent = 0; /*环形消息队列的取数据当前位置*/
unsigned int uiMessageInsert = 0; /*环形消息队列的插入新消息时候的位置*/
unsigned int uiMessageCnt = 0; /*统计环形消息队列的消息数量 等于0时表示消息队列里没有消息*/
unsigned char ucMessage = 0; /*当前获取到的消息*/
/*为串口计时器多增加一个原子锁,作为中断与主函数共享数据的保护*/
unsigned char ucVoiceLock = 0; /*蜂鸣器鸣叫的原子锁*/
unsigned int uiVoiceCnt = 0; /*蜂鸣器鸣叫的持续时间计数器*/
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 char ucSendStep = 0; /*发送一串数据的运行步骤*/
unsigned int uiSendTimeCnt = 0; /*累计主循环次数的计数延时器*/
unsigned int uiSendCnt = 0; /*发送数据时的中间变量*/
/**
* @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)
{
ET0 = 1;/*允许定时中断*/
TR0 = 1;/*启动定时中断*/
TR1 = 1;
ES = 1; /*允许串口中断*/
EA = 1;/*开总中断*/
}
/**
* @brief 初始化函数
* @param 无
* @retval 初始化单片机
**/
void Init(void)
{
LED = 0;
BEEP = 1;
Key_Gnd = 0;
Init_T0();
Init_USART();
}
/**
* @brief 延时函数
* @param 无
* @retval 无
**/
void Delay_Long(unsigned int uiDelayLong)
{
unsigned int i;
unsigned int j;
for(i=0;i for(j=0;j<500;j++) /*内嵌循环的空指令数量*/ { ; /*一个分号相当于执行一条空语句*/ } } } // /** // * @brief 延时函数 // * @param 无 // * @retval 无 // **/ // void Delay_Short(unsigned int uiDelayShort) // { // unsigned int i; // for(i=0;i // ; /*一个分号相当于执行一条空语句*/ // } // } /** * @brief 插入信息函数 * @param 无 * @retval 插入新的消息到环形消息队列里 * 通过判断数组下标是否超范围的条件,把一个数组的首尾连接起来,就像一个环形, * 因此命名为环形消息队列。环形消息队列有插入消息,获取消息两个核心函数,以及一个 * 统计消息总数的uiMessageCnt核心变量,通过此变量,我们可以知道消息队列里面是否有消息需要处理. * 做项目中很少用消息队列的,大部分的单片机项目其实直接用一两个中间变量就可以起到传递消息的作用,就能满足系统的要求。 * 以下是各变量的含义: * #define const_Message_size 10 //环形消息队列的缓冲区数组大小 * unsigned char ucMessageBuf[const_Message_size]; //环形消息队列的缓冲区数据 * unsigned int uiMessageCurrent=0; //环形消息队列的取数据当前位置 * unsigned int uiMessageInsert=0; //环形消息队列的插入新消息时候的位置 * unsigned int uiMessageCnt=0; //统计环形消息队列的消息数量 等于0时表示消息队列里没有消息 **/ void insert_message(unsigned char ucMessageTemp) /*插入新的消息到环形消息队列里*/ { if(uiMessageCnt < const_Message_size) /*消息总数小于环形消息队列的缓冲区才允许插入新消息*/ { ucMessageBuf[uiMessageInsert] = ucMessageTemp; uiMessageInsert ++; /*插入新消息时候的位置*/ if(uiMessageInsert >= const_Message_size) /*到了缓冲区末尾,则从缓冲区的开头重新开始。数组的首尾连接,看起来就像环形*/ { uiMessageInsert = 0; } uiMessageCnt ++; /*消息数量累加 等于0时表示消息队列里没有消息*/ } } /** * @brief 提取信息函数 * @param 无 * @retval 从环形消息队列里提取消息 **/ unsigned char get_message(void) /*从环形消息队列里提取消息*/ { unsigned char ucMessageTemp = 0; /*返回的消息中间变量,默认为0*/ if(uiMessageCnt > 0) /*只有消息数量大于0时才可以提取消息*/ { ucMessageTemp = ucMessageBuf[uiMessageCurrent]; uiMessageCurrent ++; /*环形消息队列的取数据当前位置*/ if(uiMessageCurrent >= const_Message_size) /*到了缓冲区末尾,则从缓冲区的开头重新开始。数组的首尾连接,看起来就像环形*/ { uiMessageCurrent = 0; } uiMessageCnt --; /*每提取一次,消息数量就减一 等于0时表示消息队列里没有消息*/ } return ucMessageTemp; } /** * @brief 串口发送函数 * @param ucSendData * @retval 在发送一串数据中,每个字节之间必须添加一个延时,用来等待串口发送完成。 * 不增加延时,单单靠发送完成标志位来判断还是容易出错,在51,PIC单片机中都是这么做。 * 在stm32单片机中,可以不增加延时,直接靠单片机自带的标志位来判断就很可靠。 **/ void eusart_send(unsigned char ucSendData) { ES = 0; /*关串口中断*/ TI = 0; /*清零串口发送完成中断请求标志*/ SBUF = ucSendData; /*发送一个字节*/ // Delay_Short(400); /*因为外部在每个发送字节之间用了累计主循环次数的计数延时,因此不要此行的delay延时*/ TI = 0; /*清零串口发送完成中断请求标志*/ ES = 1; /*允许串口中断*/ } /** * @brief 发送服务函数 * @param 无 * @retval 利用累计主循环次数的计数延时方式来发送一串数据 **/ void send_service(void) /*利用累计主循环次数的计数延时方式来发送一串数据*/ { switch(ucSendStep) /*发送一串数据的运行步骤*/ { case 0: /*从环形消息队列里提取消息*/ if(uiMessageCnt > 0) /*说明有消息需要处理*/ { ucMessage = get_message(); switch(ucMessage) /*消息处理*/ { case 1: ucSendregBuf[0] = 0xeb; /*把准备发送的数据放入发送缓冲区*/ ucSendregBuf[1] = 0x00; ucSendregBuf[2] = 0x55; ucSendregBuf[3] = 0x01; /*01代表1号键*/ ucSendregBuf[4] = 0x00; ucSendregBuf[5] = 0x00; ucSendregBuf[6] = 0x00; ucSendregBuf[7] = 0x00; ucSendregBuf[8] = 0x41; uiSendCnt = 0; /*发送数据的中间变量清零*/ uiSendTimeCnt = 0; /*累计主循环次数的计数延时器清零*/ ucSendStep =1; /*切换到下一步发送一串数据*/ break; case 2: ucSendregBuf[0] = 0xeb; /*把准备发送的数据放入发送缓冲区*/ ucSendregBuf[1] = 0x00; ucSendregBuf[2] = 0x55; ucSendregBuf[3] = 0x02; /*02代表2号键*/ ucSendregBuf[4] = 0x00; ucSendregBuf[5] = 0x00; ucSendregBuf[6] = 0x00; ucSendregBuf[7] = 0x00; ucSendregBuf[8] = 0x42; uiSendCnt = 0; /*发送数据的中间变量清零*/ uiSendTimeCnt = 0; /*累计主循环次数的计数延时器清零*/ ucSendStep =1; /*切换到下一步发送一串数据*/ break; case 3: ucSendregBuf[0] = 0xeb; /*把准备发送的数据放入发送缓冲区*/ ucSendregBuf[1] = 0x00; ucSendregBuf[2] = 0x55; ucSendregBuf[3] = 0x03; /*03代表3号键*/ ucSendregBuf[4] = 0x00; ucSendregBuf[5] = 0x00; ucSendregBuf[6] = 0x00; ucSendregBuf[7] = 0x00; ucSendregBuf[8] = 0x43; uiSendCnt = 0; /*发送数据的中间变量清零*/ uiSendTimeCnt = 0; /*累计主循环次数的计数延时器清零*/ ucSendStep =1; /*切换到下一步发送一串数据*/ break; case 4: ucSendregBuf[0] = 0xeb; /*把准备发送的数据放入发送缓冲区*/ ucSendregBuf[1] = 0x00; ucSendregBuf[2] = 0x55; ucSendregBuf[3] = 0x04; /*04代表4号键*/ ucSendregBuf[4] = 0x00; ucSendregBuf[5] = 0x00; ucSendregBuf[6] = 0x00; ucSendregBuf[7] = 0x00; ucSendregBuf[8] = 0x44; uiSendCnt = 0; /*发送数据的中间变量清零*/ uiSendTimeCnt = 0; /*累计主循环次数的计数延时器清零*/ ucSendStep =1; /*切换到下一步发送一串数据*/ break; default: /*如果没有符合要求的消息,则不处理*/ ucSendStep = 0; /*维持现状,不切换*/ break; } } break; case 1: /*利用累加主循环次数的计数延时方式来发送一串数据*/ /* * 这里的计数延时为什么不用累计定时中断次数的延时,而用累计主循环次数的计数延时? * 因为本程序定时器中断一次需要500个指令时间,时间分辨率太低,不方便微调时间。因此 * 就用累计主循环次数的计数延时方式,在做项目的时候,应该根据系统的实际情况 * 来调整const_send_time的大小。 */ uiSendTimeCnt ++; /*累计主循环次数的计数延时,为每个字节之间增加延时*/ if(uiSendTimeCnt > const_send_time) /*请根据实际系统的情况,调整const_send_time的大小*/ { uiSendTimeCnt = 0; eusart_send(ucSendregBuf[uiSendCnt]); /*发送一串数据给上位机*/ uiSendCnt ++; if(uiSendCnt >= 9) /*说明数据已经发送完毕*/ { uiSendCnt = 0; ucSendStep = 0; /*返回到上一步,处理其它未处理的消息*/ } } 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; } } 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; } } 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; } } if(Key_S4 == 1) /*IO是高电平,说明按键没有被按下,这时要及时清零一些标志位*/ { ucKeyLock4 = 0; /*按键自锁标志清零*/ uiKeyTimeCnt4 = 0; /*按键去抖动延时计数器清零*/ } else if(ucKeyLock4 == 0) /*有按键按下,且是第一次被按下*/
史海拾趣
|
关于__irq 的使用 __irq为一个标识,用来表示一个函数是否为中断函数。对于不同的编译器,__irq在函数名中的位置不一样,例如: ADS编译器中 : void __irq IRQ_Eint0(void); Keil编译器中 : void IRQ_Eint0(void) __irq; 但是其意义一 ...… 查看全部问答> |
|
我买了块AT89S52学习板 有2个接口 一根是USB借口 这个接了之后 电源按钮可以控制单片机 但是还有一个是ISP并行口转USB的接口 接了之后就不能控制电源了 就一直供电 很郁闷 而且还有1个问题就是用KEIL写了段最简单的程序 为什么我没有用循环语句 也 ...… 查看全部问答> |
|
请问NAND FLAHS/NOR FLASH,PSRAM方面的文档 请问NAND FLAHS/NOR FLASH,PSRAM方面的文档或者书籍,哪位接触过?工作中遇到一些存储方案的问题,看到NOR FLASH + PSRAM、NAND FLASH + LPSDRAM等方案,只了解少许inteface/features上的差别,我希望能看到些在memory 架构、电路、工艺级的信息 ...… 查看全部问答> |
|
我做的一个基于DSP的系统中,DSP做主处理器,控制着整个系统,包括信号处理,整体调度等;选择了一块Xilinx的FPGA做FIFO UART和系统的逻辑控制和译码。DSP的时钟输入为15MHz,经过内部的PLL倍频为较高频率,FPGA需要25M或一下的时钟输 ...… 查看全部问答> |
|
LM3S811 + VPC3+S 震撼Profibus DP方案 利用M3的高速SPI或则I2C来控制VPC3+S的Profibus-DP方案,非常受到广大客户青睐。仅占用MCU的4到8个管脚就可以做成一个Profibus DP网关。而且本身LM3S811体积非常小,VPC3+S更是BGA48的超小体积,所以非常棒。 有需要做得可以找我,提供免 ...… 查看全部问答> |
|
我现在想用单片机的I/O口来控制30路电源的开断(电源都是12V,1A),因为有很多路要控制,所以不想用继电器,太占板子的空间了。请问下大家有没有什么好的电路或者是方法可以推荐一下。每一路电源都是相互独立的,考虑到I/O的数量,如果能节省点I/O ...… 查看全部问答> |
|
有8个LED灯,限流是370mA。我是把电流一下降到只有1个LED都不会过流的电压好,还是慢慢的把电压降下来好????????????… 查看全部问答> |




