1

Below is an Arduino sketch for a basic animation, taken from the book Beginning Arduino by Michael McRoberts. Also, the connections have been shown. From what I have read elsewhere, this is the standard kind of code used to drive a LED dot matrix using the 595 shift registry, or the like (for example the MAX7219). Regarding the functioning of the code, I have two questions.

  1. For what percentage of the time will the LED stay lit? In the screenupdate() there are no set delays. Hence, each row will stay lit only for the time it takes for the Arduino to execute the remaining loop and come back with the updated row values. How do we know for what time will the LEDs be lit, and hence predict their brightness, or for what can we do to set a particular value for this?

  2. As I understand from the code, it works assuming that the ISR will fire as the code is running and will include all stages of the led[]. Although in this case it is not much significant, but how do we know that the ISR will fire including all the stages?If say we are updating each row of the matrix, to give a scroll effect, we would need the ISR to fire after each modification. How do we ensure this? If the ISR is too frequent, it may not let the code run. Or if it is too dispersed, the code may skip lines giving a jagged animation. Also, since the code would have some periodicity, is it not possible that some LED rows are always skipped? How do we determine the time of ISR firing perfect for the code or modify the code to ensure these do not happen(except trial and error)?

#include <TimerOne.h>

int latchPin = 8; //Pin connected to Pin 12 of 74HC595 (Latch)
int clockPin = 12; //Pin connected to Pin 11 of 74HC595 (Clock)
int dataPin = 11; //Pin connected to Pin 14 of 74HC595 (Data)
byte led[8]; // 8 element unsigned integer array to hold the sprite

void setup() {
  // set the 3 digital pins to outputs
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  // Load the binary representation of the image into the array
  led[0] = B11111111;
  led[1] = B10000001;
  led[2] = B10111101;
  led[3] = B10100101;
  led[4] = B10100101;
  led[5] = B10111101;
  led[6] = B10000001;
  led[7] = B11111111;
  // set a timer of length 10000 microseconds (1/100th of a second)
  // and attach the screenUpdate function to the interrupt timer
  Timer1.initialize(10000);
  Timer1.attachInterrupt(screenUpdate);
}

// invert each row of the binary image and wait 1/4 second
void loop() {
  for (int i = 0; i < 8; i++) {
    led[i] = ~led[i];
  }
  delay (250);
}

// Display the image
void screenUpdate() {
  byte row = B10000000; // row 1
  for (byte k = 0; k < 8; k++) {
    shiftOut(dataPin, clockPin, LSBFIRST, led[k] ); // LED array
    shiftOut(dataPin, clockPin, LSBFIRST, ~row); // row binary number (active low)
    // latch low to high to output data
    digitalWrite(latchPin, LOW);
    digitalWrite(latchPin, HIGH);
    // bitshift right
    row = row >> 1;
  }
  // Turn all rows off until next interrupt
  shiftOut(dataPin, clockPin, LSBFIRST, 0);
  shiftOut(dataPin, clockPin, LSBFIRST, ~0);
  // latch low to high to output data
  digitalWrite(latchPin, LOW);
  digitalWrite(latchPin, HIGH);
}

enter image description here

ADDENDUM

In the above code, I tried putting Serial.println(micros()) in the screenUpdate() loop, but I git a very confusing results in which the time displayed seemed to increase and then decrease randomly. I did not understand why. Has it got to do something with ISRs? Sorry for the apparently clumsy question.

I executed the following code (from the same book) out, which includes a MAX7219 driver (with the usual setup) and creates scrollable text. It worked perfectly well. And I just want to understand how this ISR and multiplexing go together, and how do we make sure the frequency is appropriate. More of an academic question rather than a practical one.

