Using an external wireless thermometer Buro H999 in conjunction with homemade devices

    All good weather station Buro H146G with an external wireless thermometer H999. But here only to see the readings on her black LCD display, good lighting is required. And I would be better if the output of temperature and humidity outside the window was displayed on fairly bright indicators (for example, by combining the display of temperature and humidity with the clock on the gas-discharge indicators IN-12). It is easy to make such an article, but you need to know the protocol of exchange with a wireless thermometer. There have already been articles on the use of a wireless thermometer weather stations to obtain temperature and humidity over the air. But for Buro stations, the exchange protocol has not yet been described. So, it is necessary to fix it: perhaps someone can come in handy.

    I did not find descriptions of the exchange protocol for BURO stations on the Internet. And this means that you have to open the protocol of the exchange of the wireless sensor.

    My external thermometer looks like this:



    By connecting the Chinese 433.92 MHz superregenerative receiver to the oscilloscope and pressing the TEST button on the thermometer, it was clearly visible how the transmission pulses were running. Well, since the frequency there is small, the output of the receiver was connected to the input of the sound card through a resistive divider. After processing the recorded sound file with a comparator, the following image was obtained:



    Like other weather stations, modulation is performed by changing the duty cycle. The transmission from the sync signal unit begins, then another sync signal goes, and then the data goes, followed by the final sync signal. Two zeros after the sync signal, apparently, are the identifier of the beginning of the data - in any case, I have never noticed their changes. Data with a start and end clock is duplicated six times. Data exchange is conducted by nibbles.

    For decoding, I decided to start receiving on the first clock signal and two zeros, and finish on the last clock signal.

    To decode such a signal, it is sufficient to calculate the duration between signal drops.

    For this I wrote a simple test program for the Atmega8 controller:

    Atmega8 software
    //----------------------------------------------------------------------------------------------------//библиотеки//----------------------------------------------------------------------------------------------------#include<avr/io.h>#include<util/delay.h>#include<string.h>#include<stdlib.h>#include<stdbool.h>#include<stdio.h>#include<avr/interrupt.h>#include<avr/pgmspace.h>#include<string.h>#include<stdbool.h>#include<stdint.h>//----------------------------------------------------------------------------------------------------//частота контроллера//----------------------------------------------------------------------------------------------------#define F_CPU 8000000UL//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//макроопределения//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//скорость передачи данных UART, бит/с#define UART_SPEED 9600UL//----------------------------------------------------------------------------------------------------//перечисления//----------------------------------------------------------------------------------------------------//тип блокаenum BLOCK_TYPE
    {
     BLOCK_TYPE_UNKNOW,//неизвестный блок
     BLOCK_TYPE_DIVIDER,//разделитель
     BLOCK_TYPE_SYNCHRO,//синхросигнал
     BLOCK_TYPE_ONE,//единица
     BLOCK_TYPE_ZERO//ноль
    };
    //режим декодированияenum MODE
    {
     MODE_WAIT_SYNCHRO,//ожидание синхросигнала
     MODE_WAIT_ZERO_FIRST,//ожидание первого нуля
     MODE_WAIT_ZERO_SECOND,//ожидание второго нуля
     MODE_RECEIVE_DATA//приём данных
    }; 
    //----------------------------------------------------------------------------------------------------//глобальные переменные//----------------------------------------------------------------------------------------------------staticconstuint16_t MAX_TIMER_INTERVAL_VALUE=0xFFFF;//максимальное значение интервала таймераstaticvolatilebool TimerOverflow=false;//было ли переполнение таймераstaticuint8_t Buffer[20];//буфер сборки полубайтаstaticuint8_t BitSize=0;//количество принятых битstaticuint8_t Byte=0;//собираемый байт//----------------------------------------------------------------------------------------------------//прототипы функций//----------------------------------------------------------------------------------------------------voidInitAVR(void);//инициализация контроллераvoidUART_Write(unsignedchar byte);//передача символа в COM-портvoidSendText(constchar *text);//отправить текст в COM-портvoidRF_Init(void);//инициализацияvoidRF_SetTimerOverflow(void);//установить флаг переполнения таймераvoidRF_ResetTimerOverflow(void);//сбросить флаг переполнения таймераboolRF_IsTimerOverflow(void);//получить, есть ли переполнение таймераuint16_t RF_GetTimerValue(void);//получить значение таймераvoidRF_ResetTimerValue(void);//сбросить значение таймера BLOCK_TYPE RF_GetBlockType(uint32_t counter,bool value);//получить тип блокаvoidRF_AddBit(bool state);//добавить бит данныхvoidRF_ResetData(void);//начать сборку данных зановоvoidRF_AnalizeCounter(uint32_t counter,bool value,MODE &mode);//анализ блока//----------------------------------------------------------------------------------------------------//основная функция программы//----------------------------------------------------------------------------------------------------intmain(void){
     InitAVR();  
     _delay_ms(200); 
     SendText("Thermo unit\r\n");
     _delay_ms(200);  
     sei();
     while(1);
     cli();  
    }
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//общие функции//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//----------------------------------------------------------------------------------------------------//инициализация контроллера//----------------------------------------------------------------------------------------------------voidInitAVR(void){
     //настраиваем порты
     DDRB=0;
     DDRD=0;
     DDRC=0; 
     //задаём состояние портов
     PORTB=0;
     PORTD=0;
     PORTC=0;
     //устанавливаем режим передачи данных UART
     UCSRB=(1<<RXEN)|(1<<TXEN)|(0<<RXCIE);  
     //RXCIE=1 и прерывания разрешены (бит I=1 в регистре SREG) : прерывание по завершению приёма по UART разрешено//TXCIE=1 и прерывания разрешены (бит I=1 в регистре SREG) : прерывание по завершению передачи по UART разрешено//UDRIE=1 и прерывания разрешены (бит I=1 в регистре SREG) : прерывание по опустошению регистра данных UART разрешено//RXEN=1 : активация приёмника, вывод D0 становится входом UART.//TXEN=1 : активация передатчика, вывод D1 становится выходом UART.//CHR9=1 : длина передаваемой посылки с становится равной 11 бит (9 бит данных + старт-стоповый бит + стоп-бит).//RXB8-расширенный стоп-бит//TXB8-расширенный стоп-бит//вычисляем значение регистра скорости передачи данныхunsignedlong speed=F_CPU/(16UL);
     speed=(speed/UART_SPEED)-1UL;
     UBRRH=(speed>>8)&0xff;
     UBRRL=speed&0xFF;
     RF_Init();  
    }
    //----------------------------------------------------------------------------------------------------//передача символа в COM-порт//----------------------------------------------------------------------------------------------------voidUART_Write(unsignedchar byte){ 
     while(!(UCSRA&(1<<UDRE)));
     UDR=byte;
    }
    //----------------------------------------------------------------------------------------------------//отправить текст в COM-порт//----------------------------------------------------------------------------------------------------voidSendText(constchar *text){
     while((*text))
     {
      UART_Write(*text);
      text++;
     }
    }
    //----------------------------------------------------------------------------------------------------//инициализация//----------------------------------------------------------------------------------------------------voidRF_Init(void){
     //настраиваем аналоговый компаратор
     ACSR=(0<<ACD)|(1<<ACBG)|(0<<ACO)|(0<<ACI)|(1<<ACIE)|(0<<ACIC)|(0<<ACIS1)|(0<<ACIS0);
     //ACD - включение компаратора (0 - ВКЛЮЧЁН!)//ACBG - подключение к неинвертрирующему входу компаратора внутрреннего ИОН'а//ACO - результат сравнения (выход компаратора)//ACI - флаг прерывания от компаратора//ACIE - разрешение прерываний от компаратора//ACIC - подключение компаратора к схеме захвата таймера T1//ACIS1,ACID0 - условие генерации прерывания от компаратора//настраиваем таймер T1 на частоту 31250 Гц
     TCCR1A=(0<<WGM11)|(0<<WGM10)|(0<<COM1A1)|(0<<COM1A0)|(0<<COM1B1)|(0<<COM1B0);
     //COM1A1-COM1A0 - состояние вывода OC1A//COM1B1-COM1B0 - состояние вывода OC1B //WGM11-WGM10 - режим работы таймера
     TCCR1B=(0<<WGM13)|(0<<WGM12)|(1<<CS12)|(0<<CS11)|(0<<CS10)|(0<<ICES1)|(0<<ICNC1);
     //WGM13-WGM12 - режим работы таймера//CS12-CS10 - управление тактовым сигналом (выбран режим деления тактовых импульсов на 256 (частота таймера 31250 Гц))//ICNC1 - управление схемой подавления помех блока захвата//ICES1 - выбор активного фронта сигнала захвата
     TCNT1=0;//начальное значение таймера
     TIMSK|=(1<<TOIE1);//прерывание по переполнению таймера (таймер T1 шестнадцатибитный) 
    } 
    //----------------------------------------------------------------------------------------------------//установить флаг переполнения таймера//----------------------------------------------------------------------------------------------------voidRF_SetTimerOverflow(void){
     cli();
     TimerOverflow=true;
     sei();
    }
    //----------------------------------------------------------------------------------------------------//сбросить флаг переполнения таймера//----------------------------------------------------------------------------------------------------voidRF_ResetTimerOverflow(void){
     cli();
     TimerOverflow=false;
     sei();
    }
    //----------------------------------------------------------------------------------------------------//получить, есть ли переполнение таймера//----------------------------------------------------------------------------------------------------boolRF_IsTimerOverflow(void){
     cli();
     bool ret=TimerOverflow;
     sei();
     return(ret);
    }
    //----------------------------------------------------------------------------------------------------//получить значение таймера //----------------------------------------------------------------------------------------------------uint16_t RF_GetTimerValue(void)
    {
     cli();
     uint16_t ret=TCNT1;
     sei(); 
     return(ret);
    } 
    //----------------------------------------------------------------------------------------------------//сбросить значение таймера //----------------------------------------------------------------------------------------------------voidRF_ResetTimerValue(void){
     cli();
     TCNT1=0;
     sei();
     RF_ResetTimerOverflow();
    } 
    //----------------------------------------------------------------------------------------------------//получить тип блока//----------------------------------------------------------------------------------------------------BLOCK_TYPE RF_GetBlockType(uint32_t counter,bool value){ 
     staticconstuint32_t DIVIDER_MIN=(31250UL*12)/44100UL;
     staticconstuint32_t DIVIDER_MAX=(31250UL*25)/44100UL;
     staticconstuint32_t ZERO_MIN=(31250UL*80)/44100UL;
     staticconstuint32_t ZERO_MAX=(31250UL*100)/44100UL;
     staticconstuint32_t ONE_MIN=(31250UL*160)/44100UL;
     staticconstuint32_t ONE_MAX=(31250UL*200)/44100UL;
     staticconstuint32_t SYNCHRO_MIN=(31250UL*320)/44100UL;
     staticconstuint32_t SYNCHRO_MAX=(31250UL*400)/44100UL;
     if (counter>DIVIDER_MIN && counter<DIVIDER_MAX) return(BLOCK_TYPE_DIVIDER);//разделительif (counter>ZERO_MIN && counter<ZERO_MAX) return(BLOCK_TYPE_ZERO);//нольif (counter>ONE_MIN && counter<ONE_MAX) return(BLOCK_TYPE_ONE);//одинif (counter>SYNCHRO_MIN && counter<SYNCHRO_MAX) return(BLOCK_TYPE_SYNCHRO);//синхросигналreturn(BLOCK_TYPE_UNKNOW);//неизвестный блок
    }
    //----------------------------------------------------------------------------------------------------//добавить бит данных//----------------------------------------------------------------------------------------------------voidRF_AddBit(bool state){
     if ((BitSize>>2)>=19) return;//буфер заполнен
     Byte<<=1;
     if (state==true) Byte|=1;
     BitSize++; 
     if ((BitSize&0x03)==0)
     {
      Buffer[(BitSize>>2)-1]=Byte;
      Byte=0;
     }
    }
    //----------------------------------------------------------------------------------------------------//начать сборку данных заново//----------------------------------------------------------------------------------------------------voidRF_ResetData(void){
     BitSize=0;
     Byte=0;
    }
    //----------------------------------------------------------------------------------------------------//анализ блока//----------------------------------------------------------------------------------------------------voidRF_AnalizeCounter(uint32_t counter,bool value,MODE &mode){
     //узнаем тип блока
     BLOCK_TYPE type=RF_GetBlockType(counter,value);
     if (type==BLOCK_TYPE_UNKNOW)
     {
      mode=MODE_WAIT_SYNCHRO;
      RF_ResetData();
      return;
     } 
     if (type==BLOCK_TYPE_DIVIDER) return;//разделитель бесполезен для анализа //посылка должна начинаться и завершаться синхросигналомif (mode==MODE_WAIT_SYNCHRO)//ждём синхросигнала
     {
      if (type==BLOCK_TYPE_SYNCHRO)
      {
       mode=MODE_WAIT_ZERO_FIRST;
       return;
      }
      mode=MODE_WAIT_SYNCHRO;
      RF_ResetData();
      return;
     }
     if (mode==MODE_WAIT_ZERO_FIRST || mode==MODE_WAIT_ZERO_SECOND)//ждём два нуля
     {
      if (type==BLOCK_TYPE_SYNCHRO && mode==MODE_WAIT_ZERO_FIRST) return;//продолжается синхросигналif (type==BLOCK_TYPE_ZERO && mode==MODE_WAIT_ZERO_FIRST)
      {
       mode=MODE_WAIT_ZERO_SECOND;
       return;
      }
      if (type==BLOCK_TYPE_ZERO && mode==MODE_WAIT_ZERO_SECOND)
      {
       mode=MODE_RECEIVE_DATA;
       return;
      }
      mode=MODE_WAIT_SYNCHRO;
      RF_ResetData();
      return;
     }
     //принимаем данныеif (type==BLOCK_TYPE_SYNCHRO)//приём окончен
     {
      uint8_t size=(BitSize>>2);
      char str[30];
      if  (size!=10)
      {
       mode=MODE_WAIT_SYNCHRO;
       RF_ResetData();
       return; 
      }
      //выдаём блокfor(uint8_t n=0;n<size;n++)
      {
       uint8_t b=Buffer[n];  
       uint8_t mask=(1<<3);
       for(uint8_t m=0;m<4;m++,mask>>=1)
       {
        if (b&mask) SendText("1");
    	       else SendText("0");
       }
       SendText(" "); 
      }
      uint8_t channel=Buffer[2]&0x03;
      uint8_t key=(Buffer[8]>>3)&0x01;
      uint8_t h=(Buffer[7]<<4)|(Buffer[6]);//влажностьint16_t temp=(Buffer[5]<<8)|(Buffer[4]<<4)|(Buffer[3]);//температура int16_t k=18;
      int16_t t=(10*(temp-1220))/k;
      sprintf(str,"%i",key);
      SendText("Key:");
      SendText(str);  
      sprintf(str,"%i",channel+1);  
      SendText(" Ch:");
      SendText(str);  
      sprintf(str,"%i",h);  
      SendText(" H:");
      SendText(str);
      SendText("%, T:");
      if (t<0)
      {
       t=-t;
       sprintf(str,"-%i.%i",(int)(t/10),(int)(t%10));
      }
      else
      {
       sprintf(str,"%i.%i",(int)(t/10),(int)(t%10));
      }  
      SendText(str);
      SendText(" C\r\n");
      mode=MODE_WAIT_SYNCHRO;
      RF_ResetData();
      return;
     }
     //приём данныхif (type==BLOCK_TYPE_ONE)
     {
      RF_AddBit(true);
      return;
     }
     if (type==BLOCK_TYPE_ZERO)
     {
      RF_AddBit(false);
      return;
     }
     mode=MODE_WAIT_SYNCHRO;
     RF_ResetData();
    }
    //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//обработчики векторов прерываний//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//----------------------------------------------------------------------------------------------------//обработчик вектора прерывания таймера T1 (16-ми разрядный таймер) по переполнению//----------------------------------------------------------------------------------------------------
    ISR(TIMER1_OVF_vect)
    {   
     RF_SetTimerOverflow();
    } 
    //----------------------------------------------------------------------------------------------------//обработчик вектора прерывания от компаратора//----------------------------------------------------------------------------------------------------
    ISR(ANA_COMP_vect)
    {
     ACSR&=0xFF^(1<<ACIE);//запрещаем прерывания
     ACSR|=(1<<ACI);//сбрасываем флаг прерывания компаратораstatic MODE mode=MODE_WAIT_SYNCHRO;
     //узнаём длительность интервалаuint16_t length=RF_GetTimerValue();
     if (RF_IsTimerOverflow()==true) length=MAX_TIMER_INTERVAL_VALUE;//было переполнение, считаем интервал максимальным
     RF_ResetTimerValue();
     //отправляем на анализbool value=true;
     if (ACSR&(1<<ACO)) value=false;
     RF_AnalizeCounter(length,value,mode);
     ACSR|=(1<<ACIE);//разрешаем прерывания
    }
    


    The output of the receiver is connected to pin 13 (AIN1). Atmega via max232 connects to a computer’s COM port (or to a USB-COM adapter). The speed of the port is 9600 baud.

    After decoding, the following data stream will be obtained (I emit two initial zeros):

    // without a button, channel 1
    1100 1100 0000 1110 1000 0110 1100 0001 0000 1001 Humidity: 28% Temperature: 25.4
    // without a button, channel 2
    1100 1100 0001 1110 1000 0110 1101 0001 0000 0110 Humidity: 29% Temperature: 25.4

    Total, the package looks like this:



    I0-I7 is a thermometer identifier. Each time the thermometer is turned on, the identifier changes.

    C0-C1 channel (there are 3 possible in total). Channels are numbered from scratch.

    H0-H7 - humidity. Humidity in percent is read as it is, but for some reason the temperature (T0-T11) is set in an unusual format for meteorological stations. Judging by the descriptions of the exchange protocols of various meteorological stations found by me, one would expect a temperature in tenths of a degree and with a shift in the lower limit of measurement of the thermometer. So, no. Experiments have shown that the temperature code of this weather station is converted to degrees Celsius as (T-1220) / 18. From where these magic numbers are known only by the Chinese, who invented this exchange protocol.

    As the wolowizard suggested in the comments, the station transmits the temperature in tenths of degrees Fahrenheit, so a meaningful translation to Celsius will be 0.1 * (T-320) * 5 / 9-500 = 0.1 * (T-1220) /1.8.

    Bit K corresponds to pressing the TEST button.

    The assignment of the remaining fields could not be established, but it turned out that the value of the Fahrenheit / Celsius switch on the thermometer does not fall into the exchange protocol. Presumably, the last nibble (and maybe the last part of the last one) is a CRC, but I haven’t managed to calculate the algorithm yet (there is a suspicion that rows and nibbls are involved in the calculation). If anyone can solve this riddle, please inform the calculation algorithm.
    For those who want to break their heads, but do not have such a thermometer, I provide a table of received data.

    Table
    1001 0110 0101 1011 1000 0110 1000 0010 0001 1111 Key:0 Ch:2 H:40%, T:25.2 C
    1001 1001 0000 1101 1010 0100 0101 0101 0000 0110 Key:0 Ch:1 H:85%, T:-1.2 C
    1001 0110 0101 1100 1000 0110 1010 0010 0001 0100 Key:0 Ch:2 H:42%, T:25.3 C
    1001 0110 1001 0110 0111 0110 1101 0001 0010 1111 Key:0 Ch:2 H:29%, T:24.1 C
    1001 0110 1001 0000 0111 0110 1101 0001 0010 1000 Key:0 Ch:2 H:29%, T:23.7 C
    1001 0110 1001 0010 0101 0110 1110 0001 0010 1111 Key:0 Ch:2 H:30%, T:22.1 C
    1001 0110 1001 1001 0011 0110 1110 0001 0010 1100 Key:0 Ch:2 H:30%, T:20.7 C
    1001 0110 1001 1111 0001 0110 1111 0001 0010 1010 Key:0 Ch:2 H:31%, T:19.2 C
    1001 0110 0101 1001 0000 0110 0001 0010 0010 1000 Key:0 Ch:2 H:33%, T:18.0 C
    1001 0110 0101 0010 1111 0101 0010 0010 0010 0111 Key:0 Ch:2 H:34%, T:16.7 C
    1001 0110 0101 0100 1110 0101 0010 0010 0010 0010 Key:0 Ch:2 H:34%, T:16.0 C
    1001 0110 0101 0100 1101 0101 0011 0010 0010 0001 Key:0 Ch:2 H:35%, T:15.1 C
    1001 0110 0101 1100 1100 0101 0100 0010 0010 1110 Key:0 Ch:2 H:36%, T:14.6 C
    1001 0110 0101 1111 1011 0101 0101 0010 0010 1111 Key:0 Ch:2 H:37%, T:13.9 C
    1001 0110 0101 0011 1011 0101 0101 0010 0010 0001 Key:0 Ch:2 H:37%, T:13.2 C
    1001 0110 0101 1001 1010 0101 0110 0010 0010 0101 Key:0 Ch:2 H:38%, T:12.7 C
    1001 0110 0101 0100 1010 0101 0111 0010 0010 1000 Key:0 Ch:2 H:39%, T:12.4 C
    1001 0110 0101 1011 1001 0101 0111 0010 0010 1010 Key:0 Ch:2 H:39%, T:11.9 C
    1001 0110 0101 0011 1001 0101 1000 0010 0010 1001 Key:0 Ch:2 H:40%, T:11.5 C
    1001 0110 0101 1011 1000 0101 1000 0010 0010 1110 Key:0 Ch:2 H:40%, T:11.0 C
    1001 0110 0101 0111 1000 0101 1001 0010 0010 0101 Key:0 Ch:2 H:41%, T:10.8 C
    1001 0110 0101 1111 0111 0101 1001 0010 0010 1101 Key:0 Ch:2 H:41%, T:10.3 C
    1001 0110 0101 0111 0111 0101 1010 0010 0010 0111 Key:0 Ch:2 H:42%, T:9.9 C
    1001 0110 0101 0001 0111 0101 1011 0010 0010 0101 Key:0 Ch:2 H:43%, T:9.6 C
    1001 0110 0101 1011 0110 0101 1100 0010 0010 0110 Key:0 Ch:2 H:44%, T:9.2 C
    1001 0110 0101 1000 0110 0101 1100 0010 0010 1100 Key:0 Ch:2 H:44%, T:9.1 C
    1001 0110 0101 0011 0110 0101 1101 0010 0010 0110 Key:0 Ch:2 H:45%, T:8.8 C
    1001 0110 0101 1001 0101 0101 1110 0010 0010 0110 Key:0 Ch:2 H:46%, T:8.2 C
    1001 0110 0101 0101 0101 0101 1111 0010 0010 1101 Key:0 Ch:2 H:47%, T:8.0 C
    1001 0110 0101 0010 0101 0101 1111 0010 0010 1100 Key:0 Ch:2 H:47%, T:7.8 C
    1001 0110 0101 1110 0100 0101 1111 0010 0010 0000 Key:0 Ch:2 H:47%, T:7.6 C
    1001 0110 0101 1100 0100 0101 1111 0010 0010 1100 Key:0 Ch:2 H:47%, T:7.5 C


    Also popular now: