Adafruit-RGB_matrix_Panel(32*16)

Dependencies:   Adafruit-GFX

Committer:
lelect
Date:
Sat May 24 11:43:34 2014 +0000
Revision:
2:6136465ffd3a
Parent:
1:0078213d3fa4
Child:
3:aa3762e0dfee
log_debug add;

Who changed what in which revision?

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