【IoT】51 单片机 IO 口模拟串口数据收发

简介: 模拟串口

程序硬件平台:11.0592M晶振,STC单片机(兼容51)。

1、发送数据

/*

  • 在单片机上模拟了一个串口,使用P2.1作为发送端
  • 把单片机中存放的数据通过P2.1作为串口TXD发送出去

*/

include <reg51.h>

include <stdio.h>

include <string.h>

typedef unsigned char uchar;

int i;

uchar code info[] =
{
0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55,0x55
};

sbit newTXD = P2^1;//模拟串口的发送端设为P2.1

void UartInit()
{

SCON  = 0x50;     // SCON: serail mode 1, 8-bit UART
TMOD |= 0x21;     // T0工作在方式1,十六位定时
PCON |= 0x80;     // SMOD=1;
TH0   = 0xFE;     // 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=11.0592MHz
TL0   = 0x7F;     // 定时器0初始值,延时417us,目的是令模拟串口的波特率为2400bps fosc=11.0592MHz

}

void WaitTF0(void)
{

while(!TF0);
TF0=0;
TH0=0xFE;    // 定时器重装初值 fosc=11.0592MHz
TL0=0x7F;    // 定时器重装初值 fosc=11.0592MHz

}

void WByte(uchar input)
{

//发送启始位
uchar j=8;
TR0=1;
newTXD=(bit)0;
WaitTF0();

//发送8位数据位
while(j--)
{
   newTXD=(bit)(input&0x01);      //先传低位
   WaitTF0();
   input=input>>1;
}

//发送校验位(无)
//发送结束位
newTXD=(bit)1;
WaitTF0();
TR0=0;

}

void Sendata()
{

for(i=0;i<sizeof(info);i++)//外层循环,遍历数组
{
    WByte(info[i]);
}

}

void main()
{

UartInit();
while(1)
{
    Sendata();
}

}
2、接收数据

/*

  • 模拟接收程序,这个程序的作用从模拟串口接收数据,然后将这些数据发送到实际串口
  • 在单片机上模拟了一个串口,使用P3.2作为发送和接收端
  • 以P3.2模拟串口接收端,从模拟串口接收数据发至串口

*/

include<reg51.h>

include<stdio.h>

include<string.h>

typedef unsigned char uchar;

//这里用来切换晶振频率,支持11.0592MHz

define F11_0592

uchar tmpbuf2[64]={0};

//用来作为模拟串口接收数据的缓存
struct
{

uchar recv :6 ;//tmpbuf2数组下标,用来将模拟串口接收到的数据存放到tmpbuf2中
uchar send :6 ;//tmpbuf2数组下标,用来将tmpbuf2中的数据发送到串口

}tmpbuf2_point={0,0};

sbit newRXD=P3^2 ;//模拟串口的接收端设为P3.2

void UartInit()
{

SCON=0x50 ;// SCON: serail mode 1, 8-bit UART
TMOD|=0x21 ;// TMOD: timer 1, mode 2, 8-bit reload,自动装载预置数(自动将TH1送到TL1);T0工作在方式1,十六位定时
PCON|=0x80 ;// SMOD=1;

#ifdef F11_0592 
TH1=0xE8 ;// Baud:2400  fosc=11.0592MHz 2400bps为从串口接收数据的速率
TL1=0xE8 ;// 计数器初始值,fosc=11.0592MHz 因为TH1一直往TL1送,所以这个初值的意义不大
TH0=0xFF ;// 定时器0初始值,延时208us,目的是令模拟串口的波特率为9600bps fosc=11.0592MHz
TL0=0xA0 ;// 定时器0初始值,延时208us,目的是令模拟串口的波特率为9600bps fosc=11.0592MHz
#endif 

IE|=0x81 ;// 中断允许总控制位EA=1;使能外部中断0
TF0=0 ;
IT0=1 ;// 设置外部中断0为边沿触发方式
TR1=1 ;// 启动TIMER1,用于产生波特率

}

void WaitTF0(void)
{

while(!TF0);
TF0=0 ;

#ifdef F11_0592 
TH0=0xFF ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz
TL0=0xA0 ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz
#endif 

}

//接收一个字符
uchar RByte()
{

uchar Output=0 ;
uchar i=8 ;
TR0=1 ;    //启动Timer0

#ifdef F11_0592 
TH0=0xFF ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz
TL0=0xA0 ;// 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz
#endif 

TF0=0 ;

WaitTF0();//等过起始位

//接收8位数据位
while(i--)
{
    Output>>=1 ;
    if(newRXD)Output|=0x80 ;//先收低位
        WaitTF0();//位间延时
}

TR0=0 ;//停止Timer0
return Output ;

}

//向COM1发送一个字符
void SendChar(uchar byteToSend)
{

SBUF=byteToSend ;
while(!TI);
TI=0 ;

}

void main()
{

UartInit();
while(1)
{
    if(tmpbuf2_point.recv!=tmpbuf2_point.send)//差值表示模拟串口接收数据缓存中还有多少个字节的数据未被处理(发送至串口)
    {
        SendChar(tmpbuf2[tmpbuf2_point.send++]);
    }
}

}

