前言:上一篇文章中说到了UART的数据帧格式以及它的几种工作模式,在实际应用场合中,UART协议大多通过集成在MCU模块中的硬件逻辑来实现。为了更好的理解UART,在这里写篇文章用软件模拟串口通信,加深印象。

一、模拟前的准备

1.工作模式的选择

  • 这里选择最常见异步全双工模式,无校验位,即TX和RX分别用于收发数据,可同时进行。因为这种模式相比其他的最具有代表性,也比较容易模拟,毕竟学东西都是由易到难的模式 :)

2.数据的格式

  • 这里设定的数据帧格式为1位起始位+8位数据位+1位停止位 = 10位数据位。

3.波特率的选择

  • 这里的波特率选择4800Bd/s,之前说过UART下,码元的状态只有0或1两种,所以此时波特率=比特率,即此时比特率为4800bps。

4.传输时间的计算

  • 按上述所说,1s可以传输4800个bit,所以传输一个Bit的时间是1/4800s = 208us。

5.程序模式的设计

  • 为了便于理解和模拟,这里采用定时器计数轮询的方法,只要含有定时器的可编程芯片都能轻易实现。

6.数据结构的设计

  • 1)状态控制
uint8_t UART_SCON;  //模拟控制寄存器,可控制UART是否有效
#define RIEN (1<<0)
#define TIEN (1<<1)uint8_t UART_ISR; //模拟状态寄存器
#define RI_Bit (1<<0) //标志传送或接收一个字节的过程中
#define TI_Bit (1<<1)
#define RI_Byte (1<<2) //标志传送或接收一个字节完成
#define TI_Byte (1<<3)#define UART_TXD_Port M0P_GPIO->P3OUT  //定义传送或接收引脚
#define UART_RXD_Port M0P_GPIO->P3IN
#define UART_TXD_Pin  1<<5
#define UART_RXD_Pin  1<<6#define UART_BitNum   10  //1bit起始位+8bit数据位+1bit停止位 = 10
#define UART_BitCnt   13 //7 9600 //52 1200 //13 4800

:上述代码中的UART_BitCnt是传送一个Bit需要多少个计数周期。笔者使用的定时器为16us定时器,4800的波特率,计算方法为1/4800*1000000/16 = 13,表示传送一个Bit需要的13个16us的时间。

  • 2)发送数据
typedef struct{uint8_t BitNum;  //用于计数传送了第几个Bituint8_t BitCnt;  //用于计数每个Bit传送需要时间uint16_t BitSndBuf; //存储要传送的Bit,包括起始位、数据位和停止位uint8_t SBUF;  //要发送的一个字节数据的缓存
}UART_Tran;
  • 3)接收数据
typedef struct{uint8_t BitNum;  //计数接受的Bit数uint8_t BitCnt;  //计数每个Bit的传送时间uint16_t BitRcvBuf;  //接收的一个帧的数据,包括起始位、数据位和停止位uint16_t SBUF; //存储接收数据的缓存uint8_t lastLev;  //存储RX上个周期的状态,发现下降沿开始接收uint8_t sampCnt; //采样时间计数器uint8_t sample;  //计数三次采样期间共出现多少次高电平
}UART_Recv;

二、发送一个字节的数据

发送数据和接收数据相比要简单些,所以我们先来做数据的发送。我们需要写一个函数来处理一个字节数据的发送,之后每发送一个字节只要循环调用即可。

void UART_SIM_SendByte(UART_Tran *T)
{if(UART_ISR&TI_Bit)  //开始传送一个字节的数据{T->BitCnt++;  //16us计数if(T->BitSndBuf&0x01) UART_TXD_Port |= UART_TXD_Pin;  //set 1else UART_TXD_Port &= ~UART_TXD_Pin; //set 0if(T->BitCnt >= UART_BitCnt) //传送一个Bit结束{T->BitCnt = 0;  T->BitNum++; T->BitSndBuf >>= 1; //传送序为低Bit位先传送}if(T->BitNum >= UART_BitNum) {T->BitNum = 0; //传送完一个字节后计数器清0UART_ISR &= ~TI_Bit; //清空传送标志UART_ISR |= TI_Byte; //置传送完成标志}}else UART_TXD_Port |= UART_TXD_Pin;  //无数据传送时,TX为高
}

三、接收一个字节的数据

接收数据和发送数据相比,多了一个重复采样的环节。这里设定是在每个Bi传送期间,进行三次采样,对高电平计数。若高电平次数大于等于2次,则判断为这个Bit是1,否则判断这个Bit是0。

