Информация

Дата основания
1993
Местоположение
США
Сайт
www.epam.com
Численность
свыше 10 000 человек
Дата регистрации

Блог на Хабре

Обновить
EPAM
Компания для карьерного и профессионального роста

Платформа с web-камерой на ESP32

Блог компании EPAMCПрограммирование микроконтроллеровРазработка под ArduinoИнтернет вещей
Идея собрать мобильную платформу с web-камерой на борту появилась практически спонтанно. Мне хотелось иметь в арсенале скромной домашней автоматизации что-то вроде IP-камеры. И тут вопрос не столь в цене или качестве, сколь в своеобразном творческом эксперименте. Материалом для вдохновения были различные статьи DIY и проекты вроде этого.

image

Собранная конструкция выглядит так:



Комплектующие


В качестве базы выступает мобильная двухпалубная робо-платформа Car Chassis 2WD Mini Kit

image

Размеры платформы: 135 мм х 135 мм х 80 мм

Приводом являются два стандартных мотор-колеса с редуктором и двигателем постоянного тока с растровыми дисками для датчиков скорости:

  • номинальный ток: 250 мА макс. при напряжении 3,6 В
  • крутящий момент 800 г/см (при напряжении 6В)
  • напряжение питания: 6 — 8 В
  • скорость вращения без нагрузки: 170 об/мин (при напряжении 3,6 В)
  • передаточное число редуктора: 1: 48
  • оси выходят с двух сторон
  • диаметр осей: 5 мм
  • размеры: 64x20x20 мм
  • вес: 26 г


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

image

Технические параметры:

  • Напряжение питания: 2 — 10 В
  • Рабочий ток на один канал: 1.5 А (пиковый ток 2.5 А, не более 10 секунд)
  • Входной сигнал логика: 5 В
  • Габариты: 24,7 х 21 х 0,5 мм


Для горизонтального и вертикального перемещения IP-камеры выбраны популярные серводвижки SG90 2кг.

image

На сайте производителя представлена следующая спецификация:

  • Weight: 9g
  • Dimension: 23×12.2x29mm
  • Stall torque: 1.8kg/cm(4.8v)
  • Gear type: POM gear set
  • Operating speed: 0.1sec/60degree(4.8v)
  • Operating voltage: 4.8v
  • Temperature range: 0℃_ 55℃
  • Dead band width: 1us
  • Power Supply: Through External Adapter
  • servo wire length: 25 cm
  • Servo Plug: JR (Fits JR and Futaba)


Для web-камеры был выбран держатель FPV Bracket Kit.

image

Описание держателя в интернет-магазине: “FPV позволит ориентировать FPV-камеру в 3-х плоскостях. Простое подключение и управление позволит быстро собрать и подключить платформу к контроллеру или полетному контроллеру. Используется вместе с сервоприводами EMAX 9g ES08A Mini Servo или сервами SG90 (с некоторыми доработками).”
“С некоторыми доработками” — следует учитывать, набор пришлось дорабатывать напильником в прямом смысле слова. Но для DIY за $3 вполне ничего. Некоторые жаловались, что даже доработка не помогла и сервы не подошли по размерам, в моем же случае все нормально. Используется два движка SG90 для горизонтального и вертикального перемещения камеры. Вариант спроектировать и распечатать на 3D-принтере тоже рассматривался, но остановился пока на этом держателе.

IP-камера на базе ESP32 CAM

image

Согласно описанию: “The I2S subsystem in the ESP32 also provides a high speed bus connected directly to RAM for Direct Memory Access. Putting it simply, you can configure the ESP32 I2S subsystem to send or receive parallel data under hardware control.”
То есть можно настроить интерфейс I2S ESP32 для отправки или получения параллельных данных под аппаратным управлением, что и реализовано для подключения камеры. Разработкой данной платы занимается компания Seeed Studio, здесь представлена цена $9.90, но в наших радиомагазинах продают за $8, видимо не только Seeed Studio их может производить.

Технические данные:

  • The smallest 802.11b/g/n Wi-Fi BT SoC Module
  • Low power 32-bit CPU,can also serve the application processor
  • Up to 160MHz clock speed,Summary computing power up to 600 DMIPS
  • Built-in 520 KB SRAM, external 4MPSRAM
  • Supports UART/SPI/I2C/PWM/ADC/DAC
  • Support OV2640 and OV7670 cameras,Built-in Flash lamp.
  • Support image WiFI upload
  • Support TF card
  • Supports multiple sleep modes.
  • Embedded Lwip and FreeRTOS
  • Supports STA/AP/STA+AP operation mode
  • Support Smart Config/AirKiss technology
  • Support for serial port local and remote firmware upgrades (FOTA)


