想来串口通信是我第一次接触嵌入式就知道的,一直调试也是用串口线,但是里面的原理还真不清楚。这次难得把环境弄出来了,就顺便学学。
一 环境搭建
还是老规矩,废话不多说,干就完事。
这次用的树莓派zero小板,一个USB转串口。接线真的很简单,RX->TX,TX->RX,再接个地(我的实验里面地不接也行)。电脑上串口终端软件用的是mobaXterm。代码还是老朋友micropython。
二 协议分析
这次实验的目标是做一个串口echo程序。最开始是直接用GPT的代码上来怼,本来以为很简单,一个小时结束战斗。但是真跑起来根本没法用。回显的全是乱码。没办法,又拿出祖传的逻辑分析仪看看。
先还是抓几个PC出来的波形吧。波特率设置的9600。很多协议,只要看了波形,真的很好懂。
传输数字1
因为波特率是9600,那么每个信号的时间就是1/9600秒,大概就是104us。所以可以看出,这里的波形是0 1000 1100。第一个下拉0是起始位。所以数据是1000 1100。
但是1的ascii码是49,换成二进制是110001,这里好像是没对啊。看了很久眼睛都看痛了,终于发现110001如果补全就是0011 0001,刚好和上面的1000 1100是反的。查了一下这个原来叫LSB first。取数据的时候倒着取就行了。
传输数字2
有了1的经验,下面就简单很多,这个波形是0 0100 1100。2的ascii是50,刚好是110010,补全之后是0011 0010,刚好也是直接反过来就行。
总结
总结一下UART协议
基本就是两根线(不算地线),甚至你都可以理解成一根线,只是方向相反而已。也没有时钟那些乱七八糟的,时隙直接sleep就欧了。数据一次一个byte,高就是1,低就是0。真的很简单。。在嵌入式里面,不知道有没有比它更简单的协议。。。
上图的校验位据说基本上不会用的。。。
三 代码实现
解决了LSB first问题,后面没有太多难点。虽然说协议真的不难,但是编程的时候还是遇到一些坑。
1 需要微秒级的定时器。
GPT给的代码是:
baudrate = 1200
bit_time = 1 / baudrate
utime.sleep(bit_time)
事实上这样用根本就不准,取出来的值每次都不同。改成下面才算解决问题。
bit_time = 832
utime.sleep_us(bit_time)
2 Python的性能确实不大行
最开始设置的波特率是9600,已经算很低了,每个时隙是差不多100微妙。但是micropython软件处理这个时间好像也够呛,最后索性改成最小的波特率1200,每个时隙接近1毫秒,才算把问题解决。
3 调试
在以往上层纯软环境中,调试环境往往相对简单。直接用print或者cout都不用考虑太多。但是在底层编程中,print就会有影响了,因为print本上也是一个io操作。稍微不注意,就把程序的时间逻辑给破坏了。
for i in range(8):
if rx_pin.value():
print("1")
byte |= (1 << i)
else:
print("0")
最开始我是想这样看收到的数据,但是这个打印只要一加上,程序立马就混乱了。。。最后只有等一个byte抓完,再整体打印,这样就暂时没问题了。
做的好还是应该像Android那样,log是一个独立的系统,这样影响最小。
好了,解决完上面的问题,也就比较顺利了。
最后的结果:
最后的代码
import machine
import utime
# 定义 UART 参数
bit_time = 832
# 定义 GPIO 引脚
tx_pin = machine.Pin(0, machine.Pin.OUT)
rx_pin = machine.Pin(1, machine.Pin.IN)
# 初始化 TX 引脚为高电平(空闲状态)
tx_pin.value(1)
def uart_send_byte(byte):
# 发送起始位(低电平)
tx_pin.value(0)
utime.sleep_us(bit_time)
# 发送数据位(LSB first)
for i in range(8):
tx_pin.value((byte >> i) & 1)
utime.sleep_us(bit_time)
# 发送停止位(高电平)
tx_pin.value(1)
utime.sleep_us(bit_time)
def uart_receive_byte():
# 等待起始位(低电平)
while rx_pin.value() == 1:
utime.sleep_us(2)
# 等待半个位时间,进入数据位的中间
utime.sleep_us(832 + 416)
# 接收数据位
byte = 0
for i in range(8):
if rx_pin.value():
byte |= (1 << i)
utime.sleep_us(bit_time)
# 等待停止位
#utime.sleep_us(bit_time)
return byte
# UART Echo 功能实现
while True:
byte = uart_receive_byte()
print("Received:", chr(byte)) # 打印接收到的数据
uart_send_byte(byte) # 回显接收到的数据