В двух предыдущих статьях я дал общее описание контроллера Lavritech V7.1 Lite и рассказал о его схемотехнике, сегодня я попытаюсь осветить ещё один важный аспект — программирование этого контроллера.

Всё содержимое Lavritech V7.1 Lite можно условно разделить на три части: ядро (ESP32 и всё, что находится на материнской плате), внутренние подключаемые модули и внешние стыкуемые блоки на DIN-рейку.

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

Итак, как же программировать это чудо техники?

Готовые прошивки


Поскольку ядро контроллера Lavritech V7.1 Lite содержит ESP32, то программировать его можно как угодно в любой из множества поддерживающих его сред программирования. А можно вместо программирования воспользоваться одной из готовых общедоступных прошивок для ESP32.

Сами разработчики придерживаются второго варианта, по их задумке необходимая конфигурация контроллера набирается из типовых блоков и модулей, как в конструкторе Lego, затем заливается готовая прошивка — и контроллер готов к работе.

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

Самих общедоступных прошивок для ESP32 существует множество и на любой вкус. В этой статье я не буду останавливаться на описании работы с готовыми прошивками (в интернете более чем достаточно информации и инструкций по ним). Далее мы поговорим о гораздо более интересной теме — самостоятельном программировании Lavritech V7.1 Lite.

Самостоятельное программирование


Этот вариант подходит не всем, а только тем у кого есть время и желание заниматься программированием. Но зато самостоятельно программируя микроконтроллер, вы получаете полную свободу и можете реализовать все свои «хотелки» и так, как хочется именно вам.

Мне всегда был ближе второй вариант и я всегда предпочитал не «рыбу», а «удочку», поэтому никогда не использовал (и не планирую использовать) готовые прошивки.

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

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

В общем, для меня не стоит вопрос «использовать готовые прошивки или самому запрограммировать Lavritech V7.1 Lite», тем более, что речь идёт о ESP32, который предоставляет для этого все возможности.

Начинаем программировать


Как я уже заметил выше, программировать ESP32 (Lavritech V7.1 Lite) можно в любой из множества сред программирования. Вы можете использовать для этого вашу любимую IDE, я же буду программировать и приводить примеры кода для Arduino (версии 1.8.5).

Программирование контроллеров на ESP32 можно разделить на несколько частей:

0 — программирование самого ESP32
1 — программирование подключённых компонентов
2 — программирование коммуникационных функций
3 — программирование прикладной логики разных уровней
4 — программирование веб-интерфейса (если есть)

В этой статье мы поговорим о 0-м и 1-м аспектах, то есть о программировании самого ESP32 и подключённых к нему компонентов.

Для того, чтобы создать сколько-нибудь функциональную прошивку, сначала нужно научиться работать с отдельными её частями. Далее я последовательно разберу «атомарное» программирование отдельных компонентов Lavritech V7.1 Lite.

Распиновка


Начнём с распиновки. Иллюстрация ниже должна многое сказать вам об устройстве Lavritech V7.1 Lite. Если что-то непонятно, то далее мы будем часто возвращаться к этой распиновке и использовать её в своей работе (я постараюсь дать необходимые пояснения).



Тут мы видим 2 SPI интерфейса для беспроводного LoRa модуля и сетевой платы на W5500, а также многофункциональные GPIO (I36, S1_RX, S1_TX, S3_RX, S3_TX, S3_G1, S3_G2), выведенные в различные разъёмы на материнской плате.

Начнём с разбора программирования Ethernet интерфейса Lavritech V7.1 Lite.

Ethernet интерфейс на W5500




Использование Ethernet интерфейса на W5500 несколько нетипично для контроллеров на ESP32. Обычно в этом качестве используются чипы «физики» LAN8270A. Нельзя сказать, что W5500 это однозначно плохой вариант, сам по себе чип W5500 имеет 8 аппаратных сокетов и прекрасно работает в микроконтроллерах. Возможно, LAN8270A — это более правильный вариант, но и W5500, на мой взгляд, это вполне приемлемо.

В случае Lavritech V7.1 Lite, W5500 «посажен» на нестандартные пины SPI интерфейса MISO (15), MOSI (2), SCK(0) и CS(4), что не смертельно, но потребует от нас некоторых дополнительных телодвижений по их настройке.