Источник питания


Управление платформы от автономного питания длительное время без подзарядки не предусматривалось. Поэтому в качестве источника был выбран модуль питания 2А на 18650 с USB-выходом с одним слотом.

image

Характеристики:
  • Тип аккумулятора: 18650 Li-Ion (без защиты)
  • Напряжение зарядного устройства: от 5В до 8В
  • Выходные напряжения:
  • 3В — непосредственно с аккумулятора через защитное устройство
  • 5В — через повышающий преобразователь.
  • Максимальный выходной ток:
  • Выход 3В — 1А
  • Выход 5В — 2А
  • Максимальный ток зарядки: 1А
  • Тип входного разъёма: micro-USB
  • Тип выходного разъёма: USB-A
  • Потребителей 5В — от перегрузки и короткого замыкания
  • Габаритные размеры:
  • Печатная плата: 29,5 х 99,5 х 19 мм
  • Всего устройства: 30 х 116 х 20 мм


В качестве основного контроллера выбран ESP-WROOM-32

image

Ранее я описывал характеристики ESP32 более детально. Здесь приведу базовые характеристики модуля:
  • 32-битный двуядерный микропроцессор Xtensa LX6 до 240 МГц
  • Флеш-память: 4 МБ
  • Беспроводная связь Wi-Fi 802.11b/g/n до 150 Мб/c, Bluetooth 4.2 BR/EDR/BLE
  • Поддержка STA/AP/STA+AP режимов, встроенный стек TCP/IP
  • GPIO 32 (UART, SPI, I2C, I2S интерфейсы, ШИМ, SD контроллеры, емкостные сенсорные, АЦП, ЦАП и не только
  • Питание: через microUSB разъем (CP2102 преобразователь) или выводы
  • Шаг выводов: 2.54 мм (можно вставить в макетную плату)
  • Размер платы: 5.2 х 2.8 см


В качестве датчиков скорости применены два оптических энкодеров “Noname” для подсчета импульсов вращения растровых дисков мотор-колеса.

image

Характеристики:
  • Напряжение питания: 3,3В — 5В
  • Ширина паза датчика: 6 мм;
  • Тип выхода: аналоговый и цифровой
  • Индикатор: cостояние выхода


Для измерения расстояния применен популярный ультразвуковой дальномер HC-SR04.

image

Характеристики:
  • Напряжение питания: 5 В
  • Потребление в режиме тишины: 2 мА
  • Потребление при работе: 15 мА
  • Диапазон расстояний: 2–400 см
  • Эффективный угол наблюдения: 15
  • Рабочий угол наблюдения: 30°


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


Первым шагом стало знакомство и прошивка модуля ESP32 CAM.
Описание работы с модулем представлены на Харбе, здесь, здесь, а также на других ресурсах.
В основном статьи описывают простой процесс прошивки с помощью Arduino IDE. В большинстве случает этого достаточно и меня по-началу такой вариант тоже устраивал.

image

В радиомагазинах модули ESP32-CAM продаются с камерой OV2640, поэтому в скетче необходимо сделать небольшое изменение:

// Select camera model
//#define CAMERA_MODEL_WROVER_KIT
//#define CAMERA_MODEL_ESP_EYE
//#define CAMERA_MODEL_M5STACK_PSRAM
//#define CAMERA_MODEL_M5STACK_WIDE
#define CAMERA_MODEL_AI_THINKER


А также указать SSID и пароль к точке доступа Wi-Fi.

const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";


Одним из условий работы web-камеры в моем случае является возможность передавать видеопоток через прокси-сервер Keenetic. Я использую домашний роутер Keenetik Viva. Сервис KeenDNS предоставляет доменное имя домашнему web-ресурсу. Но к моему удивлению первая попытка завершилась неудачей. При попытке удаленного доступа через интернет я получил ошибку «Header fields are too long for server to interpret». С подобной проблемой я столкнулся не первый. Решением вопроса является изменение конфигурации CONFIG_HTTPD_MAX_REQ_HDR_LEN, например:

#define CONFIG_HTTPD_MAX_REQ_HDR_LEN 2048


В случае с Arduino IDE ESP32 модули уже скомпилированы и представлены в виде статических библиотек, которые располагаются в Windows по пути — %userprofile%\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\tools\sdk\
Просто изменение параметра в заголовке ничего не даст.
То есть для изменения конфигурации нам необходимо перекомпилировать библиотеки ESP-IDF.
Решением стало клонирование проекта github.com/espressif/esp-who. В каталоге с примерами находим проект camera_web_server, делаем изменение параметра максимальной длины заголовка, ну а также не забываем указать настройки подключения к Wi-Fi.

image

Для того, чтобы проект скомпилировался, пришлось установить еще один чек-бокс — Support array ‘rtc_gpio_desc’ for ESP32.

image

После успешной компиляции и загрузки проекта переходим по соответствующему IP адресу в браузере и попадаем на страницу с интерфейсом нашей web-камеры.

image

Интерфейс похож на Arduino-примеры, но добавлен некоторый функционал.

Я внес небольшие изменения в исходный файл app_httpd.c для управления сигналом вывода GPIO_NUM_2 с web-интерфейса. Хотя в описании модуля говорится об использовании пинов для нужд SD-карты, но я ее не использую, поэтому могу задействовать данные пины.

void app_httpd_main()
{
	gpio_set_direction(GPIO_NUM_2, GPIO_MODE_OUTPUT);

static esp_err_t cmd_handler(httpd_req_t *req)
{
.......
//строчка 736
else if(!strcmp(variable, "gpio2")) {
    		if (val == 0)
                gpio_set_level(GPIO_NUM_2, 0);
            else
                gpio_set_level(GPIO_NUM_2, 1);
    	}


Для дистанционного управления я наверстал незамысловатую панель на Node-Red, которая крутится на Raspberry pi.

image

Изображение видеопотока удалось встроить в ноду template:

<iframe 
    src="http://192.168.1.61"
    width="300" height="300">
</iframe>


Тут важен один момент: необходимо встраивать именно http, в случае https будут проблемы с Content-Security-Policy. Если же и в этом случае будут возникать проблемы, то можно попробовать добавить заголовки:

<script>
    var meta = document.createElement('meta');
    meta.httpEquiv = "Content-Security-Policy";
    meta.content = "default-src * 'unsafe-inline' 'unsafe-eval'; script-src * 'unsafe-inline' 'unsafe-eval'; connect-src * 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src *; style-src * 'unsafe-inline';";
document.getElementsByTagName('head')[0].appendChild(meta);
</script>


Для управления пином GPIO_NUM_2 модуля ESP32-CAM после внесения изменений в прошивку следует выполнять следующий http GET запрос:

http://192.168.1.61/control?var=gpio2&val=1 //или 0


На интерфейсе панели — это переключатель wakeup, в рабочем потоке все выглядит так

image

где функция request:

var newMsg = {}
var i = msg.payload ? 1 : 0;
newMsg.query = "control?var=gpio2&val=" + i
node.send(newMsg)


Настройки ноды http request:

image

Остальные параметры и статусы передаются по MQTT.

Подключение к Wi-Fi и MQTT


Я приведу примеры, используя Arduino фреймворк, так с ним я также экспериментировал. Но в итоге рабочее приложение у меня на ESP-IDF.

Подключение заголовка #include <WiFi.h>

Функция подключения к Wi-Fi для Arduino фреймворка
void setup_wifi()
{
  Serial.println("Starting connecting WiFi.");
  delay(1000);
  for (int8_t i = 0; i < 3; i++)
  {
    WiFi.begin(ssid, password);
    uint32_t start = millis();
    while (WiFi.status() != WL_CONNECTED && ((millis() - start) < 4000))
    {
      Serial.print(".");
      delay(500);
    }
    if (WiFi.status() == WL_CONNECTED)
    {
      Serial.println("WiFi connected");
      Serial.println("IP address: ");
      Serial.println(WiFi.localIP());
      return;
    }
    else
    {
      Serial.println("Connecting Failed");
      //WiFi.reconnect(); // this reconnects the AP so the user can stay on the page
    }
  }
}



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

Подключение к MQTT для Arduino-фреймворка выполняется с помощью библиотеки github.com/knolleary/pubsubclient.git.

Для использования библиотеки необходимо подключить заголовок #include <PubSubClient.h>

Функция подключения к MQTT
bool setup_mqtt()
{
  if (WiFi.status() == WL_CONNECTED)
  {
    if (!client.connected())
    {
      client.setServer(mqtt_server, 1883);
      client.setCallback(callback);
    }
    Serial.print("Connecting to MQTT server ");
    Serial.print(mqtt_server);
    Serial.println("...");
    String clientId = "ESP32_car_client";
    if (client.connect(clientId.c_str()))
    {
      Serial.println("Connected to MQTT server ");
      //subscribing to topics
      client.subscribe("esp32/car/#");
      client.subscribe("esp32/camera/#");
      return true;
    }
    else
    {
      Serial.println("Could not connect to MQTT server");
      return false;
    }
  }
  return false;
}



Вначале мы проверяем, что подключены к Wi-Fi, затем подключаемся к брокеру client.setServer(mqtt_server, 1883);

И устанавливаем коллбек функцию client.setCallback(callback);

MQTT коллбек функция
void callback(char *topic, byte *payload, unsigned int length)
{
  Serial.println("Message arrived ");
  memset(payload_buf, 0, 10);
  for (int i = 0; i < length; i++)
  {
    payload_buf[i] = (char)payload[i];
  }

  command_t mqtt_command = {
      .topic = topic,
      .message = payload_buf};
  xQueueSend(messageQueue, (void *)&mqtt_command, 0);
}



В случае успешного подключения подписываемся на топики:

client.subscribe("esp32/car/#");
client.subscribe("esp32/camera/#");


Были замечены случаи “отваливания” MQTT соединения, поэтому была добавлена проверка в периодическую задачу опроса.

Периодическая задача опроса
void pollingTask(void *parameter)
{
  int32_t start = 0;

  while (true) {
    if (!client.connected()) {
      long now = millis();
      if (now - start > 5000) {
        start = now;
        // Attempt to reconnect
        if (setup_mqtt()) {
          start = 0;
        }
      }
    }
    else {
      client.loop();
      int val = digitalRead(WAKEUP_PIN);
      if (val == LOW) {
        Serial.println("Going to sleep now");
        esp_deep_sleep_start();
      }
    }
    vTaskDelay(100 / portTICK_PERIOD_MS);
  }
  vTaskDelete(NULL);
}



Пример подключения к Wi-FI и MQTT c помощью ESP-IDF был описан в предыдущей статье.

В случае использования ESP-IDF сбоев при подключении к Wi-Fi и MQTT не наблюдалось. Один нюанс при обработке данных из MQTT топика в функции esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event): при типе события MQTT_EVENT_DATA следует учитывать параметры event->topic_len и event->data_len и брать имя топика и данные именно соответствующей длины, в противном случае мы получим мусор. Для этого мы можем создать буферные массивы или выделить память динамически (затем освободить ее), и скопировать данные, например так:

strncpy(topic, event->topic, event->topic_len);
strncpy(data, event->data, event→data_len);


Отправка данных в топик производится с помощью функции esp_mqtt_client_publish

esp_mqtt_client_publish(client, topics[i], topic_buff[i], 0,0,0);


Обработка данных ультразвукового датчика HC-SR04


HC-SR04 – доступный и популярный датчик для проектирования устройств с микроконтроллерами. Как обычно — в сети куча материала на эту тему: здесь и здесь. Описание также можно посмотреть здесь, а краткий даташит — здесь.
Если коротко, то для начала измерения расстояния необходимо подать высокий сигнал длительностью 10 μs на пин Trig. Это инициирует передачу сенсором 8 циклов ультразвукового импульса с частотой 40 кГц и ожидания отраженного ультразвукового импульса. Когда датчик обнаруживает ультразвуковой сигнал от приемника, он устанавливает для вывода Echo высокий уровень и задержку на период (ширину), пропорциональный расстоянию. Чтобы вычислить расстояние необходимо вычислить формулу:

distance = duration * 340 м/с = duration * 0.034 м/мкс, где

340 м/с — скорость распространения звука в воздухе.

image

В Arduino-фреймворке функция pulseIn позволяет узнать длительность импульса в μs
Для ESP-IDF есть проект ESP-IDF Components library, в котором также есть компонент ultrasonic для HC-SR04.

Пример кода
esp_err_t ultrasonic_measure_cm(const ultrasonic_sensor_t *dev, uint32_t max_distance, uint32_t *distance)
{
    CHECK_ARG(dev && distance);

    PORT_ENTER_CRITICAL;

    // Ping: Low for 2..4 us, then high 10 us
    CHECK(gpio_set_level(dev->trigger_pin, 0));
    ets_delay_us(TRIGGER_LOW_DELAY);
    CHECK(gpio_set_level(dev->trigger_pin, 1));
    ets_delay_us(TRIGGER_HIGH_DELAY);
    CHECK(gpio_set_level(dev->trigger_pin, 0));

    // Previous ping isn't ended
    if (gpio_get_level(dev->echo_pin))
        RETURN_CRITICAL(ESP_ERR_ULTRASONIC_PING);

    // Wait for echo
    int64_t start = esp_timer_get_time();
    while (!gpio_get_level(dev->echo_pin))
    {
        if (timeout_expired(start, PING_TIMEOUT))
            RETURN_CRITICAL(ESP_ERR_ULTRASONIC_PING_TIMEOUT);
    }

    // got echo, measuring
    int64_t echo_start = esp_timer_get_time();
    int64_t time = echo_start;
    int64_t meas_timeout = echo_start + max_distance * ROUNDTRIP;
    while (gpio_get_level(dev->echo_pin))
    {
        time = esp_timer_get_time();
        if (timeout_expired(echo_start, meas_timeout))
            RETURN_CRITICAL(ESP_ERR_ULTRASONIC_ECHO_TIMEOUT);
    }
    PORT_EXIT_CRITICAL;

    *distance = (time - echo_start) / ROUNDTRIP;

    return ESP_OK;
}



В комментариях присутствует объяснение к алгоритму. Измерение длительности импульса происходит в цикле while пока уровень сигнала высокий на Echo пине (после // got echo, measuring) после чего расстояние измеряется:

*distance = (time - echo_start) / ROUNDTRIP

Коэффициент для получения расстояния в сантиметрах ROUNDTRIP = 58.

В Arduino-фреймворке это выглядит еще проще:

Пример кода
#include "ultrasonic.h"

portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
#define PORT_ENTER_CRITICAL portENTER_CRITICAL(&mux)
#define PORT_EXIT_CRITICAL portEXIT_CRITICAL(&mux)

Ultrasonic::Ultrasonic() {
  pinMode(GPIO_NUM_33, OUTPUT);
  pinMode(GPIO_NUM_26, INPUT);
}

uint32_t Ultrasonic::calculateDistance() {
    PORT_ENTER_CRITICAL;
    digitalWrite(GPIO_NUM_33, LOW);
    delayMicroseconds(2);
    digitalWrite(GPIO_NUM_33, HIGH);
    delayMicroseconds(10);
    digitalWrite(GPIO_NUM_33, LOW);
    duration = pulseIn(GPIO_NUM_26, HIGH);
    PORT_EXIT_CRITICAL;
    distance = duration / 58;
    return distance;
}

uint32_t Ultrasonic::getDistance() {
    return distance;
}



Была попытка использования библиотеки ultrasonic ESP-IDF для Arduino проекта ESP32, но работает это дело до первого сбоя датчика. Почему так — точно выяснить не удалось. Сбой датчика — это периодический просчет в импульсах и выдача ложных показаний, в вычисленных цифрах он выглядит как расстояние более 20000 см. На форумах пишут, что это из-за некачественного датчика (китайская копия).

Измерение скорости с помощью оптических датчиков


Оптический модуль считывания импульсов создан на основе компаратора LM393 и щелевого датчика. Предназначен для использования с растровыми дисками, которые одеваются на вал редуктора или электродвигателя.

Как обычно на эту тему уже есть статьи: digitrode.ru, mirrobo.ru, и arduino-kit.ru.

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

image

В Arduino-фреймворке мы вычисляем скорость следующим образом.
Определяем переменную (структуру) счетчика, например:
typedef struct {
  int encoder_pin = ENCODER_PIN; // pulse output from the module
  unsigned int rpm = 0; // rpm reading
  volatile byte pulses = 0; // number of pulses
  unsigned long timeold = 0;
  unsigned int pulsesperturn = 20;
} pulse_t;


Затем в функции setup мы должны зарегистрировать входной пин и прерывание на него:

pinMode(pulse_struct.encoder_pin, INPUT);
attachInterrupt(pulse_struct.encoder_pin, counter, FALLING);


Далее вычисляется количество оборотов в минуту:

pulse_struct.rpm = 
        (60 * 1000 / pulse_struct.pulsesperturn )/ 
        (1000)* pulse_struct.pulses;


Пример кода
void pulseTask(void *parameters) {
  sensor_data_t data;
  data.sensor = OPTICAL_SENSOR;
  portBASE_TYPE xStatus;

   while (true) {
      //Don't process interrupts during calculations
      detachInterrupt(0);
      pulse_struct.rpm = 
        (60 * 1000 / pulse_struct.pulsesperturn )/ 
        (1000)* pulse_struct.pulses;
      pulse_struct.pulses = 0;
      data.value = pulse_struct.rpm;
      //Restart the interrupt processing
      attachInterrupt(0, counter, FALLING);
      Serial.print("optical: ");
      Serial.println(data.value);
     //Sending data to sensors queue
    xStatus = xQueueSend(sensorQueue, (void *)&data, 0);
    if( xStatus != pdPASS ) {
     printf("Could not send optical to the queue.\r\n");
    }
    taskYIELD();
    vTaskDelay(1000 / portTICK_PERIOD_MS);
   }
}



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

Пример кода обрабатываемой задачи
typedef struct {
      uint16_t delay; //delay im ms
      int pin;
      int ctrl_pin;
      pcnt_channel_t channel;
      pcnt_unit_t unit;
      int16_t count;
} speed_sensor_params_t;

void pulseTask(void *parameters) {
  sensor_data_t data_1;
  sensor_data_t data_2;
  data_1.sensor = OPTICAL_SENSOR_1;
  data_2.sensor = OPTICAL_SENSOR_2;
  portBASE_TYPE xStatus;

  speed_sensor_params_t params_1 = {
      .delay = 100,
      .pin = ENCODER_1_PIN,
      .ctrl_pin = GPIO_NUM_0,
      .channel = PCNT_CHANNEL_0,
      .unit = PCNT_UNIT_0,
      .count = 0,
  };
    ESP_ERROR_CHECK(init_speed_sensor(&params_1));

    speed_sensor_params_t params_2 = {
      .delay = 100,
      .pin = ENCODER_2_PIN,
      .ctrl_pin = GPIO_NUM_1,
      .channel = PCNT_CHANNEL_0,
      .unit = PCNT_UNIT_1,
      .count = 0,
  };
    ESP_ERROR_CHECK(init_speed_sensor(&params_2));

    while(true) {
        data_1.value = calculateRpm(&params_1);
        data_2.value = calculateRpm(&params_2);
        sensor_array[OPTICAL_SENSOR_1] = data_1.value;
        sensor_array[OPTICAL_SENSOR_2] = data_2.value;
        printf("speed 1 = %d\n", data_1.value);
        printf("speed 2 = %d\n", data_2.value);
        xStatus = xQueueSend(sensorQueue, (void *)&data_1, 0);
        xStatus = xQueueSend(sensorQueue, (void *)&data_2, 0);
        if( xStatus != pdPASS ) {
        printf("Could not send optical to the queue.\r\n");
        }
        vTaskDelay(100 / portTICK_PERIOD_MS);
}
}



ШИМ управление


Про управление сервоприводами на Arduino можно почитать на developer.alexanderklimov, wiki.amperka.ru.
Как сказано в источнике выше: “Сервопривод — это механизм с электромотором, который может поворачиваться в заданный угол и удерживать текущее положение.” Практически мы имеем дело с широтно-импульсной модуляцией, где от ширины импульса сигнала зависит угол поворота привода.

image

Для ESP32 на Arduino-фреймворке можно использовать библиотеку ESP32Servo.

Для этого мы подключаем заголовок:

#include <ESP32Servo.h>


Создаем объект:

Servo servo_horisontal;


Указываем выходной пин:

 servo_horisontal.attach(SERVO_CAM_HOR_PIN);


После этого можем записывать необходимое значение величины поворота:

servo_horisontal.write(value);


ШИМ управление для других типов устройств на Arduino-фреймворке производится с помощью библиотеки esp32-hal-ledc.h.
Микроконтроллеры ESP32 не поддерживают стандартную функцию Arduino analogWrite() для ШИМ. Вместо их предусмотрены функции:
ledcSetup(channel, freq, resolution_bits) — указываются канал, частота и разрешение;
ledcAttachPin(GPIO, channel) — указываются порт и канал;
ledcWrite(channel, dutycycle) — указываются канал и коэффициент заполнения ШИМ-сигнала.
Примеры можно увидеть по ссылке.
Как видно из названия, изначально функции проектировались для управления светодиодными модулями, но их также используют и для других целей.

В ESP-IDF фреймворке управление серво-приводом осуществляется так же, как и управление коллекторным с использованием модуля MCPWM, как описано в предыдущей статье. Пример MCPWM servo motor control можно посмотреть здесь.

Пример кода
static uint32_t servo_per_degree_init(uint32_t degree_of_rotation)
{
    uint32_t cal_pulsewidth = 0;
    cal_pulsewidth = (SERVO_MIN_PULSEWIDTH + (((SERVO_MAX_PULSEWIDTH -          SERVO_MIN_PULSEWIDTH) * (degree_of_rotation)) / (SERVO_MAX_DEGREE)));
    return cal_pulsewidth;
}

void mcpwm_example_servo_control(void *arg)
{
    uint32_t angle, count;
    //1. mcpwm gpio initialization
    mcpwm_example_gpio_initialize();

    //2. initial mcpwm configuration
    printf("Configuring Initial Parameters of mcpwm......\n");
    mcpwm_config_t pwm_config;
    pwm_config.frequency = 50;    //frequency = 50Hz, i.e. for every servo motor time period should be 20ms
    pwm_config.cmpr_a = 0;    //duty cycle of PWMxA = 0
    pwm_config.cmpr_b = 0;    //duty cycle of PWMxb = 0
    pwm_config.counter_mode = MCPWM_UP_COUNTER;
    pwm_config.duty_mode = MCPWM_DUTY_MODE_0;
    mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);    //Configure PWM0A & PWM0B with above settings
    while (1) {
        for (count = 0; count < SERVO_MAX_DEGREE; count++) {
            printf("Angle of rotation: %d\n", count);
            angle = servo_per_degree_init(count);
            printf("pulse width: %dus\n", angle);
            mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, angle);
            vTaskDelay(10);     //Add delay, since it takes time for servo to rotate, generally 100ms/60degree rotation at 5V
        }
    }
}



То есть нам необходимо инициализировать модуль с помощью функции mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &pwm_config);
А затем задавать значение угла
mcpwm_set_duty_in_us(MCPWM_UNIT_0, MCPWM_TIMER_0, MCPWM_OPR_A, angle);

