СоХабр закрыт.

С 13 мая 2019 года он не сканирует новые посты.
С 20 мая 2019 года не перенесённые в черновики посты теперь редиректят на Хабр.

| сохранено

H Arduino + Raspberry Pi. Радиосвязь в черновиках

Подключение датчиков к Raspberry Pi по радиоканалу.



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

Большинство датчиков легче всего подключаются к Arduino, за счет наличия библиотек, безгеморройных PWM, ADC и прочих вкусностей.
Но слишком умную логику на Arduino не построишь, и волей-неволей возникает необходимость использования чего-то более быстрого, универсального, и с простой индикацией\управлением. Например, почти ставший стандартом де-факто, Raspberry Pi.
У него есть HDMI\AV выход. Есть флешка, сеть, память, USB и SSH. image Все что нам нужно.

Но напрямую датчики к Raspberry Pi подключить весьма непросто, в силу отсутствия большого количества библиотек, примеров, и просто потому что она не совсем к этому приспособлена. Да и держать эти датчики тогда придется рядом, а значит либо Малинке придется жить на балконе, либо датчику влажности воздуха у вас в шкафу.

Разумеется, тянуть два-три провода от датчиков к Малине — тоже не вариант, ибо тогда умный дом станет сложным домом.

Интересно?



Для этих целей была придумана радиосвязь.

В качестве самой дешевой, понятной и доступной радиосвязи, я выбрал радиомодуль-трансивер nrf24l01, и вот почему:
1) Дальность. В пределах дома — до ста метров. То что нам подходит.
2) Скорость передачи данных — до 2 Мбит. Хоть потоковое видео передавай.
3) Возможности подключения точка-точка, точки-хаб, хаб-хаб, хаб-точка.
4) Толерантны к 5В, хотя могут принимать и 3.3В.
5) Эти контроллеры имеют свою прошивку, которая позволяет им становиться независимым девайсом.
6) Автоматически шлет идентификатор сигнала, избавляя нас от необходимости заморачиваться с индивидуальной настройкой каналов для каждого трансивера.
7) Дуплекс.
8) Дешевизна. Кстати взял я их не дешево, решил не ждать три недели ради экономии 10 баксов.

Вот так выглядит этот девайс.

image

Топология связи очень проста, дешева и понятна.

1) Набор датчиков в какой-либо локации, подключается к Arduino. Таких локаций может быть несколько.
2) Она (Arduino) играет роль предварительного буфера, который собирает данные с датчиков, и комбинирует их для последующей отправки.
Ну и выполняет какую-то базовую логику, например включает на балконе свет по датчику движения (без участия центрального сервера). В общем согласно наши потребностям.
3) Raspberry Pi через такой же самый модуль, принимает радиосигнал.

В интернете очень много примеров связи Arduino-Raspberry Pi, используя эти трансиверы nrf24l01.
Однако почему-то все эти примеры рассматривают Arduino как центральный узел (хаб), а Raspberry Pi как придаток (клиент). И мало что из примеров работоспособно с первого раза.

Методом проб и ошибок, мне удалось сделать обратное — один Raspberry Pi-блок, и несколько узлов с Ардуинами, которые настраиваются абсолютно одинаково, не нуждаются в выборе каналов, частот и прочего. Поменяли циферку в строке — получили новое устройство.

Несколько фоток из центра событий.


Малиновый сервер. Не удобно подключаться по сети, WiFi рулит.
image

Первый подопечный. Arduino Nano с датчиком движения.
image

Второй подопечный. Arduino Mini и USB-TTL. Где-то там отстает проводок.
image

Все вместе.
image

Итак, не буду вдаваться в подробности установок IDE, библиотек, а выложу сразу работающий код для двух устройств.

Arduino-часть.


#include <SPI.h>
#include <RF24.h>
 
// CE,CSN пины
RF24 radio(9,10);
 
// init counter
 unsigned long count = 0;
int sensor1 = 2;

