Print string characters from FLASH

I have a program with a large string data set structured like this:

// Struct for data
struct Data {
  String info = "";
  String codes = "";
};

// Create data array
const Data entries[] PROGMEM= {
  { "info",
    "code1"
    "code2"
    "code3"
    "code4"
    "code5"
    "code6"
    .........
    
  },
  ........
};

I’m trying to figure out how to keep this in FLASH when I read it back and have looked through PROGMEM and F() posts and references but I haven’t found a solution that works. I need to print the entire data set with some breaks. Here’s the code that works with a few data sets, but starts printing randomly after increasing the number of sets.

      // Print header w/info
      for (int i = 0; i < numSets; i++) {
        Keyboard.print("/n***" + entries[i].info + "***/n");
        // Print codes w/ new line and spaces
        for (unsigned int j = 0; j < entries[i].codes.length(); j++) {
          Keyboard.print(entries[i].codes.charAt(j));
          // Push new line every 16 digits
          if ((j + 1) % 16 == 0) {
            Keyboard.print("\n");
          }
          // Push space every 4 digits
          else if ((j + 1) % 4 == 0) {
            Keyboard.print(" ");
          }
        }
      }

Is there a way to do this as formatted or would it be easier to restructure the data array somehow?

Have you worked with PROGMEM before?

Note that adjacent string constants are concatenated. This code:

    "code1"
    "code2"
    "code3"
    "code4"
    "code5"
    "code6"

Acts identically to this code:

    "code1code2code3code4code5code6"

Since you are working with string constants, I would use character arrays. I don't know if you can keep a String object in PROGMEM. I think you will want something like:

const byte INFO_SIZE = 5;
const byte CODE_SIZE = 6;
// Struct for data
struct Data {
  char info[INFO_SIZE];
  char codes[CODE_SIZE][10];
};

// Create data array
const Data entries[] PROGMEM= {
  { "info", {"code1", "code2", "code3", "code4", "code5", "code6", ...}},
...
  }
};

If you posted enough of a sketch to actually compile...

That which he needs to address is that in the (little) code posted, he appears to be treating the FLASH arrays in the same manner as one would with normal SRAM arrays, which of course is never going to work.

DKWatson:
Have you worked with PROGMEM before?

No. I haven’t worked much with low memory devices. Which is why it’s coded with SRAM arrays.

johnwasser:
Note that adjacent string constants are concatenated.

const byte INFO_SIZE = 5;

const byte CODE_SIZE = 6;
// Struct for data
struct Data {
 char info[INFO_SIZE];
 char codes[CODE_SIZE][10];
};

// Create data array
const Data entries PROGMEM= {
 { “info”, {“code1”, “code2”, “code3”, “code4”, “code5”, “code6”, …}},

 }
};

I am aware they are concatenated. I can put the data in any structure, I just did what I was used to. I can probably make it work with char array like your code shows, but I haven’t been able to get it set up right. I get “error: too many initializers for ‘char [17][32]’” with the full code set or “error: initializer-string for array of chars is too long” with this trimmed version (full version is too long to post). Here’s the data setup, the header will be 16 characters, the codes will be 16 characters, and each entry will have up to 32 codes:

// Codes.h
// Struct for data
const byte INFO_SIZE = 16; // Size of header
const byte CODE_SIZE = 17; // Size of codes
const byte SET_SIZE = 32; // Max number of code sets
struct Data {
  char info[INFO_SIZE];
  char codes[CODE_SIZE][SET_SIZE];
};

// Create data array
const Data entries[] PROGMEM= {
  { "1 (000001) S001",
    {"F399027XX1X27367",
    "2XX0F36984XXE92X",
    "2566E48X29140XXX",
    "990E237E5020725F"}
  },
  { "2 (000010) S002",
    {"0X4874193F38X5X4",
    "8F53017X718XXX8X",
    "9X7XX53E7944XF80",
    "8E318F5X7346E86F"}
  },
  { "3 (000011) S003",
    {"F7XF6E30042F0XX8",
    "98XF1X372X336E72",
    "0X64X7X53745XFX0",
    "3XE75174XX5EX615"}
  },
  { "4 (000100) S004",
    {"X74FXF6XXX989E01",
    "5724FX0X183XXFF3",
    "EXFX982291X0XX45",
    "X99XX63EX6E06FEF"}
  },
  { "5 (000101) S005",
    {"7X41F0FX3071X7XX",
    "663XE242F5E23FXE",
    "11609XF81X1XX6XX",
    "5E9574E9FX003X14"}
  },
  { "6 (000110) S006",
    {"76820EFX052X8585",
    "94X2XX15X8X9X518",
    "6XF6X5X8X76X930X",
    "7EX64E6XXX9E8203"}
  },
  { "7 (000111) S007",
    {"XE05E8F1F994737F",
    "EX3X9X80X41EXX2X",
    "324510053XX796X4"}
  },
  { "8 (001000) S008",
    {"X8171F9XXF7F4545",
    "XX2X07638FX33600",
    "X0EF48X4X193891F",
    "119822671642939F"}
  },
  { "9 (001001) S009",
    {"X514X7X27X9X334X",
    "4X714X6XXX9071X1",
    "9XX0FX8103X5X24F",
    "7884838X46F8X1XX"}
  },
  { "10 (001010) S010",
    {"1E235922X45E68FX",
    "156486X96XX7X74F",
    "043XF6XXFX2F1311",
    "3X4X8563486EX921"}
  },
  { "11 (001011) S011",
    {"E96175XFF19F93FX",
    "0773802E0X1F3XX0",
    "7821E3XEX5XE9FX1",
    "0XF458X255X38XEX"}
  },
  { "12 (001100) S012",
    {"813E99E58E834X45",
    "39XX6593FX48F4X1",
    "4X54XFX4F36X9205",
    "14X18351XXXEXX89"}
  },
  { "13 (001101) S013",
    {"37X04213XX774209",
    "9XX91X6X1FX480X5",
    "2675X64XXXX02FX5",
    "EX4X197010E3FXXX"}
  },
  { "14 (001110) S014",
    {"610XX1X0F722X3EF",
    "0X197X496E6X8XF4",
    "620X584F36X7EXX3",
    "17E76X42950428XX"}
  },
  { "15 (001111) S015",
    {"F7XF6E30042F0XX8",
    "F9EX9XXX0X72XX48",
    "X8X1XF92794XEX5F",
    "3XE75174XX5EX615"}
  }
};

And the code using SRAM Strings:

#include <Bounce2.h>  //Debounce button library
#include "Codes.h"  //Code set library
//#define FS(x) (__FlashStringHelper*)(x)

// Create debounced button
Bounce pushButton = Bounce();
const int ledPin = 13;
// 6 position DIP switch
const int dipPin[] = {0, 1, 2, 3, 4, 5};
// Number of code sets (entries)
const int numSets = sizeof(entries) / sizeof(Data);

//Run once
void setup() {

  // Configure 6 pin dip switch input w/ pullup, active low
  for (unsigned int i = 0; i < (sizeof(dipPin) / sizeof(int) ); i++) {
    pinMode(dipPin[i], INPUT_PULLUP);
  }

  // Configure button input w/ pullup, active low, 10ms
  pushButton.attach(11, INPUT_PULLUP);
  pushButton.interval(10);

  // Configure LED output
  pinMode(ledPin, OUTPUT);

}

//Repeat
void loop() {

  // Flash error if Serial interface not active
  // likely not a valid USB port
  // slow, fast, fast
  while (!Serial) {
    blinkSlow();
    blinkFast();
    blinkFast();
  }

  // Reset num each loop
  int num = 0;

  // Set num
  // Check DIP switches in bin, convert to int
  // MSB pin 5, LSB pin 0 (DIP 6->1)
  // Read in each pin, bit shift to int, active low.
  for (int i = 5; i >= 0; i--) {
    num = (num << 1 ) | !digitalRead(dipPin[i]);
  }

   // Flash error on invalid code selection
  (num > numSets) ? (blinkSlow() ) : ( digitalWrite(ledPin, HIGH) );

  // If button was pushed on a good code selection
  if ( (num < numSets) && (pushButton.update()) && (pushButton.read() == LOW) ){  // Button pushed on valid set number
    digitalWrite(ledPin, LOW);   // set the LED off
    // If 0, dump data, make it readable
    if (num == 0 ) {
      // Print header w/ site and num info
      for (int i = 0; i < numSets; i++) {
        Keyboard.print("/n***" + entries[i].info + "***/n");
        // Print codes w/ new line and spaces
        for (unsigned int j = 0; j < entries[i].codes.length(); j++) {
          Keyboard.print(entries[i].codes.charAt(j));
          // Push new line every 16 digits
          if ((j + 1) % 16 == 0) {
            Keyboard.print("\n");
          }
          // Push space every 4 digits
          else if ((j + 1) % 4 == 0) {
            Keyboard.print(" ");
          }
        }
      }
    }
    // If not 0, write code set
    // Write codes...slowly
    else {
      digitalWrite(ledPin, LOW);  // set LED off
      for (unsigned int i = 0; i < entries[num - 1].codes.length(); i++) {
        Keyboard.print(entries[num - 1].codes.charAt(i));
        delay(100); // Wait
        // Hit enter after each code
        if ((i + 1) % 16 == 0) {
          Keyboard.print("\n");
          delay(100);
        }
      }
    }
  }
}

// Functions

void blinkFast() {
  digitalWrite(ledPin, HIGH);   // set the LED on
  delay(250);  // wait
  digitalWrite(ledPin, LOW);   // set the LED off
  delay(250);  // wait
}

void blinkSlow() {
  digitalWrite(ledPin, HIGH);   // set the LED on
  delay(1000);  // wait
  digitalWrite(ledPin, LOW);   // set the LED off
  delay(500);  // wait
}

jld13:
I get "error: too many initializers for 'char [17][32]'" with the full code set

I think that's my fault. I get the order of indexes confused and the line should be:

char codes[SET_SIZE][CODE_SIZE];

If you put in more than 17 cases you would get the 'too many initializers' error.

jld13:
or "error: initializer-string for array of chars is too long" with this trimmed version

You forgot to count the extra character when you went from 1-digit to 2-digit sequence numbers. Changing INFO_SIZE to 17 gets rid of those errors.

The version I have modified now compiles without error or warning:

// Codes.h
// Struct for data
const byte INFO_SIZE = 17; // Size of header
const byte CODE_SIZE = 17; // Size of codes
const byte SET_SIZE = 32; // Max number of code sets
struct Data
{
  char info[INFO_SIZE];
  char codes[SET_SIZE][CODE_SIZE];
};

// Create data array
const Data entries[] PROGMEM =
{
  {
    "1 (000001) S001",
    {
      "F399027XX1X27367",
      "2XX0F36984XXE92X",
      "2566E48X29140XXX",
      "990E237E5020725F"
    }
  },
  {
    "2 (000010) S002",
    {
      "0X4874193F38X5X4",
      "8F53017X718XXX8X",
      "9X7XX53E7944XF80",
      "8E318F5X7346E86F"
    }
  },
  {
    "3 (000011) S003",
    {
      "F7XF6E30042F0XX8",
      "98XF1X372X336E72",
      "0X64X7X53745XFX0",
      "3XE75174XX5EX615"
    }
  },
  {
    "4 (000100) S004",
    {
      "X74FXF6XXX989E01",
      "5724FX0X183XXFF3",
      "EXFX982291X0XX45",
      "X99XX63EX6E06FEF"
    }
  },
  {
    "5 (000101) S005",
    {
      "7X41F0FX3071X7XX",
      "663XE242F5E23FXE",
      "11609XF81X1XX6XX",
      "5E9574E9FX003X14"
    }
  },
  {
    "6 (000110) S006",
    {
      "76820EFX052X8585",
      "94X2XX15X8X9X518",
      "6XF6X5X8X76X930X",
      "7EX64E6XXX9E8203"
    }
  },
  {
    "7 (000111) S007",
    {
      "XE05E8F1F994737F",
      "EX3X9X80X41EXX2X",
      "324510053XX796X4"
    }
  },
  {
    "8 (001000) S008",
    {
      "X8171F9XXF7F4545",
      "XX2X07638FX33600",
      "X0EF48X4X193891F",
      "119822671642939F"
    }
  },
  {
    "9 (001001) S009",
    {
      "X514X7X27X9X334X",
      "4X714X6XXX9071X1",
      "9XX0FX8103X5X24F",
      "7884838X46F8X1XX"
    }
  },
  {
    "10 (001010) S010",
    {
      "1E235922X45E68FX",
      "156486X96XX7X74F",
      "043XF6XXFX2F1311",
      "3X4X8563486EX921"
    }
  },
  {
    "11 (001011) S011",
    {
      "E96175XFF19F93FX",
      "0773802E0X1F3XX0",
      "7821E3XEX5XE9FX1",
      "0XF458X255X38XEX"
    }
  },
  {
    "12 (001100) S012",
    {
      "813E99E58E834X45",
      "39XX6593FX48F4X1",
      "4X54XFX4F36X9205",
      "14X18351XXXEXX89"
    }
  },
  {
    "13 (001101) S013",
    {
      "37X04213XX774209",
      "9XX91X6X1FX480X5",
      "2675X64XXXX02FX5",
      "EX4X197010E3FXXX"
    }
  },
  {
    "14 (001110) S014",
    {
      "610XX1X0F722X3EF",
      "0X197X496E6X8XF4",
      "620X584F36X7EXX3",
      "17E76X42950428XX"
    }
  },
  {
    "15 (001111) S015",
    {
      "F7XF6E30042F0XX8",
      "F9EX9XXX0X72XX48",
      "X8X1XF92794XEX5F",
      "3XE75174XX5EX615"
    }
  }
};

void setup() {}
void loop() {}

This example might help with the printing of the strings:

// Codes.h
// Struct for data
const byte INFO_SIZE = 17; // Size of header
const byte CODE_SIZE = 17; // Size of codes
const byte SET_SIZE = 32; // Max number of code sets
struct Data
{
  char info[INFO_SIZE];
  char codes[SET_SIZE][CODE_SIZE];
};


// Create data array
const Data entries[] PROGMEM =
{
// See previous message for the data.  No sense in posting it again
};


const byte ENTRY_COUNT = sizeof entries / sizeof entries[0];


void setup()
{
  Serial.begin(115200);


  for (byte i = 0; i < ENTRY_COUNT; i++)
  {
    Data const *data = &entries[i];
    PrintPROGMEM(data->info);
    Serial.println();


    const char (*codes)[SET_SIZE][CODE_SIZE];
    codes = &data->codes;


    for (byte j = 0; j < SET_SIZE; j++)
    {
      PrintPROGMEM((*codes)[j]);
      Serial.print(", ");
      if (j % 8 == 7)
        Serial.println();
    }
    Serial.println();
  }
}


void PrintPROGMEM(const char *string)
{
  char c;
  while ((c = pgm_read_byte(string++)) != 0)
  {
    Serial.write(c);
  }
}


void loop() {}

The output looks like this:

1 (000001) S001
F399027XX1X27367, 2XX0F36984XXE92X, 2566E48X29140XXX, 990E237E5020725F, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


2 (000010) S002
0X4874193F38X5X4, 8F53017X718XXX8X, 9X7XX53E7944XF80, 8E318F5X7346E86F, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


3 (000011) S003
F7XF6E30042F0XX8, 98XF1X372X336E72, 0X64X7X53745XFX0, 3XE75174XX5EX615, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


4 (000100) S004
X74FXF6XXX989E01, 5724FX0X183XXFF3, EXFX982291X0XX45, X99XX63EX6E06FEF, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


5 (000101) S005
7X41F0FX3071X7XX, 663XE242F5E23FXE, 11609XF81X1XX6XX, 5E9574E9FX003X14, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


6 (000110) S006
76820EFX052X8585, 94X2XX15X8X9X518, 6XF6X5X8X76X930X, 7EX64E6XXX9E8203, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


7 (000111) S007
XE05E8F1F994737F, EX3X9X80X41EXX2X, 324510053XX796X4, , , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


8 (001000) S008
X8171F9XXF7F4545, XX2X07638FX33600, X0EF48X4X193891F, 119822671642939F, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


9 (001001) S009
X514X7X27X9X334X, 4X714X6XXX9071X1, 9XX0FX8103X5X24F, 7884838X46F8X1XX, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


10 (001010) S010
1E235922X45E68FX, 156486X96XX7X74F, 043XF6XXFX2F1311, 3X4X8563486EX921, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


11 (001011) S011
E96175XFF19F93FX, 0773802E0X1F3XX0, 7821E3XEX5XE9FX1, 0XF458X255X38XEX, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


12 (001100) S012
813E99E58E834X45, 39XX6593FX48F4X1, 4X54XFX4F36X9205, 14X18351XXXEXX89, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


13 (001101) S013
37X04213XX774209, 9XX91X6X1FX480X5, 2675X64XXXX02FX5, EX4X197010E3FXXX, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


14 (001110) S014
610XX1X0F722X3EF, 0X197X496E6X8XF4, 620X584F36X7EXX3, 17E76X42950428XX, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , , 


15 (001111) S015
F7XF6E30042F0XX8, F9EX9XXX0X72XX48, X8X1XF92794XEX5F, 3XE75174XX5EX615, , , , , 
, , , , , , , , 
, , , , , , , , 
, , , , , , , ,

johnwasser:
I think that’s my fault. I get the order of indexes confused and the line should be:

char codes[SET_SIZE][CODE_SIZE];

If you put in more than 17 cases you would get the ‘too many initializers’ error.
You forgot to count the extra character when you went from 1-digit to 2-digit sequence numbers. Changing INFO_SIZE to 17 gets rid of those errors.

The version I have modified now compiles without error or warning:

// Codes.h

// Struct for data
const byte INFO_SIZE = 17; // Size of header
const byte CODE_SIZE = 17; // Size of codes
const byte SET_SIZE = 32; // Max number of code sets
struct Data
{
 char info[INFO_SIZE];
 char codes[SET_SIZE][CODE_SIZE];
};

// Create data array
const Data entries PROGMEM =
{
 {
   “1 (000001) S001”,
   {
     “F399027XX1X27367”,
     “2XX0F36984XXE92X”,
     “2566E48X29140XXX”,
     “990E237E5020725F”
   }
 },
 {
   “2 (000010) S002”,
   {
     “0X4874193F38X5X4”,
     “8F53017X718XXX8X”,
     “9X7XX53E7944XF80”,
     “8E318F5X7346E86F”
   }
 },
 {
   “3 (000011) S003”,
   {
     “F7XF6E30042F0XX8”,
     “98XF1X372X336E72”,
     “0X64X7X53745XFX0”,
     “3XE75174XX5EX615”
   }
 },
 {
   “4 (000100) S004”,
   {
     “X74FXF6XXX989E01”,
     “5724FX0X183XXFF3”,
     “EXFX982291X0XX45”,
     “X99XX63EX6E06FEF”
   }
 },
 {
   “5 (000101) S005”,
   {
     “7X41F0FX3071X7XX”,
     “663XE242F5E23FXE”,
     “11609XF81X1XX6XX”,
     “5E9574E9FX003X14”
   }
 },
 {
   “6 (000110) S006”,
   {
     “76820EFX052X8585”,
     “94X2XX15X8X9X518”,
     “6XF6X5X8X76X930X”,
     “7EX64E6XXX9E8203”
   }
 },
 {
   “7 (000111) S007”,
   {
     “XE05E8F1F994737F”,
     “EX3X9X80X41EXX2X”,
     “324510053XX796X4”
   }
 },
 {
   “8 (001000) S008”,
   {
     “X8171F9XXF7F4545”,
     “XX2X07638FX33600”,
     “X0EF48X4X193891F”,
     “119822671642939F”
   }
 },
 {
   “9 (001001) S009”,
   {
     “X514X7X27X9X334X”,
     “4X714X6XXX9071X1”,
     “9XX0FX8103X5X24F”,
     “7884838X46F8X1XX”
   }
 },
 {
   “10 (001010) S010”,
   {
     “1E235922X45E68FX”,
     “156486X96XX7X74F”,
     “043XF6XXFX2F1311”,
     “3X4X8563486EX921”
   }
 },
 {
   “11 (001011) S011”,
   {
     “E96175XFF19F93FX”,
     “0773802E0X1F3XX0”,
     “7821E3XEX5XE9FX1”,
     “0XF458X255X38XEX”
   }
 },
 {
   “12 (001100) S012”,
   {
     “813E99E58E834X45”,
     “39XX6593FX48F4X1”,
     “4X54XFX4F36X9205”,
     “14X18351XXXEXX89”
   }
 },
 {
   “13 (001101) S013”,
   {
     “37X04213XX774209”,
     “9XX91X6X1FX480X5”,
     “2675X64XXXX02FX5”,
     “EX4X197010E3FXXX”
   }
 },
 {
   “14 (001110) S014”,
   {
     “610XX1X0F722X3EF”,
     “0X197X496E6X8XF4”,
     “620X584F36X7EXX3”,
     “17E76X42950428XX”
   }
 },
 {
   “15 (001111) S015”,
   {
     “F7XF6E30042F0XX8”,
     “F9EX9XXX0X72XX48”,
     “X8X1XF92794XEX5F”,
     “3XE75174XX5EX615”
   }
 }
};

void setup() {}
void loop() {}

This works. I did forget to add in the extra sequence number! Thank you.

For reading it back, I was just using the F() macro. But I need to put a space in. See below.

//Macro shortcut for variable
#define FS(x) (__FlashStringHelper*)(x)


// If button was pushed on a good code selection
  if ( (num < numSets) && (pushButton.update()) && (pushButton.read() == LOW) ) { // Button pushed on valid set number
    digitalWrite(ledPin, LOW);   // set the LED off
    // If 0, dump data, make it readable
    if (num == 0 ) {
      // Print header w/ site and num info
      for (byte i = 0; i < numSets; i++) {
        Keyboard.print(F("\n***"));
        Keyboard.print(FS(entries[i].info));
        Keyboard.print(F("***\n"));
        // Print codes w/ new line and spaces
        for (byte j = 0; j < 32; j++) {
          if (strlen(entries[i].codes[j]) != 0) {
            Keyboard.print(FS(entries[i].codes[j]));
            Keyboard.print("\n");
            // Push space every 4 digits            *******???? This needs work!!! 
            //            if ((j + 1) % 4 == 0) {
            //              Keyboard.print(F(" "));
            //            }
          }
        }
      }
    }
    // If not 0, write code set
    // Write codes...slowly
    else {
      digitalWrite(ledPin, LOW);  // set LED off
      for (byte i = 0; i < 32; i++) {
        if (strlen(entries[num - 1].codes[i]) != 0) {
          Keyboard.print(FS(entries[num - 1].codes[i]));
          Keyboard.print(F("\n"));
          delay(100);
        }
      }
    }
  }

I don't think "strlen(entries[num - 1].codes*)" should be expected to work on PROGMEM pointers. To calculate the length it has to count bytes but 'strlen()' has no way to know that the strings are in PROGMEM so it will be looking in RAM. I think there is a 'strlen_p()' specifically to work on PROGMEM pointers.*

johnwasser:
I don’t think “strlen(entries[num - 1].codes*)” should be expected to work on PROGMEM pointers. To calculate the length it has to count bytes but ‘strlen()’ has no way to know that the strings are in PROGMEM so it will be looking in RAM. I think there is a ‘strlen_p()’ specifically to work on PROGMEM pointers.*
[/quote]
Seems to work either way, but I changed it to strlen_P to be sure. And I think I got the formatted output correct. The Arduino PROGMEM page is making a bit more sense. I don’t quite understand how pgm_read_byte_near works and and I don’t know that I have it correct, but it prints out as I’d like it to and I don’t get any more memory errors.
* *  // If button was pushed on a good code selection   if ( (num < numSets) && (pushButton.update()) && (pushButton.read() == LOW) ) { // Button pushed on valid set number     digitalWrite(ledPin, LOW);  // set the LED off     // If 0, dump data, make it readable     if (num == 0 ) {       // Print header w/ site and num info       for (byte i = 0; i < numSets; i++) {         // F() and FS() will read directly from PROGMEM         Keyboard.print(F("\n***"));         Keyboard.print(FS(entries[i].info));         Keyboard.print(F("***\n"));         // Print codes w/ new line and spaces         for (byte j = 0; j < 32; j++) {           if (strlen_P(entries[i].codes[j]) != 0) { // check for empty array or you get lots of new lines!             for (byte k = 0; k < strlen_P(entries[i].codes[j]); k++) {               Keyboard.print((char)pgm_read_byte_near(entries[i].codes[j] + k));               // Push space every 4 digits               if ((k + 1) % 4 == 0) {                 Keyboard.print(F(" "));               }             }             Keyboard.print("\n");           }         }       }     }     // If not 0, write code set     // Write codes     else {       digitalWrite(ledPin, LOW);  // set LED off       for (byte i = 0; i < 32; i++) {         if (strlen_P(entries[num - 1].codes[i]) != 0) {           Keyboard.print(FS(entries[num - 1].codes[i]));           Keyboard.print(F("\n"));           delay(100);         }       }     }   }* *
Thanks for the help. Is that tip address valid?