element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Embedded and Microcontrollers
  • Technologies
  • More
Embedded and Microcontrollers
Blog C++ library for ST Teseo GPS - pt. 3: Pico and UART support
  • Blog
  • Forum
  • Documents
  • Quiz
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Embedded and Microcontrollers requires membership for participation - click to join
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 19 Jul 2024 7:35 PM Date Created
  • Views 290 views
  • Likes 5 likes
  • Comments 3 comments
  • i2c
  • uart
  • Teseo
  • gps
  • OO
  • teseo_c++
Related
Recommended

C++ library for ST Teseo GPS - pt. 3: Pico and UART support

Jan Cumps
Jan Cumps
19 Jul 2024

This article is related to  Fun With Arduino, Global Navigation Satellite Systems (GNSS) and Teseo III - again.

In the previous two posts, I created a device-and-comms-protocol independent C++ GPS library. The test code was for a Raspberry Pico and I2C though. It didn't test that independence.
The exercise proves that it's protocol independent. The same teseo class works with I2C and UART. As long as you tell it how to talk using that protocol. 

image

Pico resources:

  • UART1
  • TX: GP4
  • RX:  GP5
  • baud: 9600
  • UART1 RX interrupt
  • GPS RESET: GP18

Connections:

  • 5V to VBUS
  • 0V to GND
  • RX to GP4
  • TX to GP5
  • RST to GP18

Software

As a firmware developer, you provide a reader and writer, just like in the previous i2c example. They should be able to send and get data from a UART peripheral. In this post, I used a Pico, its UART1, and the SDK UART API.

// ...

#include "hardware/uart.h"
#include <cassert>
#define UART_PORT (uart1)
#define UART_BAUD (9600)
#define UART_TX (4)
#define UART_RX (5)
#define BUFFSIZE (180)
#define UART_WAITFORREPLY_MS (200)

// ...

// forward declaration
void on_uart_rx();
int UART_IRQ = UART1_IRQ;
uint8_t *pBuf; // explicitely uninitialised
volatile bool bWantChars; // explicitely uninitialised

void init () {
    stdio_init_all();
    
    // ...
    uart_init(UART_PORT, UART_BAUD);
    uart_set_fifo_enabled(UART_PORT, false);
    gpio_set_function(UART_TX, GPIO_FUNC_UART);
    gpio_set_function(UART_RX, GPIO_FUNC_UART);
    // set up and enable the interrupt handlers
    irq_set_exclusive_handler(UART_IRQ, on_uart_rx);
    irq_set_enabled(UART_IRQ, true);
    
    // ...
}

void on_uart_rx() {
    uint8_t letter;
    static uint8_t previousletter;

    if(pBuf == buf) { //* initialise previousletter at each buffer start
        previousletter = 0;
    }
    while (uart_is_readable(UART_PORT)) {
        letter = uart_getc(UART_PORT);
        if (bWantChars) {
            pBuf[0] = letter;
            if (pBuf[0] == '\n') {
                if (previousletter == '\n') { // two newlines is end of conversation
                    bWantChars = false;
                }
            }
            if (pBuf[0] == 0) {
                bWantChars = false; // a null read
            }
            previousletter = letter;
            if ((pBuf - buf) < BUFFSIZE-1) { // if we reach max buffer size, just keep emptying any additional characters in the last position;
                pBuf++;
            }
            assert ((pBuf - buf) < BUFFSIZE);
        }
    }
}

void write(const std::string& s) {
    uart_write_blocking(UART_PORT, reinterpret_cast<const uint8_t*>(s.c_str()), s.length() +1);
    return;  
}

void read(std::string& s) {
    memset (buf, 0, BUFFSIZE);  // initialise buffer before reading
    pBuf = buf;
    bWantChars = true;
    // enable the UART to send interrupts - RX only
    uart_set_irq_enables(UART_PORT, true, false);
    absolute_time_t  fail_at = delayed_by_ms(get_absolute_time(), UART_WAITFORREPLY_MS);
    while (bWantChars){
        if (absolute_time_diff_us(fail_at, get_absolute_time()) >= 0) {
            bWantChars = false; // timeout
        }
    };
    // disable the UART to send interrupts
    uart_set_irq_enables(UART_PORT, false, false);
    s = std::string(reinterpret_cast<const char*>(buf));

    return;
}

