ITead Studio Quasi-Colorduino RGB LED Matrix Shield: Redesign Doodles

Some notes on a recent acquisition that ought to allow random dots with individual brightness control (unlike my simple resistor-limited hack job):

Color Shield - DM163 M54565 - demo
Color Shield – DM163 M54565 – demo

A Colorduino is a dedicated board that combines an Arduino-class microcontroller with hardware drivers for an 8×8 RGB LED matrix, with daisy-chaining I/O to build bigger displays. The Colors Shield you see above omits the Arduino circuitry and daisy-chaining hardware: it plugs atop an ordinary Arduino UNO-class board as a dedicated 8×8 tile driver.

I do not profess to understand the ancestry & family tree of those designs and their various incarnations. This schematic doesn’t match the knockoff hardware in hand, which isn’t surprising after half a dozen years of relentless product cheapnification:

ITeadStudio - RGB LED shield - DM163 M54564 - SPI notes
ITeadStudio – RGB LED shield – DM163 M54564 – SPI notes

It comes close enough for a big-picture overview…

The DM163 has 8×3 constant current sink PWM pins that connect to the column cathodes of the RGB matrix. It provides either 8 or 6 bits of PWM control for each output, with either 6 or 8 bits of gamma correction to make the grayscale shades work out properly (those are separate shift registers and the PWM generators use both, so the chip doesn’t care how you divvy up the 14 bits).

The three 1 kΩ resistors set the current to 60 mA per output pin. The LED matrix might support anywhere from  70 to 120 mA peak current per LED, but I doubt the supplied matrix matches any of the available datasheets. The total current depends on the number of LEDs lit on each row, so large dark areas are a Good Thing.

The serial protocol looks enough like SPI to get by, with controls for Reset, Latch, and Bank Select.

The board has no power supply other than the single Arduino VCC pin, so you’re looking at a peak of 24 x 60 mA = 1.44 A through that pin. The Arduino regulator must supply that load pretty much full-time, which is obviously a Bad Thing; plan on plenty of dark areas.

The DM163 SPI connections don’t use the Arduino’s hardware SPI, so it’s full-frontal bit-banging all the way. Three DM163 control bits use a trio of analog inputs as digital outputs. No harm in that, works fine with the knockoff Neopixels.

The M54564 is a PNP high-side driver converting logic-level inputs to the current required for the row anodes of the matrix. The eight input bits are non-contiguous across the Arduino’s digital outputs. You could turn on all the M54564 outputs at once, which would be a Bad Thing.

You shift 24 bytes of RGB data into the DM163 and latch the data, then raise one of the M54564 inputs to enable a given row of LEDs, which light up with the corresponding colors.

The bit-banged SPI runs at 1.9 µs/bit and sending all 24 bits to the DM163 requires 450 µs. With a 100 Hz refresh, that’s a mere 5% overhead, but the fact that the board soaks up essentially all the I/O pins means the Arduino isn’t not doing much else in the way of real-world interaction.

The Arduino driver, of dubious provenance, sets Timer 0 for 100-ish Hz interrupts. Each interrupt shifts another batch of bytes into the DM163 and selects the appropriate row. The driver uses a double-buffered array that soaks up 2x8x8x3 = 384 bytes of precious RAM, in addition to a bunch of working storage.

If I were (re)designing this board…

A separate power input jack for the DM163 that might optionally feed the Arduino’s VIN raw power pin.

Use the Arduino SPI hardware, dammit.

Put an HC595 shift register behind the M54564, so you’d shift 24 + 8 = 32 bits into the board, then strobe the latches. That eliminates eight digital pins used as a parallel port.

You’d surely want to disable the row driver while switching the column drivers to avoid ghosting, so figure on a separate output enable for the HC595. That tri-states the 595’s outputs; although the M54564 has internal pulldowns, it might need more.

It’s entirely usable as-is, but sheesh it’d be so easy to do a better job. That wouldn’t be software compatible with all the Arduino Love for the existing boards out there; there’s no point.

 

Epson R380 Printer: Resetting the Waste Ink Counter Again

