ESP8266/ESP32

How to fix ESP32 ESP_LOGI(…) error: expected ‘)’ before ‘msg’

Problem:

You have code in your project such as

ESP_LOGI("MyTag", msg);

where msg is a const char*, but the project fails to compile with an error message such as

src/main.cpp:280:26: error: expected ')' before 'msg'
  280 |         ESP_LOGI("MyTag", msg);

Solution:

The format parameter can’t be any string but needs to be a string constant. Replace your code by

ESP_LOGI("MyTag", "%s", msg);

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32

How many MCPWM peripherals does the ESP32-S2 have?

The ESP32-S2 has no MCPWM (Motor control PWM) periperal.

However, the related ESP32-S3 features two MCPWM peripherals.

Source: Datasheet

Posted by Uli Köhler in ESP8266/ESP32

Measurement averaging code with fixed time windows for microcontrollers (C++)

This class performs averaging with a fixed time window (variable number of values).

From the platform, it only needs some method to get the current time. This code contains implementations for both ESP-IDF and Arduino

#pragma once
#include <optional>

// Find the correct driver
#ifdef ARDUINO
    #include <Arduino.h>
    #define TTAA_GETTIME_MILLIS millis()
#elif defined(IDF_VER) && !defined(ARDUINO)
    #include <esp_timer.h>
    #define TTAA_GETTIME_MILLIS esp_timer_get_time() / 1000
#else
    #error "Could not determine how to get the current time (Arduino or ESP-IDF)"
#endif

/**
 * @brief This class is designed to accumulate and average values over a specified time threshold.
 * 
 * The class is templated to allow accumulation of different numerical types, defaulting to float. It uses the Arduino `millis()`
 * function to track elapsed time, automatically calculating the average of accumulated values once the predefined time threshold is reached.
 * The `add()` method adds a new value to the accumulation, returning the average as a `std::optional<T>` when the threshold is reached,
 * and `std::nullopt` otherwise. The `lastAverage()` method allows retrieval of the last calculated average at any time.
 */
template<typename T = float>
class TimeThresholdAccumulatingAverager {
public:
    /**
     * Constructs a new TimeThresholdAccumulatingAverager object.
     * 
     * @param threshold The time threshold in milliseconds over which to accumulate and average values.
     */
    TimeThresholdAccumulatingAverager(unsigned long threshold) : _threshold(threshold), _startTime(TTAA_GETTIME_MILLIS), _sum(), _count() {}

    /**
     * Adds a value to the accumulator. If the time threshold has been reached, 
     * calculates and returns the average of the accumulated values since the last threshold reset.
     * Resets the accumulator for the next period if the threshold is reached.
     * 
     * The given value is not added to the current average if the threshold has been
     * exceeded. It is automatically added to the next period's average. 
     * 
     * @param value The value to add to the accumulator.
     * @return std::optional<T> The average of accumulated values if the threshold is reached, otherwise std::nullopt.
     */
    std::optional<T> add(T value) {
        unsigned long currentTime = TTAA_GETTIME_MILLIS;

        if (currentTime - _startTime >= _threshold) {
            // Time threshold exceeded. Calculcate average
            T average = _count > 0 ? _sum / static_cast<T>(_count) : 0;
            _lastCount = _count;
            // Reset sum & count to zero, but add the current value immediatey
            _sum = value;
            _count = 1;
            _startTime = currentTime;
            _lastAverage = average;
            return average;
        } else { // Time threshold not exceeded
            _sum += value;
            _count++;
            return std::nullopt;
        }

    }

    /**
     * Returns the average of the last completed accumulation period.
     * 
     * @return T The average value of the last completed period.
     */
    inline T lastAverage() const {
        return _lastAverage;
    }

    /**
     * Returns the count value of the last completed accumulation period.
     *
     * @return The last count value.
     */
    inline unsigned int lastCount() const {
        return _lastCount;
    }

private:
    unsigned long _threshold;
    unsigned long _startTime;
    T _sum;
    int _count = 0;
    T _lastAverage = 0;
    unsigned int _lastCount = 0;
};

