Unable to read array item in a for loop with local variable "i"

Hi All,
I am new to Arduino. And I am learning to do some basic stuff at the moment.
I have gone through many forums already and haven't been able to still understand where the problem lies.

Setup: I am using Arduino Uno and have attached an OLED screen to it and an APDS9960 sensor
Objective: To show a list of items on the screen one after the other

This is the code:

const char *checkList[] = {"Item 1","Item 2","Item 3","Item 4","Item 5","Item 6","Item 7","Item 8"}; 
 
void setup() {
    // I have some code here that displays some messages on the screen- success or failure depending on the sensor
  }

void loop() {
    // Read the checkList  
    for (int i = 0; i < 8; i++)   
    {  
       Serial.println(F("In FOR LOOP !!!!!!!!!!"));  
       display.clearDisplay();
       display.setTextSize(1);             
       display.setTextColor(SSD1306_WHITE);        
       display.setCursor(0,0);            
       display.println( checkList[i] );
       display.display();
       delay(1000);  
     }    
}

Here, display.println( checkList[i] ) is causing problem. No error is given but the program doesn't do anything and it shows me a "Failure" msg on my screen that is coded in my Setup()

I am sure that this line is a problem because If I comment this line, it works.
Also, if I change this line to display.println( checkList[0] ) or display.println( checkList[1] ) with explicit index value- it works. But doesn't work with the variable "i".

I thought this is a pretty simple, basic line of code however still have no clue what is creating the problem. I tried reading up a lot on the internet too, saw some examples for the for loop as well, but still haven't understood where the problem lies.

Would appreciate any help or leads in order for me to understand what's going on better.

Thanks a lot!

➜ so we need to see the setup... why do you think the for loop is the issue?

post the full code

It has some code related to the sensor as well. Should I still post that as well?

yes, post everything and a description of what you built

(please read How to get the best out of this forum for more details).

Okay. Let me share my entire code. It's all still in the experimenting and learning phase. Just so that you know. Thanks a lot for your help!

Actually I did read that forum. But thought may be the sensor code is not required and hence removed it to just focus on the OLED screen.
But as you suggest, I will share the entire code. Thanks once again!

don't worry about that, we all started knowing nothing about C++ and micro-controllers.

If for some reason you don't want to share all of your code, it's fine to post a complete, condensed sketch that demonstrates the problem.

No, sharing the code is not a problem for me.
Here it is:

#include <SPI.h>
#include <Wire.h>
#include <SparkFun_APDS9960.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define APDS9960_INT    2 // Needs to be an interrupt pin

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library. 
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3D ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Global Variables
SparkFun_APDS9960 apds = SparkFun_APDS9960();
int isr_flag = 0;
const char *checkList[] = {"Item 1","Item 2","Item 3","Item 4","Item 5","Item 6","Item 7","Item 8"};  

void setup() {
  Serial.begin(9600);
  Serial.println(F("In setup()"));
  Serial.println();
  Serial.println(F("--------------------------------"));
  Serial.println(F("APDS-9960 - GestureTest"));
  Serial.println(F("--------------------------------"));

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

   // Initialize APDS-9960 (configure I2C and initial values)
  if ( apds.init() ) {
    Serial.println(F("APDS-9960 initialization complete"));
    display.clearDisplay();
    display.setCursor(0,0);  
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.println("ADPS Initialization");
    display.println("");
    display.setTextSize(2);
    display.println("Success! ");
    display.display();
  } else {
    Serial.println(F("Something went wrong during APDS-9960 init!"));
    display.clearDisplay();
    display.setCursor(0,0);  
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.println("ADPS Initialization");
    display.println("");
    display.setTextSize(2);
    display.println("Failed! ");
    display.display();
  }
 
  // Start running the APDS-9960 gesture sensor engine
  if ( apds.enableGestureSensor(true) ) {
    Serial.println(F("Gesture sensor is now running"));
    display.clearDisplay();
    display.setCursor(0,0);  
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.println("ADPS Initialization");
    display.println("");
    display.setTextSize(2);
    display.println("Success! ");
    display.display();
  } else {
    Serial.println(F("Something went wrong during gesture sensor init!"));
    display.clearDisplay();
    display.setCursor(0,0);  
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.println("ADPS Initialization");
    display.println("");
    display.setTextSize(2);
    display.println("Failed! ");
    display.display();
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  // Draw a single pixel in white
  display.drawPixel(10, 10, SSD1306_WHITE);

  // Show the display buffer on the screen. You MUST call display() after
  // drawing commands to make them visible on screen!
  display.display();
  delay(2000);

  // Invert and restore display, pausing in-between
  Serial.println(F("calling invertDisplay(true))"));
  display.invertDisplay(true);
  delay(1000);
  Serial.println(F("calling invertDisplay(false))"));
  display.invertDisplay(false);
  delay(1000);

 }

void loop() {
  Serial.println(F("In loop()"));
  isr_flag = 1; // Hard-coding it for now. Ideally isr_flag should be set to 1 by the interrupt routine. But it's not happening. TODO: To be fixed.
   if( isr_flag == 1 ) {
    Serial.println(F("In loop with isr_flag==1"));
    detachInterrupt(0);
    // Read the checkList  
    for (int i = 0; i < 8; i++)   
    {  
      Serial.println(F("In FOR LOOP !!!!!!!!!!"));  
      display.clearDisplay();
      display.setTextSize(1);             
      display.setTextColor(SSD1306_WHITE);        
      display.setCursor(0,0);            
      display.println(checkList[i]);
      display.display();
      
      delay(1000);  
    }  
    // TODO: Combine the below code related to the gestures with the every item displayed on the screen. Firt get the items displayed on the screen working
      handleGesture();
      isr_flag = 0;
      attachInterrupt(0, interruptRoutine, FALLING);      
  }
}

void interruptRoutine() {
  Serial.println(F("In interruptRoutine()- setting isr_flag to 1"));
  isr_flag = 1;
}

void handleGesture() {
  Serial.println(F("In handleGesture()"));
    if ( apds.isGestureAvailable() ) {
    switch ( apds.readGesture() ) {
      case DIR_UP:
        Serial.println("UP");
        display.clearDisplay();
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,0);             // Start at top-left corner
        display.println(F("UP"));
        display.display();   
        break;
      case DIR_DOWN:
        Serial.println("DOWN");
        display.clearDisplay();
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,0);             // Start at top-left corner
        display.println(F("DOWN"));
        display.display();   
        break;
      case DIR_LEFT:
        Serial.println("LEFT");
        display.clearDisplay();
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,0);             // Start at top-left corner
        display.println(F("<- LEFT"));
        display.display();   
        break;
      case DIR_RIGHT:
        Serial.println("RIGHT");
        display.clearDisplay();
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,0);             // Start at top-left corner
        display.println(F("RIGHT ->"));
        display.display();        
        break;
      case DIR_NEAR:
        Serial.println("NEAR");
        display.clearDisplay();
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,0);             // Start at top-left corner
        display.println(F("NEAR"));
        display.display();   
        break;
      case DIR_FAR:
        Serial.println("FAR");
        display.clearDisplay();
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0,0);             // Start at top-left corner
        display.println(F("FAR"));
        display.display();   
        break;
      default:
        Serial.println("NONE");
    }
  }
}

In the above code, when I have the line as
display.println(checkList[0]);
It shows me "Item 1" on my OLED screen.

And when I have it as
display.println(checkList[i]);
It shows me the "Failure" message. See the attached image

image

Hope this helps.
Thanks!

const char *checkList[] = {"Item 1","Item 2","Item 3","Item 4","Item 5","Item 6","Item 7","Item 8"}; 
 
void setup() {
    Serial.begin(115200);
  }

void loop() {
    // Read the checkList  
    for (int i = 0; i < 8; i++)   
    {  
       Serial.println(F("In FOR LOOP !!!!!!!!!!"));  
       Serial.println( checkList[i] );
       delay(1000);  
     }    
}

this works

