Платформа с web-камерой на ESP32
Идея собрать мобильную платформу с web-камерой на борту появилась практически спонтанно. Мне хотелось иметь в арсенале скромной домашней автоматизации что-то вроде IP-камеры. И тут даже не столько вопрос в цене или в качестве, скорее это можно назвать творческим экспериментом. Материалом для вдохновения были различные статьи DIY и проекты вроде этого
В сборе конструкция выглядит так
Комплектующие
В качестве базы выступает мобильная двухпалубная робо-платформаCar Chassis 2WD Mini Kit
Размеры платформы: 135 мм х 135 мм х 80 мм
Приводом являются два стандартных мотор-колеса с редуктором и двигателем постоянного тока с растровыми дисками для датчиков скорости:
- номинальный ток: 250 мА макс. при напряжении 3,6 В
- крутящий момент 800 г/см (при напряжении 6В)
- напряжение питания: 6 — 8 В
- скорость вращения без нагрузки: 170 об/мин (при напряжении 3,6 В)
- передаточное число редуктора: 1: 48
- оси выходят с двух сторон
- диаметр осей: 5 мм
- размеры: 64×20x20 мм
- вес: 26 г
В качестве драйвера электродвигателя выбран модуль MX1508
Почитать про модуль можно здесь
Технические параметры:
- Напряжение питания: 2 — 10 В
- Рабочий драйвера на один канал: 1.5 А (пиковый ток 2.5 А, не более 10 секунд)
- Входной сигнал логика: 5 В
- Габариты: 24,7×21 х 0,5 мм
Для горизонтального и вертикального перемещения IP камеры выбраны популярные серводвижки SG90 2 кг
На сайте производителя представлена следующая спецификация:
- Weight: 9g
- Dimension: 23×12.2×29mm
- 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
Описание держателя в интернет-магазине: «FPV позволит ориентировать FPV-камеру в 3-х плоскостях. Простое подключение и управление позволит быстро собрать и подключить платформу к контроллеру или полетному контроллеру. Используется вместе с сервоприводами EMAX 9g ES08A Mini Servo или сервами SG90 (с некоторыми доработками).»
«С некоторыми доработками» — следует учитывать, набор пришлось дорабатывать напильником в прямом смысле. Но для DIY за $0.36 вполне себе ничего. Некоторые жаловались, что даже доработка не помогла, и сервы не подошли по размерам, в моем же случае все норм. Используется два движка SG90 для горизонтального и вертикального перемещения камеры. Вариант спроектировать и распечатать на 3D-принтере тоже рассматривался, но остановился пока на этом держателе.
IP Камера на базе ESP32 CAM
Согласно описанию: «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-выходом с одним слотом.
Характеристики:
- Тип аккумулятора: 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
Ранее я описывал характеристики 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» для подсчета импульсов вращения растровых дисков мотор-колеса
Характеристики:
- Напряжение питания: 3,3В — 5В
- Ширина паза датчика: 6 мм;
- Тип выхода: аналоговый и цифровой
- Индикатор: cостояние выхода
Для измерения расстояния применен популярный ультразвуковой дальномер HC-SR04
Характеристики:
- Напряжение питания: 5 В
- Потребление в режиме тишины: 2 мА
- Потребление при работе: 15 мА
- Диапазон расстояний: 2–400 см
- Эффективный угол наблюдения: 15
- Рабочий угол наблюдения: 30°
Программные реализации
Первым шагом стало знакомство и прошивка модуля ESP32 CAM.
Описание работы с модулем представлены на Харбе, здесь, здесь и на других ресурсах.
В основном статьи описывают простой процесс прошивки с помощью Arduino IDE. В большинстве случает этого достаточно, и меня по-началу этот вариант тоже устраивал.
В радиомагазинах модули 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
Для того, чтобы проект скомпилировался, пришлось установить еще один чек-бокс — Support array «rtc_gpio_desc» for ESP32
После успешной компиляции и загрузки проекта переходим по соответствующему IP адресу в браузере и попадаем на страницу с интерфейсом нашей web-камеры.
Интерфейс похож на 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.
Изображение видеопотока удалось встроить в ноду template:
Тут важен один момент: необходимо встраивать именно http, в случае https будут проблемы с Content-Security-Policy. Если же и в этом случае будут возникать проблемы, то можно попробовать добавить заголовки:
Для управления пином GPIO_NUM_2 модуля ESP32-CAM после внесения изменений в прошивку следует выполнять следующий http GET запрос:
http://192.168.1.61/control?var=gpio2&val=1 //или 0
На интерфейсе панели — это переключатель wakeup, в рабочем потоке это выглядит так
где функция request:
var newMsg = {}
var i = msg.payload ? 1 : 0;
newMsg.query = "control?var=gpio2&val=" + i
node.send(newMsg)
Настройки ноды http request:
Остальные параметры и статусы передаются по MQTT
Подключение к Wi-Fi и MQTT
Я приведу примеры, используя Arduino фреймворк, так как я на нем также экспериментировал. Но в итоге рабочее приложение у меня на ESP-IDF.
Подключение заголовка #include
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
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);
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 м/с — скорость распространения звука в воздухе.
В 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.
Схема датчика:
В 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(¶ms_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(¶ms_2));
while(true) {
data_1.value = calculateRpm(¶ms_1);
data_2.value = calculateRpm(¶ms_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.
Как сказано в источнике выше: «сервопривод — это механизм с электромотором, который может поворачиваться в заданный угол и удерживать текущее положение.» Практически мы имеем дело с широтно-импульсной модуляцией, где от ширины импульса сигнала зависит угол поворота привода.
Для ESP32 на Arduino фреймворке можно использовать библиотеку ESP32Servo
Для этого мы подключаем заголовок
#include
Создаем объект
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 robotosha.ru/robotics/robot-motion.html. Для оптимального управления мотор-редукторами применен PID-алгоритм с обратной связью в виде оптических датчиков. Описание алгоритма представлено здесь и здесь.
Описание уравнений движения, а также алгоритмов управления выходит за рамки данной статьи. Дифференциальная кинематика еще не реализованы в коде.
Sleep Modes
Согласно документации, а также описания в статье, ESP32 может переключаться между различными режимами питания:
- Active mode
- Modem Sleep mode
- Light Sleep mode
- Deep Sleep mode
- Hibernation mode
В таблице приведены различия потребления тока в разных режимах.
Я задействовал режим 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.