#include <avr/pgmspace.h>
#include <TimerOne.h>
int DataPin = 2; // Pin 1 on MAX
int LoadPin = 3; // Pin 12 on MAX
int ClockPin = 4; // Pin 13 on MAX
byte buffer[8];
#define SCAN_LIMIT_REG 0x0B
#define DECODE_MODE_REG 0x09
#define SHUTDOWN_REG 0x0C
#define INTENSITY_REG 0x0A
static byte font[][8] PROGMEM = {
// The printable ASCII characters only (32-126)
}
void clearDisplay() {
for (byte x=0; x<8; x++) {
buffer[x] = B00000000;
}
screenUpdate();
}
void initMAX7219() {
pinMode(DataPin, OUTPUT);
pinMode(LoadPin, OUTPUT);
pinMode(ClockPin, OUTPUT);
clearDisplay();
writeData(SCAN_LIMIT_REG, B00000111); // scan limit set to 0:7
writeData(DECODE_MODE_REG, B00000000); // decode mode off
writeData(SHUTDOWN_REG, B00000001); // Set shutdown register to normal operation
intensity(15); // Values 0 to 15 only (4 bit)
}
void intensity(int intensity) {
writeData(INTENSITY_REG, intensity); //B0001010 is the Intensity Register
}
void writeData(byte msb, byte lsb) {
digitalWrite(LoadPin, LOW); // set loadpin ready to receive data
shiftOut(DataPin, ClockPin, MSBFIRST, (msb));
shiftOut(DataPin, ClockPin, MSBFIRST, (lsb));
digitalWrite(LoadPin, HIGH); // latch the data
}
void scroll(char myString[], int rate) {
byte firstChrRow, secondChrRow;
byte ledOutput;
byte chrIndex = 0; // Initialise the string position index
byte Char1, Char2;
byte scrollBit = 0;
byte strLength = 0;
unsigned long time;
unsigned long counter;
while (myString[strLength]) { // Increment count till we reach the end of the string
strLength++;}
counter = millis();
while (chrIndex < (strLength)) {
time = millis();
if (time > (counter + rate)) {
Char1 = constrain(myString[chrIndex],32,126);
Char2 = constrain(myString[chrIndex+1],32,126);
for (byte y= 0; y<8; y++) {
firstChrRow = pgm_read_byte(&font[Char1 - 32][y]);
secondChrRow = (pgm_read_byte(&font[Char2 - 32][y])) << 1
ledOutput = (firstChrRow << scrollBit)
| (secondChrRow >> (8 - scrollBit) );
buffer[y] = ledOutput;
}
scrollBit++;
if (scrollBit > 6) {
scrollBit = 0;
chrIndex++;
}
counter = millis();
}
}
}
void screenUpdate() {
for (byte row = 0; row < 8; row++) {
writeData(row+1, buffer[row]);
}
}
void setup() {
initMAX7219();
Timer1.initialize(10000); // initialize timer1 and set interrupt period
Timer1.attachInterrupt(screenUpdate);
Serial.begin(9600);
}
void loop() {
clearDisplay();
scroll(" BEGINNING ARDUINO ", 45);
scroll(" Chapter 7 - LED Displays ", 45);
scroll(" HELLO WORLD!!! :) ", 45);
}
5
  • You also might want to add some transitors, instead of resistors for the row. Otherwise, the more leds are lid in a single row, the dimmer they appear.
    – Gerben
    Jan 20, 2017 at 15:30
  • @Gerben, I always hear this advice but I put together a dotmatrix display and yanked out all the LEDs but one for a row and found them to be the same brightness. What I'd suggest is use one resistor per column, and use one transistor per row, which should ensure the LEDs get even power but my multimeter isn't in yet so I'm not sure (I know my LEDs aren't variable brightness though). This actually makes sense when you remember the lights are actually in a parallel circuit, they shouldn't change brightness when you have less lights. Jan 20, 2017 at 15:57
  • This setup also ensures (because the display isn't all lit at once) that one transistor is active for a row of LEDs, and each LED would still have one resistor limiting current. (I should note I also haven't tested powering an entire row of LEDs on an 8x8 dotmatrix display from just one pin without a transistor as I was afraid of drawing more than 40mA). Jan 20, 2017 at 15:59
  • @redstarcoder you're suggesting the same thing I was trying (and maybe failing) to say.
    – Gerben
    Jan 20, 2017 at 16:07
  • 1
    @Gerben oh probably! I read your suggestion as saying to not use any resistors are all, which alarmed me :). Jan 20, 2017 at 16:11

