В последнее время на Хабре было описано множество примеров реализации погодных термометров, систем сбора информации, управлением в системах «умный дом» — как проводных, передающих информацию по 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 для работы с ЖК дисплеем. Ее также нужно скачать и установить.
#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("rnData 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("rn 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("rn 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("rnData 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("rnACK from sensor:"));
Serial.print(SlaveAddr, HEX);
}
break;
default:
break;
}
} else print_P(PSTR("rn XOR error!"));
}
else print_P(PSTR("rn 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("rnServo function response OK!"));
print_P(PSTR("rnPOS1:"));
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("rnReg:"));
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("rn 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("rn 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, с ее помощью будем считывать показания температуры и влажности. Качаем и устанавливаем эту библиотеку.
#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("rnHello!"));
#endif
delay (100);
SI4431.begin();
#ifdef DEBUG_MODE
// Распечатаем регистры радиомодуля, для проверки SPI связи
for (u8 reg = 0; reg <=0x7f; reg++){
print_P(PSTR("rnReg:"));
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("rnRadio 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("rn 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("rn 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("rn 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("rn RX CRC Error!"));
#endif
//сброс FIFO передачи
SI4431.FIFOReset();
//помигаем всеми светодиодами для индикации ошибки
break; // Выход из цикла ожидания ответа
}
else if( (ItStatus1 & 0x02) == 0x02 )
{
// Было прерывание по приёму нормального пакета
#ifdef DEBUG_MODE
print_P(PSTR("rn 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("rn 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("rn ACK MASTER RESPONSE!"));
#endif
RFTX_ACK_FUNC02(0x00, PayloadData, PayloadLen);
}
}
break;
default:
#ifdef DEBUG_MODE
print_P(PSTR("rnInvalid RX packet!"));
#endif
break;
}
}
}
break; // Выход из цикла ожидания ответа
}
else
{
Timer++;
}
}
#ifdef DEBUG_MODE
if (Timer == RESP_TIMEOUT) print_P(PSTR("rn Response Timeout!"));
print_P(PSTR("rn SLEEP..."));
#endif
// 8) Выводим радиомодуль в режим сна
RF_sleep();
setup_WDT();
//digitalWrite(13, LOW);
}
// 9) Выводим атмегу в режим сна, запуская таймер на N секунд для пробуждения
// 10) Как проснемся, идем на п. 1.
sys_sleep();
}
Схема работы данной программы проста. После инициализации радиоканала, где ему назначается адрес 1 и первой передачи данных на устройство с адресом 0, микроконтроллер переводит трансивер в спящий режим и потом сам засыпает на 15 секунд. По истечению данного времени по сторожевому таймеру он пробуждается, включает трансивер и вновь происходит передача данных.
К одному головному устройству можно привязать множество таких датчиков.
Загружаем этот скетч в Колибри. После загрузки отключаем все лишнее, подключаем батарейное питание и включаем питание. Через некоторое время на головном устройстве получим показания с удаленного датчика.
Уф, вроде все написал. Если что-то непонятно, спрашивайте :)
Автор: Prometheus