Usage example:

#include <optional>
#include <iostream>

#include "TimeThresholdAccumulatingAverager.hpp"

int main() {
    TimeThresholdAccumulatingAverager<float> averager(1000); // 1 second threshold

    while (true) {
        float newValue = acquireValue(); // Pseudocode for acquiring a new value
        auto maybeAverage = averager.add(newValue);

        if (maybeAverage.has_value()) {
            std::cout << "Average over threshold period: " << average.value() << std::endl;
        }

        // Add a delay or wait for a real new value in a real application
        delay(10);
    }
}

 

Posted by Uli Köhler in Arduino, Embedded, ESP8266/ESP32, PlatformIO

How to find out if ESP-IDF framework is used using the preprocessor (PlatformIO)

You can use How to print all preprocessor flags in PlatformIO to print preprocessor flags. You can find all ESP-IDF related flags using

cat .pio/build/esp32dev/src/main.o | grep IDF

which is – with an empty main.c file just

#define IDF_VER "5.1.2"

Note that IDF_VER is also defined for Arduino since it is used internally by Arduino.

So you can use the following check which distinguistes between Arduino & ESP-iDF

#if !defined(ARDUINO) && defined(IDF_VER)
  // ESP-IDF code goes here
#else
  // Non-ESP-IDF code goes here
#else

 

Posted by Uli Köhler in ESP8266/ESP32

ESP32-S3 multiple frequency PWM output using the LEDC driver

This Arduino/PlatformIO firmware outputs 50% duty cycle PWM at different frequencies on multiple pins using the LEDC PWM driver, utilizing all four timers:

  • 10 kHz on GPIO10
  • 100 kHz on GPIO11
  • 500 kHz on GPIO12
  • 1 MHz on GPIO13
#include <Arduino.h>
#include <driver/ledc.h>

void setup() {

  ledc_timer_config_t ledc_timer1 = {
      .speed_mode       = LEDC_LOW_SPEED_MODE,
      .duty_resolution  = LEDC_TIMER_1_BIT,
      .timer_num        = LEDC_TIMER_0,
      .freq_hz          = 10000,
      .clk_cfg          = LEDC_AUTO_CLK
  };
  ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer1));

  ledc_timer_config_t ledc_timer2 = {
      .speed_mode       = LEDC_LOW_SPEED_MODE,
      .duty_resolution  = LEDC_TIMER_1_BIT,
      .timer_num        = LEDC_TIMER_1,
      .freq_hz          = 100000,
      .clk_cfg          = LEDC_AUTO_CLK
  };
  ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer2));

  ledc_timer_config_t ledc_timer3 = {
      .speed_mode       = LEDC_LOW_SPEED_MODE,
      .duty_resolution  = LEDC_TIMER_1_BIT,
      .timer_num        = LEDC_TIMER_2,
      .freq_hz          = 500000,
      .clk_cfg          = LEDC_AUTO_CLK
  };
  ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer3));

  ledc_timer_config_t ledc_timer4 = {
      .speed_mode       = LEDC_LOW_SPEED_MODE,
      .duty_resolution  = LEDC_TIMER_1_BIT,
      .timer_num        = LEDC_TIMER_3,
      .freq_hz          = 1000000,
      .clk_cfg          = LEDC_AUTO_CLK
  };
  ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer4));


  /**
   * @brief Configure LEDC output channels
   */

  ledc_channel_config_t ledc1 = {
      .gpio_num       = GPIO_NUM_10,
      .speed_mode     = LEDC_LOW_SPEED_MODE,
      .channel        = LEDC_CHANNEL_0,
      .intr_type      = LEDC_INTR_DISABLE,
      .timer_sel      = LEDC_TIMER_0,
      .duty           = 1, // Set duty to 50%
      .hpoint         = 0
  };
  ESP_ERROR_CHECK(ledc_channel_config(&ledc1));

  ledc_channel_config_t ledc2 = {
      .gpio_num       = GPIO_NUM_11,
      .speed_mode     = LEDC_LOW_SPEED_MODE,
      .channel        = LEDC_CHANNEL_1,
      .intr_type      = LEDC_INTR_DISABLE,
      .timer_sel      = LEDC_TIMER_1,
      .duty           = 1, // Set duty to 50%
      .hpoint         = 0
  };
  ESP_ERROR_CHECK(ledc_channel_config(&ledc2));

  ledc_channel_config_t ledc3 = {
      .gpio_num       = GPIO_NUM_12,
      .speed_mode     = LEDC_LOW_SPEED_MODE,
      .channel        = LEDC_CHANNEL_2,
      .intr_type      = LEDC_INTR_DISABLE,
      .timer_sel      = LEDC_TIMER_2,
      .duty           = 1, // Set duty to 50%
      .hpoint         = 0
  };
  ESP_ERROR_CHECK(ledc_channel_config(&ledc3));

  ledc_channel_config_t ledc4 = {
      .gpio_num       = GPIO_NUM_13,
      .speed_mode     = LEDC_LOW_SPEED_MODE,
      .channel        = LEDC_CHANNEL_3,
      .intr_type      = LEDC_INTR_DISABLE,
      .timer_sel      = LEDC_TIMER_3,
      .duty           = 1, // Set duty to 50%
      .hpoint         = 0
  };
  ESP_ERROR_CHECK(ledc_channel_config(&ledc4));

}

