Table of contents
The purpose of this page is to help you dive into the RFzero programs and change or add functionality. The purpose is NOT to teach you how to write programs in Arduino. There are many places where you can learn that.
If all what you want to do is to use the RFzero programs, or third party programs, the way they are, then you don’t need to understand anything about the underlying program structure and flow.
If you have wishes to this page please add them to this thread in the user group.
Presentation from the intermediate webinar 2020-06-20.
General Arduino structure
Generally speaking an Arduino sketch, i.e. program, consists of only one file called “filename”.ino. However, often other files are used to break down the program into small, and more manageable files with the .cpp and .h extensions.
The .ino file ALWAYS has at least two functions
- void setup()
this is the first function that runs and it only runs ones - void loop()
when setup() has finished the next in line is the the loop() function and this runs over and over again hence the name loop.
The main Arduino file .ino.
In the header file other header files may be included. In the header file variables and functions are declared.
Header file .h that matches the .cpp file.
In the .cpp files the variables and functions are written.
The .cpp file that contains the extra functions.
To minimize confusion it is a good idea to use matching filenames for the .h and .cpp files. There can be as many .h and .cpp files as you like.
The above structure is also the case for the RFzero programs, and in most cases at least a set of command.cpp/~.h, config.cpp/~h, display.cpp/~.h and global.cpp/~h files are also used.
Hardware and software interactions
The RFzero is both a hardware and software platform, that goes hand in hand through the RFzero and Arduino libraries. The RFzero programs, that comes with the RFzero library, are there for your use, modification or inspiration. You can write your own programs using the library or write everything yourself from scratch, the RFzero hardware doesn’t care.
The RFzero programs can be grouped into different categories
- The beacon and transmitter programs that are configured and then put into operation and “forgotten”
- The continuous user interaction programs like the signal generator and VFO
- Service programs with no or limited interaction like the GPSDO, QO-100 and Multi LO programs
- Test programs that are outside any category
General H/W and S/W interactions of most RFzero programs.
To use the any of the RFzero libraries it is important include them in the declaration, just like any other library you want to use
- #include <RFzero.h>
- #include <RFzero_modes.h>
- #include <RFzero_util.h>
If you forget to include a library, you will get an error message, when you try to compile the program.
Program structure
Generally speaking most of the RFzero programs follow the same structure. First the various libraries are included, and the variables are created. Then the RFzero is initialized, and the configuration parameters are loaded from the EEPROM, and some program specific one time settings are evoked in the setup() function.
Next the loop() function runs continuously servicing the program specific functionality, e.g. maintaining the Si5351A frequency, modulating the signal, scanning rotary encoder(s), checking push buttons … Also the yield() function is called regularly to look for USB and GPS data.
Very simplified flow chart.
Another way to depict the program structure is shown below, where the function calls and variable flows between the program files are illustrated.
Program structure showing how functions are called and values are transferred.
MMI configuration flow
When an MMI command is received from the USB port a series of events occur
- The MMI command is received by the yield() function in the .ino file
- The MMI command is sent to the command parser function in the command.cpp file
If the command is a “config” command a configuration flag is set, and the program is configuration mode - If the command is valid, and includes data to be saved, this is written to the EEPROM
A configuration has changed flag is also set - When the “exit” command is received, and if the configuration flag is set, the configuration is loaded from the EEPROM
- The loaded EEPROM values are written to the global variables
- The display is updated if relevant
- Finally the new global variables affect the program flow
MMI configuration flow where the numbers indicate the sequence of events.
Inside the files
Most of the RFzero programs have nine files
- The program .ino file
- A commands.cpp and a commands.h file
- A config.cpp and a config.h file
- A display.cpp and a display.h file
- A global.cpp and a global.h file
When other files are found they always come in pair named “FileName”.cpp and “FileName”.h. This is done to break down the size of the individual files. This makes it easier to group functions and ease the overview. It does, however, add a bit more complexity, but the tradeoff is still to use the extra files.
Fundamentally, there is nothing wrong in having everything in the .ino file, but it will become very big, when the functionality of the program grows. For the same reason the smaller RFzero programs, with limited functionality, may only consist of the .ino file.
If you need to add a function, that can be accessed from other files, you will have to add the function declaration to the relevant header file, i.e “filename”.h.
If you want to add a new parameter, i.e. variable, please see “Adding new parameter” section below.
commands.cpp/~.h
In commands.cpp file the MMI commands, coming from the USB port, are parsed, i.e. split into pieces. If the command is valid the value(s) is saved to the EEPROM, help text displayed or the current values are shown. If the command is invalid an error message is shown. The command parser flow is as follows:
- Receives the MMI strings from the yield() function
- Trims the received strings
- Checks if the program is in run or config mode
- If “config” is received the program is put into config mode
- Checks if the command is valid and processes the variable(s)
- When the “exit” command is received, and the configuration was changed, a function in config.cpp is called to load all the parameters – both changed and unchanged
“High level code” showing the functional principle of the commands.cpp file.
config.cpp/~.h
In the config.cpp files the configuration parameters are read from the EEPROM. In the config.h files the EEPROM map addresses are defined.
When the program start the LoadConfiguration() function called by the setup() function and the EEPROM data is read. Afterwards the EEPROM is only loaded when changes to the configuration have been made and an “exit” command has been evoked.
- Loads parameters from the EEPROM
- Relies on the EEPROM map
- Checks to see if the parameter value is valid, and if not a valid value is written to the EEPROM
- Sets global configurable variables, and updates the display
“High level code” showing the functional principle of the config.cpp file.
global.cpp/~.h
The global.cpp and global.h files are where the global variables are declared. A variable may be needed for the program flow or as configurable parameter.
A variable has to be declared in both files, and in the global.h file it must be declared as an external variable, e.g.
- global.h: extern int displayMode;
- global.cpp: int displayMode;
This is because the variable is declared in the global.cpp file, but is accessible to other files via the global.h file. Global variables are directly accessible. Thus, no function calls are needed to get or set them.
display.cpp/~.h
In the display.cpp file all the functions that initialize or write to the display are located. Each function has a matching function declaration in the display.h file. In most cases the information displayed are global variables or called by the relevant display function, e.g. GPS data. On rare occasions the information to be displayed is parsed as a variable in the function call.
- Initializes the display. It must be called after changing the display mode
- Update function(s) must be called whenever there is a change
- Update functions handle the different display modes
- Parameters are often global or GPS variables, otherwise they are passed as parameters
All the functions in the display.cpp file are controlled by the chosen display mode. This means that inside each function there is an “if” and a series of “else if” sections for each of the display modes. Each prints the relevant information at the right place. Sometimes a section for a display mode may be empty or sometimes it has been removed. The end result is the same, the display mode is not supported. If you need a new mode just add a new section with the relevant “else if” display mode check.
“High level code” showing the functional principle of the display.cpp file.
Displays are slow devices. Thus, don’t update the information if hasn’t been changed. Especially the TFT displays are slow since the data to be printed also includes the position, text size, background and foreground colors.
The Arduino yield() function
The yield() function is a standard Arduino function that is little known. It is of the type “weak”. This means that it can be “replaced” by a custom function with the same name by the programmer. The yield() function is included in the standard Arduino delay functions. This way background jobs can be serviced even during delays that would otherwise block if they are not interrupt driven.
In many of the RFzero programs the yield() function has been replaced by a custom yield() function, that receives the GPS and USB data. If the yield() function is not called the first sign is that the USB port will not respond, and the RFzero will eventually stall.
It is therefore important to call the yield() function in continuous, long or slow loops. Sometimes the yield() functions may be called more than ones if the loop contains a lot of functionality.
You may add functionality to the yield() function if you like. However, make sure it is fast and not executed more than necessary, i.e. add a counter to throttle back calling the extra functionality.
1 2 3 4 5 6 7 8 9 |
static int counter = 0; if (--counter < 0) { // Do something ... counter = 10000; // Number of loops before running again } |
The RFzero library
The RFzero library is a collection of functions, where some are specific to the RFzero, while others are generic. You can read more about the RFzero library here.
The only functionality that the RFzero library does by default is to measure the crystal frequency of the Si5351A, and collects the GPS data, but it does’t process the GPS data automatically. This has to be done by calling the gps.nmeaAutoParse() function.
The crystal frequency result is available via a function call to freqCount.getReferenceFrequency() function. Please remember that when the frequency counter is used for counting external frequencies, like done in the FrequencyCounter program, the Si5351A frequency counter is disabled.
In the RFzero programs the use of interrupts are limited to measuring the Si5351A crystal frequency and timing of the modulation. This allows you to have more interrupts available and less conflicts. But just like interrupt functions the use of functionality executed by the yield() function should be kept small and fast.
Adding new parameters
If you want to add a new parameter, i.e. a global variable, the process is as follows
- Create the parameter in the global.h file
e.g. external int myNewParameter;
remember the external declaration - Create the parameter in the global.cpp file
e.g. int myNewParameter;
you may assign a value to it here if you want, e.g. int myNewParameter = 73; - Assign an EEPROM address to the new parameter, while observing the EEPROM map and the size of the parameter, and create it in the config.h file
e.g. #define EEPROM_MyOwn_myNewParameter 1000
where 1000 is the address in the EEPROM - Load the value, from the EEPROM, of the parameter in the LoadConfiguation() function in the config.cpp file
e.g. myNewParameter = eeprom.readInteger(EEPROM_MyOwn_myNewParameter, 0); - In the commands.cpp file add a command that makes it possible to change the parameter
Hint: find an existing command that is similar to the new parameter then copy, paste and edit it to match the criteria of the new parameter - In the “rd cfg” command, in the commands.cpp file, add the new parameter so it will be included when reading the configuration. Otherwise it is may be difficult to know its value
- In the “?”/”help” command, in the commands.cpp file, add a description to the help text
The new parameter is now available to the program, and may be used to control the program flow and/or shown on the display.
If you want to remove a parameter the process is the same, but removing it from the use across all the files.
The Template program
If you want to write a program almost from scratch the Template program has all of the above, and is a fast way to start a new program. Alternatively you may use one of the other RFzero programs, that comes closest to what you want, and modify it to match the requirements.
The GPSDO program
The GPSDO program is a simple program that incorporates all of the above. All it does is to set and maintain a single output frequency and update the display. Thus, it is a good program to study before diving into the other programs.
The below code is from the version 1.0.0 of the RFzero library.
The GPSDO.ino file
The GPSDO.ino file consists of four sections
- The declarations
- The setup() function
- The loop() function
- The yield() function, that replaces the default Arduino yield() function
The declaration section
The first thing that happens in the declaration is to include the core RFzero library and the RFzero utilities library.
Then comes the includes of the global, display, config and commands header files. If some of the files use a function in one of the the other files, it is important that the includes are done in the right order, e.g. if a variable has not been compiled yet, it is not visible to the rest of the program.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// RFzero include and object creation #include <RFzero.h> // MUST ALWAYS be included for RFzero #include <RFzero_util.h> // Adds some general purpose functions // Program includes. Located in the same directory as this .ino file #include "global.h" #include "display.h" #include "config.h" #include "commands.h" // Flow control variables bool firstCalibationSaved = false; uint32_t startTime; double lastFref = -1.0; |
Finally the variable, that are only visible to the .ino file, are declared.
The setup() function
In the setup() function the first thing it does, is to initialize the RFzero library, that also starts the Si5351A frequency counter. The call to the initialization function must include the type of EEPROM used.
The next thing is to start the USB port. It take up to 2 s for the USB H/W to be ready to receive or send data. Thus a fixed delay is used.
Then follows an EEPROM check to see if it has some basic data which means that some configuration has happened, and it is safe to proceed. If the EEPROM is not configured, the program automatically enters the configuration mode, and waits for the user to do something, i.e. at least an “exit” command, before proceeding.
If the EEPROM was configured a welcome message is printed on the USB port.
Then all of the GPSDO configuration parameters are loaded, and if a display is connected a splash screen is shown.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
void setup() { // Initialize RFzero for the specific application (must always be present) RFzero.init(EEPROM_TYPE_24LC08B); // For RFzero v.1.x or EEPROM_TYPE_M24128 for RFzero v.2.x // Setup USB SerialUSB.begin(9600); delay(2000); // Delay used instead of "while (!(SerialUSB));" because it halts if not connected to USB. Checks show up to 1 s so 2 s to be on the safe side // Load configuration if (eeprom.isUnconfig()) { SerialUSB.println("\nEEPROM is unconfigured. Entering config mode"); SerialUSB.println("Enter ? to see commands\n"); ParseCommand((char*)"config"); while (configMode) // Wait until initial configuration is completed before continuing yield(); } else { // Print welcome message on USB RFzero.printLibPrgVer(0, swPackage); SerialUSB.println("Enter ? for help"); SerialUSB.println("RFzero>"); } LoadConfiguration(true); // Show splash screen // Wait for warming up? if (warmUp) { char buf[40]; sprintf(buf, "Warming up for %d s. Please wait", warmUp); SerialUSB.print(buf); for (int i = 0; i < warmUp; i++) { SerialUSB.print("."); for (int j = 0; j < 5; j++) { mcu.txLed(ON); delay(100); mcu.txLed(OFF); delay(100); } } if (configMode) SerialUSB.println("\nRFzero config>"); else SerialUSB.println("\nRFzero>"); } // Wait for GPS to be valid? if (gpsWait) while (!gps.getValid()) yield(); // Switch on the RF si5351a.rfLevel(RF_LEVEL_ON); mcu.txLed(ON); // Keep the start time startTime = millis(); } |
If the warm up parameter has been set the program waits for the set duration.
If the program has been configured to wait for a valid GPS the program will wait.
Finally the RF is enabled and a timestamp is made. The time stamp is used later on in the loop() function.
The loop() function
In the loop function the set GPSDO frequency is maintained. This is done every time the Si5351A reference frequency has changed. This can at most be ones per second, even if the loop is much much faster.
Ones per loop cycle the yield() function is called.
After running for five minutes the Si5351A reference frequency is saved to the EEPROM, so the GPSDO will be spot on the next time the program is started.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void loop() { double fref = freqCount.getReferenceFrequency(); if (fref != lastFref) // Retune the GPSDO { si5351a.retuneFrequency(); lastFref = fref; } yield(); // Already saved first measured reference frequency to EEPROM? if (!firstCalibationSaved) { if (millis() > startTime + 5 * 60 * 1000) // Save fref after running for 5 minutes { eeprom.saveReferenceStartFreq(); firstCalibationSaved = true; } } } |
The yield() function
The yield() function collects incoming USB data and calls the gps.nmeaAutoParse() function.
Every time a character is received, from the USB port, the buffer is checked to see if there is still room for more characters. Otherwise the buffer is cleared.
Unless the received character is a Line Feed the character is appended to the buffer. If a Line Feed character is received the USB data is sent to the ParseCommand() function in the comands.cpp file. When returning from the parser function the USB buffer is cleared.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
// USB and GPS receivers woven into standard Arduino function. Always include in "slow" loops void yield() { static unsigned int usbBufCount = 0; static char usbBuf[100]; bool keep_going ; do { keep_going = false; if (SerialUSB.available()) // Collect incoming chars { char ch = (char) SerialUSB.read(); if (usbBufCount < sizeof(usbBuf) - 1) { if (ch == '\n') { // End of line found so parse the buffer ParseCommand(usbBuf); usbBufCount = 0; // Clear buffer usbBuf[0] = 0; } else { // Pad buffer with latest char usbBuf[usbBufCount] = ch; usbBufCount++; usbBuf[usbBufCount] = 0; } } else { // Overflow so reset buffer usbBufCount = 0; usbBuf[0] = 0; } keep_going = true ; } // GPS receiver, also initiates the display updating if relevant if (gps.nmeaAutoParse()) { const char* frame = gps.nmeaGetLastFrame(); if (gpsEcho) SerialUSB.println(frame); if ((displayMode > 0) & (displayAutoUpdate)) Display_GPSUpdate(); } } while (keep_going) ; } |
If there was GPS data to parse it may also be echoed to the USB port, if the GPS echo parameter is enabled. Since the GPS data may also printed on the display, i.e. if displayMode is larger than 0, the Display_GPSUpdate() function is also called. The displayAutoUpdate variable is used to prevent the GPS data to be printed before the display mode has been loaded, the display is ready and the splash screen has been shown.
The commands files
The commands.h file
The commands.h file only has two variables namely the configuration mode flag and configuration changed flag. It has just one function the command parser. All are included in the commands.h file, so they can be accessed from other parts of the program.
The #ifndef _COMMANDS_H, #define _COMMANDS_H and #endif make sure that the files are only included/complied ones.
1 2 3 4 5 6 7 8 9 10 |
#ifndef _COMMANDS_H #define _COMMANDS_H // Prototypes extern uint8_t configMode; // Indicates if program is in config mode extern uint8_t configChanged; // Indicates if the configuration has changed void ParseCommand(char *cmd); #endif // _COMMANDS_H |
The commands.cpp file
The commands.cpp file also includes its own commands.h file and the other .h files. The latter makes it possible for the parser function to access the global variables and functions in the other files.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 |
#include <avr/dtostrf.h> // RFzero™ includes #include <RFzero.h> #include <RFzero_util.h> // Program includes. Located in the same directory as the .ino file #include "global.h" #include "display.h" #include "config.h" #include "commands.h" uint8_t configMode = 0; // Indicates if program is in config mode uint8_t configChanged = 0; // Indicates if the configuration has changed void ParseCommand(char *cmd) { const uint8_t NONE = 5; uint8_t comStatus = NONE; int value, value1; double freq; char buffreq[30]; char buf[80]; char str[100]; strcpy(str, cmd); TrimCharArray(str); if (strlen(str)) { comStatus = 0; SerialUSB.println(str); // *** Configuration control *** // config if (!configMode) { if (0 == strncmp("config", str, sizeof("config") - 1)) { configMode = 1; comStatus = 1; gpsEcho = 0; // Turn GPS echo off when in configuration mode RFzero.printLibPrgVer(0, swPackage); } else if ((0 == strncmp("help", str, sizeof("help") - 1)) || (0 == strncmp("?", str, sizeof("?") - 1))) { RFzero.printLibPrgVer(0, swPackage); if (!configMode) // In run mode SerialUSB.println("Enter config to enter configuration mode"); comStatus = 1; } } // In configuration mode else { // exit if (0 == strncmp("exit", str, sizeof("exit") - 1)) { configMode = 0; comStatus = NONE; gpsEcho = eeprom.readByte(EEPROM_GPS_Echo, 0); if (configChanged) { SerialUSB.println("Configuration changed, please wait"); LoadConfiguration(false); // Don't show splash screen configChanged = 0; } } // HARDWARE .......................................................... // wr hw T1 else if (0 == strncmp("wr t1 ", str, sizeof("wr t1 ") - 1)) { unsigned int value; if (1 == sscanf(&str[sizeof("wr t1 ") - 1], "%u", &value)) { if (value <= 2) { eeprom.writeByte(EEPROM_HW_T1, value); comStatus = 1; configChanged = 1; } else comStatus = 2; } else comStatus = 2; } // wr display MODE else if (0 == strncmp("wr display ", str, sizeof("wr display ") - 1)) { if (1 == sscanf(&str[sizeof("wr display ") - 1], "%u", &value)) { if (value < 256) { eeprom.writeByte(EEPROM_HW_DisplayMode, value); comStatus = 1; configChanged = 1; } else comStatus = 2; } else comStatus = 2; } // wr pcf8574 ADDR else if (0 == strncmp("wr pcf8574 ", str, sizeof("wr pcf8574 ") - 1)) { unsigned int addr; if (1 == sscanf(&str[sizeof("wr pcf8574 ") - 1], "%x", &addr)) { if ((addr == 0) || ((addr >= 0x20) && (addr <= 0x27)) || ((addr >= 0x38) && (addr <= 0x3F))) { eeprom.writeByte(EEPROM_HW_PCF8574addr, addr); comStatus = 1; configChanged = 1; } else comStatus = 5; } else comStatus = 2; } // wr warmup DURATION else if (0 == strncmp("wr warmup ", str, sizeof("wr warmup ") - 1)) { if (1 == sscanf(&str[sizeof("wr warmup ") - 1], "%d", &value)) { if ((0 <= value) && (value <= 0xFF)) { eeprom.writeByte(EEPROM_HW_WarmUp, value); comStatus = 1; configChanged = 1; } else comStatus = 2; } else comStatus = 2; } // wr level LEVEL else if (0 == strncmp("wr level ", str, sizeof("wr level ") - 1)) { if (1 == sscanf(&str[sizeof("wr level ") - 1], "%d", &value)) { if ((0 <= value) && (value <= 3)) { eeprom.writeByte(EEPROM_HW_CurLevels, value); comStatus = 1; configChanged = 1; } else comStatus = 2; } else comStatus = 2; } // GPS PARAMETERS .......................................................... // wr wait ONOFF else if (0 == strncmp("wr wait ", str, sizeof("wr wait ") - 1)) { if (1 == sscanf(&str[sizeof("wr wait ") - 1], "%d", &value)) { if ((0 <= value) && (value <= 1)) { eeprom.writeByte(EEPROM_GPS_Wait, value); comStatus = 1; configChanged = 1; } else comStatus = 2; } else comStatus = 2; } // wr echo ONOFF else if (0 == strncmp("wr echo ", str, sizeof("wr echo ") - 1)) { if (1 == sscanf(&str[sizeof("wr echo ") - 1], "%d", &value)) { if ((0 <= value) && (value <= 1)) { eeprom.writeByte(EEPROM_GPS_Echo, value); comStatus = 1; configChanged = 1; } else comStatus = 2; } else comStatus = 2; } // wr offset HOURS MINUTES else if (0 == strncmp("wr offset ", str, sizeof("wr offset ") - 1)) { if (2 == sscanf(&str[sizeof("wr offset ") - 1], "%d %d", &value, &value1)) { if ((-13 <= value) && (value <= 14) && (0 <= value1) && (value1 <= 59)) { eeprom.writeByte(EEPROM_GPS_LocalHoursOffset, value); eeprom.writeByte(EEPROM_GPS_LocalMinutesOffset, value1); comStatus = 1; configChanged = 1; } else comStatus = 2; } else comStatus = 2; } // wr dst ONOFF else if (0 == strncmp("wr dst ", str, sizeof("wr dst ") - 1)) { if (1 == sscanf(&str[sizeof("wr dst ") - 1], "%d", &value)) { if ((0 <= value) && (value <= 1)) { eeprom.writeByte(EEPROM_GPS_LocalDST, value); comStatus = 1; configChanged = 1; } else comStatus = 2; } else comStatus = 2; } // QO100 PARAMETERS .......................................................... // wr defaults else if (0 == strncmp("wr defaults", str, sizeof("wr defaults") - 1)) { // HARDWARE eeprom.writeInteger(EEPROM_HW_RefStartFreq, 27000000L); eeprom.writeByte(EEPROM_HW_T1, 0); eeprom.writeByte(EEPROM_HW_DisplayMode, 2); eeprom.writeByte(EEPROM_HW_PCF8574addr, 0); eeprom.writeByte(EEPROM_HW_WarmUp, 0); eeprom.writeByte(EEPROM_HW_CurLevels, 3); // GPS eeprom.writeByte(EEPROM_GPS_Wait, 0); eeprom.writeByte(EEPROM_GPS_Echo, 0); eeprom.writeByte(EEPROM_GPS_LocalHoursOffset, 0); eeprom.writeByte(EEPROM_GPS_LocalMinutesOffset, 0); eeprom.writeByte(EEPROM_GPS_LocalDST, 0); // GPSDO eeprom.writeDouble(EEPROM_GPSDO_Freq, 10000000.0); comStatus = 1; configChanged = 1; } // wr freq FREQ else if (0 == strncmp("wr freq ", str, sizeof("wr freq ") - 1)) { if (1 == sscanf(&str[sizeof("wr freq ") - 1], "%s", buf)) { double fr = strtod(buf, NULL); if (100000.0 <= fr) // Accept only frequencies from 100 kHz and up { eeprom.writeDouble(EEPROM_GPSDO_Freq, fr); comStatus = 1; configChanged = 1; } else comStatus = 3; } else comStatus = 2; } // OVERVIEW .......................................................... // rd cfg else if (0 == strncmp("rd cfg", str, sizeof("rd cfg") - 1)) { RFzero.printLibPrgVer(1, swPackage); SerialUSB.println("Configuration"); SerialUSB.println("============="); sprintf(buf, "T1: 0: transformer*, 1: combiner, 2: none : %d", eeprom.readByte(EEPROM_HW_T1, 0)); SerialUSB.println(buf); sprintf(buf, "Display: 0: none, 1: 16x2, 2: 20x4*, ... : %d", eeprom.readByte(EEPROM_HW_DisplayMode, 2)); SerialUSB.println(buf); sprintf(buf, "PCF8574 I2C addr: 0, 0x20 to 0x27, 0x38 to 0x3F : %02X", eeprom.readByte(EEPROM_HW_PCF8574addr, 0x27)); SerialUSB.println(buf); sprintf(buf, "Warm up before transmitting: 0* to 255 s : %d", eeprom.readByte(EEPROM_HW_WarmUp, 0)); SerialUSB.println(buf); sprintf(buf, "Curr. level: 0: 2 mA, 1: 4 mA, 2: 6 mA, 3: 8 mA*: %d", eeprom.readByte(EEPROM_HW_CurLevels, 3)); SerialUSB.println(buf); // GPS sprintf(buf, "\nWait for valid GPS before TX: 0: no*, 1: yes : %d", eeprom.readByte(EEPROM_GPS_Wait, 0)); SerialUSB.println(buf); sprintf(buf, "Echo GPS data to USB port: 0: no*, 1: yes : %d", eeprom.readByte(EEPROM_GPS_Echo, 0)); SerialUSB.println(buf); sprintf(buf, "Local hours relative to GPS west is negative: 0*: %d", (int8_t)eeprom.readByte(EEPROM_GPS_LocalHoursOffset, 0)); SerialUSB.println(buf); sprintf(buf, "Local minutes relative to GPS: 0* : %d", eeprom.readByte(EEPROM_GPS_LocalMinutesOffset, 0)); SerialUSB.println(buf); sprintf(buf, "Local daylight saving time: 0: normal*, 1: DST : %d", eeprom.readByte(EEPROM_GPS_LocalDST, 0)); SerialUSB.println(buf); // GPSDO freq = eeprom.readDouble(EEPROM_GPSDO_Freq, 0.0); dtostrf(freq, 0, 3, buffreq); sprintf(buf, "\nThe GPSDO frequency: 10 MHz* : %s", buffreq); SerialUSB.println(buf); SerialUSB.println("\n*: default value\n"); comStatus = NONE; } // help or ? else if ((0 == strncmp("help", str, sizeof("help") - 1)) || (0 == strncmp("?", str, sizeof("?") - 1))) { RFzero.printLibPrgVer(1, swPackage); SerialUSB.println("Available MMI commands. Type exit to return to run mode\n"); SerialUSB.println("Configuration"); SerialUSB.println("============="); SerialUSB.println(" wr freq FREQ to set GPSDO frequency from 100 kHz and up\n"); SerialUSB.println(" wr warmup SECONDS to set the number of seconds to warm up the H/W before switching the RF on 0 - 255\n"); SerialUSB.println(" wr wait ONOFF to turn on/off waiting for the GPS to be valid before signal is on: 0: off, 1: on"); SerialUSB.println(" wr echo ONOFF to turn on/off GPS data echo on the USB on/off: 0: off, 1: on"); SerialUSB.println(" wr offset HOURS MINUTES to set the differente between UTC and local time, west is negative difference"); SerialUSB.println(" wr dst ONOFF to turn on/off daylight saving time: 0: normal, 1: DST\n"); SerialUSB.println(" wr defaults to set the H/W and S/W defaults"); SerialUSB.println("\n wr t1 MODE to set the T1 hardware mode where MODE is:"); SerialUSB.println(" 0: transformer, 1: combiner, 2: none"); SerialUSB.println(" wr display MODE to set the display mode where MODE is:"); SerialUSB.println(" 0: none, 1: LCD 16x2 on JP12, 2: LCD 20x4 on JP12, ..."); SerialUSB.println(" see www.rfzero.net/documentation/hardware/displays for more information"); SerialUSB.println(" wr pcf8574 ADDR to set the PCF8574 I2C address: 0, 0x20-0x27 or 0x38 to 0x3F"); SerialUSB.println(" wr level LEVEL to set the Si5351A output stages\' drive strength current level where:"); SerialUSB.println(" 0: 2 mA, 1: 4 mA, 2: 6 mA, 3: 8 mA*\n"); SerialUSB.println(" rd cfg to list the current configuration\n"); comStatus = NONE; } } } switch (comStatus) { case 0: SerialUSB.println("Unknown command"); break; case 1: SerialUSB.println("OK"); break; case 2: SerialUSB.println("Invalid data"); break; case 3: SerialUSB.println("Invalid frequency"); break; case 4: SerialUSB.println("Invalid level"); break; default : break; // No response since result is self explanatory } if (configMode) SerialUSB.println("RFzero config>"); else SerialUSB.println("RFzero>"); } |
The ParseComand function first trims the received command and removes duplicate spaces. Then the command falls through the if, else if tree to see if there is a match. If not an error is printed.
When there is a command match the variable(s) included with the command is processed. Below is an example of processing the “wr display” command
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// wr display MODE else if (0 == strncmp("wr display ", str, sizeof("wr display ") - 1)) { if (1 == sscanf(&str[sizeof("wr display ") - 1], "%u", &value)) { if (value < 256) { eeprom.writeByte(EEPROM_HW_DisplayMode, value); comStatus = 1; configChanged = 1; } else comStatus = 2; } else comStatus = 2; } |
The strncmp() function, (0 == strncmp(“wr display “, str, sizeof(“wr display “) – 1)), compares the received string with “wr display “. If there is a match zero (0) is returned.
Next is the use of the sscanf() function, (1 == sscanf(&str[sizeof(“wr display “) – 1], “%u”, &value)), that extracts the variable, display mode, from the string.
Finally, there is a check if the display mode is valid, and if so it is written to the EEPROM, the configuration changed flag is set and a no-error code is set in the command status comStatus variable. If there was an error the relevant error code is set.
The config files
The config.h file
The config.h file contains the EEPROM map definition and access to the LoadConfiguration () function.
The #ifndef _CONFIG_H, #define _CONFIG_H and #endif make sure that the files are only included/complied ones.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#ifndef _CONFIG_H #define _CONFIG_H // Arduino includes // Enable RFzero class // EEPROM MAP // HARDWARE #define EEPROM_HW_RefStartFreq 16 // 4 bytes #define EEPROM_HW_T1 21 // 1 byte #define EEPROM_HW_DisplayMode 22 // 1 byte #define EEPROM_HW_WarmUp 24 // 1 byte #define EEPROM_HW_PCF8574addr 28 // 1 byte #define EEPROM_HW_CurLevels 29 // 1 byte // GPS #define EEPROM_GPS_Wait 33 // 1 byte #define EEPROM_GPS_Echo 34 // 1 byte #define EEPROM_GPS_LocalHoursOffset 35 // 1 byte #define EEPROM_GPS_LocalMinutesOffset 36 // 1 byte #define EEPROM_GPS_LocalDST 37 // 1 byte // GPSDO #define EEPROM_GPSDO_Freq 432 // 8 bytes // Function prototypes void LoadConfiguration(bool splash); #endif // _CONFIG_H |
The config.cpp file
The LoadConfiguration() function loads the values from the EEPROM. When the splash boolean parameter is set the display initializing function is called showing the spalsh screen. When the function is called the current display mode is stored so the display can be properly initialized if it has been changed.
One by one the EEPROM parameters are loaded. If there is a mismatch between the read value and a valid value, a default value is used, and this is also written to the EEPROM. This is done to prevent invalid situations that could otherwise lock up the program.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
// Program includes. Located in the same directory as the .ino file #include "global.h" #include "display.h" #include "config.h" #include <RFzero_util.h> void LoadConfiguration(bool splash) { // EXISTING VALUES int oldDisplayMode = displayMode; // HARDWARE switch (eeprom.readByte(EEPROM_HW_T1, 0)) { default: case 0 : si5351a.setHardwareT1(HARDWARE_T1_TRANSFORMER); break; case 1 : si5351a.setHardwareT1(HARDWARE_T1_COMBINER); break; case 2 : si5351a.setHardwareT1(HARDWARE_T1_NONE); break; } displayMode = eeprom.readByte(EEPROM_HW_DisplayMode, 2); pcf8574Addr = eeprom.readByte(EEPROM_HW_PCF8574addr, 0); if ((pcf8574Addr > 0) && ((pcf8574Addr < 0x20) || (pcf8574Addr > 0x27)) && ((pcf8574Addr < 0x38) || (pcf8574Addr > 0x3F))) { pcf8574Addr = 0; eeprom.writeByte(EEPROM_HW_PCF8574addr, pcf8574Addr); } if ((displayMode == 3) || (displayMode == 4) || (pcf8574Addr != 0)) Wire.begin(); else Wire.end(); warmUp = eeprom.readByte(EEPROM_HW_WarmUp, 0); currentLevels = eeprom.readByte(EEPROM_HW_CurLevels, 3); if (currentLevels > 3) { currentLevels = 3; eeprom.writeByte(EEPROM_HW_CurLevels, currentLevels); } switch (currentLevels) { case 0 : si5351a.rfLevel(RF_LEVEL_2); break; case 1 : si5351a.rfLevel(RF_LEVEL_4); break; case 2 : si5351a.rfLevel(RF_LEVEL_6); break; default: si5351a.rfLevel(RF_LEVEL_8); } // GPS gpsWait = eeprom.readByte(EEPROM_GPS_Wait, 0); gpsEcho = eeprom.readByte(EEPROM_GPS_Echo, 0); //int gpsLocalHoursOffset = static_cast<signed char>(eeprom.readByte(EEPROM_GPS_LocalHoursOffset, 0)); int gpsLocalHoursOffset = (int8_t)(eeprom.readByte(EEPROM_GPS_LocalHoursOffset, 0)); int gpsLocalMinutesOffset = eeprom.readByte(EEPROM_GPS_LocalMinutesOffset, 0); gps.setLocalTimeDiff(gpsLocalHoursOffset, gpsLocalMinutesOffset); localDST = eeprom.readByte(EEPROM_GPS_LocalDST, 0); gps.setLocalDST(localDST); // GPSDO frequency = eeprom.readDouble(EEPROM_GPSDO_Freq, 10000000.0); if ((frequency < 100000.0) || (frequency > 298765432.0)) // In frequency is outside range then set to 10 MHz and save { frequency = 10000000.0; eeprom.writeDouble(EEPROM_GPSDO_Freq, frequency); } si5351a.setFrequency(frequency, CALC_EPSILON); // Prepare display if (displayMode != oldDisplayMode) { SerialGPS.end(); displayAutoUpdate = 0; Display_StartScreen(splash); SerialGPS.begin(9600); } Display_LocalNormalDSTUpdate(); } |
Finally the display is updated, if the display mode has been changed.
The global files
The global.h file
In the global.h file the global variables are made accessible to other parts of the program.
The #ifndef _GLOBAL_H, #define _GLOBAL_H and #endif make sure that the files are only included/complied ones.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#ifndef _GLOBAL_H #define _GLOBAL_H // Arduino includes #include <Arduino.h> // RFzero™ and RFzero_modes includes #include <RFzero.h> // S/W package info extern char swPackage[]; // Hardware extern int displayMode; extern int pcf8574Addr; extern int displayAutoUpdate; // Allow automated updating of the display extern int currentLevels; // The Si5351A drive strength current levels extern int warmUp; // Warm up seconds counter // GPS extern int gpsEcho; // Echo GPS data to management port extern int gpsWait; // Wait for valid GPS indicator extern int localDST; // Daylight saving time // GPSDO data extern double frequency; // The GPSDO frequency #endif // _GLOBAL_H |
The global.cpp file
The initial values for the global variables are set in the global.cpp file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// Own include #include "global.h" // S/W package info char swPackage[] = "GPSDO"; // Hardware int displayMode = -1; int pcf8574Addr = 0; int displayAutoUpdate = 0; // Allow automated updating of the display int currentLevels = 3; // The Si5351A drive strength current levels int warmUp = 0; // Warm up seconds counter // GPS int gpsEcho = 0; // Echo GPS data to management port int gpsWait = 0; // Wait for valid GPS indicator int localDST = 0; // Daylight saving time // GPSDO data double frequency = 0.0; // The GPSDO frequency |
E.g. setting a variable like the display mode to -1 will ensure that any valid EEPROM value, 0-255, will be diffent forcing a relevant action in the config.cpp file.
The display files
Please remember that the display update functions should only be called when there is a change. This is because displays are slow devices.
The display.h file
The display.h file has three functions available to other parts of the program.
The #ifndef _DISPLAY_H, #define _DISPLAY_H and #endif make sure that the files are only included/complied ones.
1 2 3 4 5 6 7 8 9 |
#ifndef _LCD_H #define _LCD_H // Function declarations void Display_StartScreen(bool splash); void Display_LocalNormalDSTUpdate(); void Display_GPSUpdate(); #endif // _LCD_H |
The display.cpp file
The display.cpp file contains all declaration and includes necessary for the display variants, i.e. LCDs, LCDs via I2C and TFTs and their H/W connections.
The hdopGlyphs() function defines a custom set of characters used for the GPS HDOP bars in the LCDs.
Within each of the three functions
- Display_StartScreen(bool splash)
- Display_LocalNormalDSTUpdate()
- Display_GPSUpdate()
there are, up to, six if, else if sections each handling the specific display for the function both when it comes which data to print, at which X,Y location and how to print it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 |
// RFzero includes #include <RFzero.h> // Display includes #include <LiquidCrystal.h> LiquidCrystal LCD(PIN_LCD_RS, PIN_LCD_EN, PIN_LCD_DB4, PIN_LCD_DB5, PIN_LCD_DB6, PIN_LCD_DB7); // Create parallel LCD object #include <RFzero_LCD_I2C.h> LCD_I2C lcdI2C; // Create lcdI2C object and use default I2C on D8 and D9 #define PIN_TFT_CS 10 #define PIN_TFT_RST 11 #define PIN_TFT_DC 12 #define backgroundColor 0x000F // Navy blue #define foregroundColor 0xFFE0 // Yellow #include <Adafruit_GFX.h> // by Adafruit: https://github.com/adafruit/Adafruit-GFX-Library #include <Adafruit_ILI9341.h> // by Adafruit: https://github.com/adafruit/Adafruit_ILI9341 Adafruit_ILI9341 ili9341 = Adafruit_ILI9341(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST); #include <ILI9488.h> // by Jaret Burkett: https://github.com/jaretburkett/ILI9488 ILI9488 ili9488 = ILI9488(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST); // Program includes. Located in the same directory as the .ino file #include "global.h" #include "display.h" // Update control variables int lastGPSValid; double lastHDOP; unsigned int lastSatellites; unsigned int lastHours; unsigned int lastMinutes; unsigned int lastSeconds; unsigned int lastYear; unsigned int lastMonth; unsigned int lastDay; int lastLocalHours; int lastLocalMinutes; int lastLocalYear; int lastLocalMonth; int lastLocalDay; byte hdopGlyphs[5][8] = { {B00000, B00000, B00000, B00000, B00000, B00000, B01000, B00000}, {B00000, B00000, B00000, B00000, B00000, B01000, B11000, B00000}, {B00000, B00000, B00000, B00000, B00100, B01100, B11100, B00000}, {B00000, B00000, B00010, B00010, B00110, B01110, B11110, B00000}, {B00001, B00001, B00011, B00011, B00111, B01111, B11111, B00000} }; void lcdPrintHdop(double hdop) { if (displayMode == 1) { if (hdop > 9.0) LCD.print(" "); else if (hdop > 8.0) LCD.write(byte(0)); else if (hdop > 6.0) LCD.write(byte(1)); else if (hdop > 4.0) LCD.write(byte(2)); else if (hdop > 2.0) LCD.write(byte(3)); else LCD.write(byte(4)); } else if (displayMode == 3) { if (hdop > 9.0) lcdI2C.print(" "); else if (hdop > 8.0) lcdI2C.write(byte(0)); else if (hdop > 6.0) lcdI2C.write(byte(1)); else if (hdop > 4.0) lcdI2C.write(byte(2)); else if (hdop > 2.0) lcdI2C.write(byte(3)); else lcdI2C.write(byte(4)); } } void Display_StartScreen(bool splash) { char buf[40]; // LCD 16x2, parallel if (displayMode == 1) { // Initialize the LCD for (int i = 0; i < 5; i++) LCD.createChar(i, hdopGlyphs[i]); LCD.begin(16, 2); if (splash) { // Print S/W version LCD.print("RFzero v. "); LCD.print(RFZERO_LIBRARY_VERSION); sprintf(buf, "%.16s", swPackage); LCD.setCursor(0, 1); LCD.print(buf); delay(3000); LCD.clear(); } // Print standard screen layout LCD.print("GPSDO HH:MM:SS"); LCD.setCursor(0, 1); LCD.print("Inval YYYY-MM-DD"); } // LCD 20x4, parallel else if (displayMode == 2) { // Initialize the LCD LCD.begin(20, 4); if (splash) { // Print S/W version LCD.print("RFzero v. "); LCD.print(RFZERO_LIBRARY_VERSION); sprintf(buf, "%.16s", swPackage); LCD.setCursor(0, 1); LCD.print(buf); delay(3000); LCD.clear(); } // Print standard screen layout LCD.print("RFzero GPSDO no GPS"); LCD.setCursor(4, 1); LCD.print("UTC Local "); if (localDST) LCD.print("D"); else LCD.print("N"); LCD.setCursor(0, 2); LCD.print("YYYY-MM-DD HH:MM:SS"); LCD.setCursor(0, 3); LCD.print("HH:MM:SS YYYY-MM-DD"); } // LCD 16x2, I2C else if (displayMode == 3) { // Initialize the LCD lcdI2C.begin(pcf8574Addr, 16, 2); for (int i = 0; i < 5; i++) lcdI2C.createChar(i, hdopGlyphs[i]); if (splash) { // Print S/W version lcdI2C.print("RFzero v. "); lcdI2C.print(RFZERO_LIBRARY_VERSION); sprintf(buf, "%.16s", swPackage); lcdI2C.setCursor(0, 1); lcdI2C.print(buf); delay(3000); lcdI2C.clear(); } // Print standard screen layout lcdI2C.print("GPSDO HH:MM:SS"); lcdI2C.setCursor(0, 1); lcdI2C.print("Inval YYYY-MM-DD"); } // LCD 20x4, I2C else if (displayMode == 4) { // Initialize the LCD lcdI2C.begin(pcf8574Addr, 20, 4); if (splash) { // Print S/W version lcdI2C.print("RFzero v. "); lcdI2C.print(RFZERO_LIBRARY_VERSION); sprintf(buf, "%.16s", swPackage); lcdI2C.setCursor(0, 1); lcdI2C.print(buf); delay(3000); lcdI2C.clear(); } // Print standard screen layout lcdI2C.print("RFzero GPSDO no GPS"); lcdI2C.setCursor(4, 1); lcdI2C.print("UTC Local "); if (localDST) lcdI2C.print("D"); else lcdI2C.print("N"); lcdI2C.setCursor(0, 2); lcdI2C.print("YYYY-MM-DD HH:MM:SS"); lcdI2C.setCursor(0, 3); lcdI2C.print("HH:MM:SS YYYY-MM-DD"); } // ILI9341 320x240 else if (displayMode == 5) { // Initialize the display ili9341.begin(); ili9341.fillScreen(ILI9341_BLACK); ili9341.setRotation(3); if (splash) { // Print S/W version ili9341.setTextColor(ILI9488_WHITE, ILI9488_BLACK); ili9341.setTextSize(8); ili9341.setCursor(20, 0); ili9341.print("RFzero"); ili9341.setTextSize(3); ili9341.setCursor(15, 100); sprintf(buf, "Library v. %s", RFZERO_LIBRARY_VERSION); ili9341.print(buf); ili9341.setCursor(15, 150); ili9341.print(swPackage); ili9341.setCursor(35, 220); ili9341.print("www.rfzero.net"); delay(3000); } // Print standard screen layout ili9341.fillScreen(backgroundColor); ili9341.fillRect(0, 0, 320, 30, ILI9341_RED); ili9341.setTextColor(ILI9341_WHITE, ILI9341_RED); ili9341.setTextSize(3); ili9341.setCursor(4, 4); ili9341.print("RFzero GPSDO"); ili9341.setTextColor(foregroundColor, backgroundColor); ili9341.setTextSize(2); ili9341.setCursor(0, 35); ili9341.print("GPS"); ili9341.setCursor(0, 90); ili9341.print("UTC"); ili9341.setCursor(200, 90); ili9341.print("YYYY-MM-DD"); ili9341.setCursor(200, 170); ili9341.print("YYYY-MM-DD"); ili9341.setTextSize(3); ili9341.setCursor(20, 55); ili9341.print("Invalid"); ili9341.drawFastHLine(0, 85, 320, foregroundColor); ili9341.drawFastHLine(0, 165, 320, foregroundColor); ili9341.setTextSize(6); ili9341.setCursor(15, 115); ili9341.print("HH:MM:00"); ili9341.setCursor(15, 195); ili9341.print("HH:MM:00"); } // ILI9488 480x320 else if (displayMode == 6) { // Initialize the display ili9488.begin(); ili9488.fillScreen(ILI9488_BLACK); ili9488.setRotation(3); if (splash) { // Print S/W version ili9488.setTextColor(ILI9488_WHITE, ILI9488_BLACK); ili9488.setTextSize(8); ili9488.setCursor(20, 0); ili9488.print("RFzero"); ili9488.setTextSize(3); ili9488.setCursor(15, 100); sprintf(buf, "Library v. %s", RFZERO_LIBRARY_VERSION); ili9488.print(buf); ili9488.setCursor(15, 150); ili9488.print(swPackage); ili9488.setCursor(35, 220); ili9488.print("www.rfzero.net"); delay(3000); } // Print standard screen layout ili9488.fillScreen(backgroundColor); ili9488.fillRect(0, 0, 480, 30, ILI9488_RED); ili9488.setTextColor(ILI9488_WHITE, ILI9488_RED); ili9488.setTextSize(3); ili9488.setCursor(4, 4); ili9488.print("RFzero GPSDO"); ili9488.setTextColor(foregroundColor, backgroundColor); ili9488.setTextSize(2); ili9488.setCursor(0, 35); ili9488.print("GPS"); ili9488.setTextSize(3); ili9488.setCursor(20, 60); ili9488.print("Invalid"); ili9488.drawFastHLine(0, 90, 480, foregroundColor); ili9488.drawFastHLine(0, 210, 480, foregroundColor); ili9488.setCursor(0, 95); ili9488.print("UTC"); ili9488.setCursor(300, 95); ili9488.print("YYYY-MM-DD"); ili9488.setCursor(300, 215); ili9488.print("YYYY-MM-DD"); ili9488.setTextSize(9); ili9488.setCursor(30, 135); ili9488.print("HH:MM:00"); ili9488.setCursor(30, 255); ili9488.print("HH:MM:00"); } // Initialize update control variables lastGPSValid = -1; lastHDOP = -1.0; lastSatellites = 999; lastSeconds = 999; lastMinutes = 999; lastHours = 999; lastYear = 999; lastMonth = 999; lastDay = 999; lastLocalMinutes = -1; lastLocalHours = -1; lastLocalYear = -1; lastLocalMonth = -1; lastLocalDay = -1; displayAutoUpdate = 1; } // Display update function for local time normal or daylight saving void Display_LocalNormalDSTUpdate() { if (displayMode == 1) { } else if (displayMode == 2) // Update the display with normal/DST if any LCD { LCD.setCursor(18, 1); if (localDST) LCD.print("D"); else LCD.print("N"); } else if (displayMode == 3) { } else if (displayMode == 4) // Update the display with normal/DST if any LCD { lcdI2C.setCursor(18, 1); if (localDST) lcdI2C.print("D"); else lcdI2C.print("N"); } else if (displayMode == 5) // Update the display with normal/DST if any LCD { ili9341.setTextColor(foregroundColor, backgroundColor); ili9341.setTextSize(2); ili9341.setCursor(0, 170); if (localDST) ili9341.print("Local DST"); else ili9341.print("Local "); } else if (displayMode == 6) // Update the display with normal/DST if any LCD { ili9488.setTextColor(foregroundColor, backgroundColor); ili9488.setTextSize(3); ili9488.setCursor(0, 215); if (localDST) ili9488.print("Local DST"); else ili9488.print("Local "); } } // Display update function triggered by GPS data parsed void Display_GPSUpdate() { struct gpsSatData gpsInfo; // Create local object with all parameters gps.getSatData(&gpsInfo); if (displayMode == 1) { if (lastGPSValid != gpsInfo.valid) { LCD.setCursor(0, 1); if (gpsInfo.valid) LCD.print("Valid"); else LCD.print("Inval"); lastGPSValid = gpsInfo.valid; } if (lastHDOP != gpsInfo.hdop) { LCD.setCursor(6, 0); lcdPrintHdop(gpsInfo.hdop); lastHDOP = gpsInfo.hdop; } // Update the seconds for both UTC if (lastSeconds != gpsInfo.utcSeconds) { LCD.setCursor(14, 0); if (gpsInfo.utcSeconds < 10) LCD.print('0'); LCD.print(gpsInfo.utcSeconds); lastSeconds = gpsInfo.utcSeconds; } if (lastMinutes != gpsInfo.utcMinutes) { LCD.setCursor(11, 0); if (gpsInfo.utcMinutes < 10) LCD.print('0'); LCD.print(gpsInfo.utcMinutes); lastMinutes = gpsInfo.utcMinutes; } if (lastHours != gpsInfo.utcHours) { LCD.setCursor(8, 0); if (gpsInfo.utcHours < 10) LCD.print('0'); LCD.print(gpsInfo.utcHours); lastHours = gpsInfo.utcHours; } if (lastDay != gpsInfo.utcDay) { LCD.setCursor(14, 1); if (gpsInfo.utcDay < 10) LCD.print('0'); LCD.print(gpsInfo.utcDay); lastDay = gpsInfo.utcDay; } if (lastMonth != gpsInfo.utcMonth) { LCD.setCursor(11, 1); if (gpsInfo.utcMonth < 10) LCD.print('0'); LCD.print(gpsInfo.utcMonth); lastMonth = gpsInfo.utcMonth; } if (lastYear != gpsInfo.utcYear) { LCD.setCursor(6, 1); if (gpsInfo.utcYear < 1000) LCD.print("0000"); else { LCD.print(gpsInfo.utcYear); lastYear = gpsInfo.utcYear; } } } else if (displayMode == 2) { if (lastGPSValid != gpsInfo.valid) { LCD.setCursor(14, 0); if (gpsInfo.valid) LCD.print("GPS OK"); else LCD.print("no GPS"); lastGPSValid = gpsInfo.valid; } // Update the seconds for both UTC and local if (lastSeconds != gpsInfo.utcSeconds) { LCD.setCursor(18, 2); if (gpsInfo.utcSeconds < 10) LCD.print('0'); LCD.print(gpsInfo.utcSeconds); LCD.setCursor(6, 3); if (gpsInfo.utcSeconds < 10) LCD.print('0'); LCD.print(gpsInfo.utcSeconds); lastSeconds = gpsInfo.utcSeconds; } // Update the UTC if (lastMinutes != gpsInfo.utcMinutes) { LCD.setCursor(3, 3); if (gpsInfo.utcMinutes < 10) LCD.print('0'); LCD.print(gpsInfo.utcMinutes); lastMinutes = gpsInfo.utcMinutes; } if (lastHours != gpsInfo.utcHours) { LCD.setCursor(0, 3); if (gpsInfo.utcHours < 10) LCD.print('0'); LCD.print(gpsInfo.utcHours); lastHours = gpsInfo.utcHours; } if (lastDay != gpsInfo.utcDay) { LCD.setCursor(8, 2); if (gpsInfo.utcDay < 10) LCD.print('0'); LCD.print(gpsInfo.utcDay); lastDay = gpsInfo.utcDay; } if (lastMonth != gpsInfo.utcMonth) { LCD.setCursor(5, 2); if (gpsInfo.utcMonth < 10) LCD.print('0'); LCD.print(gpsInfo.utcMonth); lastMonth = gpsInfo.utcMonth; } if (lastYear != gpsInfo.utcYear) { LCD.setCursor(0, 2); if (gpsInfo.utcYear < 1000) LCD.print("0000"); else { LCD.print(gpsInfo.utcYear); lastYear = gpsInfo.utcYear; } } // Update the local time int lclYear, lclMonth, lclDay, lclHours, lclMinutes; gps.getLocalTime(lclYear, lclMonth, lclDay, lclHours, lclMinutes); if (lastLocalMinutes != lclMinutes) { LCD.setCursor(15, 2); if (lclMinutes < 10) LCD.print('0'); LCD.print(lclMinutes); lastLocalMinutes = lclMinutes; } if (lastLocalHours != lclHours) { LCD.setCursor(12, 2); if (lclHours < 10) LCD.print('0'); LCD.print(lclHours); lastLocalHours = lclHours; } if (lastLocalDay != lclDay) { LCD.setCursor(18, 3); if (lclDay < 10) LCD.print('0'); LCD.print(lclDay); lastLocalDay = lclDay; } if (lastLocalMonth != lclMonth) { LCD.setCursor(15, 3); if (lclMonth < 10) LCD.print('0'); LCD.print(lclMonth); lastLocalMonth = lclMonth; } if (lastLocalYear != lclYear) { LCD.setCursor(10, 3); if (lclYear < 1000) LCD.print("0000"); else { LCD.print(lclYear); lastLocalYear = lclYear; } } } else if (displayMode == 3) { if (lastGPSValid != gpsInfo.valid) { lcdI2C.setCursor(0, 1); if (gpsInfo.valid) lcdI2C.print("Valid"); else lcdI2C.print("Inval"); lastGPSValid = gpsInfo.valid; } if (lastHDOP != gpsInfo.hdop) { lcdI2C.setCursor(6, 0); lcdPrintHdop(gpsInfo.hdop); lastHDOP = gpsInfo.hdop; } // Update the seconds for both UTC if (lastSeconds != gpsInfo.utcSeconds) { lcdI2C.setCursor(14, 0); if (gpsInfo.utcSeconds < 10) lcdI2C.print('0'); lcdI2C.print(gpsInfo.utcSeconds); lastSeconds = gpsInfo.utcSeconds; } if (lastMinutes != gpsInfo.utcMinutes) { lcdI2C.setCursor(11, 0); if (gpsInfo.utcMinutes < 10) lcdI2C.print('0'); lcdI2C.print(gpsInfo.utcMinutes); lastMinutes = gpsInfo.utcMinutes; } if (lastHours != gpsInfo.utcHours) { lcdI2C.setCursor(8, 0); if (gpsInfo.utcHours < 10) lcdI2C.print('0'); lcdI2C.print(gpsInfo.utcHours); lastHours = gpsInfo.utcHours; } if (lastDay != gpsInfo.utcDay) { lcdI2C.setCursor(14, 1); if (gpsInfo.utcDay < 10) lcdI2C.print('0'); lcdI2C.print(gpsInfo.utcDay); lastDay = gpsInfo.utcDay; } if (lastMonth != gpsInfo.utcMonth) { lcdI2C.setCursor(11, 1); if (gpsInfo.utcMonth < 10) lcdI2C.print('0'); lcdI2C.print(gpsInfo.utcMonth); lastMonth = gpsInfo.utcMonth; } if (lastYear != gpsInfo.utcYear) { lcdI2C.setCursor(6, 1); if (gpsInfo.utcYear < 1000) lcdI2C.print("0000"); else { lcdI2C.print(gpsInfo.utcYear); lastYear = gpsInfo.utcYear; } } } else if (displayMode == 4) { if (lastGPSValid != gpsInfo.valid) { lcdI2C.setCursor(14, 0); if (gpsInfo.valid) lcdI2C.print("GPS OK"); else lcdI2C.print("no GPS"); lastGPSValid = gpsInfo.valid; } // Update the seconds for both UTC and local if (lastSeconds != gpsInfo.utcSeconds) { lcdI2C.setCursor(18, 2); if (gpsInfo.utcSeconds < 10) lcdI2C.print('0'); lcdI2C.print(gpsInfo.utcSeconds); lcdI2C.setCursor(6, 3); if (gpsInfo.utcSeconds < 10) lcdI2C.print('0'); lcdI2C.print(gpsInfo.utcSeconds); lastSeconds = gpsInfo.utcSeconds; } // Update the UTC if (lastMinutes != gpsInfo.utcMinutes) { lcdI2C.setCursor(3, 3); if (gpsInfo.utcMinutes < 10) lcdI2C.print('0'); lcdI2C.print(gpsInfo.utcMinutes); lastMinutes = gpsInfo.utcMinutes; } if (lastHours != gpsInfo.utcHours) { lcdI2C.setCursor(0, 3); if (gpsInfo.utcHours < 10) lcdI2C.print('0'); lcdI2C.print(gpsInfo.utcHours); lastHours = gpsInfo.utcHours; } if (lastDay != gpsInfo.utcDay) { lcdI2C.setCursor(8, 2); if (gpsInfo.utcDay < 10) lcdI2C.print('0'); lcdI2C.print(gpsInfo.utcDay); lastDay = gpsInfo.utcDay; } if (lastMonth != gpsInfo.utcMonth) { lcdI2C.setCursor(5, 2); if (gpsInfo.utcMonth < 10) lcdI2C.print('0'); lcdI2C.print(gpsInfo.utcMonth); lastMonth = gpsInfo.utcMonth; } if (lastYear != gpsInfo.utcYear) { lcdI2C.setCursor(0, 2); if (gpsInfo.utcYear < 1000) lcdI2C.print("0000"); else { lcdI2C.print(gpsInfo.utcYear); lastYear = gpsInfo.utcYear; } } // Update the local time int lclYear, lclMonth, lclDay, lclHours, lclMinutes; gps.getLocalTime(lclYear, lclMonth, lclDay, lclHours, lclMinutes); if (lastLocalMinutes != lclMinutes) { lcdI2C.setCursor(15, 2); if (lclMinutes < 10) lcdI2C.print('0'); lcdI2C.print(lclMinutes); lastLocalMinutes = lclMinutes; } if (lastLocalHours != lclHours) { lcdI2C.setCursor(12, 2); if (lclHours < 10) lcdI2C.print('0'); lcdI2C.print(lclHours); lastLocalHours = lclHours; } if (lastLocalDay != lclDay) { lcdI2C.setCursor(18, 3); if (lclDay < 10) lcdI2C.print('0'); lcdI2C.print(lclDay); lastLocalDay = lclDay; } if (lastLocalMonth != lclMonth) { lcdI2C.setCursor(15, 3); if (lclMonth < 10) lcdI2C.print('0'); lcdI2C.print(lclMonth); lastLocalMonth = lclMonth; } if (lastLocalYear != lclYear) { lcdI2C.setCursor(10, 3); if (lclYear < 1000) lcdI2C.print("0000"); else { lcdI2C.print(lclYear); lastLocalYear = lclYear; } } } else if (displayMode == 5) { char buf[15]; // Update the seconds for both UTC and local ili9341.setTextSize(6); if (lastSeconds != gpsInfo.utcSeconds) { if (gpsInfo.utcSeconds % 10 == 0) { ili9341.setCursor(231, 115); sprintf(buf, "%02u", gpsInfo.utcSeconds); ili9341.print(buf); ili9341.setCursor(231, 195); ili9341.print(buf); } else { ili9341.setCursor(267, 115); ili9341.print(gpsInfo.utcSeconds % 10); ili9341.setCursor(267, 195); ili9341.print(gpsInfo.utcSeconds % 10); } lastSeconds = gpsInfo.utcSeconds; } // Update the UTC if (lastMinutes != gpsInfo.utcMinutes) { sprintf(buf, "%02u", gpsInfo.utcMinutes); ili9341.setCursor(123, 115); ili9341.print(buf); lastMinutes = gpsInfo.utcMinutes; } yield(); if (lastHours != gpsInfo.utcHours) { sprintf(buf, "%02u", gpsInfo.utcHours); ili9341.setCursor(15, 115); ili9341.print(buf); lastHours = gpsInfo.utcHours; } // Update the local time int lclYear, lclMonth, lclDay, lclHours, lclMinutes; gps.getLocalTime(lclYear, lclMonth, lclDay, lclHours, lclMinutes); if (lastLocalMinutes != lclMinutes) { sprintf(buf, "%02u", lclMinutes); ili9341.setCursor(123, 195); ili9341.print(buf); lastLocalMinutes = lclMinutes; } yield(); if (lastLocalHours != lclHours) { sprintf(buf, "%02u", lclHours); ili9341.setCursor(15, 195); ili9341.print(buf); lastLocalHours = lclHours; } ili9341.setTextSize(2); if ((lastDay != gpsInfo.utcDay) || (gpsInfo.utcYear < 1000)) { if (gpsInfo.utcYear < 1000) sprintf(buf, "0000-%02u-%02u", gpsInfo.utcMonth, gpsInfo.utcDay); else sprintf(buf, "%4u-%02u-%02u", gpsInfo.utcYear, gpsInfo.utcMonth, gpsInfo.utcDay); ili9341.setCursor(200, 90); ili9341.print(buf); lastDay = gpsInfo.utcDay; } yield(); if ((lastLocalDay != lclDay) || (lclYear < 1000)) { if (lclYear < 1000) sprintf(buf, "0000-%02u-%02u", lclMonth, lclDay); else sprintf(buf, "%4u-%02u-%02u", lclYear, lclMonth, lclDay); ili9341.setCursor(200, 170); ili9341.print(buf); lastLocalDay = lclDay; } ili9341.setTextSize(3); if (lastGPSValid != gpsInfo.valid) { ili9341.setCursor(20, 55); if (gpsInfo.valid) ili9341.print("Valid "); else ili9341.print("Invalid"); lastGPSValid = gpsInfo.valid; } yield(); if (lastSatellites != gpsInfo.satellites) { sprintf(buf, "%2d", gpsInfo.satellites); ili9341.setCursor(184, 55); ili9341.print(buf); lastSatellites = gpsInfo.satellites; } if (lastHDOP != gpsInfo.hdop) { ili9341.setCursor(245, 55); if (gpsInfo.hdop > 9.9) ili9341.print("9.9"); else ili9341.print(gpsInfo.hdop, 1); lastHDOP = gpsInfo.hdop; } } else if (displayMode == 6) { char buf[15]; // Update the seconds for both UTC and local ili9488.setTextSize(9); if (lastSeconds != gpsInfo.utcSeconds) { if (gpsInfo.utcSeconds % 10 == 0) { ili9488.setCursor(354, 135); sprintf(buf, "%02u", gpsInfo.utcSeconds); ili9488.print(buf); ili9488.setCursor(354, 255); ili9488.print(buf); } else { ili9488.setCursor(408, 135); ili9488.print(gpsInfo.utcSeconds % 10); ili9488.setCursor(408, 255); ili9488.print(gpsInfo.utcSeconds % 10); } lastSeconds = gpsInfo.utcSeconds; } // Update the UTC if (lastMinutes != gpsInfo.utcMinutes) { sprintf(buf, "%02u", gpsInfo.utcMinutes); ili9488.setCursor(192, 135); ili9488.print(buf); lastMinutes = gpsInfo.utcMinutes; } yield(); if (lastHours != gpsInfo.utcHours) { sprintf(buf, "%02u", gpsInfo.utcHours); ili9488.setCursor(30, 135); ili9488.print(buf); lastHours = gpsInfo.utcHours; } // Update the local time int lclYear, lclMonth, lclDay, lclHours, lclMinutes; gps.getLocalTime(lclYear, lclMonth, lclDay, lclHours, lclMinutes); if (lastLocalMinutes != lclMinutes) { sprintf(buf, "%02u", lclMinutes); ili9488.setCursor(192, 255); ili9488.print(buf); lastLocalMinutes = lclMinutes; } yield(); if (lastLocalHours != lclHours) { sprintf(buf, "%02u", lclHours); ili9488.setCursor(30, 255); ili9488.print(buf); lastLocalHours = lclHours; } ili9488.setTextSize(3); if ((lastDay != gpsInfo.utcDay) || (gpsInfo.utcYear < 1000)) { if (gpsInfo.utcYear < 1000) sprintf(buf, "0000-%02u-%02u", gpsInfo.utcMonth, gpsInfo.utcDay); else sprintf(buf, "%4u-%02u-%02u", gpsInfo.utcYear, gpsInfo.utcMonth, gpsInfo.utcDay); ili9488.setCursor(300, 95); ili9488.print(buf); lastDay = gpsInfo.utcDay; } yield(); if ((lastLocalDay != lclDay) || (lclYear < 1000)) { if (lclYear < 1000) sprintf(buf, "0000-%02u-%02u", lclMonth, lclDay); else sprintf(buf, "%4u-%02u-%02u", lclYear, lclMonth, lclDay); ili9488.setCursor(300, 215); ili9488.print(buf); lastLocalDay = lclDay; } if (lastGPSValid != gpsInfo.valid) { ili9488.setCursor(20, 60); if (gpsInfo.valid) ili9488.print("Valid "); else ili9488.print("Invalid"); lastGPSValid = gpsInfo.valid; } yield(); if (lastSatellites != gpsInfo.satellites) { sprintf(buf, "%2d", gpsInfo.satellites); ili9488.setCursor(148, 60); ili9488.print(buf); lastSatellites = gpsInfo.satellites; } if (lastHDOP != gpsInfo.hdop) { ili9488.setCursor(202, 60); if (gpsInfo.hdop > 9.9) ili9488.print("9.9"); else ili9488.print(gpsInfo.hdop, 1); lastHDOP = gpsInfo.hdop; } } } |
The other programs
Like the GPSDO program the beacon, transmitter (WSPR transmitter) and service (Multi LO and QO-100 ) programs only have the same nine files, i.e. .ino, commands.cpp/~h, config.cpp/~h, display.cpp/~h and global.cpp/~h. But the continuous user interaction programs (signal generator and VFO) have many other pairs of .cpp/.h files because they have to service rotary encoders, push buttons, keypad, calculate atenuator bits, set filters etc. The code for these peripheral devices and functions put in their own file pairs.