Have you tried running the APDS9960 library example sketches? Anyway, it appears to me that maybe the APDS9960 driver is using too much RAM.

Referring to reply #10, that will make no difference in memory usage (if it's even correct). The proper thing is to use PROGMEM for arrays like that.

But it's not a large array, and you've used the F macro for text strings which is excellent.

Serial printing in an interrupt context is forbidden.

When you ran the SSD1306 did the example work?

Because this,

is why your display is not showing a thing do as per post#9 screen shot of the SSD1306 driver failing to do the thing.

Looks like a RAM issue indeed, 128x64 sounds much too big for an Uno and the error message confirms it.

when you call this

you execute this malloc()

if ((!buffer) && !(buffer = (uint8_t *)malloc(WIDTH * ((HEIGHT + 7) / 8))))

you screen size is

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

so you are asking for 128*((64+7)/8) = 1136 bytes of SRAM
when you compile on a UNO, you need also 819 bytes of SRAM

So that's a total of 1955 bytes when you have 2048 bytes in total... that's not enough for the stack and all the other dynamic needs

if you change all your display.println("xxx"); into display.println(F("xxx")); and the naked display.println(""); into display.println(); then you'll save about 40 bytes of RAM... that's enough to accommodate your 16 pointers of checkList and the content in SRAM and it should compile, but it will likely be risky as you are still very close of memory saturation.

I would move the items if Flash too to gain a bit more memory and go down to 707 instead of 819

the code would look like this

#include <SPI.h>
#include <Wire.h>
#include <SparkFun_APDS9960.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define APDS9960_INT    2 // Needs to be an interrupt pin

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library.
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3D ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Global Variables
SparkFun_APDS9960 apds = SparkFun_APDS9960();
int isr_flag = 0;

struct t_Item {
  char label[10];
};

static const t_Item checkList[]  PROGMEM = {
  {"Item 1"}, {"Item 2"}, {"Item 3"}, {"Item 4"}, {"Item 5"}, {"Item 6"}, {"Item 7"}, {"Item 8"}
};

void setup() {
  Serial.begin(9600);
  Serial.println(F("In setup()"));
  Serial.println();
  Serial.println(F("--------------------------------"));
  Serial.println(F("APDS-9960 - GestureTest"));
  Serial.println(F("--------------------------------"));

  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Don't proceed, loop forever
  }

  // Initialize APDS-9960 (configure I2C and initial values)
  if ( apds.init() ) {
    Serial.println(F("APDS-9960 initialization complete"));
    display.clearDisplay();
    display.setCursor(0, 0);
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.println(F("ADPS Initialization"));
    display.println();
    display.setTextSize(2);
    display.println(F("Success! "));
    display.display();
  } else {
    Serial.println(F("Something went wrong during APDS-9960 init!"));
    display.clearDisplay();
    display.setCursor(0, 0);
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.println(F("ADPS Initialization"));
    display.println();
    display.setTextSize(2);
    display.println(F("Failed! "));
    display.display();
  }

  // Start running the APDS-9960 gesture sensor engine
  if ( apds.enableGestureSensor(true) ) {
    Serial.println(F("Gesture sensor is now running"));
    display.clearDisplay();
    display.setCursor(0, 0);
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.println(F("ADPS Initialization"));
    display.println();
    display.setTextSize(2);
    display.println(F("Success! "));
    display.display();
  } else {
    Serial.println(F("Something went wrong during gesture sensor init!"));
    display.clearDisplay();
    display.setCursor(0, 0);
    display.setTextSize(1);
    display.setTextColor(WHITE);
    display.println(F("ADPS Initialization"));
    display.println();
    display.setTextSize(2);
    display.println(F("Failed! "));
    display.display();
  }

  // Show initial display buffer contents on the screen --
  // the library initializes this with an Adafruit splash screen.
  display.display();
  delay(2000); // Pause for 2 seconds

  // Clear the buffer
  display.clearDisplay();

  // Draw a single pixel in white
  display.drawPixel(10, 10, SSD1306_WHITE);

  // Show the display buffer on the screen. You MUST call display() after
  // drawing commands to make them visible on screen!
  display.display();
  delay(2000);

  // Invert and restore display, pausing in-between
  Serial.println(F("calling invertDisplay(true))"));
  display.invertDisplay(true);
  delay(1000);
  Serial.println(F("calling invertDisplay(false))"));
  display.invertDisplay(false);
  delay(1000);

}

