Google+ Followers

Sunday, September 29, 2013

35. Driving the Bi-Colour 8 x 8 LED Array Properly

The last post shows a bi-colour 8 x 8 but it's only by disconnecting the reds and connecting the greens, and vice versa, that you can get them both going.  To get them going at the same time in the same sketch, you need a bit more circuitry...that's what I mean by 'properly'.

As I said in the last post, building a circuit for the 8 x 8 bi-colour array was one of the first things I tried.  I soldered it up on a Perfboard and it worked!  I don't know why, but I dismantled the whole thing, probably because it looked a mess with all the wires.

Anyway, I didn't document it so here it is again on breadboard - documented this time! It's still a mess, but never mind.

The tutorial was recorded by Derek Molloy of Dublin City University, one of his many excellent video tutorials on electronics. (ref http://www.youtube.com/watch?v=wXvs0CrRPIw).  He develops the circuit using an increasing number of ICs, the final configuration taking care of the provision of enough, and not too much current to the array.  He uses 74HC595B serial to parallel converters, also described as an 8-bit shift register (I used 74HC595Ns.  Apparently this is the same chip, with just some difference in manufacturer or packaging).  One of these is devoted to the rows of the common anode dot matrix, while another is used for the green columns, and a third for the red columns.



You can just about see the 8 x 8 LED array at the bottom right beside the 16 resistors.  At the top is the Arduino Uno R3, supplying power and ground below and, above, only 3 pins - Pin 8 for data, Pin 9 for the clock and Pin 10 for the latch.

In order to supply current comfortably to the rows, (there is only one row illuminated at a time - persistence of vision makes you think there are more rows on), a UDN2981A 8-channel current source is employed (mine was a UDN2981AT).   This is essentially a transistor array.

Derek Molloy hasn't published his circuit diagram and code, other than on the video, but I was able to freeze the video at all the critical parts and reproduce them:


The diagram is a little blurred, but the detail of the 8 x 8 array is given below:
You can see the array pin numbers in the circles, referring to both rows and columns.  Pin 1 on the array itself can be identified by an indication on the underside.  Notice that on the circuit diagram, the UDN2981A is upside-down, but that's just because of the way the wiring worked out.

Here is the sketch code:

1:  /*  
2:   8 x 8 LED array with 3 x 74HC595N Serial to Parallel converters  
3:   and a UDN2981AT 8-channel Source Driver  
4:   driving a dual colour 8 x 8 array  
5:   Derek Molloy, Dublin City University  
6:   Modified to only use a single data line from the Arduino  
7:  */  
8:  const int NUM_ROWS = 8;  
9:  const int NUM_COLS = 8;  
10:  const int dataPin = 8;  // Row data line  
11:  const int clockPin = 9; // Clock sequence  
12:  const int latchPin = 10; // Latch pin  
13:  int gdataset[13][8] = {{3,6,12,24,48,96,192,96}, {6,12,24,48,96,192,96,48},  
14:    {12,24,48,96,192,96,48,0}, {24,48,96,192,96,48,0,12},  
15:    {48,96,192,96,48,0,12,6}, {96,192,96,48,0,12,6,3},  
16:    {192,96,48,0,12,6,3,6}, {96,48,0,12,6,3,6,12},  
17:    {48,0,12,6,3,6,12,24}, {0,12,6,3,6,12,24,48},  
18:    {12,6,3,6,12,24,48,96}, {6,3,6,12,24,48,96,192}, {3,6,12,24,48,96,192,96}  
19:   };  
20:  int rdataset[13][8] = {{192,96,48,0,12,6,3,6}, {96,48,0,12,6,3,6,12},  
21:    {48,0,12,6,3,6,12,24}, {0,12,6,3,6,12,24,48},  
22:    {12,6,3,6,12,24,48,96}, {6,3,6,12,24,48,96,192},  
23:    {3,6,12,24,48,96,192,96}, {6,12,24,48,96,192,96,48},  
24:    {12,24,48,96,192,96,48,0}, {24,48,96,192,96,48,0,12},  
25:    {48,96,192,96,48,0,12,6}, {96,192,96,48,0,12,6,3}, {192,96,48,0,12,6,3,6}  
26:   };  
27:  void setup(){  
28:   pinMode(clockPin, OUTPUT);   
29:   pinMode(dataPin, OUTPUT);   
30:   pinMode(latchPin, OUTPUT);   
31:  }  
32:  void loop() {  
33:   int i=0;  
34:   while(true) // replay the animation forever  
35:   {  
36:    displayPixels(i,20); // call the function below with the chatacter  
37:               // to be displayed  
38:               // the 2nd value indicates the animation speed  
39:    i++;  
40:    if (i==11) {i=0;}   // go back to the start  
41:   }   
42:  }  
43:  void displayPixels(int val, int delay){  
44:   for (int i=0; i<delay; i++)  
45:   {   
46:    for (int row=0; row<NUM_ROWS; row++){  
47:     int rcur = rdataset[val][row];  
48:     int gcur = gdataset[val][row];  
49:     shiftOut(dataPin, clockPin, MSBFIRST, 255-rcur);  
50:     shiftOut(dataPin, clockPin, MSBFIRST, 255-gcur);  
51:     shiftOut(dataPin, clockPin, MSBFIRST, B00000001 << row);  
52:     digitalWrite(latchPin, HIGH);  
53:     digitalWrite(latchPin, LOW);  
54:     delayMicroseconds(100);  
55:     shiftOut(dataPin, clockPin, MSBFIRST, 255);  
56:     shiftOut(dataPin, clockPin, MSBFIRST, 255);  
57:     shiftOut(dataPin, clockPin, MSBFIRST, B00000001 << row);    
58:     digitalWrite(latchPin, HIGH);  
59:     digitalWrite(latchPin, LOW);  
60:    }  
61:   }  
62:  }  

And here's a video of it working:



Not a pretty sight, but it works.  (You might notice a little flaw in the way the LEDs are illuminated - I haven't quite gotten to the bottom of that.  Suggestions welcome!)

I reaslised that I must have had a connection or two incorrect.

Anyway, because the whole thing is a mess, and on two breadboards, I decided to completely pull it apart and build it again, all on one breadboard.  And I did.  This time it's not so messy looking, but the error now is that one of the columns is not working.  I'll keep fiddling.

Here's the new setup:




Much neater - eh?







Thursday, September 26, 2013

34. The 8 x 8 Bi-Colour LED Matrix

3000 page views!!
Yesterday I got the 3 Arduinos that I ordered from China - and they're the Atmega 328P-PU type - the same as all my others.  I now have 7 Arduinos (8 if you count the Gertboard) and 2 Raspberry Pis.  Maybe I should stop collecting them!  

One of the earliest components I had was this bi-colour LED matrix.  I actually had it wired up to display patterns with both the red and the green colours active, before I started this blog.

Dismantling it before I had it documented was one of my regrets which led to the making of this entire blog.  The LED array has 64 red and 64 green LEDs, which is amazing in such a small space.  There seems to be room for 4 LEDs in each dot, so presumably you could get red, green, blue and white LEDs - I must look out for this.  

This time I followed an oomlout.com recipe for activating either the red or the green, or some of the red and some of the green LEDs, but not all at the same time.  The pinout is completely different from the previous mono-colour 8 x 8 array. (ref http://smokespark.blogspot.co.uk/2013/08/30-multiplexing-8-x-8-led-array.html)

Here is the result of a sketch which is designed just to test the LEDs which have been activated by hardware configuration (I connected up the red LEDs for the top 4 rows, and the green LEDs for the lower 4 rows:




and running a scrolling text sketch, this time I connected up the green LEDs on the top 4 rows and the red LEDs on the bottom 4 rows.  Here is the result:



oomlout.com provide a very useful sheet (ref http://oomlout.com/parts/LED-8X8M-03-guide.pdf) and here are some extracted diagrams:


Note that this is the common-anode type.  Circuitry would have to be different for the common-cathode type.
This is a kit supplied by oomlout.com, but I used my own parts.

This is a very useful diagram, because without it, it's very easy to get the wiring wrong.

The code for testing the LEDs is here:


1:  /*  
2:   * Example Code for an 8 x 8 LED Matrix  
3:   * For More Details Visit http://www.tinyurl.com/yhwxv6h  
4:   *  
5:   * Displays a test pattern lighting one LED after another  
6:   * To Play around with modifying the bitmap, un comment Example #2  
7:   */  
8:  int speed = 5; //the delay time in milliseconds  
9:  int pauseDelay = 1;  //the number of milliseconds to display each scanned line  
10:  //Pin Definitions  
11:  int rowA[] = {9,8,7,6,5,4,3,2};     //An Array defining which pin each row is attached to  
12:                       //(rows are common anode (drive HIGH))  
13:  int colA[] = {17,16,15,14,13,12,11,10}; //An Array defining which pin each column is attached to  
14:                       //(columns are common cathode (drive LOW))  
15:  //The array used to hold a bitmap of the display   
16:  //(if you wish to do something other than scrolling marque change the data in this  
17:  //variable then display)  
18:  byte data[] = {0,0,0,0,0,0,0,0};    
19:  //Setup runs once when power is applied  
20:  void setup()  
21:  {   
22:   Serial.begin(9600);     //Open the Serial port for debugging  
23:   for(int i = 0; i <8; i++){ //Set the 16 pins used to control the array as OUTPUTs  
24:    pinMode(rowA[i], OUTPUT);  
25:    pinMode(colA[i], OUTPUT);  
26:   }  
27:  }  
28:  //repeats    
29:  void loop()  
30:  {  
31:   //Example #1 - test pattern  
32:   //Run a small test program which lights up each light in time  
33:   test();  
34:   //Example #2 - static image  
35:   //Display a defined bitmap  
36:  /*   
37:   data[0] = B10101010; //row 1s bit mask (1 LED is on 0 LED is off)  
38:   data[1] = B01010101; //row 1s bit mask (1 LED is on 0 LED is off)  
39:   data[2] = B10101010; //row 1s bit mask (1 LED is on 0 LED is off)  
40:   data[3] = B01010101; //row 1s bit mask (1 LED is on 0 LED is off)  
41:   data[4] = B10101010; //row 1s bit mask (1 LED is on 0 LED is off)  
42:   data[5] = B01010101; //row 1s bit mask (1 LED is on 0 LED is off)  
43:   data[6] = B10101010; //row 1s bit mask (1 LED is on 0 LED is off)   
44:   data[7] = B01010101; //row 1s bit mask (1 LED is on 0 LED is off)   
45:   showSprite(speed);  
46:  */   
47:  }  
48:  //An array to store power values to act as bit masks  
49:  const int powers[] = {1,2,4,8,16,32,64,128};  
50:  //Runs a pattern where each LED is lit one after another  
51:  void test(){  
52:   for(int i = 0; i < 8; i++){  
53:    for(int ii = 0; ii< 8; ii++){  
54:    data[i] = data[i]+ powers[ii];  //Goes through each row of lights lighting each column one after another  
55:    showSprite(speed);  
56:    }  
57:   }  
58:   for(int i = 0; i < 8; i++){  
59:    for(int ii = 0; ii< 8; ii++){  
60:    data[i] = data[i] - powers[ii];  //Goes through each row of lights turning off each column one after another  
61:    showSprite(speed);  
62:    }  
63:   }   
64:  }  
65:  void showSprite(int speed2){  
66:   for(int iii = 0; iii < speed2; iii++){         //show the current frame speed2 times  
67:   for(int column = 0; column < 8; column++){      //iterate through each column  
68:    for(int i = 0; i < 8; i++){               
69:      digitalWrite(rowA[i], LOW);           //turn off all row pins   
70:    }  
71:    for(int i = 0; i < 8; i++){ //Set only the one pin  
72:     if(i == column){   digitalWrite(colA[i], LOW);} //turns the current row on  
73:     else{        digitalWrite(colA[i], HIGH); }//turns the rest of the rows off  
74:    }  
75:    for(int row = 0; row < 8; row++){          //iterate through each pixel in the current column  
76:    int bit = (data[column] >> row) & 1;  
77:    if(bit == 1){   
78:      digitalWrite(rowA[row], HIGH);          //if the bit in the data array is set turn the LED on  
79:    }  
80:    }  
81:    delay(pauseDelay);            //leave the column on for pauseDelay microseconds (too high a delay causes flicker)  
82:   }   
83:   }  
84:  }  

The code for displaying a scrolling message is here  (note that I am using my own character set that I used before inpost 31... http://smokespark.blogspot.co.uk/2013/08/31-displaying-scrolling-messages-on-8-x.html):

1:  /*  
2:   * Example Code for an 8 x 8 LED Matrix  
3:   * For More Details Visit http://www.tinyurl.com/yhwxv6h  
4:   *  
5:   * Scrolls a message across an 8 x 8 LED Matrix  
6:   * To adjust the speed change the variable speed.  
7:   * The message is held in requestString[]  
8:   */  
9:  int speed = 20; //number of times to repeat each frame  
10:  int pauseDelay = 500; //microseconds to leave each row on before moving to the next  
11:  char requestString[] = " Hello World of Sparks N Smoke - - - "; //The string to display  
12:                        //to change the message in code you right yourself simply   
13:                        //change this data and reset index and offset to 0  
14:  //Variables used for scrolling (both start at 0  
15:  int index = 0; //this is the current character in the string being displayed  
16:  int offset = 0; //this is how many columns it is offset by  
17:  //Pin Definitions  
18:  int rowA[] = {9,8,7,6,5,4,3,2};     //An Array defining which pin each row is attached to  
19:                       //(rows are common anode (drive HIGH))  
20:  int colA[] = {17,16,15,14,13,12,11,10}; //An Array defining which pin each column is attached to  
21:                       //(columns are common cathode (drive LOW))  
22:  //Constants defining each characters position in an array of integer arrays  
23:  //Letters  
24:  const int A = 0; const int B = 1; const int C = 2; const int D = 3; const int E = 4;  
25:  const int F = 5; const int G = 6; const int H = 7; const int I = 8; const int J = 9;   
26:  const int K = 10; const int L =11; const int M = 12; const int N = 13; const int O = 14;   
27:  const int P = 15; const int Q =16; const int R = 17; const int S = 18; const int T = 19;   
28:  const int U = 20; const int V =21; const int W = 22; const int X = 23; const int Y = 24;   
29:  const int Z = 25;  
30:  //Punctuation  
31:  const int COL =26; const int DASH = 27; const int BRA2 = 28; const int _ = 29; const int LINE = 34;  
32:  const int DOT =36;  
33:  //Extra Charchters  
34:  const int FULL =30; const int CHECK = 31; const int B2 = 32; const int TEMP = 33;   
35:  const int SMILE =35; const int COLDOT = 36;  
36:  //The array used to hold a bitmap of the display   
37:  //(if you wish to do something other than scrolling marque change the data in this  
38:  //variable then display)  
39:  byte data[] = {0,0,0,0,0,0,0,0};      
40:  //The alphabet  
41:  //Each character is an 8 x 7 bitmap where 1 is on and 0 if off  
42:  const int _A[] = {B0111000,  
43:           B1100100,  
44:           B1100100,  
45:           B1100100,  
46:           B1111100,  
47:           B1100100,  
48:           B1100100,  
49:           B0000000};  
50:  const int _B[] = {B1111000,  
51:           B1101100,  
52:           B1101100,  
53:           B1111100,  
54:           B1101100,  
55:           B1101100,  
56:           B1111000,  
57:              B0000000};  
58:  const int _C[] = {B0111000,  
59:           B1100100,  
60:           B1100000,  
61:           B1100000,  
62:           B1100000,  
63:           B1100100,  
64:           B0111000,  
65:              B0000000};  
66:  const int _D[] = {B1111000,  
67:           B1100100,  
68:           B1100110,  
69:           B1100110,  
70:           B1100110,  
71:           B1100100,  
72:           B1111000,  
73:              B0000000};  
74:  const int _E[] = {B1111100,  
75:           B1100100,  
76:           B1100000,  
77:           B1111000,  
78:           B1100000,  
79:           B1100100,  
80:           B1111100,  
81:              B0000000};  
82:  const int _F[] = {B1111100,  
83:           B1100100,  
84:           B1100000,  
85:           B1111000,  
86:           B1100000,  
87:           B1100000,  
88:           B1100000,  
89:              B0000000};  
90:  const int _G[] = {B0111100,  
91:           B1100100,  
92:           B1100000,  
93:           B1101110,  
94:           B1100100,  
95:           B1100100,  
96:           B0111100,  
97:              B0000000};  
98:  const int _H[] = {B1100110,  
99:           B1100110,  
100:           B1100110,  
101:           B1111110,  
102:           B1100110,  
103:           B1100110,  
104:           B1100110,  
105:              B0000000};  
106:  const int _I[] = {B0111100,  
107:           B0011000,  
108:           B0011000,  
109:           B0011000,  
110:           B0011000,  
111:           B0011000,  
112:           B0111100,  
113:              B0000000};  
114:  const int _J[] = {B0011110,  
115:           B0001100,  
116:           B0001100,  
117:           B0001100,  
118:           B1101100,  
119:           B1101100,  
120:           B0111100,  
121:              B0000000};  
122:  const int _K[] = {B1100011,  
123:           B1100110,  
124:           B1101100,  
125:           B1111000,  
126:           B1101100,  
127:           B1100110,  
128:           B1100011,  
129:              B0000000};  
130:  const int _L[] = {B1111000,  
131:           B0110000,  
132:           B0110000,  
133:           B0110000,  
134:           B0110000,  
135:           B0110010,  
136:           B1111110,  
137:              B0000000};  
138:  const int _M[] = {B1000001,  
139:           B1100011,  
140:           B1110111,  
141:           B1101011,  
142:           B1100011,  
143:           B1100011,  
144:           B1100011,  
145:              B0000000};  
146:  const int _N[] = {B1100011,  
147:           B1110011,  
148:           B1110011,  
149:           B1101011,  
150:           B1100111,  
151:           B1100111,  
152:           B1100011,  
153:              B0000000};  
154:  const int _O[] = {B0111100,  
155:           B1100110,  
156:           B1100110,  
157:           B1100110,  
158:           B1100110,  
159:           B1100110,  
160:           B0111100,  
161:              B0000000};  
162:  const int _P[] = {B1111000,  
163:           B1101100,  
164:           B1101100,  
165:           B1111000,  
166:           B1100000,  
167:           B1100000,  
168:           B1100000,  
169:              B0000000};  
170:  const int _Q[] = {B0111100,  
171:           B1100110,  
172:           B1100110,  
173:           B1100110,  
174:           B1101110,  
175:           B1100100,  
176:           B0111010,  
177:           B0000001};  
178:  const int _R[] = {B1111000,  
179:           B1101100,  
180:           B1101100,  
181:           B1111000,  
182:           B1111000,  
183:           B1101100,  
184:           B1100110,  
185:              B0000000};  
186:  const int _S[] = {B0111100,  
187:           B1100010,  
188:           B1100000,  
189:           B0111100,  
190:           B0000110,  
191:           B1000110,  
192:           B0111100,  
193:              B0000000};  
194:  const int _T[] = {B1111110,  
195:           B1011010,  
196:           B0011000,  
197:           B0011000,  
198:           B0011000,  
199:           B0011000,  
200:           B0011000,  
201:              B0000000};  
202:  const int _U[] = {B1100110,  
203:           B1100110,  
204:           B1100110,  
205:           B1100110,  
206:           B1100110,  
207:           B1100110,  
208:           B0111100,  
209:              B0000000};  
210:  const int _V[] = {B1100011,  
211:           B1100011,  
212:           B1100011,  
213:           B1100011,  
214:           B0110110,  
215:           B0011100,  
216:           B0001000,  
217:              B0000000};  
218:  const int _W[] = {B1100011,  
219:           B1100011,  
220:           B1100011,  
221:           B1101011,  
222:           B1110111,  
223:           B1100011,  
224:           B1000001,  
225:              B0000000};  
226:  const int _X[] = {B1100011,  
227:           B0110110,  
228:           B0011100,  
229:           B0001000,  
230:           B0011100,  
231:           B0110110,  
232:           B1100011,  
233:              B0000000};  
234:  const int _Y[] = {B1100010,  
235:           B0110100,  
236:           B0011000,  
237:           B0011000,  
238:           B0011000,  
239:           B0011000,  
240:           B0011000,  
241:              B0000000};  
242:  const int _Z[] = {B1111111,  
243:           B1000111,  
244:           B0001100,  
245:           B0011000,  
246:           B0110000,  
247:           B1110001,  
248:           B1111111,  
249:              B0000000};  
250:  const int _COL[] = {B0000000,  
251:           B0011000,  
252:           B0011000,  
253:           B0000000,  
254:           B0011000,  
255:           B0011000,  
256:           B0000000,  
257:              B0000000};  
258:  const int _DASH[] = {B0000000,  
259:           B0000000,  
260:           B0000000,  
261:           B0111110,  
262:           B0111110,  
263:           B0000000,  
264:           B0000000,  
265:              B0000000};  
266:  const int _BRA2[] = {B0010000,  
267:           B0001000,  
268:           B0000100,  
269:           B0000100,  
270:           B0001000,  
271:           B0010000,  
272:           B0000000,  
273:              B0000000};           
274:  const int __[] = {B0000000,  
275:           B0000000,  
276:           B0000000,  
277:           B0000000,  
278:           B0000000,  
279:           B0000000,  
280:           B0000000,  
281:              B0000000};  
282:  const int _FULL[] = {B1111111,  
283:             B1111111,  
284:             B1111111,  
285:             B1111111,  
286:             B1111111,  
287:             B1111111,  
288:             B1111111,  
289:              B0000000};           
290:  const int _CHECK[] = {B1010101,  
291:             B0101010,  
292:             B1010101,  
293:             B0101010,  
294:             B1010101,  
295:             B0101010,  
296:             B1010101,  
297:              B0000000};  
298:  const int _B2[] = {B0111110,  
299:            B0000001,  
300:            B0000001,  
301:            B0001111,  
302:            B0000001,  
303:            B1000001,  
304:            B0111110,  
305:              B0000000};  
306:  const int _TEMP[] = {B0000011,  
307:             B0011111,  
308:             B0111111,  
309:             B1111110,  
310:             B1111111,  
311:             B0011111,  
312:             B0000011,  
313:              B0000000};  
314:  const int _LINE[] = {B0000001,  
315:             B0000001,  
316:             B0000001,  
317:             B0000001,  
318:             B0000001,  
319:             B0000001,  
320:             B0000001,  
321:              B0000000};             
322:  const int _SMILE[] = {B000000,  
323:             B1100100,  
324:             B1100010,  
325:             B0011001,  
326:             B1100010,  
327:             B1100100,  
328:             B0000000,  
329:             B0000000};             
330:  const int _DOT[] = {B0000000,  
331:           B0000000,  
332:           B0000000,  
333:           B0000000,  
334:           B1100000,  
335:           B1100000,  
336:           B0000000,  
337:           B0000000};             
338:  const int _COLDOT[] = {B0000000,  
339:              B0110000,  
340:              B0110000,  
341:              B0000000,  
342:              B0110011,  
343:              B0110011,  
344:              B0000000,  
345:              B0000000};           
346:  //Load the bitmap characters into an array (each characters position corresponds to its previously defined index (ie _A (a's bitmap)   
347:  //is at index 0 and A = 0 so letters[A] will return the 'A' bitmap)  
348:  const int* letters[] = {_A,_B,_C,_D,_E,_F,_G,_H,_I,_J,_K,_L,_M,_N,_O,_P,_Q,_R,_S,_T,_U,_V,_W,_X,_Y,_Z,_COL,_DASH,_BRA2,__, _FULL, _CHECK, _B2, _TEMP, _LINE, _SMILE, _DOT, _COLDOT};  
349:  //Setup runs once when power is applied  
350:  void setup()  
351:  {   
352:   for(int i = 0; i <8; i++){ //Set the 16 pins used to control the array as OUTPUTs  
353:    pinMode(rowA[i], OUTPUT);  
354:    pinMode(colA[i], OUTPUT);  
355:   }  
356:  }  
357:  //repeats    
358:  void loop()  
359:  {  
360:   updateMatrix();  
361:  }  
362:  void updateMatrix(){  
363:   loadSprite();  
364:   showSprite(speed);  
365:  }  
366:  //An array holding the powers of 2 these are used as bit masks when calculating what to display  
367:  const int powers[] = {1,2,4,8,16,32,64,128};  
368:  //Loads the current scroll state frame into the data[] display array  
369:  void loadSprite(){  
370:   int currentChar = getChar(requestString[index]);  
371:   int nextChar = getChar(requestString[index+1]);  
372:   for(int row=0; row < 8; row++){          //iterate through each row  
373:    data[row] = 0;                  //reset the row we're working on  
374:    for(int column=0; column < 8; column++){     //iterate through each column  
375:     data[row] = data[row] + ((powers[column] & (letters[currentChar][row] << offset)));  //loads the current character offset by offset pixels   
376:     data[row] = data[row] + (powers[column] & (letters[nextChar][row] >> (8-offset) ));  //loads the next character offset by offset pixels  
377:    }  
378:   }  
379:   offset++;                     //increment the offset by one row  
380:   if(offset==8){offset = 0; index++; if(index==sizeof(requestString)-2){index=0;}}     //if offset is 8 load the next character pair for the next time through  
381:  }  
382:  void showSprite(int speed2){  
383:   for(int iii = 0; iii < speed2; iii++){         //show the current frame speed2 times  
384:   for(int column = 0; column < 8; column++){      //iterate through each column  
385:    for(int i = 0; i < 8; i++){               
386:      digitalWrite(rowA[i], LOW);           //turn off all row pins   
387:    }  
388:    for(int i = 0; i < 8; i++){ //Set only the one pin  
389:     if(i == column){   digitalWrite(colA[i], LOW);} //turns the current row on  
390:     else{        digitalWrite(colA[i], HIGH); }//turns the rest of the rows off  
391:    }  
392:    for(int row = 0; row < 8; row++){          //iterate through each pixel in the current column  
393:    int bit = (data[column] >> row) & 1;  
394:    if(bit == 1){   
395:      digitalWrite(rowA[row], HIGH);          //if the bit in the data array is set turn the LED on  
396:    }  
397:    }  
398:    delayMicroseconds(pauseDelay);            //leave the column on for pauseDelay microseconds (too high a delay causes flicker)  
399:   }   
400:   }  
401:  }  
402:  //returns the index of a given character  
403:  //for converting from a string to a lookup in our array of character bitmaps  
404:  int getChar(char character){  
405:   int returnValue = Z;  
406:   switch(character){  
407:   case 'A': returnValue = A; break;  
408:   case 'a': returnValue = A; break;  
409:   case 'B': returnValue = B; break;  
410:   case 'b': returnValue = B; break;  
411:   case 'C': returnValue = C; break;  
412:   case 'c': returnValue = C; break;  
413:   case 'D': returnValue = D; break;  
414:   case 'd': returnValue = D; break;  
415:   case 'E': returnValue = E; break;  
416:   case 'e': returnValue = E; break;  
417:   case 'F': returnValue = F; break;  
418:   case 'f': returnValue = F; break;  
419:   case 'G': returnValue = G; break;  
420:   case 'g': returnValue = G; break;  
421:   case 'H': returnValue = H; break;  
422:   case 'h': returnValue = H; break;  
423:   case 'I': returnValue = I; break;  
424:   case 'i': returnValue = I; break;  
425:   case 'J': returnValue = J; break;  
426:   case 'j': returnValue = J; break;  
427:   case 'K': returnValue = K; break;  
428:   case 'k': returnValue = K; break;  
429:   case 'L': returnValue = L; break;  
430:   case 'l': returnValue = L; break;  
431:   case 'M': returnValue = M; break;  
432:   case 'm': returnValue = M; break;  
433:   case 'N': returnValue = N; break;  
434:   case 'n': returnValue = N; break;  
435:   case 'O': returnValue = O; break;  
436:   case 'o': returnValue = O; break;  
437:   case 'P': returnValue = P; break;  
438:   case 'p': returnValue = P; break;  
439:   case 'Q': returnValue = Q; break;  
440:   case 'q': returnValue = Q; break;  
441:   case 'R': returnValue = R; break;  
442:   case 'r': returnValue = R; break;  
443:   case 'S': returnValue = S; break;  
444:   case 's': returnValue = S; break;  
445:   case 'T': returnValue = T; break;  
446:   case 't': returnValue = T; break;  
447:   case 'U': returnValue = U; break;  
448:   case 'u': returnValue = U; break;  
449:   case 'V': returnValue = V; break;  
450:   case 'v': returnValue = V; break;  
451:   case 'W': returnValue = W; break;  
452:   case 'w': returnValue = W; break;  
453:   case 'X': returnValue = X; break;  
454:   case 'x': returnValue = X; break;  
455:   case 'Y': returnValue = Y; break;  
456:   case 'y': returnValue = Y; break;  
457:   case 'Z': returnValue = Z; break;  
458:   case 'z': returnValue = Z; break;  
459:   case ' ': returnValue = _; break;  
460:   case '3': returnValue = B2; break;  
461:   case '<': returnValue = TEMP; break;  
462:   case '*': returnValue = FULL; break;  
463:   case '|': returnValue = LINE; break;   
464:   case '_': returnValue = _; break;   
465:   case ':': returnValue = COL; break;   
466:   case '-': returnValue = DASH; break;   
467:   case ')': returnValue = BRA2; break;   
468:   case '%': returnValue = SMILE; break;   
469:   case '.': returnValue = DOT; break;    
470:   case '^': returnValue = COLDOT; break;     
471:   }  
472:   return returnValue;  
473:  }  






Sunday, September 22, 2013

33. A Raspberry Pi Bird Cam in the Making

I saw an offer for a Raspberry Pi Model A / RasPiCam board bundle from CPC and it seemed like a good offer - £26.14 including VAT & delivery.  I am currently running my Pi (Model B) on top of my field scope, and it has been producing some interesting results, so rather than disturb it, I thought I would go for the CPC offer.

In the back of my mind is some sort of bird cam.  My peanut bird feeder is well established - the tits, finches, wrens and robins (and even a grey squirrel) seem to trust it now, in fact they probably depend on it.  The only hazard to them is our cat.

If the bird cam is to be placed remotely beside the feeder, it will need to be battery powered, and the best Pi for the job is the Model A as it uses less power than the B.  Measuring the power consumption while running a test program revealed that the Model A used 0.795 W while the Model B used 2.09 W. (Ref: http://www.raspberrypi.org/phpBB3/viewtopic.php?f=29&t=32955).  This is obviously important when powering the Pi by batteries.

So the CPC Model A seemed to be the one of choice.  Now to get the best out of a bird cam.  I thought it would have to be up close.  The problem with the RasPiCam is that it will only image objects sharply if they are at least 0.5 m away, at and this distance they occupy only a small portion of the field of view.

This problem can be remedied with some optics.  A couple of posts on RasPi.TV some time back (Ref: http://raspi.tv/2013/adapt-your-raspberry-pi-camera-for-close-up-use and http://raspi.tv/2013/raspicamcorder-software-released-on-github) featured a Raspberry Pi camcorder developed for close-up and personal video.  So, based on that advice, I ordered a set of close-up lenses and an adapter ring.  The 49 mm to 52 mm step-up adapter ring cost £2.54 delivered, from Ultra Sales Global.  The lenses have a diameter of 52 mm and have screw threads for mounting on SLR cameras etc.

The idea is to house the Pi in a case (another one from ModMyPi Mix 'N Match as before (£2.49 before VAT & delivery), and this time they sent me a clear bottom and blue top, again thankfully not pink).  I had already purchased a 50 mm CSI DSI Camera Ribbon Cable (£2.50 delivered, from AlienSpec), so I replaced the 150 mm one that came with the bundle, with that.  The 50 mm one is very handy and just right for this job.

I assembled the Pi in its case.  The new Pi has bolt holes through the board for fixing so that it can be bolted to the case bottom.  With the short ribbon cable coming up through the special slot to the camera, the camera board could be mounted on top of the case, but to do this effectively, I had to drill a couple of holes in the case so that 2 screws could pass through it and into the RasPiCam board.

To keep the camera board parallel to the case top, I padded it with some plastic foam (the sort that ICs come with, with their legs pushed into, to protect them).  This also allows for a little angular fine tuning if necessary, by adjusting the screws.




On aligning the adapter ring, and close-up lenses over the RasPiCam, I realised that some of the lenses would be too close to the camera itself, so I had to improvise some sort of spacer.  A quick root in the pantry, and the plastic cap of a Cadbury's Cocoa Powder container was 'borrowed'.  I cut the centre out of this and was left with a lovely red spacer ring.  This I hot-glued so that the camera was as dead centre as possible, and then I hot-glued the adapter ring in place.  Centring the assembly over the camera is not critical - the main thing is that it looks nice.  And it does - don't you think?  The hot-glueing job isn't very pretty, but that was my first hot-glue job.

The close-up lenses came from XCSource (£11.15 delivered) in a handy protective soft case, with compartments for the +1D, +2D, +4D and +10D lenses.  OK - now a short lesson in optics:  The D in +1D, +2D etc stands for dioptre (diopter in the USA).  It's a way of describing the Optical Power of a lens.  A +4D lens is a converging lens (described by the + as opposed to a - for a diverging lens) with a focal length of 250 cm, ie f = 0.25 m.  1/f = 4 dioptres, so the power in D is the inverse of the focal length in metres.  1/1.0 m = 1D, 1/0.5 m = 2D and 1/0.1 m = 10D.




The other cool thing about these lenses, is that some (but not all, due to the construction of their mounts - the +1D and +2D lenses cannot accept further lenses in front of them) can be piggy-backed together, and the total power is the sum of the dioptres.  So if you screw together the +10D, +4D and +2D, you end up with a +16D combination - equivalent to a single lens with a focal length of 62.5 mm - about 2½ inches.  Now that's what I call close-up!

Now it's time to put the whole thing together and try it out:  I want it to run 'headless' so I borrowed the SD card from the other Pi, plugged in the power mini-USB connector, attached the Edimax wifi dongle to a 25 cm Male to Female USB A cable (a thing I learned earlier, to allow the dongle to be optimally positioned).

The I ran Xming and PuTTY on the PC, and while I was at it, WinSCP also.  These all had the same log-in details as before, because it was the same SD card that was being communicated with.  Then I ran the VNC server on the Pi, and the VNC Session on the PC, and up popped the Pi's desktop!

On double-clicking the Geany icon, I ran the Python program MotionDetection.py which I had been using for motion detection, and lo and behold - pictures started recording.  I quickly checked these on ImageJ and yes - all is working - magic!  The pictures are quite good, and I took a series with:
No close-up lens (sharp imaging from 50 cm to )

+4D close-up lens (sharp imaging around 25 cm)

+4D & 10D close-up lenses (sharp imaging around 7.14 cm)

+4D & +10D & +2D close-up lenses (sharp imaging around 6.25 cm)

and you can see that the magnification is going up, the field of view is getting smaller (naturally) and, most importantly, the focus is getting shorter.  You can also see in the last three images, the internal reflection of the RasPiCam LED.  There are ways of turning this off, but for the moment, it's not a problem.  When it comes to spying on birds, I'm sure they won't like the flashing light.  The point of maximum sharpness is getting closer and closer.  So now I should be able (eventually) to see the birds within inches!

While writing this up, I remembered that the pictures were a little dark, and that I had forgotten to remove the protective plastic from the camera board lens.  So I removed it and took some more pictures, and to be honest, I couldn't see much difference!!

Here's a picture taken by the Bird Cam of motion-detected images of my Charlieplexed LED array at a distance of about 3 inches:




And here's the close-up set-up:


When I get time, I'll do a raspivid video.

ADDENDUM:  The Bird Cam project didn't get much further, as my 6 x 2400 mAh AA rechargeable power source didn't last more than a few hours.  I will have to look at the power end of things.  

I came across this post by Dave Ackerman (the chap who puts Raspberry Pi's into space) and it is very interesting and useful.  However, it does entail a bit of risky modification to the Pi, (voiding the warranty) and I haven't rustled up the nerve to go through with it.  However, it is an excellent article and worth coming back to for the next phase of this project, or indeed, any remote application.  Here's the link: Running The Raspberry Pi On Batteries and here's Babbage the Bionic Bear on his flight into near space at 41km high:
ascent96

It may be possible to get up to something like 28 hours with 6 x AA (1.5V) batteries (a bit less with 1.2V rechargeables) using his modification and an MCP1825S 3.3V regulator. 

Hmm interesting....