2 Answers 2

2

(In a comment the asker mentioned they're not sure of the "usual approach", so I've provided this answer to hopefully help clarify)

Here is my code for using a single 595 to drive the columns, and hooking up the rows to the Arduino pins themselves. Here I demonstrate a clear delay in each row update, as well as another delay before the image changes. I hope this helps clear up your confusion, and I apologize for not have a 2 595 setup handy (though I have 2 595s so if this doesn't help you, I'd be more than happy to set it up...), but it should be easy to adapt for a second 595. This supports a variable amount of width and height.

I also answered this assuming you just want the dotmatrix display functioning correctly, and I didn't focus on ISR. On first glance I see no delay in the row updating on the ISR version, or a delay when setting the latch pin and the whole thing seems pretty confusing. Honestly I think it might work in a perfect universe but I'm not sure (it wouldn't, as the other answer said, you'll probably just maybe observe a flicker and one row being lit). If this isn't an X Y problem and you really need ISR, I can answer that too, it just seems like we're getting into a lot of questions at once here...

// Pin connected to ST_CP of 74HC595
const int latchPin = 8;
// Pin connected to SH_CP of 74HC595
const int clockPin = 12;
// Pin connected to DS of 74HC595
const int dataPin = 11;
// The pin of the first row of your display
const int row1Pin = 2; // WARNING: All future rows are assumed to be row1Pin + n
// The amount of rows your display has
const int HEIGHT = 3;
// The amount of columns your display has
const int WIDTH = 2;
// The reset pin of 74HC595 (not required as it isn't used, but this pin is pulled low)
const int resetPin = 9;
// Variable used to keep track of currently lit up row
int currentRow = 0;
// Array of rows
int rows[] = {0b11,
              0b01,
              0b11};
unsigned long lastChange = 0;

void setup() {
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(resetPin, OUTPUT);
  digitalWrite(resetPin, LOW);
}

/* I don't think this works, but it might with a much higher delay.
void reset() {
  pinMode(resetPin, INPUT);
  delayMicroseconds(1);
  pinMode(resetPin, OUTPUT);
  digitalWrite(resetPin, LOW);
}
*/

// enableRow changes the currently enabled row for future calls, it also changes
// the pinMode on the previous row to "INPUT", disabling all lights on that row.
void enableRow(int row) {
  if (currentRow != 0) {
    pinMode(currentRow, INPUT);
  }
  currentRow = row;
}

// This is a quick function to test one single LED or row on your dotmatrix display (I should have documented this one better but pass an incremeneting number to it and you'll see what it does...).
void lightOne(int pos) {
  enableRow(row1Pin + int(pos / (WIDTH+1)));
  shiftOut(dataPin, clockPin, MSBFIRST, 1 + pos % (WIDTH+1));
}

// This function draws the display stored in "rows".
void displayRows() {
  for (int i = 0; i < HEIGHT; i++) {
    digitalWrite(latchPin, LOW);
    enableRow(row1Pin+i);
    // shift out the bits:
    shiftOut(dataPin, clockPin, MSBFIRST, rows[HEIGHT-i-1]);
    digitalWrite(latchPin, HIGH);
    pinMode(currentRow, OUTPUT);   // set the current row's pin as OUTPUT
    digitalWrite(currentRow, LOW); // output LOW on currentRow to complete light's circuit and display
    delay(3); // time in ms to hold each row lit
  }
}

void loop() {
  displayRows();
  if (lastChange+1000 /* 1000ms between changes */ < millis()) {
    for (int i = 0; i < HEIGHT; i++) {
      rows[i] = random(4);
    }
    lastChange = millis();
  }
}
2

I think the code is likely wrong.

the code is called every 10ms, each time refreshing all columns and all rows and then immediately overwriting them all. so you should just see a quick flicker.

the typical way to approach this is to have a static variable to remember the row (or column) to be lit, and then load up the corresponding data for that row + columns when the display routine is called.

blanking isn't necessary for hc595.

edit: 1/21/2017.

now that I have a little bit more time, I would just add to the comments above.

we know the issues, as outlined above. here is a confirmation of it, in a simulation.

enter image description here

I ran your original code on an arduino uno. U2 is the shift register connected to the led matrix's rows (active low), and U3 to the columns (active high).

two things are obvious: 1. the code is invoked every 10ms; 2. the code for the most part resets the display: other than when the code is updating all rows / columns (the first half of the screenUpdate()), rows are high (inactive) and the columns are low (inactive), thanks to the display being reset by the bottom of the screenUpdate() routine.

the net result is that you for the most part don't see anything being displayed on the led, other than very fast flickers.

here is the solution: rewrite screenUpdate(), as below:

// Display the image - alternate routine
void screenUpdate2(void) {
    static unsigned char row = 0; // start with row 0
    //for (byte k = 0; k < 8; k++) {
        shiftOut(dataPin, clockPin, LSBFIRST, led[row] ); // LED array
        shiftOut(dataPin, clockPin, LSBFIRST, ~(1<<row)); // row binary number (active low)
        // latch low to high to output data
        digitalWrite(latchPin, LOW);
        digitalWrite(latchPin, HIGH);
        // bitshift right
        //row = row >> 1;
        row = (row==7)?0:(row+1);
    //}
    // Turn all rows off until next interrupt
    //shiftOut(dataPin, clockPin, LSBFIRST, 0);
    //shiftOut(dataPin, clockPin, LSBFIRST, ~0);
    // latch low to high to output data
    //digitalWrite(latchPin, LOW);
    //digitalWrite(latchPin, HIGH);
}

the updated routine, screenUpdate2(), utilizes a static variable, row, to remember the current row to be displayed. it shifts out the column data, and sets up the row to be activated, and then strikes out the display to the led matrix. after that, it updates row for the next invokation.

here is how it works in the simulation: enter image description here

hope it helps.

post-it: if the main loop has good execution time (short, and fairly deterministic), you can run screenUpdate2() in the loop and it will work as well. running it through an interrupt provides better consistency.

edit: 1/21/2017 v2

short after posting the above, a couple sharp eyed individuals noticed that the output data seems to be flipped (more noticeable at 320ms in the simulation posted above). good catch.

that's due to the following lines in the main loop:

for (int i=0; i<8; i++) {
    led[i]= ~led[i];
}
delay (250);

I'm not sure what its purpose was in the original code - creating a blinking effect? - but if you comment out those lines, you will see that the execution would be as you would have expected:

enter image description here

you will find that the values match exactly what's in the led[] array.

you can think of led[] as a ram display buffer that stores information to be displayed. all your application code needs to do is to manipulate / update it in the main loop. the display routine, when called by the timer isr, will update that for you periodically, in a fully transparent and carefree manner.

I tend to utilize this approach with a lot of things. Thus having a hook to a timer to execute a piece of code is a valuable tool for a programmer to have.

edit: 1/21/2017 v3

I was asked how long screenUpdate() and screenUpdate() will run on an arduino (16MIPS): screenUpdate() takes about 2.1ms to run, and screenUpdate2() about 0.2ms. those numbers will determine the upper-end of the display update frequency.

4
  • Sorry, but being a beginner, I am not aware if the usual approach. Can you expand a bit? And also, I want to know whenever an ISR is set, how to determine the firing frequency. Jan 20, 2017 at 14:49
  • I checked another one, with the MAX7219 based on the same principles (added in the question), which worked perfectly well. I actually do not want to know another way to do multiplexing, but rather understanding the general principles of IRS and the time taken for execution. Jan 22, 2017 at 15:01
  • Okay, so a slight confirmation. The small flickers that were visible in the original code, that were the time it took the loop of the screenUpdate() function to complete one round, right? Jan 22, 2017 at 15:16
  • Also, what software/instrument is the one you used for analysis? Jan 22, 2017 at 16:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.