void loop() {
  Serial.println(F("In loop()"));
  isr_flag = 1; // Hard-coding it for now. Ideally isr_flag should be set to 1 by the interrupt routine. But it's not happening. TODO: To be fixed.
  if ( isr_flag == 1 ) {
    Serial.println(F("In loop with isr_flag==1"));
    detachInterrupt(0);
    // Read the checkList
    for (int i = 0; i < 8; i++)
    {
      Serial.println(F("In FOR LOOP !!!!!!!!!!"));
      display.clearDisplay();
      display.setTextSize(1);
      display.setTextColor(SSD1306_WHITE);
      display.setCursor(0, 0);
      display.println((__FlashStringHelper*) checkList[i].label);
      display.display();

      delay(1000);
    }
    // TODO: Combine the below code related to the gestures with the every item displayed on the screen. Firt get the items displayed on the screen working
    handleGesture();
    isr_flag = 0;
    attachInterrupt(0, interruptRoutine, FALLING);
  }
}

void interruptRoutine() {
  Serial.println(F("In interruptRoutine()- setting isr_flag to 1"));
  isr_flag = 1;
}

void handleGesture() {
  Serial.println(F("In handleGesture()"));
  if ( apds.isGestureAvailable() ) {
    switch ( apds.readGesture() ) {
      case DIR_UP:
        Serial.println("UP");
        display.clearDisplay();
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0, 0);            // Start at top-left corner
        display.println(F("UP"));
        display.display();
        break;
      case DIR_DOWN:
        Serial.println("DOWN");
        display.clearDisplay();
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0, 0);            // Start at top-left corner
        display.println(F("DOWN"));
        display.display();
        break;
      case DIR_LEFT:
        Serial.println("LEFT");
        display.clearDisplay();
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0, 0);            // Start at top-left corner
        display.println(F("<- LEFT"));
        display.display();
        break;
      case DIR_RIGHT:
        Serial.println("RIGHT");
        display.clearDisplay();
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0, 0);            // Start at top-left corner
        display.println(F("RIGHT ->"));
        display.display();
        break;
      case DIR_NEAR:
        Serial.println("NEAR");
        display.clearDisplay();
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0, 0);            // Start at top-left corner
        display.println(F("NEAR"));
        display.display();
        break;
      case DIR_FAR:
        Serial.println("FAR");
        display.clearDisplay();
        display.setTextSize(1);             // Normal 1:1 pixel scale
        display.setTextColor(SSD1306_WHITE);        // Draw white text
        display.setCursor(0, 0);            // Start at top-left corner
        display.println(F("FAR"));
        display.display();
        break;
      default:
        Serial.println("NONE");
    }
  }
}

you are still close to the memory limit (~200 bytes left) but it might be good enough.

in order to print the text, you would use (__FlashStringHelper*) checkList[i].label instead of just checkList[i]

1 Like

Thanks a lot! The moment J-M-L pointed out that the problem would not necessarily be in the for loop, I also quickly did a short code only for the for loop and it worked to confirm that the problem is not in the for loop.
Thanks a lot for your time and help

Oh!.. Thanks for pointing that out. I was not aware of that.
My flag was not getting set to 1 and hence I wanted to debug to see if the interruptroutine gets called or not. Will remove the serial printing.

Yes, example worked.
Problems seen only when I tried to integrate both the components - sensor and the OLED code together.

not really forbidden, just discouraged and a bad practice as you want to keep ISR as short as possible

If you are still short of memory, the U8g2 library will use a considerably smaller display buffer when using a page buffer. Unlike the Adafruit library, U8g2 allocates the buffer at compile-time, so you will see how much memory is used.