Self-Aligning Satellite Dish in Rust: Final
Ian Ndeda
Posted on November 29, 2024
In this part, we'll be largely tying up loose ends in the application logic.
Table of contents
Requirements
- 1 x Raspberry Pico board
- 1 x USB Cable type 2.0
- 1 x HC-05 Bluetooth module
- 1 x HMC5833L Compass Module
- 40 x M-M Jumper Wires
- 2 x Mini Breadboards
- 2 x SG90 Servo Motor
- 1 x PTZ Kit
- 1 x GPS Module
- 2 x Relay modules
- 2 x 4.2V Li-ion Batteries
- 1 x DC-DC Step-down Converter
- 1 x Diode
Implementation
Connections
The electrical connections will be the same as from the last part. The magnetometer should now be mounted on the PTZ kit to properly capture the heading of the moving system as shown below.
Auto
We are now going to flesh out the auto
arm of the application loop
by programming for the automatic pan and tilt movements.
We have already found that compass headings obtained from the magnetometer vary significantly when tilted. Coupling a gyroscope with the magnetometer would be the proper solution to achieve tilt-compensated compass headings.
Our makeshift solution to this problem will be to check whether the auto
loop is in its first iteration. If so, the PTZ kit must initially face up before determining the compass heading. This is to avoid readings distorted by tilting.
The
AUTO_FIRST_ITER
flag is set while toggling from manual to auto.
If, however, the auto
loop isn't in its first iteration, the heading will be taken at a tilt and the value compared to adjusted_theta
, which is simply the heading after the pan and tilt from the first iteration.
Refer to this algorithm for a clearer explanation.
Replace the
Uart
tests in our setup area with an announcing message:Self-Aligning Satellite Dish in Rust
.We do not need to print the raw GPS data and coordinates; we can just show the look angles and magnetic heading.
Add the following variables that we'll use.
let mut adjusted_theta: f32 = 0.;// adjusted theta; new theta to track, considers tilt
let mut heading = 0.;// magnetic heading
let mut ref_theta = 0.;// reference theta; initial value to be referenced for variance
In the auto
arm after acquiring look angles, add the following to enable the auto alignment of the dish.
// Confirm theta == ref_theta from last iteration
// otherwise go back to first iteration
if theta.round() != ref_theta.round() {
AUTO_FIRST_ITER.borrow(cs).set(true);
}
if AUTO_FIRST_ITER.borrow(cs).get() {
// Tilt to face up for accurate heading readings
tilt_angle = 90.;
position_kit_tilt(pwm2, &mut tilt_angle);
delay.delay_ms(250);// wait for tilt kit to arrive at 90 deg
// Get the magnetic heading
heading = get_magnetic_heading(&mut i2c0);
writeln!(serialbuf, "> heading: {} deg.\n> tilt: {} deg.",
heading.round(), &mut tilt_angle).unwrap();
transmit_uart_data(
uart_data.as_mut().unwrap(),
serialbuf);
} else {
let tilted_heading = get_magnetic_heading(&mut i2c0);// get the adjusted magnetic heading i.e. at a tilt
heading = tilted_heading;
theta = adjusted_theta;// we'll now track adjusted theta since we're at a tilt
writeln!(serialbuf, "adjusted theta: {} deg.", theta).unwrap();
transmit_uart_data(
uart_data.as_mut().unwrap(),
serialbuf);
writeln!(serialbuf, "> tilted heading: {} deg.\n> tilt: {} deg.",
heading.round(), &mut tilt_angle).unwrap();
transmit_uart_data(
uart_data.as_mut().unwrap(),
serialbuf);
}
// Pan moded servo cw while heading != theta i.e.
// Dish is not locked with theta
while !heading_within_range(theta, heading) {
if pan_clockwise(theta, heading) {
pan(&mut delay, sio.as_mut().unwrap(), Direction::Cw);
heading = get_magnetic_heading(&mut i2c0);
writeln!(serialbuf, "Pan Cw: {} --> {}",
heading.round(), theta.round()).unwrap();
transmit_uart_data(
uart_data.as_mut().unwrap(),
serialbuf);
} else {
pan(&mut delay, sio.as_mut().unwrap(), Direction::Ccw);
heading = get_magnetic_heading(&mut i2c0);
writeln!(serialbuf, "Pan Ccw: {} --> {}",
heading.round(), theta.round()).unwrap();
transmit_uart_data(
uart_data.as_mut().unwrap(),
serialbuf);
}
}
Afterwards clear the GGA_ACQUIRED_FLAG
to say we are through.
GGA_ACQUIRED.borrow(cs).set(false);// clear the flag
Position Zero
In the manual
arm, we need to write code for position Zero
: when the system is generally looking North and facing up.
// Face 0° and look up
tilt_angle = 90.;
position_kit_tilt(pwm2, &mut tilt_angle);
delay.delay_ms(250);// wait for tilt kit to arrive at 90 deg
heading = get_magnetic_heading(&mut i2c0);
theta = 0.;
while !heading_within_range(theta, heading) {
if pan_clockwise(theta, heading) {
pan(&mut delay, sio.as_mut().unwrap(), Direction::Cw);
heading = get_magnetic_heading(&mut i2c0);
} else {
pan(&mut delay, sio.as_mut().unwrap(), Direction::Ccw);
heading = get_magnetic_heading(&mut i2c0);
}
}
writeln!(serialbuf, "heading: {} tilt angle: {}",
heading.round(), tilt_angle).unwrap();
transmit_uart_data(uart_data.as_mut().unwrap(), serialbuf);
Add the following helper functions:
fn tilt_up(phi: f32, tilt_angle: f32) -> bool {
let tilt = phi - tilt_angle;
tilt < 0.
}
fn heading_within_range(theta: f32, heading: f32) -> bool {
// checks whether heading is +/- 2 of desired pan angle
heading.round() == theta.round() || heading.round() == theta.round() - 1.
|| heading.round() == theta.round() - 2.
|| heading.round() == theta.round() + 1.
|| heading.round() == theta.round() + 2.
}
fn pan_clockwise(theta: f32, heading: f32) -> bool {
// Check if pan sd be cw or ccw
let pan = theta - heading;
if pan.abs() < 180. {
pan > 0.
} else {
pan < 0.
}
}
Results
Here is the final copy of code after the above algorithm has been implemented.
❗ Do not forget to conduct one last calibration with the mounted magnetometer in its final position2. You can load the final code from the
examples/compass.rs
to facilitate.
The project is complete! We can now remotely control our model satellite dish via Bluetooth and give it commands to run either in auto or manual modes.
Load the program into the Pico in release
mode.
cargo run --release
While in manual mode, giving the CW
command has an appropriate effect on the value of the compass heading; the same with the CCW
command.
The Zero
command aligns the system North.
While in auto mode, the kit aligns to the look angles, starting with a pan until theta
is achieved and then a tilt up to phi
.
The PTZ kit is shown in the demonstration below while in auto mode. The selected GSO satellite is tracked when the entire system is rotated.
Recommendations.
This project took a naive and straightforward approach in fulfilling its objective. There is a lot of room for improvement. Some low-lying picks are listed below:
- Introduce the RP2040's watchdog timer.Some of the code we wrote in the project is "blocking," which means that subsequent code must wait until it has finished running. Sometimes these code segments might not finish, freezing the software as a whole. We could use Async Rust or Rust's RTIC framework to write alternative non-blocking programs. We could also use a watchdog timer to break out of these extended loops.
- Have more functions returning. In case of an error during a run, the function will simply return the error for handling instead of stalling the entire system.
- Use a gyroscope to complement the magnetometer. This will enable the acquisition of tilt-compensated compass headings. Check this.
- Implement the entire program as a state machine.
- Produce an electrical schematic and PCB for the project.
Posted on November 29, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.