一、原理部分
之前用外设都是直接用的硬件自带的库,虽然以前有学过原理和时序,但是因为工作其实也很少会有需要gpio模拟串口的情况,但总会有串口用完,但是需要一个类似打印串口的情况。今天也是开整,然后用硬件的库对比一下。
1、协议要点
①、异步通讯:
通信双方没有共享时钟信号。依靠预先约定好的波特率 (Baud Rate) 来同步。
②、数据帧格式 (常见):
起始位 (Start Bit): 1位,低电平 (逻辑0)。标志着数据帧的开始,用于同步接收方。
数据位 (Data Bits): 5-9位(通常8位)。从最低有效位 (LSB) 开始发送/接收。
校验位 (Parity Bit): 可选,1位(奇校验、偶校验或无校验)。用于简单的错误检测。
停止位 (Stop Bit): 1位、1.5位或2位,高电平 (逻辑1)。标志数据帧的结束,并为下一帧提供缓冲空间。
空闲状态 (Idle State): 当没有数据传输时,数据线保持高电平。
波特率: 定义了每秒传输的符号(位)数。例如,9600 bps 表示每秒传输9600位数据。位周期 (Bit Time) 是波特率的倒数:T_bit = 1 / BaudRate。这是软件模拟时最关键的参数。
③、时间计算
例如现在波特率为9600,T_bit = 1 / 9600 ≈ 0.0001041667 秒 = 104.1667 微秒 (μs)
用我们最常见的格式,8为数据位,1位起始位,1位停止位,即10位
计算传输一个字节 (10位) 所需的总时间 (T_frame):
T_frame = 10 位 * T_bit = 10 * 104.1667 μs ≈ 1041.667 μs ≈ 1.042 ms
可以理解为 大约 1 毫秒 发送一个字节。
发送端要求:需要在 104.17 μs 的整数倍时间点精确切换GPIO电平。
接收端要求:需要在起始位下降沿后等待1.5*T_bit ≈156.25us进行第一次采样(定位到数据位中间),之后每隔104.17us 采样一次后续位
波特率115200的话即速度是 9600 波特率的 12 倍,这里就不再写那么长
2、GPIO 模拟发送 (TX)
①、配置:
将选定的GPIO引脚配置为推挽输出模式,初始状态设置为高电平(空闲状态)。
②、发送一个字节的流程:
a. 起始位:
将GPIO引脚拉低,并保持1个完整的位周期 (T_bit)。这告诉接收方数据开始传输。
b. 数据位 (LSB First):
从要发送字节的最低位 (bit 0) 开始。
根据该位的值是0还是1,将GPIO引脚设置为低电平或高电平。
保持这个电平状态1个完整的位周期 (T_bit)。
接着发送下一位 (bit 1),重复上述电平设置和延时过程。
依此类推,直到发送完指定的数据位数 (通常是8位)。
c. 校验位 (如果启用):
计算已发送数据位的奇偶性 (奇校验或偶校验)。
根据计算结果,将GPIO引脚设置为相应的电平 (0或1)。
保持这个电平状态1个完整的位周期 (T_bit)。
d. 停止位:
将GPIO引脚拉高。
保持高电平状态1个 (或1.5/2个) 完整的位周期 (T_bit)。这标志着一帧数据的结束,并使线路恢复到空闲状态。
需要注意的点为:
精确的时序: 每个位(起始位、数据位、校验位、停止位)的电平持续时间必须严格等于位周期 (T_bit)。这是模拟成功的最核心要求。任何累积的时序误差都会导致通信失败。
位顺序: 严格遵守协议约定的位顺序(通常是LSB first)。
中断的影响这里我就先不考虑了,因为我只是回顾下原理,不是真的要用模拟gpio来实现项目需求
3、GPIO 模拟接收 (RX)
①、配置:
将选定的GPIO引脚配置为浮空输入或上拉输入模式(确保空闲时为高电平)。
②、检测起始位:
持续轮询(或在中断中检测)GPIO引脚状态。
当检测到引脚从高电平变为低电平(下降沿)时,可能是一个起始位开始。
③、同步与采样:
检测到下降沿后,不能立即采样数据。需要避开信号边沿可能不稳定的区域。
关键延时: 等待 1.5个位周期 (1.5 * T_bit)。这个延时将采样点定位到第一个数据位 (LSB) 的中间位置。这是提高抗噪性和采样稳定性的常用技巧。
④、读取数据位:
在预期的采样点(每个数据位的中间位置),读取GPIO引脚的电平状态。
将读取到的电平值 (0或1) 存入接收字节的对应位(从LSB开始)。
每读取完一位,等待1个位周期 (T_bit) 到达下一位的采样点。
重复这个过程,读取完所有数据位 (如8位)。
⑤、读取校验位 (如果启用):
在下一个采样点(等待1个位周期后),读取GPIO引脚的电平作为校验位。
根据协议(奇/偶校验)和收到的数据位计算期望的校验位,与实际收到的校验位比较,进行错误检查(可选)。
⑥、检测停止位:
在下一个采样点(等待1个位周期后),读取GPIO引脚的电平。期望是高电平 (1)。
如果检测到高电平,说明停止位有效,一帧数据接收基本完成(可考虑校验结果)。
如果检测到低电平,说明发生了帧错误(Frame Error)
需要注意的点为:1.5 * T_bit 和后续的 1 * T_bit 延时必须非常准确。采样点定位在位的中间是最佳实践。
二、配置工程
我的板子是APM32F107VC EVAL_V1.0
因为之前看是pin to pin,我以为是完全兼容,所以用cubemx生成了工程,后来程序一直跑不了,查了一下要用极海官方的sdk才行。
在官方下载sdk和pack即可
之前都是用IAR,现在公司用keil,就用回keil
我用的是他串口中断的例程做base
APM32F107_EVAL_SDK_v1.0\Examples\USART\USART_Interrupt\Project\MDK
如上图复制到自己工程的文件夹
再将路径改一下
然后这里每个都要重新添加一下
编译成功,跑了一下程序OK
三、修改程序
1、定义的结构体和宏
用PD13做TX,PD14做RX
我这里做的波特率9600,没有做太高的
tx rx初始化,其中rx配置为中断
2、初始化
其中rx配置为外部中断
两个定时器,定时器5做帧间隔的计时,定时器6为判断数据是否结束(跟之前stm32接收不定长数据差不多STM32+Cubemx+Esp8266(一)串口接收不定长数据_stm32cubemx esp8266-CSDN博客)
2、发送部分
单个字符
字符串
发送数组
这里我是直接延时硬等,可以再加一个定时器用作发送。
3、接收部分
在接收首个低电平的时候,即为起始帧,然后打开定时器5处理后边的数据
定时器5的中断就是数据处理
这里我是1为起始位,8位数据位,1位停止位,无校验,依次处理就好,用UART_TEST_Handle.rxBitCount作判断当前在第几位。数据保存到UART_TEST_Handle.rxBuffer这个buff,
然后每收到一个字节都打开定时器6,定时器6做了11位的时间,若是定时器6中断溢出则说明这帧数据结束,标志位置1
4、主函数
检测标志位是否置1,置1则发送rx的buffer,相当于回显
实际上我这里虽然做了发送队列,但我接收buff没做,所以也是只能发一条而已。这个是之前打印其他比较多的时候做的队列
四、验证程序:
成功
然后发0x55,用逻辑分析仪看看