Sensores: do número cru ao valor
escala, tensão, TMP36, NTC, divisor, média móvel e histerese — a matemática que todo projeto reescreve errado.
Um pino analógico devolve um número de 0 a 1023. Ele não é temperatura, nem luz, nem umidade: é o quanto da tensão de referência chegou ali. Transformar isso em grandeza é a parte que todo projeto refaz — e onde os mesmos quatro erros acontecem.
escala — o `map` que limita#
adopt Arcane.IoT as IoT
assert IoT.escala(512, 0, 1023, 0, 100) > 50.0 and IoT.escala(512, 0, 1023, 0, 100) < 50.1
assert IoT.escala(2000, 0, 1023, 0, 100) is 100 // limita, por padrão
assert IoT.escala(-5, 0, 1023, 0, 100) is 0
assert IoT.escala(2000, 0, 1023, 0, 100, no) > 100 // sem limitar, extrapolatensão — a conta que depende da placa#
adopt Arcane.IoT as IoT
assert IoT.tensao(1023) is 5.0 // UNO
assert round(IoT.tensao(2048, 3.3, 12), 2) is 1.65 // ESP32: 12 bitsTMP36 — o sensor de três pernas#
A folha de dados diz: 750 mV a 25 °C, 10 mV por grau. É uma reta, e a conta é uma linha — mas o deslocamento de 500 mV é o que quase todo mundo esquece:
adopt Arcane.IoT as IoT
leitura := 0.75 / 5.0 * 1023 // 750 mV num ADC de 10 bits
temperatura := IoT.tmp36(leitura)
assert temperatura > 24.5 and temperatura < 25.5
out $"{round(temperatura, 1)} °C"NTC — o termistor, e a equação B#
adopt Arcane.IoT as IoT
// com o termistor na resistência nominal, o divisor lê a metade da escala
t := IoT.ntc(1023 / 2)
assert t > 24.7 and t < 25.3
// e a ponta da escala não é temperatura: é fio solto ou curto
monitor:
IoT.ntc(0)
handle Error as e:
out e.message // …ponta da escala…Recusar as pontas é o ponto: com a leitura em 0 ou 1023 a equação divide por zero ou tira log de zero, e o que sai é um número absurdo com cara de temperatura. Um termostato que vê −273 °C liga o aquecedor e não desliga mais.
divisor — quando o sensor é uma resistência#
adopt Arcane.IoT as IoT
// LDR, sensor de umidade de solo, potenciômetro: tudo vira resistência
r := IoT.divisor(1023 / 2)
assert r > 9900 and r < 10100
out $"{round(r)} Ω"média móvel — o ruído do ADC#
Uma leitura pura treme: 512, 509, 514, 511. Num gráfico isso é grama; num controle, é o relé batendo. A média móvel é a resposta mais barata, e ela esquece:
adopt Arcane.IoT as IoT
media := IoT.media_movel(3)
assert media(10) is 10
assert media(20) is 15
assert media(30) is 20
assert media(40) is 30 // o 10 saiu da janelahisterese — o relé que não bate#
Ligar acima de 30 e desligar abaixo de 30 faz o relé chavear dezenas de vezes por minuto quando a temperatura fica em 30 — e é assim que um contato se queima. A saída é ter duas soleiras:
adopt Arcane.IoT as IoT
ventoinha := IoT.histerese(30, 28) // liga em 30, só desliga em 28
assert ventoinha(29) is no // subindo, ainda não
assert ventoinha(31) is yes
assert ventoinha(29) is yes // no meio, segura o estado
assert ventoinha(27) is noInvertendo as duas, ela liga abaixo — é o umidificador, o aquecedor, a bomba:
adopt Arcane.IoT as IoT
umidificador := IoT.histerese(40, 50) // liga abaixo de 40
assert umidificador(45) is no
assert umidificador(35) is yes
assert umidificador(45) is yes
assert umidificador(55) is noadopt Arcane.IoT as IoT
monitor:
IoT.histerese(30, 30) // duas soleiras iguais são uma só
handle Error as e:
out e.messageOs quatro juntos, num sensor de verdade#
adopt Arcane.IoT as IoT
placa := IoT.conectar_simulada("uno")
placa.modo(14, "analogico")
placa.relatar_analogico(0)
placa.amostragem(20)
suavizar := IoT.media_movel(5)
aquecedor := IoT.histerese(18, 21) // liga abaixo de 18, desliga em 21
placa.modo(7, "saida")
placa.simulador.definir_analogico(0, 123) // ~10 °C
sleep(120)
graus := IoT.tmp36(suavizar(placa.analogico(0)))
ligado := aquecedor(graus)
placa.escrever(7, ligado)
out $"{round(graus, 1)} °C — aquecedor {"ligado" given ligado otherwise "desligado"}"
assert ligado is yes
placa.fechar()