Embedded

STM32 HAL PlatformIO LED blink example

In the autogenerated main.c, use the following while() loop:

/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
  HAL_GPIO_WritePin(LD1_GPIO_Port, LD1_Pin, GPIO_PIN_SET);
  HAL_Delay(1000);
  HAL_GPIO_WritePin(LD1_GPIO_Port, LD1_Pin, GPIO_PIN_RESET);
  HAL_Delay(1000);
}
/* USER CODE END 3 */

On STM32H7 Nucleo boards, this will toggle the green LED.

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

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 provoke HardFault on STM32H7

((volatile int*)0xFFFFFFFF)[0] = 0;

This will perform an illegal memory access and hence trigger the hard faul. Due to voltatile, it won’t be optimized out by the compiler.

Posted by Uli Köhler in STM32

STM32H743 DAC instant switch off (no fall time)

In our previous post STM32H743 DAC rise/fall time experiments we showed that the STM23H743 has relatively long turn-on / turn-off times of approximately 950 nanoseconds, limiting the generation of fast rectangular signals:

However, there’s a trick how to obtain fall times almost 3 orders of magnitude better for the special case of switching either to full-scale VDD or to GND.

Instead of setting the DAC to the new value, you can just disable the DAC and let the GPIO take care of the rest. This allows for extremely fast fall times of approximately 5ns.

Note that switching the GPIO to digital mode while the DAC is on does not seem to have any effect.

Please note that I didn’t take any care to make the measurement setup immune to the high transients, leading to some oscillation. You should take your own measurements if you have specific requirements.

DAC fall time from 3/4 full scale value (3072)

Note that switching on the DAC quickly is outside the scope of this post, but when toggling the DAC enable bit, you can still obtain 250ns rise times pretty easily.

Code example

void Init() {    
    DAC_ChannelConfTypeDef sConfig = {0};

    // Initialize DAC
    hdac.Instance = DAC1;
    if (HAL_DAC_Init(&hdac) != HAL_OK)
    {
        // Initialization Error
        __BKPT();
    }

    // Configure DAC channel
    sConfig.DAC_Trigger = DAC_TRIGGER_NONE;  // No trigger, free-running mode
    sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;

    if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1) != HAL_OK)
    {
        // Channel configuration Error
        __BKPT();
    }

    // Enable DAC Channel and start the conversion
    if (HAL_DAC_Start(&hdac, DAC_CHANNEL_1) != HAL_OK)
    {
        // Starting Error
        __BKPT();
    }

    // Set DAC to some value, which won't be changed for this example
    if (HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 3072) != HAL_OK)
    {
        // Setting DAC value Error
         __BKPT();
    }
}
void Pulse_On() {
    // Enable DAC
    hdac.Instance->CR |= DAC_CR_EN1;

    // Set GPIO to analog mode
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_4;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

void Pulse_Off() {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_4;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);

    // Clear EN1 bit of DAC_CR
    hdac.Instance->CR &= ~DAC_CR_EN1;
}

 

 

Posted by Uli Köhler in C/C++, STM32

STM32H743 DAC rise/fall time experiments

This oscilloscope trace was obtained by first setting the STM32H743ZI (Nucleo) DAC to 0x00, then setting it to maximum value (4096) without any intermediate steps.

The output buffer was enabled.

As can be seen on the trace, the rise/fall time is approximately 1us. No information about the clock speed etc is available for this example (Arduino on PlatformIO was used with standard settings). However, it does not appear that the rise/fall time is caused by the update rate. Setting the GPIO speed to maximum does not change the value.

This matches well with the datasheet-provided settling time of 1.7us(typ).

When disabling the output buffer, the result looks like this:

When, on the other hand, using the same pin as GPIO – using the exact same measurement setup (direct connection to BNC with 1M measurement impedance), the rise/fall time is almost zero.

Code example

// Function to initialize the DAC
void MX_DAC_Init(void)
{
    DAC_ChannelConfTypeDef sConfig = {0};

    // Initialize DAC
    hdac.Instance = DAC1;
    if (HAL_DAC_Init(&hdac) != HAL_OK)
    {
        // Initialization Error
        __BKPT();
    }

    // Configure DAC channel
    sConfig.DAC_Trigger = DAC_TRIGGER_NONE;  // No trigger, free-running mode
    sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;

    if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1) != HAL_OK)
    {
        // Channel configuration Error
        __BKPT();
    }

    // Enable DAC Channel and start the conversion
    if (HAL_DAC_Start(&hdac, DAC_CHANNEL_1) != HAL_OK)
    {
        // Starting Error
        __BKPT();
    }
}

