Servo and Pico
To control a servo with the Raspberry Pi Pico, we need to set a 50Hz PWM frequency. Currently, RP-HAL doesn't allow directly setting the frequency, so we achieve this by adjusting the top
and div_int
values.
Refer the 1073th page of the RP2350 Datasheet to understand how top
and div_int
works.
Formula from datasheet
The following formula from the datasheet is used to calculate the period and determine the output frequency based on the system clock frequency.
-
Period calculation: \[ \text{period} = (\text{TOP} + 1) \times (\text{CSR_PH_CORRECT} + 1) \times \left( \text{DIV_INT} + \frac{\text{DIV_FRAC}}{16} \right) \]
-
PWM output frequency calculation:
\[ f_{PWM} = \frac{f_{sys}}{\text{period}} = \frac{f_{sys}}{(\text{TOP} + 1) \times (\text{CSR_PH_CORRECT} + 1) \times \left( \text{DIV_INT} + \frac{\text{DIV_FRAC}}{16} \right)} \]
Where:
- \( f_{PWM} \) is the PWM output frequency.
- \( f_{sys} \) is the system clock frequency. For the pico2, it is is 150MHZ.
Let's calculate top
We want the PWM frequency (f_pwm) to be 50 Hz. In order to achieve that, we are going to adjust the top
and div_int
values.
The top value must be within the range of 0 to 65535 (since it's a 16-bit unsigned integer). To make sure the top value fits within this range, I chose values for the divisor (div_int) in powers of 2 (such as 8, 16, 32, 64), though this isn't strictly necessary (it's just a preference). In this case, we chose div_int = 64
to calculate a top value that fits within the u16 range.
With the chosen div_int and system parameters, we can calculate the top using the following formula: \[ \text{top} = \frac{150,000,000}{50 \times 64} - 1 \]
\[ \text{top} = \frac{150,000,000}{3,200} - 1 \]
\[ \text{top} = 46,875 - 1 \]
\[ \text{top} = 46,874 \]
After performing the calculation, we find that the top value is 46,874
.
You can experiment with different div_int and corresponding top values. Just ensure that div_int stays within the u8 range, top fits within the u16 range, and the formula yields a 50Hz frequency.
Note:
- In case you are wondering, we are not setting the
div_frac
which is 0 by default. That's why it is not included in the calculation. - We are not going to enable the phase correct for this exercise, so it also can be excluded from the calculation (since it is just multiplying by 1); if you enable phase correct, then the calculation will differ since you have to multiply by 2 (1+1)
Position calculation based on top
To calculate the duty cycle that corresponds to specific positions (0, 90, and 180 degrees), we use the following formula based on the top value:
#![allow(unused)] fn main() { const PWM_DIV_INT: u8 = 64; const PWM_TOP: u16 = 46_874; const TOP: u16 = PWM_TOP + 1; // 0.5ms is 2.5% of 20ms; 0 degrees in servo const MIN_DUTY: u16 = (TOP as f64 * (2.5 / 100.)) as u16; // 1.5ms is 7.5% of 20ms; 90 degrees in servo const HALF_DUTY: u16 = (TOP as f64 * (7.5 / 100.)) as u16; // 2.4ms is 12% of 20ms; 180 degree in servo const MAX_DUTY: u16 = (TOP as f64 * (12. / 100.)) as u16; }
We multiply the TOP value by a duty cycle percentage to determine the appropriate pulse width for each position of the servo. You might need to adjust the percentage based on your servo.