Примечание. На ESP32 нам доступны для пользовательского программирования SPI2 (HSPI) и SPI3 (VSPI) с пинами по умолчанию, соответственно 12,13, 14,15 и 5, 18,19, 23.

В качестве примера создадим скетч для получения точного времени с NTP сервера в интернете. Для работы нам понадобится библиотека Ethernet Library for Arduino.

/*
  Ethernet test (Lavritech V7.1 Lite)
*/

#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>

#define ETH_MISO 15
#define ETH_MOSI 2
#define ETH_SCK 0
#define ETH_SS 4

byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED};
EthernetUDP Udp;

unsigned int localPort = 8888;
const char timeServer[] = "time.nist.gov";
const int NTP_PACKET_SIZE = 48;
byte packetBuffer[NTP_PACKET_SIZE];

void setup() {
  Serial.begin(115200);
  Serial.println(F("Start..."));

  SPI.begin(ETH_SCK, ETH_MISO, ETH_MOSI, ETH_SS);

  initEth();
  
  Serial.println(F("loop..."));
} // setup

void initEth() {
  Serial.println(F("initEth..."));
  Ethernet.init(ETH_SS);

  if (Ethernet.begin(mac) == 0) {
    Serial.println(F("Failed to configure Ethernet using DHCP"));
    delay(2000);

    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println(F("Shield not found"));
    }
  
    if (Ethernet.linkStatus() == LinkOFF) {
      Serial.println("Cable not connected.");
    }
    while(true) {}
  } else {
    Serial.println(F("Start Eth & UDP"));
    Udp.begin(localPort);
  }
} // initEth()

void sendNTPpacket(const char * address) {
  memset(packetBuffer, 0, NTP_PACKET_SIZE);

  packetBuffer[0] = 0b11100011;
  packetBuffer[1] = 0;
  packetBuffer[2] = 6;
  packetBuffer[3] = 0xEC;
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  Udp.beginPacket(address, 123); // NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket(); 
}

void parse() {
  if (Udp.parsePacket()) {
    Udp.read(packetBuffer, NTP_PACKET_SIZE);

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    const unsigned long seventyYears = 2208988800UL;
    unsigned long epoch = secsSince1900 - seventyYears;

    Serial.print("UTC time is "); 
    Serial.print((epoch % 86400L) / 3600); Serial.print(":");
    if (((epoch % 3600) / 60) < 10) {Serial.print('0');}
    Serial.print((epoch  % 3600) / 60); Serial.print(':');
    if ((epoch % 60) < 10) {Serial.print('0');}
    Serial.println(epoch % 60); 
  }
}

void checkTime() {
  sendNTPpacket(timeServer);
  delay(2000);
  
  parse();
  Ethernet.maintain();
}

void loop() {
  checkTime();
  //displayTime();
  delay(5000);
}

Здесь периодически посылаются запросы и получаются ответы с точным временем от NTP сервера. Сам скетч разбирать, я думаю, нет смысла — там всё очевидно. Замечу только, что наш Ethernet модуль на W5500 нормально работает в связке с ESP32 на плате Lavritech V7.1 Lite.



Работа с LoRa модулем




Моя ревизия платы Lavritech V7.1 Lite предусматривает установку популярных китайских LoRa модулей (с обратной стороны платы). Плата также содержит SMA разъём для подключения LoRa антенны. Сама по себе полезность наличия беспроводной LoRa связи в контроллере не вызывает сомнений, остаётся только задействовать этот модуль в коде прошивки.

В целом, это несложная и привычная задача, небольшая сложность в данном случае заключается в том, что базовый VSPI уже занят Ethernet интерфейсом, а LoRa модуль «посажен» на нестандартные пины 19 (MISO), 27 (MOSI), 5 (CLK).

В результате нам придётся применить немного магии и переназначить дефолтные пины HSPI интерфейса на нестандартные пины подключённого LoRa модуля.

Также нам нужно определить и задействовать прочие GPIO обслуживания LoRa модуля NSS (21), RST (18) и D0 (12).

/*
  LoRa Sender (Lavritech V7.1 Lite)
*/

#include <SPI.h>
#include <LoRa.h>

#define LORA_MISO 19
#define LORA_MOSI 27
#define LORA_CLK   5
  
