
В последнее время на Хабре было описано множество примеров реализации погодных термометров, систем сбора информации, управлением в системах «умный дом» — как проводных, передающих информацию по Ethernet, так и беспроводных, по WiFi™. В каждом конкретном случае — есть своя специфика, есть свои плюсы и минусы. И в данном материале речь пойдет об еще одном способе передачи данных — передаче в ISM-диапазоне 868 МГц.
В Российской Федерации к нелицензируемому диапазону частот, которые могут быть использованы без оформления разрешения ГКРЧ при условии соблюдения требований по ширине полосы, излучаемой мощности и назначению готового изделия, относят:
- 433.075—434.750 МГц
- 868.0—868.2 МГц
- 868.7—869.2 МГц
- 2400.0—2483.5 МГц
Коротко, для 434 МГц мощность передатчика должна составлять не более 10 мВт, для 868.0—868.2 МГц — до 10 мВт, для 868.7—869.2 МГц — до 25 мВт, для 2.4 ГГц — не более 100 мВт. Подробнее об ограничениях читайте в «Постановлении Правительства РФ от 12 октября 2004 г. N 539 «О порядке регистрации радиоэлектронных средств и высокочастотных устройств».
Основное различие между данными ISM-диапазонами определяется частотой излучения и как следствие, свойствами радиоволн. Применительно к задаче — сбора данных, систем беспроводного управления и контроля, наиболее оптимальным решением является использование диапазона 868 МГц. По сравнению с СВЧ диапазоном 2.4 ГГц, более длинные волны 868 МГц имеют меньшую интенсивность затухания, соответственно большая проницаемость сквозь преграды и дальность передачи сигнала гораздо выше. Для примера, кирпичная стена толщиной 89 мм поглощает около 3.5 дБ мощности волны 868 МГц и 6 дБ у 2.4 ГГц. Также в сравнении с диапазоном 433 МГц, у 868 МГц меньшая загруженность частоты, что способствует более надежной работе радиоканала.
| Предельная толщина препятствия, через которую может пройти радиосигнал | ||
| Частоты | Кирпичная стена, м. | Бетон, м. |
| 434 МГц | 4.3 | 0.47 |
| 868 МГц | 2.18 | 0.24 |
| 2.4 ГГц | 0.78 | 0.09 |
Следующей важной характеристикой является скорость передачи данных. Современные ISM трансиверы имеют достаточно высокие показатели, в среднем это значение от 256 до 1000 кбит/сек, что для подобного рода задач в��олне достаточно.
Таким образом, можно заключить, что в совокупности таких параметров как — высокая проницаемость, меньшая загруженность частотного диапазона, а также достаточно высокая скорость передачи данных, радиоволны 868 МГц является наиболее оптимальным решением данного рода задач по сравнению с остальным ISM-диапазоном.
Для примера передачи данных в ISM-диапазоне соберем устройство сбора показаний с удаленных датчиков. Допустим это будет температура и влажность воздуха. Т.е. нам нужно собрать 2 разнесенных устройства — первое будет выполнять роль головного и отображать сводную информацию, а второе — датчик, будет периодически производить замеры и отсылать данные на головное устройство. Причем оба устройства будут размещены вне прямой видимости, в двух разных зданиях.
В качестве платформы, позволяющей организовать радиоканал 868 МГц, воспользуемся платкой «Колибри» (Arduino Mini + RF). В ней используется трансивер EZRadioPRO Si4431 с программируемой выходной мощностью от -8 до +13dbM, что соответствует нормам ГКРЧ (20 мВт). Шаг настройки 3dbM. Чувствительность приемника –121 dBm. Энергопотребление 18.5 mA на прием и 30 mA на передачу. Допустимое питание платы от 5 или от 3.3V. Скорость передачи данных 0.123 — 256 кбит/сек. Помимо всего прочего плата программно совместима с Arduino IDE, что позволяет ее легко программировать. Принципиальная схема.
Для замера влажности и температуры воспользуемся цифровым датчиком SHT10. Он достаточно компактен и требует минимальной обвязки. Точность измерения показаний температуры ±0.5℃, а влажности 4.5%. Даташит.
Для отображения информации на головном устройстве возьмем графический ЖК-дисплей с разрешением 128*64 точки (WG12864A-TGH-VNW). Подсветка белая, цвет точки серый. И, дабы не занимать все пины микроконтроллера под дисплей, подключать его будем по SPI с помощью микросхемки MCP23S17. Но об этом чуть позже.
| Ключевые компоненты системы | |
| Для головного устройства | Для удаленного датчика |
| «Железо» | |
|
|
| Софт | |
Головное устройство
1. Сборка
Для начала соберем схему головного устройства.
Головное устройство будет состоять из платы Колибри, которая будет принимать и отображать данные на ЖК-дисплее. И как было сказано выше, работать с ЖК-дисплеем будем через интерфейс SPI, посредством микросхемы MCP23S17.

Данную схему соберем на макетной плате. Выводы обозначенные синими линиями подключим к плате Колибри — это цифровые контакты 10, 11, 12, 13 (SPI). На макетную плату и на Колибри остается подать питание 5V. Питание устройства предполагается либо от блока питания, где “честные” 5V, либо через линейный стабилизатор.

2. Прошивка
Для работы с радиомодулем платы Колибри воспользуемся готовой библиотекой EZRadioPRO под Arduinо IDE. Ее нужно скачать и установить внутри IDE. Также нам понадобится библиотека I2C_graphical_LCD_display для работы с ЖК дисплеем. Ее также нужно скачать и установить.
Стартуем Arduino IDE и создаем на основе примера вот такой вот скетч.
#include <SI4431.h> #include "SimRF.h" #include <Wire.h> #include <SPI.h> #include <I2C_graphical_LCD_display.h> I2C_graphical_LCD_display lcd; // Локальный адрес радиомодуля (0 для мастера) #define LOCAL_ADDR (0) // Функция 1: приём данных с датчика #define RF_FUNC01 (1) // Функция 2: передача данных датчику #define RF_FUNC02 (2) // Буфер передаваемых данных unsigned char RFTX_buffer[32]; // Буфер принимаемых данных unsigned char RFRX_buffer[32]; char i2a_buf[6]; /* Структура с 4-мя 16-битными регистрами удаленных датчиков */ typedef struct { u16 Register0; u16 Register1; u16 Register2; u16 Register3; } tRemoteSensor; tRemoteSensor RemoteSensorStatus[2]; // Массив регистров состояния удаленных датчиков 1 и 2 tRemoteSensor RemoteSensorCmd[2]; // Массив регистров для отправки на удаленные датчики 1 и 2 void print_P (const char* s) { char c; while ((c = pgm_read_byte(s++)) != 0) Serial.print(c); } u8 get_xor(u8* Src, u8 len) { u8 xoracc = 0; while (len--) { xoracc ^= *Src++; } return xoracc; } // Передача функции 02 с параметрами для датчиков из массива RemoteSensorCmd[]; void RFTX_FUNC02(u8 addr) { RemoteSensorCmd[addr -1].Register0 = RemoteSensorStatus[addr -1].Register0; RemoteSensorCmd[addr -1].Register1 = RemoteSensorStatus[addr -1].Register1; RemoteSensorCmd[addr -1].Register2 = RemoteSensorStatus[addr -1].Register2; RemoteSensorCmd[addr -1].Register3 = RemoteSensorStatus[addr -1].Register3; RFTX_buffer[0] = addr; //Адрес назначения RFTX_buffer[1] = LOCAL_ADDR; //Адрес отправителя RFTX_buffer[2] = RF_FUNC02; //Код функции 02 u16* p16 = (u16*) &RFTX_buffer[3]; *p16++ = RemoteSensorCmd[addr -1].Register0; *p16++ = RemoteSensorCmd[addr -1].Register1; *p16++ = RemoteSensorCmd[addr -1].Register2; *p16++ = RemoteSensorCmd[addr -1].Register3; // полученный ХЭШ код передаваемых данных RFTX_buffer[11] = get_xor ((u8*)RFTX_buffer, 11); //Отправим пакет по заданному адресу /*print_P(PSTR("\r\nData to TX: ")); for (u8 i = 0; i< 12; i++) { Serial.print((u8)RFTX_buffer[i], HEX); print_P(PSTR(",")); }*/ SI4431.TXData((u8*) RFTX_buffer, 12); } void RFRX_FUNC01(u8 SlaveAddr, u16* Payload) { RemoteSensorStatus [SlaveAddr - 1].Register0 = *Payload++; RemoteSensorStatus [SlaveAddr - 1].Register1 = *Payload++; RemoteSensorStatus [SlaveAddr - 1].Register2 = *Payload++; RemoteSensorStatus [SlaveAddr - 1].Register3 = *Payload++; } /** Пример обработки принятых пакетов * ВХОД: * addr - ожидаемый адрес ведомого устройства * len - длина принятого пакета * pData - указатель на начало принятого пакета в SRAM * ВЫХОД: * (0..127) код принятой функции * -1 ошибка */ void RFRX_MASTER_PROCESS(u8 len) { s8 funccode; u8 SlaveAddr; // Слишком короткие пакеты не рассматриваем if (len < 3) return; //print_P(PSTR("\r\n Dump:")); /*for (u8 i = 0; i< len; i++) { Serial.print(RFRX_buffer[i], HEX); print_P(PSTR(",")); i2a((u16)RFRX_buffer[i], i2a_buf); lcd.string(i2a_buf); lcd.string("-"); } */ SlaveAddr = RFRX_buffer[1]; // Получили адрес слейва if ((RFRX_buffer[0] == LOCAL_ADDR)&&(SlaveAddr != LOCAL_ADDR)) { //Если пакет предназначен для MASTER и принят от SLAVE (адрес >= 1) // Проверим XOR // print_P(PSTR("\r\n Correct Addr!")); if (get_xor((u8*)RFRX_buffer, len - 1) == (u8) RFRX_buffer[len-1]) { funccode = RFRX_buffer[2]; //получили код функции switch (funccode) { case RF_FUNC01: { // Пришли данные с датчика //print_P(PSTR("\r\nData from sensor: ")); //Serial.print(SlaveAddr, HEX); tRemoteSensor* pSensorData = (tRemoteSensor*)&RFRX_buffer[3]; // Получили указатель на структуру данных с датчика // Разбираем данные по переменным s16 Temperature_x100 = (s16) (pSensorData-> Register0); u16 Humidity_x100 = pSensorData-> Register1; u16 Data1 = pSensorData-> Register2; u16 Data2 = pSensorData-> Register3; // Печать температуры lcd.gotoxy (0, 16); lcd.string("Temperature: "); lcd.gotoxy (80, 16); if (Temperature_x100 < 0) { lcd.string("-"); Temperature_x100 = -Temperature_x100;// преобразем в положительное } u16 Temp = Temperature_x100 / 100; //Целая часть i2a(Temp, i2a_buf); showString(i2a_buf); //lcd.string(i2a_buf); showString("."); i2a(Temperature_x100 - Temp * 100, i2a_buf); //Дробная часть showString(i2a_buf); lcd.string("C "); // Печать влажности lcd.gotoxy (0, 32); lcd.string("Humidity: "); lcd.gotoxy (80, 32); Temp = Humidity_x100 / 100; i2a(Temp, i2a_buf); showString(i2a_buf); showString("."); i2a(Humidity_x100 - Temp * 100, i2a_buf); showString(i2a_buf); lcd.string("% "); // Печать произвольного числа //i2a(Data, i2a_buf); //lcd.string(i2a_buf); // Скопируем данные в виртуальные регистры датчика для датчиков 01 и 02 if (SlaveAddr <= 0x02) { RFRX_FUNC01(SlaveAddr, (u16*) &RFRX_buffer[3]); } // Теперь ждем 500 мс delay (500); // И отправляем посылку с командой 02 RFTX_FUNC02(SlaveAddr); } break; case RF_FUNC02: { // Пришло подтверждение команды с датчика // Пока ничего делать не будем - просто напишем в консоли, что комнда дошла print_P(PSTR("\r\nACK from sensor:")); Serial.print(SlaveAddr, HEX); } break; default: break; } } else print_P(PSTR("\r\n XOR error!")); } else print_P(PSTR("\r\n ADDR error!")); } // Обработчик ответа для функции 01 void RFRX_RESP_FUNC02(t_cmd_servo* pcmd_servo) { s16 POS1, POS2, POS3, POS4; POS1 = pcmd_servo->POS1; POS2 = pcmd_servo->POS2; POS3 = pcmd_servo->POS3; POS4 = pcmd_servo->POS4; /*print_P(PSTR("\r\nServo function response OK!")); print_P(PSTR("\r\nPOS1:")); Serial.print(POS1, HEX); Serial.print(POS2, HEX); Serial.print(POS3, HEX); Serial.print(POS4, HEX); */ } void setup(){ u8 ItStatus1, ItStatus2; lcd.begin(0x20, 0, 10); Serial.begin(38400); u8 length; u8 temp8; delay (1000); SI4431.begin(); // Распечатаем регистры радиомодуля, для проверки SPI связи /*for (u8 reg = 0; reg <=0x7f; reg++) { print_P(PSTR("\r\nReg:")); u8 c = SI4431.ReadRegister(reg); Serial.print(reg, HEX); print_P(PSTR(", ")); Serial.print(c, HEX); }*/ SI4431.Init(7); lcd.gotoxy(36, 0); lcd.string("I'm Ready!"); } void loop(){ u8 ItStatus1, ItStatus2; u8 len; //Разрешим 2 прерывания: // a) первое показывает прием правильного пакета: 'ipkval' // б) второе показывает прием пакета с неправильной КС CRC: 'icrcerror' SI4431.RXIRQEnable(); //читаем регистры состояния прерываний для очистки флагов прерываний и освобождения вывода NIRQ SI4431.ReadStatus(&ItStatus1, &ItStatus2); /*Снова разрешаем тракт приёма*/ SI4431.RXEnable(); // ждём событие прерывания // если оно наступило, значит принят пакет либо возникла ошибка CRC if(SI4431.IRQstate() == 0 ){ //запрет тракта приёма SI4431.RXDisable(); //читаем регистры состояния прерываний для очистки флагов прерываний и освобождения вывода NIRQ SI4431.ReadStatus(&ItStatus1, &ItStatus2); // Было прерывание по ошибке CRC if( (ItStatus1 & 0x01) == 0x01 ){ print_P(PSTR("\r\n Received CRC Error!")); //сброс FIFO передачи SI4431.FIFOReset(); //помигаем всеми светодиодами для индикации ошибки /*TX_LED_SET RX_LED_SET delay(500); TX_LED_CLR RX_LED_CLR*/ } // Было прерывание по приёму нормального пакета if( (ItStatus1 & 0x02) == 0x02 ){ print_P(PSTR("\r\n Received good packet! Len = ")); //Читаем размер полезных данных len = SI4431.RXPacketLen(); Serial.print(len, HEX); //убеждаемся, что число принятых байт поместится в буфер МК if(len <= sizeof(RFRX_buffer)) { SI4431.RXData((u8 *)RFRX_buffer, len); //Читаем принятые данные RFRX_MASTER_PROCESS (len); } } //сброс RX FIFO SI4431.FIFOReset(); //Разрешение тракта приёма SI4431.RXEnable(); } } void i2a( unsigned int i, char* pOut_buf ){ int ii; char int_buf[5]; for (ii=0; ii < 5; ){ int_buf[ii++] = '0'+ i % 10; i = i / 10; } do{ ii--; }while( (int_buf[ii] == '0') && (ii != 0) ); do { *pOut_buf++= int_buf[ii--]; } while (ii >= 0); *pOut_buf = 0x00; }
Данный скетч инициализирует радиоканал, головному устройству назначается адрес 0. Выставлена максимальная мощность передатчика 13dBm: SI4431.Init(7);
В данном примере на дисплей выводятся показания от 1 удаленного датчика, при необходимости аналогичным образом можно выводить показания от множества датчиков.
Далее прошивочку нужно загрузить в плату Колибри. Сделать это можно несколькими способами.
- С помощью платы Ардуино
- С помощью USB-Serial Converter
- С помощью внутрисхемного программатора.
Для прошивки с помощью Ардуино, сперва нужно извлечь из него микроконтроллер. После этого нужн�� соединить проводками обе платы следующими пинами:
| Ардуино | Колибри |
| Pin 0 | Pin 0 |
| Pin 1 | Pin 1 |
| RESET | RESET |
| +5V | +5V |
| GND | GND |
После того как соединили и выставили на Колибри джампер питания в положение 5V, можно подключить Ардуино к ПК. В Arduino IDE укажите правильный порт и в разделе Tools -> Board выберите параметр «Arduino Nano w/ Atmega 168», после чего нажмите на кнопку «Загрузить».
Аналогичные действия предпринимаются при загрузки с помощью USB-Serial конвертера. Ну и самый легкий способ — это загрузка с помощью программатора. В Arduino IDE нажимаете кнопку «Скомпилировать», далее «находите» hex-файл с прошивкой. Подключаетесь программатором к ICSP разъему Колибри, подаете на плату питание, загружаете в нее прошивку, указав предварительно в оболочке вашего программатора МК ATMEGA168A. Фьюзы: 0xF8, 0xDF, 0xFF. Lockbit: 0xCF.
Прошили, отключили все лишние проводки от Колибри. Теперь подаем на плату питания и на ЖК дисплее должна появится надпись: I'm Ready! Головное устройство собрано, переходим к следующему шагу.
Датчик
1. Сборка
Плата с датчиком будет состоять только из одного цифрового сенсора. Питание платы будет батарейное, 3V.
Схема датчика SHT10.

