The ESP32-S2 has no MCPWM (Motor control PWM) periperal.
However, the related ESP32-S3 features two MCPWM peripherals.
Source: Datasheet
The ESP32-S2 has no MCPWM (Motor control PWM) periperal.
However, the related ESP32-S3 features two MCPWM peripherals.
Source: Datasheet
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; };
#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); } }
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
This Arduino/PlatformIO firmware outputs 50% duty cycle PWM at different frequencies on multiple pins using the LEDC PWM driver, utilizing all four timers:
#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); }
#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
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));
#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_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));
#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));
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); }
In contrast to our previous example from ESP32 RMT pulse generation minimal example using Arduino & 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:
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()
.
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));
According to the ESP32-S3 technical reference manual, the ESP32-S3 has four RMT channels for transmission and four RMT channels for receiving
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::StaticJsonDocumentJsonParseBuffer; /** * 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; }
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
.
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.
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.
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
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));
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); } }
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.
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); } }
While trying to compile your ESP-IDF firmware (with or without PlatformIO), you see an error message such as
src/main.cpp:13:27: error: 'portTICK_PERIOD_MS' was not declared in this scope 13 | vTaskDelay(1000 / portTICK_PERIOD_MS);
Include FreeRTOS by adding the following lines to the top of the file where the error occured (src/main.cpp
in this example):
#include <freertos/FreeRTOS.h> #include <freertos/task.h>