В прошлой статье мы познакомились с контроллером Kincony KC868-A8 и его схемотехникой, в этой статье мы разберём программирование его функциональных блоков (входов, реле, температурных датчиков, Ethernet интерфейса и т. д.). Примеры кода из этой статьи вы сможете использовать в своих проектах на KC868-A8.
Мне нравится и Kincony KC868-A4 и KC868-A8, но наш сегодняшний подопытный с его 8-ю цифровыми входами, 8-ю реле на борту, I2C разъёмом и Ethernet интерфейсом смотрится значительно более внушительно и так и просится в какой-нибудь проект по домашней автоматизации. Поэтому мне было интересно разобраться, что там и как устроено, и как всем этим можно управлять.
Разбор программирования KC868-A8 мы начнём с распиновки ESP32 и выяснения, что и как к нему подключено инженерами компании Kincony.
▍ Распиновка KC868-A8
Для начала давайте ознакомимся с полным списком всех функциональных блоков контроллера. Кроме ESP32 (ESP-WROOM-32) модуля, плата Kincony KC868-A8 содержит:
- 8 цифровых опторазвязанных входов («сухой контакт»);
- 2 аналоговых входа 0–5 В;
- 8 реле 10А 220В;
- 4 контакта для подключения температурных и прочих датчиков;
- Разъёмы для подключения модулей приёмника/передатчика на 433 МГц;
- Разъём I2C;
- Ethernet LAN8270A.
Здесь сразу выделяется «фишка» контроллера KC868-A8 — сетевой Ethernet интерфейс на чипе LAN8270A. Наличие этого интерфейса значительно расширяет возможности контроллера, но за это приходится расплачиваться «потерей» 9 GPIO из и без того крайне скудного их набора.
Проблему нехватки свободных пинов микроконтроллера ESP32 инженеры Kincony решили путём использования расширителей портов — в данном случае 8 цифровых входов и 8 реле подключаются к ESP32 через микросхемы PCF8574P расширителей портов с I2C интерфейсом.
Также стоит отметить появление на плате (по сравнению с младшим братом KC868-A4) I2C разъёма для подключения дополнительного оборудования — это значительно расширяет возможности по адаптации контроллера к задачам ваших конкретных проектов.
Ниже представлена распиновка контроллера KC868-A8. В ближних к контроллеру столбцах указаны обозначения линий и подключений из принципиальной схемы, а в дальних — входы и выходы функциональных блоков контроллера KC868-A8.
Здесь нужно пояснить: на данный момент контроллер Kincony KC868-A8 имеет две версии — базовую и модернизированную V1.4. Сама схема распиновки относится к базовому варианту, а изменения, внесённые в версию V1.4, выделены в виде отдельного блока. В моём распоряжении имеется версия контроллера V1.4, поэтому всё дальнейшее повествование будет относиться к ней.
Ещё пара пояснений. Звёздочками (*) на схеме обозначены соединения, которые «запроектированы» инженерами Kincony, но отсутствуют в реальности на плате (по крайней мере, в базовой версии), а загадочные обозначения «DI9» и «DI10» относятся к «цифровым входам» 9 и 10 (после 8-и опторазвязанных цифровых входов на расширителе портов) — всё это креатив (недоработки или переработки) инженеров компании Kincony.
Теперь, после разбора распиновки контроллера, можно приступать к изучению программирования функциональных блоков Kincony KC868-A8.
▍ Программная среда для KC868-A8
Программировать контроллер Kincony KC868-A8 мы будем в среде Arduino 1.8.5. Я здесь не буду останавливаться на установке поддержки ESP32 в Arduino и прочих подготовительных действиях — в интернете есть огромное количество руководств на эту тему. Для работы с контроллером Kincony KC868-A8 нам нужно выбрать вариант «NodeMCU-32S».
Здесь есть ещё один небольшой, но важный момент: в базовом варианте платы NodeMCU-32S I2C интерфейс завязан на пины 21 и 22, а в нашем контроллере Kincony KC868-A8 это пины 4 и 5. Соответственно, в файле
\hardware\espressif\esp32\variants\nodemcu-32s\pins_arduino.h
нужно поменять старое определение
static const uint8_t SDA = 21;
static const uint8_t SCL = 22;
на соответствующее нашему KC868-A8
static const uint8_t SDA = 4;
static const uint8_t SCL = 5;
иначе ни реле, ни цифровые входы контроллера работать не будут.
Теперь переходим непосредственно к программированию функциональных блоков KC868-A8.
▍ Программирование работы реле
Поскольку для управления работой реле используется расширитель портов на микросхеме PCF8574P, то и обращение к реле в коде отличается от стандартного. Расширитель работает по интерфейсу I2C и для работы с ним нужно установить специальную библиотеку PCF8574_library.
Чип PCF8574P обслуживания реле имеет адрес 0x24 на I2C шине и нужно соответствующим образом инициализировать объект для работы с ним. Сам код управления реле довольно прозрачен:
/*
Kincony KC868-A8
Relays example
*/
#include "Arduino.h"
#include "PCF8574.h"
#define I2C_RELAYS_ADR 0x24
PCF8574 pcf(I2C_RELAYS_ADR);
void setup() {
Serial.begin(115200);
Serial.println(F("Start Kincony KC868-A8 Relays example..."));
pcf.pinMode(P0, OUTPUT);
pcf.pinMode(P1, OUTPUT);
pcf.pinMode(P2, OUTPUT);
pcf.pinMode(P3, OUTPUT);
pcf.pinMode(P4, OUTPUT);
pcf.pinMode(P5, OUTPUT);
pcf.pinMode(P6, OUTPUT);
pcf.pinMode(P7, OUTPUT);
Serial.print("Init PCF8574... ");
if (pcf.begin()){Serial.println(F("Ok"));}
else {Serial.println(F("Error"));}
}
void loop() {
pcf.digitalWrite(P0, HIGH);
Serial.print(F("Relay #")); Serial.print(P0); Serial.println(F(" ON"));
delay(10000);
pcf.digitalWrite(P0, LOW);
Serial.print(F("Relay #")); Serial.print(P0); Serial.println(F(" OFF"));
delay(10000);
}
И для вас не составит труда реализовать любую логику управления реле контроллера Kincony KC868-A8 на его основе. Результат вывода в Serial тестового скетча управления реле через расширитель портов PCF8574P:
▍ Цифровые входы
Контроллер Kincony KC868-A8 имеет 8 опторазвязанных цифровых входов «сухой контакт», которые подключены через I2C расширитель портов PCF8574P. Как и в случае с реле, программирование цифровых входов осуществляется при помощи библиотеки PCF8574_library.
Чип PCF8574P обслуживания цифровых входов имеет адрес 0x22 на I2C шине. Код, как и в случае с реле, тоже прост и прозрачен и не должен вызвать у вас никаких затруднений:
/*
Kincony KC868-A8
Digital input example
*/
#include "Arduino.h"
#include "PCF8574.h"
#define I2C_DIGITAL_ADR 0x22
PCF8574 pcf(I2C_DIGITAL_ADR);
void setup() {
Serial.begin(115200);
Serial.println(F("Start Kincony KC868-A8 Digital input example..."));
pcf.pinMode(P0, INPUT);
pcf.pinMode(P1, INPUT);
pcf.pinMode(P2, INPUT);
pcf.pinMode(P3, INPUT);
pcf.pinMode(P4, INPUT);
pcf.pinMode(P5, INPUT);
pcf.pinMode(P6, INPUT);
pcf.pinMode(P7, INPUT);
Serial.print("Init PCF8574... ");
if (pcf.begin()){Serial.println(F("Ok"));}
else {Serial.println(F("Error"));}
delay(20);
}
void loop() {
byte val1 = pcf.digitalRead(P0);
byte val2 = pcf.digitalRead(P1);
byte val3 = pcf.digitalRead(P2);
byte val4 = pcf.digitalRead(P3);
byte val5 = pcf.digitalRead(P4);
byte val6 = pcf.digitalRead(P5);
byte val7 = pcf.digitalRead(P6);
byte val8 = pcf.digitalRead(P7);
if (val1 == LOW) Serial.println("Key1 pressed");
if (val2 == LOW) Serial.println("Key2 pressed");
if (val3 == LOW) Serial.println("Key3 pressed");
if (val4 == LOW) Serial.println("Key4 pressed");
if (val5 == LOW) Serial.println("Key5 pressed");
if (val6 == LOW) Serial.println("Key6 pressed");
if (val7 == LOW) Serial.println("Key7 pressed");
if (val8 == LOW) Serial.println("Key8 pressed");
delay(300);
}
Результат работы тестового скетча работы с цифровыми входами через I2C расширитель портов PCF8574P:
Всё работает чётко и предсказуемо, так как и должно работать.
▍ Аналоговые входы
Kincony KC868-A8 имеет 2 аналоговых входа для сигналов 0–5 В. С этими входами у KC868-A8 есть некоторая запутанность — в разных версиях платы они разведены по-разному. Мы будем разбирать устройство аналоговых входов для версии V1.4 в которой они подключены к GPIO34 и GPIO35 (в базовой версии это GPIO32 и GPIO33).
Код также предельно прост и сводится к вызову стандартной функции analogRead() и последующему анализу возвращаемых ей значений.
/*
Kincony KC868-A8
Analog example
*/
#include "Arduino.h"
#define ANALOG_A1 34 // IO34 (V1.4)
#define ANALOG_A2 35 // IO35 (V1.4)
void setup() {
Serial.begin(115200);
Serial.println(F("Start Kincony KC868-A8 Analog example..."));
pinMode(ANALOG_A1, INPUT);
pinMode(ANALOG_A2, INPUT);
}
void loop() {
Serial.printf("Current Reading A1 on Pin%d=%d\n", ANALOG_A1, analogRead(ANALOG_A1));
Serial.printf("Current Reading A2 on Pin%d=%d\n", ANALOG_A2, analogRead(ANALOG_A2));
delay(5000);
}
▍ Датчики температуры и влажности
Со входами для датчиков у KC868-A8 тоже есть некоторая путаница — в разных версиях платы они тоже разведены по-разному. В версии V1.4 это пины 14, 13, 32, 33. К этим GPIO могут быть подключены датчики температуры, влажности или любые другие датчики, подходящий по типу подключения. Нужно только помнить, что на плате уже установлена подтяжка линий данных к напряжению питания.
Для работы с датчиками температуры DS18B20 требуются библиотеки DS18B20 и OneWire. Ниже приведён код для одного датчика DS18B20, подключённого к плате KC868-A8 на GPIO14.
/*
Kincony KC868-A8
DS18B20 example
*/
#include <DS18B20.h>
#define LOW_ALARM 30
#define HIGH_ALARM 40
DS18B20 ds(14); // 14, 13, 32, 33 (V1.4)
void setup() {
Serial.begin(115200);
Serial.println(F("Start Kincony KC868-A8 DS18B20 example..."));
ds.doConversion();
while (ds.selectNext()) {
ds.setAlarms(LOW_ALARM, HIGH_ALARM);
}
}
void loop() {
ds.doConversion();
while (ds.selectNextAlarm()) {
Serial.print("Alarm Low: "); Serial.print(ds.getAlarmLow()); Serial.println(" °C");
Serial.print("Alarm High: "); Serial.print(ds.getAlarmHigh()); Serial.println(" °C");
Serial.print("Temperature: "); Serial.print(ds.getTempC()); Serial.println(" °C\n");
}
delay(2000);
}
В коде выставляются пороги срабатывания и, в случае выхода контролируемой температуры за определённые значения, в Serial выводится соответствующее сообщение.
Библиотека DS18B20 содержит различные примеры использования датчиков температуры DS18B20 — вы можете подобрать наиболее подходящий для вашего проекта.
▍ Приёмник/передатчик 433 МГц
Здесь мы не будем подробно останавливаться на использовании приёмника и передатчика на 433 МГц, вы можете прочитать об этом в статье о плате KC868-A4. Упомяну только, что в KC868-A8 приёмник и передатчик подключены на GPIO15 и GPIO2.
А в том случае, если вам не нужна работа с беспроводной передачей данных на 433 МГц, вы можете использовать GPIO15 и GPIO2 для подключения каких-то своих компонентов (см. схемотехнику KC868-A8 из предыдущей статьи).
▍ Ethernet LAN8270A
Контроллер Kincony KC868-A8 имеет на борту чип Ethernet интерфейса LAN8270A и возможность подключения и работы по проводной Ethernet сети. При этом ESP32 даёт возможность работы по беспроводному Wi-Fi интерфейсу. То есть у нас появляется возможность подключать наш контроллер либо по проводному Ethernet, либо по беспроводному Wi-Fi, либо и по тому и по другому интерфейсу одновременно. Это позволяет очень гибко организовать управление контроллером в различных ситуациях (например, в случае потери связи по беспроводному каналу и т. п.).
Использование Ethernet интерфейса совместно с ESP32 довольно просто. Вначале нужно указать соответствующие библиотеки:
#include <ETH.h>
#include <SPI.h>
Затем тип используемой микросхемы:
#define ETH_TYPE ETH_PHY_LAN8720
Пины управления:
#define ETH_CLK_MODE ETH_CLOCK_GPIO17_OUT
#define ETH_MDC_PIN 23
#define ETH_MDIO_PIN 18
И прочие настройки интерфейса:
#define ETH_POWER_PIN -1
#define ETH_ADDR 0
#define NRST 5
Далее идёт код тестового скетча, который инициализирует Ethernet интерфейс и производит запросы к сайту в интернете.
/*
Kincony KC868-A8
Ethernet example
*/
#include <ETH.h>
#include <SPI.h>
#define ETH_CLK_MODE ETH_CLOCK_GPIO17_OUT
#define ETH_POWER_PIN -1
#define ETH_TYPE ETH_PHY_LAN8720
#define ETH_ADDR 0
#define ETH_MDC_PIN 23
#define ETH_MDIO_PIN 18
#define NRST 5
static bool eth_connected = false;
void WiFiEvent(WiFiEvent_t event) {
switch (event) {
case SYSTEM_EVENT_ETH_START:
Serial.println("ETH Started");
ETH.setHostname("esp32-ethernet");
break;
case SYSTEM_EVENT_ETH_CONNECTED:
Serial.println("ETH Connected");
break;
case SYSTEM_EVENT_ETH_GOT_IP:
Serial.print("ETH MAC: "); Serial.print(ETH.macAddress());
Serial.print(", IPv4: "); Serial.print(ETH.localIP());
if (ETH.fullDuplex()) {Serial.print(", FULL_DUPLEX");}
Serial.print(", "); Serial.print(ETH.linkSpeed()); Serial.println("Mbps");
eth_connected = true;
break;
case SYSTEM_EVENT_ETH_DISCONNECTED:
Serial.println("ETH Disconnected");
eth_connected = false;
break;
case SYSTEM_EVENT_ETH_STOP:
Serial.println("ETH Stopped");
eth_connected = false;
break;
default:
break;
}
} // WiFiEvent( )
void testClient(const char *host, uint16_t port) {
Serial.print("\nconnecting to "); Serial.println(host);
WiFiClient client;
if (!client.connect(host, port)) {
Serial.println("connection failed");
return;
}
client.printf("GET / HTTP/1.1\r\nHost: %s\r\n\r\n", host);
while (client.connected() && !client.available());
while (client.available()) {
Serial.write(client.read());
}
Serial.println("closing connection\n");
client.stop();
}
void setup() {
Serial.begin(115200);
Serial.println(F("Start Kincony KC868-A8 Ethernet example..."));
WiFi.onEvent(WiFiEvent);
pinMode(NRST, OUTPUT);
digitalWrite(NRST, 0); delay(200);
digitalWrite(NRST, 1); delay(200);
digitalWrite(NRST, 0); delay(200);
digitalWrite(NRST, 1);
ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE, ETH_CLK_MODE);
}
void loop() {
if (eth_connected) {
testClient("baidu.com", 80);
}
delay(10000);
}
Здесь: инициализируется отслеживание сетевых событий.
WiFi.onEvent(WiFiEvent);
Производится запуск чипа LAN8270A (что в нашем случае излишне, поскольку на KC868-A8 пин NRST вообще не подключён к ESP32).
pinMode(NRST, OUTPUT);
digitalWrite(NRST, 0); delay(200);
digitalWrite(NRST, 1); delay(200);
digitalWrite(NRST, 0); delay(200);
digitalWrite(NRST, 1);
И запускается Ethernet интерфейс с соответствующими параметрами:
ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE, ETH_CLK_MODE);
Далее в цикле отправляются запросы к сайту в интернете:
void loop() {
if (eth_connected) {
testClient("baidu.com", 80);
}
delay(10000);
}
А саму работу по отправке запросов выполняет функция testClient().
void testClient(const char *host, uint16_t port) {
Serial.print("\nconnecting to "); Serial.println(host);
WiFiClient client;
if (!client.connect(host, port)) {
Serial.println("connection failed");
return;
}
client.printf("GET / HTTP/1.1\r\nHost: %s\r\n\r\n", host);
while (client.connected() && !client.available());
while (client.available()) {
Serial.write(client.read());
}
Serial.println("closing connection\n");
client.stop();
}
Громоздкая структура
void WiFiEvent(WiFiEvent_t event) {
switch (event) {
case SYSTEM_EVENT_ETH_START:
Serial.println("ETH Started");
ETH.setHostname("esp32-ethernet");
break;
case SYSTEM_EVENT_ETH_CONNECTED:
Serial.println("ETH Connected");
break;
case SYSTEM_EVENT_ETH_GOT_IP:
Serial.print("ETH MAC: "); Serial.print(ETH.macAddress());
Serial.print(", IPv4: "); Serial.print(ETH.localIP());
if (ETH.fullDuplex()) {Serial.print(", FULL_DUPLEX");}
Serial.print(", "); Serial.print(ETH.linkSpeed()); Serial.println("Mbps");
eth_connected = true;
break;
case SYSTEM_EVENT_ETH_DISCONNECTED:
Serial.println("ETH Disconnected");
eth_connected = false;
break;
case SYSTEM_EVENT_ETH_STOP:
Serial.println("ETH Stopped");
eth_connected = false;
break;
default:
break;
}
} // WiFiEvent( )
отвечает за анализ, интерпретацию и визуализацию сетевых Ethernet событий.
Результат работы тестового скетча контроллера Kincony KC868-A8 по Ethernet интерфейсу:
Всё работает чётко и предсказуемо: в процессе тестирования не было замечено никаких проблем с сетевой работой связки ESP32 и LAN8270A.
▍ Заключение
В этой статье мы рассмотрели примеры программирования различных функциональных блоков контроллера Kincony KC868-A8. Следующую статью мы посвятим разбору более сложных примеров работы с KC868-A8 и подробно разберём проблематику работы по двум интерфейсам — проводному Ethernet и беспроводному Wi-Fi.