void loop() {
  // Nothing to do here
  delay(1000);
}

GPIO10

GPIO11

GPIO12

GPIO13

Posted by Uli Köhler in Arduino, ESP8266/ESP32, PlatformIO

ESP32(-S3) ADC1 ESP-IDF minimal oneshot example with curve calibration

#include <esp_adc/adc_oneshot.h>
#include <esp_adc/adc_cali.h>
#include <esp_adc/adc_cali_scheme.h>


adc_oneshot_unit_handle_t adc1_handle = nullptr;
adc_cali_handle_t adc_cali_channel_handle = nullptr;

void InitADC() {
    //-------------ADC1 Init---------------//
    adc_oneshot_unit_init_cfg_t init_config1 = {
        .unit_id = ADC_UNIT_1,
    };
    ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));

    //-------------ADC1 Config---------------//
    adc_oneshot_chan_cfg_t config = {
        .atten = ADC_ATTEN_DB_11,
        .bitwidth = ADC_BITWIDTH_DEFAULT, // default width is max supported width
    };
    ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, ADC_CHANNEL_6, &config));

    // Initialize 
    adc_cali_curve_fitting_config_t cali_config = {
        .unit_id = ADC_UNIT_1,
        .chan = ADC_CHANNEL_6,
        .atten = ADC_ATTEN_DB_11,
        .bitwidth = ADC_BITWIDTH_DEFAULT,
    };
    ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_channel_handle));


}

void ADCRead() {
    // Read raw value
    int raw = 0;
    ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, ADC_CHANNEL_6, &raw));

    // Apply calibration to value
    int voltage = 0;
    ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_channel_handle, raw, &voltage));

    printf("ADC Channel[%d] Cali Voltage: %d mV\n", ADC_CHANNEL_6, voltage);
}

Usage example:

InitADC();
while(true)
{
    ADCRead();
    // Wait for some delay before reading again
    vTaskDelay(50 / portTICK_PERIOD_MS);
}

This prints, for example:

ADC Channel[6] Cali Voltage: 163 mV

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32

ESP32(-S3) LEDC complementary output with programmable dead time example

On the ESP32 platform, you can use the LEDC PWM driver to generate programmable dead time by setting the duty (i.e. length of pulse) and hpoint(time offset of pulse) appropriately.

