IO口怎么模拟成串口

IO口怎么模拟成串口,第1张

方法一:延时法

通过计算大家知道,串口的每位需延时0.104秒,中间可执行96个指令周期。

#define uchar unsigned char

sbit P1_0 = 0x90

sbit P1_1 = 0x91

sbit P1_2 = 0x92

#define RXD P1_0

#define TXD P1_1

#define WRDYN 44 //写延时

#define RDDYN 43 //读延时

//往串口写一个字节

void WByte(uchar input)

{

uchar i=8

TXD=(bit)0//发送启始

Delay2cp(39)

//发送8位数据

while(i--)

{

TXD=(bit)(input&0x01)//先传低位

Delay2cp(36)

input=input>>1

}

//发送校验位(无)

TXD=(bit)1//发送结束

Delay2cp(46)

}

//从串口读一个字节

uchar RByte(void)

{

uchar Output=0

uchar i=8

uchar temp=RDDYN

//发送8位数据位

Delay2cp(RDDYN*1.5)//此处注意,等过起始位

while(i--)

{

Output >>=1

if(RXD) Output =0x80//先收低位

Delay2cp(35)//(96-26)/2,循环共

占用26个指令周期

}

while(--temp) //在指定的

时间内搜寻结束位。

{

Delay2cp(1)

if(RXD)break//收到结束位便退出

}

return Output

}

//延时程序*

void Delay2cp(unsigned char i)

{

while(--i)//刚好两个

指令周期。

}

此种方法在接收上存在一定的难度,主要是采样定位存在需较准确,另外还必须知道

每条语句的指令周期数。

方法二:计数法

51的计数器在每指令周期加1,直到溢出,同时硬件置溢出标志位。这样我们就可以

通过预置初值的方法让机器每96个指令周期产生一次溢出,程序不断的查询溢出标志来决定是否

发送或接收下一位。

//计数器初始化

void S2INI(void)

{

TMOD =0x02//计数器0,方式2

TH0=0xA0//预值为256-96=140,十六进制A0

TL0=TH0

TR0=1//开始计数

TF0=0

}

void WByte(uchar input)

{

//发送启始位

uchar i=8

TR0=1

TXD=(bit)0

WaitTF0()

//发送8位数据位

while(i--)

{

TXD=(bit)(input&0x01)//先传低位

WaitTF0()

input=input>>1

}

//发送校验位(无)

//发送结束位

TXD=(bit)1

WaitTF0()

TR0=0

}

//查询计数器溢出标志位

void WaitTF0( void )

{

while(!TF0)

TF0=0

}

这种方法接收和发送都很准确,另外不需要计算每条语句的指令周期数。

方法三:中断法

中断的方法和计数器的方法差不多,只是当计算器溢出时便产生一次中断,用户可以

在中断程序中置标志,程序不断的查询该标志来决定是否发送或接收下一位,当然程序中需对中

断进行初始化,同时编写中断程序。本程序使用Timer0中断。

#define TM0_FLAG P1_2 //设传输标志位

//计数器及中断初始化

void S2INI(void)

{

TMOD =0x02//计数器0,方式2

TH0=0xA0//预值为256-96=140,十六进制A0

TL0=TH0

TR0=0//在发送或接收才开始使用

TF0=0

ET0=1//允许定时器0中断

EA=1//中断允许

总开关

}

//接收一个字符

uchar RByte()

{

uchar Output=0

uchar i=8

TR0=1//启动Timer0

TL0=TH0

WaitTF0()//等过起始位

//发送8位数据位

while(i--)

{

Output >>=1

if(RXD) Output =0x80//先收低位

WaitTF0()//位间延时

}

return Output

}

//中断1处理程序

void IntTimer0() interrupt 1

{

TM0_FLAG=1//设置标志位。

}

//查询传输标志位

void WaitTF0( void )

{

while(!TM0_FLAG)

TM0_FLAG=0//清标志位

}

一、 IO模拟UART发送

串口通信属于 串行 异步 半双工 的通信模式

1、 最近在调试一个IO模拟UART的程序,把调试过程中遇到的问题总结一下。对于UART的发送部分(主机模式)还是比较容易实现的。比较麻烦的做从机时,UART接收还在调试,可以接收数据,但还存在很多问题。

(1) 起始位 :总线没通信是高电平状态,要进行通信时,总线拉低发出“逻辑0”信号,表示开始传输数据

(2) 数据位 :通常以1byte数据为标准,从低位开始传输,通过时钟频率来定位数据的传输

(3) 奇偶校验位 :通过在数据末尾加上1位,使得数据包中的1的个数为偶数(偶校验)或者是奇数(奇校验),来确定数据的准确性。

(4) 停止位 :在通信结束时,将总线拉高发出“逻辑1”信号,表示传输结束,也给下次进行通信提供了校准时间的机会

2、波特率

波特率表示每秒钟传送的码元符号的个数,是 衡量数据传输速率的指标 ,它用单位时间内载波调制状态改变的次数来表示,1波特即指每秒传输1个符号。

若 波特率为9600bps,那么传输一位数据的时间是在1000ms/9600bps=0.104ms

3、串口发送数据组成

数据由:1bit起始位+8bit数据+1bit停止位构成;

所以,搞懂这些原理,串口发送并不难,主要把单片机的定时器配置在104us左右溢出中断,保证每一位时间间隔在104us 左右即可。

我用的是51单片机,51本身自带一个串口,但自己想用IO模拟一个串口出来,便于自己更好的理解串口,也作为自己的一个技术储备。

/**********************************************51单片机IO模拟UART实验************************************************************************************/

/****************************************************

*文件名:uart.c

****************************************************/

#include "reg52.h"

#include "type.h"

#include "uart.h"

#include "timer.h"

void UART_INIT(void)

{

    TX_D=1

    Timer1Init()    //T1 初始化

}

void WAIT_TF1(void)

{

    while(!TF1)  //查询计数器溢出标志位

    TF1=0

}

//写数据很快的话,定时器不稳定而导致发送的数据会有错码,发送时应适当延时降低错码概率

void Write_DATA(uint8_t input)

{

    uint8_t i=8  //写入1byte数据

    TR1=1        //开始计时

    TX_D=0        //拉低信号线准备发送数据  起始信号

    WAIT_TF1()    //106us溢出一次,溢出进行下一位的传输

    while(i--)    //开始写数据

    {

        TX_D=input&0X01

        WAIT_TF1()

        input>>=1

    }

    TX_D=1      //8位数据发完拉高信号线  停止信号

    WAIT_TF1()

    TR1=0        //停止计时

}

//发送字符串

void Send_Char(uint8_t *buf)

{

    while(*buf != '\0')

    {

    Write_DATA(*buf)

    buf++

    }

}

//发送数字

void Send_Num(uint8_t *buf,uint16_t s)

{

    while(s--)

    {

        Write_DATA(*buf+48)    //ASCII 将字符转换成数字

        buf++

    }

}

//发送多个数据

void Send_ND(uint8_t *buf,uint16_t len)

{

    while(len--)

    {

        Write_DATA(*buf) 

        buf++

    }

}

#include "type.h"

#ifndef  __UART_H__

#define  __UART_H__

sbit TX_D=P0^6

extern uint8_t flag2

extern void Send_Char(uint8_t *buf)                //发送字符

extern void Send_ND(uint8_t *buf,uint16_t len)    //发送N个数据

extern void Send_Num(uint8_t *buf,uint16_t s)      //发送数字

extern void UART_INIT(void)                        //串口初始化

extern void WAIT_TF1(void)                        //等待TF1溢出

extern void Write_DATA(uint8_t input)              //写数据

#endif

/*****************************************************************

文件名:timer.c

*******************************************************************/

#include "reg52.h"

#include "type.h"

#include "timer.h"

uint8_t flag1=0

void Timer1Init()

{

  TMOD = 0x10  //T1  方式1

    TH1 = 0xFF    //波特率9600bps 1000ms/9600bps=0.104ms

    TL1 = 0xA0    //定时器误差2us左右  106us

    EA = 1

    ET1 = 1

    TR1 = 1

  TF1=0

}

void Time1() interrupt 3

{

if(TR1==1)

{

TH1=0XFF

TL1=0xA0

flag1=1

}

}

#include "type.h"

#ifndef  __TIMER_H__

#define  __TIMER_H__

extern uint8_t flag1

extern void Timer1Init(void)

#endif

#ifndef  __TYPE_H__

#define  __TYPE_H__

#define uint8_t  unsigned char

#define uint16_t  unsigned int

#endif

/*******************************************************主程序--测试部分****************************************************************/

#include "reg52.h"

#include "type.h"

#include "timer.h"

