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. 2: Dynamic GPS configuration (and some other things)
  • Blog
  • Forum
  • Documents
  • Quiz
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Embedded and Microcontrollers to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 13 Jul 2024 3:50 PM Date Created
  • Views 822 views
  • Likes 6 likes
  • Comments 10 comments
  • Teseo
  • gps
  • OO
  • teseo_c++
Related
Recommended

C++ library for ST Teseo GPS - pt. 2: Dynamic GPS configuration (and some other things)

Jan Cumps
Jan Cumps
13 Jul 2024

The Teseo-LIV3 GPS module (as used in shabaz ' GPS / Galileo / BeiDou / GLONASS receiver) talks UART and I2C. I'm writing an OO driver for it, for embedded systems. In this blog, I configure the Teseo IC at runtime. And write some code guards (asserts) to validate that a (your?) project uses the library in the right way.

Initialisation

In the previous post, you had to pre-configure the Teseo-LIV3. This is advised in ST's application note for I2C communication. And if you're building a commercial device, that's the best option. A side effect is that it changes the out-of-box behaviour of the GPS. Not permanently, because it can be reverted. 

I could do this configuration programmatically, at first run. But when you are designing, or when you want top try out my library, it may be unfunny when your GPS behaves differently, and you didn't expect that. A solution is to do the configuration for the current session only, at each startup. That's what I'm developing now.

Pre-configuring the IC

The ST application note proposes this one-time configuration (text copied from the previous post):

$PSTMCFGMSGL,3,1,0,0
$PSTMSETPAR,1227,1,2
$PSTMSAVEPAR

(from the appnote:) This set of commands will:

  • Reset the i2c message list
  • Disable the eco-ing message
  • Save the configuration on flash

This will put the GPS in the right state for us at power on. If you don't mind reconfiguring the GPS (it's reversable), use this approach.

Run-time configuration

If you prefer to keep the device in its original configuration, you can use my init() functionality. Here's what it does:

  • reset the IC
  • stop the GPS engine
  • do the same configuration as in the pre-configure scenario, but don't save the settings to persistent storage
  • restart the GPS engine

Advantage:

  • once you reset or power cycle the GPS, it's back to its original state

Disadvantage:

  • it adds 4 seconds and a some ms to the startup time.
  • an additional GPIO pin is needed to control the GPS reset.

To make dynamic configuration work reliable and repeatable, the IC needs to be reset. The i2C interface is available 4 seconds later. I made this configurable. I get reliable communication when waiting4 seconds before doing business. If the IC is preconfigured, there's no wait needed and we can get to business right away.

Init functionality

Again: no need to execute this if you pre-config the device. In that case, the project should not call init().

 void teseo::init() {

    std::string s;

    resetter.call();

    // stop the engine
    write("$PSTMGPSSUSPEND\n\r");

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

    // disable the eco-ing message
    write("$PSTMSETPAR,1227,1,2\n\r");

    write("$PSTMGPSRESTART\n\r");
    do {
        read(s);            
    }
    while((s.find("$PSTMGPSRESTART") == std::string::npos)); // command successful 

}

The code follows the sequence described above. You can see that it requires a new callback: the resetter(). It's a simple function you'll have to provide that bumps the reset pin and waits as appropriately. I want to keep the library independent of the microcontroller that you use. So you 'll have to provide the reset function.

Here's my Pico implementation:

#define RESET_PIN (18)
#define RESET_APPLY_MS (10)
// recover must be more than 3 seconds
#define RESET_RECOVER_MS (4000)

// ...

void init () {
    // ...
    gpio_init(RESET_PIN);
    gpio_put(RESET_PIN, 1);
    gpio_set_dir(RESET_PIN, GPIO_OUT);    
}

void reset() {
    gpio_put(RESET_PIN, 0);
    sleep_ms(RESET_APPLY_MS);
    gpio_put(RESET_PIN, 1);
    sleep_ms(RESET_RECOVER_MS);
    return;
}

int main() {
    init();
    
    // ...

    gps.getResetCallback().set([]() -> void {
        reset();
    });
    
    // ...

Hardware changes

  • Connect Teseo RST Pico GP18.

Assist the developer with asserts

I'm trying to help users, by checking pre-conditions. Without increasing the final product's code size.

image

The library expects that you have your chickens in a row before you use the library. If I have to check that each time at runtime, it's going to increase the footprint of your production firmware.
That's why I use a debug-only function: asserts. Asserts are functions that will validate if something is true. At debug only. Once you build a release version of the firmware, these checks vaporise. They are ideal to catch if your code performs an action, when the object isn't properly set up. Or if you use parameters that are invalid.

I use them here to check, that you don't use functionality that needs a callback, before you registered the callback.

write:

#include <cassert>

void teseo::write(const std::string& s) {
    assert(writer.armed());
    writer.call(s);
}

read:

void teseo::read(std::string& s) {
    assert(reader.armed());
    reader.call(s);
}

init:

 void teseo::init() {
    assert(writer.armed());
    assert(reader.armed());
    assert(resetter.armed());
    
    // ...

I had to adapt my callback code to support this. I could have just put the assert inside that class. But not having a callback may be a valid scenario.
That's why I added a checker method, and made it available for users. I think it may be useful in a program.
The price is low. It's an inline function. It 'll just resolve to the cheap C construct  (poiner != null). 

Why assertions?

There is a hierarchy in the hurt caused by a bug:

  1. found at compilation: very low price. The developer can fix it
  2. found at test: mid cost. Developer or tester can find it. Fixed before the gizmo hits the market
  3. found in production: shame

Asserts fit in category 2. They add code to your project in the debug configuration. When you test the firmware, it can do additional (sometimes expensive) validations. And show where something invalid happens. As long as you test your code (you test it, don't you?) it 'll find logical errors that can't be found during compilation.

The release build of your project will not include these checks. So they don't add to the firmware resource reqs. It doesn't make sense to add them to the release version, because your code doesn't know to handle them anyways. That's why you want to catch them at test.

comments welcome Slight smile

Anything else?

Yes. This second post also brings joy to early adopters. To make this early project easier to grasp, I print the GPS reply to UART every time it's read. I use the Pico SDK stdio functions. So you can route it to a picoprobe or to the Pico's  USB.

image

Enjoy. Project is attached. With uf2 firmware for drag&drop.

pico_gps_teseo_i2c_20240713_03.zip

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

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

Top Comments

  • michaelkellett
    michaelkellett 11 months ago +2
    Here's a thought, mainly applicable to high reliability hard real time systems, I like the ideas, which were very prevalent in an aerospace project on which I worked: 1) if it executes ever it executes…
  • Jan Cumps
    Jan Cumps 11 months ago in reply to michaelkellett +1
    > 2) what you test is what you ship yes. I think that asserts and printfs have a place before that moment. When the developer develops. Formal testing should be done on the production/release binaries…
  • Jan Cumps
    Jan Cumps 11 months ago in reply to Jan Cumps

    because the project now supports UART and I2C, I renamed the repo from pico_gps_teseo_i2c to pico_gps_teseo.
    If only I would think ahead Slight smile.

    New doco link: https://jancumps.github.io/pico_gps_teseo/

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

    now moved to  GitHub: automate project documentation with DoxyGen, and publish online after pushing commits

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

    I once worked on a system that was real-time but running on a large box that was essentially internally a server with storage, etc, rather than a microcontroller, and customers could switch on a logging mode (in the same production release, i.e. not a separate build) but were advised never to do that in their production systems (many of those customers kept a separate testbed to try things out). I can't recall now, but I think for a long time we only released builds built with debug, i.e. we took the performance hit, but eventually changed the build flags once we had more confidence.

    Some ran the systems (as advised with logging switched off) like racing cars and to save costs wouldn't have purchased many of the systems to run in parallel, and if there was ever a crash, if they couldn't replicate on their testbed, then it was of course a nightmare to troubleshoot. And, of course, since it was such a severe thing, almost all of our other work would stop apart from focussing on the crash problem, trying to replicate it ourselves, and getting it solved.

    One trick we ended up doing for a particular build for one customer was to give them a 'special' release that logged a minimal amount (events and the first dozen or so bytes of any messages) but instead of to disk which was slow, we decided to store into a circular RAM buffer of the last 1000 or 10000 events or whatever. That way, whenever there was a crash, there was a good chance that the RAM was not significantly corrupted, and we could read the contents and figure out what might have happened. That was not too difficult because the underlying OS would create the crash dump containing all the RAM contents, so we'd simply get the customer to ship across the (usually huge, many Gbytes) file (if it didn't contain anything they considered confidential), otherwise we would send them software to pull out what we needed. The RAM was easy to search since the buffer started with a magic sequence.

    Over time we managed to weed out all the race conditions and improve reliability loads, but it was a stressful time since it's the worst customer experience when things crash. We improved guidelines (we created an online or Excel file calculator for the customer and salespeople to easily figure out how many of the systems each customer would require so as to not be running with massive amounts of events per sec), and improved performance testing (developing ways to hammer the system to simulate peak times, artificially loading up the CPU, etc), and then as it got more mature, it was a nice result that when the customer trends changed, it was possible to quickly port the code, and even virtualize it, without too many hiccups.

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

    > 2) what you test is what you ship

    yes. I think that asserts and printfs have a place before that moment. When the developer develops. Formal testing should be done on the production/release binaries. 

    The side effects you describe are a real thing Slight smile. I've seen that even changing the log file level in a config file from debug to error can (un)mask race conditions. Or make multi-CPU designs work different.
    Another $&# thing was that in Microsoft's C++ MFC libraries, the Date class was initialised in the Debug profile (in an #ifdefined DEBUG section) , and  not when you build for release. A great source for late bug detection Slight smile.

    That said, I am a fan of having validations available for the developer only, to assist them. To be switch-offable when the tests are run .That's what asserts offer.

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • michaelkellett
    michaelkellett 11 months ago

    Here's a thought, mainly applicable to high reliability hard real time systems,

    I like the ideas, which were very prevalent in an aerospace project on which I worked:

    1) if it executes ever it executes always

    2) what you test is what you ship

    To amplify a little:

    rule 1 implies that if a stream of debug information is sent to serial port X the it should ideally be emitted at a constant rate. This is to avoid bugs of the kind where the extra processing load of debug printfs slows things down enough to avoid a fatal race condition elsewhere.

    rule 2 implies that release code and test code are the same code

    Compliance with these rules can be very hard to achieve - on some code I'm working on now a particular function runs in 20us with maximum optimisation, but debugging only works correctly at a much lesser optimisation setting, when the function takes 47us to run.  So I must test the code with more less no debugging operating. My answer to that is to build instrumentation into the code (which is always there and will always execute according to rule 1).

    I don't use asserts but if I did I think I would need to add an assert handler so I could leave them in the code.

    One of the questions I have for the next code review is "what shall the code do when the scheduler detects that a task returned without error but took too long to do so". (Too long in this case is defined as greater than the maximum time set in the task definition  - doesn't necessarily mean that anything bad happens if enough of the other tasks did better !).

    MK

    • Cancel
    • Vote Up +2 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