Ryan Savitski
/
LED_multiple_panels
9 tile screen working with one image space, platform for development
ledScreen.h@0:8b26631e8c70, 2012-03-06 (annotated)
- Committer:
- rsavitski
- Date:
- Tue Mar 06 19:58:34 2012 +0000
- Revision:
- 0:8b26631e8c70
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
rsavitski | 0:8b26631e8c70 | 1 | #include "mbed.h" |
rsavitski | 0:8b26631e8c70 | 2 | |
rsavitski | 0:8b26631e8c70 | 3 | /* |
rsavitski | 0:8b26631e8c70 | 4 | TODO: wasrunning - works as intended? |
rsavitski | 0:8b26631e8c70 | 5 | TODO: OPTIME - tweak value |
rsavitski | 0:8b26631e8c70 | 6 | |
rsavitski | 0:8b26631e8c70 | 7 | */ |
rsavitski | 0:8b26631e8c70 | 8 | |
rsavitski | 0:8b26631e8c70 | 9 | extern "C" void frameout(unsigned char dsVal[], unsigned char transformedSource[]); |
rsavitski | 0:8b26631e8c70 | 10 | |
rsavitski | 0:8b26631e8c70 | 11 | class ledScreen { |
rsavitski | 0:8b26631e8c70 | 12 | public: |
rsavitski | 0:8b26631e8c70 | 13 | ledScreen(); |
rsavitski | 0:8b26631e8c70 | 14 | ~ledScreen() {} |
rsavitski | 0:8b26631e8c70 | 15 | |
rsavitski | 0:8b26631e8c70 | 16 | void transformFrame(unsigned char* imageSource); |
rsavitski | 0:8b26631e8c70 | 17 | void outputFrame(); |
rsavitski | 0:8b26631e8c70 | 18 | void start(); // start outputting frames on an interrupt |
rsavitski | 0:8b26631e8c70 | 19 | |
rsavitski | 0:8b26631e8c70 | 20 | private: |
rsavitski | 0:8b26631e8c70 | 21 | |
rsavitski | 0:8b26631e8c70 | 22 | int MAX_PULSE_WIDTH; // constant: max enable pulse duration |
rsavitski | 0:8b26631e8c70 | 23 | int pulseLength; // length of current pulse (used in delta-sigma pwm) |
rsavitski | 0:8b26631e8c70 | 24 | int OP_TIME; |
rsavitski | 0:8b26631e8c70 | 25 | |
rsavitski | 0:8b26631e8c70 | 26 | static const int XPANS = 3; // number of panels horizontally |
rsavitski | 0:8b26631e8c70 | 27 | static const int YUNITS = 1; |
rsavitski | 0:8b26631e8c70 | 28 | static const int YPANS = 3; // 3* YUNITS |
rsavitski | 0:8b26631e8c70 | 29 | static const int PIXPERPAN = 256; |
rsavitski | 0:8b26631e8c70 | 30 | |
rsavitski | 0:8b26631e8c70 | 31 | int running; |
rsavitski | 0:8b26631e8c70 | 32 | int subFrameCtr; |
rsavitski | 0:8b26631e8c70 | 33 | |
rsavitski | 0:8b26631e8c70 | 34 | Timeout nextFrameTimer; // timeout routine |
rsavitski | 0:8b26631e8c70 | 35 | |
rsavitski | 0:8b26631e8c70 | 36 | // Buffers to hold the RGB data after rearranging to match the LED shifting pattern |
rsavitski | 0:8b26631e8c70 | 37 | unsigned char transformedSource[3*PIXPERPAN*XPANS*YPANS]; |
rsavitski | 0:8b26631e8c70 | 38 | |
rsavitski | 0:8b26631e8c70 | 39 | // Error values for all 256 brightness levels |
rsavitski | 0:8b26631e8c70 | 40 | unsigned int dsErr[256]; |
rsavitski | 0:8b26631e8c70 | 41 | unsigned int ssdsErr[256]; |
rsavitski | 0:8b26631e8c70 | 42 | |
rsavitski | 0:8b26631e8c70 | 43 | // On/off state per sub-frame for all 256 brightness levels |
rsavitski | 0:8b26631e8c70 | 44 | unsigned char dsVal[256]; |
rsavitski | 0:8b26631e8c70 | 45 | |
rsavitski | 0:8b26631e8c70 | 46 | // Precomputed gamma for all 256 brightness levels |
rsavitski | 0:8b26631e8c70 | 47 | unsigned short gamma[256]; |
rsavitski | 0:8b26631e8c70 | 48 | |
rsavitski | 0:8b26631e8c70 | 49 | |
rsavitski | 0:8b26631e8c70 | 50 | DigitalOut flatch; // data latch (for all connected panels in parallel) |
rsavitski | 0:8b26631e8c70 | 51 | DigitalOut MA0; // module address 0 |
rsavitski | 0:8b26631e8c70 | 52 | DigitalOut MA1; |
rsavitski | 0:8b26631e8c70 | 53 | DigitalOut NREN; // active low enable for red channel (low -> LED on). Note: need to have enable high when latching data |
rsavitski | 0:8b26631e8c70 | 54 | DigitalOut Rdat0; // red data |
rsavitski | 0:8b26631e8c70 | 55 | DigitalOut Gdat0; // green data |
rsavitski | 0:8b26631e8c70 | 56 | DigitalOut Bdat0; // blue data |
rsavitski | 0:8b26631e8c70 | 57 | DigitalOut Rdat1; // red data |
rsavitski | 0:8b26631e8c70 | 58 | DigitalOut Gdat1; // green data |
rsavitski | 0:8b26631e8c70 | 59 | DigitalOut Bdat1; // blue data |
rsavitski | 0:8b26631e8c70 | 60 | DigitalOut Rdat2; // red data |
rsavitski | 0:8b26631e8c70 | 61 | DigitalOut Gdat2; // green data |
rsavitski | 0:8b26631e8c70 | 62 | DigitalOut Bdat2; // blue data |
rsavitski | 0:8b26631e8c70 | 63 | DigitalOut sclk; // clock |
rsavitski | 0:8b26631e8c70 | 64 | |
rsavitski | 0:8b26631e8c70 | 65 | DigitalOut debug; |
rsavitski | 0:8b26631e8c70 | 66 | |
rsavitski | 0:8b26631e8c70 | 67 | }; |
rsavitski | 0:8b26631e8c70 | 68 | |
rsavitski | 0:8b26631e8c70 | 69 | ledScreen::ledScreen() : |
rsavitski | 0:8b26631e8c70 | 70 | flatch(p10), // data latch (for all connected panels in parallel) |
rsavitski | 0:8b26631e8c70 | 71 | MA0(p18), // module address 0 |
rsavitski | 0:8b26631e8c70 | 72 | MA1(p19), |
rsavitski | 0:8b26631e8c70 | 73 | NREN(p9), // active low enable for red channel (low -> LED on). Note: need to have enable high when latching data |
rsavitski | 0:8b26631e8c70 | 74 | Rdat0(p15), // red data |
rsavitski | 0:8b26631e8c70 | 75 | Gdat0(p16), // green data |
rsavitski | 0:8b26631e8c70 | 76 | Bdat0(p17), // blue data |
rsavitski | 0:8b26631e8c70 | 77 | Rdat1(p7), // red data |
rsavitski | 0:8b26631e8c70 | 78 | Gdat1(p6), // green data |
rsavitski | 0:8b26631e8c70 | 79 | Bdat1(p5), // blue data |
rsavitski | 0:8b26631e8c70 | 80 | Rdat2(p13), // red data |
rsavitski | 0:8b26631e8c70 | 81 | Gdat2(p12), // green data |
rsavitski | 0:8b26631e8c70 | 82 | Bdat2(p11), // blue data |
rsavitski | 0:8b26631e8c70 | 83 | sclk(p14), |
rsavitski | 0:8b26631e8c70 | 84 | debug(p27) { // clock |
rsavitski | 0:8b26631e8c70 | 85 | |
rsavitski | 0:8b26631e8c70 | 86 | // precompute gamma for every possible RGB intensity value (0-255). |
rsavitski | 0:8b26631e8c70 | 87 | // Gamma correction with gamma = 3, downshifting by 8 to bring the range of values back to 0-65535 |
rsavitski | 0:8b26631e8c70 | 88 | for (int i=0; i<256; i++) { |
rsavitski | 0:8b26631e8c70 | 89 | gamma[i] = pow(i, 2.2) * 0.33;//(i*i*i)>>8; |
rsavitski | 0:8b26631e8c70 | 90 | } |
rsavitski | 0:8b26631e8c70 | 91 | |
rsavitski | 0:8b26631e8c70 | 92 | // initialising lines |
rsavitski | 0:8b26631e8c70 | 93 | flatch = 1; |
rsavitski | 0:8b26631e8c70 | 94 | NREN = 1; |
rsavitski | 0:8b26631e8c70 | 95 | sclk = 1; |
rsavitski | 0:8b26631e8c70 | 96 | |
rsavitski | 0:8b26631e8c70 | 97 | // initialising values |
rsavitski | 0:8b26631e8c70 | 98 | MAX_PULSE_WIDTH = 512; //must currently be a power of 2, and when changing this, you must change the ssdsErr crossover masking |
rsavitski | 0:8b26631e8c70 | 99 | pulseLength = MAX_PULSE_WIDTH; |
rsavitski | 0:8b26631e8c70 | 100 | OP_TIME = 510; //Determined by scoping. Change this every time you change num screens |
rsavitski | 0:8b26631e8c70 | 101 | //NUM_PANELS = 3 |
rsavitski | 0:8b26631e8c70 | 102 | |
rsavitski | 0:8b26631e8c70 | 103 | running=0; |
rsavitski | 0:8b26631e8c70 | 104 | subFrameCtr=0; |
rsavitski | 0:8b26631e8c70 | 105 | |
rsavitski | 0:8b26631e8c70 | 106 | // initialising errors for delta-sigma |
rsavitski | 0:8b26631e8c70 | 107 | for (int j=0; j<256; j++) { |
rsavitski | 0:8b26631e8c70 | 108 | dsErr[j] = 0; |
rsavitski | 0:8b26631e8c70 | 109 | ssdsErr[j] = 0; |
rsavitski | 0:8b26631e8c70 | 110 | } |
rsavitski | 0:8b26631e8c70 | 111 | |
rsavitski | 0:8b26631e8c70 | 112 | } |
rsavitski | 0:8b26631e8c70 | 113 | |
rsavitski | 0:8b26631e8c70 | 114 | void ledScreen::start() { |
rsavitski | 0:8b26631e8c70 | 115 | running=1; |
rsavitski | 0:8b26631e8c70 | 116 | outputFrame(); |
rsavitski | 0:8b26631e8c70 | 117 | } |
rsavitski | 0:8b26631e8c70 | 118 | |
rsavitski | 0:8b26631e8c70 | 119 | |
rsavitski | 0:8b26631e8c70 | 120 | |
rsavitski | 0:8b26631e8c70 | 121 | void ledScreen::transformFrame(unsigned char* imageSource) |
rsavitski | 0:8b26631e8c70 | 122 | { |
rsavitski | 0:8b26631e8c70 | 123 | int i=0; |
rsavitski | 0:8b26631e8c70 | 124 | int panseqnum=0, t=0, out=0, x=0, y=0, MA=0; |
rsavitski | 0:8b26631e8c70 | 125 | |
rsavitski | 0:8b26631e8c70 | 126 | for (int q=0; q < 256*3*3*3; q+=3) |
rsavitski | 0:8b26631e8c70 | 127 | { |
rsavitski | 0:8b26631e8c70 | 128 | i = q/3; |
rsavitski | 0:8b26631e8c70 | 129 | |
rsavitski | 0:8b26631e8c70 | 130 | x = i % (16*XPANS); |
rsavitski | 0:8b26631e8c70 | 131 | y = i / (16*XPANS); |
rsavitski | 0:8b26631e8c70 | 132 | |
rsavitski | 0:8b26631e8c70 | 133 | |
rsavitski | 0:8b26631e8c70 | 134 | int MA = (y/16) % 3; |
rsavitski | 0:8b26631e8c70 | 135 | panseqnum = x/16 + y/(16*3) * XPANS; |
rsavitski | 0:8b26631e8c70 | 136 | |
rsavitski | 0:8b26631e8c70 | 137 | if (y%2 == 0) |
rsavitski | 0:8b26631e8c70 | 138 | { |
rsavitski | 0:8b26631e8c70 | 139 | t = (y%16)/2*0x20 + ((x%16)/8*0x10+(7-(x%16)%8)); |
rsavitski | 0:8b26631e8c70 | 140 | } |
rsavitski | 0:8b26631e8c70 | 141 | else |
rsavitski | 0:8b26631e8c70 | 142 | { |
rsavitski | 0:8b26631e8c70 | 143 | t = 8 + (y%16)/2*0x20 + ((x%16)/8*0x10+(x%16)%8); |
rsavitski | 0:8b26631e8c70 | 144 | } |
rsavitski | 0:8b26631e8c70 | 145 | |
rsavitski | 0:8b26631e8c70 | 146 | out = 3*(MA * YUNITS * XPANS * 256 + t * XPANS * YUNITS + panseqnum); |
rsavitski | 0:8b26631e8c70 | 147 | |
rsavitski | 0:8b26631e8c70 | 148 | transformedSource[out] = imageSource[q]; |
rsavitski | 0:8b26631e8c70 | 149 | transformedSource[out+1] = imageSource[q+1]; |
rsavitski | 0:8b26631e8c70 | 150 | transformedSource[out+2] = imageSource[q+2]; |
rsavitski | 0:8b26631e8c70 | 151 | } |
rsavitski | 0:8b26631e8c70 | 152 | |
rsavitski | 0:8b26631e8c70 | 153 | } |
rsavitski | 0:8b26631e8c70 | 154 | |
rsavitski | 0:8b26631e8c70 | 155 | // Output one frame and call itself after a period of time if running is set to true |
rsavitski | 0:8b26631e8c70 | 156 | void ledScreen::outputFrame() { |
rsavitski | 0:8b26631e8c70 | 157 | |
rsavitski | 0:8b26631e8c70 | 158 | debug = 1; |
rsavitski | 0:8b26631e8c70 | 159 | |
rsavitski | 0:8b26631e8c70 | 160 | if (pulseLength != MAX_PULSE_WIDTH) |
rsavitski | 0:8b26631e8c70 | 161 | NREN = 0; // turn off |
rsavitski | 0:8b26631e8c70 | 162 | |
rsavitski | 0:8b26631e8c70 | 163 | if (subFrameCtr<=0) subFrameCtr=36; |
rsavitski | 0:8b26631e8c70 | 164 | subFrameCtr--; |
rsavitski | 0:8b26631e8c70 | 165 | |
rsavitski | 0:8b26631e8c70 | 166 | if (subFrameCtr == 0) { // Every cycle of delta sigma we take a snapshot of the error that needs to be corrected by the short pulses. |
rsavitski | 0:8b26631e8c70 | 167 | for (int i = 0; i < 256; i++) { // This is required to eliminate visible flicker due to beat frequencies otherwise created. |
rsavitski | 0:8b26631e8c70 | 168 | dsErr[i] += ssdsErr[i] & 0xFE000000; |
rsavitski | 0:8b26631e8c70 | 169 | ssdsErr[i] %= 0x10000; |
rsavitski | 0:8b26631e8c70 | 170 | ssdsErr[i] += dsErr[i] % (512 * 0x10000); |
rsavitski | 0:8b26631e8c70 | 171 | dsErr[i] &= 0xFE000000; |
rsavitski | 0:8b26631e8c70 | 172 | } |
rsavitski | 0:8b26631e8c70 | 173 | |
rsavitski | 0:8b26631e8c70 | 174 | // Doing delta sigma for the snapshot |
rsavitski | 0:8b26631e8c70 | 175 | for (int i = 0; i <= 9; i++) { |
rsavitski | 0:8b26631e8c70 | 176 | int lpl = 1<<i; |
rsavitski | 0:8b26631e8c70 | 177 | |
rsavitski | 0:8b26631e8c70 | 178 | if (ssdsErr[i]/0x10000 & lpl) |
rsavitski | 0:8b26631e8c70 | 179 | ssdsErr[i]-=(0x10000-gamma[i])*lpl; |
rsavitski | 0:8b26631e8c70 | 180 | else |
rsavitski | 0:8b26631e8c70 | 181 | ssdsErr[i]+=gamma[i]*lpl; |
rsavitski | 0:8b26631e8c70 | 182 | } |
rsavitski | 0:8b26631e8c70 | 183 | |
rsavitski | 0:8b26631e8c70 | 184 | } |
rsavitski | 0:8b26631e8c70 | 185 | |
rsavitski | 0:8b26631e8c70 | 186 | // produce pulse lengths of 1, 2, 4, ... 256, spread throughout all subframes (only one in four are not MAX_PULSE_WIDTH long) |
rsavitski | 0:8b26631e8c70 | 187 | pulseLength = ((subFrameCtr%4)?MAX_PULSE_WIDTH:(1<<(subFrameCtr>>2))); |
rsavitski | 0:8b26631e8c70 | 188 | |
rsavitski | 0:8b26631e8c70 | 189 | for (int i = 0; i < 256; i++) { |
rsavitski | 0:8b26631e8c70 | 190 | if (pulseLength == MAX_PULSE_WIDTH) { |
rsavitski | 0:8b26631e8c70 | 191 | // Delta-Sigma modulation with variable pulse length weighting |
rsavitski | 0:8b26631e8c70 | 192 | // Based on energy dimensions (time * amplitude) |
rsavitski | 0:8b26631e8c70 | 193 | if (dsErr[i] > (0x10000-gamma[i])*pulseLength) { |
rsavitski | 0:8b26631e8c70 | 194 | dsVal[i] = 0;//-1; Invert as we are using inverting buffers |
rsavitski | 0:8b26631e8c70 | 195 | dsErr[i]-=(0x10000-gamma[i])*pulseLength; |
rsavitski | 0:8b26631e8c70 | 196 | } else { |
rsavitski | 0:8b26631e8c70 | 197 | dsVal[i] = (unsigned char)-1; |
rsavitski | 0:8b26631e8c70 | 198 | dsErr[i]+=gamma[i]*pulseLength; |
rsavitski | 0:8b26631e8c70 | 199 | } |
rsavitski | 0:8b26631e8c70 | 200 | } else { // if short pulse |
rsavitski | 0:8b26631e8c70 | 201 | if (ssdsErr[i]/0x10000 & pulseLength) { |
rsavitski | 0:8b26631e8c70 | 202 | //Doing proper least significant delta sigma live still causes flicker (but only for dim pixels) |
rsavitski | 0:8b26631e8c70 | 203 | //ssdsErr[i]-=(0x10000-gamma[i])*pulseLength; |
rsavitski | 0:8b26631e8c70 | 204 | dsVal[i] = 0; |
rsavitski | 0:8b26631e8c70 | 205 | } else { |
rsavitski | 0:8b26631e8c70 | 206 | dsVal[i] = (unsigned char)-1; |
rsavitski | 0:8b26631e8c70 | 207 | } |
rsavitski | 0:8b26631e8c70 | 208 | |
rsavitski | 0:8b26631e8c70 | 209 | } |
rsavitski | 0:8b26631e8c70 | 210 | } |
rsavitski | 0:8b26631e8c70 | 211 | |
rsavitski | 0:8b26631e8c70 | 212 | // output data |
rsavitski | 0:8b26631e8c70 | 213 | for (int i = 0; i < 3; i++) { //FIX |
rsavitski | 0:8b26631e8c70 | 214 | MA0 = !(i&1); |
rsavitski | 0:8b26631e8c70 | 215 | MA1 = !(i&2); |
rsavitski | 0:8b26631e8c70 | 216 | |
rsavitski | 0:8b26631e8c70 | 217 | frameout(dsVal, &transformedSource[i*256*3*3]); |
rsavitski | 0:8b26631e8c70 | 218 | } |
rsavitski | 0:8b26631e8c70 | 219 | |
rsavitski | 0:8b26631e8c70 | 220 | NREN = 0; // need to have enables high before every latch, (in case we are on a long pulse) |
rsavitski | 0:8b26631e8c70 | 221 | flatch = 0; // latching all data to LEDs |
rsavitski | 0:8b26631e8c70 | 222 | flatch = 1; |
rsavitski | 0:8b26631e8c70 | 223 | NREN = 1; // turn on LEDs |
rsavitski | 0:8b26631e8c70 | 224 | |
rsavitski | 0:8b26631e8c70 | 225 | if (pulseLength < 4) { // short pulses done through wait |
rsavitski | 0:8b26631e8c70 | 226 | wait_us(pulseLength); |
rsavitski | 0:8b26631e8c70 | 227 | NREN = 0; //Turn off LEDs |
rsavitski | 0:8b26631e8c70 | 228 | |
rsavitski | 0:8b26631e8c70 | 229 | bool wasrunning = running; |
rsavitski | 0:8b26631e8c70 | 230 | running = false; |
rsavitski | 0:8b26631e8c70 | 231 | outputFrame(); //this will recurse only once due to the distrubution of pulses. pulseLength of the next instance will be attached. |
rsavitski | 0:8b26631e8c70 | 232 | running = wasrunning; |
rsavitski | 0:8b26631e8c70 | 233 | } |
rsavitski | 0:8b26631e8c70 | 234 | // long waits done through attaching an interrupt that will turn off the LEDs at the start of next function call. |
rsavitski | 0:8b26631e8c70 | 235 | // Meanwhile, the main code can run between the interrupts. |
rsavitski | 0:8b26631e8c70 | 236 | if (running) nextFrameTimer.attach_us(this, &ledScreen::outputFrame, (pulseLength == MAX_PULSE_WIDTH) ? pulseLength - OP_TIME : pulseLength); |
rsavitski | 0:8b26631e8c70 | 237 | debug = 0; |
rsavitski | 0:8b26631e8c70 | 238 | } |