赞
踩
关于树莓派Pico里的PIO(Programmed I/O)编程,琢磨起来上瘾了,连续写了5篇文章:
看到网上有人用PIO写了DHT11/DHT22的温湿度传感器的程序,就下载下来研究了一番,国外论坛讨论的原贴在这里。
我复制ashchap的代码,稍微修改后:
import machine import utime import rp2 @rp2.asm_pio(set_init=rp2.PIO.OUT_HIGH, autopush=True, push_thresh=8) def DHT11(): # 给DHT发送开始信号 set(pindirs, 1) # 电平拉低20毫秒 set(pins, 0) set(y, 31) # 32*32*32*0.625 = 20480 > 20 ms label ('loop_y') set(x, 31) label ('loop_x') nop() [31] jmp(x_dec, 'loop_x') jmp(y_dec, 'loop_y') set(pindirs, 0) # 从DHT读数据 wait(1, pin, 0) wait(0, pin, 0) wait(1, pin, 0) wait(0, pin, 0) label('readdata') set(x, 20) #reset x register to count down from 20 wait(1, pin, 0) #wait for high signal label('countdown') jmp(pin,'continue') #if pin still high continue counting #pin is low before countdown is complete - bit '0' detected set(y, 0) in_(y, 1) #shift '0' into the isr jmp('readdata') #read the next bit label('continue') jmp(x_dec,'countdown') #decrement x reg and continue counting if x!=0 #pin is still high after countdown complete - bit '1' detected set(y,1) in_(y, 1) #shift one bit into the isr wait(0,pin,0) #wait for low signal (next bit) jmp('readdata') #read the next bit dht_data = machine.Pin(26) # DHT11的数据针 while True: data=[] total=0 sm=rp2.StateMachine(0, DHT11, freq=1_600_000, set_base=dht_data, in_base=dht_data, jmp_pin=dht_data) sm.active(1) #state machine frequency adjusted so that PIO countdown during 'readdata' ends somewhere between the #duration of a '0' and a '1' high signal for i in range(5): #data should be 40 bits (5 bytes) long data.append(sm.get()) #read byte print("data: " + str(data)) #check checksum (lowest 8 bits of the sum of the first 4 bytes) for i in range(4): total=total+data[i] if((total & 255) == data[4]): humidity=data[0] #DHT11 provides integer humidity (no decimal part) temperature=(1-2*(data[2] >> 7) )*(data[2] & 0x7f) #DHT11 provides signed integer temperature (no decimal part) print("Humidity: %d%%, Temp: %dC" % (humidity, temperature)) else: print("Checksum: failed") utime.sleep_ms(1000)
先了解一下DHT11的数据传输协议,要能够看懂下面这个信号时序图。
主机(单片机Pico)发送开始信号,set(pindirs, 1) 表示DATA信号线是输出状态,Pico向DHT输出,set(pins, 0) 拉低电平,后面的x,y的循环要保证延迟20毫秒。
状态机的频率为1.6M Hz,这样一个指令周期为1/1,600,600 = 0.625us(微秒),pioasm程序里的循环 323232*0.625 = 20480 us > 20 ms,其实jmp指令也占用时间,肯定会超过20ms。
然后,执行 set(pindirs, 0) 变为输入状态,即Pico从DHT读取数据,好像这个时候DATA会变为高电位,开始等待DHT的数据信号。
后面紧跟着4行wait(1, pins, 0), wait(0, pins, 0),我在下图标出了相应位置。实际上第一个wait(1, pins, 0)不写逻辑也没问题。
再后面的代码写得就比较啰嗦了,主要用于判断数据位0或1,DHT协议按高电位的持续时间来表示0或1,如下图。
有人改进了这段代码,但把状态机的工作频率设定为500K Hz:
@rp2.asm_pio(set_init=rp2.PIO.OUT_HIGH, autopush=True, push_thresh=8) def DHT11(): # 状态机要用 500K hz的频率 # 给DHT发送信号,保持低电位至少20毫秒 set(pindirs, 1) set(pins, 0) set(y, 9) # 延迟20毫秒 label ('outer_20ms') # (9+1) * (31+1) * (31+1) / 500kHz = 20.48 ms > 20 ms set(x, 31) label ('inner_20ms') nop() [31] jmp(x_dec, 'inner_20ms') jmp(y_dec, 'outer_20ms') # Look for the device response (~90us low pulse) # Add some delay to 'debounce' edges set(pindirs, 0) wait(1, pin, 0) [1] # Wait for input to release (high pulse ~12us) wait(0, pin, 0) [1] # Wait for device to drive low (low pulse ~83us) wait(1, pin, 0) [1] # Wait for device to release line (high pulse ~86us) wrap_target() # Read data bits from device # (once 40 bits have been read code will block here with input permanently high) wait(0,pin,0) [1] # Wait for start of data + debounce (low pulse ~54us) wait(1,pin,0) [24] # Wait for high signal and then 50us # After 50us input will indicate bit value (high pulse zero ~23us, one ~70us) in_(pins, 1) # Shift the data bit into the isr wrap() # Loop to read the remaining bits from device
实际上,那个无限循环也有点多余,我们只需要读40位的数据,也不用频繁地重启状态机,我用sm.put()后,给DHT发送开始信号,取出40位数据之后,就一直阻塞等待着。
40位(5个字节)数据分别代表湿度、温度的整数、小数部分,第5个字节是校验数据,最开始的处理40位数据的代码有点逻辑不清楚,也一并修改了。
最后的代码:
import machine import utime import rp2 @rp2.asm_pio(set_init=rp2.PIO.OUT_HIGH, autopush=True, push_thresh=8) def DHT11(): pull(block) # 状态机要用 500K hz的频率 # 给DHT发送信号,保持低电位至少20毫秒 set(pindirs, 1) set(pins, 0) mov(y, osr) #如果y为10000,则10000/500k = 20 ms label ('loop_20ms') jmp(y_dec, 'loop_20ms') # Look for the device response (~90us low pulse) # Add some delay to 'debounce' edges set(pindirs, 0) wait(1, pin, 0) [1] # Wait for input to release (high pulse ~12us) wait(0, pin, 0) [1] # Wait for device to drive low (low pulse ~83us) wait(1, pin, 0) [1] # Wait for device to release line (high pulse ~86us) set(y, 4) label('read_5bytes') set(x, 7) label('read_8bits') # (once 40 bits have been read code will block here with input permanently high) wait(0, pin, 0) [1] # Wait for start of data + debounce (low pulse ~54us) wait(1, pin, 0) [24] # Wait for high signal and then 50us # After 50us input will indicate bit value (high pulse zero ~23us, one ~70us) in_(pins, 1) # Shift the data bit into the isr jmp(x_dec, 'read_8bits') jmp(y_dec, 'read_5bytes') dht_data = machine.Pin(26) sm = rp2.StateMachine(0, DHT11, freq=500_000, set_base=dht_data, in_base=dht_data, jmp_pin=dht_data) sm.active(1) while True: sm.put(20 * 500) #持续20 ms的低电位信号 data=[] for i in range(5): #读40位(5字节)的数据 data.append(sm.get()) print("data: " + str(data)) checksum = (data[0] + data[1] + data[2] + data[3]) & 0xFF; if(data[4] == checksum): humidity = data[0] + data[1] / 10 temperature = data[2] + data[3] / 10 #这个代码不严谨,data[2]是有符号的整数,负数代表零下温度 print("湿度:{:6.1f}%,温度:{:6.1f} ℃".format(humidity, temperature)) else: print("Checksum: failed") utime.sleep(1)
这里sm.put(10000)是为了延迟20ms的低电位,我试着调小了这个数值,传感器仍能正常工作。
参考:
https://forums.raspberrypi.com/viewtopic.php?t=303606
推荐阅读:
树莓派Pico开发系列文章
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。