C примерами использования модуля MCPWM для разных типов привода можно ознакомиться на github.
Пример управления коллекторным двигателем также был представлен в предыдущей статье.

Хочется отметить, что подобная платформа представляет собой дифференциально-управляемую неголономную систему. Двигатели имеют разброс в характеристиках, поэтому приходится задавать программное смещение для одного их них для обеспечения равномерной скорости. Ознакомиться с теорией можно на сайте robotosha.ru. Для оптимального управления мотор-редукторами применен PID-алгоритм с обратной связью в виде оптических датчиков. Описание алгоритма представлено здесь и здесь.
Описание уравнений движения, а также алгоритмов управления выходит за рамки данной статьи. Дифференциальная кинематика еще не реализована в коде.

Sleep Modes


Согласно документации, а также описанию в статье , ESP32 может переключаться между различными режимами питания:
  • Active mode
  • Modem Sleep mode
  • Light Sleep mode
  • Deep Sleep mode
  • Hibernation mode


В таблице приведены различия потребления тока в разных режимах.

image

Я задействовал режим Deep Sleep mode в случае отсутствия высокого сигнала на пине GPIO_NUM_13:

gpio_set_direction(WAKEUP_PIN, GPIO_MODE_INPUT);
esp_sleep_enable_ext0_wakeup(WAKEUP_PIN,1); //1 = High, 0 = Low


В случае отсутствия внешнего воздействия я подтянул вход 10к резистором к 3.3 В, хотя можно и программно. А в задаче периодического опроса проверяю состояние сигнала входа:

if(!gpio_get_level(WAKEUP_PIN)) {
         printf("Going to sleep now\n");
        esp_deep_sleep_start();
    }


На этом буду завершать описание. Выше был показан практический пример использования модулей ESP32 с различной периферией. Также затронуты некоторые вопросы программной реализации и сравнение подходов ESP-IDF и Arduino.
Теги:esp32arduinoESP-IDFCWi-FimqttШИМESP32-CAM
Хабы: Блог компании EPAM C Программирование микроконтроллеров Разработка под Arduino Интернет вещей
Рейтинг +16
Количество просмотров 10,8k Добавить в закладки 72
Комментарии
Комментарии 8

Похожие публикации

Лучшие публикации за сутки