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
UART0and 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.
              



    
Top comments (0)