The DAC value was set using

if (HAL_DAC_SetValue(&hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, 4095) != HAL_OK) {
    // Setting DAC value Error
    __BKPT();
}

 

Posted by Uli Köhler in C/C++, STM32

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

STM32H743 Arduino PlatformIO example: Read ADC with 16 bit resolution

This example configures ADC1 to read a 16 bit analog value of PA7 using a polled loop. The serial output is available on the STLink header.

#include <Arduino.h>
#include <stm32h7xx_hal.h>

ADC_HandleTypeDef hadc1;

static void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    // Enable the GPIOA clock
    __HAL_RCC_GPIOA_CLK_ENABLE();

    /**ADC1 GPIO Configuration    
    PA7     ------> ADC1_IN7 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

static void MX_ADC1_Init(void)
{
    ADC_ChannelConfTypeDef sConfig = {0};

    hadc1.Instance = ADC1;
    hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
    hadc1.Init.Resolution = ADC_RESOLUTION_16B;
    hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
    hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
    hadc1.Init.LowPowerAutoWait = DISABLE;
    hadc1.Init.ContinuousConvMode = DISABLE;
    hadc1.Init.NbrOfConversion = 1;
    hadc1.Init.DiscontinuousConvMode = DISABLE;
    hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
    hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
    //hadc1.Init.DMAContinuousRequests = DISABLE;
    hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
    hadc1.Init.OversamplingMode = DISABLE;
    if (HAL_ADC_Init(&hadc1) != HAL_OK)
    {
        // Initialization Error
    }

    sConfig.Channel = ADC_CHANNEL_7;
    sConfig.Rank = ADC_REGULAR_RANK_1;
    sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
    sConfig.SingleDiff = ADC_SINGLE_ENDED;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    sConfig.Offset = 0;
    if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
    {
        // Channel Configuration Error
    }
}

void setup() {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_ADC1_Init();

  Serial.begin(115200);

}

void loop() {
    HAL_ADC_Start(&hadc1); // Start ADC conversion
    HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY); // Wait for conversion to complete

    uint32_t adcValue = HAL_ADC_GetValue(&hadc1); // Read the ADC converted value
    Serial.printf("ADC value: %04X\n", adcValue);
}
[env:nucleo_h743zi]
platform = ststm32
board = nucleo_h743zi
framework = arduino
monitor_speed = 115200

Example output (unconnected):

ADC value: 04B6
ADC value: 049C
ADC value: 04AC
ADC value: 04AE
ADC value: 0497
ADC value: 04AF
ADC value: 04A7
ADC value: 04C6
ADC value: 0491
ADC value: 04A1
ADC value: 04AF
ADC value: 0493
ADC value: 0497
ADC value: 04AF
ADC value: 04A3
ADC value: 047D
ADC value: 04C1
ADC value: 04B1
ADC value: 04AF
ADC value: 0498
ADC value: 04A1
ADC value: 04C3
ADC value: 04AE
ADC value: 04AC
ADC value: 0489
ADC value: 0491
ADC value: 0491
ADC value: 047E
ADC value: 04B8
ADC value: 0494
ADC value: 04A5
ADC value: 0491
ADC value: 0494
ADC value: 048A
ADC value: 0499
ADC value: 0494
ADC value: 049E

 

Posted by Uli Köhler in Arduino, C/C++, PlatformIO, STM32

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

How to set a fixed exposure time using libcamera-still on the Raspberry Pi

Use the --shutter argument. If no unit is given, the number represents microseconds of fixed exposure time

Example: --shutter 10000 means: Fixed 10 milliseconds (10000 microseconds) exposure.

Full example

libcamera-still -n 1 --width 4056 --height 3040 -o test.jpg --shutter 400

 

Posted by Uli Köhler in Raspberry Pi

How to enable Custom AWB white balance using libcamera-still on the Raspberry Pi

libcamera-still -n 1 --width 4056 --height 3040 -o test.jpg --awb custom --awbgains 0.9,2.0

In --awbgains 0.9,2.0 , 0.9 is the gain for the red channel and 2.0 is the gain for the blue channel.

In other words, if your image is:

  • too red -> decrease the first number
  • too blue -> decrease the second number
  • too yellow -> increase the second number (blue)
  • too green -> increase both numbers
  • etc
Posted by Uli Köhler in Raspberry Pi

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