Использование внешнего беспроводного термометра Buro H999 совместно с самодельными устройствами

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

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

o3y4exet3g-ru6d1bdhpwrqiakm.jpeg

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

kpghsvyozbxt9dtkqfk4ap-f9hw.gif

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

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

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

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

Программа для Atmega8
//----------------------------------------------------------------------------------------------------
//библиотеки
//----------------------------------------------------------------------------------------------------
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
//----------------------------------------------------------------------------------------------------
//частота контроллера
//----------------------------------------------------------------------------------------------------
#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<>8)&0xff;
 UBRRL=speed&0xFF;
 
 RF_Init();  
}
//----------------------------------------------------------------------------------------------------
//передача символа в COM-порт
//----------------------------------------------------------------------------------------------------
void UART_Write(unsigned char byte)
{ 
 while(!(UCSRA&(1<DIVIDER_MIN && counterZERO_MIN && counterONE_MIN && counterSYNCHRO_MIN && counter>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>=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<


Выход приёмника подключается к выводу 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

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

dqmjiuixxbenuwzxubzgfftypow.png

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

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

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

© Habrahabr.ru