#include "uart.h"

#include "delay.h"

uint8_t buffer[5]

uint8_t Buff[5]={1,2,3,4,5}

void main(void)

{

UART_INIT()

Write_DATA('A')

Send_Char("testuart")

Send_Num(Buff,5)

while(1)

}

如果发送发在while(1)中跑的话,需要加延时来降低由于查询过快导致的发送乱码的概率,事实证明加延时效果更好,结果更准确。

二、 IO模拟UART接收

1、模拟串口接收部分是可以接收,但存在很多问题

2、我对问题进行了总结以便后期改进

3、读数据  接收数据的效果不是很好,需要连续的点击发送字符才会成功

    问题解析 :估计是等到单片机扫描时,串口helper已经把数据发送完了,单片机这边对不上起始信号从而导致错峰,所以串口helper需要不停的点击发送在这个期间对上了就进去了。

最近调试了一下,现在接收部分已经可以正常接收了。

接收部分

uint8_t Receive_Data(void)

{

uint8_t receive=0,t=0

uint8_t i=8

while(RX_D)    //等待起始信号,超时自动退出

TR1=1    //开始计时

WAIT_TF1()

while(i--)

{

receive>>=1

if(RX_D)receive|=0x80

WAIT_TF1()

}

TR1=0

return receive

}

void Recevice(uint8_t *temp,uint8_t data_size)   

{

while(data_size--)

{

*temp++=Receive_Data()

}

}

TEST

uint8_t recv[20]={0}

void main(void)

{

UART_INIT()

while(1)

{

  Recevice(recv,5)

  Send_ND(recv,5)

}

}

用普通 I/O 口也可以模拟标准 UART 串行口,进行串行通信。

UART 通信规范是以 8 位二进制数为一帧,低位在前,逐位的传输。

为了区分各个帧,在每一帧之前,要有一个 0 作为起始标记,之后,有一个 1,作为结束符。

在结束符之前,还可选发一个“校验位”,但是,目前多数的应用都不选择这个位。

那么,每次的串行通信,就是传送一个字节,加上前后的标记,共 10 位二进制数。

空闲时,发送的都是 1;一旦出现了 0,就说明开始传输数据了。

波特率

串行通信的一个重要指标就是传输速度,就是每秒传送了多少位二进制数。

这个速度称为波特率,单位是 bps,中文就是“位/秒”。

时间设定

当以 9600bps 来传送数据时,每一位数的持续时间是 (1/9600)s,这也就是间隔时间。

如果选用晶振频率是 11059200Hz,一个机器周期T的时间就是 (12/11059200)s

那么,一位数的持续时间 (1/9600)s,是多少个机器周期T呢 ?

这是很容易算的,就是下面的这个算式:

X = (1/9600) / (12/11059200) = 11059200 / 12 / 9600 = 96T

为了精确定时,可以利用定时器来定时,每当 96T 时间到了,就发送出去一位二进制数,这就行了。

实验程序

用 IO 口模拟串口输出的程序如下:

#include<reg52.h>

sbit TXD1 = P2^0//用IO口模拟串口发送端

sbit RXD1 = P2^1//用IO口模拟串口接收端

bit T96 //位变量

//----------------------------------------

void Wait96(void) //延时,控制波特率

{

while(T96) //等待出现0

T96 = 1 //清标志

}

//----------------------------------------

void WByte(char x)//发送一帧数据

{

char i

TL0 = 160 //初值=256-96=160

TXD1 = 0//发送起始位0

TR0 = 1 //启动定时器

Wait96()//等待96T

for (i = 0i <8i++) { //8位数

TXD1 = x &1 //先传低位

x >>= 1

Wait96() //等待96T

}

TXD1 = 1//发送结束位1

Wait96()//等待96T

TR0 = 0 //关闭定时器

}

//----------------------------------------

void main()

{

char i

TMOD = 0x02 //T0定时方式2

TH0 = 160 //初值=256-96=160

IE = 0x82

T96 = 1 //清标志

while(1) {

for (i = 0x41i <0x5bi++) //A~Z

WByte(i)

WByte(0x0D)

WByte(0x0A)

}

}

//----------------------------------------

void inttime0() interrupt 1 //T0中断

{

T96 = 0 //设置标志

}

//----------------------------------------


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/7736512.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-09
下一篇 2023-04-09

发表评论

登录后才能评论

评论列表(0条)

保存