void UART_SIM_RecvByte(UART_Recv *R)
{if(UART_ISR&RI_Bit)  //开始接收一个字节{R->BitCnt++; //16us 计数器R->sampCnt++;  //采样计数器if(R->sampCnt >UART_BitCnt/4) //达到时间进行一次采样{R->sampCnt = 0;if(UART_RXD_Port&UART_RXD_Pin)// 判断是否是高电平R->sample ++; //高电平计数}if(R->BitCnt >= UART_BitCnt)  //接收一个Bit结束  {R->BitCnt = 0;  //清空计数器R->sampCnt = 0;if(R->sample > 1) R->BitRcvBuf |= (1<<R->BitNum); //根据采样结果判断此时是高电平还是低电平R->sample = 0;R->BitNum++; }if(R->BitNum >= UART_BitNum)  //接收一个字节结束{R->BitNum = 0; UART_ISR &= ~RI_Bit; //清接收字节状态R->SBUF = R->BitRcvBuf >> 1; //将接收到的数据部分送到缓存R->BitRcvBuf = 0;UART_ISR |= RI_Byte; //置接收完成状态}}
}

四、监听TX和RX的状态变化

上述两个函数分别对应传送一个字节和接收一个字节,这时应该还需要一个函数来监听RX和对应的状态控制信息,以便于MCU能够进入传送或者接收一个字节的函数处理过程,同时它还应该完成对要传送数据的装载以及要接收数据的存储。下面的DatTran数组和DatRecv数组要发生的数据帧和要接收的数据区域,可自行根据需要进行替换。

void UART_SIM_STAT(UART_Tran *T,UART_Recv *R)
{if(!(UART_ISR&TI_Bit) && UART_SCON&TIEN) UART_ISR |= TI_Bit; //如果使能了UART且未进入传送Bit的状态,则置位开始进入传送状态if(UART_ISR&TI_Byte) //当传送完成一个字节后{UART_ISR &= ~TI_Byte;  //清标志if(SpTran<12)  //假设要传送的数据为12个字节{T->SBUF = DatTran[++SpTran]; //装载传送数据T->BitSndBuf = ((uint16_t)(T->SBUF << 1)) | 0x0200;  // 填充要传送的Bit流,因为只有1个停止位,8个数据位,所以是0x0200}else{UART_SCON &= ~TIEN;  //传送完连续12个字节后,停止发送数据}}if(!(UART_ISR&RI_Bit))  //没有数据接收时{if(!(UART_RXD_Port&UART_RXD_Pin) && R->lastLev) UART_ISR |= RI_Bit; //产生一个下降沿,开始进行数据的接收}R->lastLev = UART_RXD_Port&UART_RXD_Pin; //存储上个计数周期时的引脚状态if(UART_ISR&RI_Byte) //接收完一个字节数据后{UART_ISR &= ~RI_Byte; //清标志if(SpRecv<12) DatRecv[SpRecv++] = R->SBUF; //从缓存中读取数据}
}
  • 总结:上述就是简单使用GPIO模拟串口通信的过程,下面来说说在实验中可能遇到的问题。
    1.关于波特率
    本次实验中的波特率设置为4800,在实验过程中发现,对于较低的波特率,模拟串口可以较好的实现数据收发,但对于较高的波特率,数据可能出现错误。
    2.关于中断定时
    本次采用的是16us定时器,如果在程序中其他中断开的较多,那么也很可能对模拟串口产生影响。为了减弱该影响,可以在传送或者接收一个字节的前后分别关和开中断。
    3.关于模拟串口引脚串联电阻
    笔者在和某个芯片进行通信时,出现数据包丢失,数据紊乱,通信时好时坏的问题,寻找多次无果。最后发现是硬件引脚串联了10K电阻导致电平出现问题,无法正确被接收。最后换成220欧电阻解决问题。

    也就是说,如果能用集成的UART模块就尽量用,引脚不够时也可以选择扩展芯片,软件模拟受到的干扰较大,数据传输出错的可能性较高。

