OLIMEX ESP32-EVB: программируем непрограммируемое

wgmzjsm7-bkbboskzqfgysc3m08.jpeg

Продолжаем изучение платы OLIMEX ESP32-EVB и сегодня мы поговорим о её программировании. Напомню, что на небольшой квадрат текстолита размером 75×75 мм компания OLIMEX умудрилась поместить микроконтроллер ESP32, Ethernet физику LAN8710A, microSD картридер, IR приёмник и передатчик, CAN трансивер, 2 реле, разъёмы расширения, подсистему зарядки и обслуживания аккумулятора и прочие элементы — все эти компоненты плотно «посажены» на нестандартные GPIO и имеют свои особенности работы

Далее мы попробуем со всем этим разобраться — в результате вы сможете легко и просто использовать ESP32-EVB в своих проектах, а заодно повысите свою квалификацию в программировании и понимание работы контроллеров на ESP32.

Распиновка


Для начала давайте ещё раз посмотрим на распиновку платы ESP32-EVB. Как вы видите, нет ни одного свободного GPIO, незанятого подключением к какому-нибудь компоненту на плате. Подробнее о распределении GPIO мы поговорим в отдельных разделах, посвящённых работе конкретных подсистем ESP32-EVB.

asvguiegyq-olbug9z7asewmu6o.png

План по коду


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

Список подсистем ESP32-EVB:

  1. Кнопка User (BUT1)
  2. Управление реле
  3. Ethernet LAN8710A
  4. microSD картридер
  5. IR приёмник и передатчик
  6. CAN интерфейс


Я буду приводить примеры кода в виде скетчей Arduino, но вы можете использовать для работы с ESP32-EVB вашу любимую среду программирования.

Кнопка User (BUT1)


Начнём с программирования пользовательской кнопки BUT1, подключённой к GPIO34. Работа с кнопкой из скетча Arduino тривиальна и не должна вызывать у вас никаких проблем.

uexuibv-ifxt4krfc_spqej6jm8.jpeg

Код скетча получения состояния кнопки:

/*
  OLIMEX ESP32-EVB Button example
*/

#define BUTTON_PIN 34

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println(F("Start OLIMEX ESP32-EVB Button example..."));
  
  pinMode(BUTTON_PIN, INPUT);
}

void loop() {
  Serial.println(digitalRead(BUTTON_PIN));
  delay(100);
}


Запускаем скетч, нажимаем на кнопку и видим результат наших действий в Serial мониторе: кнопка не нажата — вывод »1», при нажатии выводится »0».

rvorihido0biipbrp4cjqsh8amw.png

Управление реле


Управление реле ненамного сложнее, чем работа с кнопкой. Просто определяем в скетче GPIO, к которым подключены реле (32 и 33) и создаём код, который поочерёдно переключает их. Особенность этого скетча заключается в том, что он не просто поочерёдно переключает два реле, а при небольшой доработке может в режиме «бегущего огня» управлять любым (в разумных пределах, конечно) количеством реле на плате.

ys_cmseq-enkhbnfuc0geu77qxw.jpeg

Код скетча управления реле:

/*
  OLIMEX ESP32-EVB Relays example
*/

byte pins[] = {32, 33};
byte pos = 0;

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println(F("Start OLIMEX ESP32-EVB Relays example..."));

  pinMode(pins[0], OUTPUT);
  pinMode(pins[1], OUTPUT);
}

void clear() {
  digitalWrite(pins[0], LOW);
  digitalWrite(pins[1], LOW);
}

void change(byte n) {
  clear();
  digitalWrite(pins[n], HIGH);
}

void loop() {
  change(pos);
  Serial.print(F("ON Relay #")); Serial.println(pos);
  delay(10000);
  pos++;
  if (pos > 1) {pos = 0;}
}


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

Результат работы нашего скетча (синхронно с выводом в Serial изменяются состояния и самих реле).

c_7mdxfd7s8f71pozrt5gjdd6xu.png

