Teensy 3 VGA colour with code

Is this of any practical use? Probably not. But it does offer up some interesting potential for low-fi low-res and potential art applications. However, if you are looking for something to control your large lcd vga screen with, it’s probably not for you.

VGA_output_Teensy31

Lukas Hartman (www.youtube.com/watch?v=9rqThgI5cB8) has done some great working in getting the timings sorted out to achieve a reasonably solid VGA signal with the Teensy 3. The Horizontal and vertical sync are controlled using the IntervalTimer() function available for the Teensy. Each sync pulse is allocated a pin which is drawn Low or High to generate the pulse of the required length.

Nick Gammon (http://www.gammon.com.au/forum/?id=11608) has a very good description of VGA timing issues and pulse length requirements linked to the work he has done on getting VGA working with Arduino Uno.

Because colour generation for the VGA monitor is derived from voltage values (i.e. an analogue process), and because the Teensy does not have the capability of outputting three analogue signals, one each for B, R and G, we have to get our colour mix from a set of static voltage values. Each of these colours, in this implementation, is derived from its own pin. The colour value can, however, be manipulated to an alternative fixed value by the use of resistors of different values. This is because of the use of the voltage divider concept.

So, using six output pins on the Teensy 3.1 we can derive two colour values for each of Red, Green and Blue, one dull and one bright. Six base colours can be used to give a theoretical palette of 64 colours.

VGA_Teensy3_1

The pins that are to be used to control the colour voltage output are going to be on the same port of the microcontroller. Why? Well, it means that we can set values for the pins in a single processor cycle. This means that we can get the colour settings out as fast as possible, and leaves time for the processor to get other things done.

Port manipulation is described very well across the web, with specifics for Teensy in this tutorial here: https://forum.pjrc.com/threads/17532-Tutorial-on-digital-I-O-ATMega-PIN-PORT-DDR-D-B-registers-vs-ARM-GPIO_PDIR-_PDOR

We assign the colour pins of the VGA monitor to six pins of the Teensy PortB, and with this we are able to switch on or off for each of the six colour values for each of our pixels. For example
If on the below portB pins we have our colour connections as: x|x|g|g|b|b|r|r
And we set the port to:
PORTB = B00000001
Only the pin for the first “r” would be set to high, so producing one of our red pixels. These colour pins can then be mixed as we want them but either setting the pin to high or low for each clock / processor cycle.
In the code, we can put these port configurations into an array and choose from it, or can explicitly set as above, or use hex values to describe the binary. Such as 0x01 being B00000001, 0x10 being B00010000, or blend colour output with 0x14 being B00010100 (which gives some blue and green mixed together).

If we do some loop coding, we can feed a buffer that holds a value for each of our screen pixels with a colour value fast enough to get a refresh rate that works. At the moment I can have 200*200 pixels buffer size. The values in this buffer can be updated by a number of methods. A simple maths for loop can update the buffer values in between each screen refresh so that we can get some geometric patterns flowing. We can use byte colour values (8bits) and shift them into the right six bit places, or we can use some more complex bitwise operations to move colour values around to get them into our RGB values.

Teensy 3 displaying 8bit BMP over VGA, converted to 6bit colours
Teensy 3 displaying 8bit BMP over VGA, converted to 6bit colours

Or, we can update the buffer from an sd card file, such as this 8bit BMP file. 8bit colour = one pixel per byte, but… an 8bit BMP is a little different in concept, as it uses an index to a colour table/palette. The colours are a off, so some of the code needs tweeking, but you get the idea. This needs some more work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
//
/*
* this code is a basic 'get you up and running' code
* and is rough and ready. It is availble to help you 
* figure things out. Hopefully you can improve on it.
* mortonkopf
*/
//based on timings from Lukas Hartmann
//also added the drawing functions from Lukas.
//https://www.youtube.com/watch?v=FfxH6zlsDGo
// using 72MHz Teensy 3.1
// using resistors on outputs to derive c0.7v signals
//based on wiring from Nick Gammon VGA page
 
#define Width  200 //sreen pixels
#define Height  200  //screen pixels
 
/////for spiral pattern///////
unsigned long runTime = 0;
float sx = 0, sy = 0;
uint16_t xx = 0, xx1 = 0, yy = 0, yy1 = 0;
/////////////
#include <SdFat.h>
SdFat sd;
SdFile myFile;
const int chipSelect = 10;
unsigned long bitmapOffset = 54;//0x36;
 
uint8_t lcdBuffer[Height*Width]; 
// PortB[0:3, 16:19] = {16, 17, 19, 18, 0, 1, 32, 25}
/* Teensy external pin number / GPIO port / bit of port
0 B 16
1 B 17
16 B 0
17 B 1
18 B 3
19 B 2
25 B 19
32 B 18
*/
//* bit colour values from bitmap will be?
//Bit    7  6  5  4  3  2  1  0
//Data   R  R  R  G  G  G  B  B
 
// VGA PINS
#define RGB_OUT GPIOB_PDOR
#define PIN_VR1 18//16 
#define PIN_VR2 19//17
#define PIN_VG1 16//19
#define PIN_VG2 17//18
#define PIN_VB1 0
#define PIN_VB2 1
#define PIN_VBLANK 8
#define PIN_HBLANK 7
 
//#define RED B00110000
//#define BLUE B00000011
//#define GREEN B00001100
 
//byte colours[6] = {0x01,0x02,0x04,0x08,0x10,0x20};//option for calling all single bit/pin values r,r,b,b,g,g
 
static volatile int VSYNC = 0;
 
#define NOP asm volatile("nop\n\t");
 
volatile uint32_t currentLine = 0;
volatile uint32_t currentFrame = 0;
 
IntervalTimer timer0;
 
#define UPPER_BORDER 40
 
void timerLine() {
  cli();
 
  if (currentLine>1) {
    digitalWrite(PIN_VBLANK, 1);
  } else {
    digitalWrite(PIN_VBLANK, 0);
  }
 
  digitalWrite(PIN_HBLANK, 0);
  RGB_OUT = 0x00;
  RGB_OUT = 0x00;
  RGB_OUT = 0x00;
  RGB_OUT = 0x00;
 
  digitalWrite(PIN_HBLANK, 1);
 
  currentLine++;
  currentLine = currentLine%525;
 
  NOP;
 
  uint32_t y = (currentLine - UPPER_BORDER)>>1;//>>2;//how many rows to duplicate (to square up the image)
 
//display output section is within the timer >>
  if (y>=0 && y<Height){  //for each row do
 
    for (int i=0; i<40; i++) {//left edge buffer
      RGB_OUT = 0;
    }
 
    uint8_t* displayPointer = lcdBuffer + Width*y;
 
   for (int x=0; x<Width; x++) {
 
    uint32_t color = *(displayPointer++);
 
      color = color | (color<<12); //shift bits
      RGB_OUT = color;
   //   RGB_OUT = color;
  //    RGB_OUT = color;
  //    RGB_OUT = color; //four calls wide to square up each pixel
    }//end of one row of pixels
  }//end of display output section
 
  VSYNC = 0;
   if (y==Height) {  
    VSYNC = 1;
  }
  RGB_OUT = 0;
  sei();
}
 
 
void setup()   {
  delay(500);
    // Initialize SdFat or print a detailed error message and halt
  if (!sd.begin(chipSelect, SPI_FULL_SPEED)) sd.initErrorHalt();
 
//////-------------------------////////////  
  delay(500);
  pinMode(PIN_VR1, OUTPUT);
  pinMode(PIN_VG1, OUTPUT);
  pinMode(PIN_VB1, OUTPUT);
  pinMode(PIN_VR2, OUTPUT);
  pinMode(PIN_VG2, OUTPUT);
  pinMode(PIN_VB2, OUTPUT);
  pinMode(PIN_VBLANK, OUTPUT);
  pinMode(PIN_HBLANK, OUTPUT);
 
   pinMode(23, INPUT);//for input for patterns controlled by abalogue input?
 
  timer0.begin(timerLine, 31.468);
 
}
 
unsigned int tick = 0;
int t=0;
int y =0;
 
void loop()          
{
    if (VSYNC==1) {
      tick++;   
      VSYNC=0;
//---------------in this section we can fill the buffer with our colours------//
 
//display a bmp from sd card
//displayBMP("holly21.bmp"); //bmp needs to be 8bit bmp
 
spiral();
//drawCircle(100,100,50,0x1);    // DRAW the circle
maths1();
 
//fill the screen with a colour
//fillScreen(0x0);
 
//then draw a diagonal series of dots
 // drawLine(10,10,100,100,0x1); 
 
//then draw a line 
//  for(int x=0;x<100;x++){
//   putPixel(x,10,0x1);
//   }
 
    }
}
 
 
 
//------------drawing functions-------------------------//
void drawPixel(int x0,int y0,uint8_t color) {
  lcdBuffer[y0*Width+x0] = color;
}
void drawLine(int x0, int y0, int x1, int y1, uint8_t color) {
  int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1;
  int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1; 
  int err = (dx>dy ? dx : -dy)/2, e2;
 
  for(;;){
    drawPixel(x0,y0,color);
    if (x0==x1 && y0==y1) break;
    e2 = err;
    if (e2 >-dx) { err -= dy; x0 += sx; }
    if (e2 < dy) { err += dx; y0 += sy; }
  }
}
void fillScreen(uint8_t c) {
  for (int y=0; y<Height; y++) {
    for (int x=0; x<Width; x++) {
      lcdBuffer[y*Width+x] = c;
    }
  }
}
//----------------------------//
 
void drawCircle(int xm, int ym, int r, int col){  //centre values
 
   int x = -r, y = 0, err = 2-2*r;                /* bottom left to top right */
   do {                           
 
      drawPixel(xm-x, ym+y,col);                        /*   I. Quadrant +x +y */
      drawPixel(xm-y, ym-x,col);                        /*  II. Quadrant -x +y */
      drawPixel(xm+x, ym-y,col);                        /* III. Quadrant -x -y */
      drawPixel(xm+y, ym+x,col);                        /*  IV. Quadrant +x -y */
      r = err;                                   
      if (r <= y) err += ++y*2+1;                             /* e_xy+e_y < 0 */
      if (r > x || err > y)                  /* e_xy+e_x > 0 or no 2nd y-step */
         err += ++x*2+1;                                     /* -> x-step now */
   } while (x < 0); 
}
 
//------------use a bmp to load values into buffer---------------//
 
void displayBMP(char* filename){ //this is really flakey on the numbers for pad1 to make it work
  //----------------------------------------------------//
      //open the file for reading:
  if (!myFile.open(filename/*"holly.bmp"*/, O_READ)) {
    sd.errorHalt("opening file for read failed");
  }
  int pad1 = Width*6; //for the colour table ref for 8bit bmp? //these pad values are trial and error for me
   byte data;
   byte dataG, dataB, dataR, pix;
   int row,col;
 
 // loop through each pixel in the file
for(int j=pad1; j<(Height*Width)+(pad1-Width);j++){
   //set position in file to read
   int x = bitmapOffset+j;    
      myFile.seekSet(x);
      data = myFile.read();
      //attempt to move 8bit colour to 6pins of output using bit mask- might not be right
      dataR = data & B00000011; //should this be &= ?
      dataB = data & B00011000;
      dataG = data & B11000000;
      pix = (dataG>>2) + (dataB) + dataR;
 
//put the data into the buffer - but is upside down?
   lcdBuffer[j-pad1+109] = pix;
  }
  // close the file:
  myFile.close();  
}
//---------------end of displaybmp------------------------------------//
 
 
//---------------patternFrac-------------------------//
 /*  https://github.com/pixelmatix/aurora
 * based on Copyright (c) 2014 Jason Coon
*/
int time = 0;
int cycles = 0;
int16_t v;
int z;
 void patternFrac() {
    for (int x = 0; x < Width; x++) {
        for (int y = 0; y < Height; y++) {
           v = 0;
            uint8_t wibble = (time);
            v += (x * wibble * 2 + time);
            v += (y * (128 - wibble) * 2 + time);
            v += (y * x * (-time) / 2);
 
    //    if(((v>>8)+127)>20) {
          lcdBuffer[y*Width+x] = (v >> 8) + 127;
    //    }
    //        else { lcdBuffer[y*Width+x] = 0;}
        }
    }
    time += 1;
    cycles++;
    if (cycles >= 750) {
        time = 0;
        cycles = 0;
    }
}
//-------------------------------end of pattern--------------//
 
//--------------------------maths1 pattern------------------//
int deep =65;
void maths1(){
 
  for(int number=0;number<150;number++){
    for (int x = 0; x < Width; x++) {
        for (int y = 0; y < Height; y++) {
           v = 0;
            uint8_t wibble = (time);
            v += (x * wibble * 2 + time);
            v += (y * (128 - wibble) * 2 + time);
            v += (y * x * (-time) / 2);
 
        if(((v>>8)+127)>deep) {
          lcdBuffer[y*Width+x] = (v >> 8) + 127;}
            else { lcdBuffer[y*Width+x] = 0;}
        }
    }
    deep +=1;
    time += 1;
    cycles++;
    if (cycles >= 180) { //keep cycles and deep max lengths different for more interesting pattern variation
      deep =65;
        time = 0;
        cycles = 0;
    }
}
//second part
deep = 245;
cycles = 170;
time=0;
  for(int number=0;number<170;number++){
    for (int x = 0; x < Width; x++) {
        for (int y = 0; y < Height; y++) {
           v = 0;
            uint8_t wibble = (time);
            v += (x * wibble * 2 + time);
            v += (y * (128 - wibble) * 2 + time);
            v += (y * x * (-time) / 2);
 
        if(((v>>8)+127)>deep) {
          lcdBuffer[y*Width+x] = (v >> 8) + 127;}
            else { lcdBuffer[y*Width+x] = 0;}
        }
    }
    deep -=1;
    time -= 1;
    cycles--;
    }
}
 
//------------------------------------------------//
 
//--------spiral pattern----------------//
void spiral(){
  fillScreen(0x0);
byte c = random(0x01,0x40);
int n = random(12, 30), r = random(25, 55);
 // Rainbow patern generator
// Alan Senior 22/2/15 
  for (long i = 0; i < (360 * n); i++) {
    sx = cos((i / n - 90) * 0.0174532925);
    sy = sin((i / n - 90) * 0.0174532925);
    xx = sx * (20 - r) + 100;
    yy = sy * (20 - r) + 100;
 
    sy = cos(((i % 360) - 90) * 0.0174532925);
    sx = sin(((i % 360) - 90) * 0.0174532925);
    xx1 = sx * r + xx;
    yy1 = sy * r + yy;
    drawPixel(xx1, yy1, c);
}
byte cc =random(0x01,0x40);
  n = random(2, 20),
  r = random(30, 55);
  for (long i = 0; i < (360 * n); i++) {
    sx = cos((i / n - 90) * 0.0174532925);
    sy = sin((i / n - 90) * 0.0174532925);
    xx = sx * (20 - r) + 100;
    yy = sy * (20 - r) + 100;
 
    sy = cos(((i % 360) - 90) * 0.0174532925);
    sx = sin(((i % 360) - 90) * 0.0174532925);
    xx1 = sx * r + xx;
    yy1 = sy * r + yy;
    drawPixel(xx1, yy1, cc);//i
  }fillScreen(0x00);
}
//---------end of sprial pattern--------------------//