Teensy 3.6 VGA driver

Hi, Sorry to dig up this older thread, but I was searching for VGA on the Teensy. After watching the Ben Eater makes a video card I tried to write a routine to display something to VGA and after a lot of tweaking I managed to display 162x600 mono using this code
Code:
#define NOP __asm__ __volatile__ ("nop\n\t")
bool screenbuf[200][628];

const int vsync=16;
const int hsync=15;
const int redpix=19;
int ledState = 0x00;
const int ledPin = 13;
int bar=0;

void setup() {
  // put your setup code here, to run once:
  pinMode(vsync, OUTPUT);
  pinMode(hsync, OUTPUT);
  pinMode(redpix, OUTPUT);  
  for (int y=0;y<601;y++){
    for (int x=0;x<162;x++){
      if (bar<10){
         screenbuf[x][y]=  true;        
      } else {
         screenbuf[x][y]=  false;          
      }

     }
      bar++;
      if (bar>20){
        bar=0;
      }
     
  }  
}

void loop() {
  for (int y=0;y<628;y++){
    if (y>600 && y<605){
      ledState=0x00;
    } else {
      ledState=0x01;
    }
  GPIOC_PDOR = ledState;    
  for (int x=0;x<231;x++){
    if (x<=162){// pixels
      if (screenbuf[x][y]){
        ledState = 0x05;
      } else {
        ledState = 0x01;
      }          
    }
    if (x>162 && x<176){// front porch
      ledState=0x01;
    }
    if (x>171 && x<209){// vsync
      ledState=0x00;
    }
    if (x>208 && x<231){//back porc
      ledState=0x01;
    }
    GPIOB_PDOR = ledState;
  }
  }

}

I then moved this over to a interrupt running at 60hz and that also worked, but my cpu was maxed and I couldnt do anything else apart from display what was in the initial array.

Although it will not let me do anything else I am quite pleases that I managed to display anything at all !

So I am going to look at your lib and make up a interface and see what it can do.
 
Would be nice to see the VGA get attention - especially if possible for pending Teensy 4. Not looked at that here for some time since I wired up the needed adapter and used it some on the Teensy64 VGA board FrankB built.

Is the 60 Hz _isr() code not shown? It gets 16.7 ms to run and leave any time to run loop() at 60 Hz.
 
Would be nice to see the VGA get attention - especially if possible for pending Teensy 4. Not looked at that here for some time since I wired up the needed adapter and used it some on the Teensy64 VGA board FrankB built.

Is the 60 Hz _isr() code not shown? It gets 16.7 ms to run and leave any time to run loop() at 60 Hz.

I will have a look for that, but I did find this that looks easy enough for me to tinker with.

http://orchardelica.com/wp/?page_id=529
Code:
//
/*
* 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--------------------//
 
Oh - and congrats on getting something to show as noted.

The interval timer "myTimer.begin(function, microseconds);" with : timer0.begin(timerLine, 31.468);
is asking to be called 31778.31 times per second ?

reformatted the _isr to read it - timer calls:
Code:
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;
    }//end of one row of pixels
  }//end of display output section

  VSYNC = 0;
  if (y == Height) {
    VSYNC = 1;
  }
  RGB_OUT = 0;
  sei();
}
 
Regarding VGA output on the Teensy4.0... I’m reading the CPU manual, and the chip seems to have Video generation hardware built in to the chip already.

Perhaps someone more knowledgeable with these microcontrollers could have a look at chapter 35 and see how much, if any, this hardware might be usable for generating a VGA display?
 
I apologize in advance for the lack of knowledge!

I am attempting to use this library with a Teensy 3.2. I have VGA output working successfully, but the resolution is leaving me wanting more :)

When I run the following code, I'm shown the the Frame Buffer width is 222 and the height is 120.

Code:
#include <uVGA.h>

uVGA uvga;

// Defining this (UVGA_DEFAULT_REZ) will use the default resolution set for your CPU frequency.
// You can choose a different modeline by changing the define. Accepted values are in uVGA_valid_settings.h

#define UVGA_DEFAULT_REZ
#include <uVGA_valid_settings.h>

void setup()
{
  
  int ret; 
  ret = uvga.begin(&modeline);

  Serial.println(ret);

  if(ret != 0)
  {
    Serial.println("fatal error");
    while(1);
  }

  int fb_width, fb_height;
  uvga.get_frame_buffer_size(&fb_width, &fb_height);
  uvga.print("Frame Buffer H size = "); uvga.println(fb_width);
  uvga.print("Frame Buffer V size = "); uvga.println(fb_height);
  delay(3000);
}

I still don't quite understand how I can point this library to use a different resolution. I see the following in the Github README ("You can choose a different modeline by changing the define. Accepted values are in uVGA_valid_settings.h") but I'm not sure what "choose" mean in this sentence or how I apply my choice once picked.

Any thoughts as to whether or not it's possible to get a 640 horizontal resolution on a Teensy 3.2?
 
HI Qix67,

I looking howto transform a Lepton 160x120 send to teensy via MISO, on to a PAL on a pin teensy output.. It seem appear that you do it?
could you help ?
regard's
Laurent.

sample code arduino :
goal is to write on pin in PAL format otherwise of png...
from https://github.com/NachtRaveVL/Lepton-FLiR-Arduino/blob/master/examples/ImageCaptureExample/ImageCaptureExample.ino
Code:
// Lepton-FLiR-Arduino Image Capture Example
// In this example, we will copy out thermal image frames to individual BMP files located
// on a MicroSD card using the SD library. Note that you will need a MicroSD card reader
// module for this example to work. Both the FLiR module and MicroSD card reader module
// will be on the same SPI lines, just using different chip enable pins/wires.

#include "LeptonFLiR.h"
#include <SD.h>

const byte flirCSPin = 22;
LeptonFLiR flirController(Wire, flirCSPin); // Library using Wire and chip select pin D22

const byte cardCSPin = 24;

void setup() {
    Serial.begin(115200);

    Wire.begin();                       // Wire must be started first
    Wire.setClock(400000);              // Supported baud rates are 100kHz, 400kHz, and 1000kHz
    SPI.begin();                        // SPI must be started first as well

    SD.begin(cardCSPin);                // SD library using chip select pin D24

    // Using memory allocation mode 80x60 8bpp and fahrenheit temperature mode
    flirController.init(LeptonFLiR_ImageStorageMode_80x60_8bpp, LeptonFLiR_TemperatureMode_Fahrenheit);

    // Setting use of AGC for histogram equalization (since we only have 8-bit per pixel data anyways)
    flirController.agc_setAGCEnabled(ENABLED);

    flirController.sys_setTelemetryEnabled(ENABLED); // Ensure telemetry is enabled

    SD.rmdir("FLIR");                   // Starting fresh with new frame captures
}

uint32_t lastFrameNumber = -1;          // Tracks for when a new frame is available

void loop() {
    if (flirController.readNextFrame()) { // Read next frame and store result into internal imageData
        uint32_t frameNumber = flirController.getTelemetryFrameCounter();

        if (frameNumber > lastFrameNumber) { // Frame counter increments every 3rd frame due to export restrictions
            lastFrameNumber = frameNumber;

            char fileName[] = "FLIR/IMG0000.BMP";
            uint16_t fileNumber = (uint16_t)(frameNumber / 3);
            wordsToHexString((uint16_t *)&fileNumber, 1, &fileName[8], 4);

            File bmpFile = SD.open(fileName, FILE_WRITE);

            if (bmpFile) {
                writeBMPFile(bmpFile,
                             flirController.getImageData(),
                             flirController.getImageWidth(),
                             flirController.getImageHeight(),
                             flirController.getImagePitch());

                bmpFile.close();

                Serial.print(fileName);
                Serial.println(" written...");
            }
        }

        // Occasionally flat field correction normalization needs ran
        if (flirController.getShouldRunFFCNormalization())
            flirController.sys_runFFCNormalization();
    }
}

// Writes a BMP file out, code from: http://stackoverflow.com/questions/2654480/writing-bmp-image-in-pure-c-c-without-other-libraries
void writeBMPFile(File &bmpFile, byte *imageData, int width, int height, int pitch) {
    byte file[14] = {
        'B','M', // magic
        0,0,0,0, // size in bytes
        0,0, // app data
        0,0, // app data
        40+14,0,0,0 // start of data offset
    };
    byte info[40] = {
        40,0,0,0, // info hd size
        0,0,0,0, // width
        0,0,0,0, // heigth
        1,0, // number color planes
        24,0, // bits per pixel
        0,0,0,0, // compression is none
        0,0,0,0, // image bits size
        0x13,0x0B,0,0, // horz resoluition in pixel / m
        0x13,0x0B,0,0, // vert resolutions (0x03C3 = 96 dpi, 0x0B13 = 72 dpi)
        0,0,0,0, // #colors in pallete
        0,0,0,0, // #important colors
    };

    uint32_t padSize  = (4-(width*3)%4)%4;
    uint32_t sizeData = width*height*3 + height*padSize;
    uint32_t sizeAll  = sizeData + sizeof(file) + sizeof(info);

    file[ 2] = (byte)((sizeAll      ) & 0xFF);
    file[ 3] = (byte)((sizeAll >>  8) & 0xFF);
    file[ 4] = (byte)((sizeAll >> 16) & 0xFF);
    file[ 5] = (byte)((sizeAll >> 24) & 0xFF);
    info[ 4] = (byte)((width      ) & 0xFF);
    info[ 5] = (byte)((width >>  8) & 0xFF);
    info[ 6] = (byte)((width >> 16) & 0xFF);
    info[ 7] = (byte)((width >> 24) & 0xFF);
    info[ 8] = (byte)((height      ) & 0xFF);
    info[ 9] = (byte)((height >>  8) & 0xFF);
    info[10] = (byte)((height >> 16) & 0xFF);
    info[11] = (byte)((height >> 24) & 0xFF);
    info[20] = (byte)((sizeData      ) & 0xFF);
    info[21] = (byte)((sizeData >>  8) & 0xFF);
    info[22] = (byte)((sizeData >> 16) & 0xFF);
    info[23] = (byte)((sizeData >> 24) & 0xFF);

    bmpFile.write((byte *)file, sizeof(file));
    bmpFile.write((byte *)info, sizeof(info));

    byte pad[3] = {0,0,0};
    imageData += (height - 1) * pitch;

    for (int y = height - 1; y >= 0; --y) {
        for (int x = 0; x < width; ++x) {
            byte pixel[3]; // blue green red
            pixel[0] = pixel[1] = pixel[2] = imageData[x];

            bmpFile.write((byte *)pixel, 3);
        }

        bmpFile.write((byte *)pad, padSize);
        imageData -= pitch;
    }
}
 
Last edited:
Back
Top