//外部中断0,说明模拟串口的起始位到来了
void Simulated_Serial_Start()interrupt 0
{

EX0=0 ;    //屏蔽外部中断0
tmpbuf2[tmpbuf2_point.recv++]=RByte();    //从模拟串口读取数据,存放到tmpbuf2数组中
IE0=0 ;    //防止外部中断响应2次,防止外部中断函数执行2次
EX0=1 ;    //打开外部中断0

}
以上是两个独立的测试程序,分别是模拟串口发送的测试程序和接收的测试程序。

3、基于 实现模拟串口的三种方法 的优化

上面两个程序在编写过程中参考了文章《51单片机模拟串口的三种方法》(在后文中简称《51》),但在它的基础上做了一些补充,下面是若干总结的内容:

a、《51》在接收数据的程序中,采用的是循环等待的方法来检测起始位(见《51》的“附:51 IO口模拟串口通讯C源程序(定时器计数法)” 部分),这种方法在较大程序中,可能会错过起始位(比如起始位到来的时候程序正好在干别的,而没有处于判断起始位到来的状态),或者一直在检测起始位,而没有办法完成其他工作。

为了避免这个问题,在本接收程序中采用了外部中断的方法,将外部中断0引脚作为模拟串口的接收端,设IT0=1(将外部中断0设为边缘触发)。这样当起始位(低电平)到来时,就会引发外部中断,然后在外部中断处理函数中接收余下的数据。

这种方法可以保证没数据的时候程序该干什么干什么,一旦模拟串口接收端有数据,就可以立即接收到。

b、加入了模拟串口接收缓冲区。在较大程序中,单片机要完成的工作很多,在模拟串口接收到了数据之后立即处理的话,有可能处理不过来造成丢失数据,或者影响程序其他部分执行。本程序中加入了64个字节的缓冲区,从模拟串口接收到的数据先存放在缓冲区中。这样就算程序一时没工夫处理这些数据,腾出手来之后也能在缓冲区中找到它们。

c、《51》文中的WByte函数和RByte函数中都先打开计数器后关闭计数器。如果使用本文的外部中断法来接收数据,并且外部中断处理函数里外都调用了WByte或RByte的话,需要将这两个函数中的TR0=1,TR0=0操作的语句除去,并在UartInit()中加入一句TR0=1;即让TR0始终开着就可以。

由于之前没有意识到这个问题,因此在具体应用时出现了奇怪的问题:

表现为中断处理函数执行完毕之后,似乎回不到主程序,程序停在了一个不知道的地方。后来经过排查后找到了问题所在,那个程序的中断处理函数中用了RByte,中断处理函数外用到了WByte,而这两个函数的最后都有TR0=0。

这样当中断处理函数执行完毕后,TR0实际上是为0的,返回主程序后(中断前的主程序可能正好处于其他的WByte或RByte执行中),原先以来定时器0溢出改变TF0才能执行下去的WByte函数就无法进行下去,从而导致整个程序停下来不动。(在本文的接收测试程序中不存在这个问题,因为中断处理程序中虽调用了RByte,但中断处理程序外却没有调用RByte或WByte)。

下面是修改后的RByte、WByte和WaitTF0函数,仅供参考:

/**

  • 定时器0溢出后重装初值

**/
void WaitTF0(void)
{

TF0=0 ;

#ifdef F11_0592 
TH0=0xFF ;// 定时器重装初值 fosc=11.0592MHz
TL0=0xA0 ;// 定时器重装初值 fosc=11.0592MHz
#endif 

while(!TF0);
TF0=0 ;

}

/**

  • 从串口B接收一个字符

**/
uchar RByte()
{

uchar Output=0 ;
uchar i=8 ;
//    TR0=1;                             //启动Timer0

/*

#ifdef F11_0592
TH0      = 0xFF;    // 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz
TL0   = 0xA0;    // 定时器重装初值 模拟串口的波特率为9600bps fosc=11.0592MHz
#endif

#ifdef F18_432
TH0      = 0xFF;    // 定时器重装初值 fosc=18.432MHz
TL0   = 0x60;    // 定时器重装初值 fosc=18.432MHz
#endif

*/

WaitTF0();//等过起始位

//接收8位数据位
while(i--)
{
    Output>>=1 ;
    if(newRXD)Output|=0x80 ;        //先收低位
        WaitTF0();//位间延时
}
//  while(!TF0) if(newRXD) break;    //此句和下一句不能加,如果加上了将导致耗时过长,影响下一个字节的接收
//    WaitTF0();                        //等过结束位
//    TR0=0;                             //停止Timer0

return Output ;

}

/**

  • 发送一个字节到串口B

**/
void WByte(uchar input)
{

//发送启始位
uchar j=8 ;
//TR0=1;
newTXD=(bit)0 ;
WaitTF0();
//发送8位数据位
while(j--)
{
    newTXD=(bit)(input&0x01);//先传低位
        WaitTF0();
    input=input>>1 ;
}

//发送校验位(无)

//发送结束位
newTXD=(bit)1 ;
WaitTF0();
//TR0=0;

}
在上面的新修改后的RByte()函数中,有被注释掉的如下两句:

//while(!TF0) if(newRXD) break; //此句和下一句不能加,如果加上了将导致耗时过长,影响下一个字节的接收
//WaitTF0(); //等过结束位

这两句在《51》文中的程序是存在的,但是使用中断接收法后,加上这两句后出现了问题。表现为接收到的下一个字节的数据不完整或直接接收不到,似乎这两句占用了过多的时间。

看这两句的目的似乎是要延时以跳过结束位,但是我感觉这个结束位可以不用管它,反正结束位是个高电平,不会妨碍下一个字节是否到来的判断(下一个字节的起始位是低电平)。那就由它去吧,没有必要为了它而占用 CPU 的时间。

卫朋

人人都是产品经理受邀专栏作家,CSDN 嵌入式领域新星创作者、资深技术博主。2020 年 8 月开始写产品相关内容,截至目前,人人都是产品经理单渠道阅读 56 万+,鸟哥笔记单渠道阅读200 万+,CSDN 单渠道阅读 210 万+,51CTO单渠道阅读 180 万+。

卫朋入围2021/2022年人人都是产品经理平台年度作者,光环国际学习社区首批原创者、知识合作伙伴,商业新知 2021 年度产品十佳创作者,腾讯调研云2022年达人榜第三名。

文章被人人都是产品经理、CSDN、华为云、运营派、产品壹佰、鸟哥笔记、光环国际、商业新知、腾讯调研云等头部垂直类媒体转载。文章见仁见智,各位看官可策略性选择对于自己有用的部分。

相关文章
|
7月前
|
存储 缓存 Linux
Linux IO的奥秘:深入探索数据流动的魔法
Linux I/O(输入/输出)系统是其核心功能之一,负责处理数据在系统内部及与外界之间的流动。为了优化这一流程,Linux进行了一系列努力和抽象化,以提高效率、灵活性和易用性。🚀
Linux IO的奥秘:深入探索数据流动的魔法
|
7月前
|
芯片
STC15F100E单片机模拟串口
STC15F100E单片机模拟串口
STC15F100E单片机模拟串口
|
4月前
|
物联网 数据管理 Apache
拥抱IoT浪潮,Apache IoTDB如何成为你的智能数据守护者?解锁物联网新纪元的数据管理秘籍!
【8月更文挑战第22天】随着物联网技术的发展,数据量激增对数据库提出新挑战。Apache IoTDB凭借其面向时间序列数据的设计,在IoT领域脱颖而出。相较于传统数据库,IoTDB采用树形数据模型高效管理实时数据,具备轻量级结构与高并发能力,并集成Hadoop/Spark支持复杂分析。在智能城市等场景下,IoTDB能处理如交通流量等数据,为决策提供支持。IoTDB还提供InfluxDB协议适配器简化迁移过程,并支持细致的权限管理确保数据安全。综上所述,IoTDB在IoT数据管理中展现出巨大潜力与竞争力。
115 1
|
2月前
【通信协议讲解】单片机基础重点通信协议解析与总结之串口通信(三)
【通信协议讲解】单片机基础重点通信协议解析与总结之串口通信(三)
|
4月前
|
NoSQL Redis 数据库
Redis AOF重写问题之同一数据产生两次磁盘IO如何解决
Redis AOF重写问题之同一数据产生两次磁盘IO如何解决
Redis AOF重写问题之同一数据产生两次磁盘IO如何解决
|
4月前
|
存储 传感器 监控
理解并利用物联网(IoT)数据的技术探索
【8月更文挑战第11天】物联网数据是数字化转型的重要资源。通过深入理解物联网数据的特性和价值,并采取有效的收集、处理和分析策略,我们可以更好地利用这些数据为企业决策提供支持、优化运营效率、创造新的商业模式并推动数字化转型的深入发展。
|
6月前
单片机IO口模拟串口实现原理
单片机IO口模拟串口实现原理
93 5
|
7月前
|
存储 移动开发
80C51单片机----数据传送类指令
80C51单片机----数据传送类指令
213 2
|
6月前
|
机器学习/深度学习 传感器 算法
物联网(IoT)数据与机器学习的结合
【6月更文挑战第6天】物联网和机器学习加速融合,驱动数据收集与智能分析。通过机器学习算法处理 IoT 数据,实现智能家居、工业生产的智能化。示例代码展示如何用线性回归预测温度。结合带来的优势包括实时监测、预警、资源优化,但也面临数据质量、隐私安全、算法选择等挑战。未来需强化技术创新,应对挑战,推动社会智能化发展。
186 0
|
7月前
|
存储 缓存 安全
Linux IO:打开数据之窗的魔法
Linux I/O(输入/输出)是操作系统中一个至关重要的组成部分,它涉及到数据在内存🧠、存储设备💾、网络接口🌐等之间的传输过程。在Linux中,I/O操作不仅仅是文件读写那么简单,它包括了一系列复杂的机制和策略,旨在提高数据处理的效率,保证系统的稳定性和性能。📊
Linux IO:打开数据之窗的魔法