Pular para o conteúdo

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#

dataforge
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, extrapola

tensão — a conta que depende da placa#

dataforge
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 bits

TMP36 — 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:

dataforge
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#

dataforge
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#

dataforge
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:

dataforge
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 janela

histerese — 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:

dataforge
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 no

Invertendo as duas, ela liga abaixo — é o umidificador, o aquecedor, a bomba:

dataforge
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 no
dataforge
adopt Arcane.IoT as IoT

monitor:
    IoT.histerese(30, 30)               // duas soleiras iguais são uma só
handle Error as e:
    out e.message

Os quatro juntos, num sensor de verdade#

dataforge
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()