Skip to content

Commit

Permalink
Fix ADC implementation so it works! Fixes incorrect return type, adds…
Browse files Browse the repository at this point in the history
… +1 to top_count, fixes missing pwm_config_set_wrap() call.
  • Loading branch information
multiplemonomials committed Jan 15, 2024
1 parent fff1870 commit 1494794
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 26 deletions.
2 changes: 1 addition & 1 deletion platform/mbed_lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 1 addition & 2 deletions targets/TARGET_RASPBERRYPI/TARGET_RP2040/analogin_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
65 changes: 49 additions & 16 deletions targets/TARGET_RASPBERRYPI/TARGET_RP2040/pwmout_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,17 @@

#include <math.h>

// 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 <stdio.h>
#include <inttypes.h>
#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
Expand All @@ -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).
Expand Down Expand 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);
}

Expand Down Expand Up @@ -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);
}

Expand Down
30 changes: 23 additions & 7 deletions targets/targets.json5
Original file line number Diff line number Diff line change
Expand Up @@ -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": [
Expand Down Expand Up @@ -9758,21 +9763,32 @@
"RTC"
]
},
"RASPBERRY_PI_PICO_SWD": {
"RASPBERRY_PI_PICO": {
"inherits": ["RP2040"],
"macros_add": [
"PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1",
"MBED_MPU_CUSTOM",
"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
}
}
}

0 comments on commit 1494794

Please sign in to comment.