Товары из Китая

TinyRF — радиоуправление и обмен данными на ATttiny13


TinyRF - радиоуправление и обмен данными на ATttiny13

При разработке различных DIY устройств нередко возникает потребность в передаче/получении каких-либо данных по воздуху. Если в случае использования «взрослых» микроконтроллеров (ATmega, STM) эта задача решается за пару минут подключением сторонней библиотеки, то с младшими версиями не все так просто. Наиболее популярные библиотеки зачастую не влезают в них, а существующие адаптации имеют сильно урезанный функционал. Продолжая тему выжимания нетипичных возможностей из дешевых и примитивных микроконтроллеров ATtiny13, я специально для них написал простую и компактную библиотеку, которая реализует один из наиболее популярных протоколов радиообмена и позволяет не только передавать/принимать данные, но и управлять некоторыми сторонними устройствами. Возможно эта тема окажется многим полезной, поэтому я хочу поделиться исходниками и показать некоторые примеры ее использования.

Вселенная на проводе

Как передать цифровой сигнал, состоящий из нулей и единиц, из точки A в точку B? Кажущаяся тривиальной задача на самом деле не является таковой, даже в случае передачи данных по физическому проводу. Если мы просто подадим обычный («сырой») цифровой сигнал на линию связи, то приемник на другом конце не сможет различать между собой отдельные биты в длинных последовательностях нулей и единиц. В проводных линиях связи самый простой способ решения проблемы — передача тактирующего сигнала по отдельному каналу:

TinyRF - радиоуправление и обмен данными на ATttiny13

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

Но как быть, если провод для передачи данных один единственный? Для этого придумали различные методы кодирования сигнала, наиболее известным из которых является манчестерское кодирование:

TinyRF - радиоуправление и обмен данными на ATttiny13

Манчестерский код довольно прост в понимании и реализации: в нем нулевые и единичные биты исходных данных кодируются направлением изменения сигнала (0-1 или 1-0) в середине каждого бита. Тем не менее есть еще более простые способы передачи данных, особенно если не столь важна скорость.

EV1527

EV1527 — это один из самых популярных аппаратных кодеров/декодеров для использования в различных радиоуправляемых устройствах (розетках, лампах, воротах, шлагбаумах). Интересен он в первую очередь тем, что использует очень простой ШИМ-подобный метод кодирования информации, заключающийся в том, что логические нули и единицы в сигнале кодируются разным соотношением длительности импульса и тишины:

TinyRF - радиоуправление и обмен данными на ATttiny13

Единичный бит кодируется последовательностью {3; 1}, т.е. сначала в течение 3-х некоторых отсчетов времени предается высокий уровень, а затем 1 отсчет времени — низкий. Нулевой бит имеет обратные значения {1; 3} — 1 высокий импульс и 3 низких. Длина единичного отсчета времени (импульса) у EV1527 составляет 350 микросекунд. Кроме того, перед данными передается особая преамбула, имеющая формулу {1; 31}:

TinyRF - радиоуправление и обмен данными на ATttiny13

Она позволяет приемнику отследить начало передачи данных и начать их принимать. В зависимости от реализации преамбула может передаваться как в начале, так и в конце посылки. Это не играет большой разницы, так как в целях повышения надежности посылки с сообщениями отправляются многократно (10 и более раз подряд). Кодированная данным протоколом единичная посылка из 8 бит двоичного числа 10101010 выглядит как-то так:

TinyRF - радиоуправление и обмен данными на ATttiny13

В стандартном исполнении посылки EV1527 имеют длину 3 байта (24 бит), но что мешает нам в личных целях использовать любой размер передаваемых данных? В идеальных условиях — ничто не мешает, но в реальности слишком длинные сообщения данным методом передавать не выйдет из-за помех и отсутствия какого-либо устранения ошибок. Тем не менее, для отправки коротких посылок с данными на 2-4 байта этот протокол подходит отлично, к тому же он поддерживается сторонними библиотеками для Arduino (например, RCSwitch) поэтому я решил реализовать именно его. Существует множество разновидностей таких протоколов, есть даже трехуровневые (tri-state) вариации, где бит помимо стандартных «0» и «1» может принимать третье значение «F» и называется трит, но я реализовал только самый простой вариант — двоичный.

