Adafruit-RGB_matrix_Panel(32*16)

Dependencies:   Adafruit-GFX

Committer:
lelect
Date:
Fri May 23 15:08:14 2014 +0000
Revision:
0:06d9443a018f
Child:
1:0078213d3fa4
Initial commit

Who changed what in which revision?

UserRevisionLine numberNew contents of line
lelect 0:06d9443a018f 1 #include "RGBmatrixPanel.h"
lelect 0:06d9443a018f 2 #include "gamma.h"
lelect 0:06d9443a018f 3
lelect 0:06d9443a018f 4 #define DATAPORT PORTD
lelect 0:06d9443a018f 5 #define DATADIR DDRD
lelect 0:06d9443a018f 6 #define SCLKPORT PORTB
lelect 0:06d9443a018f 7
lelect 0:06d9443a018f 8 #define nPlanes 4
lelect 0:06d9443a018f 9
lelect 0:06d9443a018f 10 // The fact that the display driver interrupt stuff is tied to the
lelect 0:06d9443a018f 11 // singular Timer1 doesn't really take well to object orientation with
lelect 0:06d9443a018f 12 // multiple RGBmatrixPanel instances. The solution at present is to
lelect 0:06d9443a018f 13 // allow instances, but only one is active at any given time, via its
lelect 0:06d9443a018f 14 // begin() method. The implementation is still incomplete in parts;
lelect 0:06d9443a018f 15 // the prior active panel really should be gracefully disabled, and a
lelect 0:06d9443a018f 16 // stop() method should perhaps be added...assuming multiple instances
lelect 0:06d9443a018f 17 // are even an actual need.
lelect 0:06d9443a018f 18 static RGBmatrixPanel *activePanel = NULL;
lelect 0:06d9443a018f 19
lelect 0:06d9443a018f 20 // Code common to both the 16x32 and 32x32 constructors:
lelect 0:06d9443a018f 21 void RGBmatrixPanel::init(uint8_t rows, uint8_t a, uint8_t b, uint8_t c, uint8_t sclk, uint8_t latch, uint8_t oe, bool dbuf)
lelect 0:06d9443a018f 22 {
lelect 0:06d9443a018f 23 nRows = rows; // Number of multiplexed rows; actual height is 2X this
lelect 0:06d9443a018f 24 // Allocate and initialize matrix buffer:
lelect 0:06d9443a018f 25 int buffsize = 32 * nRows * 3, // x3 = 3 bytes holds 4 planes "packed"
lelect 0:06d9443a018f 26 allocsize = (dbuf == true) ? (buffsize * 2) : buffsize;
lelect 0:06d9443a018f 27 if(NULL == (matrixbuff[0] = (uint8_t *)malloc(allocsize))) return;
lelect 0:06d9443a018f 28 memset(matrixbuff[0], 0, allocsize);
lelect 0:06d9443a018f 29 // If not double-buffered, both buffers then point to the same address:
lelect 0:06d9443a018f 30 matrixbuff[1] = (dbuf == true) ? &matrixbuff[0][buffsize] : matrixbuff[0];
lelect 0:06d9443a018f 31
lelect 0:06d9443a018f 32 // Save pin numbers for use by begin() method later.
lelect 0:06d9443a018f 33 _a = a;
lelect 0:06d9443a018f 34 _a = a;
lelect 0:06d9443a018f 35 _b = b;
lelect 0:06d9443a018f 36 _c = c;
lelect 0:06d9443a018f 37 _sclk = sclk;
lelect 0:06d9443a018f 38 _latch = latch;
lelect 0:06d9443a018f 39 _oe = oe;
lelect 0:06d9443a018f 40
lelect 0:06d9443a018f 41 // Look up port registers and pin masks ahead of time,
lelect 0:06d9443a018f 42 // avoids many slow digitalWrite() calls later.
lelect 0:06d9443a018f 43 sclkpin = digitalPinToBitMask(sclk);
lelect 0:06d9443a018f 44 latport = portOutputRegister(digitalPinToPort(latch));
lelect 0:06d9443a018f 45 latpin = digitalPinToBitMask(latch);
lelect 0:06d9443a018f 46 oeport = portOutputRegister(digitalPinToPort(oe));
lelect 0:06d9443a018f 47 oepin = digitalPinToBitMask(oe);
lelect 0:06d9443a018f 48 addraport = portOutputRegister(digitalPinToPort(a));
lelect 0:06d9443a018f 49 addrapin = digitalPinToBitMask(a);
lelect 0:06d9443a018f 50 addrbport = portOutputRegister(digitalPinToPort(b));
lelect 0:06d9443a018f 51 addrbpin = digitalPinToBitMask(b);
lelect 0:06d9443a018f 52 addrcport = portOutputRegister(digitalPinToPort(c));
lelect 0:06d9443a018f 53 addrcpin = digitalPinToBitMask(c);
lelect 0:06d9443a018f 54 plane = nPlanes - 1;
lelect 0:06d9443a018f 55 row = nRows - 1;
lelect 0:06d9443a018f 56 swapflag = false;
lelect 0:06d9443a018f 57 backindex = 0; // Array index of back buffer
lelect 0:06d9443a018f 58 }
lelect 0:06d9443a018f 59
lelect 0:06d9443a018f 60 // Constructor for 16x32 panel:
lelect 0:06d9443a018f 61 RGBmatrixPanel::RGBmatrixPanel(uint8_t a, uint8_t b, uint8_t c, uint8_t sclk, uint8_t latch, uint8_t oe, bool dbuf)
lelect 0:06d9443a018f 62 :Adafruit_GFX(32, 16)
lelect 0:06d9443a018f 63 {
lelect 0:06d9443a018f 64 init(8, a, b, c, sclk, latch, oe, dbuf);
lelect 0:06d9443a018f 65 }
lelect 0:06d9443a018f 66
lelect 0:06d9443a018f 67 // Constructor for 32x32 panel:
lelect 0:06d9443a018f 68 RGBmatrixPanel::RGBmatrixPanel(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint8_t sclk, uint8_t latch, uint8_t oe, bool dbuf)
lelect 0:06d9443a018f 69 :Adafruit_GFX(32, 32)
lelect 0:06d9443a018f 70 {
lelect 0:06d9443a018f 71 init(16, a, b, c, sclk, latch, oe, dbuf);
lelect 0:06d9443a018f 72
lelect 0:06d9443a018f 73 // Init a few extra 32x32-specific elements:
lelect 0:06d9443a018f 74 _d = d;
lelect 0:06d9443a018f 75 addrdport = portOutputRegister(digitalPinToPort(d));
lelect 0:06d9443a018f 76 addrdpin = digitalPinToBitMask(d);
lelect 0:06d9443a018f 77 }
lelect 0:06d9443a018f 78
lelect 0:06d9443a018f 79 void RGBmatrixPanel::begin(void)
lelect 0:06d9443a018f 80 {
lelect 0:06d9443a018f 81
lelect 0:06d9443a018f 82 backindex = 0; // Back buffer
lelect 0:06d9443a018f 83 buffptr = matrixbuff[1 - backindex]; // -> front buffer
lelect 0:06d9443a018f 84 activePanel = this; // For interrupt hander
lelect 0:06d9443a018f 85
lelect 0:06d9443a018f 86 // Enable all comm & address pins as outputs, set default states:
lelect 0:06d9443a018f 87 pinMode(_sclk , OUTPUT);
lelect 0:06d9443a018f 88 SCLKPORT &= ~sclkpin; // Low
lelect 0:06d9443a018f 89 pinMode(_latch, OUTPUT);
lelect 0:06d9443a018f 90 *latport &= ~latpin; // Low
lelect 0:06d9443a018f 91 pinMode(_oe , OUTPUT);
lelect 0:06d9443a018f 92 *oeport |= oepin; // High (disable output)
lelect 0:06d9443a018f 93 pinMode(_a , OUTPUT);
lelect 0:06d9443a018f 94 *addraport &= ~addrapin; // Low
lelect 0:06d9443a018f 95 pinMode(_b , OUTPUT);
lelect 0:06d9443a018f 96 *addrbport &= ~addrbpin; // Low
lelect 0:06d9443a018f 97 pinMode(_c , OUTPUT);
lelect 0:06d9443a018f 98 *addrcport &= ~addrcpin; // Low
lelect 0:06d9443a018f 99 if(nRows > 8) {
lelect 0:06d9443a018f 100 pinMode(_d , OUTPUT);
lelect 0:06d9443a018f 101 *addrdport &= ~addrdpin; // Low
lelect 0:06d9443a018f 102 }
lelect 0:06d9443a018f 103
lelect 0:06d9443a018f 104 // The high six bits of the data port are set as outputs;
lelect 0:06d9443a018f 105 // Might make this configurable in the future, but not yet.
lelect 0:06d9443a018f 106 DATADIR = B11111100;
lelect 0:06d9443a018f 107 DATAPORT = 0;
lelect 0:06d9443a018f 108
lelect 0:06d9443a018f 109 // Set up Timer1 for interrupt:
lelect 0:06d9443a018f 110 TCCR1A = _BV(WGM11); // Mode 14 (fast PWM), OC1A off
lelect 0:06d9443a018f 111 TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10); // Mode 14, no prescale
lelect 0:06d9443a018f 112 ICR1 = 100;
lelect 0:06d9443a018f 113 TIMSK1 |= _BV(TOIE1); // Enable Timer1 interrupt
lelect 0:06d9443a018f 114 sei(); // Enable global interrupts
lelect 0:06d9443a018f 115 }
lelect 0:06d9443a018f 116
lelect 0:06d9443a018f 117 // Original RGBmatrixPanel library used 3/3/3 color. Later version used
lelect 0:06d9443a018f 118 // 4/4/4. Then Adafruit_GFX (core library used across all Adafruit
lelect 0:06d9443a018f 119 // display devices now) standardized on 5/6/5. The matrix still operates
lelect 0:06d9443a018f 120 // internally on 4/4/4 color, but all the graphics functions are written
lelect 0:06d9443a018f 121 // to expect 5/6/5...the matrix lib will truncate the color components as
lelect 0:06d9443a018f 122 // needed when drawing. These next functions are mostly here for the
lelect 0:06d9443a018f 123 // benefit of older code using one of the original color formats.
lelect 0:06d9443a018f 124
lelect 0:06d9443a018f 125 // Promote 3/3/3 RGB to Adafruit_GFX 5/6/5
lelect 0:06d9443a018f 126 uint16_t RGBmatrixPanel::Color333(uint8_t r, uint8_t g, uint8_t b)
lelect 0:06d9443a018f 127 {
lelect 0:06d9443a018f 128 // RRRrrGGGgggBBBbb
lelect 0:06d9443a018f 129 return ((r & 0x7) << 13) | ((r & 0x6) << 10) |
lelect 0:06d9443a018f 130 ((g & 0x7) << 8) | ((g & 0x7) << 5) |
lelect 0:06d9443a018f 131 ((b & 0x7) << 2) | ((b & 0x6) >> 1);
lelect 0:06d9443a018f 132 }
lelect 0:06d9443a018f 133
lelect 0:06d9443a018f 134 // Promote 4/4/4 RGB to Adafruit_GFX 5/6/5
lelect 0:06d9443a018f 135 uint16_t RGBmatrixPanel::Color444(uint8_t r, uint8_t g, uint8_t b)
lelect 0:06d9443a018f 136 {
lelect 0:06d9443a018f 137 // RRRRrGGGGggBBBBb
lelect 0:06d9443a018f 138 return ((r & 0xF) << 12) | ((r & 0x8) << 8) |
lelect 0:06d9443a018f 139 ((g & 0xF) << 7) | ((g & 0xC) << 3) |
lelect 0:06d9443a018f 140 ((b & 0xF) << 1) | ((b & 0x8) >> 3);
lelect 0:06d9443a018f 141 }
lelect 0:06d9443a018f 142
lelect 0:06d9443a018f 143 // Demote 8/8/8 to Adafruit_GFX 5/6/5
lelect 0:06d9443a018f 144 // If no gamma flag passed, assume linear color
lelect 0:06d9443a018f 145 uint16_t RGBmatrixPanel::Color888(uint8_t r, uint8_t g, uint8_t b)
lelect 0:06d9443a018f 146 {
lelect 0:06d9443a018f 147 return ((r & 0xF8) << 11) | ((g & 0xFC) << 5) | (b >> 3);
lelect 0:06d9443a018f 148 }
lelect 0:06d9443a018f 149
lelect 0:06d9443a018f 150 // 8/8/8 -> gamma -> 5/6/5
lelect 0:06d9443a018f 151 uint16_t RGBmatrixPanel::Color888(
lelect 0:06d9443a018f 152 uint8_t r, uint8_t g, uint8_t b, bool gflag)
lelect 0:06d9443a018f 153 {
lelect 0:06d9443a018f 154 if(gflag) { // Gamma-corrected color?
lelect 0:06d9443a018f 155 r = pgm_read_byte(&gamma[r]); // Gamma correction table maps
lelect 0:06d9443a018f 156 g = pgm_read_byte(&gamma[g]); // 8-bit input to 4-bit output
lelect 0:06d9443a018f 157 b = pgm_read_byte(&gamma[b]);
lelect 0:06d9443a018f 158 return (r << 12) | ((r & 0x8) << 8) | // 4/4/4 -> 5/6/5
lelect 0:06d9443a018f 159 (g << 7) | ((g & 0xC) << 3) |
lelect 0:06d9443a018f 160 (b << 1) | ( b >> 3);
lelect 0:06d9443a018f 161 } // else linear (uncorrected) color
lelect 0:06d9443a018f 162 return ((r & 0xF8) << 11) | ((g & 0xFC) << 5) | (b >> 3);
lelect 0:06d9443a018f 163 }
lelect 0:06d9443a018f 164
lelect 0:06d9443a018f 165 uint16_t RGBmatrixPanel::ColorHSV(
lelect 0:06d9443a018f 166 long hue, uint8_t sat, uint8_t val, bool gflag)
lelect 0:06d9443a018f 167 {
lelect 0:06d9443a018f 168
lelect 0:06d9443a018f 169 uint8_t r, g, b, lo;
lelect 0:06d9443a018f 170 uint16_t s1, v1;
lelect 0:06d9443a018f 171
lelect 0:06d9443a018f 172 // Hue
lelect 0:06d9443a018f 173 hue %= 1536; // -1535 to +1535
lelect 0:06d9443a018f 174 if(hue < 0) hue += 1536; // 0 to +1535
lelect 0:06d9443a018f 175 lo = hue & 255; // Low byte = primary/secondary color mix
lelect 0:06d9443a018f 176 switch(hue >> 8) { // High byte = sextant of colorwheel
lelect 0:06d9443a018f 177 case 0 :
lelect 0:06d9443a018f 178 r = 255 ;
lelect 0:06d9443a018f 179 g = lo ;
lelect 0:06d9443a018f 180 b = 0 ;
lelect 0:06d9443a018f 181 break; // R to Y
lelect 0:06d9443a018f 182 case 1 :
lelect 0:06d9443a018f 183 r = 255 - lo;
lelect 0:06d9443a018f 184 g = 255 ;
lelect 0:06d9443a018f 185 b = 0 ;
lelect 0:06d9443a018f 186 break; // Y to G
lelect 0:06d9443a018f 187 case 2 :
lelect 0:06d9443a018f 188 r = 0 ;
lelect 0:06d9443a018f 189 g = 255 ;
lelect 0:06d9443a018f 190 b = lo ;
lelect 0:06d9443a018f 191 break; // G to C
lelect 0:06d9443a018f 192 case 3 :
lelect 0:06d9443a018f 193 r = 0 ;
lelect 0:06d9443a018f 194 g = 255 - lo;
lelect 0:06d9443a018f 195 b = 255 ;
lelect 0:06d9443a018f 196 break; // C to B
lelect 0:06d9443a018f 197 case 4 :
lelect 0:06d9443a018f 198 r = lo ;
lelect 0:06d9443a018f 199 g = 0 ;
lelect 0:06d9443a018f 200 b = 255 ;
lelect 0:06d9443a018f 201 break; // B to M
lelect 0:06d9443a018f 202 default:
lelect 0:06d9443a018f 203 r = 255 ;
lelect 0:06d9443a018f 204 g = 0 ;
lelect 0:06d9443a018f 205 b = 255 - lo;
lelect 0:06d9443a018f 206 break; // M to R
lelect 0:06d9443a018f 207 }
lelect 0:06d9443a018f 208
lelect 0:06d9443a018f 209 // Saturation: add 1 so range is 1 to 256, allowig a quick shift operation
lelect 0:06d9443a018f 210 // on the result rather than a costly divide, while the type upgrade to int
lelect 0:06d9443a018f 211 // avoids repeated type conversions in both directions.
lelect 0:06d9443a018f 212 s1 = sat + 1;
lelect 0:06d9443a018f 213 r = 255 - (((255 - r) * s1) >> 8);
lelect 0:06d9443a018f 214 g = 255 - (((255 - g) * s1) >> 8);
lelect 0:06d9443a018f 215 b = 255 - (((255 - b) * s1) >> 8);
lelect 0:06d9443a018f 216
lelect 0:06d9443a018f 217 // Value (brightness) & 16-bit color reduction: similar to above, add 1
lelect 0:06d9443a018f 218 // to allow shifts, and upgrade to int makes other conversions implicit.
lelect 0:06d9443a018f 219 v1 = val + 1;
lelect 0:06d9443a018f 220 if(gflag) { // Gamma-corrected color?
lelect 0:06d9443a018f 221 r = pgm_read_byte(&gamma[(r * v1) >> 8]); // Gamma correction table maps
lelect 0:06d9443a018f 222 g = pgm_read_byte(&gamma[(g * v1) >> 8]); // 8-bit input to 4-bit output
lelect 0:06d9443a018f 223 b = pgm_read_byte(&gamma[(b * v1) >> 8]);
lelect 0:06d9443a018f 224 } else { // linear (uncorrected) color
lelect 0:06d9443a018f 225 r = (r * v1) >> 12; // 4-bit results
lelect 0:06d9443a018f 226 g = (g * v1) >> 12;
lelect 0:06d9443a018f 227 b = (b * v1) >> 12;
lelect 0:06d9443a018f 228 }
lelect 0:06d9443a018f 229 return (r << 12) | ((r & 0x8) << 8) | // 4/4/4 -> 5/6/5
lelect 0:06d9443a018f 230 (g << 7) | ((g & 0xC) << 3) |
lelect 0:06d9443a018f 231 (b << 1) | ( b >> 3);
lelect 0:06d9443a018f 232 }
lelect 0:06d9443a018f 233
lelect 0:06d9443a018f 234 void RGBmatrixPanel::drawPixel(int16_t x, int16_t y, uint16_t c)
lelect 0:06d9443a018f 235 {
lelect 0:06d9443a018f 236 uint8_t r, g, b, bit, limit, *ptr;
lelect 0:06d9443a018f 237 if((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) return;
lelect 0:06d9443a018f 238 switch(rotation) {
lelect 0:06d9443a018f 239 case 1:
lelect 0:06d9443a018f 240 swap(x, y);
lelect 0:06d9443a018f 241 x = _rawWidth - 1 - x;
lelect 0:06d9443a018f 242 break;
lelect 0:06d9443a018f 243 case 2:
lelect 0:06d9443a018f 244 x = _rawWidth - 1 - x;
lelect 0:06d9443a018f 245 y = _rawHeight - 1 - y;
lelect 0:06d9443a018f 246 break;
lelect 0:06d9443a018f 247 case 3:
lelect 0:06d9443a018f 248 swap(x, y);
lelect 0:06d9443a018f 249 y = _rawHeight - 1 - y;
lelect 0:06d9443a018f 250 break;
lelect 0:06d9443a018f 251 }
lelect 0:06d9443a018f 252
lelect 0:06d9443a018f 253 // Adafruit_GFX uses 16-bit color in 5/6/5 format, while matrix needs
lelect 0:06d9443a018f 254 // 4/4/4. Pluck out relevant bits while separating into R,G,B:
lelect 0:06d9443a018f 255 r = c >> 12; // RRRRrggggggbbbbb
lelect 0:06d9443a018f 256 g = (c >> 7) & 0xF; // rrrrrGGGGggbbbbb
lelect 0:06d9443a018f 257 b = (c >> 1) & 0xF; // rrrrrggggggBBBBb
lelect 0:06d9443a018f 258 // Loop counter stuff
lelect 0:06d9443a018f 259 bit = 2;
lelect 0:06d9443a018f 260 limit = 1 << nPlanes;
lelect 0:06d9443a018f 261
lelect 0:06d9443a018f 262 if(y < nRows) {
lelect 0:06d9443a018f 263 // Data for the upper half of the display is stored in the lower
lelect 0:06d9443a018f 264 // bits of each byte.
lelect 0:06d9443a018f 265 ptr = &matrixbuff[backindex][y * _rawWidth * (nPlanes - 1) + x]; // Base addr
lelect 0:06d9443a018f 266 // Plane 0 is a tricky case -- its data is spread about,
lelect 0:06d9443a018f 267 // stored in least two bits not used by the other planes.
lelect 0:06d9443a018f 268 ptr[64] &= ~B00000011; // Plane 0 R,G mask out in one op
lelect 0:06d9443a018f 269 if(r & 1) ptr[64] |= B00000001; // Plane 0 R: 64 bytes ahead, bit 0
lelect 0:06d9443a018f 270 if(g & 1) ptr[64] |= B00000010; // Plane 0 G: 64 bytes ahead, bit 1
lelect 0:06d9443a018f 271 if(b & 1) ptr[32] |= B00000001; // Plane 0 B: 32 bytes ahead, bit 0
lelect 0:06d9443a018f 272 else ptr[32] &= ~B00000001; // Plane 0 B unset; mask out
lelect 0:06d9443a018f 273 // The remaining three image planes are more normal-ish.
lelect 0:06d9443a018f 274 // Data is stored in the high 6 bits so it can be quickly
lelect 0:06d9443a018f 275 // copied to the DATAPORT register w/6 output lines.
lelect 0:06d9443a018f 276 for(; bit < limit; bit <<= 1) {
lelect 0:06d9443a018f 277 *ptr &= ~B00011100; // Mask out R,G,B in one op
lelect 0:06d9443a018f 278 if(r & bit) *ptr |= B00000100; // Plane N R: bit 2
lelect 0:06d9443a018f 279 if(g & bit) *ptr |= B00001000; // Plane N G: bit 3
lelect 0:06d9443a018f 280 if(b & bit) *ptr |= B00010000; // Plane N B: bit 4
lelect 0:06d9443a018f 281 ptr += WIDTH; // Advance to next bit plane
lelect 0:06d9443a018f 282 }
lelect 0:06d9443a018f 283 } else {
lelect 0:06d9443a018f 284 // Data for the lower half of the display is stored in the upper
lelect 0:06d9443a018f 285 // bits, except for the plane 0 stuff, using 2 least bits.
lelect 0:06d9443a018f 286 ptr = &matrixbuff[backindex][(y - nRows) * WIDTH * (nPlanes - 1) + x];
lelect 0:06d9443a018f 287 *ptr &= ~B00000011; // Plane 0 G,B mask out in one op
lelect 0:06d9443a018f 288 if(r & 1) ptr[32] |= B00000010; // Plane 0 R: 32 bytes ahead, bit 1
lelect 0:06d9443a018f 289 else ptr[32] &= ~B00000010; // Plane 0 R unset; mask out
lelect 0:06d9443a018f 290 if(g & 1) *ptr |= B00000001; // Plane 0 G: bit 0
lelect 0:06d9443a018f 291 if(b & 1) *ptr |= B00000010; // Plane 0 B: bit 0
lelect 0:06d9443a018f 292 for(; bit < limit; bit <<= 1) {
lelect 0:06d9443a018f 293 *ptr &= ~B11100000; // Mask out R,G,B in one op
lelect 0:06d9443a018f 294 if(r & bit) *ptr |= B00100000; // Plane N R: bit 5
lelect 0:06d9443a018f 295 if(g & bit) *ptr |= B01000000; // Plane N G: bit 6
lelect 0:06d9443a018f 296 if(b & bit) *ptr |= B10000000; // Plane N B: bit 7
lelect 0:06d9443a018f 297 ptr += WIDTH; // Advance to next bit plane
lelect 0:06d9443a018f 298 }
lelect 0:06d9443a018f 299 }
lelect 0:06d9443a018f 300 }
lelect 0:06d9443a018f 301
lelect 0:06d9443a018f 302 void RGBmatrixPanel::fillScreen(uint16_t c)
lelect 0:06d9443a018f 303 {
lelect 0:06d9443a018f 304 if((c == 0x0000) || (c == 0xffff)) {
lelect 0:06d9443a018f 305 // For black or white, all bits in frame buffer will be identically
lelect 0:06d9443a018f 306 // set or unset (regardless of weird bit packing), so it's OK to just
lelect 0:06d9443a018f 307 // quickly memset the whole thing:
lelect 0:06d9443a018f 308 memset(matrixbuff[backindex], c, 32 * nRows * 3);
lelect 0:06d9443a018f 309 } else {
lelect 0:06d9443a018f 310 // Otherwise, need to handle it the long way:
lelect 0:06d9443a018f 311 Adafruit_GFX::fillScreen(c);
lelect 0:06d9443a018f 312 }
lelect 0:06d9443a018f 313 }
lelect 0:06d9443a018f 314
lelect 0:06d9443a018f 315 // Return address of back buffer -- can then load/store data directly
lelect 0:06d9443a018f 316 uint8_t *RGBmatrixPanel::backBuffer()
lelect 0:06d9443a018f 317 {
lelect 0:06d9443a018f 318 return matrixbuff[backindex];
lelect 0:06d9443a018f 319 }
lelect 0:06d9443a018f 320
lelect 0:06d9443a018f 321 // For smooth animation -- drawing always takes place in the "back" buffer;
lelect 0:06d9443a018f 322 // this method pushes it to the "front" for display. Passing "true", the
lelect 0:06d9443a018f 323 // updated display contents are then copied to the new back buffer and can
lelect 0:06d9443a018f 324 // be incrementally modified. If "false", the back buffer then contains
lelect 0:06d9443a018f 325 // the old front buffer contents -- your code can either clear this or
lelect 0:06d9443a018f 326 // draw over every pixel. (No effect if double-buffering is not enabled.)
lelect 0:06d9443a018f 327 void RGBmatrixPanel::swapBuffers(bool copy)
lelect 0:06d9443a018f 328 {
lelect 0:06d9443a018f 329 if(matrixbuff[0] != matrixbuff[1]) {
lelect 0:06d9443a018f 330 // To avoid 'tearing' display, actual swap takes place in the interrupt
lelect 0:06d9443a018f 331 // handler, at the end of a complete screen refresh cycle.
lelect 0:06d9443a018f 332 swapflag = true; // Set flag here, then...
lelect 0:06d9443a018f 333 while(swapflag == true) delay(1); // wait for interrupt to clear it
lelect 0:06d9443a018f 334 if(copy == true)
lelect 0:06d9443a018f 335 memcpy(matrixbuff[backindex], matrixbuff[1-backindex], 32 * nRows * 3);
lelect 0:06d9443a018f 336 }
lelect 0:06d9443a018f 337 }
lelect 0:06d9443a018f 338
lelect 0:06d9443a018f 339 // Dump display contents to the Serial Monitor, adding some formatting to
lelect 0:06d9443a018f 340 // simplify copy-and-paste of data as a PROGMEM-embedded image for another
lelect 0:06d9443a018f 341 // sketch. If using multiple dumps this way, you'll need to edit the
lelect 0:06d9443a018f 342 // output to change the 'img' name for each. Data can then be loaded
lelect 0:06d9443a018f 343 // back into the display using a pgm_read_byte() loop.
lelect 0:06d9443a018f 344 void RGBmatrixPanel::dumpMatrix(void)
lelect 0:06d9443a018f 345 {
lelect 0:06d9443a018f 346
lelect 0:06d9443a018f 347 int i, buffsize = 32 * nRows * 3;
lelect 0:06d9443a018f 348
lelect 0:06d9443a018f 349 Serial.print("\n\n"
lelect 0:06d9443a018f 350 "#include <avr/pgmspace.h>\n\n"
lelect 0:06d9443a018f 351 "static const uint8_t PROGMEM img[] = {\n ");
lelect 0:06d9443a018f 352
lelect 0:06d9443a018f 353 for(i=0; i<buffsize; i++) {
lelect 0:06d9443a018f 354 Serial.print("0x");
lelect 0:06d9443a018f 355 if(matrixbuff[backindex][i] < 0x10) Serial.print('0');
lelect 0:06d9443a018f 356 Serial.print(matrixbuff[backindex][i],HEX);
lelect 0:06d9443a018f 357 if(i < (buffsize - 1)) {
lelect 0:06d9443a018f 358 if((i & 7) == 7) Serial.print(",\n ");
lelect 0:06d9443a018f 359 else Serial.print(',');
lelect 0:06d9443a018f 360 }
lelect 0:06d9443a018f 361 }
lelect 0:06d9443a018f 362 Serial.println("\n};");
lelect 0:06d9443a018f 363 }
lelect 0:06d9443a018f 364
lelect 0:06d9443a018f 365 // -------------------- Interrupt handler stuff --------------------
lelect 0:06d9443a018f 366
lelect 0:06d9443a018f 367 ISR(TIMER1_OVF_vect, ISR_BLOCK) // ISR_BLOCK important -- see notes later
lelect 0:06d9443a018f 368 {
lelect 0:06d9443a018f 369 activePanel->updateDisplay(); // Call refresh func for active display
lelect 0:06d9443a018f 370 TIFR1 |= TOV1; // Clear Timer1 interrupt flag
lelect 0:06d9443a018f 371 }
lelect 0:06d9443a018f 372
lelect 0:06d9443a018f 373 // Two constants are used in timing each successive BCM interval.
lelect 0:06d9443a018f 374 // These were found empirically, by checking the value of TCNT1 at
lelect 0:06d9443a018f 375 // certain positions in the interrupt code.
lelect 0:06d9443a018f 376 // CALLOVERHEAD is the number of CPU 'ticks' from the timer overflow
lelect 0:06d9443a018f 377 // condition (triggering the interrupt) to the first line in the
lelect 0:06d9443a018f 378 // updateDisplay() method. It's then assumed (maybe not entirely 100%
lelect 0:06d9443a018f 379 // accurately, but close enough) that a similar amount of time will be
lelect 0:06d9443a018f 380 // needed at the opposite end, restoring regular program flow.
lelect 0:06d9443a018f 381 // LOOPTIME is the number of 'ticks' spent inside the shortest data-
lelect 0:06d9443a018f 382 // issuing loop (not actually a 'loop' because it's unrolled, but eh).
lelect 0:06d9443a018f 383 // Both numbers are rounded up slightly to allow a little wiggle room
lelect 0:06d9443a018f 384 // should different compilers produce slightly different results.
lelect 0:06d9443a018f 385 #define CALLOVERHEAD 60 // Actual value measured = 56
lelect 0:06d9443a018f 386 #define LOOPTIME 200 // Actual value measured = 188
lelect 0:06d9443a018f 387 // The "on" time for bitplane 0 (with the shortest BCM interval) can
lelect 0:06d9443a018f 388 // then be estimated as LOOPTIME + CALLOVERHEAD * 2. Each successive
lelect 0:06d9443a018f 389 // bitplane then doubles the prior amount of time. We can then
lelect 0:06d9443a018f 390 // estimate refresh rates from this:
lelect 0:06d9443a018f 391 // 4 bitplanes = 320 + 640 + 1280 + 2560 = 4800 ticks per row.
lelect 0:06d9443a018f 392 // 4800 ticks * 16 rows (for 32x32 matrix) = 76800 ticks/frame.
lelect 0:06d9443a018f 393 // 16M CPU ticks/sec / 76800 ticks/frame = 208.33 Hz.
lelect 0:06d9443a018f 394 // Actual frame rate will be slightly less due to work being done
lelect 0:06d9443a018f 395 // during the brief "LEDs off" interval...it's reasonable to say
lelect 0:06d9443a018f 396 // "about 200 Hz." The 16x32 matrix only has to scan half as many
lelect 0:06d9443a018f 397 // rows...so we could either double the refresh rate (keeping the CPU
lelect 0:06d9443a018f 398 // load the same), or keep the same refresh rate but halve the CPU
lelect 0:06d9443a018f 399 // load. We opted for the latter.
lelect 0:06d9443a018f 400 // Can also estimate CPU use: bitplanes 1-3 all use 320 ticks to
lelect 0:06d9443a018f 401 // issue data (the increasing gaps in the timing invervals are then
lelect 0:06d9443a018f 402 // available to other code), and bitplane 0 takes 920 ticks out of
lelect 0:06d9443a018f 403 // the 2560 tick interval.
lelect 0:06d9443a018f 404 // 320 * 3 + 920 = 1880 ticks spent in interrupt code, per row.
lelect 0:06d9443a018f 405 // From prior calculations, about 4800 ticks happen per row.
lelect 0:06d9443a018f 406 // CPU use = 1880 / 4800 = ~39% (actual use will be very slightly
lelect 0:06d9443a018f 407 // higher, again due to code used in the LEDs off interval).
lelect 0:06d9443a018f 408 // 16x32 matrix uses about half that CPU load. CPU time could be
lelect 0:06d9443a018f 409 // further adjusted by padding the LOOPTIME value, but refresh rates
lelect 0:06d9443a018f 410 // will decrease proportionally, and 200 Hz is a decent target.
lelect 0:06d9443a018f 411
lelect 0:06d9443a018f 412 // The flow of the interrupt can be awkward to grasp, because data is
lelect 0:06d9443a018f 413 // being issued to the LED matrix for the *next* bitplane and/or row
lelect 0:06d9443a018f 414 // while the *current* plane/row is being shown. As a result, the
lelect 0:06d9443a018f 415 // counter variables change between past/present/future tense in mid-
lelect 0:06d9443a018f 416 // function...hopefully tenses are sufficiently commented.
lelect 0:06d9443a018f 417
lelect 0:06d9443a018f 418 void RGBmatrixPanel::updateDisplay(void)
lelect 0:06d9443a018f 419 {
lelect 0:06d9443a018f 420 uint8_t i, tick, tock, *ptr;
lelect 0:06d9443a018f 421 uint16_t t, duration;
lelect 0:06d9443a018f 422
lelect 0:06d9443a018f 423 *oeport |= oepin; // Disable LED output during row/plane switchover
lelect 0:06d9443a018f 424 *latport |= latpin; // Latch data loaded during *prior* interrupt
lelect 0:06d9443a018f 425
lelect 0:06d9443a018f 426 // Calculate time to next interrupt BEFORE incrementing plane #.
lelect 0:06d9443a018f 427 // This is because duration is the display time for the data loaded
lelect 0:06d9443a018f 428 // on the PRIOR interrupt. CALLOVERHEAD is subtracted from the
lelect 0:06d9443a018f 429 // result because that time is implicit between the timer overflow
lelect 0:06d9443a018f 430 // (interrupt triggered) and the initial LEDs-off line at the start
lelect 0:06d9443a018f 431 // of this method.
lelect 0:06d9443a018f 432 t = (nRows > 8) ? LOOPTIME : (LOOPTIME * 2);
lelect 0:06d9443a018f 433 duration = ((t + CALLOVERHEAD * 2) << plane) - CALLOVERHEAD;
lelect 0:06d9443a018f 434
lelect 0:06d9443a018f 435 // Borrowing a technique here from Ray's Logic:
lelect 0:06d9443a018f 436 // www.rayslogic.com/propeller/Programming/AdafruitRGB/AdafruitRGB.htm
lelect 0:06d9443a018f 437 // This code cycles through all four planes for each scanline before
lelect 0:06d9443a018f 438 // advancing to the next line. While it might seem beneficial to
lelect 0:06d9443a018f 439 // advance lines every time and interleave the planes to reduce
lelect 0:06d9443a018f 440 // vertical scanning artifacts, in practice with this panel it causes
lelect 0:06d9443a018f 441 // a green 'ghosting' effect on black pixels, a much worse artifact.
lelect 0:06d9443a018f 442
lelect 0:06d9443a018f 443 if(++plane >= nPlanes) { // Advance plane counter. Maxed out?
lelect 0:06d9443a018f 444 plane = 0; // Yes, reset to plane 0, and
lelect 0:06d9443a018f 445 if(++row >= nRows) { // advance row counter. Maxed out?
lelect 0:06d9443a018f 446 row = 0; // Yes, reset row counter, then...
lelect 0:06d9443a018f 447 if(swapflag == true) { // Swap front/back buffers if requested
lelect 0:06d9443a018f 448 backindex = 1 - backindex;
lelect 0:06d9443a018f 449 swapflag = false;
lelect 0:06d9443a018f 450 }
lelect 0:06d9443a018f 451 buffptr = matrixbuff[1-backindex]; // Reset into front buffer
lelect 0:06d9443a018f 452 }
lelect 0:06d9443a018f 453 } else if(plane == 1) {
lelect 0:06d9443a018f 454 // Plane 0 was loaded on prior interrupt invocation and is about to
lelect 0:06d9443a018f 455 // latch now, so update the row address lines before we do that:
lelect 0:06d9443a018f 456 if(row & 0x1) *addraport |= addrapin;
lelect 0:06d9443a018f 457 else *addraport &= ~addrapin;
lelect 0:06d9443a018f 458 if(row & 0x2) *addrbport |= addrbpin;
lelect 0:06d9443a018f 459 else *addrbport &= ~addrbpin;
lelect 0:06d9443a018f 460 if(row & 0x4) *addrcport |= addrcpin;
lelect 0:06d9443a018f 461 else *addrcport &= ~addrcpin;
lelect 0:06d9443a018f 462 if(nRows > 8) {
lelect 0:06d9443a018f 463 if(row & 0x8) *addrdport |= addrdpin;
lelect 0:06d9443a018f 464 else *addrdport &= ~addrdpin;
lelect 0:06d9443a018f 465 }
lelect 0:06d9443a018f 466 }
lelect 0:06d9443a018f 467
lelect 0:06d9443a018f 468 // buffptr, being 'volatile' type, doesn't take well to optimization.
lelect 0:06d9443a018f 469 // A local register copy can speed some things up:
lelect 0:06d9443a018f 470 ptr = (uint8_t *)buffptr;
lelect 0:06d9443a018f 471
lelect 0:06d9443a018f 472 ICR1 = duration; // Set interval for next interrupt
lelect 0:06d9443a018f 473 TCNT1 = 0; // Restart interrupt timer
lelect 0:06d9443a018f 474 *oeport &= ~oepin; // Re-enable output
lelect 0:06d9443a018f 475 *latport &= ~latpin; // Latch down
lelect 0:06d9443a018f 476
lelect 0:06d9443a018f 477 // Record current state of SCLKPORT register, as well as a second
lelect 0:06d9443a018f 478 // copy with the clock bit set. This makes the innnermost data-
lelect 0:06d9443a018f 479 // pushing loops faster, as they can just set the PORT state and
lelect 0:06d9443a018f 480 // not have to load/modify/store bits every single time. It's a
lelect 0:06d9443a018f 481 // somewhat rude trick that ONLY works because the interrupt
lelect 0:06d9443a018f 482 // handler is set ISR_BLOCK, halting any other interrupts that
lelect 0:06d9443a018f 483 // might otherwise also be twiddling the port at the same time
lelect 0:06d9443a018f 484 // (else this would clobber them).
lelect 0:06d9443a018f 485 tock = SCLKPORT;
lelect 0:06d9443a018f 486 tick = tock | sclkpin;
lelect 0:06d9443a018f 487
lelect 0:06d9443a018f 488 if(plane > 0) { // 188 ticks from TCNT1=0 (above) to end of function
lelect 0:06d9443a018f 489
lelect 0:06d9443a018f 490 // Planes 1-3 copy bytes directly from RAM to PORT without unpacking.
lelect 0:06d9443a018f 491 // The least 2 bits (used for plane 0 data) are presumed masked out
lelect 0:06d9443a018f 492 // by the port direction bits.
lelect 0:06d9443a018f 493
lelect 0:06d9443a018f 494 // A tiny bit of inline assembly is used; compiler doesn't pick
lelect 0:06d9443a018f 495 // up on opportunity for post-increment addressing mode.
lelect 0:06d9443a018f 496 // 5 instruction ticks per 'pew' = 160 ticks total
lelect 0:06d9443a018f 497 #define pew asm volatile( \
lelect 0:06d9443a018f 498 "ld __tmp_reg__, %a[ptr]+" "\n\t" \
lelect 0:06d9443a018f 499 "out %[data] , __tmp_reg__" "\n\t" \
lelect 0:06d9443a018f 500 "out %[clk] , %[tick]" "\n\t" \
lelect 0:06d9443a018f 501 "out %[clk] , %[tock]" "\n" \
lelect 0:06d9443a018f 502 :: [ptr] "e" (ptr), \
lelect 0:06d9443a018f 503 [data] "I" (_SFR_IO_ADDR(DATAPORT)), \
lelect 0:06d9443a018f 504 [clk] "I" (_SFR_IO_ADDR(SCLKPORT)), \
lelect 0:06d9443a018f 505 [tick] "r" (tick), \
lelect 0:06d9443a018f 506 [tock] "r" (tock));
lelect 0:06d9443a018f 507
lelect 0:06d9443a018f 508 // Loop is unrolled for speed:
lelect 0:06d9443a018f 509 pew pew pew pew pew pew pew pew
lelect 0:06d9443a018f 510 pew pew pew pew pew pew pew pew
lelect 0:06d9443a018f 511 pew pew pew pew pew pew pew pew
lelect 0:06d9443a018f 512 pew pew pew pew pew pew pew pew
lelect 0:06d9443a018f 513
lelect 0:06d9443a018f 514 buffptr += 32;
lelect 0:06d9443a018f 515
lelect 0:06d9443a018f 516 } else { // 920 ticks from TCNT1=0 (above) to end of function
lelect 0:06d9443a018f 517
lelect 0:06d9443a018f 518 // Planes 1-3 (handled above) formatted their data "in place,"
lelect 0:06d9443a018f 519 // their layout matching that out the output PORT register (where
lelect 0:06d9443a018f 520 // 6 bits correspond to output data lines), maximizing throughput
lelect 0:06d9443a018f 521 // as no conversion or unpacking is needed. Plane 0 then takes up
lelect 0:06d9443a018f 522 // the slack, with all its data packed into the 2 least bits not
lelect 0:06d9443a018f 523 // used by the other planes. This works because the unpacking and
lelect 0:06d9443a018f 524 // output for plane 0 is handled while plane 3 is being displayed...
lelect 0:06d9443a018f 525 // because binary coded modulation is used (not PWM), that plane
lelect 0:06d9443a018f 526 // has the longest display interval, so the extra work fits.
lelect 0:06d9443a018f 527 for(i=0; i<32; i++) {
lelect 0:06d9443a018f 528 DATAPORT =
lelect 0:06d9443a018f 529 ( ptr[i] << 6) |
lelect 0:06d9443a018f 530 ((ptr[i+32] << 4) & 0x30) |
lelect 0:06d9443a018f 531 ((ptr[i+64] << 2) & 0x0C);
lelect 0:06d9443a018f 532 SCLKPORT = tick; // Clock lo
lelect 0:06d9443a018f 533 SCLKPORT = tock; // Clock hi
lelect 0:06d9443a018f 534 }
lelect 0:06d9443a018f 535 }
lelect 0:06d9443a018f 536 }