2

I have a OLED SSD1306 & DS3231 RTC, 2 buttons to set the time & date. My project is to display a scrolling text at the top of the OLED, the RTC time in the middle, the day & date on the last line. My issue is I can't get the scrolling text & the time to run right when in the loop together, individually they work normal. When they are together, the scrolling text is fine, but the time is static, its not displaying the current RTC. Then after 1 loop cycle the time seconds text become jumbled, then after a 2nd cycle the minutes & seconds become jumbled. I have tried putting display.clearDisplay(); display.display(); at the end of the loop, doing this fixes the time from becoming jumbled by clearing the buffer before the next cycle, but does not fix the time from being static. How can I get the time to not be static anymore? Any help would be greatly appreciated. Here is my sketch:

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

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

#define OLED_RESET 4 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

#define button1 9 // Button B1 is connected to Arduino pin 9 #define button2 8 // Button B2 is connected to Arduino pin 8

void setup(void) { pinMode(button1, INPUT_PULLUP); pinMode(button2, INPUT_PULLUP); delay(1000);

// by default, the high voltage from the 3.3v line display.begin(SSD1306_SWITCHCAPVCC, 0x3c); // initialize with the I2C address // init done

// Clear the display buffer. display.clearDisplay(); display.display();

display.setTextColor(WHITE, BLACK); }

char Time[] = " : : "; char Calendar[] = " / /20 "; byte i, second, minute, hour, day, month, date, year;