#include <driver/ledc.h>
ledc_timer_config_t ledc_timer = {
    .speed_mode       = LEDC_LOW_SPEED_MODE,
    .duty_resolution  = LEDC_TIMER_8_BIT,
    .timer_num        = LEDC_TIMER_0,
    .freq_hz          = 100000,
    .clk_cfg          = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

uint32_t deadTime = 20; // Large value to show clearly on the oscilloscope

ledc_channel_config_t ledc_noninverted_channel = {
    .gpio_num       = GPIO_NUM_10,
    .speed_mode     = LEDC_LOW_SPEED_MODE,
    .channel        = LEDC_CHANNEL_0,
    .intr_type      = LEDC_INTR_DISABLE,
    .timer_sel      = LEDC_TIMER_0,
    .duty           = 127-deadTime/2, // Set duty to 50%
    .hpoint         = 0
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_noninverted_channel));

ledc_channel_config_t ledc_complementary_channel = {
    .gpio_num       = GPIO_NUM_11,
    .speed_mode     = LEDC_LOW_SPEED_MODE,
    .channel        = LEDC_CHANNEL_1,
    .intr_type      = LEDC_INTR_DISABLE,
    .timer_sel      = LEDC_TIMER_0,
    .duty           = 127-deadTime/2, // Set cycle to start just after 50%
    .hpoint         = 127,
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_complementary_channel));

Posted by Uli Köhler in ESP8266/ESP32

ESP32(-S3) LEDC inverted output example

#include <driver/ledc.h>
ledc_timer_config_t ledc_timer = {
    .speed_mode       = LEDC_LOW_SPEED_MODE,
    .duty_resolution  = LEDC_TIMER_8_BIT,
    .timer_num        = LEDC_TIMER_0,
    .freq_hz          = 100000,
    .clk_cfg          = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&amp;ledc_timer));

ledc_channel_config_t ledc_inverted_channel = {
    .gpio_num       = GPIO_NUM_11,
    .speed_mode     = LEDC_LOW_SPEED_MODE,
    .channel        = LEDC_CHANNEL_1,
    .intr_type      = LEDC_INTR_DISABLE,
    .timer_sel      = LEDC_TIMER_0,
    .duty           = 128, // Set cycle to start just after 50%
    .hpoint         = 0,
    .flags = {.output_invert = true}
};
ESP_ERROR_CHECK(ledc_channel_config(&amp;ledc_inverted_channel));

 

Posted by Uli Köhler in ESP8266/ESP32

ESP32(-S3) LEDC complementary PWM example

#include <driver/ledc.h>
ledc_timer_config_t ledc_timer = {
    .speed_mode       = LEDC_LOW_SPEED_MODE,
    .duty_resolution  = LEDC_TIMER_8_BIT,
    .timer_num        = LEDC_TIMER_0,
    .freq_hz          = 100000,
    .clk_cfg          = LEDC_AUTO_CLK
};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));

ledc_channel_config_t ledc_noninverted_channel = {
    .gpio_num       = GPIO_NUM_10,
    .speed_mode     = LEDC_LOW_SPEED_MODE,
    .channel        = LEDC_CHANNEL_0,
    .intr_type      = LEDC_INTR_DISABLE,
    .timer_sel      = LEDC_TIMER_0,
    .duty           = 127, // Set duty to 50%
    .hpoint         = 0
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_noninverted_channel));

ledc_channel_config_t ledc_inverted_channel = {
    .gpio_num       = GPIO_NUM_11,
    .speed_mode     = LEDC_LOW_SPEED_MODE,
    .channel        = LEDC_CHANNEL_1,
    .intr_type      = LEDC_INTR_DISABLE,
    .timer_sel      = LEDC_TIMER_0,
    .duty           = 128, // Set cycle to start just after 50%
    .hpoint         = 0,
    .flags = {.output_invert = true}
};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_inverted_channel));

Posted by Uli Köhler in ESP8266/ESP32

ESP32 rmt_tx sync manager minimal example

See ESP32 rmt_tx simple pulse example (ESP-IDF) for an example without sync manager