#define LORA_NSS  21
#define LORA_RST  18
#define LORA_D0   12

SPIClass hhSPI(HSPI);

long count = 0;

void setup() {
  Serial.begin(115200);
  Serial.println(F("Start LoRa Sender..."));

  hhSPI.begin(LORA_CLK, LORA_MISO, LORA_MOSI, LORA_NSS);
  LoRa.setSPI(hhSPI);
  LoRa.setPins(LORA_NSS, LORA_RST, LORA_D0);

  if (!LoRa.begin(868E6)) {
    Serial.println(F("Starting LoRa failed"));
    while(true);
  }
}

void loop() {
  Serial.print(F("Sending packet: ")); Serial.println(count);

  LoRa.beginPacket();
    LoRa.print(F("Sending packet: ")); LoRa.print(count);
  LoRa.endPacket();

  count++;
  delay(5000);
}

Этот скетч формирует и каждые 5 секунд посылает в эфир LoRa пакеты данных. Другими словами, нам удалось успешно задействовать работу двух устройств (Ethernet и LoRa) на двух SPI интерфейсах ESP32 с подключением к нестандартным номерам GPIO.



И контроль реальных посылок LoRa пакетов в эфир при помощи популярной программы SDRSharp. Всё работает отлично и именно так, как и ожидалось.



Кнопки на плате


На плате Lavritech V7.1 Lite имеются две кнопки, которые пользователь может применять для собственных нужд. Это кнопка USER (SAFE) на GPIO34 и кнопка (переключатель) SWTH на GPIO35. Программирование их тривиально и не требует особых пояснений.

/*
  Keys test (Lavritech V7.1 Lite)
*/

#define KEY_USER 34
#define KEY_SWTH 35

void setup() {
  Serial.begin(115200);
  Serial.println(F("Keys test starting..."));
  
  pinMode(KEY_USER, INPUT);
  pinMode(KEY_SWTH, INPUT);
}

void loop() {
  Serial.print(F("USER: ")); Serial.println(digitalRead(KEY_USER));
  Serial.print(F("SWTH: ")); Serial.println(digitalRead(KEY_SWTH));
  Serial.println();
  delay(1000);
}

Кнопка SWITCH на моей плате отсутствует. Кнопка USER в исходном состоянии выдаёт «1», в при нажатии — «0».



Далее от распаянных на материнской плате компонентов переходим к разбору программирования подключаемых (и, при необходимости, отключаемых) внутренних Wirenboard модулей.

Wirenboard модуль WBE2-DI-DR-3




Вообще, подобных модулей в ассортименте Wirenboard и прочих производителей существует огромное количество, на любой вкус и на любые потребности по автоматизации. В моём распоряжении имеется несколько таких модулей, один из которых WBE2-DI-DR-3 (3 входа «сухой контакт»), с этого модуля мы и начнём разбор их программирования.

Как вы помните из предыдущей статьи, Wirenboard разъёмы на плате Lavritech V7.1 Lite могут быть сконфигурированы либо как I2C, либо как UART (GPIO). В моём случае один из двух Wirenboard разъёмов сконфигурирован как I2C (WB1.2), а второй как UART (WB1.1).



Поскольку модуль WBE2-DI-DR-3 предназначен для работы с обычными линиями GPIO (не I2C), то очевидно, что подключать его нужно именно к WB1.1. Здесь бы не помешала схема самого модуля WBE2-DI-DR-3, но, к сожалению, Wirenboard не раскрывает схемы своих модулей, поэтому ограничимся только общедоступной распиновкой разъёма.



В контексте программирования нас интересуют контакты TX, RX и RTS, выделенные синим цветом. Это ни что иное, как нужные нам (входные со стороны ESP32) линии GPIO. На немного странные названия в данном случае можно не обращать внимания, TX, RX и RTS — это всего лишь обозначения одних из возможных ролей этих GPIO линий.

Чтобы определить к каким конкретно линиям GPIO подключены контакты TX, RX и RTS в разъёме WB1.1 идём на сайт производителя и видим следующую распиновку:



И устанавливаем, что RX это GPIO25, TX это GPIO26, а RTS это GPIO22. Далее — дело техники, создаём соответствующий скетч и как угодно работаем с входными цифровыми сигналами через модуль WBE2-DI-DR-3.

