Self-Aligning Satellite Dish in Rust: Servo
Ian Ndeda
Posted on November 28, 2024
We'll use servo motors, specifically the SG90, to actuate our PTZ kit. Servo motors are driven by pulse width modulation(PWM) signals.
Table of Contents
Servo
The SG90 is a microservo that can rotate through 180 degrees. It is operated by pulse width modulation (PWM) signals oscillating at 50 Hz. A pulse with a period of 1.5 ms (7.5% duty cycle) will position it to 0 degrees, 2 ms (10% duty cycle) to 90 degrees, and 1 ms (5% duty cycle) to -90 degrees. Feeding a signal with duty cycles ranging from 5 to 10% should therefore sweep the motor from 0 to 180 degrees.
❗ Testing of the servo at different duty cycles is necessary to ascertain the positions at a given duty. There's some variance from one servo to another.
Implementation
PWM
PWM is a common technique used to supply variable power to a system.
The RP2040 has 8 PWM slices, each with two output channels that can be run at different duty cycles. We shall use pwm2 with its second channel for this project.
First we'll need to configure the pins associated with pwm2. The PWM pin map below shows that we can select one of the following pins for pwm2: gp4
, gp5
, gp20
, or gp21
. We shall use gp20
in this project.
Under Pins
configure the pin.
// Configure GP20 as PWM channel 1A
pads_bank0.gpio(20).modify(|_, w| w
.pue().set_bit()// pull up enable
.pde().set_bit()// pull down enable
.od().clear_bit()// output disable
.ie().set_bit()// input enable
);
io_bank0.gpio(20).gpio_ctrl().modify(|_, w| w.funcsel().pwm());// connect to matching pwm
The programming parameters for the RP2040's PWM are set using the below formula.
Where:
- TOP is the upper limit count of the PWM's counter,
- CSR_PH_CORRECT is the phase correction selector that determines whether the PWM's counter counts backwards from TOP,
- and DIV_INT and DIV_FRAC are further used in dividing the counter to acquire a final PWM output.
For a wrap value of 50,000 and with phase correction enabled we can get the divider values below:
We will therefore set our DIV_INT as 25 and DIV_FRAC as 0.
// pwm2 set up
let pwm2 = dp.PWM.ch(2);// Acquire handle for pwm slice 2
resets.reset().modify(|_, w| w.pwm().clear_bit());// Deassert pwm
// Configuring pwm2
pwm2.csr().modify(|_, w| w
.divmode().div()// free runnning counter determined by fractional divider
.ph_correct().set_bit()// enable phase correction
);
pwm2.top().modify(|_, w| unsafe { w.bits(50_000) });// sets the wrap value: TOP
// For a fpwm of 50Hz w/ top of 50000 div need to be 25
pwm2.div().modify(|_, w| unsafe { w.int().bits(25).frac().bits(0) });
pwm2.csr().modify(|_, w| w.en().set_bit());// Enable pwm2
Final Code
Rearranging the code we get this final copy.
In the next part we shall run an example showing PWM in operation.
Posted on November 28, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.