#include <driver/rmt_tx.h>
rmt_channel_handle_t channel;
rmt_tx_channel_config_t tx_chan_config = {
    .gpio_num = GPIO_NUM_19,          // GPIO number
    .clk_src = RMT_CLK_SRC_DEFAULT,   // select source clock
    .resolution_hz = 1 * 1000 * 1000, // 1 MHz resolution
    .mem_block_symbols = 64,          // memory block size, 64 * 4 = 256 Bytes
    .trans_queue_depth = 1,           // set the number of transactions that can pend in the background
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &channel));
ESP_ERROR_CHECK(rmt_enable(channel));

rmt_symbol_word_t txdata[64];
txdata[0] = {
    .duration0 = 100,
    .level0 = 1,
    .duration1 = 0,
    .level1 = 0,
};

// install sync manager
rmt_channel_handle_t tx_channels[] = {channel};
rmt_sync_manager_handle_t synchro = NULL;
rmt_sync_manager_config_t synchro_config = {
    .tx_channel_array = tx_channels,
    .array_size = sizeof(tx_channels) / sizeof(rmt_channel_handle_t),
};
ESP_ERROR_CHECK(rmt_new_sync_manager(&synchro_config, &synchro));

// Create simple encoder
rmt_copy_encoder_config_t encoder_config;
rmt_encoder_handle_t encoder;
ESP_ERROR_CHECK(rmt_new_copy_encoder(&encoder_config, &encoder));

rmt_transmit_config_t tx_config = {
    .loop_count = 0, // no transfer loop
};

while(true)
{
    ESP_ERROR_CHECK(rmt_transmit(channel, encoder, pulseRMT, 1*sizeof(rmt_symbol_word_t), &tx_config));
    // Wait for one second
    vTaskDelay(10 / portTICK_PERIOD_MS);
}

 

Posted by Uli Köhler in Allgemein, ESP8266/ESP32

ESP32 rmt_tx simple pulse example (ESP-IDF)

In contrast to our previous example from  ESP32 RMT pulse generation minimal example using Arduino &#038; PlatformIO this example uses the new rmt_tx API.

#include <driver/rmt_tx.h>
rmt_channel_handle_t channel;
rmt_tx_channel_config_t tx_chan_config = {
    .gpio_num = GPIO_NUM_19,          // GPIO number
    .clk_src = RMT_CLK_SRC_DEFAULT,   // select source clock
    .resolution_hz = 1 * 1000 * 1000, // 1 MHz resolution
    .mem_block_symbols = 64,          // memory block size, 64 * 4 = 256 Bytes
    .trans_queue_depth = 1,           // set the number of transactions that can pend in the background
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &channel));
ESP_ERROR_CHECK(rmt_enable(channel));

rmt_symbol_word_t txdata[64];
txdata[0] = {
    .duration0 = 100,
    .level0 = 1,
    .duration1 = 0,
    .level1 = 0,
};

// Create simple encoder
rmt_copy_encoder_config_t encoder_config;
rmt_encoder_handle_t encoder;
ESP_ERROR_CHECK(rmt_new_copy_encoder(&encoder_config, &encoder));

rmt_transmit_config_t tx_config = {
    .loop_count = 0, // no transfer loop
};

while(true)
{
    ESP_ERROR_CHECK(rmt_transmit(channel, encoder, pulseRMT, 1*sizeof(rmt_symbol_word_t), &tx_config));
    // Wait for one second
    vTaskDelay(10 / portTICK_PERIOD_MS);
}

Result:

Posted by Uli Köhler in ESP8266/ESP32

How to fix ESP32 rmt: rmt_transmit(476): channel not in enable state

If you get the following error during a rmt_transmit() call:

rmt: rmt_transmit(476): channel not in enable state

call

ESP_ERROR_CHECK(rmt_enable(channel));

after calling rmt_new_tx_channel().

Full initialization example