UART通信协议(三)GPIO模拟串口相关推荐

  1. GPIO模拟串口TX与RX,波特率115200

    串口协议的简单介绍: UART使用异步模式工作,不需要时钟信号,其一般格式为:起始位+数据位+校验位+停止位.其中起始位1位,数据位5~8位,校验位0或1位,停止位1.1.5或2位.不过最常用的格式是 ...

  2. linux gpio 模拟串口,STM32的GPIO口模拟串口通信.rar

    [实例简介] 利用GPIO.EXTI外部中断.TIM定时器实现URAT串口,该例子来自21IC网,未做改动,明天自己调试,看看效果 完全是根据UART协议编写 [实例截图] [核心代码] STM32的 ...

  3. 九齐NY8B072A单片机使用笔记(三)模拟串口RX

    因为这款单片机没有硬件串口,所以需要我们自己做软件模拟串口. 用PA3作为RX,因为PA3可以作为外部输入中断EXTI1. 本人首先用轮询的方式查PA3是否从高电平跳变到低电平(起始信号),但是因为还 ...

  4. 泰凌微 Telink TLSR825X Printf gpio 模拟 串口 打印信息 log

    如题: 使用telink可以使用printf功能,之前我不了解,我一度以为这是uart 实现的printf 在配置的时候,居然配置 uart 对应的gpio接口,后面这个问题坑了一段时间. 其实pri ...

  5. 用GPIO口模拟串口通信,它真的来了

    你是否遇到过某个MCU串口不够的情况? 这时我们可以考虑用GPIO去模拟,如何具体实现呢? 首选我们需要了解串口的传输协议, UART使用异步模式工作,不需要时钟信号,其一般格式为:起始位+数据位+校 ...

  6. GPIO模拟I2C通信协议(二)

    GPIO模拟2C读写E2PROM 1 E2PROM简介 2 AT24C28的读写逻辑 2.1 单字节写入 (BYTE WRITE) 2.2 页写入 (PAGE WRITE) 2.3 读取当前地址 (C ...

  7. GPIO口模拟串口发送接收(基于H861)

    以前常听说码农需要有严密的逻辑思维,以前不明白.没有思维框架,真的很难写代码,不能瞎蒙,等我的只是效率低下 思路 首先我们要模拟串口通信,就要了解通信的必须条件,包括数据位及其他标志位,以及他的时序, ...

  8. 单片机IO口模拟串口程序(发送+接收

    单片机IO口模拟串口程序(发送+接收)[转] qcmc 发表于 - 2011-6-23 0:42:00 前一阵一直在做单片机的程序,由于串口不够,需要用IO口来模拟出一个串口.经过若干曲折并参考了一些 ...

  9. 03【Verilog实战】UART通信协议,半双工通信方式(附源码)

    脚 本:makefile(点击直达) 应用工具:vcs 和 verdi 写在前面 这个专栏的内容记录的是个人学习过程,博文中贴出来的代码是调试前的代码,方便bug重现. 调试后的程序提供下载,[下载地 ...

最新文章

  1. elasticsearch 查询模板
  2. 老生常谈.优化linux内核参数
  3. Confluence 6 索引支持的语言并进行修改
  4. 江山如此多娇,大美中国
  5. pHp30充电宝能用快充吗,65W快充 30分钟充满电 是时候淘汰充电宝了吗?
  6. matlab 合成生物学,合成生物学原理
  7. 信息学奥赛一本通 1069:乘方计算 | OpenJudge NOI 1.5 13
  8. phpcms v9输出内容过滤html代码 - 代码篇
  9. java关键字值transient
  10. mysql默认端口号_什么是MySQL默认端口号?
  11. EXCEL单元格公式-实现阿克曼函数计算
  12. java视频压缩 lz4_压缩包格式有哪些?
  13. STM32F407——SYN6288语音播报模块串口一修改为串口三
  14. 干货!纯干货! 手把手教你做云专线互联网备援接入-上集
  15. ARM Cortex-M3/M4内核相关
  16. chm无法打开html文档,高手几招搞定WindowsXP无法打开chm文件的情况
  17. 大赛征集令|首届“万应杯”低代码应用开发大赛报名开启啦!
  18. vue鼠标悬停更改图片
  19. 【CSS 形状 (Shapes)】
  20. 环境配置 | 更改注册表使PPT导出的图片分辨率达到300dpi

热门文章

  1. 获取Android 光感Sensor的值
  2. android之统一字体大小
  3. python模型预测结果 取整_一日一技:Python里面的//并不是做了除法以后取整
  4. vue3.0 axios 引入及使用
  5. java request 原理_JavaWeb response和request对象原理及实例解析
  6. java 日期只计算年月日大小_java 日期加减天数、月数、年数的计算方式
  7. 模仿笔迹最好的软件_模仿签名代写签字行业进入战国时代,乱战模式开启!
  8. python中np没有定义_第六篇:python中numpy.zeros(np.zeros)的使用方法
  9. liunx 环境-配置docker阿里云镜像加速
  10. linux下esc退不出vi