Теперь, после разогрева на простых примерах, давайте перейдём к более сложным задачам программирования компонентов, расположенных на плате ESP32-EVB.

Ethernet LAN8710A


Как я уже заметил в первой статье, на плате ESP32-EVB установлен чип Ethernet физики LAN8710A вместо привычного нам по другим контроллерам и более простого LAN8720A. Программно оба чипа совместимы, поэтому тестовый скетч будет иметь привычный вид.

Исходные данные:

Используем внешний кварц, установленный на плате ESP32-EVB.

#define ETH_CLK_MODE    ETH_CLOCK_GPIO0_IN


Указываем номера GPIO настраиваемых пинов ADDR, MDC и MDIO.

#define ETH_ADDR        0
#define ETH_MDC_PIN     23
#define ETH_MDIO_PIN    18


На плате ESP32-EVB вывод NRST микросхемы LAN8710A подключён к плюсу питания и предусмотрена перемычка для его замыкания на землю, но он не подключён к микроконтроллеру ESP32, поэтому «сбрасываем» в скетче пин NRST на «пустой» GPIO5 (если вы будете использовать CAN интерфейс, то вместо GPIO5 нужно будет указать любой другой «свободный»).

#define NRST            5


tc5og8xkvchckk2ujdexveyuajq.jpeg

Код скетча работы с Ethernet:

/*
  OLIMEX ESP32-EVB Ethernet example
*/

#include 
#include 

#define ETH_CLK_MODE    ETH_CLOCK_GPIO0_IN
#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 setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println(F("Start OLIMEX ESP32-EVB Ethernet example..."));

  WiFi.onEvent(WiFiEvent);

  ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE, ETH_CLK_MODE);
}

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 loop() {
  if (eth_connected) {
    testClient("baidu.com", 80);
  }
  delay(10000);
}


Здесь мы обращаемся к внешнему серверу (www.baidu.com) и получаем от него ответ. На скриншоте видно, что LAN8710A прекрасно работает на плате ESP32-EVB. Таким образом вы можете использовать для связи Wi-Fi и/или Ethernet соединения в своих проектах.

6nujpi-nm_r7bgs1f27ezjr_uu4.png

microSD картридер


Если вы попробуете «завести» microSD картридер на плате OLIMEX ESP32-EVB при помощи стандартной библиотеки и её примеров, то у вас ничего не получится — здесь дело в «мудрёном» подключении GPIO микроконтроллера ESP32 и, плюс к тому (видимо, чтобы с гарантией никто ничего не смог завести), использовании режима «SDMMC_HOST_FLAG_1BIT».

ujsrjmuu1zj2l4kdir_bokyyki0.jpeg

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

Исходные данные:

MISO: GPIO15
MOSI: GPIO2
SCK: GPIO14
CS: GPIO17


Причём на распиновке GPIO17 обозначен как SPI_CS, а на схеме пин CS картридера подключён к плюсу питания. Код работы с картридером:

/*
  OLIMEX ESP32-EVB SD example
*/

#include "FS.h"
#include "SD_MMC.h"
#include "SPI.h"

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println(F("Start OLIMEX ESP32-EVB SD example..."));

  SPI.begin(14, 15, 2, 17);
  if (!SD_MMC.begin("/sdcard", true)) {
    Serial.println("Card mount failed");
    return;
  }
  
  uint8_t cardType = SD_MMC.cardType();
  if (cardType == CARD_NONE){
    Serial.println("No card attached");
    return;
  }

  Serial.print("Card Type: ");
  if      (cardType == CARD_MMC) {Serial.println("MMC");}
  else if (cardType == CARD_SD)  {Serial.println("SDSC");}
  else if (cardType == CARD_SDHC){Serial.println("SDHC");}
  else {Serial.println("UNKNOWN");}

  uint64_t cardSize = SD_MMC.cardSize() / (1024*1024);
  Serial.printf("Card Size: %llu MB\n", cardSize);

  
  listDir(SD_MMC, "/", 0);
  
  Serial.printf("Total: %llu MB\n", SD_MMC.totalBytes() / (1024*1024));
  Serial.printf( "Used: %llu MB\n", SD_MMC.usedBytes()  / (1024*1024));
} // setup