void setup(void)
{
    // Инициализируем канал связи и не только.
    radio.begin();
    radio.setPALevel(RF24_PA_MAX);
    radio.setChannel(0x4c);
    radio.openWritingPipe(0xF0F0F0F0E1LL);
    radio.enableDynamicPayloads();
    radio.powerUp();
    pinMode(sensor1, INPUT);
    pinMode(3, OUTPUT);
}
 
void loop(void)
{
char outBuffer[32]= "";
int pin1 = digitalRead(sensor1); 
// Следующая строка изменяется по нашему желанию. Я шлю на сервер три значения: ID блока (ардуины), ID пина, и его значение. Больше ничего изменять не нужно.
String out = "dev1:p1:"+pin1;

    out.toCharArray(outBuffer, 32);
    radio.write(outBuffer, 32);
    delay(50);
// Не знаю почему, но периодически трансивер зависает. Помогает повторная инициализация.
    radio.begin();
    radio.setPALevel(RF24_PA_MAX);
    radio.setChannel(0x4c);
    radio.openWritingPipe(0xF0F0F0F0E1LL);
    radio.enableDynamicPayloads();
    radio.powerUp();
    delay(50);

// Следующий костыль включает свет по датчику движения на 10 секунд, не замораживая при этом основной цикл loop.

 if (pin1=HIGH)
 {
 digitalWrite(3, HIGH);  
 count=0;   
 }
count++;
if (count>100)
{
 digitalWrite(3, LOW); 
 count=0;
}
 
}



На Малине нам понадобится библиотека, установить которую можно так:
git clone https://github.com/stanleyseow/RF24.git
cd RF24
cd librf24-rpi/librf24
make
sudo make install


C++ код для Raspberry Pi. В инклуде нужно указать путь к файлу хидера.


#include <cstdlib>
#include <iostream>
#include "../RF24.h"
#include <fstream> 
using namespace std;
 
// spi device, spi speed, ce gpio pin
RF24 radio("/dev/spidev0.0",8000000,25);
 
void setup(void)
{
    // init radio for reading
    radio.begin();
    radio.enableDynamicPayloads();
    radio.setAutoAck(1);
    radio.setRetries(15,15);
    radio.setDataRate(RF24_1MBPS);
    radio.setPALevel(RF24_PA_MAX);
    radio.setChannel(76);
    radio.setCRCLength(RF24_CRC_16);
    radio.openReadingPipe(1,0xF0F0F0F0E1LL);
    radio.startListening();
}
 
void loop(void)
{
char receivePayload[64];
    while (radio.available())
    {
uint8_t len = radio.getDynamicPayloadSize();
radio.read(receivePayload, len);

// Костыль для создания промежуточного файла.
ofstream out("/dev/nrf24");
out << receivePayload << "\n";
out.close ();
delay(200);
    }
}
 
int main(int argc, char** argv) 
{
cout << "Driver initialized, please check values of /dev/nrf24" << endl;
    setup();
    while(1)
        loop();
 
    return 0;
}


Скомпилировать на Малинке мы его можем командой
g++ -Wall -Ofast -mfpu=vfp -mfloat-abi=hard -march=armv6zk -mtune=arm1176jzf-s -L../librf24/  -lrf24 receiver.cpp -o rpi
Не забываем, где мы положили librf24.

Запустив это поделие в фоне, мы получим файл /dev/nrf24, который будет содержать строку, пришедшую к нам по радиоканалу.
Его мы можем читать и парсить как нам заблагорассудится. Хоть read, хоть cat, хоть tail.
Внимание: костыль с этим файлом был введен ТОЛЬКО для «удобства» совместной работы приложений на разных языках: bash, PHP. По феншую все делать только на С++.

На закуску, видео работы двух Arduino с одной Raspberry Pi.




Ради интереса попробовал сформировать строку в JSON, это видно на видео. Неэффективно, если датчиков много. Лимит на 32 символа, а разбираться глубже не было времени. Просто поменять циферку 32 на 64 — не помогает.

