From 14947940d8ba65297a48d5214508ae08e5a3ef67 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Mon, 15 Jan 2024 01:01:56 -0800 Subject: [PATCH] Fix ADC implementation so it works! Fixes incorrect return type, adds +1 to top_count, fixes missing pwm_config_set_wrap() call. --- platform/mbed_lib.json | 2 +- .../TARGET_RP2040/analogin_api.c | 3 +- .../TARGET_RP2040/pwmout_api.c | 65 ++++++++++++++----- targets/targets.json5 | 30 +++++++-- 4 files changed, 74 insertions(+), 26 deletions(-) diff --git a/platform/mbed_lib.json b/platform/mbed_lib.json index 4f69cc366a1..12481a50210 100644 --- a/platform/mbed_lib.json +++ b/platform/mbed_lib.json @@ -159,7 +159,7 @@ }, "minimal-printf-enable-floating-point": { "help": "Enable floating point printing when using minimal printf library", - "value": false + "value": true }, "minimal-printf-set-floating-point-max-decimals": { "help": "Maximum number of decimals to be printed when using minimal printf library", diff --git a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c index a89123f0b0a..9e8729f20f5 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c +++ b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c @@ -22,9 +22,8 @@ #include "pinmap.h" #include "PeripheralPins.h" -static float const ADC_VREF_VOLTAGE = 3.3f; /* 3.3V */ static uint16_t const ADC_RESOLUTION_BITS = 12; -static float const ADC_CONVERSION_FACTOR = ADC_VREF_VOLTAGE / (1 << 16); +static float const ADC_CONVERSION_FACTOR = 1.0f / (1 << 16); void analogin_init(analogin_t *obj, PinName pin) { diff --git a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/pwmout_api.c b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/pwmout_api.c index 54dfc07ac30..dd08c7abc2d 100644 --- a/targets/TARGET_RASPBERRYPI/TARGET_RP2040/pwmout_api.c +++ b/targets/TARGET_RASPBERRYPI/TARGET_RP2040/pwmout_api.c @@ -27,8 +27,17 @@ #include +// Change to 1 to enable debug prints of what's being calculated. +// Must comment out the critical section calls in PwmOut to use. +#define RP2040_PWMOUT_DEBUG 0 + +#if RP2040_PWMOUT_DEBUG +#include +#include +#endif + /// Largest top count value supported by hardware. Using this value will provide the highest duty cycle resolution, -/// but will limit the period to a maximum of (1 / (125 MHz / 65534) =) 524 us +/// but will limit the period to a maximum of (1 / (125 MHz / (65534 + 1)) =) 524 us const uint16_t MAX_TOP_COUNT = 65534; /// Value for PWM_CHn_DIV register that produces a division of 1 @@ -37,29 +46,34 @@ const uint16_t PWM_CHn_DIV_1 = 0x010; /// Calculate the effective PWM period (in floating point seconds) based on a divider and top_count value static float calc_effective_pwm_period(float divider, uint16_t top_count) { - return 1.0f / (clock_get_hz(clk_sys) / divider / top_count); + // Note: The hardware counts to top_count *inclusively*, so we have to add 1 + // to get the number of clock cycles that a given top_count value will produce + return 1.0f / ((clock_get_hz(clk_sys) / divider) / (top_count + 1)); } /// Calculate the best possible top_count value (rounding up) for a divider and a desired pwm period static uint16_t calc_top_count_for_period(float divider, float desired_pwm_period) { // Derivation: - // desired_pwm_period = 1.0f / ((clock_get_hz(clk_sys) / divider) / top_count) - // desired_pwm_period = top_count / (clock_get_hz(clk_sys) / divider) - // desired_pwm_period * (clock_get_hz(clk_sys) / divider) = top_count + // desired_pwm_period = 1.0f / ((clock_get_hz(clk_sys) / divider) / (top_count + 1)) + // desired_pwm_period = (top_count + 1) / (clock_get_hz(clk_sys) / divider) + // desired_pwm_period * (clock_get_hz(clk_sys) / divider) - 1 = top_count - return ceilf(desired_pwm_period * (clock_get_hz(clk_sys) / divider)); + long top_count_float = lroundf(desired_pwm_period * (clock_get_hz(clk_sys) / divider) - 1); + MBED_ASSERT(top_count_float <= MAX_TOP_COUNT); + return (uint16_t)top_count_float; } /// Calculate the best possible floating point divider value for a desired pwm period. /// This function assumes that top_count is set to MAX_TOP_COUNT. -static uint16_t calc_divider_for_period(float desired_pwm_period) +static float calc_divider_for_period(float desired_pwm_period) { // Derivation: - // (desired_pwm_period * clock_get_hz(clk_sys)) / divider = top_count - // divider = (desired_pwm_period * clock_get_hz(clk_sys)) / top_count + // (desired_pwm_period * clock_get_hz(clk_sys)) / divider - 1 = top_count + // (desired_pwm_period * clock_get_hz(clk_sys)) / divider = top_count + 1 + // divider = (desired_pwm_period * clock_get_hz(clk_sys)) / (top_count + 1) - return (desired_pwm_period * clock_get_hz(clk_sys)) / MAX_TOP_COUNT; + return (desired_pwm_period * clock_get_hz(clk_sys)) / (MAX_TOP_COUNT + 1); } /// Convert PWM divider from floating point to a fixed point number (rounding up). @@ -126,19 +140,23 @@ void pwmout_write(pwmout_t *obj, float percent) obj->percent = percent; // Per datasheet section 4.5.2.2, a period value of top_count + 1 produces 100% duty cycle - int32_t reset_count = lroundf((obj->top_count + 1) * percent); + int32_t new_reset_counts = lroundf((obj->top_count + 1) * percent); // Clamp to valid values - if(reset_count > obj->top_count + 1) + if(new_reset_counts > obj->top_count + 1) { - reset_count = obj->top_count + 1; + new_reset_counts = obj->top_count + 1; } - else if(reset_count < 0) + else if(new_reset_counts < 0) { - reset_count = 0; + new_reset_counts = 0; } - pwm_set_chan_level(obj->slice, obj->channel, reset_count); +#if RP2040_PWMOUT_DEBUG + printf("new_reset_counts: %" PRIu32 "\n", new_reset_counts); +#endif + + pwm_set_chan_level(obj->slice, obj->channel, new_reset_counts); pwm_set_enabled(obj->slice, true); } @@ -197,11 +215,26 @@ void pwmout_period(pwmout_t *obj, float period) // Step 4: For best accuracy, recalculate the top_count value using the divider. obj->top_count = calc_top_count_for_period(obj->clock_divider, period); + +#if RP2040_PWMOUT_DEBUG + printf("period = %f, desired_divider = %f\n", + period, + desired_divider); +#endif } // Save period for later obj->period = period; +#if RP2040_PWMOUT_DEBUG + printf("obj->clock_divider = %f, obj->cfg.div = %" PRIu32 ", obj->top_count = %" PRIu16 "\n", + obj->clock_divider, + obj->cfg.div, + obj->top_count); +#endif + + // Set the new divider and top_count values. + pwm_config_set_wrap(&(obj->cfg), obj->top_count); pwm_init(obj->slice, &(obj->cfg), false); } diff --git a/targets/targets.json5 b/targets/targets.json5 index 57df8503104..af4e3d0c27e 100644 --- a/targets/targets.json5 +++ b/targets/targets.json5 @@ -4876,7 +4876,12 @@ "device_name": "STM32U575ZITx", "detect_code": [ "886" - ] + ], + "overrides": { + // As shipped, this nucleo board connects VREFP to VDD_MCU, and connects VDD_MCU to 3.3V. + // Jumper JP4 can be used to switch VDD_MCU to 1.8V in which case you should override this setting to 1.8. + "default-adc-vref": 3.3 + } }, "MCU_STM32U585xI": { "inherits": [ @@ -9758,7 +9763,7 @@ "RTC" ] }, - "RASPBERRY_PI_PICO_SWD": { + "RASPBERRY_PI_PICO": { "inherits": ["RP2040"], "macros_add": [ "PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1", @@ -9766,13 +9771,24 @@ "PICO_TIME_DEFAULT_ALARM_POOL_DISABLED", "PICO_ON_DEVICE=1", "PICO_UART_ENABLE_CRLF_SUPPORT=0" - ] - }, - "RASPBERRY_PI_PICO": { - "inherits": ["RASPBERRY_PI_PICO_SWD"], + ], "overrides": { "console-usb": true, - "console-uart": false + "console-uart": false, + + // ADC_VDD sets the ADC reference voltage on this chip. + // Most RP2040 boards set this pin to 3.3V. + // Note that if the I/O voltage is set to less than the ADC reference voltage, + // voltages higher than the I/O voltage are illegal for the analog pins + // (so the ADC can never read 100%). + "default-adc-vref": 3.3 + } + }, + "RASPBERRY_PI_PICO_SWD": { + "inherits": ["RASPBERRY_PI_PICO"], + "overrides": { + "console-usb": false, + "console-uart": true } } }