void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {
  Serial.printf("Listing directory: %s\n", dirname);

  File root = fs.open(dirname);
  if (!root)               {Serial.println("Failed open directory"); return;}
  if (!root.isDirectory()) {Serial.println("Not a directory"); return;}

  File file = root.openNextFile();
  
  while (file){
    if (file.isDirectory()) {
      Serial.print("  DIR : "); Serial.println(file.name());
      if (levels){
        listDir(fs, file.name(), levels -1);
      }
    } else {
      Serial.print("  FILE: "); Serial.print(file.name());
      Serial.print("  SIZE: "); Serial.println(file.size());
    }
    file = root.openNextFile();
  }
} // listDir( )

void loop(){

}


Здесь я бы обратил ваше внимание на строку

if (!SD_MMC.begin("/sdcard", true)) {


где «true» и включает режим «SDMMC_HOST_FLAG_1BIT». Ниже я привожу две строчки из библиотеки SD_MMC для пояснения того, как это работает изнутри:

bool begin(const char * mountpoint="/sdcard", bool mode1bit=false, bool format_if_mount_failed=false);
host.flags = SDMMC_HOST_FLAG_1BIT; //use 1-line SD mode


В результате мы можем лицезреть маленькое чудо — работу microSD картридера на плате OLIMEX ESP32-EVB. Значение этого трудно переоценить — возможность использовать microSD карт памяти значительно расширяет функционал ESP32-EVB.

uvnrsbvwr7_yul7voyaf_piilik.png

IR приёмник и передатчик


Плюс ко всем прелестям, ESP32-EVB имеет встроенные инфракрасные (IR) приёмник и передатчик, подключённые, соответственно, на GPIO39 и GPIO12. Это отличное дополнение функционала платы, которое вы, я не сомневаюсь, найдёте как использовать.

khwbpudmkv9bopo2lhdejfi_sti.jpeg

Для работы с инфракрасными приёмником и передатчиком используется библиотека Arduino-IRremote. В тестовом скетче мы примем инфракрасный сигнал от пульта управления, идентифицируем его (производителя оборудования, частоту сигнала, протокол управления и код нажатой клавиши), а также пошлём в эфир записанный нами сигнал, нажимая на функциональную кнопку контроллера ESP32-EVB.

Исходные данные:

  • IR приёмник: GPIO39
  • IR излучатель: GPIO12
  • Функциональная кнопка: GPIO34
  • STATUS_PIN: используется в скетче для индикации событий, но поскольку в ESP32-EVB задействованы все GPIO ESP32 (и нет специального светодиода), то этот пин переопределён на неиспользуемый в данном скетче пин D4 (U1TXD). В реальном проекте вам нужно будет либо найти «свободный» пин для STATUS_PIN, либо переделать скетч и удалить код, ответственный за работу светодиода.


Полный код скетча содержит 2 файла: основной (ir_example.ino) и файл с настройками (PinDefinitionsAndMore.h). В файле PinDefinitionsAndMore.h нужно изменить номера GPIO в соответствии с распиновкой контроллера ESP32-EVB (строки с настройками помечены тремя восклицательными знаками).

#define LED_BUILTIN 4 // !!!
#define IR_RECEIVE_PIN       39  // !!!
#define IR_SEND_PIN          12  // !!!
#define APPLICATION_PIN      34   // !!!


Полный код файла PinDefinitionsAndMore.h
/*
 *  PinDefinitionsAndMore.h
 *
 *  Contains pin definitions for IRremote examples for various platforms
 *  as well as definitions for feedback LED and tone() and includes
 *
 *  Copyright (C) 2021  Armin Joachimsmeyer
 *  armin.joachimsmeyer@gmail.com
 *
 *  This file is part of IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
 *
 *  Arduino-IRremote is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see .
 *
 */

/*
 * Pin mapping table for different platforms
 *
 * Platform     IR input    IR output   Tone
 * -----------------------------------------
 * DEFAULT/AVR  2           3           4
 * ATtinyX5     0           4           3
 * ATtiny167    9           8           5 // Digispark pro number schema
 * ATtiny167    3           2           7
 * ATtiny3217   10          11          3 // TinyCore schema
 * ATtiny1604   2           PA5/3       %
 * SAMD21       3           4           5
 * ESP8266      14 // D5    12 // D6    %
 * ESP32        15          4           27
 * BluePill     PA6         PA7         PA3
 * APOLLO3      11          12          5
 */

#define LED_BUILTIN 4 // !!!
 
//#define _IR_MEASURE_TIMING // For debugging purposes.

#if defined(ESP8266)
#define FEEDBACK_LED_IS_ACTIVE_LOW // The LED on my board (D4) is active LOW
#define IR_RECEIVE_PIN          14 // D5
#define IR_RECEIVE_PIN_STRING   "D5"
#define IR_SEND_PIN             12 // D6 - D4/pin 2 is internal LED
#define IR_SEND_PIN_STRING      "D6"
#define _IR_TIMING_TEST_PIN     13 // D7
#define APPLICATION_PIN          0 // D3

#define tone(...) void()      // tone() inhibits receive timer
#define noTone(a) void()
#define TONE_PIN                42 // Dummy for examples using it


#elif defined(ESP32)
#include 
#define TONE_LEDC_CHANNEL        1  // Using channel 1 makes tone() independent of receiving timer -> No need to stop receiving timer.

void tone(uint8_t _pin, unsigned int frequency){
  ledcAttachPin(_pin, TONE_LEDC_CHANNEL);
  ledcWriteTone(TONE_LEDC_CHANNEL, frequency);
}

void tone(uint8_t _pin, unsigned int frequency, unsigned long duration){
  ledcAttachPin(_pin, TONE_LEDC_CHANNEL);
  ledcWriteTone(TONE_LEDC_CHANNEL, frequency);
  delay(duration);
  ledcWriteTone(TONE_LEDC_CHANNEL, 0);
}

void noTone(uint8_t _pin){
  ledcWriteTone(TONE_LEDC_CHANNEL, 0);
}

#define IR_RECEIVE_PIN       39  // !!!
#define IR_SEND_PIN          12  // !!!
#define TONE_PIN             27  // D27 25 & 26 are DAC0 and 1
#define APPLICATION_PIN      34  // !!!


#elif defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_STM32F1)
// BluePill in 2 flavors
// Timer 3 blocks PA6, PA7, PB0, PB1 for use by Servo or tone()
#define IR_RECEIVE_PIN          PA6
#define IR_RECEIVE_PIN_STRING   "PA6"
#define IR_SEND_PIN             PA7
#define IR_SEND_PIN_STRING      "PA7"
#define TONE_PIN                PA3
#define _IR_TIMING_TEST_PIN      PA5
#define APPLICATION_PIN         PA2

#elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
#include "ATtinySerialOut.hpp" // Available as Arduino library "ATtinySerialOut". saves 370 bytes program space and 38 bytes RAM for digistump core
#define IR_RECEIVE_PIN  0
#define IR_SEND_PIN     4 // Pin 2 is serial output with ATtinySerialOut. Pin 1 is internal LED and Pin3 is USB+ with pullup on Digispark board.
#define TONE_PIN        3
#define _IR_TIMING_TEST_PIN 3

#elif defined(__AVR_ATtiny87__) || defined(__AVR_ATtiny167__)
#include "ATtinySerialOut.hpp" // Available as Arduino library "ATtinySerialOut"
// For ATtiny167 Pins PB6 and PA3 are usable as interrupt source.
#  if defined(ARDUINO_AVR_DIGISPARKPRO)
#define IR_RECEIVE_PIN   9 // PA3 - on Digispark board labeled as pin 9
//#define IR_RECEIVE_PIN  14 // PB6 / INT0 is connected to USB+ on DigisparkPro boards
#define IR_SEND_PIN      8 // PA2 - on Digispark board labeled as pin 8
#define TONE_PIN         5 // PA7
#define _IR_TIMING_TEST_PIN 10 // PA4
#  else
#define IR_RECEIVE_PIN  3
#define IR_SEND_PIN     2
#define TONE_PIN        7
#  endif

#elif defined(__AVR_ATtiny88__) // MH-ET Tiny88 board
#include "ATtinySerialOut.hpp" // Available as Arduino library "ATtinySerialOut". Saves 128 bytes program space
// Pin 6 is TX pin 7 is RX
#define IR_RECEIVE_PIN   3 // INT1
#define IR_SEND_PIN      4
#define TONE_PIN         9
#define _IR_TIMING_TEST_PIN 8

#elif defined(__AVR_ATtiny3217__)
#define IR_RECEIVE_PIN  10
#define IR_SEND_PIN     11
#define TONE_PIN         3
#define APPLICATION_PIN  5

#elif defined(__AVR_ATtiny1604__)
#define IR_RECEIVE_PIN   2 // To be compatible with interrupt example, pin 2 is chosen here.
#define IR_SEND_PIN      3
#define APPLICATION_PIN  5

#define tone(...) void()      // Define as void, since TCB0_INT_vect is also used by tone()
#define noTone(a) void()
#define TONE_PIN        42 // Dummy for examples using it

#  elif defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) \
|| defined(__AVR_ATmega644__) || defined(__AVR_ATmega644P__) \
|| defined(__AVR_ATmega324P__) || defined(__AVR_ATmega324A__) \
|| defined(__AVR_ATmega324PA__) || defined(__AVR_ATmega164A__) \
|| defined(__AVR_ATmega164P__) || defined(__AVR_ATmega32__) \
|| defined(__AVR_ATmega16__) || defined(__AVR_ATmega8535__) \
|| defined(__AVR_ATmega64__) || defined(__AVR_ATmega128__) \
|| defined(__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) \
|| defined(__AVR_ATmega8515__) || defined(__AVR_ATmega162__)
#define IR_RECEIVE_PIN      2
#define IR_SEND_PIN        13
#define TONE_PIN            4
#define APPLICATION_PIN     5
#define ALTERNATIVE_IR_FEEDBACK_LED_PIN 6 // E.g. used for examples which use LED_BUILDIN for example output.
#define _IR_TIMING_TEST_PIN  7

#elif defined(ARDUINO_ARCH_APOLLO3)
#define IR_RECEIVE_PIN  11
#define IR_SEND_PIN     12
#define TONE_PIN         5

#elif defined(ARDUINO_ARCH_MBED) // Arduino Nano 33 BLE
#define IR_RECEIVE_PIN      2
#define IR_SEND_PIN         3
#define TONE_PIN            4
#define APPLICATION_PIN     5
#define ALTERNATIVE_IR_FEEDBACK_LED_PIN 6 // E.g. used for examples which use LED_BUILDIN for example output.
#define _IR_TIMING_TEST_PIN  7

#elif defined(TEENSYDUINO)
#define IR_RECEIVE_PIN      2
#define IR_SEND_PIN         3
#define TONE_PIN            4
#define APPLICATION_PIN     5
#define ALTERNATIVE_IR_FEEDBACK_LED_PIN 6 // E.g. used for examples which use LED_BUILDIN for example output.
#define _IR_TIMING_TEST_PIN  7

#elif defined(__AVR__) // Default as for ATmega328 like on Uno, Nano etc.
#define IR_RECEIVE_PIN      2 // To be compatible with interrupt example, pin 2 is chosen here.
#define IR_SEND_PIN         3
#define TONE_PIN            4
#define APPLICATION_PIN     5
#define ALTERNATIVE_IR_FEEDBACK_LED_PIN 6 // E.g. used for examples which use LED_BUILDIN for example output.
#define _IR_TIMING_TEST_PIN  7

#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_SAM)
#define IR_RECEIVE_PIN      2
#define IR_SEND_PIN         3
#define TONE_PIN            4
#define APPLICATION_PIN     5
#define ALTERNATIVE_IR_FEEDBACK_LED_PIN 6 // E.g. used for examples which use LED_BUILDIN for example output.
#define _IR_TIMING_TEST_PIN  7

// On the Zero and others we switch explicitly to SerialUSB
#define Serial SerialUSB

// Definitions for the Chinese SAMD21 M0-Mini clone, which has no led connected to D13/PA17.
// Attention!!! D2 and D4 are switched on these boards!!!
// If you connect the LED, it is on pin 24/PB11. In this case activate the next two lines.
//#undef LED_BUILTIN
//#define LED_BUILTIN 24 // PB11
// As an alternative you can choose pin 25, it is the RX-LED pin (PB03), but active low.In this case activate the next 3 lines.
//#undef LED_BUILTIN
//#define LED_BUILTIN 25 // PB03
//#define FEEDBACK_LED_IS_ACTIVE_LOW // The RX LED on the M0-Mini is active LOW

#elif defined (NRF51) // BBC micro:bit
#define IR_RECEIVE_PIN      2
#define IR_SEND_PIN         3
#define APPLICATION_PIN     1
#define _IR_TIMING_TEST_PIN  4

#define tone(...) void()    // no tone() available
#define noTone(a) void()
#define TONE_PIN           42 // Dummy for examples using it

#else
#warning Board / CPU is not detected using pre-processor symbols -> using default values, which may not fit. Please extend PinDefinitionsAndMore.h.
// Default valued for unidentified boards
#define IR_RECEIVE_PIN      2
#define IR_SEND_PIN         3
#define TONE_PIN            4
#define APPLICATION_PIN     5
#define ALTERNATIVE_IR_FEEDBACK_LED_PIN 6 // E.g. used for examples which use LED_BUILDIN for example output.
#define _IR_TIMING_TEST_PIN  7
#endif // defined(ESP8266)

#if !defined (FLASHEND)
#define FLASHEND 0xFFFF // Dummy value for platforms where FLASHEND is not defined
#endif
/*
 * Helper macro for getting a macro definition as string
 */
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)