Программная реализация

Благодаря своей примитивности метод кодирования/декодирования легко реализуется программно и занимает очень мало места. Настолько мало, что код приемника и передатчика можно уместить в ATtiny13 с 1024 байтами памяти, и еще останется место для какой-нибудь полезной работы (!) Я не буду подробно расписывать код, он довольно короткий и хорошо задокументирован. Исходники и примеры доступны здесь и распространяются под MIT лицензией. Как вы догадываетесь, вся библиотека — это один лишь заголовочный файл tinyrf.h.

Процесс отправки и приема сообщений завязан на аппаратном таймере (у ATtiny13 он один единственный), вычисление настроек таймера выполняется на этапе компиляции с учетом тактовой частоты микроконтроллера, поэтому в проекте обязательно должна быть объявлена директива F_CPU с указанием тактовой частоты в герцах (например, 1.2 МГц = 1200000 Гц). В целом поддерживаются все основные частоты (0.6, 1.2, 4.8, 9.6 МГц), на других совместимых микроконтроллерах с другими частотами (например, 16.5 МГц на ATtiny85) тоже нет никаких проблем.

Таймер (а точнее тактовый генератор) у ATtiny может быть очень кривым, поэтому при каких-либо проблемах с приемом/передачей в первую очередь необходимо попробовать подкрутить F_CPU, а в идеале — откалибровать генератор. С этим я столкнулся, когда проверял правильность отсчета времени таймером. Так по мнению ATtiny13, работающего на частоте 4.8МГц, выглядит меандр с шириной импульса 350 микросекунд:

TinyRF - радиоуправление и обмен данными на ATttiny13

Поначалу я грешил на вычислительные затраты, но при понижении до частоты 1.2 МГц ситуация наоборот улучшилась:

TinyRF - радиоуправление и обмен данными на ATttiny13

Разгадка оказалась проста — у многих версий ATtiny13 внутри стоят 2 разных тактовых генератора: на 9.6 МГц и 4.8 МГц, причем первый более-менее откалиброван с завода, а второй — как получится. Частоты 1.2 МГц и 0.6 МГц соответственно получаются с помощью деления на 8, поэтому погрешности сохраняются. Тем не менее, как показали эксперименты, разница в 50мкс оказалась несущественной, поэтому прием/передача практически всегда работают нормально без лишних калибровок и настроек.

Обобщенный пример передачи сообщений:

// Шаг 1: задаем параметры

#define F_CPU 1200000UL // Тактовая частота МК в Гц
#define TRF_TX_PIN PB1 // Пин, к которому подключен передатчик (если не указано - используется PB0)
#define TRF_DATA_SIZE 2 // Размер сообщения в байтах (если не указано - используются 3 байта)
#define TRF_RX_DISABLED // Исключить код приемника для экономии места
// Шаг 2: подключаем библиотеку
#include "tinyrf.h"
// Шаг 3: вызываем инициализацию
// Если ваш код не меняет настройки прерываний и таймера, то вызвать инициализацию можно один раз при запуске
// иначе следует вызывать инициализацию каждый раз перед тем, как хотим отправить сообщение
trf_init();
// Шаг 4: готовим сообщение и отправляем его
uint8_t message[TRF_DATA_SIZE] = { 123, 234 };
trf_send(message);

Обобщенный пример приема сообщений:

// Шаг 1: задаем параметры

#define F_CPU 1200000UL // Тактовая частота МК в Гц
#define TRF_RX_PIN PB0 // Пин, к которому подключен приемник (если не указано - используется PB1)
#define TRF_DATA_SIZE 2 // Размер сообщения в байтах (если не указано - используются 3 байта)
#define TRF_TX_DISABLED // Исключить код передатчика для экономии места
// Шаг 2: подключаем библиотеку
#include "tinyrf.h"
// Шаг 3: вызываем инициализацию
// Если ваш код не меняет настройки прерываний и таймера, то вызвать инициализацию можно один раз при запуске
// иначе следует вызывать инициализацию каждый раз перед тем, как хотим начать принимать сообщения
trf_init();
// Шаг 4: проверка наличия нового сообщения
if (trf_has_received_data()) {
// Шаг 5: подготовка буфера и чтение в него сообщения
uint8_t data_buffer[TRF_DATA_SIZE];
trf_get_received_data(data_buffer);
// Шаг 6: проверка корректности полученного сообщения и выполнение требуемых действий
if (data_buffer[0] == 123 && data_buffer[1] == 234) {
...
}
// Шаг 7: сброс флага наличия сообщения для приема следующего
trf_reset_received();
}