Соберем по схеме на макетной плате. Сенсор подключается к Колибри по 4 проводкам (цифровые пины 6 и 7 и питание).

Прежде чем подключить к Колибри батарейный отсек, необходимо загрузить прошивку. А уже после этого подключаем к Колибри батарейный отсек к разъему питания на 3V и джампер также выставляем в положение 3V.
Пару слов об питании. Если предполагается эксплуатация датчика вне помещений, тогда необходимо использовать соответствующие элементы питания. Наиболее морозоустойчивыми являются элементы питания — литий-тионил-хлоридные (LiSOCl2), литий-железо-фосфатные (LiFePO4).
2. Прошивка
Помимо библиотеки EZRadioPRO для датчика понадобится библиотека SHT1x, с ее помощью будем считывать показания температуры и влажности. Качаем и устанавливаем эту библиотеку.
Стартуем Arduino IDE и создаем на основе примера вот такой вот скетч.
#include <SI4431.h> #include "SimRF.h" #include <avr/sleep.h> #include <avr/wdt.h> #include <SHT1x.h> #define dataPin 7 #define clockPin 6 SHT1x sht1x(dataPin, clockPin); //#define DEBUG_MODE // Локальный адрес радиомодуля (0 для мастера) #define SLAVE_ADDR (1) // Коды функций протокола // Функция 1: передача измерений мастеру #define RF_FUNC01 (1) // Функция 2: прием команд от мастера #define RF_FUNC02 (2) // Буфер передаваемых данных unsigned char RFTX_buffer[32]; // Буфер принимаемых данных unsigned char RFRX_buffer[32]; unsigned char putch(unsigned char send_char) { while (!(UCSR0A & 0x20)); UDR0 = (unsigned char) send_char; while (!(UCSR0A & 0x20)); return(send_char); } /* Печать в консоль строки из FLASH ПЗУ */ void print_P (const char* s) { char c; while ((c = pgm_read_byte(s++)) != 0) putch(c); } /* Вычисление XOR для массива данных u8* Src - указатель на начало данных в ОЗУ u8 len - длина обрабатываемого массива */ u8 get_xor(u8* Src, u8 len){ u8 xoracc = 0; while (len--){ xoracc ^= *Src++; } return xoracc; } /* Передача функции 01 с параметрами измерений u8 addr - Адрес назначения u8* Data - Указатель на начало массива данных u8 Len - Длина массива данных */ void RFTX_FUNC01(u8 addr, u8* Data, u8 Len){ RFTX_buffer[0] = addr; //Адрес назначения RFTX_buffer[1] = SLAVE_ADDR; //Адрес отправителя RFTX_buffer[2] = 0x01; //Код функции 01 //Укладываем массив данных в пакет для передачи for (u8 i = 0 ; i < Len; i++){ RFTX_buffer[3+i] = *Data++; } // получем XOR код передаваемых данных RFTX_buffer[3 + Len] = get_xor ((u8*)RFTX_buffer, Len + 3); SI4431.TXData((u8*) RFTX_buffer, 3 + Len + 1); //Отправим пакет по заданному адресу по радиоканалу } /** Обработка принятых пакетов * ВХОД: * addr - ожидаемый адрес удаленного устройства * len - длина принятого пакета * pData - указатель на начало принятого пакета в SRAM * ВЫХОД: * (0..127) код принятой функции * -1 ошибка */ s8 RFRX_PROCESS(u8 addr, u8 len, u8* pData){ s8 funccode; // Слишком короткие пакеты не рассматриваем if (len < 3) return -1; if ((RFRX_buffer[0] == SLAVE_ADDR)&&(RFRX_buffer[1] == addr)) { //Если пакет предназначен для нас и принят от заданного адреса отправителя if (get_xor((u8*)RFRX_buffer, len - 1) == RFRX_buffer[len-1]){ funccode = RFRX_buffer[2]; pData = (u8*) &RFRX_buffer[3]; return funccode; } else return -1; } else return -1; } /* Обработчик ответа от мастера для функции 02 u8* PayloadData - начало полезных данных u8 PayloadLen - длина полезных данных */ void RFRX_RESP_FUNC02(u8* PayloadData, u8 PayloadLen){ // Пока просто распечатаем принятые регистры от мастера в консоли #ifdef DEBUG_MODE for (u8 i = 0; i< PayloadLen; i++){ Serial.print(*PayloadData++, HEX); } #endif } //Формирование и отправка подтверждения приёма пакета void RFTX_ACK_FUNC02(u8 addr, u8* Data, u8 Len){ RFTX_buffer[0] = 0x00; //Адрес мастера RFTX_buffer[1] = SLAVE_ADDR; //Наш адрес RFTX_buffer[2] = RF_FUNC02; //Укладываем массив данных в пакет для передачи for (u8 i = 0 ; i < Len; i++){ RFTX_buffer[3+i] = *Data++; } // получем XOR код передаваемых данных RFTX_buffer[3 + Len] = get_xor ((u8*)RFTX_buffer, Len+3); SI4431.TXData((u8*) RFTX_buffer, 3 + Len + 1); //Отправим пакет по заданному адресу по радиоканалу } u16 SensorData [4]; // Массив с 4мя 16 битными данными датчика volatile unsigned char WDT_wake; volatile unsigned char SLEEP_TIME = 15; //volatile unsigned char WDT_counter; // увеличим счетчик прерываний /* Обработчик прерываний по срабатыванию охранного таймера */ ISR(WDT_vect) { static unsigned char WDT_counter; // увеличим счетчик прерываний if (WDT_counter++ == SLEEP_TIME) { WDT_counter = 0; WDT_wake = 1; } //Serial.print(WDT_counter, HEX); asm ("WDR"); } void setup_WDT(void) { asm ("CLI"); asm ("WDR"); MCUSR &= ~(1<<WDRF); //Сброс флага срабатывания WDTCSR = (1 << WDCE)|(1<<WDE); WDTCSR = (1 << WDP2)|(1 << WDP1)|(1 << WDIE)|(1 << WDIF); // Настройка сторожевого таймера на период 1 сек. Перевод в режим работы генерирования прерываний asm ("SEI"); } void off_WDT(void){ asm ("CLI"); asm ("WDR"); MCUSR &= ~(1<<WDRF); //Сброс флага срабатывания WDTCSR = (1 << WDCE)|(1<<WDE)|(1 << WDIF); /* Turn off WDT */ WDTCSR = 0x00; asm ("SEI"); } void sys_sleep(void){ asm ("CLI"); ADCSRA &= ~(1 << ADEN); // Выключим АЦП SMCR |= (1<<SE); // Конфигурируем режим power-down asm ("SEI"); asm ("SLEEP"); } void sys_wake(void){ // тут можно включить АЦП } void RF_sleep (void){ u8 ItStatus1, ItStatus2; //читаем регистры состояния прерываний для очистки флагов прерываний и освобождения вывода NIRQ SI4431.ReadStatus(&ItStatus1, &ItStatus2); SI4431.WriteRegister(0x08, 0x03); //Сброс FIFO SI4431.WriteRegister(0x08, 0x00); SI4431.WriteRegister(0x07, 0x00); // Усыпляем модуль } void setup(){ Serial.begin(38400); delay (1000); u8 ItStatus1, ItStatus2; u8 length; u8 temp8; #ifdef DEBUG_MODE print_P (PSTR("\r\nHello!")); #endif delay (100); SI4431.begin(); #ifdef DEBUG_MODE // Распечатаем регистры радиомодуля, для проверки SPI связи for (u8 reg = 0; reg <=0x7f; reg++){ print_P(PSTR("\r\nReg:")); u8 c = SI4431.ReadRegister (reg); Serial.print(reg, HEX); print_P(PSTR(", ")); Serial.print(c, HEX); } #endif SI4431.Init(7); #ifdef DEBUG_MODE print_P(PSTR("\r\nRadio initialisation is OK")); #endif delay (1000); setup_WDT(); SMCR = (1<<SE)|(1<<SM1); // Конфигурируем режим power-down sys_sleep(); } void loop(){ u8 ItStatus1, ItStatus2; unsigned long Timer; if (WDT_wake){ Timer = 0; WDT_wake = 0; // Просыпаемся раз в N секунд // Отключаем сторожевой таймер off_WDT(); //digitalWrite(13, HIGH); // Включим светодиод (индикация просыпания и работы) #ifdef DEBUG_MODE print_P(PSTR("\r\n WAKE UP!")); #endif // 1) Сначала получим данные, например от АЦП или от SHT11x float Temp = sht1x.readTemperatureC(); s16 Temp_x100 = (s16) (Temp * 100.0); // Умножаем на 100.0 SensorData[0] = (u16) Temp_x100; float Humidity = sht1x.readHumidity(); u16 Humidity_x100 = (s16) (Humidity * 100.0); // Умножаем на 100.0 SensorData[1] = Humidity_x100; SensorData[2] = 0; SensorData[3] = 0; // 2) Потом, сформируем в ОЗУ пакет для передачи в эфир // 3) Потом, выводим радиомодули из спящего режима // 4) Отправляем данные мастеру по адресу 0 (ответа ждать не будем... мне кажется не обязательно это) for (u8 TXcnt = 0; TXcnt< 4; TXcnt++) { #ifdef DEBUG_MODE print_P(PSTR("\r\n TX to MASTER #")); #endif Serial.print(TXcnt + 1, DEC); RFTX_FUNC01(0x00, (u8*) SensorData, 8); // Отправим по адресу 0 8 байт информации с датчика // 4) Повторяем отправку данных мастеру по адресу 0 через короткое время для надежности } // 5) Переводим радиомодуль в режим RX //после передачи установим биты разрешения прерываний для режима приёма //Разрешим 2 прерывания: // a) первое показывает прием правильного пакета: 'ipkval' // б) второе показывает прием пакета с неправильной КС CRC: 'icrcerror' SI4431.RXIRQEnable(); //читаем регистры состояния прерываний для очистки флагов прерываний и освобождения вывода NIRQ SI4431.ReadStatus(&ItStatus1, &ItStatus2); //сброс FIFO передачи SI4431.FIFOReset(); /*Снова разрешаем тракт приёма*/ SI4431.RXEnable(); // 6) Ждем команды от мастера в течение 1 секунд #ifdef DEBUG_MODE print_P(PSTR("\r\n Wait Resp")); #endif Timer = 0; #define RESP_TIMEOUT (65535 * 32) while (Timer < RESP_TIMEOUT){ //asm ("WDR"); if(SI4431.IRQstate() == 0 ) { //принят пакет либо возникла ошибка CRC //запрет тракта приёма SI4431.RXDisable(); //читаем регистры состояния прерываний для очистки флагов прерываний и освобождения вывода NIRQ SI4431.ReadStatus(&ItStatus1, &ItStatus2); // Было прерывание по ошибке CRC if( (ItStatus1 & 0x01) == 0x01 ) { #ifdef DEBUG_MODE print_P(PSTR("\r\n RX CRC Error!")); #endif //сброс FIFO передачи SI4431.FIFOReset(); //помигаем всеми светодиодами для индикации ошибки break; // Выход из цикла ожидания ответа } else if( (ItStatus1 & 0x02) == 0x02 ) { // Было прерывание по приёму нормального пакета #ifdef DEBUG_MODE print_P(PSTR("\r\n RX packet Len = ")); #endif //Читаем размер полезных данных u8 len = SI4431.RXPacketLen(); #ifdef DEBUG_MODE Serial.print(len, HEX); #endif //убеждаемся, что число принятых байт поместится в буфер МК if(len <= sizeof(RFRX_buffer)) { SI4431.RXData((u8 *)RFRX_buffer, len); //Читаем принятые данные s8 funccode = RFRX_PROCESS (0x00, len, (u8*)RFRX_buffer); switch (funccode) { case RF_FUNC02: { // Получили ответ от мастера нам! #ifdef DEBUG_MODE print_P(PSTR("\r\n MASTER RESPONSE!")); #endif u8* PayloadData = (u8*) &RFRX_buffer[3]; //Начало полезных данных u8 PayloadLen = len - 4; //Длина полезных данных RFRX_RESP_FUNC02(PayloadData, PayloadLen); //Обработаем ответ // 7) Если команда пришла, то выполняем ее и отправляем подтверждение выполнения //Формирование и отправка подтверждения приёма пакета мастеру for (u8 TXcnt = 0; TXcnt< 4; TXcnt++) { #ifdef DEBUG_MODE print_P(PSTR("\r\n ACK MASTER RESPONSE!")); #endif RFTX_ACK_FUNC02(0x00, PayloadData, PayloadLen); } } break; default: #ifdef DEBUG_MODE print_P(PSTR("\r\nInvalid RX packet!")); #endif break; } } } break; // Выход из цикла ожидания ответа } else { Timer++; } } #ifdef DEBUG_MODE if (Timer == RESP_TIMEOUT) print_P(PSTR("\r\n Response Timeout!")); print_P(PSTR("\r\n SLEEP...")); #endif // 8) Выводим радиомодуль в режим сна RF_sleep(); setup_WDT(); //digitalWrite(13, LOW); } // 9) Выводим атмегу в режим сна, запуская таймер на N секунд для пробуждения // 10) Как проснемся, идем на п. 1. sys_sleep(); }
Схема работы данной программы проста. После инициализации радиоканала, где ему назначается адрес 1 и первой передачи данных на устройство с адресом 0, микроконтроллер переводит трансивер в спящий режим и потом сам засыпает на 15 секунд. По истечению данного времени по сторожевому таймеру он пробуждается, включает трансивер и вновь происходит передача данных.
К одному головному устройству можно привязать множество таких датчиков.
Загружаем этот скетч в Колибри. После загрузки отключаем все лишнее, подключаем батарейное питание и включаем питание. Через некоторое время на головном устройстве получим показания с удаленного датчика.
Уф, вроде все написал. Если что-то непонятно, спрашивайте :)