Полный код файла ir.ino
/*
  OLIMEX ESP32-EVB IR example

 * ReceiveAndSend.cpp
 * 
 *
 * Record and play back last received IR signal at button press.
 * The logic is:
 * If the button is pressed, send the IR code.
 * If an IR code is received, record it.
 *
 * An example for simultaneous receiving and sending is in the UnitTest example.
 *
 * An IR detector/demodulator must be connected to the input IR_RECEIVE_PIN.
 *
 * A button must be connected between the input SEND_BUTTON_PIN and ground.
 * A visible LED can be connected to STATUS_PIN to provide status.
 *
 * Initially coded 2009 Ken Shirriff http://www.righto.com
 *
 *  This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote.
 *
 */

#include 
#include "PinDefinitionsAndMore.h"

//#define EXCLUDE_EXOTIC_PROTOCOLS // saves around 900 bytes program space

#include 

int SEND_BUTTON_PIN = APPLICATION_PIN;
int STATUS_PIN = LED_BUILTIN;

int DELAY_BETWEEN_REPEAT = 50;

// On the Zero and others we switch explicitly to SerialUSB
#if defined(ARDUINO_ARCH_SAMD)
#define Serial SerialUSB
#endif

struct storedIRDataStruct { // Storage for the recorded code
  IRData receivedIRData;
  // extensions for sendRaw
  uint8_t rawCode[RAW_BUFFER_LENGTH]; // durations if raw
  uint8_t rawCodeLength;              // length of code
} sStoredIRData;