Как видно, некоторые параметры можно конфигурировать с помощью директив, можно даже частично перенастроить сам протокол. Полный список доступных директив можно найти здесь

Примеры использования

От примеров теоретических предлагаю перейти к примерам практическим. В первую очередь помимо микроконтроллеров нам понадобятся передатчики и приемники, я взял самые дешевые на 433.92 МГц:

TinyRF - радиоуправление и обмен данными на ATttiny13

  • Передатчик — FS1000A
  • Приемник — MX-RM-5V

Купить сразу пару можно здесь. Это самые примитивные и дешевые модули, поэтому ждать от них каких-либо выдающихся характеристик не приходится, но для демонстрации вполне сгодятся.

Пример №1 — управляем светодиодами

Исходник. Пример по сути представляет собой реализацию радиореле на 4 канала: передатчик последовательно передает 8 различных команд (4 на включение и 4 на выключение) с интервалом по 0.5 секунды между ними, приемник принимает и зажигает/гасит светодиоды, которых у него 4.

Схема приемника:

TinyRF - радиоуправление и обмен данными на ATttiny13

Для тестов я собрал его из подручных материалов на обрезке макетки:

TinyRF - радиоуправление и обмен данными на ATttiny13

TinyRF - радиоуправление и обмен данными на ATttiny13

Для увеличения дальности следует припаять к приемнику обрезок провода длиной 17.3 см. Скомпилированный с оптимизацией по размеру пример приемника занимает 636 байт (62% памяти микроконтроллера), причем довольно много весит сама логика проверки сообщений. Тем не менее, еще остается достаточно места на добавление каких-либо действий.

Схема передатчика:

TinyRF - радиоуправление и обмен данными на ATttiny13

В качестве антенны — такой же кусок провода:

TinyRF - радиоуправление и обмен данными на ATttiny13

TinyRF - радиоуправление и обмен данными на ATttiny13

Пример работы:

TinyRF - радиоуправление и обмен данными на ATttiny13

Передатчик весит заметно скромнее — всего 286 байт (28%), что дает пространство для маневра и добавления целой кучи своей логики. Дальность уверенной связи — около 20 метров по прямой или в пределах одной комнаты, в первую очередь из-за шумности приемника. Кроме того, как выяснилось, приемник работает нормально только в узком диапазоне напряжений питания (4-5В), в остальных случаях безбожно шумит и не может обеспечить нормальный сигнал на выходе даже при передаче «в упор».

Библиотека совместима с некоторыми другими микроконтроллерами из серии ATtiny, один из них — ATtiny85, который известен тем, что используется в Arduino-совместимой плате Digispark. Это сильно упрощает эксперименты, потому что его можно прошить через обычный USB порт из Arduino IDE без всяких программаторов. Я портировал пример мигания светодиодами в скетч для заливки в Digispark с той лишь разницей, что вместо управления 4-мя внешними диодами в нем переключается только 1 светодиод, встроенный в Digispark (висит на пине PB1). Важно отметить, что в скетчах не нужно указывать частоту в F_CPU, потому что Arduino IDE подставляет ее сама при компиляции.

Схема приемника и передатчика:

TinyRF - радиоуправление и обмен данными на ATttiny13

Пример работы:

TinyRF - радиоуправление и обмен данными на ATttiny13

В приведенном примере представлен простой и немного избыточный, но действенный способ проверки целостности коротких сообщений. Фактически размер команды — 1 байт, вторым байтом в сообщении отправляется инвертированное значение первого:

{ 11, (uint8_t) ~11 }

Приемник предварительно проверяет корректность полученной команды, пытаясь сравнить на равенство:

if (data_buffer[0] == (uint8_t)(~data_buffer[1])) {...}