rmt_channel_handle_t channel;
rmt_tx_channel_config_t tx_chan_config = {
    .gpio_num = GPIO_NUM_19,          // GPIO number
    .clk_src = RMT_CLK_SRC_DEFAULT,   // select source clock
    .resolution_hz = 1 * 1000 * 1000, // 1 MHz resolution
    .mem_block_symbols = 64,          // memory block size, 64 * 4 = 256 Bytes
    .trans_queue_depth = 1,           // set the number of transactions that can pend in the background
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &channel));
ESP_ERROR_CHECK(rmt_enable(channel));

 

Posted by Uli Köhler in ESP8266/ESP32

ESP32 HTTP POST with JSON body using ArduinoJson

This example parses a HTTP POST request body using ArduinoJson. I recommend using my HumanESPHTTP library as simple server library based on the ESP-IDF HTTP server.

constexpr size_t JsonParseBufferSize = 2048;
typedef ArduinoJson::StaticJsonDocument JsonParseBuffer;

/**
 * This buffer is used to parse /api/configure events
*/
static JsonParseBuffer document;

static const httpd_uri_t configureHandler = {
    .uri       = "/api/configure",
    .method    = HTTP_POST,
    .handler   = [](httpd_req_t *req) {
        // Receive POST body data into a new buffer
        char* jsonBody = (char*)malloc(req->content_len + 1);
        jsonBody[req->content_len] = '\0'; // NUL terminate [just in case]
        if(jsonBody == nullptr) {
            return SendStatusError(req, "Failed to allocate memory for JSON body");
        }
        int err = httpd_req_recv(req, jsonBody, req->content_len);
        if(err != req->content_len) {
            free(jsonBody);
            return SendStatusError(req, "Failed to read request body");
        }
        // Parse the body as JSON
        deserializeJson(document, jsonBody, req->content_len);
        // TODO: Do something with [document]
        // Cleanup
        free(jsonBody);
        return SendStatusOK(req);
    }
};

which uses the following utility functions:

esp_err_t SendStatusError(httpd_req_t *request, const char* description) {
    httpd_resp_set_type(request, "application/json");
    httpd_resp_send_chunk(request, "{\"status\":\"error\", \"error\": \"", HTTPD_RESP_USE_STRLEN);
    // NOTE: We silently assume that description does not have any special characters
    httpd_resp_send_chunk(request, description, HTTPD_RESP_USE_STRLEN);
    httpd_resp_send_chunk(request, "\"}", HTTPD_RESP_USE_STRLEN);
    httpd_resp_send_chunk(request, nullptr, 0); // Finished
    return ESP_OK;
}

esp_err_t SendStatusOK(httpd_req_t *request) {
    httpd_resp_set_type(request, "application/json");
    httpd_resp_sendstr(request, "{\"status\":\"ok\"}");
    return ESP_OK;
}

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32

How to fix ESP32 rmt: rmt_transmit(466): loop count is not supported

If you encounter

rmt: rmt_transmit(466): loop count is not supported

on the ESP32, this is because you have used a rmt_transmit_config_t with explicitly set loop_count.

rmt_transmit_config_t cfg = {
  .loop_count = 1,
  .flags = {
      .eot_level = 0,
   }
};
ESP_ERROR_CHECK(rmt_transmit(/* ... */, &cfg));

but your IC (e.g. ESP32-D0WD-V3)  does not support hardware loop mode.

Fixing this is easy: Just comment out the .loop_count line:

rmt_transmit_config_t cfg = {
  //.loop_count = 1, // DISABLED as chip does not support it
  .flags = {
      .eot_level = 0,
   }
};
ESP_ERROR_CHECK(rmt_transmit(/* ... */, &cfg));

Note that if you leave .loop_count at its default, it will always act as if .loop_count = 1.

Posted by Uli Köhler in C/C++, ESP8266/ESP32

ESP32-D0WD-V3 does not support RMT sync manager

I was trying to use a RMT sync manager on the ESP32-D0WD-V3 (Version 3.1). However, trying to use rmt_new_sync_manager() returns ESP_ERR_NOT_SUPPORTED with the following message:

