From 687a94b0d9f5d39b3b7679131a46735b829be616 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Wed, 19 Jun 2024 17:50:31 -0700 Subject: [PATCH] STMicro: Improve PWM resolution (#283) * STMicro: Improve PWM resolution * Update all objects.h, rename counts -> top_count * Revert pin mode change, seems to be not needed --- targets/TARGET_STM/TARGET_STM32F0/objects.h | 10 --- targets/TARGET_STM/TARGET_STM32F1/objects.h | 10 --- targets/TARGET_STM/TARGET_STM32F2/objects.h | 10 --- targets/TARGET_STM/TARGET_STM32F3/objects.h | 10 --- targets/TARGET_STM/TARGET_STM32F4/objects.h | 10 --- targets/TARGET_STM/TARGET_STM32F7/objects.h | 10 --- targets/TARGET_STM/TARGET_STM32G0/objects.h | 11 --- targets/TARGET_STM/TARGET_STM32G4/objects.h | 10 --- targets/TARGET_STM/TARGET_STM32H7/objects.h | 10 --- targets/TARGET_STM/TARGET_STM32L0/objects.h | 11 --- targets/TARGET_STM/TARGET_STM32L1/objects.h | 11 --- targets/TARGET_STM/TARGET_STM32L4/objects.h | 10 --- targets/TARGET_STM/TARGET_STM32L5/objects.h | 10 --- targets/TARGET_STM/TARGET_STM32U5/objects.h | 10 --- targets/TARGET_STM/TARGET_STM32WB/objects.h | 10 --- targets/TARGET_STM/TARGET_STM32WL/objects.h | 10 --- targets/TARGET_STM/device.h | 1 + targets/TARGET_STM/pwmout_api.c | 96 ++++++++++++++------- targets/TARGET_STM/stm_pwmout_api.h | 50 +++++++++++ 19 files changed, 117 insertions(+), 193 deletions(-) create mode 100644 targets/TARGET_STM/stm_pwmout_api.h diff --git a/targets/TARGET_STM/TARGET_STM32F0/objects.h b/targets/TARGET_STM/TARGET_STM32F0/objects.h index ef188ba48b0..dc30a58945a 100644 --- a/targets/TARGET_STM/TARGET_STM32F0/objects.h +++ b/targets/TARGET_STM/TARGET_STM32F0/objects.h @@ -34,16 +34,6 @@ extern "C" { #endif -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32F1/objects.h b/targets/TARGET_STM/TARGET_STM32F1/objects.h index 94d93ad825d..621880d3b7c 100644 --- a/targets/TARGET_STM/TARGET_STM32F1/objects.h +++ b/targets/TARGET_STM/TARGET_STM32F1/objects.h @@ -60,16 +60,6 @@ struct port_s { __IO uint32_t *reg_out; }; -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32F2/objects.h b/targets/TARGET_STM/TARGET_STM32F2/objects.h index ef3a22493cb..765a5019423 100644 --- a/targets/TARGET_STM/TARGET_STM32F2/objects.h +++ b/targets/TARGET_STM/TARGET_STM32F2/objects.h @@ -125,16 +125,6 @@ struct i2c_s { #endif }; -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - #if DEVICE_CAN struct can_s { CAN_HandleTypeDef CanHandle; diff --git a/targets/TARGET_STM/TARGET_STM32F3/objects.h b/targets/TARGET_STM/TARGET_STM32F3/objects.h index 96c9a1231ea..42c80d7c2d4 100644 --- a/targets/TARGET_STM/TARGET_STM32F3/objects.h +++ b/targets/TARGET_STM/TARGET_STM32F3/objects.h @@ -47,16 +47,6 @@ extern "C" { #endif -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32F4/objects.h b/targets/TARGET_STM/TARGET_STM32F4/objects.h index 320848eaa9f..1a9f9d30e89 100644 --- a/targets/TARGET_STM/TARGET_STM32F4/objects.h +++ b/targets/TARGET_STM/TARGET_STM32F4/objects.h @@ -47,16 +47,6 @@ struct port_s { __IO uint32_t *reg_out; }; -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; diff --git a/targets/TARGET_STM/TARGET_STM32F7/objects.h b/targets/TARGET_STM/TARGET_STM32F7/objects.h index b311d1f4888..9f6f2b82be0 100644 --- a/targets/TARGET_STM/TARGET_STM32F7/objects.h +++ b/targets/TARGET_STM/TARGET_STM32F7/objects.h @@ -65,16 +65,6 @@ struct trng_s { RNG_HandleTypeDef handle; }; -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32G0/objects.h b/targets/TARGET_STM/TARGET_STM32G0/objects.h index 1d8672a3085..227932cfe44 100644 --- a/targets/TARGET_STM/TARGET_STM32G0/objects.h +++ b/targets/TARGET_STM/TARGET_STM32G0/objects.h @@ -45,17 +45,6 @@ struct port_s { __IO uint32_t *reg_out; }; - -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32G4/objects.h b/targets/TARGET_STM/TARGET_STM32G4/objects.h index 368ec3c87fa..f22b73baa9e 100644 --- a/targets/TARGET_STM/TARGET_STM32G4/objects.h +++ b/targets/TARGET_STM/TARGET_STM32G4/objects.h @@ -45,16 +45,6 @@ struct port_s { __IO uint32_t *reg_out; }; -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32H7/objects.h b/targets/TARGET_STM/TARGET_STM32H7/objects.h index 8608dce0f22..5f8894d8b00 100644 --- a/targets/TARGET_STM/TARGET_STM32H7/objects.h +++ b/targets/TARGET_STM/TARGET_STM32H7/objects.h @@ -54,16 +54,6 @@ struct trng_s { RNG_HandleTypeDef handle; }; -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32L0/objects.h b/targets/TARGET_STM/TARGET_STM32L0/objects.h index 93498419f76..0a45d3e34d7 100644 --- a/targets/TARGET_STM/TARGET_STM32L0/objects.h +++ b/targets/TARGET_STM/TARGET_STM32L0/objects.h @@ -47,17 +47,6 @@ struct port_s { __IO uint32_t *reg_out; }; - -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32L1/objects.h b/targets/TARGET_STM/TARGET_STM32L1/objects.h index d98642b56ba..89cc310e8ac 100644 --- a/targets/TARGET_STM/TARGET_STM32L1/objects.h +++ b/targets/TARGET_STM/TARGET_STM32L1/objects.h @@ -46,17 +46,6 @@ struct port_s { __IO uint32_t *reg_out; }; - -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32L4/objects.h b/targets/TARGET_STM/TARGET_STM32L4/objects.h index 8f1e182cf9b..81022a2fc29 100644 --- a/targets/TARGET_STM/TARGET_STM32L4/objects.h +++ b/targets/TARGET_STM/TARGET_STM32L4/objects.h @@ -44,16 +44,6 @@ struct dac_s { }; #endif -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32L5/objects.h b/targets/TARGET_STM/TARGET_STM32L5/objects.h index ac1c39fd010..e9e41b58436 100644 --- a/targets/TARGET_STM/TARGET_STM32L5/objects.h +++ b/targets/TARGET_STM/TARGET_STM32L5/objects.h @@ -54,16 +54,6 @@ struct trng_s { RNG_HandleTypeDef handle; }; -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32U5/objects.h b/targets/TARGET_STM/TARGET_STM32U5/objects.h index dd46048ac79..9afc8983740 100644 --- a/targets/TARGET_STM/TARGET_STM32U5/objects.h +++ b/targets/TARGET_STM/TARGET_STM32U5/objects.h @@ -54,16 +54,6 @@ struct trng_s { RNG_HandleTypeDef handle; }; -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32WB/objects.h b/targets/TARGET_STM/TARGET_STM32WB/objects.h index 65eb19e7a2a..9251bd877f1 100644 --- a/targets/TARGET_STM/TARGET_STM32WB/objects.h +++ b/targets/TARGET_STM/TARGET_STM32WB/objects.h @@ -37,16 +37,6 @@ extern "C" { #endif -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/TARGET_STM32WL/objects.h b/targets/TARGET_STM/TARGET_STM32WL/objects.h index 780971d2d95..0d4b071ec0f 100644 --- a/targets/TARGET_STM/TARGET_STM32WL/objects.h +++ b/targets/TARGET_STM/TARGET_STM32WL/objects.h @@ -40,16 +40,6 @@ struct dac_s { }; #endif -struct pwmout_s { - PWMName pwm; - PinName pin; - uint32_t prescaler; - uint32_t period; - uint32_t pulse; - uint8_t channel; - uint8_t inverted; -}; - struct serial_s { UARTName uart; int index; // Used by irq diff --git a/targets/TARGET_STM/device.h b/targets/TARGET_STM/device.h index ff2c27bdfbb..0c2824efea8 100644 --- a/targets/TARGET_STM/device.h +++ b/targets/TARGET_STM/device.h @@ -38,6 +38,7 @@ #include "objects.h" #include "stm_i2c_api.h" #include "stm_spi_api.h" +#include "stm_pwmout_api.h" #if DEVICE_USTICKER #include "us_ticker_defines.h" diff --git a/targets/TARGET_STM/pwmout_api.c b/targets/TARGET_STM/pwmout_api.c index 8026921c670..51845d1aacf 100644 --- a/targets/TARGET_STM/pwmout_api.c +++ b/targets/TARGET_STM/pwmout_api.c @@ -37,8 +37,26 @@ #include "PeripheralPins.h" #include "pwmout_device.h" +#include + static TIM_HandleTypeDef TimHandle; +// Maximum counts per timer cycle. +// Note: Hardware can do 65536, but I don't believe you can have 100% duty cycle with 65536 counts/cycle. +#define MAX_COUNTS_PER_CYCLE 65535 + +// Maximum prescaler division possible +#define MAX_PRESCALER 65536 + +// Change to 1 to enable debug prints of what's being calculated. +// Must comment out the critical section calls in PwmOut to use. +#define STM_PWMOUT_DEBUG 0 + +#if STM_PWMOUT_DEBUG +#include +#include +#endif + /* Convert STM32 Cube HAL channel to LL channel */ uint32_t TIM_ChannelConvert_HAL2LL(uint32_t channel, pwmout_t *obj) { @@ -200,8 +218,8 @@ static void _pwmout_init_direct(pwmout_t *obj, const PinMap *pinmap) obj->pin = pinmap->pin; obj->period = 0; - obj->pulse = 0; - obj->prescaler = 1; + obj->compare_value = 0; + obj->top_count = 1; pwmout_period_us(obj, 20000); // 20 ms per default } @@ -235,11 +253,22 @@ void pwmout_write(pwmout_t *obj, float value) value = 1.0; } - obj->pulse = (uint32_t)((float)obj->period * value + 0.5); + // Calculate the correct compare value. The PWM output changes to 0 once the counter becomes + // >= the compare value. + // Examples: + // - if value is .999 and counts is 3, we want to write 3 so the PWM is on all the time + // - if value is .33 and counts is 3, we want to write 1 so that we turn off after the counter becomes 1. + // - if value is .1 and counts is 3, that rounds to 0 so we want to write 0 so that the PWM is off all the time + + obj->compare_value = lroundf((float)obj->top_count * value); + +#if STM_PWMOUT_DEBUG + printf("Setting compare value to %" PRIu32 "\n", obj->compare_value); +#endif // Configure channels sConfig.OCMode = TIM_OCMODE_PWM1; - sConfig.Pulse = obj->pulse / obj->prescaler; + sConfig.Pulse = obj->compare_value; sConfig.OCPolarity = TIM_OCPOLARITY_HIGH; sConfig.OCFastMode = TIM_OCFAST_DISABLE; #if defined(TIM_OCIDLESTATE_RESET) @@ -292,7 +321,7 @@ float pwmout_read(pwmout_t *obj) { float value = 0; if (obj->period > 0) { - value = (float)(obj->pulse) / (float)(obj->period); + value = (float)(obj->compare_value) / (float)(obj->top_count); } return ((value > (float)1.0) ? (float)(1.0) : (value)); } @@ -341,35 +370,41 @@ void pwmout_period_us(pwmout_t *obj, int us) #endif } - - /* By default use, 1us as SW pre-scaler */ - obj->prescaler = 1; // TIMxCLK = PCLKx when the APB prescaler = 1 else TIMxCLK = 2 * PCLKx + uint32_t timxClk; if (APBxCLKDivider == RCC_HCLK_DIV1) { - TimHandle.Init.Prescaler = (((PclkFreq) / 1000000)) - 1; // 1 us tick + timxClk = PclkFreq; } else { - TimHandle.Init.Prescaler = (((PclkFreq * 2) / 1000000)) - 1; // 1 us tick - } - TimHandle.Init.Period = (us - 1); - - /* In case period or pre-scalers are out of range, loop-in to get valid values */ - while ((TimHandle.Init.Period > 0xFFFF) || (TimHandle.Init.Prescaler > 0xFFFF)) { - obj->prescaler = obj->prescaler * 2; - if (APBxCLKDivider == RCC_HCLK_DIV1) { - TimHandle.Init.Prescaler = (((PclkFreq) / 1000000) * obj->prescaler) - 1; - } else { - TimHandle.Init.Prescaler = (((PclkFreq * 2) / 1000000) * obj->prescaler) - 1; - } - TimHandle.Init.Period = (us - 1) / obj->prescaler; - /* Period decreases and prescaler increases over loops, so check for - * possible out of range cases */ - if ((TimHandle.Init.Period < 0xFFFF) && (TimHandle.Init.Prescaler > 0xFFFF)) { - error("Cannot initialize PWM\n"); - break; - } + timxClk = PclkFreq * 2; } - TimHandle.Init.ClockDivision = 0; + // To generate the desired frequency, we have 2 knobs to play with: the reload value and the + // duty cycle. We generally want to have the reload value as high as possible since that will + // give the best duty cycle resolution at high frequencies. + + // Step 1: Calculate the smallest prescaler that will allow the desired period to be achieved by + // tuning the reload value. + // (prescaler * reloadValue) / (timxClk) = period + // prescaler = (period * timxClk) / reloadValue + // minimum needed prescaler (floating point) = (period * timxClk) / 65536 + + const float periodSeconds = us * 1e-6f; + const uint32_t prescaler = ceilf(periodSeconds * timxClk / MAX_COUNTS_PER_CYCLE); + MBED_ASSERT(prescaler <= MAX_PRESCALER); + + // Step 2: Calculate top count based on determined prescaler + // reloadValue = period * timxClk / prescaler + uint32_t topCount = lroundf(periodSeconds * timxClk / prescaler); + MBED_ASSERT(topCount <= MAX_COUNTS_PER_CYCLE); + +#if STM_PWMOUT_DEBUG + printf("Setting prescaler to %" PRIu32 " and top count to %" PRIu32 "\n", prescaler, topCount); +#endif + + TimHandle.Init.Prescaler = prescaler - 1; // value of 0 means divide by 1 + TimHandle.Init.Period = topCount - 1; // value of 0 means count once + + TimHandle.Init.ClockDivision = 0; // Dead time generators and digital filters use CK_INT directly TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP; if (HAL_TIM_PWM_Init(&TimHandle) != HAL_OK) { @@ -378,6 +413,7 @@ void pwmout_period_us(pwmout_t *obj, int us) // Save for future use obj->period = us; + obj->top_count = topCount; // Set duty cycle again pwmout_write(obj, dc); @@ -409,7 +445,7 @@ void pwmout_pulsewidth_us(pwmout_t *obj, int us) int pwmout_read_pulsewidth_us(pwmout_t *obj) { float pwm_duty_cycle = pwmout_read(obj); - return (int)(pwm_duty_cycle * (float)obj->period); + return lroundf(pwm_duty_cycle * (float)obj->period); } const PinMap *pwmout_pinmap() diff --git a/targets/TARGET_STM/stm_pwmout_api.h b/targets/TARGET_STM/stm_pwmout_api.h new file mode 100644 index 00000000000..46bcacea22d --- /dev/null +++ b/targets/TARGET_STM/stm_pwmout_api.h @@ -0,0 +1,50 @@ +/* mbed Microcontroller Library + * Copyright (c) 2024 STMicroelectronics + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef MBED_OS_STM_PWMOUT_API_H +#define MBED_OS_STM_PWMOUT_API_H + +#include "PeripheralNames.h" +#include "PinNames.h" + +struct pwmout_s { + + // PWM (timer) peripheral that this pwmout is created with + PWMName pwm; + + // Pin that this pwmout is using. This is used to reset the pin function in pwmout_free() + PinName pin; + + // Current period of the pwmout, in microseconds + uint32_t period; + + // Current compare value. Once the PWM counter becomes >= this value, + // the PWM turns off. + uint32_t compare_value; + + // How many counts the PWM timer makes before it resets. + // Example: if top_count = 3, the timer will count 0, 1, 2, 0, 1, 2, etc. + uint16_t top_count; + + // Channel number on the timer that this pin is connected to + uint8_t channel; + + // Whether this hardware channel is an inverted output (1) or not (0) + uint8_t inverted; +}; + +#endif //MBED_OS_STM_PWMOUT_API_H \ No newline at end of file