À medida em que vou entendendo o funcionamento do PIO (Programmable I/O) do Raspberry Pi Pico, mais oportunidades de usá-lo eu vou enxergando. O dispositvo da vez é o sensor de distância HC-SR04, cujo funcionamento já examinamos oito anos atrás (!).
Resumindo para quem teve preguiça de clicar no link, o HC-SR04 emite um sinal ultrassônico quando colocamos um pulso de 10us no pino "trigger" e depois mantêm o pino "echo" em nível alto até receber o eco do sinal (ou passar o tempo máximo de 38ms). Estes valores eu obtive em um datasheet da Elec Freaks, já encontrei alguns códigos que usam valores diferentes.
Antes de olhar o software, vamos ver a conexão ao PiPico. São apenas 4 sinais:
- Vcc: o datasheet fala 5V. Já vi descrições ligando a 3,3V, para mim não funcionou direito. Como estou alimentando o PiPico pela USB, liguei o VCC ao VBUS.
- GND: ligando ao GND do PiPico.
- Trigger: o datasheet fala que é um sinal TTL de entrada, portanto pode ser ligado direto a um pino do PiPico. No meu teste usei o pino 3.
- Echo: outro sinal TTL, mas é um sinal de saída. Alimentado a 5V, este sinal poderá ficar acima dos 3,3V e queimar o PiPico. A solução é o tradicional divisor resistivo: o sinal vai para um resistor de 22K cuja outra ponta vai para o pino 3 do PiPico e um resistor de 33K com a outra ponta em terra.
No Arduino, costuma-se usar a função pulseIn para medir o pulso do sinal echo. Os exemplos que eu achei em MicroPython usam utime.ticks_us() e um loop. O resultado parece bom, mas (a) o processador fica preso no loop e (b) interrupções podem interferir na medida. Usando o PIO a medição roda independente do processador. Vamos aproveitar e usar o PIO também para gerar o pulso de trigger.
Minha primeira decisão foi qual clock a usar no PIO. Vamos querer medir um tempo entre 150us e 38ms, para um resolução razoável resolvi adotar um tempo de ciclo de meio microssegundo. Ou seja, um clock de 2MHz no PIO.
O programa em si não é complexo, mas existem algumas peculiaridades nas instruções do PIO:
- Um registrador só pode ser iniciado por programa com um valor de 0 a 31. O jeito é receber o valor inicial do contador pela fila de transmissão e movê-lo para o registrador que fara a contagem (PULL, MOV X,OSR)
- A instrução WAIT não tem timeout. Se não tiver um sensor ligado, o programa vai ficar parado no WAIT do início do pulso. O jeito seria fazer a espera "na unha" (como é feito na espera do fim do pulso). Fica como exercício para o leitor.
- A instrução de desvio (JMP) só tem a opção de desviar se um pino estiver em nível alto. Como estamos esperando o pino retornar ao nível baixo, o código fica meio esquisito.
- Para fazer um loop com contador a instrução JMP oferece somente o desvio quando o contador for diferente de zero, com pós decremento. Ou seja se o contador for zero segue em frente, senão decrementa e desvia. Meio estranho, mas se encaixa bem neste caso.
O resultado final é que o programa do PIO recebe um valor de timeout (usei 300000 ciclos, o que equivale a 150ms) e retorna quanto sobrou ao final do pulso. Aí é calcular quantos ciclos demorou para o sinal baixar, converter em microssegundos e determinar a distância (usando a velocidade de 340m/s e lembrando que o tempo é para ida e volta).
Segue o programa completo:
import utime import rp2 from rp2 import PIO, asm_pio from machine import Pin # Programa para o PIO @asm_pio(set_init=rp2.PIO.OUT_LOW,autopush=False) def ULTRA_PIO(): # aguarda uma solicitação do programa pull() mov (x,osr) # gera um pulso de 10 us (20 ciclos) set(pins,1) [19] set(pins,0) # aguarda (indefinidamente) o inicio do pulso de eco wait(1,pin,0) # aguarda o fim do pulso de eco # decrementa X a cada 2 ciclos (1us) label('espera') jmp(pin,'continue') jmp('fim') label('continue') jmp(x_dec,'espera') # retorna a duração do pulso de eco label('fim') mov(isr,x) push() trigger = Pin(3, Pin.OUT) echo = Pin(2, Pin.IN) sm = rp2.StateMachine(0) while True: sm.init(ULTRA_PIO,freq=2000000,set_base=trigger,in_base=echo,jmp_pin=echo) sm.active(1) sm.put(300000) val = sm.get() sm.active(0) tempo = 300000 - val distancia = (tempo * 0.0343) / 2 print('Distancia = {0:.1f} cm'.format(distancia)) utime.sleep_ms(1000)
Um comentário:
Obrigado pelo conteudo!!
Fiz um teste do seu codigo, com duas maquinas de estado e dois sensores em um simulador online...
https://wokwi.com/projects/328389014755213906
Usei o clock da segunda SM 34000 qual o clock minimo na prática?
obs.. é o meu primeiro cod em python deve estar muito ruim...
Postar um comentário