␛[0;31mE (560) rmt: rmt_new_sync_manager(331): sync manager not supported␛[0m
ESP_ERROR_CHECK failed: esp_err_t 0x106 (ESP_ERR_NOT_SUPPORTED) at 0x400d2681

This leads me to believe that the ESP32-D0WD-V3 does not support any RMT sync managers. So far, I have not tried with other controllers.

Posted by Uli Köhler in ESP8266/ESP32

ESP-IDF minimal Wifi client hello world example using PlatformIO

This basic example showcases how to use PlatformIO with ESP-IDF only (no Arduino) to connect to Wifi in station mode and print Hello world in a loop. It is based on the basic Hello World example from PlatformIO ESP32 with ESP-IDF minimal C++ example:

#include <cstdio>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

#include <esp_wifi.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <esp_netif.h>
#include <esp_event.h>


extern "C" {
    void app_main(void);
}


static void NetworkEventHandler(void* arg, esp_event_base_t event_base,
                          int32_t event_id, void* event_data) {
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        esp_wifi_connect();
        ESP_LOGI("wifi", "Retrying to connect to Wifi...");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI("wifi", "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
    }
}

void InitNVS() {
    // Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
}

void InitWifi() {

    // Initialize TCP/IP network interface (required for Wi-Fi)
    ESP_ERROR_CHECK(esp_netif_init());

    // Initialize the event loop
    ESP_ERROR_CHECK(esp_event_loop_create_default());    
    esp_netif_create_default_wifi_sta();

    // Initialize Wi-Fi
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // Set Wi-Fi to station mode
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));

    // Configure the Wi-Fi connection
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "MyWifi",
            .password = "mypassword"
        }
    };
    // Register event handler
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &NetworkEventHandler,
                                                        NULL,
                                                        NULL));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &NetworkEventHandler,
                                                        NULL,
                                                        NULL));

    // Set Wi-Fi configuration and start Wi-Fi
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
}

void app_main() {
    InitNVS();
    InitWifi();

    while(true) {
        printf("Hello PlatformIO!\n");
        // Wait for one second
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = espidf
monitor_speed = 115200

All sdkconfig settings have been left at their respective defaults.

Posted by Uli Köhler in C/C++, ESP8266/ESP32, PlatformIO

How to fix ESP-IDF error: cannot convert ‘esp_interface_t’ to ‘wifi_interface_t’

Problem:

While trying to  compile your ESP-IDF app using code such as

ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));

you see an error message such as

src/main.cpp:76:41: error: cannot convert 'esp_interface_t' to 'wifi_interface_t'
   76 |     ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
      |                                         ^~~~~~~~~~~~~~~
      |                                         |
      |                                         esp_interface_t

Solution:

Instead of ESP_IF_WIFI_STA, use WIFI_IF_STA (which has the correct type):

ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32

PlatformIO ESP32 with ESP-IDF minimal C++ example

By default, when you initialize a PlatformIO ESP-IDF project, it will generate main.c – not a C++ but a pure C file

This minimal example instead uses main.cpp (you can just rename your main.c – see How to use C++ main.cpp with PlatformIO on ESP32 / esp-idf for a detailed list of steps to do).

#include <cstdio>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

extern "C" {
    void app_main(void);
}

void app_main() {
    while(true) {
        printf("Hello PlatformIO!\n");
        // Wait for one second
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32, PlatformIO

How to use C++ main.cpp with PlatformIO on ESP32 / esp-idf

When you are using PlatformIO to compile your firmware, it is easily possible to use a C++ main.cpp instead of the pure C main.c by just renaming main.c to main.cpp

However, you also need to properly declary app_main(). After the #include section of your main.cpp (or at the top of the file if you don’t have an #include section yet), add this code:

extern "C" {
    void app_main(void);
}

This will tell the compiler that app_main() is a C function, not a C++ function.

Full main.cpp example

#include <cstdio>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

extern "C" {
    void app_main(void);
}

void app_main() {
    while(true) {
        printf("Hello PlatformIO!\n");
        // Wait for one second
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32, PlatformIO