Примечание. Обозначенные зелёным цветом R19, R20 и R26 — это конфигурационные резисторы (перемычки), которые и подключают GPIO25, GPIO26 и GPIO22 к контактам разъёма WB1.1 (видны на фото выше).

/*
  DI test (Wirenboard WBE2-DI-DR-3)
*/

#define DI1 25 // S1_RX
#define DI2 26 // S1_TX
#define DI3 22 // S3_G2

void setup() {
  Serial.begin (115200);  
  Serial.println(F("Start DI test..."));

  pinMode(DI1, INPUT);
  pinMode(DI2, INPUT);
  pinMode(DI3, INPUT);
} 

void loop() {
  Serial.print(F("DI1: ")); Serial.println(digitalRead(DI1));
  Serial.print(F("DI2: ")); Serial.println(digitalRead(DI2));
  Serial.print(F("DI3: ")); Serial.println(digitalRead(DI3));
  Serial.println();
  delay(2000);
}

В целом, если один раз понять логику организации внутренних разъёмов Lavritech V7.1 Lite, то подключение и программирование внутренних Wirenboard и EUHP модулей становится простым и понятным делом.

Wirenboard модуль WBE2-DO-OC-2




Wirenboard модуль WBE2-DO-OC-2 — это 2 выхода «открытый коллектор». Согласно информации с сайта производителя, коммутацией занимается N-канальный полевой транзистор (в составе сборки), максимальное коммутируемое напряжение 40 В постоянного тока, максимальный коммутируемый ток 1 А (на каждый канал).

Подключение WBE2-DO-OC-2 к Lavritech V7.1 Lite тоже не составляет никаких проблем — поскольку речь идёт о простом управлении при помощи GPIO, то устанавливать этот модуль нужно в тот же разъёме WB1.1.

Поскольку такой разъём на плате Lavritech V7.1 Lite всего один, то мы можем использовать либо модуль WBE2-DI-DR-3, либо WBE2-DO-OC-2, но не оба одновременно, для этого нужно уже работать с полной версией контроллера Lavritech V7.1 (без приставки Lite), где в наличии имеется большее количество разъёмов.

Либо нам нужно перепаять резисторы R21-R24 и соответствующим образом переконфигурировать разъём WB1.2 (I2C/UART).


Схема подключения нагрузки к выходам модуля WBE2-DO-OC-2 с сайта Wirenboard.

Скетч управления выходами. Здесь задействованы уже знакомые нам GPIO25 и GPIO26 (GPIO22 «отдыхает»). Выходы модуля подключены на колодку к контактам Q1.1 (DO2) и Q2.1 (DO1).

/*
  DO test (Wirenboard WBE2-DO-OC-2)
*/

#define DO1 25 // S1_RX
#define DO2 26 // S1_TX

#define PERIOD 5000

void setup() {
  Serial.begin (115200);  
  Serial.println(F("Start DO test..."));

  pinMode(DO1, OUTPUT);
  pinMode(DO2, OUTPUT);
}

void loop() {
  Serial.println(F("HIGH"));
  digitalWrite(DO1, HIGH); // Q2
  digitalWrite(DO2, HIGH); // Q1
  
  delay(PERIOD);
  digitalWrite(DO1, LOW);
  digitalWrite(DO2, LOW);
  delay(PERIOD);
}

Этот скетч раз в 5 секунд (синхронно) включает и выключает выходы с открытым коллектором модуля WBE2-DO-OC-2, подключённого к разъёму WB1.1 (региона/сокета S1). Далее с этими выходами можно делать всё, что угодно, в соответствии с логикой решаемой вами задачи по автоматизации.

В моём распоряжении есть ещё два модуля интерфейса RS485 производства Wirenboard (WBE2-I-RS485-ISO) и Lavritech (RS485 V1), но это обширная тема и разбор их подключения и программирования я оставлю для отдельной статьи.

Заключение


В этой статье мы разобрали программирование внутренних модулей контроллера Lavritech V7.1 Lite, в следующей статье я познакомлю вас с программированием внешних подключаемых Wirenboard блоков на DIN-рейку, ассортимент которых поражает воображение своим разнообразием (и позволяет решить чуть ли не любую задачу по автоматизации).

Комментарии (0)