![](/media/cache/img/default_profile.jpg.50x50_q85.jpg)
LED screen driver build for hackspace.
ledScreen.h@1:1af5060b2a34, 2012-02-29 (annotated)
- Committer:
- madcowswe
- Date:
- Wed Feb 29 17:09:46 2012 +0000
- Revision:
- 1:1af5060b2a34
- Parent:
- 0:f16a1d69a386
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
madcowswe | 0:f16a1d69a386 | 1 | #include "mbed.h" |
madcowswe | 0:f16a1d69a386 | 2 | |
madcowswe | 0:f16a1d69a386 | 3 | /* |
madcowswe | 0:f16a1d69a386 | 4 | ****************************************************** |
madcowswe | 0:f16a1d69a386 | 5 | *Hacked together by Imperial College Robotics Society* |
madcowswe | 0:f16a1d69a386 | 6 | ****************************************************** |
madcowswe | 0:f16a1d69a386 | 7 | |
madcowswe | 0:f16a1d69a386 | 8 | Usage: |
madcowswe | 0:f16a1d69a386 | 9 | Example code supplied (main.cpp). Format of data input: 8bit RGB channels per pixel sequential in one framebuffer. |
madcowswe | 0:f16a1d69a386 | 10 | |
madcowswe | 0:f16a1d69a386 | 11 | Note: |
madcowswe | 0:f16a1d69a386 | 12 | This version uses inverted outputs (to bring voltage from 3.3 to 5v and drive more current) for data, clock and address lines. |
madcowswe | 0:f16a1d69a386 | 13 | |
madcowswe | 0:f16a1d69a386 | 14 | Quirks of current platform: |
madcowswe | 1:1af5060b2a34 | 15 | transformFrame crammed into the code, should be united with transformFrame2 for proper orientation |
madcowswe | 1:1af5060b2a34 | 16 | |
madcowswe | 0:f16a1d69a386 | 17 | address lines are put through inverters, code still thinks it's addressing with logic 1's, |
madcowswe | 0:f16a1d69a386 | 18 | whereas in reality MA0 is inverted and put into the ribbon cable line of MA1 and vice-versa |
madcowswe | 0:f16a1d69a386 | 19 | |
madcowswe | 0:f16a1d69a386 | 20 | */ |
madcowswe | 0:f16a1d69a386 | 21 | |
madcowswe | 0:f16a1d69a386 | 22 | extern "C" void frameout(unsigned char dsVal[], unsigned char transformedSource[]); |
madcowswe | 0:f16a1d69a386 | 23 | |
madcowswe | 0:f16a1d69a386 | 24 | class ledScreen { |
madcowswe | 0:f16a1d69a386 | 25 | public: |
madcowswe | 0:f16a1d69a386 | 26 | ledScreen(); |
madcowswe | 0:f16a1d69a386 | 27 | ~ledScreen() {} |
madcowswe | 0:f16a1d69a386 | 28 | |
madcowswe | 0:f16a1d69a386 | 29 | void transformFrame(unsigned char* imageSource); |
madcowswe | 0:f16a1d69a386 | 30 | void transformFrame2(unsigned char* imageSource); |
madcowswe | 0:f16a1d69a386 | 31 | void outputFrame(); |
madcowswe | 0:f16a1d69a386 | 32 | void start(); // start outputting frames on an interrupt |
madcowswe | 0:f16a1d69a386 | 33 | |
madcowswe | 0:f16a1d69a386 | 34 | private: |
madcowswe | 0:f16a1d69a386 | 35 | |
madcowswe | 0:f16a1d69a386 | 36 | int MAX_PULSE_WIDTH; // constant: max enable pulse duration |
madcowswe | 0:f16a1d69a386 | 37 | int pulseLength; // length of current pulse (used in delta-sigma pwm) |
madcowswe | 0:f16a1d69a386 | 38 | int OP_TIME; |
madcowswe | 0:f16a1d69a386 | 39 | |
madcowswe | 0:f16a1d69a386 | 40 | static const int NUM_PANELS = 3; // number of panels horizontally |
madcowswe | 0:f16a1d69a386 | 41 | |
madcowswe | 0:f16a1d69a386 | 42 | int running; |
madcowswe | 0:f16a1d69a386 | 43 | int subFrameCtr; |
madcowswe | 0:f16a1d69a386 | 44 | |
madcowswe | 0:f16a1d69a386 | 45 | Timeout nextFrameTimer; // timeout routine |
madcowswe | 0:f16a1d69a386 | 46 | |
madcowswe | 0:f16a1d69a386 | 47 | // Buffers to hold the RGB data after rearranging to match the LED shifting pattern |
madcowswe | 0:f16a1d69a386 | 48 | unsigned char transformedSource[256*3*NUM_PANELS]; |
madcowswe | 0:f16a1d69a386 | 49 | |
madcowswe | 0:f16a1d69a386 | 50 | // Error values for all 256 brightness levels |
madcowswe | 0:f16a1d69a386 | 51 | unsigned int dsErr[256]; |
madcowswe | 0:f16a1d69a386 | 52 | unsigned int ssdsErr[256]; |
madcowswe | 0:f16a1d69a386 | 53 | |
madcowswe | 0:f16a1d69a386 | 54 | // On/off state per sub-frame for all 256 brightness levels |
madcowswe | 0:f16a1d69a386 | 55 | unsigned char dsVal[256]; |
madcowswe | 0:f16a1d69a386 | 56 | |
madcowswe | 0:f16a1d69a386 | 57 | // Precomputed gamma for all 256 brightness levels |
madcowswe | 0:f16a1d69a386 | 58 | unsigned short gamma[256]; |
madcowswe | 0:f16a1d69a386 | 59 | |
madcowswe | 0:f16a1d69a386 | 60 | |
madcowswe | 0:f16a1d69a386 | 61 | DigitalOut flatch; // data latch (for all connected panels in parallel) |
madcowswe | 0:f16a1d69a386 | 62 | DigitalOut MA0; // module address 0 |
madcowswe | 0:f16a1d69a386 | 63 | DigitalOut MA1; |
madcowswe | 0:f16a1d69a386 | 64 | DigitalOut NREN; // active low enable for red channel (low -> LED on). Note: need to have enable high when latching data |
madcowswe | 0:f16a1d69a386 | 65 | DigitalOut Rdat; // red data |
madcowswe | 0:f16a1d69a386 | 66 | DigitalOut Gdat; // green data |
madcowswe | 0:f16a1d69a386 | 67 | DigitalOut Bdat; // blue data |
madcowswe | 0:f16a1d69a386 | 68 | DigitalOut sclk; // clock |
madcowswe | 0:f16a1d69a386 | 69 | |
madcowswe | 0:f16a1d69a386 | 70 | }; |
madcowswe | 0:f16a1d69a386 | 71 | |
madcowswe | 0:f16a1d69a386 | 72 | ledScreen::ledScreen() : |
madcowswe | 0:f16a1d69a386 | 73 | flatch(p11), // data latch (for all connected panels in parallel) |
madcowswe | 0:f16a1d69a386 | 74 | MA0(p18), // module address 0 |
madcowswe | 0:f16a1d69a386 | 75 | MA1(p19), |
madcowswe | 0:f16a1d69a386 | 76 | NREN(p12), // active low enable for red channel (low -> LED on). Note: need to have enable high when latching data |
madcowswe | 0:f16a1d69a386 | 77 | Rdat(p15), // red data |
madcowswe | 0:f16a1d69a386 | 78 | Gdat(p16), // green data |
madcowswe | 0:f16a1d69a386 | 79 | Bdat(p17), // blue data |
madcowswe | 0:f16a1d69a386 | 80 | sclk(p14) { // clock |
madcowswe | 0:f16a1d69a386 | 81 | |
madcowswe | 0:f16a1d69a386 | 82 | // precompute gamma for every possible RGB intensity value (0-255). |
madcowswe | 0:f16a1d69a386 | 83 | // Gamma correction with gamma = 3, downshifting by 8 to bring the range of values back to 0-65535 |
madcowswe | 0:f16a1d69a386 | 84 | for (int i=0; i<256; i++) { |
madcowswe | 0:f16a1d69a386 | 85 | gamma[i] = pow(i, 2.2) * 0.33;//(i*i*i)>>8; |
madcowswe | 0:f16a1d69a386 | 86 | } |
madcowswe | 0:f16a1d69a386 | 87 | |
madcowswe | 0:f16a1d69a386 | 88 | // initialising lines |
madcowswe | 0:f16a1d69a386 | 89 | flatch = 0; |
madcowswe | 0:f16a1d69a386 | 90 | NREN = 0; |
madcowswe | 0:f16a1d69a386 | 91 | sclk = 0; |
madcowswe | 0:f16a1d69a386 | 92 | |
madcowswe | 0:f16a1d69a386 | 93 | // initialising values |
madcowswe | 0:f16a1d69a386 | 94 | MAX_PULSE_WIDTH = 512; //must currently be a power of 2, and when changing this, you must change the ssdsErr crossover masking |
madcowswe | 0:f16a1d69a386 | 95 | pulseLength = MAX_PULSE_WIDTH; |
madcowswe | 0:f16a1d69a386 | 96 | OP_TIME = 345; //Determined by scoping. Change this every time you change num screens |
madcowswe | 0:f16a1d69a386 | 97 | //NUM_PANELS = 3 |
madcowswe | 0:f16a1d69a386 | 98 | |
madcowswe | 0:f16a1d69a386 | 99 | running=0; |
madcowswe | 0:f16a1d69a386 | 100 | subFrameCtr=0; |
madcowswe | 0:f16a1d69a386 | 101 | |
madcowswe | 0:f16a1d69a386 | 102 | // initialising errors for delta-sigma |
madcowswe | 0:f16a1d69a386 | 103 | for (int j=0; j<256; j++) { |
madcowswe | 0:f16a1d69a386 | 104 | dsErr[j] = 0; |
madcowswe | 0:f16a1d69a386 | 105 | ssdsErr[j] = 0; |
madcowswe | 0:f16a1d69a386 | 106 | } |
madcowswe | 0:f16a1d69a386 | 107 | |
madcowswe | 0:f16a1d69a386 | 108 | } |
madcowswe | 0:f16a1d69a386 | 109 | |
madcowswe | 0:f16a1d69a386 | 110 | void ledScreen::start() { |
madcowswe | 0:f16a1d69a386 | 111 | running=1; |
madcowswe | 0:f16a1d69a386 | 112 | outputFrame(); |
madcowswe | 0:f16a1d69a386 | 113 | } |
madcowswe | 0:f16a1d69a386 | 114 | |
madcowswe | 0:f16a1d69a386 | 115 | |
madcowswe | 0:f16a1d69a386 | 116 | void ledScreen::transformFrame2(unsigned char* imageSource) { |
madcowswe | 0:f16a1d69a386 | 117 | |
madcowswe | 0:f16a1d69a386 | 118 | int psp = 0; // panel space pointer |
madcowswe | 0:f16a1d69a386 | 119 | int tpsp = 0; // target psp |
madcowswe | 0:f16a1d69a386 | 120 | int isp = 8 * 3; // imag espace pointer |
madcowswe | 0:f16a1d69a386 | 121 | // preprocessing the image data to match shifting pattern |
madcowswe | 0:f16a1d69a386 | 122 | for (int panel = 0; panel < NUM_PANELS; panel++) { |
madcowswe | 0:f16a1d69a386 | 123 | // int base = panel * 0x20; //i - image space*/ |
madcowswe | 0:f16a1d69a386 | 124 | |
madcowswe | 0:f16a1d69a386 | 125 | for (int drows = 0; drows < 8; drows++) { |
madcowswe | 0:f16a1d69a386 | 126 | //for (int m = 0; m < 0x1F; m++) { //m - quad block in panel space |
madcowswe | 0:f16a1d69a386 | 127 | |
madcowswe | 0:f16a1d69a386 | 128 | for (tpsp = psp + (8*3); psp < tpsp;) { //0th quad |
madcowswe | 0:f16a1d69a386 | 129 | isp -= 3; |
madcowswe | 0:f16a1d69a386 | 130 | transformedSource[psp++] = imageSource[isp]; |
madcowswe | 0:f16a1d69a386 | 131 | transformedSource[psp++] = imageSource[isp+1]; |
madcowswe | 0:f16a1d69a386 | 132 | transformedSource[psp++] = imageSource[isp+2]; |
madcowswe | 0:f16a1d69a386 | 133 | } |
madcowswe | 0:f16a1d69a386 | 134 | |
madcowswe | 0:f16a1d69a386 | 135 | //next row |
madcowswe | 0:f16a1d69a386 | 136 | isp += 0x10 * NUM_PANELS * 3; |
madcowswe | 0:f16a1d69a386 | 137 | for (tpsp = psp + (8*3); psp < tpsp; isp+=3) { //2nd quad |
madcowswe | 0:f16a1d69a386 | 138 | transformedSource[psp++] = imageSource[isp]; |
madcowswe | 0:f16a1d69a386 | 139 | transformedSource[psp++] = imageSource[isp+1]; |
madcowswe | 0:f16a1d69a386 | 140 | transformedSource[psp++] = imageSource[isp+2]; |
madcowswe | 0:f16a1d69a386 | 141 | } |
madcowswe | 0:f16a1d69a386 | 142 | |
madcowswe | 0:f16a1d69a386 | 143 | //previous row |
madcowswe | 0:f16a1d69a386 | 144 | isp -= (0x10 * NUM_PANELS - 8) * 3; |
madcowswe | 0:f16a1d69a386 | 145 | for (tpsp = psp + (8*3); psp < tpsp;) { //1st quad |
madcowswe | 0:f16a1d69a386 | 146 | isp-=3; |
madcowswe | 0:f16a1d69a386 | 147 | transformedSource[psp++] = imageSource[isp]; |
madcowswe | 0:f16a1d69a386 | 148 | transformedSource[psp++] = imageSource[isp+1]; |
madcowswe | 0:f16a1d69a386 | 149 | transformedSource[psp++] = imageSource[isp+2]; |
madcowswe | 0:f16a1d69a386 | 150 | } |
madcowswe | 0:f16a1d69a386 | 151 | isp += 0x10 * NUM_PANELS * 3; |
madcowswe | 0:f16a1d69a386 | 152 | for (tpsp = psp + (8*3); psp < tpsp; isp+=3) { //3rd quad |
madcowswe | 0:f16a1d69a386 | 153 | transformedSource[psp++] = imageSource[isp]; |
madcowswe | 0:f16a1d69a386 | 154 | transformedSource[psp++] = imageSource[isp+1]; |
madcowswe | 0:f16a1d69a386 | 155 | transformedSource[psp++] = imageSource[isp+2]; |
madcowswe | 0:f16a1d69a386 | 156 | |
madcowswe | 0:f16a1d69a386 | 157 | } |
madcowswe | 0:f16a1d69a386 | 158 | isp += (0x10 * NUM_PANELS - 8) * 3; |
madcowswe | 0:f16a1d69a386 | 159 | } |
madcowswe | 0:f16a1d69a386 | 160 | |
madcowswe | 0:f16a1d69a386 | 161 | isp += (0x10 - 0x20 * NUM_PANELS * 8) * 3; |
madcowswe | 0:f16a1d69a386 | 162 | } |
madcowswe | 0:f16a1d69a386 | 163 | } |
madcowswe | 0:f16a1d69a386 | 164 | |
madcowswe | 0:f16a1d69a386 | 165 | void ledScreen::transformFrame(unsigned char* imageSource) { |
madcowswe | 0:f16a1d69a386 | 166 | unsigned char rotatedSource[256*3*NUM_PANELS]; |
madcowswe | 0:f16a1d69a386 | 167 | |
madcowswe | 0:f16a1d69a386 | 168 | for (int panels = 0; panels < 3; panels++){ |
madcowswe | 0:f16a1d69a386 | 169 | for (int x = 0; x<16; x++){ |
madcowswe | 0:f16a1d69a386 | 170 | for (int y = 0; y<16; y++){ |
madcowswe | 0:f16a1d69a386 | 171 | for (int c = 0; c < 3; c++){ |
madcowswe | 0:f16a1d69a386 | 172 | rotatedSource[((15-x) + panels*16 + 48*y)*3 + c] = imageSource[(y + panels*16 + 48*x)*3 + c]; |
madcowswe | 0:f16a1d69a386 | 173 | } |
madcowswe | 0:f16a1d69a386 | 174 | } |
madcowswe | 0:f16a1d69a386 | 175 | } |
madcowswe | 0:f16a1d69a386 | 176 | } |
madcowswe | 0:f16a1d69a386 | 177 | |
madcowswe | 0:f16a1d69a386 | 178 | transformFrame2(rotatedSource); |
madcowswe | 0:f16a1d69a386 | 179 | } |
madcowswe | 0:f16a1d69a386 | 180 | |
madcowswe | 0:f16a1d69a386 | 181 | // Output one frame and call itself after a period of time if running is set to true |
madcowswe | 0:f16a1d69a386 | 182 | void ledScreen::outputFrame() { |
madcowswe | 0:f16a1d69a386 | 183 | |
madcowswe | 0:f16a1d69a386 | 184 | if (pulseLength != MAX_PULSE_WIDTH) |
madcowswe | 0:f16a1d69a386 | 185 | NREN = 1; // turn off |
madcowswe | 0:f16a1d69a386 | 186 | |
madcowswe | 0:f16a1d69a386 | 187 | if (subFrameCtr<=0) subFrameCtr=36; |
madcowswe | 0:f16a1d69a386 | 188 | subFrameCtr--; |
madcowswe | 0:f16a1d69a386 | 189 | |
madcowswe | 0:f16a1d69a386 | 190 | if (subFrameCtr == 0) { // Every cycle of delta sigma we take a snapshot of the error that needs to be corrected by the short pulses. |
madcowswe | 0:f16a1d69a386 | 191 | for (int i = 0; i < 256; i++) { // This is required to eliminate visible flicker due to beat frequencies otherwise created. |
madcowswe | 0:f16a1d69a386 | 192 | dsErr[i] += ssdsErr[i] & 0xFE000000; |
madcowswe | 0:f16a1d69a386 | 193 | ssdsErr[i] %= 0x10000; |
madcowswe | 0:f16a1d69a386 | 194 | ssdsErr[i] += dsErr[i] % (512 * 0x10000); |
madcowswe | 0:f16a1d69a386 | 195 | dsErr[i] &= 0xFE000000; |
madcowswe | 0:f16a1d69a386 | 196 | } |
madcowswe | 0:f16a1d69a386 | 197 | |
madcowswe | 0:f16a1d69a386 | 198 | // Doing delta sigma for the snapshot |
madcowswe | 0:f16a1d69a386 | 199 | for (int i = 0; i <= 9; i++) { |
madcowswe | 0:f16a1d69a386 | 200 | int lpl = 1<<i; |
madcowswe | 0:f16a1d69a386 | 201 | |
madcowswe | 0:f16a1d69a386 | 202 | if (ssdsErr[i]/0x10000 & lpl) |
madcowswe | 0:f16a1d69a386 | 203 | ssdsErr[i]-=(0x10000-gamma[i])*lpl; |
madcowswe | 0:f16a1d69a386 | 204 | else |
madcowswe | 0:f16a1d69a386 | 205 | ssdsErr[i]+=gamma[i]*lpl; |
madcowswe | 0:f16a1d69a386 | 206 | } |
madcowswe | 0:f16a1d69a386 | 207 | |
madcowswe | 0:f16a1d69a386 | 208 | } |
madcowswe | 0:f16a1d69a386 | 209 | |
madcowswe | 0:f16a1d69a386 | 210 | // produce pulse lengths of 1, 2, 4, ... 256, spread throughout all subframes (only one in four are not MAX_PULSE_WIDTH long) |
madcowswe | 0:f16a1d69a386 | 211 | pulseLength = ((subFrameCtr%4)?MAX_PULSE_WIDTH:(1<<(subFrameCtr>>2))); |
madcowswe | 0:f16a1d69a386 | 212 | |
madcowswe | 0:f16a1d69a386 | 213 | for (int i = 0; i < 256; i++) { |
madcowswe | 0:f16a1d69a386 | 214 | if (pulseLength == MAX_PULSE_WIDTH) { |
madcowswe | 0:f16a1d69a386 | 215 | // Delta-Sigma modulation with variable pulse length weighting |
madcowswe | 0:f16a1d69a386 | 216 | // Based on energy dimensions (time * amplitude) |
madcowswe | 0:f16a1d69a386 | 217 | if (dsErr[i] > (0x10000-gamma[i])*pulseLength) { |
madcowswe | 0:f16a1d69a386 | 218 | dsVal[i] = 0;//-1; Invert as we are using inverting buffers |
madcowswe | 0:f16a1d69a386 | 219 | dsErr[i]-=(0x10000-gamma[i])*pulseLength; |
madcowswe | 0:f16a1d69a386 | 220 | } else { |
madcowswe | 0:f16a1d69a386 | 221 | dsVal[i] = (unsigned char)-1; |
madcowswe | 0:f16a1d69a386 | 222 | dsErr[i]+=gamma[i]*pulseLength; |
madcowswe | 0:f16a1d69a386 | 223 | } |
madcowswe | 0:f16a1d69a386 | 224 | } else { // if short pulse |
madcowswe | 0:f16a1d69a386 | 225 | if (ssdsErr[i]/0x10000 & pulseLength) { |
madcowswe | 0:f16a1d69a386 | 226 | //Doing proper least significant delta sigma live still causes flicker (but only for dim pixels) |
madcowswe | 0:f16a1d69a386 | 227 | //ssdsErr[i]-=(0x10000-gamma[i])*pulseLength; |
madcowswe | 0:f16a1d69a386 | 228 | dsVal[i] = 0; |
madcowswe | 0:f16a1d69a386 | 229 | } else { |
madcowswe | 0:f16a1d69a386 | 230 | dsVal[i] = (unsigned char)-1; |
madcowswe | 0:f16a1d69a386 | 231 | //ssdsErr[i]+=gamma[i]*pulseLength; |
madcowswe | 0:f16a1d69a386 | 232 | } |
madcowswe | 0:f16a1d69a386 | 233 | |
madcowswe | 0:f16a1d69a386 | 234 | } |
madcowswe | 0:f16a1d69a386 | 235 | } |
madcowswe | 0:f16a1d69a386 | 236 | |
madcowswe | 0:f16a1d69a386 | 237 | // output data |
madcowswe | 0:f16a1d69a386 | 238 | for (int i = 0; i < NUM_PANELS; i++) { // NUM_PANELS |
madcowswe | 0:f16a1d69a386 | 239 | MA0 = i&1; |
madcowswe | 0:f16a1d69a386 | 240 | MA1 = i&2; |
madcowswe | 0:f16a1d69a386 | 241 | |
madcowswe | 0:f16a1d69a386 | 242 | frameout(dsVal, &transformedSource[i*256*3]); |
madcowswe | 0:f16a1d69a386 | 243 | } |
madcowswe | 0:f16a1d69a386 | 244 | |
madcowswe | 0:f16a1d69a386 | 245 | NREN = 1; // need to have enables high before every latch, (in case we are on a long pulse) |
madcowswe | 0:f16a1d69a386 | 246 | flatch = 1; // latching all data to LEDs |
madcowswe | 0:f16a1d69a386 | 247 | flatch = 0; |
madcowswe | 0:f16a1d69a386 | 248 | NREN = 0; // turn on LEDs |
madcowswe | 0:f16a1d69a386 | 249 | |
madcowswe | 0:f16a1d69a386 | 250 | if (pulseLength < 4) { // short pulses done through wait |
madcowswe | 0:f16a1d69a386 | 251 | wait_us(pulseLength); |
madcowswe | 0:f16a1d69a386 | 252 | NREN = 1; //Turn off LEDs |
madcowswe | 0:f16a1d69a386 | 253 | |
madcowswe | 0:f16a1d69a386 | 254 | bool wasrunning = running; |
madcowswe | 0:f16a1d69a386 | 255 | running = false; |
madcowswe | 0:f16a1d69a386 | 256 | outputFrame(); //this will recurse only once due to the distrubution of pulses. pulseLength of the next instance will be attached. |
madcowswe | 0:f16a1d69a386 | 257 | running = wasrunning; |
madcowswe | 0:f16a1d69a386 | 258 | } |
madcowswe | 0:f16a1d69a386 | 259 | // long waits done through attaching an interrupt that will turn off the LEDs at the start of next function call. |
madcowswe | 0:f16a1d69a386 | 260 | // Meanwhile, the main code can run between the interrupts. |
madcowswe | 0:f16a1d69a386 | 261 | if (running) nextFrameTimer.attach_us(this, &ledScreen::outputFrame, (pulseLength == MAX_PULSE_WIDTH) ? pulseLength - OP_TIME : pulseLength); |
madcowswe | 0:f16a1d69a386 | 262 | } |