Adafruit-RGB_matrix_Panel(32*32)
Dependencies: Adafruit-GFX
RGBmatrixPanel.cpp@0:06d9443a018f, 2014-05-23 (annotated)
- 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?
User | Revision | Line number | New 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 | } |