4
\$\begingroup\$

Let's say I have an Arduino connected to a 8x8 LED matrix.

In my case, the LED matrix is driven using two 74HC595 shift registers (one for the rows and one for the columns).

Since it can only set the status of 8 LEDs at a time (one row), idea is to refresh the rows fast enough so that the human eye will not notice the blinking.

This is usually done in the loop() method :

int led_status[8]; //contains LED status (which is on/off) for each row

void loop() {
   process_otherstuff(); //eg: key inputs,...
   display(); //shift led_status[] to shift register and latch result.   
}

Here is potential issue : what if CPU has to do some time consuming process in the loop method (eg: heavy math) ? The display() method will be called with some delay which might be noticeable (led will start blinking or even worse : stop refreshed, appears to be stuck on one row).

A possible solution I can think of is the refresh the LED matrix in an interrupt handler :

void setup()
{
  analogWrite(3, 100);
  attachInterrupt(1, refreshdisplay, RISING);
}

int row = 0;
void refreshdisplay()
{
   //refresh one single row
   refreshrow(row++);
   if(row > 7) row = 0;
}

Is this a good idea ? That way, no matter how much time is spend in loop() method, display will be refreshed as fast as needed. Potential issue I can see is a lot of time is now spent in the interrupt, which is usually a bad thing (interrupts handler should be as short as possible).

Another solution is to use a separate LED controller driver (eg : connected to Arduino using I2C bus) but this require changing components list.

\$\endgroup\$
  • 1
    \$\begingroup\$ Don't be too shy about writing complex ISRs. It's another of these things like "don't use global structs!!" that are not quite as universal as people think. If your main loop is not time-critical, there's no harm in "interrupting" it. Waiting in ISR is not that great idea thought. Better to use a timer to trip the ISR again after the delay you want. \$\endgroup\$ – Barleyman Jul 26 '18 at 15:09
  • \$\begingroup\$ If your interrupt refreshes one-row-at-a-time, and you have eight rows, then you want the whole array to refresh in at least 1/30 seconds to maintain persistence-of-vision. So you want an on-chip timer to trigger at least 240 interrupts-per-second. Added to that is time required for interrupt handler to save and restore the processor's current state. This seems do-able, and a sensible approach. Don't forget to reset your array pointer, as well as your row-counter. \$\endgroup\$ – glen_geek Jul 26 '18 at 15:29
3
\$\begingroup\$

Yes, that is the correct approach- refresh the display in an ISR.

Even slight timing changes can result in visual artifacts, so keeping the timing fairly tight is necessary if you want a high-quality appearance to the display.

If possible, use hardware (eg. the SPI peripheral) to write to the shift registers, and of course use a relatively high clock speed. If you do that, the total time spent in the ISR should only be a few percent of the processor bandwidth, and since it's taken in small "chunks" the background processing will proceed almost as if you had a processor running at, say, 12MHz rather than 16MHz.

Simple and (relatively) timing-critical processes like refreshing a display are exactly what ISRs are good for. It's also a good time (while you are in a periodic ISR) to scan input switches for debouncing, for example.

\$\endgroup\$
  • 1
    \$\begingroup\$ Exactly the right answer. Personal experience: I was once working with those 4-character alphanumeric displays from HP (essentially a 5x28 matrix), with an 8051-based MCU. I found that even the timing jitter associated with entering the ISR produced some visual flickering. I had to implement a "precise timing" technique (reading and modifying the hardware timer value, rather than just blindly clearing it) in order to get something acceptable. \$\endgroup\$ – Dave Tweed Jul 26 '18 at 15:54
2
\$\begingroup\$

Normally interrupt handlers are indeed used for short actions. The reason is that the main program blocks.

However, if the main handler does not do any time critical actions, it is a lesser problem.

Make sure that the interrupt handler does not take longer than the next interrupt call. Than you get an interrupt call within an interrupt call, etc.

In your case your refresh function is probably always taking the same time. You can measure it (do a test with 1000 iterations and divide the time by 1000, or 1M is you cannot measure it). And you know the refresh rate. Than you know how many percent (minus some overhead) is left for your main program.

Personally I think writing 64 GPIO pins, even with shift registers should be done very quickly, but testing is better than assuming.

\$\endgroup\$
  • \$\begingroup\$ Usually you would disable nested interrupts and/or they're disabled by default anyways. Unless, you know, you like pain. \$\endgroup\$ – Barleyman Jul 26 '18 at 16:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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