Self-Aligning Dish: GPS
Ian Ndeda
Posted on November 24, 2024
To receive GPS data, we'll use the Neo-6m GPS module. Like the HC-05 Bluetooth module from the previous part, it uses UART
for communication. We'll use UART0
for this part of the project.
Table of Contents
Requirements
- 1 x Raspberry Pico board
- 1 x USB Cable type 2.0
- 9 x M-M jumper wires
- 1 x HC-05 Bluetooth Module
- 2 x Mini Breadboards
- Serial Bluetooth App
Flowchart
The above flowchart illustrates the general flow of control for the GPS section of our project.
Implementation
UART Configuration
The setup for UART0
will be identical to that of UART1
. We will however enable the FIFO for UART0
in this case. This is because we'll not be constrained to receiving only one byte at a time. The interrupt configuration will be consequently different.
// Configuring UART0; GPS data reception
let uart_data = dp.UART0;
resets.reset().modify(|_, w| w.uart0().clear_bit());// Deassert uart_data
// Set baudrate at 96 00
uart_data.uartibrd().write(|w| unsafe { w.bits(813)});
uart_data.uartfbrd().write(|w| unsafe { w.bits(51)});
uart_data.uartlcr_h().modify(|_, w| unsafe { w
.fen().set_bit()// Enable FIFO
.wlen().bits(0b11)// Set word length as 8
});
uart_data.uartcr().modify(|_, w| w
.uarten().set_bit()// Enable uart_data
.txe().set_bit()// Enable tx
.rxe().set_bit()// Enable rx
);
uart_data.uartimsc().modify(|_, w| w
.rxim().set_bit());// set interrupt for when there data in rx fifo
gp0
and gp1
can be configured as UART0
pins as can be seen from the table in the 'Command' part of the series.
// Configure gp0 as UART0 Tx Pin
// Connected to HC-05 Rx Pin
pads_bank0.gpio(0).modify(|_, w| w
.pue().set_bit()
.pde().set_bit()
.od().clear_bit()
.ie().set_bit()
);
io_bank0.gpio(0).gpio_ctrl().modify(|_, w| w.funcsel().uart());
// Configure gp1 as UART0 Rx Pin
// Will be connected to GPS Module Tx Pin
pads_bank0.gpio(1).modify(|_, w| w
.pue().set_bit()
.pde().set_bit()
.od().clear_bit()
.ie().set_bit()
);
io_bank0.gpio(1).gpio_ctrl().modify(|_, w| w.funcsel().uart());
Create a UART
global variable and move uart
into it.
static UARTDATA: Mutex<RefCell<Option<UART0>>> = Mutex::new(RefCell::new(None));
Import UART0
use rp2040_pac::UART0;
Move uart_data
into its global variable.
cortex_m::interrupt::free(|cs| {
UARTDATA.borrow(cs).replace(Some(uart_data));
});
Connections
The electrical connections will be as shown below.
Test
We can conduct a quick test for UART0
by sending a number of test bytes. We should see the results on the Serial Bluetooth App's console.
writeln!(serialbuf, "\nUart data test.\n").unwrap();
Copy the receive_uart_cmd
and transmit_uart_cmd
and edit them for UART0
.
Please note the differences: FIFO is enabled for
UART0
and will therefore be transmitting data until the Transmit FIFO is not-empty.
fn receive_uart_data(uart: &UART0) -> 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_data(uart: &UART0, 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().txfe().bit_is_clear() {}// Wait until tx finished
}
buffer.clear()
}
Continuing with our serial test...
transmit_uart_data(&uart_data, serialbuf);
Flash the program into the Pico with the Bluetooth module connected to UART0
's Tx Pin gp0
and check the terminal. Uart data test
should show:
Interrupt
We first have to unmask the UART0
interrupt to enable it fully.
It is a good idea to have all the interrupts unmasked after we are through with all the peripherals set up. We can therefore move all the unmasking to the end of the set up.
// Unmask interrupts
unsafe {
cortex_m::peripheral::NVIC::unmask(Interrupt::TIMER_IRQ_0);
cortex_m::peripheral::NVIC::unmask(interrupt::UART0_IRQ);
cortex_m::peripheral::NVIC::unmask(interrupt::UART1_IRQ);
}
We need a buffer that will hold the data from the GPS module. It is a variable shared between main
and interrupts
. We'll put it as a global variable.
static BUFFER: Mutex<RefCell<Option<String<164>>>> = Mutex::new(RefCell::new(None));// buffer to hold received gps data
In the main
thread under buffers
we'll initialize our buffer and move it into global scope.
let gpsbuf = String::new();// buffer to hold gps data
cortex_m::interrupt::free(|cs| {
BUFFER.borrow(cs).replace(Some(gpsbuf));
});
A naive approach we can use to acquire a GPS NMEA sentence is to accumulate bytes until we encounter an end-of-line byte. We can then process the received sentences into latitudes and longitudes. The flowchart below helps visualize this process.
We will need a number of flags to help along. Let's create global variables for these flags.
static STARTED: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
static GGA_CONFIRMED: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
static GGA_ACQUIRED: Mutex<Cell<bool>> = Mutex::new(Cell::new(false));
The interrupt handler will be as below.
#[interrupt]
fn UART0_IRQ() {
// Enter critical section
cortex_m::interrupt::free(|cs| {
// Peripherals
let mut uart = UARTDATA.borrow(cs).borrow_mut();
// Flags
let started = STARTED.borrow(cs);
let gga_confirmed = GGA_CONFIRMED.borrow(cs);
let gga_acquired = GGA_ACQUIRED.borrow(cs);
let mut buffer = BUFFER.borrow(cs).borrow_mut();
uart.as_mut().unwrap().uarticr().modify(|_, w| w.rxic().bit(true));// clear rx interrupt
// Data acquisition
if started.get() {// If nmea sentence acquisition started
if gga_confirmed.get() {
let b = receive_uart_data(uart.as_mut().unwrap());// Received data
buffer.as_mut().unwrap().push(char::from(b)).unwrap();// push to buffer
if b == b'\n' {// End of nmea sentence
gga_acquired.set(true);// set flag
gga_confirmed.set(false);// unset confirmed flag
started.set(false);// unset flag; start again
}
} else {
let b = receive_uart_data(uart.as_mut().unwrap());// Received data
buffer.as_mut().unwrap().push(char::from(b)).unwrap();// push to buffer
if buffer.as_mut().unwrap().len() == 6 {// at len of 6 check if gga
if buffer.as_mut().unwrap() == "$GPGGA" {// check if gga
gga_confirmed.set(true);// set flag
} else {
started.set(false);// unset flag; start again
buffer.as_mut().unwrap().clear();// clear buffer
}
}
}
} else {
gga_acquired.set(false);// service flag incase not read; to avoid reading empty buffer
let b = receive_uart_data(uart.as_mut().unwrap());// Received data
if b == b'$'{//start of nmea sentence
buffer.as_mut().unwrap().push(char::from(b)).unwrap();// push to buffer
started.set(true);
}
}
});
}
When the full intended NMEA sentence has been acquired the gga_acquired
flag is set.
Final Code
Rearranging the code we get this final copy.
Running this program will flash it into the Pico and we'll be set to receive GPS data.
Next we shall test our program by printing the received GPS data.
Posted on November 24, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.