int lastButtonState;

void storeCode(IRData *aIRReceivedData);
void sendCode(storedIRDataStruct *aIRDataToSend);

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println(F("Start OLIMEX ESP32-EVB IR example..."));
  
  // Just to know which program is running on my Arduino
  //Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_IRREMOTE));

  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK); // Start the receiver, enable feedback LED, take LED feedback pin from the internal boards definition
  IrSender.begin(IR_SEND_PIN, ENABLE_LED_FEEDBACK); // Specify send pin and enable feedback LED at default feedback LED pin

  pinMode(STATUS_PIN, OUTPUT);
  pinMode(SEND_BUTTON_PIN, INPUT); // !!!

} // setup

// Stores the code for later playback in sStoredIRData

void storeCode(IRData *aIRReceivedData) {
  if (aIRReceivedData->flags & IRDATA_FLAGS_IS_REPEAT)      {Serial.println(F("Ignore repeat")); return;}
  if (aIRReceivedData->flags & IRDATA_FLAGS_IS_AUTO_REPEAT) {Serial.println(F("Ignore autorepeat")); return;}
  if (aIRReceivedData->flags & IRDATA_FLAGS_PARITY_FAILED)  {Serial.println(F("Ignore parity error")); return;}

  sStoredIRData.receivedIRData = *aIRReceivedData; // Copy decoded data

  if (sStoredIRData.receivedIRData.protocol == UNKNOWN) {
    Serial.print(F("Received unknown code and store "));
    Serial.print(IrReceiver.decodedIRData.rawDataPtr->rawlen - 1);
    Serial.println(F(" timing entries as raw "));
    
    IrReceiver.printIRResultRawFormatted(&Serial, true); // output the results in RAW format
    
    sStoredIRData.rawCodeLength = IrReceiver.decodedIRData.rawDataPtr->rawlen - 1;
    IrReceiver.compensateAndStoreIRResultInArray(sStoredIRData.rawCode); // store current raw data in dedicated array for later usage
  } else {
    IrReceiver.printIRResultShort(&Serial);
    sStoredIRData.receivedIRData.flags = 0; // clear flags -esp. repeat- for later sending
    Serial.println();
  }
} // storeCode( )

