Self-Aligning Dish: GPS

ian_ndeda

Ian Ndeda

Posted on November 24, 2024

Self-Aligning Dish: GPS

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

gps-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
Enter fullscreen mode Exit fullscreen mode

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());
Enter fullscreen mode Exit fullscreen mode

Create a UART global variable and move uart into it.

static UARTDATA: Mutex<RefCell<Option<UART0>>> = Mutex::new(RefCell::new(None));
Enter fullscreen mode Exit fullscreen mode

Import UART0

use rp2040_pac::UART0;
Enter fullscreen mode Exit fullscreen mode

Move uart_data into its global variable.

cortex_m::interrupt::free(|cs| {
    UARTDATA.borrow(cs).replace(Some(uart_data));
});
Enter fullscreen mode Exit fullscreen mode

Connections

The electrical connections will be as shown below.

gps-intro-connection

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();
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

Continuing with our serial test...

    transmit_uart_data(&uart_data, serialbuf);
Enter fullscreen mode Exit fullscreen mode

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: 

gps-intro-test-results

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);
    }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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));
});
Enter fullscreen mode Exit fullscreen mode

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.

gps-intro-int-chart

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));
Enter fullscreen mode Exit fullscreen mode

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);
            }
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
ian_ndeda
Ian Ndeda

Posted on November 24, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related