2323#include "pinmap.h"
2424#include "objects.h"
2525#include "mbed_assert.h"
26+ #include <mbed_error.h>
2627
2728#include <math.h>
2829
@@ -48,8 +49,8 @@ struct pwm_clock_freq {
4849// - LFRC - internal low freq RC oscillator, 1.024kHz +-32% (no that's not a typo!)
4950// This means we have quite a wide range of base clock frequencies available, though period accuracy will be pretty
5051// poor if the LFRC gets selected.
51- #define NUM_CLOCK_OPTIONS 16
52- static const struct pwm_clock_freq pwm_clocks [ NUM_CLOCK_OPTIONS ] = {
52+ #define NUM_CLOCK_SOURCE_OPTIONS 16
53+ static const struct pwm_clock_freq pwm_clock_sources [ NUM_CLOCK_SOURCE_OPTIONS ] = {
5354 {AM_HAL_CTIMER_HFRC_12MHZ , 12000000 },
5455 {AM_HAL_CTIMER_HFRC_3MHZ , 3000000 },
5556 {AM_HAL_CTIMER_HFRC_187_5KHZ , 187500 },
@@ -73,63 +74,8 @@ static const struct pwm_clock_freq pwm_clocks[NUM_CLOCK_OPTIONS] = {
7374};
7475
7576/// Largest top count value supported by hardware. Using this value will provide the highest duty cycle resolution.
76- const uint16_t MAX_TOP_COUNT = 65534 ;
77-
78-
79-
80- /// Calculate the effective PWM period (in floating point seconds) based on a divider and top_count value
81- static float calc_effective_pwm_period (float divider , uint16_t top_count )
82- {
83- // Note: The hardware counts to top_count *inclusively*, so we have to add 1
84- // to get the number of clock cycles that a given top_count value will produce
85- return 1.0f / ((clock_get_hz (clk_sys ) / divider ) / (top_count + 1 ));
86- }
87-
88- /// Calculate the best possible top_count value (rounding up) for a divider and a desired pwm period
89- static uint16_t calc_top_count_for_period (float divider , float desired_pwm_period )
90- {
91- // Derivation:
92- // desired_pwm_period = 1.0f / ((clock_get_hz(clk_sys) / divider) / (top_count + 1))
93- // desired_pwm_period = (top_count + 1) / (clock_get_hz(clk_sys) / divider)
94- // desired_pwm_period * (clock_get_hz(clk_sys) / divider) - 1 = top_count
95-
96- long top_count_float = lroundf (desired_pwm_period * (clock_get_hz (clk_sys ) / divider ) - 1 );
97- MBED_ASSERT (top_count_float <= MAX_TOP_COUNT );
98- return (uint16_t )top_count_float ;
99- }
100-
101- /// Calculate the best possible floating point divider value for a desired pwm period.
102- /// This function assumes that top_count is set to MAX_TOP_COUNT.
103- static float calc_divider_for_period (float desired_pwm_period )
104- {
105- // Derivation:
106- // (desired_pwm_period * clock_get_hz(clk_sys)) / divider - 1 = top_count
107- // (desired_pwm_period * clock_get_hz(clk_sys)) / divider = top_count + 1
108- // divider = (desired_pwm_period * clock_get_hz(clk_sys)) / (top_count + 1)
109-
110- return (desired_pwm_period * clock_get_hz (clk_sys )) / (MAX_TOP_COUNT + 1 );
111- }
112-
113- /// Convert PWM divider from floating point to a fixed point number (rounding up).
114- /// The divider is returned as an 8.4 bit fixed point number, which is what the Pico registers use.
115- static uint16_t pwm_divider_float_to_fixed (float divider_float )
116- {
117- // To convert to a fixed point number, multiply by 16 and then round up
118- uint16_t divider_exact = ceil (divider_float * 16 );
119-
120- // Largest supported divider is 255 and 15/16
121- if (divider_exact > 0xFFF )
122- {
123- divider_exact = 0xFFF ;
124- }
125- return divider_exact ;
126- }
127-
128- /// Convert PWM divider from the fixed point hardware value (8.4 bits) to a float.
129- static float pwm_divider_fixed_to_float (uint16_t divider_fixed )
130- {
131- return divider_fixed / 16.0f ;
132- }
77+ /// The hardware performs (CMPR register value + 1) counts and it's a 16-bit register, so the actual max top count is 2^16.
78+ const uint32_t MAX_TOP_COUNT = 65536 ;
13379
13480void pwmout_init (pwmout_t * obj , PinName pin )
13581{
@@ -139,7 +85,7 @@ void pwmout_init(pwmout_t *obj, PinName pin)
13985 const PWMName pwmName = pinmap_peripheral (pin , PinMap_PWM_OUT );
14086
14187 /* Populate PWM object with values. */
142- obj -> pwmName = pwmName ;
88+ obj -> pwm_name = pwmName ;
14389
14490 // Configure the pin
14591 am_hal_ctimer_output_config (APOLLO3_PWMNAME_GET_CTIMER (pwmName ),
@@ -206,87 +152,80 @@ float pwmout_read(pwmout_t *obj)
206152void pwmout_period (pwmout_t * obj , const float desired_period )
207153{
208154 // To find the period, we perform the following steps:
209- // - Determine the slowest clock frequency that we can use while still hitting the needed period
155+ // - Determine the fastest clock frequency that we can use while still hitting the needed period
210156 // - Calculate the correct top_count value that will produce as close to the desired period as possible
211-
212-
213- if (period <= calc_effective_pwm_period (1 , MAX_TOP_COUNT ))
214- {
215- // Short period. Leave divider at 1 and reduce top_count to match the expected period
216- obj -> clock_divider = 1.0f ;
217- obj -> cfg .div = PWM_CHn_DIV_1 ;
218- obj -> top_count = calc_top_count_for_period (obj -> clock_divider , period );
157+ // - Write the new top_count value into the hardware
158+
159+ size_t clk_source_idx ;
160+ bool found_clk_source = false;
161+ for (clk_source_idx = 0 ; clk_source_idx < NUM_CLOCK_SOURCE_OPTIONS ; ++ clk_source_idx ) {
162+ const float divider_max_period = MAX_TOP_COUNT / (float )pwm_clock_sources [clk_source_idx ].frequency ;
163+ if (divider_max_period >= desired_period ) {
164+ found_clk_source = true;
165+ break ;
166+ }
167+ }
168+ if (!found_clk_source ) {
169+ MBED_ERROR (MBED_MAKE_ERROR (MBED_MODULE_DRIVER_PWM , MBED_ERROR_CODE_INVALID_ARGUMENT ), "Clock frequency too slow!" );
219170 }
220- else
221- {
222- // Long period, need to use divider.
223-
224- // Step 1: Calculate exact desired divider such that top_count would equal MAX_TOP_COUNT
225- float desired_divider = calc_divider_for_period (period );
226-
227- // Step 2: Round desired divider upwards to the next value the hardware can do.
228- // We go upwards so that the top_count value can be trimmed downwards for the best period accuracy.
229- uint16_t divider_fixed_point = pwm_divider_float_to_fixed (desired_divider );
230- obj -> cfg .div = divider_fixed_point ;
231-
232- // Step 3: Get the divider we'll actually be using as a float
233- obj -> clock_divider = pwm_divider_fixed_to_float (divider_fixed_point );
234171
235- // Step 4: For best accuracy, recalculate the top_count value using the divider.
236- obj -> top_count = calc_top_count_for_period (obj -> clock_divider , period );
172+ // Now that we have found the best clock source, calculate top_count to hit the desired period
173+ obj -> clock_period = 1.0f / (float )pwm_clock_sources [clk_source_idx ].frequency ;
174+ obj -> top_count = lroundf (desired_period / obj -> clock_period );
237175
238- #if APOLLO3_PWMOUT_DEBUG
239- printf ("period = %f, desired_divider = %f\n" ,
240- period ,
241- desired_divider );
242- #endif
176+ // The hardware cannot support a top_count of less than 2. If that happened than it means the
177+ // frequency is too fast.
178+ if (obj -> top_count < 2 ) {
179+ MBED_ERROR (MBED_MAKE_ERROR (MBED_MODULE_DRIVER_PWM , MBED_ERROR_CODE_INVALID_ARGUMENT ), "Clock frequency too fast!" );
243180 }
244181
245- // Save period for later
246- obj -> period = period ;
247-
248182#if APOLLO3_PWMOUT_DEBUG
249- printf ("obj->clock_divider = %f , obj->cfg.div = %" PRIu32 " , obj->top_count = %" PRIu16 "\n" ,
250- obj -> clock_divider ,
251- obj -> cfg . div ,
183+ printf ("clk_source_idx = %zu , obj->clock_period = %f , obj->top_count = %" PRIu32 "\n" ,
184+ clk_source_idx ,
185+ obj -> clock_period ,
252186 obj -> top_count );
253187#endif
254188
255- // Set the new divider and top_count values.
256- pwm_config_set_wrap (& (obj -> cfg ), obj -> top_count );
257- pwm_init (obj -> slice , & (obj -> cfg ), false);
189+ // Set new clock source. This stops the timer.
190+ am_hal_ctimer_config_single (APOLLO3_PWMNAME_GET_CTIMER (obj -> pwm_name ),
191+ APOLLO3_PWMNAME_GET_SEGMENT (obj -> pwm_name ),
192+ AM_HAL_CTIMER_FN_PWM_REPEAT | clk_source_idx );
193+
194+ // Set new period value. Note that:
195+ // - We need to program a value of (top_count - 1) into the HW register
196+ // - As part of setting the period we also need to program the duty cycle. For now we program it to zero,
197+ // in anticipation of a future pwmout_write() call
198+ if (APOLLO3_PWMNAME_GET_OUTPUT (obj -> pwm_name ) == AM_HAL_CTIMER_OUTPUT_NORMAL ) {
199+ am_hal_ctimer_period_set (APOLLO3_PWMNAME_GET_CTIMER (obj -> pwm_name ),
200+ APOLLO3_PWMNAME_GET_SEGMENT (obj -> pwm_name ),
201+ obj -> top_count - 1 ,
202+ 0 );
203+ }
204+ else {
205+ am_hal_ctimer_aux_period_set (APOLLO3_PWMNAME_GET_CTIMER (obj -> pwm_name ),
206+ APOLLO3_PWMNAME_GET_SEGMENT (obj -> pwm_name ),
207+ obj -> top_count - 1 ,
208+ 0 );
209+ }
210+
211+ am_hal_ctimer_start (APOLLO3_PWMNAME_GET_CTIMER (obj -> pwm_name ), APOLLO3_PWMNAME_GET_SEGMENT (obj -> pwm_name ));
258212}
259213
260- /** Set the PWM period specified in miliseconds, keeping the duty cycle the same
261- *
262- * Parameter obj The pwmout object
263- * Parameter ms The milisecond period
264- */
265214void pwmout_period_ms (pwmout_t * obj , int period )
266215{
267216 /* Set new period. */
268217 pwmout_period (obj , period / 1000.0f );
269218}
270219
271- /** Set the PWM period specified in microseconds, keeping the duty cycle the same
272- *
273- * Parameter obj The pwmout object
274- * Parameter us The microsecond period
275- */
276220void pwmout_period_us (pwmout_t * obj , int period )
277221{
278222 /* Set new period. */
279223 pwmout_period (obj , period / 1000000.0f );
280224}
281225
282- /** Read the PWM period specified in microseconds
283- *
284- * @param obj The pwmout object
285- * @return A int output period
286- */
287226int pwmout_read_period_us (pwmout_t * obj )
288227{
289- return lroundf (1000000 * calc_effective_pwm_period ( obj -> clock_divider , obj -> top_count ) );
228+ return lroundf (1000000 * obj -> top_count * obj -> clock_period );
290229}
291230
292231/** Set the PWM pulsewidth specified in seconds, keeping the period the same.
0 commit comments