void sendCode(storedIRDataStruct *aIRDataToSend) {
  if (aIRDataToSend->receivedIRData.protocol == UNKNOWN) { // raw
    IrSender.sendRaw(aIRDataToSend->rawCode, aIRDataToSend->rawCodeLength, 38); // 38 KHz
    Serial.print(F("Sent raw "));
    Serial.print(aIRDataToSend->rawCodeLength);
    Serial.println(F(" marks or spaces"));
  } else {
    IrSender.write(&aIRDataToSend->receivedIRData, NO_REPEATS); // write func switch for different protocols
    Serial.print(F("Sent: "));
    printIRResultShort(&Serial, &aIRDataToSend->receivedIRData);
  }
}

void loop() {
  int buttonState = digitalRead(SEND_BUTTON_PIN); // active LOW
  // Serial.print(F("=>")); Serial.println(buttonState); !!!
  if (lastButtonState == LOW && buttonState == HIGH) {
    Serial.println(F("Button released"));
    IrReceiver.start(); // re-enable receiver
  }

  // Check for static button state

  if (buttonState == LOW) {
    IrReceiver.stop();
    
    // Button pressed send stored data or repeat
    Serial.println(F("Button pressed, now sending"));
    digitalWrite(STATUS_PIN, HIGH);
    if (lastButtonState == buttonState) {
      sStoredIRData.receivedIRData.flags = IRDATA_FLAGS_IS_REPEAT;
    }
    sendCode(&sStoredIRData);
    digitalWrite(STATUS_PIN, LOW);
    delay(DELAY_BETWEEN_REPEAT); // Wait a bit between retransmissions
  } else if (IrReceiver.available()) { // Button is not pressed, check for incoming data
    storeCode(IrReceiver.read());
    IrReceiver.resume();
  }

  lastButtonState = buttonState;
} // loop


