Предыстория
Мой частный дом отапливается при помощи электрических конвекторов. Всё в них хорошо: и лёгкость монтажа и автоматическое управление температурой и режим день/ночь и режим 50% мощности. Но есть и минус — датчик температуры воздуха закреплён прямо на корпусе конвектора, поэтому нагревается и остывает вместе с ним. Из-за этого конвектор включается/выключается гораздо чаще, чем хотелось бы, невозможно установить желаемую температуру воздуха в комнате, т.к. реальная будет ниже градусов на 5, да и на надёжности постоянные переключения реле сказываются негативно. Можно было, конечно, удлинить датчик температуры и отнести его подальше, но это не наш метод. Т.к. я давно занимаюсь беспроводными технологиями и есть наработки, то решил оснастить конвектор беспроводным датчиком температуры. Это позволит разместить его в любом месте комнаты, не тянуть провода, а если нужно, использовать не один, а несколько датчиков и рассчитывать среднюю по комнате температуру. (Под катом картинки)
Переделка
Как я уже сказал, я плотно занимаюсь беспроводкой и имею по этой теме наработки. Для мониторинга различных датчиков как нельзя лучше подходит ZigBee, а качестве ZigBee микроконтроллера я уже давно использую JN5148 фирмы Jennic (куплена NXP). Для быстрого изготовления макетов я сделал себе несколько модулей с этим микроконтроллером.
Схема модуля (кликабельна):
В схему модуля включен сам микроконтроллер, внешняя память программ для него (обязательный компонент для JN5148), кварц, конденсаторы по линиям питания, ВЧ часть с антенной. Для быстрого старта нужен только разъём программирования и питание 3.3 В. Платки заказывал в seeedstudio, дёшево и сердито. Для того чтобы быстро что-то сваять отлично подходят.
Датчик температуры тоже был сделан заранее и ждал своего часа.
Схема датчика (кликабельна):
В качестве измерителя температуры использована микросхема TMP102 фирмы Texas Instruments. Микросхема довольно недорогая, измеряет температуру с точностью 0.5 градуса в диапазоне -25..+85, имеет ток потребления 10 мкА в активном режиме и 1 мкА в спящем, очень компактная, а также работает в диапазоне напряжений питания от 1.4 до 3.6 В, что важно при питании от одной литиевой батарейки. В остальном схема датчика отличается от схемы модуля наличием батарейки, делителя для измерения её напряжения, включателя питания и разъёма для программирования.
Чтобы закончить с железом и перейти собственно к переделке, забегу вперёд и скажу, что сначала я хотел только измерять температуру, передавать его конвектору и каким то образом подсовывать её микроконтроллеру вместо его родного датчика. В последствии появилась идея устанавливать температурный порог так же удаленно, с ПК. Для этого я использовал USB свисток с тем же JN5148.
Схема (тоже кликабельна):
Схема свистка включает в себя схему модуля, рассмотренного выше и USB-UART конвертер на микросхеме FT232R, который одновременно является программатором для микроконтроллера.
Теперь перейдём к реверс-инжинирингу. В качестве подопытного использовался конвектор фирмы Ballu мощностью 1000 Вт с электронной системой управления. Разобрав конвектор, я обнаружил 2 платы: силовую и плату управления.
Силовая плата:
Плата управления:
На силовой плате расположен сетевой источник питания, стабилизатор напряжения +5В на L7805, 2 реле, которые включают либо нагрузку 500Вт (50%) либо 1000Вт (100%) и зуммер. Отдельно расположены термопредохранитель и ионизатор воздуха. На плате управления расположен микроконтроллер, кнопки, а также семисегментный индикатор температуры.
Осмотр показал, что для измерения температуры используется полупроводниковый диод, который, как известно, обладает довольно линейной зависимостью прямого падения напряжения от температуры. Диод включен в верхнее плечо делителя напряжения питания, а напряжение с делителя подаётся на вход АЦП микроконтроллера.
Исходя из этой схемы измерения, самый простой способ эмулировать датчик температуры — это подавать на АЦП конвектора напряжение с ЦАП, который имеется на борту JN5148. Т.к. напряжение питания (и одновременно опорное АЦП) контроллера в конвекторе составляет 5 В, а опорное у ЦАПа — 2.4, необходимо усилить напряжение с ЦАП при помощи операционника примерно в 2 раза. Исходя из этого рисуем схему эмулятора датчика температуры (кликабельна).
Дополнительно к модулю она включает в себя усилитель на операционнике, преобразователь 5 В — 3.3 В для питания JN5148 и разъём программирования. Дальше изготавливаем плату: утюжим, травим, сверлим, лудим, паяем.
Устанавливаем плату на место и начинаем кодить. Кстати, то что плата управления отключается от силовой платы оказалось очень удобно. На неё достаточно подать +5 В и она может работать полностью автономно, поэтому в конвектор я её устанавливал после полной отладки работы системы.
Программирование
Опытным путём я снял зависимость температуры, измеренной конвектором, от напряжения на входе АЦП.
Видно, что в середине диапазона разница между реальной характеристикой и идеальной составляет примерно 1 градус, поэтому я принял решение записать соответствующие коды ЦАП в массив и в зависимости от температуры брать нужный код из массива и отправлять ЦАПу.
В качестве основы для программирования я использовал шаблон от фирмы Jennic — JN-AN-1123-ZBPro-Application-Template, который можно скачать здесь. В нём реализован весь базовый функционал сети ZigBee, которая работает на основе операционной системы JenOS, собственной разработке фирмы Jennic для её микроконтроллеров. Кому интересно, могут скачать шаблон и посмотреть, я же приведу здесь только самый важный код.
В данной системе представлены все типы устройств сети ZigBee: координатор (конвектор), маршрутизатор (USB свисток) и спящее оконечное устройство (датчик). Начнём с самого простого — с USB свистка. Он занимается тем, что сканит UART на предмет появления байта с компьютера и отправляет принятый байт координатору.
Функция сканирования представляет собой задачу операционной системы, которая запускается один раз в 50 мс. Она проверяет не пришла ли команда и выдаёт все пришедшие команды в очередь сообщений, которая обрабатывается основной задачей.
OS_TASK(APP_CommandScan)
{
APP_tsEvent sCommandEvent;
int16 word;
//Считываем слово из УАРТа
word=i16Serial_RxChar(E_AHI_UART_0);
//Если успешно
if(word != -1)
{
//Отправляем системное сообщение с кодом команды
sCommandEvent.eType = APP_E_EVENT_COMMAND;
sCommandEvent.sCommand.u8Value = (uint8)word;
OS_ePostMessage(APP_CommandEvent, &sCommandEvent);
}
//Перезапускаем таймер задачи
OS_eContinueSWTimer(APP_tmrCommandScan, APP_TIME_MS(50), NULL);
}
В основном цикле все пришедшие команды отправляются координатору.
//Пришло системное сообщение о принятии команды по УАРТу
if (APP_E_EVENT_COMMAND == sAppEvent.eType)
{
//Создаём указатель APDU
PRIVATE PDUM_thAPduInstance s_hAPduInst = PDUM_INVALID_HANDLE;
//Форматируем APDU для передачи команды
s_hAPduInst = PDUM_hAPduAllocateAPduInstance(apduCommand);
//Записываем в APDU данные для передачи
PDUM_u16APduInstanceWriteNBO(s_hAPduInst,
0, //Стартовая позиция для записи
"b", //Строка форматирования, передаём один байт (b)
sAppEvent.sCommand.u8Value);
//Устанавливаем размер APDU равным 1 байту
PDUM_eAPduInstanceSetPayloadSize(s_hAPduInst, 1);
//Отправляем данные, с указанием кластера, конечных точек отправителя и приёмника
u8Status = ZPS_eAplAfUnicastDataReq(s_hAPduInst,
MYPROFILE_MYCLUSTER_CLUSTER_ID,
USBSTICK_MYENDPOINT_ENDPOINT,
THERMOSTAT_MYENDPOINT_ENDPOINT,
0x0000, //Адрес получателя (в данном случае координатора)
ZPS_E_APL_AF_UNSECURE,
0,
&u8SeqNum);
}
Датчик температуры просыпается один раз в секунду (время, конечно, настраивается), измеряет температуру и напряжение батарейки, отправляет всё конвектору и снова засыпает.
PRIVATE void vSendSensorData()
{
uint8 u8Status;
uint8 u8SeqNum;
//Создаём структуру для измеренных данных
SensorData NewSensorData;
//Измеряем температуру и напряжение батарейки
NewSensorData.TempValue = TempMeasurement();
NewSensorData.BattValue = BatVoltageMeasurment();
//МАС адрес считывается один раз при запуске
//и отправляется для того, чтобы различать несколько датчиков температуры
uint32 MacH, MacL;
MacH = MacAddr.u32H;
MacL = MacAddr.u32L;
//Создаём указатель APDU
PRIVATE PDUM_thAPduInstance s_hAPduInst = PDUM_INVALID_HANDLE;
//Форматируем APDU для передачи температуры
s_hAPduInst = PDUM_hAPduAllocateAPduInstance(apduTemperature);
//Записываем в APDU данные для передачи
PDUM_u16APduInstanceWriteNBO( s_hAPduInst,
0, //Начальная позиция записываемых данных
"wwbh", //Строка форматирования (в данном случае мы
//хотим записать две 32-битных переменных (ww)
//одну 8-битную (b) и одну 16-битную (h)
MacH,
MacL,
NewSensorData.TempValue,
NewSensorData.BattValue);
//Устанавливаем общий размер отправляемых данных равным 11 байт
PDUM_eAPduInstanceSetPayloadSize(s_hAPduInst, 11);
//Отправляем данные широковещательным пакетом с указанием кластера и конечной точки отправителя
u8Status = ZPS_eAplAfBroadcastDataReq(s_hAPduInst,
MYPROFILE_TEMPERATURE_CLUSTER_ID,
TEMPSENSOR_TEMPSENSORENDPOINT_ENDPOINT,
0xFF,
ZPS_E_BROADCAST_ZC_ZR,
ZPS_E_APL_AF_UNSECURE,
0 ,
&u8SeqNum);
}
Координатор в свою очередь определяет от кого пришли данные и если это температура, то устанавливает соответствующее напряжение на выходе ЦАПа, а если это команда с компьютера, то выдаёт импульсы на кнопки установки температуры (эмулирует нажатие).
Функция установки температуры:
void SetTemp(int8 temp)
{
//Проверяем, что температура не выходит за диапазон
if(temp > 33) temp = 33;
else if(temp < 0) temp = 0;
//Устанавливаем напряжение ЦАП, соответствующее текущей температуре
vAHI_DacOutput(E_AHI_AP_DAC_1, temp_levels[temp]);
}
Приём данных:
//Если пришли данные с датчика температуры
if(MYPROFILE_TEMPERATURE_CLUSTER_ID == sStackEvent.uEvent.sApsDataIndEvent.u16ClusterId)
{
//Считываем
PDUM_u16APduInstanceReadNBO(sStackEvent.uEvent.sApsDataIndEvent.hAPduInst, 8, "bh", &ReceivedTempSensorData);
//Устанавливаем температуру
SetTemp(ReceivedTempSensorData.TempValue);
}
else
//Пришла команда с USB свистка
{
//Считываем
PDUM_u16APduInstanceReadNBO(sStackEvent.uEvent.sApsDataIndEvent.hAPduInst, 0, "b", &Command);
//Выдаём импульс, эмулирующий нажатие на соответствующую кнопку конвектора
if(Command== '+')
{
vAHI_DioSetOutput(0, (1 << PLUS));
vDelay(50);
vAHI_DioSetOutput((1 << PLUS), 0);
}
if(Command== '-')
{
vAHI_DioSetOutput(0, (1 << MINUS));
vDelay(50);
vAHI_DioSetOutput((1 << MINUS), 0);
}
}
И напоследок, видео работы системы:
Полезные ссылки:
Описание операционной системы JenOS
Описание стека ZigBee Pro
Описание функций для работы с периферией JN5148
Архив с проектом
Архив со схемами
И ещё, статья опубликована 7 мая, так что всех с днём Радио!
Автор: alexsaf