|
STC官方的模拟串口例程,看了很久,做了一点改动。但是核心还是他家的。
/*
STC MCU STC15F204EA 模拟串口的示例程序
原始代码来自 STCMCU的官方例程。
****************************************************************************
*** 重要说明:本程序不适用于硬件串口的产品,仅为无硬件UART的产品使用。******
****************************************************************************
本例程同时Timer0中断,同步完成接受和发送的工作,属于双工首发。
示例中,由串口将收到的信息在下一个发送周期转发出去。
示例中的收发波特率可变,其它设置为8位数据位,无奇偶校验位,1位停止位。
*/
//以下参数设置信息来自 STCMCU 官方例程。
//特别说明, MCU工作频率FOSC设置为11.0592MHz。如果使用12.000MHz,24.000时,会有频差。
#include "reg51.h"
#include "intrins.h"
//波特率设定值计算公式:BAUD = 65536 - FOSC/3/BAUDRATE/M (1T:M=1; 12T:M=12)
//为了确保通信质量,(FOSC/3/BAUDRATE) 必须大于 98,推荐大于110。
//这里每3次中断,做一次输出检查或输出。一般要求做连续16次采样,取其开始,中间,
//结束3个数据,如果稳定,则可以认为数据有效。
//3*16*2(?)=96,加上定时信号的不稳定性,在98以上合理。 ???
//因此,中断发生的频率是波特率信号的3倍以上。
//按照主频为11059200计算,如果选择信号比大于110,则最大波特率为33512.
//如果选择98,则为37616。选择38400的通讯速率都不能保证接收的正确性。
//因此,最可靠的数据接收波特率应该是19200。
//当时钟频率提升到33.1174MHz时,最大波特率为112848,依然小于115200. 使用115200时,
//波特率误差可能高达2%,出现数据差错。因此最大通讯速率选择 57600.
//#define BAUD 0xF400 // 1200bps @ 11.0592MHz
//#define BAUD 0xFA00 // 2400bps @ 11.0592MHz
//#define BAUD 0xFD00 // 4800bps @ 11.0592MHz
//#define BAUD 0xFE80 // 9600bps @ 11.0592MHz
//#define BAUD 0xFF40 //19200bps @ 11.0592MHz
//#define BAUD 0xFFA0 //38400bps @ 11.0592MHz
//#define BAUD 0xEC00 // 1200bps @ 18.432MHz
//#define BAUD 0xF600 // 2400bps @ 18.432MHz
//#define BAUD 0xFB00 // 4800bps @ 18.432MHz
//#define BAUD 0xFD80 // 9600bps @ 18.432MHz
//#define BAUD 0xFEC0 //19200bps @ 18.432MHz
//#define BAUD 0xFF60 //38400bps @ 18.432MHz
//#define BAUD 0xE800 // 1200bps @ 22.1184MHz
//#define BAUD 0xF400 // 2400bps @ 22.1184MHz
//#define BAUD 0xFA00 // 4800bps @ 22.1184MHz
//#define BAUD 0xFD00 // 9600bps @ 22.1184MHz
//#define BAUD 0xFE80 //19200bps @ 22.1184MHz
//#define BAUD 0xFF40 //38400bps @ 22.1184MHz
#define BAUD 0xFF80 //57600bps @ 22.1184MHz
//后续程序段中,对于不同的设置,可能有如下的条件编译指令
//在不同的通讯参数配置下,可能需要重新编译,以降低代码量。
#define DATALEN 8 //8位数据位
#define NOPARITY //无校验位
//#define EVENPARITY //偶校验
//#define ODDPARITY //奇校验
#define STOP1BIT //1位停止位
//#define STOP1P5BITS //停止位1.5位
//#define STOP2BITS //停止位2位
#ifdef EVENPARITY //偶校验
#endif
#ifdef ODDPARITY //奇校验
#endif
#ifdef NOPARITY //无校验
#endif
#ifdef STOP1BIT //停止位1位
#endif
#ifdef STOP1P5BITS //停止位1.5位
#endif
#ifdef STOP2BITS //停止位2位
#endif
#define YES 1
#define NO 0
sfr AUXR = 0x8E;
sbit RXB = P3^0; //UART RX 管脚
sbit TXB = P3^1; //UART TX 管脚
//数据发送相关的中间数据
unsigned char nDataToSend; //要发送的字符
unsigned char nTmpTxData; //发送过程的临时数据
unsigned char nTransmitCount; //输出定时计数器。每三次输出一个数据。
unsigned char nTxBitCount; //发送剩余位计数器
unsigned char pTx; //发送数据指针
bit tData; //***新增加:准备发送的位数据(使用CY作为中间数据位不可靠,可能会被修改)
bit bIsTransmitting;
bit bTransmitCompleted;
//数据接收相关的中间数据
unsigned char nReceiveCount; //输入电平多次采样计数器
//说明:这里默认的发送和接收过程都是在3次中断发生后采样输入信号或发送数据。
unsigned char nReceivedData; //最终接收的数据
unsigned char nTmpRcvData; //接收过程的临时数据(未完成的数据)
unsigned char nRxBitCount; //接收剩余位计数器
unsigned char pRx; //接收数据指针
bit bIsReceiving;
bit bReceiveCompleted;
//数据接收缓冲区的定义
#define BUFFLEN 0x10 //字符缓冲区长度
#define CYCLEMASK 0x0F //缓冲区循环掩模字码,=BUFFLEN-1。如长度为0x10,则掩模为0x0F,如果长度为0x20,则掩模为0x1F。
unsigned char buf[BUFFLEN];
void UartInit(void);
//基本输入输出函数,其它类型数据的处理,可以通过标准库的转换实现,如sprintf,atoi等。
void SendChar(unsigned char c); //发送一个字符
void SendStr(char *str); //发送一个字符串(长度不能超过BUFFLEN)
unsigned char uGetChar(void); //获取一个字符
char *uGetStr(void); //获取字符串,结果指向默认的通用缓冲区buf[BUFFLEN].
void SendChar(unsigned char c)
{
while (!bTransmitCompleted); //如果发送未完成,则等待
nDataToSend = c; //准备要发送的字符
bTransmitCompleted = 0;
bIsTransmitting = 1; //标识正在发送标志
}
void SendStr(char *str)
{
while (*str) SendChar(*str++);
}
unsigned char uGetChar(void)
{
while (!bReceiveCompleted); //如果数据接收未完成,则等待
bReceiveCompleted = 0; //允许开始接收下一个字符
return nReceivedData; //返回接收到的字符
}
char *uGetStr(void)
{
/*
字符串接收停止的标志:\r\n(0xD 0xA),或者 '\0'?
*/
unsigned char p; //用数字标号
unsigned char c;
p = 0; //指针指向缓冲区开头
c = uGetChar();
while (c != 0x0) {
buf[p] = c;
p++;
p &= CYCLEMASK; //防止指针溢出,自动从头到尾循环,但是会丢失前面的字符。
//if (p = CYCLEMASK) break; //第二策略:如果溢出,则自动停止接收。未测试。
c = uGetChar();
}
buf[p] = 0; //字符串结尾
return buf;
}
void main(void)
{
/*
程序的初始状态准备。
*/
bIsTransmitting = 0; //尚未开始发送
bIsReceiving = 0; //尚未开始接受
bTransmitCompleted = 1; //缓冲区数据已发送完成(没数据可以发送)
bReceiveCompleted = 0; //尚未接收到数据
nTransmitCount = 0; //发送过程的T0中断次数计数器为0
nReceiveCount = 0; //接收过程的T0中断次数计数器为0
pRx = 0; //接收指针指向起始位置
pTx = 0; //发送指针指向起始位置
UartInit(); //设置中断条件(工作模式、计数值等,并打开中断)
while (1)
{
//SendChar(uGetChar()); //测试1:边收边发。具体波形见下图1.
//测试2:按字符串进行收发,具体波形见下图2.
uGetStr();
SendStr(buf);
/*
以下程序段是 STC的官方例程。
完成接收一个字符后,由Tx再次转发出去的动作。
当没有数据输入时,也不会有数据输出。
数据的输出最少比输入要晚一个字节。
*/
/*
if (bReceiveCompleted) //如果数据接收完成
{
buf[pRx] = nReceivedData; //将已接收的字符存入缓冲区中。
pRx++; //接收指针指向下一个字符位置。
pRx &= CYCLEMASK; //修订为按缓冲区长度循环的指针。
bReceiveCompleted = 0; //可以再次接收下一个字符了。
}
if (bTransmitCompleted) //如果数据发送完成
{
if (pTx != pRx) //只有在接收指针的位置和发送指针的位置不同时,才开始发送。
{
nDataToSend = buf[pTx];
pTx++;
pTx &= CYCLEMASK;
bTransmitCompleted = 0;
bIsTransmitting = 1;
}
}
*/
}
}
//定时器0中断,用于接收和发送字符。
void Timer0ISR(void) interrupt 1 using 1
{
//数据接收过程
if (bIsReceiving) //如果正在接收过程中,则完成以下动作。
{
nReceiveCount--; //接收中断计数器值减1
if (nReceiveCount == 0) //如果接收中断计数器值为0,则开始判定输入数据。
{
nReceiveCount = 3; //重置接收中断计数器的值为3。每发生3次中断,接收1位数据。
nRxBitCount--; //当前要接受的数据剩余位计数器减1。说明接收完次数据后,还需要接收多少位。
if (nRxBitCount == 0) //如果剩余位计数器为0,说明当前数据已经接收完成。
{
nReceivedData = nTmpRcvData; //保存所接收的数据到nReceivedData中。
bIsReceiving = 0; //停止继续接收。(下一个数据要重新开始对齐起始位)
bReceiveCompleted = 1; //数据接收完成标志置1。
}
else //如果还有数据要接收
{
nTmpRcvData >>= 1; //当前接收的临时数据右移一位,最高位自动补0。
if (RXB) nTmpRcvData |= 0x80; //如果当前输入位是1(RXB=1),则将临时数据的最高位修改为1。
}
}
}
else if (!RXB) //如果不是当前数据的接收过程,但是出现 RXB=0,则表明是数据的起始位,新的输入开始。
{
bIsReceiving = 1; //设置标志,表明开始新的数据接收过程
nReceiveCount = 4; //因为停止位已经做了响应,后续的判断点设置在3次判定点的中间,
//因此在接受到起始位以后,需要4次中断才开始下一个数据判定。
nRxBitCount = 9; //剩余未接收的数据位数(8个数据位 + 0个校验位 + 1个停止位,共9位)
//****注意:没有编写2个停止位的程序。
}
//数据的发送过程:每次中断均可发生。因此数据的发送和接收几乎是同时进行。
//在main()例程中,数据的发送是等待缓冲区中至少一个数据接收完成后,才开始发送动作,因此延迟一个字符的时间。
//如果不考虑判定一个完整字符,可以同步进行接收和发送的动作(直接转发,不解释,也不判定),延迟时间是以上指令的执行时间。
nTransmitCount--; //发送中断计数器减1
if (nTransmitCount == 0) //如果发送中断计数器为0,则开始发送动作。
{
nTransmitCount = 3; //重设发送中断计数器的初值,每3次做一个发送动作。
if (bIsTransmitting) //如果当前字符尚未发送完成,正在发送过程中
{
if (nTxBitCount == 0) //如果尚未发送的位计数值是0,说明当前数据还未开始发送(此变量的含义跟接收过程的剩余位计数器意义不同)。
{
TXB = 0; //发送起始位
nTmpTxData = nDataToSend; //加在要发送的数据(把 nDataToSend 存放到 nTmpTxData 中,实际发送的是 nTmpTxData。)
#ifdef STOP1BIT
nTxBitCount = 9; //初始化剩余位计数器(这里是8个数据位 + 0个奇偶校验位 + 1 个停止位)
#endif
#ifdef STOP2BITS
nTxBitCount = 10; //初始化剩余位计数器(这里是8个数据位 + 0个奇偶校验位 + 2 个停止位)
#endif
}
else //如果剩余位计数值不等于0,说明已经有数据在发送中
{
tData = (nTmpTxData & 0x1); //****注意:此处做了修改,防止CY被后续程序改变而出错。获取要发送数据的最低位
nTmpTxData >>= 1; //数据右移到 CY 中,最高位自动补 0。
//这里要用到特殊位CY,最好查看汇编后的程序,否则可能出错。增加位变量 tData后则无此影响。
nTxBitCount--; //剩余位计数器值减1。
#ifdef STOP1BIT
if (nTxBitCount == 0) //如果已经发送完成
{
TXB = 1; //发送1位停止位。如果是2位停止位,还需要一次。如果是1.5位停止位,如何做?
bIsTransmitting = 0; //表示当前数据的所有位发送完成
bTransmitCompleted = 1; //表示整个字符的传送完成,设置标志为1。
}
#endif
#ifdef STOP2BITS
if (nTxBitCount < 2 ) //这个操作是否会影响到 CY的值?
{
TXB = 1; //发送停止位。如果是2位停止位,还需要一次。如果是1.5位停止位,如何做?
if (nTxBitCount == 0) //如果已经发送完成
{
bIsTransmitting = 0; //表示当前数据的所有位发送完成
bTransmitCompleted = 1; //表示整个字符的传送完成,设置标志为1。
}
}
#endif
else //剩余位计数器不为0,还需要继续发送数据。
{
//TXB = CY; //TX的数据来自 CY 寄存器,是循环右移指令的结果。
TXB = tData; //改用标准数据
}
}
}
}
}
//定时器0初始化,用于虚拟串口初始化
void UartInit(void)
{
TMOD = 0x00; //Timer0设置为16位自动重载模式。
AUXR = 0x80; //Timer0工作在1T模式
TL0 = BAUD; //Timer0重载值的低8位
TH0 = BAUD>>8; //Timer0重载值得高8位
TR0 = 1; //tiemr0启动
ET0 = 1; //允许Timer0中断
PT0 = 1; //Timer0中断优先级为高
EA = 1; //总中断开关开启
}
以上程序使用 Keil C51 V9.52 编译通过。其中 STC15F204EA的主频设置为22.1184MHz,通讯波特率为 57600,8位数据位,使用1或2位停止位,无校验的方式,可以顺利通讯。
使用串口调试助手,从PC向单片机发送数据(下图中通道1),由单片机在接收后进行发送(下图中通道0)。对两种工作方式进行测试:
1. 使用边收边发时的波形如下(SendChar(uGetChar());):
图1. 接收一个字符后就进行转发的例子
2. 使用整个字符串(以字符'\0'结尾)接收完成后再进行发送的波形如下(uGetStr(); SendStr(buf);):
图2. 接收完一个字符串后再转发的例子(去掉了末尾的'\0')
根据实际情况,可以选择不同的接收方式。
|
|