Результат работы скетча: сначала мы принимаем IR сигнал от пульта и декодируем его, а затем посылаем в эфир (дублируем), нажимая на кнопку «User» контроллера ESP32-EVB.

tzn76w98d9umvdnvmmn5a7orwac.png

CAN интерфейс


И последняя подсистема ESP32-EVB, которую мы разберём в этой статье — это CAN интерфейс. Для работы нам понадобится библиотека arduino-esp32-can-demo. Тестовый скетч принимает данные по CAN протоколу и отсылает по нему свои сообщения.

-jh6l__enqmxtlehhayxmsky4yu.jpeg

Код скетча работы с CAN интерфейсом:

/*
  OLIMEX ESP32-EVB CAN example
*/

#include 
#include 

CAN_device_t CAN_cfg;

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println(F("Start OLIMEX ESP32-EVB CAN example..."));
  
  CAN_cfg.speed     = CAN_SPEED_1000KBPS;
  CAN_cfg.tx_pin_id = GPIO_NUM_5;
  CAN_cfg.rx_pin_id = GPIO_NUM_35;
  CAN_cfg.rx_queue  = xQueueCreate(10, sizeof(CAN_frame_t));
  ESP32Can.CANInit();
} // setup

void loop() {
  CAN_frame_t rx_frame;
  
  //receive next CAN frame from queue
  if (xQueueReceive(CAN_cfg.rx_queue,&rx_frame, 3*portTICK_PERIOD_MS) == pdTRUE) {
    if (rx_frame.FIR.B.FF == CAN_frame_std)
      printf("New standard frame");
    else
      printf("New extended frame");

    if (rx_frame.FIR.B.RTR == CAN_RTR)
      printf(" RTR from 0x%08x, DLC %d\r\n",rx_frame.MsgID, rx_frame.FIR.B.DLC);
    else {
      printf(" from 0x%08x, DLC %d\n",rx_frame.MsgID, rx_frame.FIR.B.DLC);
      for (int i = 0; i < 8; i++) {
        printf("%c\t", (char)rx_frame.data.u8[i]);
      }
      printf("\n");
    }
  } else {
    rx_frame.FIR.B.FF = CAN_frame_std;
    rx_frame.MsgID = 1;
    rx_frame.FIR.B.DLC = 8;
    
    rx_frame.data.u8[0] = 'h';
    rx_frame.data.u8[1] = 'e';
    rx_frame.data.u8[2] = 'l';
    rx_frame.data.u8[3] = 'l';
    rx_frame.data.u8[4] = 'o';
    rx_frame.data.u8[5] = 'c';
    rx_frame.data.u8[6] = 'a';
    rx_frame.data.u8[7] = 'n';

    ESP32Can.CANWriteFrame(&rx_frame);
  }
} // loop


Для нас главным участком скетча является конфигурация CAN протокола для нашей платы ESP32-EVB, где, в частности, указываются TX (5) и RX (35) пины интерфейса.

  CAN_cfg.speed     = CAN_SPEED_1000KBPS;
  CAN_cfg.tx_pin_id = GPIO_NUM_5;
  CAN_cfg.rx_pin_id = GPIO_NUM_35;
  CAN_cfg.rx_queue  = xQueueCreate(10, sizeof(CAN_frame_t));
  ESP32Can.CANInit();


Далее вы можете модифицировать этот скетч под ваши задачи или поискать в интернете более продвинутые примеры работы с CAN протоколом.

Заключение


До этого цикла статей, в интернете не существовало вменяемого руководства по контроллеру OLIMEX ESP32-EVB и его программированию, теперь, воспользовавшись информацией из этой и предыдущей статей, вы можете с лёгкостью применять OLIMEX ESP32-EVB для реализации своих IoT проектов.

С чем я всех нас (и компанию OLIMEX, которая наконец-то получила руководство по своему контроллеру ESP32-EVB, через 6 лет после начала его производства) и поздравляю.

p-u9l27ynelxi92bcmdxhu76ma8.png

© Habrahabr.ru