O Solucionática modificou um sketch encontrado aqui. O excelente programa foi criado por Riva para uma Estação Meteorológica genérica e pelo que diz o autor não foi testado "no mundo real". Com as modificações de autoria do Solucionática, foi possível ler os dados reais dos sensores da Estação Auriol. Notem que a primeira modificação foi feita na parte //Constantes, alterando-se a duração mínima e máxima dos pulsos. Essa duração varia entre os modelo de estação, porém, os protocolos OOK (on-off keying) usados tem em comum um pulso de sincronismo com a duração de pelo menos duas vezes o pulso correspondente ao bit "1" e quatro vezes a duração do bit "zero". Essa proporção não deve ser seguida à risca, pois cada caso é um caso!
No nosso exemplo, a estação tem os seguintes parâmetros: bit de sincronismo (SYNC) de 9ms; bit "1" (ONE_BIT) de 4ms e bit "0" (ZERO_BIT) de 2ms. Aplicamos uma variação de +- 50%, para garantir a recepção dos dados mesmo quando há alteração na duração dos bits.
Outra importante modificação diz respeito à quantidade de bits do "quadro" de transmissão dos sensores. Cada estação tem uma quantidade determinadas de bits. No caso do sketch original do Riva, a estação genérica tem 42 bits. Na nossa estação real são 36 bits, sendo 8 bits usados para a identificação do sensor (PREAMBULE) e os 28 bits restantes são o "payload", ou seja, os dados propriamente ditos.
Finalmente, para que as informações correspondessem aos campos corretos, também foram introduzidas modificações no processamento desses dados, de modo que os bits correspondentes aos valores de temperatura e umidade fossem corretamente lidos. Uma dessas modificações foi o "espelhamento" dos nibbles (1/2 byte), ou seja, os bits menos significativos passaram a ocupar a posição dos bits mais significativos correspondentes. Essa explicação vai ficar para outro dia, pois requer um pouquinho mais de paciência. Analisando o sketch original com mais atenção, dá para perceber as alterações e assim deduzi-las para o seu próprio uso.
E o hardware? Como receber esses pulsos? São sinais de rádio?
O circuito decodificador usa um simples receptor RF de 433MHz, o mesmo usado nos portões automáticos.
AVISO LEGAL: o presente material não pode ser usado para fins ilícitos. O Arduino Solucionática não se responsabiliza por tais atos e não encoraja nenhum tipo de ilegalidade perpetrada por qualquer pessoa que se utilize das informações aqui contidas. O uso indevido de sinais de RF nessa faixa de frequência pode interferir em equipamentos residenciais e comerciais.
A faixa de 433MHz é regulada pela ANATEL, conforme Anexo da Resolução n° 365 (http://www.anatel.gov.br/Portal/verificaDocumentos/documento.asp?null&filtro=1&documentoPath=biblioteca/resolucao/2004/Anexo_res_365_2004.pdf):
§ 5º
A utilização da faixa de radiofreqüências de 433 MHz a 435 MHz por equipamentos de
radiação restrita em áreas internas de edificações poderá ser feita com potência irradiada limitada ao valor
máximo de 10 mW (e.i.r.p).
Logo, não é permitido o uso externo de sinais de rádio nessa faixa de frequência. A faixa "livre" é a de 2,4 GHz.
Segue abaixo o sketch modificado. Há alguns comentários nossos, entre os do autor original.
// Unknown 433Mhz weather sensor decoder. Untested in the real world. Now tested by Arduino Solucionatica
// http://arduino.cc/forum/index.php/topic,142871.msg1106336.html#msg1106336
// __ ___ ___ ___
// | | | | | | |
// |_________| |______| |___| |
//
// | Sync | 1 | 0 |
// | 8320us | 4500us | 2530us
// Defines
#define allDataBits 36 // Number of data bits to expect
// isrFlags bit numbers
#define F_HAVE_DATA 1 // 0=Nothing in read buffer, 1=Data in read buffer
#define F_GOOD_DATA 2 // 0=Unverified data, 1=Verified (2 consecutive matching reads)
#define F_CARRY_BIT 3 // Bit used to carry over bit shift from one long to the other
#define F_STATE 7 // 0=Sync mode, 1=Data mode
// Constants
const unsigned long sync_MIN = 8000; // Minimum Sync time in micro seconds
const unsigned long sync_MAX = 10000;
const unsigned long bit1_MIN = 3000;
const unsigned long bit1_MAX = 5000;
const unsigned long bit0_MIN = 1000;
const unsigned long bit0_MAX = 2800;
const unsigned long glitch_Length = 300; // Anything below this value is a glitch and will be ignored.
// Interrupt variables
unsigned long fall_Time = 0; // Placeholder for microsecond time when last falling edge occured.
unsigned long rise_Time = 0; // Placeholder for microsecond time when last rising edge occured.
byte bit_Count = 0; // Bit counter for received bits.
unsigned long build_Buffer[] = {0,0}; // Placeholder last data packet being received.
volatile unsigned long read_Buffer[] = {0,0}; // Placeholder last full data packet read.
volatile byte isrFlags = 0; // Various flag bits
void PinChangeISR0(){ // Pin 2 (Interrupt 0) service routine
unsigned long Time = micros(); // Get current time
if (digitalRead(2) == LOW) {
// Falling edge
if (Time > (rise_Time + glitch_Length)) {
// Not a glitch
Time = micros() - fall_Time; // Subtract last falling edge to get pulse time.
if (bitRead(build_Buffer[1],31) == 1)
bitSet(isrFlags, F_CARRY_BIT);
else
bitClear(isrFlags, F_CARRY_BIT);
if (bitRead(isrFlags, F_STATE) == 1) {
// Looking for Data
if ((Time > bit0_MIN) && (Time < bit0_MAX)) {
// 0 bit
build_Buffer[1] = build_Buffer[1] << 1;
build_Buffer[0] = build_Buffer[0] << 1;
if (bitRead(isrFlags,F_CARRY_BIT) == 1)
bitSet(build_Buffer[0],0);
bit_Count++;
}
else if ((Time > bit1_MIN) && (Time < bit1_MAX)) {
// 1 bit
build_Buffer[1] = build_Buffer[1] << 1;
bitSet(build_Buffer[1],0);
build_Buffer[0] = build_Buffer[0] << 1;
if (bitRead(isrFlags,F_CARRY_BIT) == 1)
bitSet(build_Buffer[0],0);
bit_Count++;
}
else {
// Not a 0 or 1 bit so restart data build and check if it's a sync?
bit_Count = 0;
build_Buffer[0] = 0;
build_Buffer[1] = 0;
bitClear(isrFlags, F_GOOD_DATA); // Signal data reads dont' match
bitClear(isrFlags, F_STATE); // Set looking for Sync mode
if ((Time > sync_MIN) && (Time < sync_MAX)) {
// Sync length okay
bitSet(isrFlags, F_STATE); // Set data mode
}
}
if (bit_Count >= allDataBits) {
// All bits arrived
bitClear(isrFlags, F_GOOD_DATA); // Assume data reads don't match
if (build_Buffer[0] == read_Buffer[0]) {
if (build_Buffer[1] == read_Buffer[1])
bitSet(isrFlags, F_GOOD_DATA); // Set data reads match
}
read_Buffer[0] = build_Buffer[0];
read_Buffer[1] = build_Buffer[1];
bitSet(isrFlags, F_HAVE_DATA); // Set data available
bitClear(isrFlags, F_STATE); // Set looking for Sync mode
digitalWrite(13,HIGH); // Used for debugging
build_Buffer[0] = 0;
build_Buffer[1] = 0;
bit_Count = 0;
}
}
else {
// Looking for sync
if ((Time > sync_MIN) && (Time < sync_MAX)) {
// Sync length okay
build_Buffer[0] = 0;
build_Buffer[1] = 0;
bit_Count = 0;
bitSet(isrFlags, F_STATE); // Set data mode
digitalWrite(13,LOW); // Used for debugging
}
}
fall_Time = micros(); // Store fall time
}
}
else {
// Rising edge
if (Time > (fall_Time + glitch_Length)) {
// Not a glitch
rise_Time = Time; // Store rise time
}
}
}
void setup() {
pinMode(13,OUTPUT); // Used for debugging
Serial.begin(115200);
pinMode(2,INPUT);
Serial.println(F("ISR Pin 2 Configured For Input."));
attachInterrupt(0,PinChangeISR0,CHANGE);
Serial.println(F("Pin 2 ISR Function Attached. Here we go."));
}
void loop() {
unsigned long myData0 = 0;
unsigned long myData1 = 0;
if (bitRead(isrFlags,F_GOOD_DATA) == 1) {
// We have at least 2 consecutive matching reads
myData0 = read_Buffer[0]; // Read the data spread over 2x 32 variables
myData1 = read_Buffer[1];
bitClear(isrFlags,F_HAVE_DATA); // Flag we have read the data
dec2binLong(myData0,8);
dec2binLong(myData1,28);
//if (Serial.available()>0)
Serial.print(" - Bateria=");
byte H = (myData1 >> 24) & 0x8; // Get Battery
Serial.print(H);
Serial.print(" Canal=");
H = (myData1 >> 25) & 0x3; // Get Channel
Serial.print(H);
Serial.print(" Temperatura=");
byte ML = (myData1 >> 16) & 0xF; // Get MMMM
ML=ML<<4;
H = (myData1 >> 12) & 0xF; // Get LLLL
H=H<<4;
// OR MMMM & LLLL nibbles together
byte TL=(myData1>>20) & 0xF; //desloca os 36 bits de dados 20 bits à direita e extrai os quatro bits mais significativos *****
TL=TL<<4;
byte TL0=(TL>>1) & 0x8;
byte TL1=(TL>>3) & 0x4;
byte TL2=(TL>>5) & 0x2;
byte TL3=(TL>>7) & 0x1;
byte TLL=TL0+TL1+TL2+TL3;
byte TM0=(ML>>7) & 0x1;
byte TM1=(ML>>5) & 0x2;
byte TM2=(ML>>3) & 0x4;
byte TM3=(ML>>1) & 0x8;
byte TM=TM0+TM1+TM2+TM3;
byte TH0=(H>>1) & 0x8;
byte TH1=(H>>3) & 0x4;
byte TH2=(H>>5) & 0x2;
byte TH3=(H>>7) & 0x1;
byte TH=TH0+TH1+TH2+TH3; //TH0 indica se a temperatura é positiva (TH0=0) ou negativa (TH0=1).
int Temperature = TH*256+TM*16+TLL; // conversão de hexadecimal para decimal.
Serial.print(Temperature/10.0,1); //imprime na tela a Temperatura em Celcius.
// Aqui começa o cálculo da umidade.
Serial.print("C Umidade=");
H = (myData1 >> 4) & 0xF; // Desloca os 36 bits de dados quatro bits à direita e extrai os 4 bits mais significativos (bits 13 a 16)
ML = (myData1 >> 4) & 0xF0; // Desloca os 36 bits de dados quatro bits à direita e extrai os 4 bits menos significativos (bits 13 a 16)
byte L0=(H>>3) & 0x1; // Põe os bits na ordem certa (B16 B15 B14 B13) e com um truque, troca a ordem dos bits do byte H para L3L2L1L0, para corrigir a posição desses bits.
byte L1=(H>>1) & 0x2;
byte L2=(H<<1) & 0x4;
byte L3=(H<<3) & 0x8;
byte L=L0+L1+L2+L3; // Restaura o nibble correspondente ao LSB da umidade (que indica a uni.
byte M0=(ML>>7) & 0x1; //Põe os bits na ordem certa (B12 B11 B10 B9) e com um truque, troca a ordem dos bits do byte ML para M3M2M1M0, para também corrigir a posição desses bits.
byte M1=(ML>>5) & 0x2;
byte M2=(ML>>3) & 0x4;
byte M3=(ML>>1) & 0x8;
byte M=M0+M1+M2+M3;// Restaura o nibble correspondente ao MSB da umidade.
Serial.print(L); // envia pela serial a dezena da medida da umidade.
Serial.print(M); //envia pela serial a unidade da medida da umidade.
Serial.println("%");
}
delay(50);
}
// essa última rotina imprime os 36 bits do pacote de dados, como uma sequencia de "0's" e"1's".
void dec2binLong(unsigned long myNum, byte NumberOfBits) {
if (NumberOfBits <= 32){
myNum = myNum << (32 - NumberOfBits);
for (int i=0; i<NumberOfBits; i++) {
if (bitRead(myNum,31) == 1)
Serial.print("1");
else
Serial.print("0");
myNum = myNum << 1;
}
}
}
ISR Pin 2 Configured For Input.
Pin 2 ISR Function Attached. Here we go.
000000010110110000000000000000000001 - Bateria=0 Canal=3 Temperatura=0.3C Umidade=00%
000000010110110000000000000000000001 - Bateria=0 Canal=3 Temperatura=0.3C Umidade=00%
000000010110110000000000000000000001 - Bateria=0 Canal=3 Temperatura=0.3C Umidade=00%
000000010110110000000000000000000001 - Bateria=0 Canal=3 Temperatura=0.3C Umidade=00%
000001010000010101001000010000100011 - Bateria=0 Canal=0 Temperatura=29.8C Umidade=42%
000001010000010101001000010000100011 - Bateria=0 Canal=0 Temperatura=29.8C Umidade=42%
000001010000010101001000010000100011 - Bateria=0 Canal=0 Temperatura=29.8C Umidade=42%
000001010000010101001000010000100011 - Bateria=0 Canal=0 Temperatura=29.8C Umidade=42%
000001010000010101001000010000100011 - Bateria=0 Canal=0 Temperatura=29.8C Umidade=42%
000001010000010101001000010000100011 - Bateria=0 Canal=0 Temperatura=29.8C Umidade=42%
000001010000010101001000010000100011 - Bateria=0 Canal=0 Temperatura=29.8C Umidade=42%
000000010110110000000000000000000001 - Bateria=0 Canal=3 Temperatura=0.3C Umidade=00%
000000010110110000000000000000000001 - Bateria=0 Canal=3 Temperatura=0.3C Umidade=00%
000000010110110000000000000000000001 - Bateria=0 Canal=3 Temperatura=0.3C Umidade=00%
000000010110110000000000000000000001 - Bateria=0 Canal=3 Temperatura=0.3C Umidade=00%
000000010110110000000000000000000001 - Bateria=0 Canal=3 Temperatura=0.3C Umidade=00%
000000010110110000000000000000000001 - Bateria=0 Canal=3 Temperatura=0.3C Umidade=00%
000000010110110000000000000000000001 - Bateria=0 Canal=3 Temperatura=0.3C Umidade=00%
000000010110110000000000000000000001 - Bateria=0 Canal=3 Temperatura=0.3C Umidade=00%
No exemplo acima, vemos claramente que há duas informações distintas sendo recebidas. Uma no Canal=3 diz respeito à informação do pluviômetro ( intensidade de chuva). O Canal=0 informa os dados de temperatura e umidade. O nosso próximo passo é decodificar as informações do Canal=3. Quem diferencia uma informação da outra são os bits 9 e 10 (contando da esquerda para direita). Quando esses bits são ambos iguais a "1", trata-se de dados distintos de temperatura e umidade, ou seja, informações do pluviômetro ou do anemômetro. Mais adiante voltaremos a explicar melhor esses bits.
Os oito primeiros bits (PREAMBULE), por sua vez, já mostram que os dados vem de outro sensor. O PREAMBULE da unidade que contém o termômetro, o higrômetro e o anemômetro é 00000101.
O pluviômetro, por sua vez, é uma unidade separada e é identificada pelo PREAMBULE 00000001. Note-se que o PREAMBULE, teoricamente, é um número aleatório, ou seja, a cada troca de bateria esse número muda. Nos nossos testes não verificamos isso, pelo contrário, o PREAMBULE tem se mantido constante, tanto no pluviômetro quanto na outra unidade.
As tabelas abaixo explicitam cada bit do quadro transmitido pelos diferentes sensores.
A primeira tabela mostra a formatação do quadro geral:
r0/r7 - PREÂMBULO
v - Estado da bateria:
v = 0 : Sensor de voltagem da bateria normal;
v = 1 :Voltagem da bateria abaixo de 2,6V.
(-) PAYLOAD
A segunda tabela contém as informações do sensor combinado de temperatura e umidade:
O random id é a identidade do sensor. Podemos ter, então, um monte de sensores transmitindo simultâneamente para o nosso Arduino decodificar? Não é tão direta a resposta. Em teoria dá pra ter 2^8 = 256 sensores, mas o bit r4 parece ser constante = 0, logo as possibilidades se reduzem a 128. Além disso, também teoricamente, como já dissemos, trata-se de um número aleatório (randômico).
Em um outro dia, faremos um módulo universal de transmissão, ou seja, um circuito que emula o protocolo de transmissão, podendo variar o id e assim verificar quantos sensores podem trabalhar em conjunto sem que haja interferência entre eles. Ou seja, há que se respeitar uma escala de transmissão para cada sensor, de modo que o sensor de temperatura/umidade 1 transmita em um intervalo diferente do sensor de temperatura/umidade 2. E assim por diante. Aguardem.
Antes, porém, vamos entender a tabela acima:
O bit v tem a mesma função de indicar o estado da bateria dos respectivos sensores. X é a combinação dos bits x0 e x1, de acordo com a seguinte tabela-verdade:
Assim, quando x0=x1=1 os valores lidos não correspondem aos dados dos sensores de temperatura e umidade. É preciso comentar, que no caso da estação Auriol comprada pela equipe do Solucionática, temos um sensor combinado com 5 variáveis lidas: temperatura, umidade, pressão atmosférica, velocidade do vento e direção do vento.
Que boa notícia para a nossa equipe de software! Temos três possibilidades de acrescentar sensores de temperatura, aproveitando as três primeiras combinações dos bits x0 e x1, sem a necessidade de programar um id para cada sensor. Afinal, no nosso manifesto fica claro que quanto mais simples a solução, melhor. Trabalhar com dois bits é mais simples do que com 8!