The Epson R380 printer never gets turned off, so it rarely has a chance to complain. After a powerdown due to refreshing the UPS batteries, it lit up with the dreaded “Service required. Visit your friendly Epson repair center” message that indicates you should just throw the printer out, because replacing the internal ink absorber mats / draining the internal tank is, mmm, economically infeasible when you pay somebody else to do it.

Having done this before, though, it’s almost easy…

  • Pop a PC with a Windows partition off the to-be-recycled stack
  • Boot System Rescue CD
  • Back up the partition to a junk hard drive, just for practice
  • Copy the subdirectory of sketchy utilities to the Windows drive
  • Boot Windows (with no network connection)
  • Run sketchy utility to reset the ink counter
  • Boot SRC, restore partition
  • Return hard drive & PC to their respective piles
  • Declare victory and move on

This time, a sketchy utility that resembled the Official Epson Reset Program actually reset something and the printer started up normally. As before, however, the saved MBR didn’t match the on-disk MBR, suggesting that either I don’t understand how to save / restore the MBR or that something once again meddled with the MBR in between the backup and the restore.

I’ve emptied the waste ink tank maybe three times since the last reset: plenty of ink down the drain. Fortunately, I loves me some good continuous-flow ink supply action…

Sheesh & similar remarks.

Switch Contact Bounce

An Arduino hairball for an upcoming Digital Machinist column:

Arduino UNO clone - test setup
Arduino UNO clone – test setup

A short program monitors the switch. When it closes, the program reads the analog voltage from the pot and blinks the LED (on Pin 13, so you don’t need an external LED) for that number of milliseconds.

Some diligent rummaging produced a spectacularly bouncy switch (lower trace) with the output pulse (upper trace):

Contact Bounce - Matsuhita - NO 1
Contact Bounce – Matsuhita – NO 1

A longer timebase shows it’s rattling around for nearly half a millisecond:

Contact Bounce - Matsuhita - NO 2
Contact Bounce – Matsuhita – NO 2

The second pulse in the upper trace shows that the code gets around the loop() fast enough to retrigger on the same button push, which is part of the lesson in the column

A midrange timebase:

Contact Bounce - Matsuhita - NO 3
Contact Bounce – Matsuhita – NO 3

You could surely get a few random numbers out of that noise, although the first few bounces seem surprisingly consistent.

Skeuomorphism Gone Wild

This truck’s home base seems to be south of Maloney on Rt 376 and it occasionally passes me on the road:

Farmers and Chefs Food Truck
Farmers and Chefs Food Truck

My eye-blink reaction that it was a junker turns out to be completely wrong, as it sports a really great paint job (vinyl wrap?):

Farmers and Chefs Food Truck - Detail
Farmers and Chefs Food Truck – Detail

The junker aspect may not be quite what they expected…

I’m not sure that’s skeuomorphic, but I don’t know the proper term.

Bicycle-Hostile Design: Raymond Avenue

I generally ride somewhat further into the travel lane than some folks would prefer, but I have good reason for that. Here’s how bicycling along Raymond Avenue at 14 mph = 20 ft/s on a pleasant summer morning works out…

T = 0.000 — Notice anything out of the ordinary?

Raymond Ave - Door Near Miss - 2016-08-03 - 0018
Raymond Ave – Door Near Miss – 2016-08-03 – 0018

T = 1.000 — Me, neither:

Raymond Ave - Door Near Miss - 2016-08-03 - 0078
Raymond Ave – Door Near Miss – 2016-08-03 – 0078

T = 1.500 — Ah!

Raymond Ave - Door Near Miss - 2016-08-03 - 0108
Raymond Ave – Door Near Miss – 2016-08-03 – 0108

T = 2.000 — I’m flinching into the right turn required for a sharp left turn:

Raymond Ave - Door Near Miss - 2016-08-03 - 0138
Raymond Ave – Door Near Miss – 2016-08-03 – 0138

Less than half a second reaction time: pretty good, sez me.

T = 2.833 — End of the flinch:

Raymond Ave - Door Near Miss - 2016-08-03 - 0183
Raymond Ave – Door Near Miss – 2016-08-03 – 0183

T = 3.000 — Now I can lean and turn left:

Raymond Ave - Door Near Miss - 2016-08-03 - 0198
Raymond Ave – Door Near Miss – 2016-08-03 – 0198

T = 3.267 — This better be far enough left:

Raymond Ave - Door Near Miss - 2016-08-03 - 0214
Raymond Ave – Door Near Miss – 2016-08-03 – 0214

T = 3.333 — The door isn’t moving:

Raymond Ave - Door Near Miss - 2016-08-03 - 0218
Raymond Ave – Door Near Miss – 2016-08-03 – 0218

T = 3.567 — So I’ll live to ride another day:

Raymond Ave - Door Near Miss - 2016-08-03 - 0232
Raymond Ave – Door Near Miss – 2016-08-03 – 0232

I carry a spectacular scar from slashing my arm on a frameless car window, back in my college days: the driver flipped the door open as I passed his gas cap at a good clip. The collision wrecked the window, the door, and my bike, but didn’t break my arm, sever any nerves, or cut any arteries. I did discover human fatty tissue, neatly scooped from under my arm onto the window, is yellowish, which wasn’t something I needed to know.

Searching for Raymond Avenue will bring up other examples of bicycle-hostile features along this stretch of NYSDOT’s trendy, traffic-calmed design…

Random LED Dots: Entropy Library for Moah Speed with Less Gimcrackery

A discussion over the Squidwrench Operating Table about injecting entropy into VMs before / during their boot sequence reminded me that I wanted to try the Entropy library with my 8×8 RGB LED matrix:

8x8 RGB LED Matrix - board overview
8×8 RGB LED Matrix – board overview

The original version trundled along with random numbers produced by timing Geiger counter ticks. The second version, digitizing the amplified noise from a reverse-biased PN junction, ran much faster.

What’s new & different: the Entropy library measures the jitter between the ATmega328 watchdog timer’s RC oscillator and the ceramic resonator (on Pro Mini boards) driving the CPU. It cranks out four bytes of uncorrelated bits every half-second, which isn’t quite fast enough for a sparkly display, but re-seeding the Arduino PRNG whenever enough entropy arrives works well enough.

One could, of course, re-seed the PRNG with Geiger bits or junction noise to the same effect. The key advantage of the Entropy library: no external hardware required. The downside: no external hardware required, so, minus those techie transistors / resistors / op amps, it will look like Just Another Arduino Project.

Reverse-bias noise amplifier - detail
Reverse-bias noise amplifier – detail

Le sigh.

In any event, the Entropy library has excellent documentation and works perfectly.

The Arduino PRNG can produce results fast enough for wonderfully twinkly output that’s visually indistinguishable from the “true” random numbers from the Geiger counter or PN junction. I dialed it back to one update every 5 ms, because letting it free-run turned the display into an unattractive blur.

The top trace shows the update actually happens every 6 ms:

Entropy TRNG - LED update vs refresh
Entropy TRNG – LED update vs refresh

The lower trace shows that each matrix row refresh takes about a millisecond. Refreshes occur on every main loop iteration and interfere with the update, not that that makes any difference. Should it matter, subtract one from the update period and it’ll be all good.

The Arduino source code as a GitHub Gist:

// Random LED Dots
// Based on Entropy library using watchdog timer jitter
// https://sites.google.com/site/astudyofentropy/project-definition/timer-jitter-entropy-sources/entropy-library
// Ed Nisley - KE4ANU - August 2016
#include <Entropy.h>
//----------
// Pin assignments
const byte PIN_HEARTBEAT = 8; // DO - heartbeat LED
const byte PIN_SYNC = A3; // DO - scope sync
const byte PIN_LATCH = 4; // DO - shift register latch clock
const byte PIN_DIMMING = 9; // AO - LED dimming control
// These are *hardware* SPI pins
const byte PIN_MOSI = 11; // DO - data to shift reg
const byte PIN_MISO = 12; // DI - data from shift reg (unused)
const byte PIN_SCK = 13; // DO - shift clock to shift reg (also Arduino LED)
const byte PIN_SS = 10; // DO - -slave select (must be positive for SPI output)
//----------
// Constants
#define UPDATE_MS 5
//----------
// Globals
// LED selects are high-active bits and low-active signals: flipped in UpdateLEDs()
// *exactly* one row select must be active in each element
typedef struct {
const byte Row;
byte ColR;
byte ColG;
byte ColB;
} LED_BYTES;
// altering the number of rows & columns will require substantial code changes...
#define NUMROWS 8
#define NUMCOLS 8
LED_BYTES LEDs[NUMROWS] = {
{0x80,0,0,0},
{0x40,0,0,0},
{0x20,0,0,0},
{0x10,0,0,0},
{0x08,0,0,0},
{0x04,0,0,0},
{0x02,0,0,0},
{0x01,0,0,0},
};
byte RowIndex;
#define LEDS_ON 0
#define LEDS_OFF 255
unsigned long MillisNow;
unsigned long MillisThen;
//-- Helper routine for printf()
int s_putc(char c, FILE *t) {
Serial.write(c);
}
//-- Useful stuff
// Free RAM space monitor
// From http://playground.arduino.cc/Code/AvailableMemory
uint8_t * heapptr, * stackptr;
void check_mem() {
stackptr = (uint8_t *)malloc(4); // use stackptr temporarily
heapptr = stackptr; // save value of heap pointer
free(stackptr); // free up the memory again (sets stackptr to 0)
stackptr = (uint8_t *)(SP); // save value of stack pointer
}
void TogglePin(char bitpin) {
digitalWrite(bitpin,!digitalRead(bitpin)); // toggle the bit based on previous output
}
void PulsePin(char bitpin) {
TogglePin(bitpin);
TogglePin(bitpin);
}
//---------
//-- SPI utilities
void EnableSPI(void) {
digitalWrite(PIN_SS,HIGH); // make sure this is high!
SPCR |= 1 << SPE;
}
void DisableSPI(void) {
SPCR &= ~(1 << SPE);
}
void WaitSPIF(void) {
while (! (SPSR & (1 << SPIF))) {
// TogglePin(PIN_HEARTBEAT);
continue;
}
}
byte SendRecSPI(byte Dbyte) { // send one byte, get another in exchange
SPDR = Dbyte;
WaitSPIF();
return SPDR; // SPIF will be cleared
}
void UpdateLEDs(byte i) {
SendRecSPI(~LEDs[i].ColB); // low-active outputs
SendRecSPI(~LEDs[i].ColG);
SendRecSPI(~LEDs[i].ColR);
SendRecSPI(~LEDs[i].Row);
analogWrite(PIN_DIMMING,LEDS_OFF); // turn off LED to quench current
PulsePin(PIN_LATCH); // make new shift reg contents visible
analogWrite(PIN_DIMMING,LEDS_ON);
}
//---------------
// Set LED from integer
// On average, this leaves the LED unchanged for 1/8 of the calls...
void SetLED(unsigned long Value) {
byte Row = Value & 0x07;
byte Col = (Value >> 3) & 0x07;
byte Color = (Value >> 6) & 0x07;
byte BitMask = (0x80 >> Col);
// printf("%u %u %u %u\r\n",Row,Col,Color,BitMask);
LEDs[Row].ColR &= ~BitMask;
LEDs[Row].ColR |= (Color & 0x04) ? BitMask : 0;
LEDs[Row].ColG &= ~BitMask;
LEDs[Row].ColG |= (Color & 0x02) ? BitMask : 0;
LEDs[Row].ColB &= ~BitMask;
LEDs[Row].ColB |= (Color & 0x01) ? BitMask : 0;
}
//------------------
// Set things up
void setup() {
pinMode(PIN_HEARTBEAT,OUTPUT);
digitalWrite(PIN_HEARTBEAT,HIGH); // show we arrived
pinMode(PIN_SYNC,OUTPUT);
digitalWrite(PIN_SYNC,LOW);
pinMode(PIN_MOSI,OUTPUT); // SPI-as-output is not strictly necessary
digitalWrite(PIN_MOSI,LOW);
pinMode(PIN_SCK,OUTPUT);
digitalWrite(PIN_SCK,LOW);
pinMode(PIN_SS,OUTPUT);
digitalWrite(PIN_SS,HIGH); // OUTPUT + HIGH is required to make SPI output work
pinMode(PIN_LATCH,OUTPUT);
digitalWrite(PIN_LATCH,LOW);
Serial.begin(57600);
fdevopen(&s_putc,0); // set up serial output for printf()
printf("Random LED Dots - Watchdog Entropy\r\nEd Nisley - KE4ZNU - August 2016\r\n");
Entropy.initialize(); // start up entropy collector
//-- Set up SPI hardware
SPCR = B01110001; // Auto SPI: no int, enable, LSB first, master, + edge, leading, f/16
SPSR = B00000000; // not double data rate
EnableSPI(); // turn on the SPI hardware
SendRecSPI(0); // set valid data in shift registers: select Row 0, all LEDs off
//-- Dimming pin must use fast PWM to avoid beat flicker with LED refresh rate
// Timer 1: PWM 9 PWM 10
analogWrite(PIN_DIMMING,LEDS_OFF); // disable column drive (hardware pulled it low before startup)
TCCR1A = B10000001; // Mode 5 = fast 8-bit PWM with TOP=FF
TCCR1B = B00001001; // ... WGM, 1:1 clock scale -> 64 kHz
//-- lamp test: send a white flash through all LEDs
printf("Lamp test begins: white flash each LED...");
digitalWrite(PIN_HEARTBEAT,LOW); // turn off while panel blinks
analogWrite(PIN_DIMMING,LEDS_ON); // enable column drive
for (byte i=0; i<NUMROWS; i++) {
for (byte j=0; j<NUMCOLS; j++) {
LEDs[i].ColR = LEDs[i].ColG = LEDs[i].ColB = 0x80 >> j;
for (byte k=0; k<NUMROWS; k++) {
UpdateLEDs(k);
delay(25);
}
LEDs[i].ColR = LEDs[i].ColG = LEDs[i].ColB = 0;
}
}
UpdateLEDs(NUMROWS-1); // clear the last LED
printf(" done!\r\n");
//-- Preload LEDs with random values
digitalWrite(PIN_HEARTBEAT,LOW);
uint32_t rn = Entropy.random();
printf("Preloading LED array with seed: %08lx\r\n",rn);
randomSeed(rn);
for (byte Row=0; Row<NUMROWS; Row++) {
for (byte Col=0; Col<NUMCOLS; Col++) { // Col runs backwards, but we don't care
LEDs[Row].ColR |= random(2) << Col; // random(2) returns 0 or 1
LEDs[Row].ColG |= random(2) << Col;
LEDs[Row].ColB |= random(2) << Col;
}
UpdateLEDs(Row);
}
check_mem();
printf("SP: %u HP: %u Free RAM: %u\r\n",stackptr,heapptr,stackptr - heapptr);
printf("Running...\r\n");
MillisThen = millis();
}
//------------------
// Run the test loop
void loop() {
unsigned long Hash;
uint32_t rn;
MillisNow = millis();
// Re-seed the generator whenever we get enough entropy
if (Entropy.available()) {
digitalWrite(PIN_HEARTBEAT,HIGH);
rn = Entropy.random();
// printf("Random: %08lx ",rn);
randomSeed(rn);
digitalWrite(PIN_HEARTBEAT,LOW);
}
// If it's time for a change, whack a random LED
if ((MillisNow - MillisThen) > UPDATE_MS) {
MillisThen = MillisNow;
SetLED(random());
}
// Refresh LED array to maintain the illusion of constant light
UpdateLEDs(RowIndex++);
if (RowIndex >= NUMROWS) {
RowIndex = 0;
PulsePin(PIN_SYNC);
}
}
view raw TimerDots.ino hosted with ❤ by GitHub