Did I have to change the original teseo class? Yes: one line.
In the original code, I only cleared the I2C buffer after reset. Because I was happily listening to the full UART data in parallel, to check GPS health.
That was just for develop purposes. Now that the code is OK for UART integration, I have to tame that UART output stream, just like I did with the I2C stream in previous posts.
One line is added in the init code of the teseo class:

    // reset the UART message list
    write("$PSTMCFGMSGL,0,1,0,0\n\r");
    // reset the I2C message list
    write("$PSTMCFGMSGL,3,1,0,0\n\r");

It does not change the behaviour of the I2C integration. 

The current state of the interface, is that the teseo class can manage both communication protocols. And it behaves the same for both.
When you ask for data, it returns the same data. With the same signature. And with the same validations.

You 'll see that the read() functions aren't that simple. That's the case for both I2C and UART. This device isn't easy to control, and the read functions reflect that.

When porting to another controller, you 'll have similar challenges as I have with the Pico. Data doesn't "just arrive right". But I hope that my Pico example code helps to port to oother controllers and environments.

image

Anything else?

yes:

  • you can choose between UART and I2C in the makefile

# select the communication protocol to talk to the Teseo GPS
#add_compile_definitions(GPS_OVER_UART)
add_compile_definitions(GPS_OVER_I2C)

This doesn't influence the teseo class. It tells the example code to use I2C or UART.

  • I introduce a new C++STL  concept: a pair.

A pair can hold two things. The name actually reveals the intent. In the latest code, I use it to hold NMEA commands, and the validation string to check if the reply is valid.

typedef const std::pair<const std::string, const std::string> nmea_rr;

// ...

private:

    //! command to retrieve GPGLL data
    static nmea_rr gpgll;
    //! command to retrieve GPRMC data
    static nmea_rr gprmc;

// ...

nmea_rr teseo::gpgll("$PSTMNMEAREQUEST,100000,0\n\r", "$GPGLL,");
nmea_rr teseo::gprmc("$PSTMNMEAREQUEST,40,0\n\r", "$GPRMC,");

bool teseo::ask_nmea(const nmea_rr& command, std::string& s, uint retries) {
    bool retval; // intentionally not initialised
    uint tries; // intentionally not initialised
    for (tries = 0; tries <= retries; tries++){
        write(command.first);
        read(s);
        retval = s.starts_with(command.second);
        if (retval) {
            break;
        }
    }
    return retval;
}

// ...

It makes the class interface smaller (1 member for each command, in stead of 2). Without introducing a new class or structure. I use an existing stl feature.
It also allows me to create a generic reply-response handler, that always applies the right return value validation for the right command.
Discuss below.

Next post:  C++ library for ST Teseo GPS - pt. 4: PCB to Pico hardware connections 

visit the github repository (git clone https://github.com/jancumps/pico_gps_teseo.git --recursive)
view the online documentation
Link to all posts.

  • Sign in to reply
  • DAB
    DAB 10 months ago

    Nice update Jan.

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • Jan Cumps
    Jan Cumps 10 months ago

    If I'd code review my own code, I'd challenge having pBuf (a pointer into the read buffer) public global.

    I'd challenge myself to have it interrupt local (as a static).
    And make it reset itself to the start of the buffer before starting a new read.

    One option would be to replace the Boolean guard:

    volatile bool bWantChars;

    by an enumerator with 3 states: start - doing - done ...

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • shabaz
    shabaz 10 months ago

    Nice work. This will make it far easier to use the module. I think this is the first time I'm seeing STL pair being used, very interesting. Neat how the command response verification string becomes simply part of a pair with the command request!

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube