Всем хороша погодная станция Buro H146G с внешним беспроводным термометром H999. Но вот только чтобы увидеть показания на её блеклом ЖК-дисплее требуется хорошее освещение. А мне было бы лучше, если бы вывод температуры и влажности за окном отображался на достаточно ярких индикаторах (например, совместив отображение температуры и влажности с часами на газоразрядных индикаторах ИН-12). Сделать такую поделку несложно, но нужно знать протокол обмена с беспроводным термометром. Здесь уже были статьи про использование беспроводного термометра метеостанций для получения температуры и влажности по радиоканалу. Но для станций Buro протокол обмена ещё не был описан. Значит, надо это исправить: возможно, кому-то он может пригодиться.

В интернете описания протокола обмена станций BURO я не нашёл. А это значит, что придётся вскрывать протокол обмена этого беспроводного датчика.

Мой внешний термометр выглядит так:



Подключив к осциллографу китайский сверхрегенеративный приёмник на 433,92 МГц и нажав кнопку TEST на термометре, было отчётливо видно, как бегут импульсы передачи. Ну а так как частота там небольшая, выход приёмника был подключён к входу звуковой карты через резистивный делитель. После обработки записанного звукового файла компаратором получилась следующая картинка:



Как и у других погодных станций модуляция осуществляется изменением скважности. Начинается передача с блока синхросигналов, потом идёт ещё один синхросигнал, а потом идут данные, после которых идёт завершающий синхросигнал. Два ноля после синхросигнала, по-видимому, являются идентификатором начала данных – во всяком случае, я ни разу не заметил их изменения. Данные с начальным и конечным синхросигналом дублируются шесть раз. Обмен данными ведётся нибблами.

Для декодирования я решил начинать приём по первому синхросигналу и двум нулям, а завершать по последнему синхросигналу.

Чтобы такой сигнал декодировать, достаточно посчитать длительности между перепадами сигнала.

Я для этого написал простенькую тестовую программу для контроллера Atmega8:

Программа для Atmega8
//----------------------------------------------------------------------------------------------------
//библиотеки
//----------------------------------------------------------------------------------------------------
#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//приём данных
}; 
 
//----------------------------------------------------------------------------------------------------
//глобальные переменные
//----------------------------------------------------------------------------------------------------
static const uint16_t MAX_TIMER_INTERVAL_VALUE=0xFFFF;//максимальное значение интервала таймера

static volatile bool TimerOverflow=false;//было ли переполнение таймера

static uint8_t Buffer[20];//буфер сборки полубайта
static uint8_t BitSize=0;//количество принятых бит
static uint8_t Byte=0;//собираемый байт

//----------------------------------------------------------------------------------------------------
//прототипы функций
//----------------------------------------------------------------------------------------------------
 
void InitAVR(void);//инициализация контроллера
void UART_Write(unsigned char byte);//передача символа в COM-порт
void SendText(const char *text);//отправить текст в COM-порт

void RF_Init(void);//инициализация
void RF_SetTimerOverflow(void);//установить флаг переполнения таймера
void RF_ResetTimerOverflow(void);//сбросить флаг переполнения таймера
bool RF_IsTimerOverflow(void);//получить, есть ли переполнение таймера
uint16_t RF_GetTimerValue(void);//получить значение таймера
void RF_ResetTimerValue(void);//сбросить значение таймера 
BLOCK_TYPE RF_GetBlockType(uint32_t counter,bool value);//получить тип блока
void RF_AddBit(bool state);//добавить бит данных
void RF_ResetData(void);//начать сборку данных заново
void RF_AnalizeCounter(uint32_t counter,bool value,MODE &mode);//анализ блока

//----------------------------------------------------------------------------------------------------
//основная функция программы
//----------------------------------------------------------------------------------------------------
int main(void)
{
 InitAVR();  
 _delay_ms(200); 
 SendText("Thermo unit\r\n");
 _delay_ms(200);  
 sei();
 while(1);
 cli();  
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//общие функции
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
//----------------------------------------------------------------------------------------------------
//инициализация контроллера
//----------------------------------------------------------------------------------------------------
void InitAVR(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-расширенный стоп-бит
 //вычисляем значение регистра скорости передачи данных
 unsigned long speed=F_CPU/(16UL);
 speed=(speed/UART_SPEED)-1UL;
 UBRRH=(speed>>8)&0xff;
 UBRRL=speed&0xFF;
 
 RF_Init();  
}
//----------------------------------------------------------------------------------------------------
//передача символа в COM-порт
//----------------------------------------------------------------------------------------------------
void UART_Write(unsigned char byte)
{ 
 while(!(UCSRA&(1<<UDRE)));
 UDR=byte;
}

//----------------------------------------------------------------------------------------------------
//отправить текст в COM-порт
//----------------------------------------------------------------------------------------------------
void SendText(const char *text)
{
 while((*text))
 {
  UART_Write(*text);
  text++;
 }
}

//----------------------------------------------------------------------------------------------------
//инициализация
//----------------------------------------------------------------------------------------------------
void RF_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 шестнадцатибитный) 
} 
//----------------------------------------------------------------------------------------------------
//установить флаг переполнения таймера
//----------------------------------------------------------------------------------------------------
void RF_SetTimerOverflow(void)
{
 cli();
 TimerOverflow=true;
 sei();
}
//----------------------------------------------------------------------------------------------------
//сбросить флаг переполнения таймера
//----------------------------------------------------------------------------------------------------
void RF_ResetTimerOverflow(void)
{
 cli();
 TimerOverflow=false;
 sei();
}
//----------------------------------------------------------------------------------------------------
//получить, есть ли переполнение таймера
//----------------------------------------------------------------------------------------------------
bool RF_IsTimerOverflow(void)
{
 cli();
 bool ret=TimerOverflow;
 sei();
 return(ret);
}

//----------------------------------------------------------------------------------------------------
//получить значение таймера 
//----------------------------------------------------------------------------------------------------
uint16_t RF_GetTimerValue(void)
{
 cli();
 uint16_t ret=TCNT1;
 sei(); 
 return(ret);
} 


//----------------------------------------------------------------------------------------------------
//сбросить значение таймера 
//----------------------------------------------------------------------------------------------------
void RF_ResetTimerValue(void)
{
 cli();
 TCNT1=0;
 sei();
 RF_ResetTimerOverflow();
} 
//----------------------------------------------------------------------------------------------------
//получить тип блока
//----------------------------------------------------------------------------------------------------
BLOCK_TYPE RF_GetBlockType(uint32_t counter,bool value)
{ 
 static const uint32_t DIVIDER_MIN=(31250UL*12)/44100UL;
 static const uint32_t DIVIDER_MAX=(31250UL*25)/44100UL;
 static const uint32_t ZERO_MIN=(31250UL*80)/44100UL;
 static const uint32_t ZERO_MAX=(31250UL*100)/44100UL;
 static const uint32_t ONE_MIN=(31250UL*160)/44100UL;
 static const uint32_t ONE_MAX=(31250UL*200)/44100UL;
 static const uint32_t SYNCHRO_MIN=(31250UL*320)/44100UL;
 static const uint32_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);//неизвестный блок
}
//----------------------------------------------------------------------------------------------------
//добавить бит данных
//----------------------------------------------------------------------------------------------------
void RF_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;
 }
}
//----------------------------------------------------------------------------------------------------
//начать сборку данных заново
//----------------------------------------------------------------------------------------------------
void RF_ResetData(void)
{
 BitSize=0;
 Byte=0;
}

//----------------------------------------------------------------------------------------------------
//анализ блока
//----------------------------------------------------------------------------------------------------
void RF_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);//разрешаем прерывания
}


Выход приёмника подключается к выводу 13 (AIN1). Atmega через max232 подключается к COM-порту компьютера (ну или к переходнику USB-COM). Скорость работы порта 9600 бод.

После декодирования получится следующий поток данных (два начальных ноля я выбрасываю):

//без кнопки, канал 1
1100 1100 0000 1110 1000 0110 1100 0001 0000 1001 Влажность:28% Температура:25.4
//без кнопки, канал 2
1100 1100 0001 1110 1000 0110 1101 0001 0000 0110 Влажность:29% Температура:25.4

Итого, пакет выглядит так:



I0-I7 – идентификатор термометра. При каждом новом включении термометра идентификатор меняется.

C0-C1 — канал (всего их возможно 3). Каналы нумеруются с нуля.

H0-H7 — влажность. Влажность в процентах считывается как есть, а вот температура (T0-T11) почему-то задана в необычном для метеостанций формате. Судя по найденным мной описаниям протоколов обмена различных метеостанций, можно было бы ожидать температуру в десятых долях градуса и со смещением нижнего предела измерения термометра. Так вот, нет. Эксперименты показали, что код температуры данной метеостанции переводится в градусы Цельсия как (T-1220)/18. Откуда эти магические числа знают только китайцы, придумавшие этот протокол обмена.

Как подсказал wolowizard в комментариях, станция передаёт температуру в десятых долях градусов Фаренгейта, поэтому осмысленный перевод в градусы Цельсия будет 0.1*(T-320)*5/9-500=0.1*(T-1220)/1.8.

Бит K соответствует нажатию на кнопку TEST.

Назначение остальных полей установить не удалось, но выяснилось, что значение переключателя Фаренгейты/Цельсии на термометре в протокол обмена не попадает. Предположительно так же последний ниббл (а может, и часть предпоследнего) является CRC, но вычислить алгоритм мне пока не удалось (есть подозрение, что в вычислении участвуют строки и столбцы нибблов). Если кто-нибудь сумеет разгадать эту загадку, соо��щите, пожалуйста, алгоритм вычисления.
Для желающих поломать голову, но не имеющих такого термометра, привожу таблицу принятых данных.

Таблица
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