void display_day(){ switch(day){ case 1: draw_text(0, 50, " SUNDAY ", 1); break; //(Column,Row,Data to be displayed,Font size) case 2: draw_text(0, 50, " MONDAY ", 1); break; //(Column,Row,Data to be displayed,Font size) case 3: draw_text(0, 50, " TUESDAY ", 1); break; //(Column,Row,Data to be displayed,Font size) case 4: draw_text(0, 50, "WEDNESDAY", 1); break; //(Column,Row,Data to be displayed,Font size) case 5: draw_text(0, 50, "THURSDAY ", 1); break; //(Column,Row,Data to be displayed,Font size) case 6: draw_text(0, 50, " FRIDAY ", 1); break; //(Column,Row,Data to be displayed,Font size) default: draw_text(0, 50, "SATURDAY ", 1); //(Column,Row,Data to be displayed,Font size) } }

void DS3231_display(){ // Convert BCD to decimal second = (second >> 4) * 10 + (second & 0x0F); minute = (minute >> 4) * 10 + (minute & 0x0F); hour = (hour >> 4) * 10 + (hour & 0x0F); month = (month >> 4) * 10 + (month & 0x0F); date = (date >> 4) * 10 + (date & 0x0F); year = (year >> 4) * 10 + (year & 0x0F); // End conversion

Time[7] = second % 10 + 48; Time[6] = second / 10 + 48; Time[4] = minute % 10 + 48; Time[3] = minute / 10 + 48; Time[1] = hour % 10 + 48; Time[0] = hour / 10 + 48; Calendar[9] = year % 10 + 48; Calendar[8] = year / 10 + 48; Calendar[4] = date % 10 + 48; Calendar[3] = date / 10 + 48; Calendar[1] = month % 10 + 48; Calendar[0] = month / 10 + 48;

//Print text draw_text(60, 50, Calendar, 1); // Display the date (format: mm/dd/yyyy) draw_text(15, 25, Time, 2); // Display the time }

void blink_parameter(){ byte j = 0; while(j < 10 && digitalRead(button1) && digitalRead(button2)){ j++; delay(25); } }

byte edit(byte x_pos, byte y_pos, byte parameter){ char text[3]; sprintf(text,"%02u", parameter); while(!digitalRead(button1)); // Wait until button B1 released while(true){ while(!digitalRead(button2)){ // If button B2 is pressed parameter++; if(i == 0 && parameter > 12) // If month > 12 ==> month = 1 parameter = 1;
if(i == 1 && parameter > 31) // If date > 31 ==> date = 1 parameter = 1; if(i == 2 && parameter > 99) // If year > 99 ==> year = 0 parameter = 0; if(i == 3 && parameter > 23) // If hours > 23 ==> hours = 0 parameter = 0; if(i == 4 && parameter > 59) // If minutes > 59 ==> minutes = 0 parameter = 0; sprintf(text,"%02u", parameter); draw_text(x_pos, y_pos, text, 1); delay(200); // Wait 200ms } draw_text(x_pos, y_pos, " ", 1); blink_parameter(); draw_text(x_pos, y_pos, text, 1); blink_parameter(); if(!digitalRead(button1)){ // If button B1 is pressed i++; // Increment 'i' for the next parameter return parameter; // Return parameter value and exit } } }

void draw_text(byte x_pos, byte y_pos, char *text, byte text_size) { display.setCursor(x_pos, y_pos); display.setTextSize(text_size); display.print(text); display.display(); }

void scroll_text(){ draw_text(0, 0, "The Pumpkin Express", 1); display.display(); display.startscrollright(0x00, 0x00); delay(8500); display.stopscroll(); delay(4000); }

void loop() {

if(!digitalRead(button1)){ // If button B1 is pressed i = 0; while(!digitalRead(button1)); // Wait for button B1 release while(true){ while(!digitalRead(button2)){ // While button B2 pressed day++; // Increment day if(day > 7) day = 1; display_day(); // Call display_day function delay(200); // Wait 200 ms } draw_text(0, 50, " ", 1); blink_parameter(); // Call blink_parameter function display_day(); // Call display_day function blink_parameter(); // Call blink_parameter function if(!digitalRead(button1)) // If button B1 is pressed break; } //set position of text when editing on button press month = edit(60, 50, month); // Edit month date = edit(80, 50, date); // Edit date year = edit(110,50, year); // Edit year hour = edit(14, 25, hour); // Edit hours minute = edit(50, 25, minute); // Edit minutes

// Convert decimal to BCD
minute = ((minute / 10) &lt;&lt; 4) + (minute % 10);
hour = ((hour / 10)  &lt;&lt; 4) + (hour % 10);
date = ((date / 10) &lt;&lt;  4) + (date % 10);
month = ((month / 10)  &lt;&lt; 4) + (month % 10);
year = ((year / 10)  &lt;&lt; 4) + (year % 10);
// End conversion

// Write data to DS3231 RTC
Wire.beginTransmission(0x68);               // Start I2C protocol with DS3231 address
Wire.write(0);                              // Send register address
Wire.write(0);                              // Reset seconds and start oscillator
Wire.write(minute);                         // Write minute
Wire.write(hour);                           // Write hour
Wire.write(day);                            // Write day
Wire.write(month);                          // Write month
Wire.write(date);                           // Write date
Wire.write(year);                           // Write year
Wire.endTransmission();                     // Stop transmission and release the I2C bus
delay(200);                                 // Wait 200ms

}

Wire.beginTransmission(0x68); // Start I2C protocol with DS3231 address Wire.write(0); // Send register address Wire.endTransmission(false); // I2C restart Wire.requestFrom(0x68, 7); // Request 7 bytes from DS3231 and release I2C bus at end of reading second = Wire.read(); // Read seconds from register 0 minute = Wire.read(); // Read minutes from register 1 hour = Wire.read(); // Read hour from register 2 day = Wire.read(); // Read day from register 3 month = Wire.read(); // Read month from register 4 date = Wire.read(); // Read date from register 5 year = Wire.read(); // Read year from register 6 Wire.beginTransmission(0x68); // Start I2C protocol with DS3231 address Wire.write(0x11); // Send register address Wire.endTransmission(false); // I2C restart Wire.requestFrom(0x68, 2); // Request 2 bytes from DS3231 and release I2C bus at end of reading

DS3231_display(); // Display time & calendar display_day(); // Display date

scroll_text(); // Display scrolling text // If i dont use the clear buffers(next 2 line codes), on the //next loop cycle the seconds & minute text becomes jumbled //display.clearDisplay(); //display.display();

delay(50); // Wait 50ms }

Stiglitz
  • 21
  • 2
  • 1
    it is not surprising, your scroll_text() function stops the program execution for 12.5 seconds – jsotola May 16 '21 at 17:06
  • Thx for the suggestions, I changed the title to my post, & I tried commented out the delays and start & stop in my void scrolling_text(); the text is not scrolling, but is glitching, & the time is all jumbled – Stiglitz May 16 '21 at 17:15
  • The reason for the delays in the void scrolling_text(); is because I want the text to scroll 1 lap around, pause to display the text, then the scrolling starts back up again. Just like the LCD screen on a train when it is displaying the next train stop – Stiglitz May 16 '21 at 17:20
  • maybe what you need is a unified display function ... the scroll function does not do any displaying .. it only sets a character buffer ... the time function also does no displaying ... it sets the value of a time variable ... the display function uses the values in the character buffer, and in the time variable, and puts them on the screen – jsotola May 16 '21 at 17:46
  • So are you saying I should create a new void called void display_text(); & in it put: draw_text(60, 50, Calendar, 1); draw_text(15, 25, Time, 2); draw_text(0, 0, "The Pumpkin Express", 1); ? – Stiglitz May 16 '21 at 18:29
  • possibly something like that ... it does not have to be in its own function .. it can be part of loop() ... whatever way you like to organize your code – jsotola May 16 '21 at 21:10
  • "is there a way to isolate those delays...?" The processor can only do one thing at a time so if is delaying(), everything else it has to do is being held up, not just the contents of the function containing the delay(). [Responding here in anticipation of you editing your "answer" into the question. – JRobert May 17 '21 at 14:06
  • So I tried creating a new void with all 3 draw_text’s, both the calendar & time would not work outside the void DS3231_display(). So I figured out my problem is the delays in the void scrolling_text() is some how being read as part of the whole sketch. I thought creating a void would prevent that & only run it when it is called. Is there a way of coding to prevent one void delays from effecting the whole program, like is there a way to isolate those delays, or have the program only read the delays only when it is executing the display.scrollleft(0x00,0x00);? – Stiglitz May 18 '21 at 00:26
  • JRobert, are you saying I cant have the scrolling text in my sketch, because the Arduino cant handle it while executing the rest of the sketch? The reason for the delay's, is because I want the OLED to scroll the text, then pause the text to show it fully on the display, then scroll again, rinse & repeat. The reason the first delay is 8500, is because that is how long it takes the text to completely scroll out of view. – Stiglitz May 18 '21 at 00:44
  • 1
    @Stiglitz Unclear what's meant by "the delays in the void scrolling_text() is somehow being read as part of the whole sketch." A delay delays: it stops the world (except for interrupts)--nothing else will happen during a delay, it's delaying (except interrupts). If you don't want the sketch to delay in a way that stops the world then you'll need to delay differently. The Blink Without Delay may be what you're ultimately asking for. – Dave Newton Jun 16 '21 at 23:39

2 Answers2

1

The answer to your title question, "How to get 2 loop functions to cooperate?" is: You can't, because your program can't have two functions with the same name. What you'd need to do instead is redesign your program, keeping in mind the MCU can only do one thing at a time, although you could break up each of several jobs into pieces and interleave them fast enough that we slow humans won't notice.

My approach, when I have a question like this, is to temporarily put aside what I know about coding and about MCUs, and just focus on how to do the job. I list the sub-tasks my code needs to do but without thinking too much about how to do them. Write them down, in the order they required doing, in any note-taking style that suits you.

Now break down each one of those sub-tasks into its sub-tasks. Repeat, until you understand how to code each one. Then you'll know enough to start writing the code.

Update:

@NathanJiang is correct. The solution is not to make two loop() functions (because the language doesn't support that), but to decide how to interleave pieces of each job within 1 function, in such a way that they each appear to operate smoothly and don't interfere with each other. Especially, neither one must call delay(), which stops your code doing anything for the duration. If some piece isn't ready to be done yet, your code must move on to other pieces and do which ever of those is ready. This keeps it checking each piece over and over again, rapidly, doing what is ready and skipping what is not.

JRobert
  • 15,246
  • 3
  • 23
  • 51
  • What do you mean I have 2 functions with the same name? All 3 functions have different names, scroll_text(), display_day(), & DS3231_display(). the reason for my title, is because I am having an issue with scroll_text() & DS3231_display() making my program glitch when both are running in the sketch. I can get them to run fine individually. – Stiglitz May 18 '21 at 00:56
  • I believe that @JRobert means that you cannot have two functions both with the name loop. – Nathan Jiang Jun 17 '21 at 04:36
0

The way to get functions to cooperate with each other is to re-code them so they don't waste time and pass control back and forth as quickly as possible. You do this by using the BlinkWithoutDelay trick to re-write the "blocking" code to be non-blocking and trust in loop() to hand back control to your function in a reasonable amount of time.

For instance take this function that @jsotola identified as a performance killer:

void scroll_text(){
  draw_text(0, 0, "The Pumpkin Express", 1);
  display.display();
  display.startscrollright(0x00, 0x00);
  delay(8500);
  display.stopscroll();
  delay(4000);
  }

It really doesn't need to monopolize the processor for 12500 microseconds--Instead it could just check the time and turn scrolling on and off every so often, and quickly pass control back to the loop() for reading and updating the clock.

I don't have your hardware or setup, so here's some untested code:

void scroll_text(){
  // some local state variables to remember where we are
  static bool isInitialized = false; // maybe a global if re-initializing is needed.
  static bool isScrolling = false;
  static unsigned long interval = 1;
  static unsigned long last = -interval;
  unsigned long now = millis();
  if (now - last >= interval ) { // time to do something
    last = now;
    if( !isInitialized ){
      draw_text(0, 0, "The Pumpkin Express", 1);
      display.display();
      isIinitialized = true;
    }
    if ( !isScrolling ) {
        isScrolling = true;
        display.startscrollright(0x00, 0x00);
        // delay(8500);  // use BWOD trick instead
        interval = 8500;
     } else { // isScrolling
       isScrolling = false;
       display.stopscroll();
       //delay(4000);
       interval = 4000;
     }
  }

This drop-in scroll_text() function can be called many time times per millisecond and will mostly do nothing and return control immediately. But when it realizes the time is right, it will update its own state variables and the output states.

By replacing the scroll_text() function with a non-blocking form, it speeds up loop() by 12.5 seconds per loop. Doing similar replacement of processor-sapping delay()s could further improve responsiveness. However it is a big mind shift to stop using serial, blocking code and shift to events. Every time you see a delay(), a while(...), or a for() loop, think about whether you really need to trap the processor inside of that loop, or what data you would need in order to skip it for now and come back to it next time through the main event loop().

I think the next step would be to wrap the reading of the reading the the DS3231 with the BWOD trick -- you only need to read the clock once per 1000ms and you could save hundreds of instructions per loop().

Dave X
  • 2,332
  • 14
  • 28