Self-Aligning Dish in Rust: Command
Ian Ndeda
Posted on November 21, 2024
Our project is designed to receive commands remotely via Bluetooth. To facilitate this transfer, the UART
peripheral in the Pico will have to be set up. The UART
is a protocol that controls serial communication between supported devices.
The Pico has two independent UART
s. We'll use UART1
for this task.
Table of Contents
Flowchart
Every time a command byte is received via Bluetooth, a UART
interrupt is fired. The received byte is processed, and the command to our system is updated.
Requirements
- 1 x Raspberry Pico board
- 1 x USB Cable type 2.0
- 1 x HC-05 Bluetooth module
- 10 x M-M jumper wires
- 2 x Mini Breadboards
- Serial Bluetooth Terminal App
Implementation
Remote commands will be sent from a Serial Bluetooth Terminal App on a phone and will be a byte each. They are shown below.
Command | Execution |
---|---|
A | Toggle the system between Manual and Auto modes |
B | Move the PTZ kit Clockwise |
C | Move the PTZ kit Counter-clockwise |
D | Move the PTZ kit Up |
E | Move the PTZ kit Down |
F | Move the PTZ kit to Position Zero |
We will organize the above options into a Direction
enum.
enum Direction {
Cw,
Ccw,
Up,
Down,
Zero,
}
Create a global static variable DIRECTION
that will hold the position of the system at any given time.
static DIRECTION: Mutex<RefCell<Option<Direction>>> = Mutex::new(RefCell::new(None));
The Serial Bluetooth App is linked to the HC-05 module which is in turn physically connected to gp8
and gp9
pins in the Pico. A complete path is therefore created from the phone issuing the command to the RP2040 processing it.
Serial Bluetooth App
As mentioned earlier we will be using the Serial Bluetooth App to send our commands to our system remotely.
We first have to set up the app so that it sends precisely one byte per press. Navigate to settings and under Newline
select None
. This is to enable the transmission of the command byte only and therefore match the UART
interrupt, which we'll also set up to receive only one byte at a time.
We will also allow local echo to help us visualize the commands we are sending.
Also edit the macro rows to have the commands discussed above i.e. Auto/Manual
, CW
, CCW
, UP
, DOWN
and Zero
.
You should have something of this sort:
Connections
The electrical connections will be as shown above.
UART Configuration
To begin we must configure the pins on the Pico board associated with UART1
. These shall provide the physical connection to the HC-05 module in our program.
We can see from above table that gp8
and gp9
can be used for UART1
. We'll configure them as push-pull uart pins.
// Configure gp8 as UART1 Tx Pin
// Will be connected to HC-05 Rx Pin
pads_bank0.gpio(8).modify(|_, w| w
.pue().set_bit()
.pde().set_bit()
.od().clear_bit()
.ie().set_bit()
);
io_bank0.gpio(8).gpio_ctrl().modify(|_, w| w.funcsel().uart());
// Configure gp9 as UART1 Rx Pin
// Will be connected to HC-05 Tx Pin
pads_bank0.gpio(9).modify(|_, w| w
.pue().set_bit()
.pde().set_bit()
.od().clear_bit()
.ie().set_bit()
);
io_bank0.gpio(9).gpio_ctrl().modify(|_, w| w.funcsel().uart());
Creating a handle for UART
:
// Configuring UART1; command reception
let uart_cmd = dp.UART1;
resets.reset().modify(|_, w| w.uart1().clear_bit());// Deassert uart_cmd
We then program the UART
to run at 9600 baud-rate. The manual provides the formula for acquiring the baud-rate divisors.
where BRDi is the integer part and BRDf the fractional part.
Replacing for our desired baud-rate of 9600:
The reference manual states the fractional part BRDf is acquired by taking the integer part of the calculation
For a baudrate of 9600 we therefore require a divisor with integer part as 813 and fractional part as 51.
// Set baudrate at 96 00
uart_cmd.uartibrd().write(|w| unsafe { w.bits(813)});
uart_cmd.uartfbrd().write(|w| unsafe { w.bits(51)});
We finally have to set the word length to 8 and enable the peripheral.
uart_cmd.uartlcr_h().modify(|_, w| unsafe { w
.wlen().bits(0b11)// Set word length as 8
});
uart_cmd.uartcr().modify(|_, w| w
.uarten().set_bit()// Enable uart_cmd
.txe().set_bit()// Enable tx
.rxe().set_bit()// Enable rx
);
We now enable the UART1
interrupt and unmask it.
uart_cmd.uartimsc().modify(|_, w| w
.rtim().set_bit());// set interrupt for when there's one byte in uart rx fifo
unsafe {
cortex_m::peripheral::NVIC::unmask(interrupt::UART1_IRQ);// Unmask interrupt
}
Since the UART
peripheral will be accessed from both the main
application and the interrupt service routine it will need to be behind a critical section to prevent data races. To facilitate this we create a UART1
global variable and move uart_cmd
into it.
static UARTCMD: Mutex<RefCell<Option<UART1>>> = Mutex::new(RefCell::new(None));
Import UART1
use rp2040_pac::UART1;
Move uart_cmd
into its global variable.
cortex_m::interrupt::free(|cs| {
UARTCMD.borrow(cs).replace(Some(uart_cmd));
});
Test
We can conduct a quick test for UART1
by sending a number of test bytes through. We should see the results on the Bluetooth App on our phone.
First, let's create the string that will act as the buffer holding all the data to be received.
// Buffers
let mut serialbuf = &mut String::<164>::new();// buffer to hold data to be serially transmitted
writeln!(serialbuf, "\nUart command test.").unwrap();
We need to add a crate that will enable us use String
s in our program since there is no core
library support.
heapless = "0.8.0"
Importing into the project:
use heapless::String;
To use writeln
we have to import fmt::Write
.
use core::fmt::Write;
We now have to create functions we'll use in receiving and transmitting data via UART
.
fn receive_uart_cmd(uart: &UART1) -> u8 {// receive 1 byte
while uart.uartfr().read().rxfe().bit_is_set() {} // wait until byte received
uart.uartdr().read().data().bits()// Received data
}
fn transmit_uart_cmd(uart: &UART1, buffer: &mut String<164>) {
for ch in buffer.chars() {
uart.uartdr().modify(|_, w| unsafe { w.data().bits(ch as u8)});// Send data
while uart.uartfr().read().busy().bit_is_set() {}// Wait until tx finished
}
buffer.clear()
}
Continuing with our serial test...
transmit_uart_cmd(&uart_cmd, serialbuf);
The above code snippet transmits data via usart_cmd
using serialbuf
as buffer.
Flash the program thus far into the Pico while the HC-05 is connected and check the App terminal. You should see the message Uart command test
displayed there.
Now that we have confirmed UART
is working, we can proceed with writing the interrupt service routine that'll receive the command bytes.
Interrupt
The algorithm below gives the general flow of the UART
interrupt service routine.
The flags AUTO
and AUTO_FIRST_ITER
need to be set as global static variables. They tell us whether we are in auto or manual modes and if an auto iteration in the super loop is the first. These will be utilized later in the project.
static AUTO: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
static AUTO_FIRST_ITER: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
Import Cell
use core::cell::Cell;
#[interrupt]
fn UART1_IRQ() {
// Enter critical section
cortex_m::interrupt::free(|cs| {
// peripheral handles
let mut uart = UARTCMD.borrow(cs).borrow_mut();
let mut dir = DIRECTION.borrow(cs).borrow_mut();
uart.as_mut().unwrap().uarticr().modify(|_, w| w.rtic().bit(true));// clear rx interrupt
// receive commands
let b = receive_uart_cmd(uart.as_mut().unwrap() );// Received byte
if b == b'A' { // toggle between auto and manual modes
if AUTO.borrow(cs).get() {
AUTO.borrow(cs).set(false);
} else {
AUTO.borrow(cs).set(true);
// if toggling to auto; set first iter true
AUTO_FIRST_ITER.borrow(cs).set(true);
}
} else {
// update direction cmd only in manual mode
if !AUTO.borrow(cs).get() {
if b == b'B' {
*dir = Some(Direction::Cw);
} else if b == b'C' {
*dir = Some(Direction::Ccw);
} else if b == b'D' {
*dir = Some(Direction::Up);
} else if b == b'E' {
*dir = Some(Direction::Down);
} else if b == b'F' {
*dir = Some(Direction::Zero);
}
}
}
});
}
Final Code
After rearranging all the code up to now we get this final copy.
Here are highlights of the changes made from the previous program.
Running this program will flash it into the Pico and we'll be set to receive commands remotely via Bluetooth.
In the next part of the series we shall finish up on command reception by implementing the application section of the program.
Posted on November 21, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.