В случае ошибок при приеме равенство не выполняется, и команда игнорируется. Такой способ проверки достаточно надежен, я поэкспериментировал и не заметил ложных срабатываний в течение нескольких дней непрерывной работы, но в реальных ответственных сценариях использования стоит усилить защиту дополнительными проверками.

Пример №2 — приемник и передатчик в одном флаконе

Исходник. Библиотека довольно компактная и построена таким образом, что использует одни и те же настройки таймера как для приема, так и для передачи сообщений, плюс на время передачи сообщений прерывания по портам отключаются. Благодаря этому коды приема и передачи сообщений можно уместить на одном микроконтроллере, главное подключить приемник и передатчик к разным пинам. Следующий пример демонстрирует простейший эхо-репитер и работает следующим образом:

  • Слушает эфир и ожидает получения 2-байтовой команды (из предыдущего примера)
  • После получения команды выжидает 2 секунды и ретранслирует ее повторно с помощью передатчика
  • Ожидает следующую команду

Схема:

TinyRF - радиоуправление и обмен данными на ATttiny13

Скомпилированный код весит всего 576 байт (56% памяти ATtiny13), что даже меньше, чем в примере управления светодиодами. Как вы догадываетесь, с помощью этого репитера можно увеличить дальность работы предыдущего примера, если разместить его между приемником и передатчиком (предварительно уменьшив время задержки до 250-500 мс).

Версия для Digispark:

TinyRF - радиоуправление и обмен данными на ATttiny13

Чтобы сделать пример более осязаемым предлагаю на примере скетча для Digispark немного видоизменить код повторителя, сделав из него «инвертор»: при получении команды на включение светодиода (11, 22, 33, 44) он будет с задержкой 250 мс передавать в эфир обратную ей команду на выключение (55, 66, 77, 88):

#define TRF_RX_PIN          PB0 // Приемник на PB0

#define TRF_TX_PIN PB1 // Передатчик на PB1
#define TRF_DATA_SIZE 2 // 2-байтовая команда
#include "tinyrf.h"
#include <util/delay.h>
// Команды включения
byte commands[4] = {11, 22, 33, 44};
// Обратные команды выключения
byte inv_commands[4][TRF_DATA_SIZE] = {
{ 55, (byte) ~55 },
{ 66, (byte) ~66 },
{ 77, (byte) ~77 },
{ 88, (byte) ~88 }
};
void setup() {
trf_init();
}
void loop() {
// Получена новая команда
if (trf_has_received_data()) {
// Извлекаем данные
byte data[TRF_DATA_SIZE];
trf_get_received_data(data);
// Проверяем корректность
if (data[0] == (byte)(~data[1])) {
// Ищем обратную команду и отправляем
for (byte i = 0; i < 4; i++) {
if (commands[i] == data[0]) {
_delay_ms(250);
trf_send(inv_commands[i]);
}
}
}
// Разрешаем прием следующей команды
trf_reset_received();
}
}

И если раньше переключение светодиодов происходило так:

TinyRF - радиоуправление и обмен данными на ATttiny13

То со включенным находящимся рядом «инвертирующим повторителем» оно происходит так:

TinyRF - радиоуправление и обмен данными на ATttiny13

Т.е. повторитель принимает «включающие» команды одновременно с основным приемником и с небольшой задержкой перебивает их своими «выключающими».

Итог

Кому-то пост может показаться внезапным и странным, но основная его цель — в очередной раз продемонстрировать, что не стоит списывать со счетов простые железки, ведь при желании из них можно выжать значительно больше. Многие избегали использования в своих проектах ATtiny13 и ему подобных из-за низкой доступности библиотек, но я надеюсь, что с появлением TinyRF, для многих станет одной проблемой меньше. В приведенных примерах показано лишь взаимодействие МК между собой, тем временем TinyRF можно приспособить и для управления некоторыми серийными устройствами вместо пульта, если они используют тот же EV1527-подобный протокол. Этот протокол легко клонируется дешевыми обучаемыми пультами:

TinyRF - радиоуправление и обмен данными на ATttiny13

Поэтому с помощью ATtiny при желании можно добавить радиоуправление в другие существующие устройства — на что хватит фантазии.

TinyRF - радиоуправление и обмен данными на ATttiny13


СМОТРИ ТАКЖЕ

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *