目录
近期学习了单片机新的外设,为了方便通过串口调试外设,根据作者(本人)的思路,顺带写了一份串口的程序。
在单片机的开发中,串口犹如单片机的一扇窗,可通过它了解或控制单片机的运行情况,找出单片机程序中出现问题的原因。正所谓工欲善其事,必先利其器,因此,有一个在开发过程中得心应手的串口,能够提高开发效率。
串口超时
简介
在使用串口接收不定长数据时,作者常常思考如何判断串口是否接收完数据了,它不像定长数据那样,计数达到即接收完成。经过大佬们的指点,知道了有所谓“串口超时”的东西。顾名思义,就是当串口开始接收到数据后,如果有一段时间没有接收到数据,就表示串口已经接收完全部数据,单片机可以对数据进行处理了。
将上面的思路用到单片机中,即在串口程序中额外添加一个定时器,当串口开始接收到数据时,清零计数值并开始计时,此后每次在计时的阈值内,接收到数据,则清零计数值,重新开始计时。直到某一次接收到数据后,在计时的阈值内没有接收到数据,串口数据接收完成,停止计时。如图:
代码详解
以STC8H8K64U单片机为例,先对串口和定时器进行初始化:
#define FOSC 11059200UL //IRC频率
#define BAUDRATE 9600UL //串口波特率
//串口1,使用定时器2作为波特率发生器(详情查看官方数据手册)
void TIMER2_UartInit()
{
AUXR |= (0x01 << 0);
AUXR |= (0x01 << 2); //1T
AUXR &= ~(0x01 << 3);
TM2PS = 0;
T2H = (65536 - FOSC/BAUDRATE/(TM2PS + 1)/4) >> 8;
T2L = (65536 - FOSC/BAUDRATE/(TM2PS + 1)/4) & 0xff;
AUXR |= (0x01 << 4);
}
//串口初始化函数(详情查看官方数据手册)
void UART1_Init()
{
P3M1 = ~(0x01 << 0);
P3M0 = ~(0x01 << 0); //准双向
P3M1 = ~(0x01 << 1);
P3M0 = ~(0x01 << 1); //准双向
SCON = 0x50; //模式1(可变波特率8位模式)
PCON &= ~(0x01 << 7); //波特率不加倍
PCON &= ~(0x01 << 6); //无帧错误检测
AUXR |= (0x01 << 0); //定时器2作为波特率发生器
ES = 1; //开启串口中断
EA = 1; //开启总中断
}
串口中断函数:
unsigned char xdata uart1_dataLength = 0;
unsigned char xdata uart1_buffer[256] = 0;
bit uart1_timeBegin = 0; //计时位(0:停止计时;1:开始计时)
unsigned int xdata uart1_freeTime_us = 0; //串口空闲时间
void UART1_Interrupt() interrupt 4
{
if (RI == 1) //接收到数据
{
RI = 0;
uart1_freeTime_us = 0; //计时清零
uart1_timeBegin = 1; //开始计时
uart1_buffer[uart1_dataLength] = SBUF;
uart1_dataLength++;
uart1_buffer[uart1_dataLength] = 0; //下一个字节清零后,缓存可当作字符串使用
}
}
其中变量uart1_freeTime_us在定时器3中断中累加:
#define TIMER3_US 10 //定时器3中断时间
void TIMER3_Init()
{
TM3PS = 0;
T4T3M &= 0xf0;
T4T3M |= 0x0a;
if(T4T3M & 0x02)
{
T3H = (65536 - FOSC/1000000UL/(TM3PS + 1) *TIMER3_US) >> 8;
T3L = (65536 - FOSC/1000000UL/(TM3PS + 1) *TIMER3_US) & 0xff;
}
else
{
T3H = (65536 - FOSC/1000000UL/(TM3PS + 1) *TIMER3_US/12) >> 8;
T3L = (65536 - FOSC/1000000UL/(TM3PS + 1) *TIMER3_US/12) & 0xff;
}
IE2 |= (0x01 << 5);
EA = 1;
}
//每TIMER3_US时间后进入中断
void TIMER3_Interrupt() interrupt 19
{
uart1_freeTime_us += 10;
}
接下来是整个程序的关键函数:
#define UART1_FREE_US 1000000UL/BAUDRATE *10 *3 //串口空闲时间超过UART1_FREE_US后判断为数据接收完成,为3帧数据的时间,一帧10位
bit uart1_readDone = 0; //串口数据接收完成标志(0:未完成数据接收;1:已完成数据接收)
//检测是否接收完串口的数据
void UART1_Loop()
{
if(uart1_timeBegin == 1) //计时开始
{
if(uart1_freeTime_us > UART1_FREE_US) //串口空闲时间达到指定数值,数据接收完成
{
uart1_readDone = 1; //数据接收完成
uart1_timeBegin = 0; //停止计时
}
}
else //未开始计时
{
uart1_freeTime_us = 0;
}
}
//处理完从串口接收到的数据后,必需调用该函数
void UART1_Clear()
{
uart1_dataLength = 0; //接收数据长度清零
uart1_readDone = 0; //清除数据接收完成标志
}
UART1_FREE_US表示的意思是该串口接收数据完成前空闲的时间,这里我以串口接收/发送3帧数据所需要的时间为准,每10bit为一帧数据。
通过调用函数UART1_Loop,可判断串口是否接收完成数据。当接收完成后,将uart1_readDone置1,此时可以处理缓存中的数据,然后调用函数UART1_Clear清除相关数据。即:
void main()
{
P_SW2 |= 0x80;
TIMER2_UartInit();
UART1_Init();
TIMER3_Init(); //定时器3用以计算串口空闲时间
while(1)
{
UART1_Loop();
if(uart1_readDone == 1) //数据接收完成
{//处理数据
UART1_Clear(); //数据处理完之后调用
}
}
}
特别注意,因单片机中能开辟的缓存有限,如果接收完数据后不处理,或者接收的数据长度超出缓存范围,那么前面接收到的数据将被覆盖,所以缓存的大小应视实际情况而定。
判断特定命令控制单片机
在单片机开发中,作者经常通过串口发送特定的命令控制单片机,在这过程中单片机应该如何识别这些特定指令呢?下面举个例子。
STC单片机不断电下载
在STC单片机开发中,每次烧录程序都需要对单片机进行冷启动,次数多了,十分影响作者心情,况且在频繁的冷启动中,十分影响开关的寿命。好在STC公司留下了一条路——软复位。
通过数据手册可知:
不难看出,作者可以通过串口发送相关命令,控制单片机软复位到用户程序区或ISP区烧录程序,实现不断电下载:
#include<string.h>
#define UART1_CMD_DOWNLOAD "DOWNLOAD MCU"
#define UART1_CMD_RESET "RESET MCU"
//判断特定指令
void UART1_CmdJudge()
{
if(strcmp(UART1_CMD_RESET,uart1_buffer) == 0) //函数判断两组字符串是否相等
{
IAP_CONTR = 0x20; //复位
}
else if(strcmp(UART1_CMD_DOWNLOAD,uart1_buffer) == 0)
{
IAP_CONTR = 0x60; //复位并烧录程序
}
}
整个主函数:
void main()
{
P_SW2 |= 0x80;
TIMER2_UartInit();
UART1_Init();
TIMER3_Init(); //定时器3用以计算串口空闲时间
while(1)
{
UART1_Loop();
if(uart1_readDone == 1) //数据接收完成
{
UART1_CmdJudge();
//处理数据
UART1_Clear(); //数据处理完之后调用
}
}
}
当然,关于不断电下载功能的程序,在单片机数据手册中有官方例程,作者的思路可能与官方思路有些出入,一切以官方为准!
最后
串口超时是作者在实际应用中而写出来的,奈何作者不管经验还是实力都远远不够,所以望读者指正,谢谢!有任何问题欢迎评论区留言。