CadQuery simple L-shaped piece example (with STEP export)

import cadquery as cq

# Define dimensions
base_length = 200  # 20cm
base_width = 80    # 8cm
plate_thickness = 2  # 2mm
upright_height = 400  # 40cm

# Create the base plate
base_plate = cq.Workplane("XY").box(base_length, base_width, plate_thickness)

# Create the upright plate
# Position is set such that it aligns with the end of the base plate and stands upright
upright_plate = cq.Workplane("XY").workplane(offset=plate_thickness).transformed(rotate=(0, 90, 0)).box(upright_height, base_width, plate_thickness).translate((base_length/2, 0, upright_height/2))

# Join the two parts into one
final_part = base_plate.union(upright_plate)

# Export to STEP
final_part.val().exportStep("L-piece.stp")

 

Posted by Uli Köhler in CadQuery, Python

Jupyter adjustment widgets with plus and minus buttons

First, define get and set functions:

# Basic examples for get and set value functions

def get_value(): # will only be used to get the initial value
    return httpx.get(f"http://{ip}/api/get-value").json()["value"]

def set_value(value):
    httpx.get(f"http://{ip}/api/set-value?nedge={value}")

 

import ipywidgets as widgets
from IPython.display import display

# Step 2: Define the widget components
value_display = widgets.IntText(value=get_value(), description='Value:', disabled=False)
plus_button = widgets.Button(description='+')
minus_button = widgets.Button(description='-')

def on_value_change(change):
    set_value(change['new'])
    
value_display.observe(on_value_change, names='value')

# Step 4: Define the update functions
def on_plus_button_clicked(b):
    value_display.value += 1

def on_minus_button_clicked(b):
    value_display.value -= 1

# Step 5: Bind the update functions to the buttons
plus_button.on_click(on_plus_button_clicked)
minus_button.on_click(on_minus_button_clicked)

# Step 6: Display the widgets
widgets_layout = widgets
display(value_display, plus_button, minus_button)

 

Posted by Uli Köhler in Jupyter, Python

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 remove all .htaccess files recursively

The following command recursively removes all .htaccess files from the current directory and all subdirectories:

Please note that the files are deleted directly and not moved to the trash bin, so consider making a backup!

find . -name ".htaccess" -exec rm -v {} \;

 

 

Posted by Uli Köhler in Linux

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

How to find out if Arduino framework is used using the preprocessor (PlatformIO)

You can use How to print all preprocessor flags in PlatformIO to print preprocessor flags. Note that these include the flags #defined in #includes such as Arduino.h. You can remove everything from main.cpp, however, so only the flags defined by the build environment are visible.

The Arduino-related flags are

#define ARDUINO_VARIANT "esp32"
#define ARDUINO_ARCH_ESP32 1
#define ARDUINO_PARTITION_default 1
#define ARDUINO 10812
#define ARDUINO_ESP32_DEV 1
#define ARDUINO_BOARD "Espressif ESP32 Dev Module"

So if you want a platform-independent check for Arduino, use

#ifdef ARDUINO
  // Arduino code goes here
#else
  // Non-Arduino code goes here
#else

 

Posted by Uli Köhler in Arduino

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

How to fix CMake build error on Ubuntu: Could NOT find HarfBuzz (missing: HarfBuzz_INCLUDE_DIR HarfBuzz_LIBRARY

Problem:

While building a software project using cmake, you see error messages like

CMake Error at /usr/share/cmake-3.25/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find HarfBuzz (missing: HarfBuzz_INCLUDE_DIR HarfBuzz_LIBRARY
  _HarfBuzz_REQUIRED_LIBS_FOUND)
Call Stack (most recent call first):
  /usr/share/cmake-3.25/Modules/FindPackageHandleStandardArgs.cmake:600 (_FPHSA_FAILURE_MESSAGE)
  cmake/FindHarfBuzz.cmake:153 (find_package_handle_standard_args)
  CMakeLists.txt:801 (find_package)

Solution:

Install libharfbuzz-dev

sudo apt -y install libharfbuzz-dev

 

Posted by Uli Köhler in CMake, Linux

How to fix CMake build error on Ubuntu: Could NOT find GLM (missing: GLM_INCLUDE_DIR GLM_VERSION)

Problem:

While building a software project using cmake, you see error messages like

CMake Error at /usr/share/cmake-3.25/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find GLM (missing: GLM_INCLUDE_DIR GLM_VERSION) (Required is at
  least version "0.9.8")
Call Stack (most recent call first):
  /usr/share/cmake-3.25/Modules/FindPackageHandleStandardArgs.cmake:600 (_FPHSA_FAILURE_MESSAGE)
  cmake/FindGLM.cmake:54 (FIND_PACKAGE_HANDLE_STANDARD_ARGS)
  CMakeLists.txt:751 (find_package)

Solution:

Install libglm-dev

sudo apt -y install libglm-dev

 

Posted by Uli Köhler in CMake, Linux

How to fix CMake build error on Ubuntu: Could NOT find GLEW (missing: GLEW_INCLUDE_DIR GLEW_LIBRARY)

Problem:

While building a software project using cmake, you see error messages like

CMake Error at /usr/share/cmake-3.25/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find GLEW (missing: GLEW_INCLUDE_DIR GLEW_LIBRARY)
Call Stack (most recent call first):
  /usr/share/cmake-3.25/Modules/FindPackageHandleStandardArgs.cmake:600 (_FPHSA_FAILURE_MESSAGE)
  cmake/FindGLEW.cmake:38 (find_package_handle_standard_args)
  CMakeLists.txt:743 (find_package

Solution:

Install libglew-dev

sudo apt -y install libglew-dev

 

Posted by Uli Köhler in CMake, Linux

How to plot cumulative Gitlab group members using matplotlib

This is based on my previous post to find the group ID by group name.

import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from datetime import datetime
import gitlab

def plot_cumulative_members(members):
    # Convert date strings to datetime objects and sort
    dates = sorted([datetime.strptime(member["access_granted_date"], '%Y-%m-%dT%H:%M:%S.%fZ') for member in members])

    # Calculate cumulative count
    cumulative_count = range(1, len(dates) + 1)

    # Plotting
    plt.figure(figsize=(10, 6))
    plt.plot(dates, cumulative_count, marker='o')
    plt.title('Cumulative Number of Users in GitLab Group Over Time')
    plt.xlabel('Date')
    plt.ylabel('Cumulative Number of Users')
    plt.grid(True)
    plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
    plt.gca().xaxis.set_major_locator(mdates.YearLocator())
    plt.gcf().autofmt_xdate()  # Rotation
    plt.show()

group_id = get_gitlab_group_id("Protectors of the Footprint Realm", 'glpat-...')
members = get_group_members(group_id, 'glpat-...')
with plt.xkcd():
    plot_cumulative_members(members)

Posted by Uli Köhler in GitLab, Python

How to find Gitlab group ID using Gitlab API via python-gitlab

pip install python-gitlab
import gitlab

def get_gitlab_group_id(group_name, access_token):
    # Initialize a GitLab instance with your private token
    gl = gitlab.Gitlab('https://gitlab.com', private_token=access_token)

    # Search for groups by name
    groups = gl.groups.list(search=group_name)
    for group in groups:
        if group.name == group_name or group.path == group_name:
            return group.id

    raise ValueError("Group not found")

# Usage example
group_id = get_gitlab_group_id("Protectors of the Footprint Realm", 'glpat-yykIsrTg6RyKcFAvd2os')

group_id will be an integer, for example 6604163

Posted by Uli Köhler in GitLab, Python

Where does matplotlib look for fonts on Linux?

Find out using

import matplotlib.font_manager
print(matplotlib.font_manager.X11FontDirectories)

On my Ubuntu 22.04, this lists:

['/usr/X11R6/lib/X11/fonts/TTF/',
 '/usr/X11/lib/X11/fonts',
 '/usr/share/fonts/',
 '/usr/local/share/fonts/',
 '/usr/lib/openoffice/share/fonts/truetype/',
 '~/.local/share/fonts',
 '~/.fonts']

 

Posted by Uli Köhler in Python

How to generate random strings that look like Gitlab access tokens

import secrets
import string

def generate_gitlab_access_token(length=20):
    characters = string.ascii_letters + string.digits + '_'
    token = ''.join(secrets.choice(characters) for _ in range(length))
    return f'glpat-{token}'

# Example usage:
access_token = generate_gitlab_access_token()
print(access_token)

Example output:

glpat-yykIsrTg6RyKcFAvd2os

 

Posted by Uli Köhler in GitLab, Python

How to fix matplotlib findfont: Font family ‘xkcd’ not found on Ubuntu 22.04+

Problem:

While plotting an XKCD-style plot using matplotlib, you see the following error messages:

findfont: Font family 'xkcd' not found.
findfont: Font family 'xkcd Script' not found.
findfont: Font family 'Comic Neue' not found.
findfont: Font family 'Comic Sans MS' not found.

Solution:

Install the Humor Sans font using

sudo apt -y install font-humor-sans

Additionally, you need to remove the matplotlib font cache:

rm -rf ~/.cache/matplotlib

 

 

Posted by Uli Köhler in Linux, Python

How to hide password strength meter with PrimeNG [p-password] InputPassword

Use [feedback]="false" to hide the strength meter:

<p-password [feedback]="false" [(ngModel)]="value"></p-password>

 

Posted by Uli Köhler in Angular

Minimal component count voltage-controlled duty cycle PWM circuit

When given a stimulus PWM e.g. from a microcontroller or other oscillator, this simple circuit allows using a capacitor’s time constant to generate

The main trick here is to use the Schottky diode D1 to discharge the capacitor quickly when the stimulus PWM is low, and charging it slowly via R1.

The values used here are for MHz-frequency PWMs. Choose a R-C time constant suitable for your PWM frequency (R1 / C1). A good place to start is with the R-C time constant at 0.5 * period with period = 1/frequency. In general, you want the capacitor to be almost fully charged when the positive duty cycle period is over. You can compute the time constant using Wolfram Alpha.

If you are using larger capacitors than ~10nF, you should add a resistor in series with D1 to avoid killing the MCU output stage by dumping all the capacitor’s energy into it. Compute the resistor so that the maximum allowable input current equals the capacitor charge voltage (= typically the MCU supply voltage) minus the minimum diode forward voltage, across the resistor. This is a conservative choice, but you should start from there.

Keep in mind that the charge voltage is not linear, therefore the resulting voltage-controlled duty cycle is not linear with input voltage.

In order to allow a high duty cycle PWM output, the stimulus PWM should have almost 100% duty cycle. There must be enough time in the off-duty-cycle to properly discharge the capacitor via the Schottky diode.

Consider adding hysteresis to the comparator to avoid output oscillation.

The choice of diode doesn’t matter too much. Schottky diodes should be preferred because they tend discharge the capacitor more completely within less time: A 1N4148 would only discharge the capacitor to Vf~=0.7V, whereas the rest of the charge would have to flow out via the resistor. With Schottky diodes, this forward voltage is just 0.3V, in effect allowing a higher maximum duty cycle.

A single R/C/D circuit can be used to power multiple comparators, allowing multiple channels of voltage controlled PWM.

Measurement results

This is what it actually looks like. The voltage rise on the capacitor is fairly linear, it just appears to be oscillating due to inadequate effort being put into measuring this circuit. Keep in mind that this is not an ultra-precision circuit and in practice it’s not completely monotonous and certainly not linear in voltage.

The stimulus is given in blue. It’s a 4-bit 1.5MHz PWM with 15/16 duty cycle.

The capacitor voltage, which can be fed into the comparator, is shown in yellow.

 

Posted by Uli Köhler in Electronics
This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Cookie settingsACCEPTPrivacy &amp; Cookies Policy