TV-Out and TinyGPS+

Hello.

I'd like to add a small GPS data device on an existing rear view camera (the LCD screen has 2 NTSC inputs).
Using an Arduino Mega 2560, no data is extracted from the GPS module (Neo6M) when TV-out library is called. When a regular 2x16 LCD is used, lcd.print(gpsData) works like a charm.
Listening to the GPS port (Serial1.read) displays the NMEA string, 1 character per second; the TV library should definitly slow down the process.
How can I define the following:
Fixed data on screen
Get NMEA during 10 seconds and extract the parameters thanks to TinyGPS
Flush the average data on the TV screen.

The «blink without delay» method to acquire data failed in my case, it is basically just a simple prog problem I cannot resolve with so few knoledge.
Thank you

Well, nobody else replied, so I'll give it a shot.

This is an advanced project, because the TV signal generation takes up so much of the MCU's time. You really can't use Serial. Instead, you have to use the provided pollSerial, and use it as shown in the NTSCserialTerm.pde.

It uses the horizontal blanking interval (HBI) to watch the UART for received characters. If one is ready, it is put into the input buffer. Then you can read them in loop and, in your case, feed them to the GPS parser.

I would also suggest using NeoGPS, a smaller faster GPS library. You can also configure it to skip GPS fields and messages that you don't use. Every little thing helps when the MCU is so busy.

If you put your code in a code block, I can help you convert it pretty quickly. Either attach the INO file, or put it in line with the "</>" button in the upper left-hand corner of the post editor or insert code tags like this:

[code]// my program
// goes here, between these
//  tags
  .
  .
  .
[/code]

Then it will look like this:

// my program
// goes here, between these
//  tags
  .
  .
  .

Be sure to follow the NeoGPS Installation Instructions, because it goes in your sketch subdirectory, not the Libraries folder.

The NeoGPS version of your sketch will look something like this:

#include <TVout.h>
#include <pollserial.h>
#include <fontALL.h>
#include "NMEAGPS.h"

NMEAGPS gps;
TVout TV;
pollserial pserial;

void setup()  {
  TV.begin(_NTSC,184,72);
  TV.select_font(font6x8);
  TV.println("GPS Display");
  TV.println("-- Version 1.0 --");
  TV.set_hbi_hook(pserial.begin(9600));
}

void loop() {
  // Check for GPS characters on the pserial port and parse them.
  while (gps.available( pserial )) { // <-- must use new pollserial from reply #7 below!

    //  When enough characters have been parsed,
    //    a complete GPS fix structure is ready to use.
    gps_fix fix = gps.read();

    //  Display some of the fix members
    TV.print( "Lat/Lon: " );
    TV.print( fix.latitude() );
    TV.print( '/' );
    TV.print( fix.longitude() );
    TV.println();
  }
}

There are several ways to optimize this further, and I can suggest a minimal NeoGPS config when you say what pieces you want to display. (EDIT - attached to reply #7 below)

Cheers,
/dev

I had a similar issue...

Thanks for that. J-M-L had some good suggestions.

I couldn't even get the pollserial example to compile

Well, that needs to be fixed, for sure. There's no way the built-in Serial will work. I'll take a look...

BTW, you really need to get rid of String. There's just no room or time for that here, and it will cause long-term instability. You've already seen some improvement by reducing your use of String.

Cheers,
/dev

Ok, I have attached a NTSCserialTerm.ino and pollserial modifications. Naturally, I can't test the video output, but the serial read and write appears to work on my Mega.

I had to reduce the baud rate, because at 57600, more than two characters are being received before the hbi_hook (USART_receive) is getting called. At 9600, no characters are lost. Other bauds might work, too.

Have fun with code I can't test,
/dev

:wink:

pollserial.cpp (3.49 KB)

pollserial.h (1.53 KB)

NTSCserialTerm.ino (454 Bytes)

travis_farmer:
Hey, cool, it compiles, and works! :smiley:

No. Way.

Say, could you update that other thread, maybe point back to this one, too? Thanks.

@TLS3, here is the NTSCserialTerm example modified into a NeoGPS example. I have also modified pollSerial so it can be used on other serial ports, like Serial1 on a Mega, and a little clean-up to bring it in line with HardwareSerial, and a way to select different serial ports. Hmm... maybe I'll turn this into NeoPolledSerial...

To select the serial port you want to use for the GPS device, modify this line in the attached pollserial.CPP:

// set this to 0, 1, 2, 3 or nothing to select a USART
#define PS_PORT_NUMBER 1

@travis_farmer, if you take these files, you would set this line to 0 for your sketch.

These three files use Serial1 for the GPS and Serial for debug prints to the Serial Monitor window. It will output the current date/time and GPS checksum errors to the Serial Monitor, and it will print the current GPS second to the TV.

Cheers,
/dev

NTSCneogps.ino (797 Bytes)

pollserial.h (1.54 KB)

pollserial.cpp (4.56 KB)

Hello folks,
Thank you very much for this guidance, i'll try everything this week end.
I was affraid of this memory/speed concern and thought of dealing with 2 arduinos: one for the sensors/buffer and one just for TV signal generation.
Thank you again, I'll come with updates.

Hi again;
Very promising, image is stable, NMEA string is recovered, but for some reason, the TV keeps displaying ASCII characters while the serial monitor is ok:

That looks like the current GPS second. Does it cycle through 0 to 59? That's what it's supposed to do, I think.

Well, actually not, it starts as following, then shows the previous screen and loops indefinitely.

update
Displaying the satellite number (more or less constant) shows it looks more like a conversion error of the characters

Yes it does.
Asking for fix.altitude() gives decent data, with 2 decimal, updated once a second.
Asking for altitude_cm gives rubish

Project is the following: on the TV LCD a rear camera is running
Pressing a button switch to the other TV input, linked to the Arduino Mega.
The Arduino Mega displays GPS data (position, time, altitude, cape, speed, etc) every 10 seconds in block.

I'm speaking about what /dev posted, as a beginning for my work.
It looks like a character conversion error in the NeoGPS Library I never used before. Some data are rubbish (like a wrong baud rate), some other displays normally on the TV screen.

TLS3:
it starts as following, then shows the previous screen and loops indefinitely.

Please attach your code. Is suspect you might be calling write instead of print.

(EDIT - TVout is doing this! Grrr... see next reply.)

Cheers,
/dev

TLS3:
It looks like a character conversion error

Doh! The TVOut library defaults to doing a write for an unsigned char. That's what printing dateTime.seconds will use.

Ok, try this instead:

      TV.print( fix.dateTime.seconds, DEC );

That should force it to print the integer value of the current second, not the character with that value. :-/

Cheers,
/dev

travis_farmer:
In the example you posted (NTSCneogps.ino), I see...
    if (gps.available( pserial )) {But I haven't found how NeoGPS handles pserial.

gps.available is a method of the NMEAGPS class (that's the gps variable type). See line 112 of NMEAGPS.h

As you can see, the first argument to gps.available is the GPS serial port object, pserial. Actually, you can pass in anything that is derived from the Stream class. HardwareSerial, AltSoftSerial, Neo**Serial, and pollserial are all derived from Stream, so you can pass any of those in.

The code in NMEAGPS.h calls port.available to see if any characters are available to be read. If there are, it reads them and parses them with the decode method... which does all the rest of the NeoGPS magic.

The gps.available returns the number of fix structures that are available, not the number of characters on the port. After hundreds of calls to pollserial.read(), one fix structure has been filled out from all those NMEA sentences. Finally, one fix structure is available, and calling gps.read will fetch it for you. Then you can do whatever you need to with the pieces... like

    TV.print( fix.altitude_cm(), DEC );

The problem you ran into is that the TVout class doesn't quite behave like Serial. That's a mistake, IMHO, but you can make it do what you want with that second argument, DEC.

Cheers,
/dev

/dev I owe you a good beer. It works like a charm:

#include <TVout.h>
#include "NMEAGPS.h"
#include "pollserial/pollserial.cpp"
#include "TVoutfonts/font6x8.cpp"

NMEAGPS gps;
TVout TV;
pollserial pserial;

const char *banner = "NTSC NeoGPS\n-- Version 0.1 --";

void setup()  {
  Serial.begin( 9600 );
  Serial.println( banner );
  Serial.println( F("Date/Time,CS errors") );
  Serial.flush();

  TV.begin(_NTSC,184,72);
  TV.select_font(font6x8);
  TV.println( banner );
  TV.set_hbi_hook(pserial.begin(9600));
}

void loop() {
  if (gps.available( pserial )) {
    gps_fix fix = gps.read();

    // For debugging, print some things to the Serial Monitor window
    Serial << fix.dateTime;
    Serial.print( ',' );
    Serial.print( fix.satellites );
    Serial.println();
    TV.clear_screen();
    TV.print("HEURE GMT: ");
    TV.print( fix.dateTime.hours, DEC );    //DEC to turn into integer
    TV.print(" : ");
    TV.println( fix.dateTime.minutes, DEC );
    TV.print("Satellites: ");
    TV.print( fix.satellites, DEC );
    TV.println("\n\n\n");
//    TV.delay(500);
  }
}

Now how can I reduce the refresh rate of the GPS, the TV screen blinks at each new frame (1/sec) and i'm afraid it could disturb while driving, TV.delay freeze the incoming data.

~Travis: you are absolutely right about the 4800 bauds, but the new ones from ebay are now 9600 bauds.

TLS3:
Now how can I reduce the refresh rate of the GPS, the TV screen blinks at each new frame (1/sec) and i'm afraid it could disturb while driving

travis_farmer:
I have the same refresh blinking in my TVout module

This was a classic problem with early computer displays. You see it flicker because you call TV.clear_screen and don't finish redrawing before the screen gets refreshed. I can suggest a few things:

1) Either eliminate all Serial printing or do a Serial.flush() before you begin updating the TV screen:

   Serial.println();
   Serial.flush();
   TV.clear_screen();

This will make sure that Serial interrupts don't um... interrupt the TV.print statements. The TV.prints will execute as quickly as possible.

2) Wait for the vertical blanking interval before you start to update the screen. Add a short function that gets called at the VBI and sets a flag. Then test for that flag in loop before starting the redraw process:

volatile bool vbi = false;

void setVBIflag()
{
  vbi = true;
}

void setup()
{
    ...
  TV.set_hbi_hook(pserial.begin(9600));
  TV.set_vbi_hook( setVBIflag );
}

void loop()
{
  if (gps.available( pserial )) {
    gps_fix fix = gps.read();

    // For debugging, print some things to the Serial Monitor window
    Serial << fix.dateTime;
    Serial.print( ',' );
    Serial.print( fix.satellites );
    Serial.println();
    Serial.flush();

    // Wait for the Vertical Blanking Interval to come around...
    while (!vbi)
      ;
    vbi = false;

    // Now we can redraw the screen
    TV.clear_screen();
       ...

3) Reduce the number of pixels you have to redraw. Instead of clearing the whole screen and redrawing the whole screen, just update the portions that have changed:

    Serial.flush();

    // Wait for the Vertical Blanking Interval to come around...
    while (!vbi)
      ;
    vbi = false;

    // Update the time
    TV.print( 11*6, 0*8, fix.dateTime.hours  , DEC );
    TV.print( 16*6, 0*8, fix.dateTime.minutes, DEC );

    // Update the satellites
    TV.print( 12*6, 1*8, fix.satellites, DEC );
  }
}

This odds of this completing during the VBI are very good. You should not see any flicker.

As you may have realized, this means that you will have to draw everything else during setup:

void setup()
{
    ...
  TV.set_vbi_hook( setVBIflag );
  TV.print("HEURE GMT:   :\nSatellites:"); // the static labels
}

I'll say it again, I CAN'T TEST THIS. So the pixel placement of the labels (in setup) and the updating numbers (in loop) may not be correct. You used the 6x8 font, so I think that's how you calculate the x and y position in pixels (zero-based). Good luck! :slight_smile:

Cheers,
/dev

/dev
You can't test this but it works perfectly, not a single flickering by applying the 3 technics.
I had to comment Serial.flush() in order to get the fix.satellites, otherwise it stays at 0 (initialization).
Do you think I can still read a 4*1 keypad by «traditional means» (array reading) or the Mega can't handle that with the TV?
The keypad will input the waypoint (+,-,next,enter).

It is now 11h99 with 70 satellites on my screen, I might need to update a larger portion of it :smiley:

I had to comment Serial.flush() in order to get the fix.satellites, otherwise it stays at 0 (initialization).

Ah, it keeps the loop from getting back in time to read pollserial. It could be because the LAST_SENTENCE is incorrectly set to RMC (see below).

One other thing that would help... Have you sent any configuration commands to the GPS device? If you are only using date, time and location, you only need to be receiving the RMC and GGA sentences (see Choosing Your Configuration). Since you're using the NEO-6M module, you would send a "$PUBX,40" command with the message name to enable or disable.

This will save all the processing time for reading and parsing the characters, reducing the characters per second from ~500 to ~160, a significant savings. That also increases the GPS quiet time from ~500ms to ~840ms each second. You will be able to do more work after fix = gps.read() before causing GPS characters to be lost.

Here's a modified setup that starts the pollserial first and then sends the configuration commands. Then it starts TVout after that's completed:

#define NEO6M_DISABLE(m)  F("PUBX,40," #m ",0,0,0,0,0,0")
#define NEO6M_ENABLE(m)   F("PUBX,40," #m ",0,1,0,0,0,0")

void setup()  {
  Serial.begin( 9600 );
  Serial.println( banner );
  Serial.println( F("Date/Time,CS errors") );
  Serial.flush();

  pt2Funct fn = pserial.begin(9600); // save the return value for later

  // Turn off the other NMEA sentences, they're wasting our time
  gps.send_P( &pserial, NEO6M_DISABLE(GLL) );
  gps.send_P( &pserial, NEO6M_DISABLE(GSA) );
  gps.send_P( &pserial, NEO6M_DISABLE(GSV) );
  gps.send_P( &pserial, NEO6M_DISABLE(VTG) );

  // Turn on these two
  gps.send_P( &pserial, NEO6M_ENABLE(RMC) );
  gps.send_P( &pserial, NEO6M_ENABLE(GGA) );

  TV.begin(_NTSC,184,72);
  TV.select_font(font6x8);
  TV.println( banner );
  delay( 1000 ); // let the banner show for a bit

  TV.set_hbi_hook( fn ); // from the pserial.begin above
  TV.set_vbi_hook( setVBIflag );

  TV.clear_screen();
  TV.print("HEURE GMT:    :\nSatellites:");
}

You will need to change line 29 in NMEAGPS_cfg.h from RMC to GGA:

    #define LAST_SENTENCE_IN_INTERVAL NMEAGPS::NMEA_GGA

The NEO-6M sends the GGA last. After this change, you might try that Serial.flush again. It's not a big deal if it still doesn't work. Also comment line 278 out:

    //#define NMEAGPS_RECOGNIZE_ALL

This saves some more program space and processing time. This would be a good time to review GPSfix_cfg.h. Right now, only DATE, TIME and SATELLITES should be uncommented:

#define GPS_FIX_DATE
#define GPS_FIX_TIME
//#define GPS_FIX_LOCATION
//#define GPS_FIX_LOCATION_DMS
//#define GPS_FIX_ALTITUDE
//#define GPS_FIX_SPEED
//#define GPS_FIX_HEADING
#define GPS_FIX_SATELLITES
//#define GPS_FIX_HDOP
//#define GPS_FIX_VDOP
//#define GPS_FIX_PDOP
//#define GPS_FIX_LAT_ERR
//#define GPS_FIX_LON_ERR
//#define GPS_FIX_ALT_ERR
//#define GPS_FIX_GEOID_HEIGHT

This will save a lot of processing time and 1700 bytes of program space. It will save a little RAM, too.

It is now 11h99 with 70 satellites on my screen, I might need to update a larger portion of it :smiley:

LOL, a leading space or zero will fix the time, and a leading or trailing space would fix the satellites count:

    // Update the time
    TV.set_cursor( 12*6, 0*8 );
    if (fix.dateTime.hours < 10)
      TV.write( ' ' );
    TV.print( fix.dateTime.hours  , DEC );

    TV.set_cursor( 15*6, 0*8 );
    if (fix.dateTime.minutes < 10)
      TV.write( '0' );
    TV.print( fix.dateTime.minutes, DEC );

    // Update the satellites
    TV.print( 12*6, 1*8, fix.satellites, DEC );
    TV.write( ' ' );

Don't forget to scoot the colon over with an extra space for the hours:

      TV.print("HEURE GMT:    :\nSatellites:"); // the static labels
                             ^ extra space

I think I used that above.

Do you think I can still read a 4*1 keypad by «traditional means» (array reading) or the Mega can't handle that with the TV? The keypad will input the waypoint (+,-,next,enter).

Sure, just poll the pins in loop and do some software debounce. I wouldn't do it with Pin Change Interrupts. If it has trouble catching the press, you can "stretch" the presses with an RC debounce on the keys and skip the software debounce.

Cheers,
/dev

This is a very, very interesting approach, I will need more than just date and satellite, but I will definitely flush all the HDOP and errors.
Following your link, it appears that PUBX00 would be the best choice (i need altitude as well), but I cannot see this option in NMEAGPS_cfg.h
only

#define NMEAGPS_PARSE_GGA
//#define NMEAGPS_PARSE_GLL
//#define NMEAGPS_PARSE_GSA
//#define NMEAGPS_PARSE_GSV
//#define NMEAGPS_PARSE_GST
#define NMEAGPS_PARSE_RMC
//#define NMEAGPS_PARSE_VTG
//#define NMEAGPS_PARSE_ZDA

Is it why you recommend to recover CGA and RMC?