Все элементы использую только для наглядности примера. Реальная реализация выглядит по-другому.
+27
11876

комментарии (58)

0
+1 –1
imwode ,  
>Но напрямую датчики к Raspberry Pi подключить весьма непросто, в силу отсутствия большого количества библиотек, примеров
>В интернете очень много примеров связи Arduino-Raspberry Pi, используя эти трансиверы nrf24l01

Серьезно, для распберри отсутствуют билиотеки? learn.adafruit.com/adafruits-raspberry-pi-lesson-12-sensing-movement
+1
+3 –2
ntfs1984 ,  
Вы не прочитали фразу целиком. Датчик движения возвращает 0 или 1, ему даже Ардуина не нужна — можно сразу светодиод цеплять.

А как насчет датчиков громкости звука, изгиба, температуры, EasyVR? Как насчет банального измерителя расстояния?
0
shtirlitsus ,  
банальный измеритель расстояния o0
+3
ntfs1984 ,  
image

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

Да подключим мы датчик температуры к Малине. И датчик влажности тоже. И будет у нас в доме 10 Малинок по 45 баксов каждая, только для того чтоб снимать показания с датчиков.

Смысл поста — в использовании Линуксовых возможностей Малины для обработки показаний УДАЛЕННЫХ датчиков.
0
nikitosk ,  
Все здорово, особенно цена на трансиверы 400р за 10шт. на Али.
Но огорчает присутствие ардуинки.

Вот если бы на малину радиомодуль и потом чтобы датчики соединялись с ним напрямую.

Может вот и есть это оно:
shop.ciseco.co.uk/slice-of-radio-wireless-rf-transciever-for-the-raspberry-pi

Вот и датчики вроде к ней беспроводные:
shop.ciseco.co.uk/sensor/
+1
bit ,  
На мой взгляд наличие на том конце ардуинки (или любого другого микроконтроллера) необходимо ещё и для того, чтобы, к примеру, шифровать передаваемые пакеты.
Вряд ли вам понравится, если живущий в соседней квартире кулхацкер вдруг начнёт управлять вашими жалюзи.
0
MaksMS ,  
В случае использования беспровода на базе nrf24LО1/nrf24LE1 даже без шифрования сложно «взломать» беспроводную сеть без определенных навыков и железа. Не каждый переберет 5 байтный адрес модуля без этого. Плюс ещё надо знать какие режимы передачи используются(AutoAck, Dynamic Payloads, канал, скорость, crc)…
0
shtirlitsus ,  
это если вы пытаетесь это сделать таким же чипом. паранойю опятьже никто не отменял
0
MaksMS ,   * (был изменён)
5) Эти контроллеры имеют свою прошивку, которая позволяет им становиться независимым девайсом.

Давно ли они стали иметь свою прошивку? Программируются только nrf24LE1 и nrf24LU1(USB версия). Про nrf24LE1 кстати сейчас уже все разжевано и программировать их не сложно, а скоро ещё конструктор прошивки будет готов…

Ради интереса попробовал сформировать строку в JSON, это видно на видео. Неэффективно, если датчиков много. Лимит на 32 символа, а разбираться глубже не было времени. Просто поменять циферку 32 на 64 — не помогает.

Как связано количество клиентов с размером пакета? 32 байта это максимально возможный размер пакета для данных устройств. Передача от клиентов должна просто разделена по времени и приемник просто должен распихать данные по полкам.
0
ntfs1984 ,  
Программируются только nrf24LE1
Да, прошу прощения, ошибся. Можно использовать и их, они не намного дороже.
Как связано количество клиентов с размером пакета?
Количество ДАТЧИКОВ подразумевает увеличение длины строки, которую мы передаем в этих 32-х байтах. Если эту строку загромождать еще и дополнительными символами — вообще мало останется для полезной инфы.
За совет по времени, спасибо. Я как-то не подумал…
0
MaksMS ,  
А… Если имеется ввиду несколько датчиков(например температуры, влажности или давления) в одном клиенте LO1, то получается, если передавать данные в float, то можно уместить до 8 датчиков, а если в int, то 16! Это если без дополнительных символов в виде структуры, например:
typedef struct{
 unsigned char identifier;// номер передатчика. Неизменяемые данные
int Analog6; //Аналоговый вход 6
unsigned char termo; //Статус термостата
unsigned char pfcerr; // Контроль низкого питания: 0- питание в норме. 1 - ниже нормы.
int Error_Message; // счетчик ошибок передач.
unsigned long count;// счетчик передач для контроля качества канала.
float temperature_Sensor; //передаём температуру.
float pressure_Sensor;// передаём давление
 }
 nf1;
0
ntfs1984 ,  
Кстати вы не знаете, ограничение в 32 байта — аппаратное, или программное?
+3
MaksMS ,   * (был изменён)
Аппаратное, в даташите много чего интересного написано :)
+2
SunX ,  
А в чем проблема каждый датчик отправлять отдельным пакетом?
0
shtirlitsus ,  
да. с заголовками и проч, запиханным в структуру?
0
SunX ,  
Угу, пакет будет состоять из:
* ID отправляющего устройства (если надо)
* Тип датчика
* ID датчика
* Данные

Учитывая, что данные от датчиков, как правило, отправляются раз в минуту (а то и реже) никакой проблемы с тем, что будет слишком много пакетов возникнуть не должно.
Единственное что — если реализовывать ровно так, как это сделано у автора, то файл /dev/nrf24 будет перезаписываться быстрее, чем его успеют прочитать (я более, чем уверен в этом), но такой подход, на мой взгляд, вообще странный, лучше уж использовать сокет или FIFO.
0
shtirlitsus ,  
фифо, или кадый раз писать в разный файл, с рандомным названием
0
SunX ,  
Каждый раз новый файл — это по мне дак вообще извращение. А почему Вы вариант с сокетом отмели?
0
shtirlitsus ,   * (был изменён)
Можно и сокет. а файлы можно писать в меморидиск, чтоб флешку не мучить. Или форкать обработчик в случае сокета или fifo для уменьшения очереди
0
ntfs1984 ,  
Подход действительно странный, но это всего лишь для наглядности примера, тем более что я не очень силен в Си.
По-хорошему, всю логику нужно делать на нем, тогда необходимость расшаривать данные между разными приложениями отпадает.
0
SunX ,  
Я бы Вам посоветовал все данные класть в SQLite базу данных, благо интерфейс из C++ к ней очень прост. А остальными программами уже оттуда и брать. Как плюс у Вас всегда будут данные за весь период, возможно пригодятся.
0
+1 –1
k0der1 ,  
1) Дальность. В пределах дома — до ста метров. То что нам подходит.

Дома за капитальной стеной теряют связь.
Прямая видимость до 10 м

4) Толерантны к 5В, хотя могут принимать и 3.3В.

На сколько я помню питание у них 3.3 В.
+2
ntfs1984 ,  
Дома за капитальной стеной теряют связь.
Прямая видимость до 10 м
Сэр, я написал о своей практике. Через три стены работает отлично.

На сколько я помню питание у них 3.3 В.
Верно. Но питаются и от пяти тоже.
–2
+1 –3
Melkij ,  
установить которую можно так… sudo make install

Как, опять?
habrahabr.ru/post/130868/
0
ntfs1984 ,  
И что?
Чтобы установить 3.5 либы, вы предлагаете компилировать, и собирать в DEB пакет со всеми правилами?

Оффтоп:

Почитал статью ради интереса. Вообще ни о чем. Мотивации «чтоб потом было легче удалить» явно недостаточно, чтобы пересилить мотивацию «Облом собирать DEB-пакет». И эта мотивация по ходу единственная в той статье.
Касательно моего примера — после написания этой статьи, SD-карточка на той Малине была перетерта раз 5, если не больше, т.е. мне это попросту не нужно.
0
beho1der ,  
А не могли бы еще выложить реальную реализацию, было бы тоже интересно посмотреть!
0
kiltum ,  
А померяйте плиз, сколько оно (ардуинка+модуль) потребляет? Раздумываю, но останавливает убеждение, что такие модули должны жрать чуть-ли не амперы…
0
MaksMS ,  
Если собрать схему правильно, с минимальной обвязкой и использовать спящий режим, то будут микроамперы…
0
kiltum ,  
Ну в спящем понятно. А в активном? Ведь модуль должен проснуться, найти соседей/головной, передать информацию…
0
ntfs1984 ,  
MaksMS прав. В минимальной обвязке у меня Atmega328P-PU. Без ничего.
0
MaksMS ,  
Не замерял режимы работы у Atmegи, но думаю они не хуже чем у модулей nrf24LE1 со встроенным м/к, где в спячке потребление всего 1 микроампер. В режиме передачи до 5-6мА. В большинстве задач устройству достаточно спать и просыпаться, например раз в 5 минут на доли секунды сделать замер и спать дальше.
0
kiltum ,  
5-6мА и с такой антенной до 100м на открытой местности? Как-то я отстал от жизни…
0
MaksMS ,   * (был изменён)
С такой антенной как уже упомянули выше реально меньше в помещении, в реальных условиях, но существуют модули с усилителем и антенной. Есть варианты с керамической антенной — вроде как лучше чем обычная вытравленная из дорожек.
Усиленный модуль можно поставить в приемную-серверную часть, а клиенты могут быть и обычные — уже будет выигрыш в расстоянии
0
SunX ,  
У меня ардуина про мини + пучек датчиков и экран от нокии в активной фазе едят 20мА 5V, активная фаза длится где-то пару секунд, потом все засыпает на минуту.
0
prostosergik ,  
А экран? Он же по-моему сам по себе то ли 2, то ли 5 мА кушает. Или тушите?
0
SunX ,  
Подсветку не использую (ну точнее она включается по кнопочке), а без подсветки он как-то не много ест, приду домой — могу померить точно, сколько он потребляет у меня
0
kireevco ,  
Кто-то уже пользовался ESP8266? Разве не это ли решение без Arduino?
0
MaksMS ,  
Пока без дополнительного микроконтроллера не получится — нет сдк для этого модуля чтобы его под себя перепрограммировать без использования UART… Тоже жду такой.
+1
beho1der ,  
Не могли бы еще выложить схему подключения nrf24l01 к Raspberry Pi.
0
Eddy_Em ,  
Там SPI-интерфейс. Все просто.
+3
Eddy_Em ,  
За delay() убивать надо!
Прерывания для кого придумали? Реализуйте конечные автоматы, а не черт-те что!
0
ntfs1984 ,  
Зачем?
0
Eddy_Em ,  
Поработайте с большим количеством периферии — поймете, зачем.
0
ntfs1984 ,  
Вы хотите, чтобы я искусственно нагрузил устройство большим количеством периферии, чтобы понять, зачем в 20-строчном скрипте юзать прерывания, верно?
+3
Eddy_Em ,  
Просто нужно сразу привыкать делать все по-человечески, а не тяп-ляп.
0
SunX ,  
А научите меня в Пишке использовать прерывания? я пытался, но ничего не удалось в юзер-спэйс приложении
0
Eddy_Em ,  
На «малинке» линукс стоит. Он позаботится о прерываниях. Ваша задача — реализовать несколько потоков и синхронизовать их.
Скажем, как происходит работа с UART? Да элементарно: при помощи select (ну, на любителя: кому-то может eselect больше нравиться, а кому-то poll) проверяем, есть ли данные в буфере порта. Если есть, считываем их и заполняем промежуточный циклический буфер. А его уже обрабатываем: если нужно построчно читать, не принимаем данные, пока не появится конец строки; если посимвольно — считываем очередной символ и обрабатываем.
Можно вообще запихать работу в отдельный поток, который будет блокировать ввод-вывод. Но на работе основного цикла в main() это никак не скажется.
0
SunX ,  
При работе с этим самым радиомодулем все не так хорошо, к сожалению, там нет (или я не нашел) способа использовать это все. Можно конечно работу с ним выделить в отдельный поток, но узнать есть-ли новые данные кроме как спросить у модуля и получить ответ в Линуксе нет, как следствие приходится постоянно этим заниматься.
Либо как-то научиться ловить прерывания с GPIO, это можно сделать пропатчив ядро или написав собственный модуль для этого (или вообще все реализовать в виде модуля) но это, по моему, как-то слишком сложно для такой задачи.
0
Eddy_Em ,  
Вы подключаете радиомодуль по SPI → как только что-то туда написали, так сразу получили ответ.
Вывод: отдельный поток должен постоянно опрашивать радиомодуль на наличие новых данных. Т.е. в отличие от UART'а, здесь просто нужно в цикле писать в SPI данные, затем проверять ответ. Вот в этом потоке пишите свои задержки в каком угодно количестве — на выполнение основного потока они не повлияют!
0
SunX ,  
Ок, но тогда это не сильно отличается от того, что предложил автор, просто у него все приложение занимается лишь тем, что опрашивает радиомодуль, а значит смысла городить отдельный поток отсутствует.
0
Eddy_Em ,  
Ну, я и сам поспешил. Увидев до боли знакомую delay(), решил, что код для микроконтроллера. Потому как в glibc для этих целей используется usleep или select.
0
Eddy_Em ,  
Кстати, посмотрел упомянутую библиотечку по работе с радиомодулями. Впечатлило. Я как раз такие хотел использовать, думал, придется неделю-две тратить, чтобы свой интерфейс реализовать. А тут готовое.
Удобно.
Надо будет только с С++ на С переписать и вынести аппаратно-зависимые интерфейсы отдельно, чтобы базовый код можно было как на одноплатнике, так и на микроконтроллере применять.

Жаль, для работы с ЖК-экранами до сих пор не встречал приличных библиотек. Там уже стырить готовенькое не получится ☹
0
shtirlitsus ,  
lcd4linux?
0
Eddy_Em ,  
Для микроконтроллеров.
0
DarkByte ,  
Раз малина подключается по WiFi, то можно вместо малины использовать ещё одну ардуину, подключенную к этому самому роутеру (либо по usb, либо напрямую к serial порту). Из плюсов получаем отказ от лишнего устройства (малины) и код для работы с радиомодулем получается одинаковым для обоих сторон.
0
ntfs1984 ,  
Не совсем. Скажем так, Малина является мозгами, решающими что делать, на основе показаний датчиков, находящихся на расстоянии.

Например, можно «Включать ли освещение в подъезде, если за окном темно, но нет дождя, и только с 12 ночи до 9 утра, или если у двери движение». т.е. использовать расширенную гибкую логику без перезагрузки скетчей.
0
Eddy_Em ,  
В данном случае, на мой взгляд, «малинка» избыточна. С другой стороны, т.к. цена на нее вполне вкусная, зачастую проще ее использовать, а не городить что-нибудь свое, скажем, на каком-нибудь STM32F407.
А вот если нужно много GPIO, то уже придется выбирать: либо к «малинке» или «кубиборде» подключать микроконтроллеры, либо сразу взять микроконтроллер с нужными интерфейсами.
0
DarkByte ,  
Ну да, это я понял, и предложил в качестве мозгов использовать роутер, который будет использовать ардуину в качестве расширителя интерфейсов. Возможно только у меня малина не отличается особой стабильностью при долговременной работе, но возможностей роутера оказалось вполне достаточно для замены функций, которые выполняла малина и проблем с фантомными зависаниями больше не было.