VGA video generation

HI, need to test an Atari SM 124 monitor.
Don't have a 1040 at hand.
The following program looked like it could do the job.
It would not compile.
"compilation terminated.
exit status 1
Compilation error: TimerHelpers.h: No such file or directory"
Found the file but Arduino but would not install into library.
Any help appreciated.

<

/*
VGA video generation

Author: Nick Gammon
Date: 20th April 2012

Example code without I2C communications.

Connections:

D1 : Pixel output (180 ohms in series) (connect to R, G, B tied together) --> Pins 1, 2, 3 on DB15 socket
D3 : Horizontal Sync (68 ohms in series) --> Pin 13 on DB15 socket
D10 : Vertical Sync (68 ohms in series) --> Pin 14 on DB15 socket

Gnd : --> Pins 5, 6, 7, 8, 10 on DB15 socket

*/

#include <TimerHelpers.h>
#include <avr/pgmspace.h>
#include "screenFont.h"
#include <avr/sleep.h>

#define BETA_ARDUINO ARDUINO < 100

const byte pixelPin = 1; // <------- Pixel data
const byte hSyncPin = 3; // <------- HSYNC
const byte MSPIM_SCK = 4; // <-- we aren't using it directly
const byte vSyncPin = 10; // <------- VSYNC

const int horizontalBytes = 20; // 160 pixels wide
const int verticalPixels = 480; // 480 pixels high

const byte i2cAddress = 42;

// Timer 1 - Vertical sync

// output OC1B pin 16 (D10) <------- VSYNC

// Period: 16.64 ms (60 Hz)
// 1/60 * 1e6 = 16666.66 µs
// Pulse for 64 µs (2 x HSync width of 32 µs)
// Sync pulse: 2 lines
// Back porch: 33 lines
// Active video: 480 lines
// Front porch: 10 lines
// Total: 525 lines

// Timer 2 - Horizontal sync

// output OC2B pin 5 (D3) <------- HSYNC

// Period: 32 µs (31.25 kHz)
// (1/60) / 525 * 1e6 = 31.74 µs
// Pulse for 4 µs (96 times 39.68 ns)
// Sync pulse: 96 pixels
// Back porch: 48 pixels
// Active video: 640 pixels
// Front porch: 16 pixels
// Total: 800 pixels

// Pixel time = ((1/60) / 525 * 1e9) / 800 = 39.68 ns
// frequency = 1 / (((1/60) / 525 * 1e6) / 800) = 25.2 MHz

// However in practice, it is the SPI speed, namely a period of 125 ns
// (that is 2 x system clock speed)
// giving an 8 MHz pixel frequency. Thus the characters are about 3 times too wide.
// Thus we fit 160 of "our" pixels on the screen in what usually takes 3 x 160 = 480

const byte screenFontHeight = 8;
const byte screenFontWidth = 8;

const int verticalLines = verticalPixels / screenFontHeight / 2; // double-height characters
const int horizontalPixels = horizontalBytes * screenFontWidth;

const byte verticalBackPorchLines = 35; // includes sync pulse?
const int verticalFrontPorchLines = 525 - verticalBackPorchLines;

volatile int vLine;
volatile int messageLine;
volatile byte backPorchLinesToGo;

char message [verticalLines] [horizontalBytes];

// ISR: Vsync pulse
ISR (TIMER1_OVF_vect)
{
vLine = 0;
messageLine = 0;
backPorchLinesToGo = verticalBackPorchLines;
} // end of TIMER1_OVF_vect

// ISR: Hsync pulse ... this interrupt merely wakes us up
ISR (TIMER2_OVF_vect)
{
} // end of TIMER2_OVF_vect

void setup()
{

// initial message ... change to suit
for (int i = 0; i < verticalLines; i++)
sprintf (message [i], "Line %03i - hello!", i);

// disable Timer 0
TIMSK0 = 0; // no interrupts on Timer 0
OCR0A = 0; // and turn it off
OCR0B = 0;

// Timer 1 - vertical sync pulses
pinMode (vSyncPin, OUTPUT);
Timer1::setMode (15, Timer1::PRESCALE_1024, Timer1::CLEAR_B_ON_COMPARE);
OCR1A = 259; // 16666 / 64 µs = 260 (less one)
OCR1B = 0; // 64 / 64 µs = 1 (less one)
TIFR1 = bit (TOV1); // clear overflow flag
TIMSK1 = bit (TOIE1); // interrupt on overflow on timer 1

// Timer 2 - horizontal sync pulses
pinMode (hSyncPin, OUTPUT);
Timer2::setMode (7, Timer2::PRESCALE_8, Timer2::CLEAR_B_ON_COMPARE);
OCR2A = 63; // 32 / 0.5 µs = 64 (less one)
OCR2B = 7; // 4 / 0.5 µs = 8 (less one)
TIFR2 = bit (TOV2); // clear overflow flag
TIMSK2 = bit (TOIE2); // interrupt on overflow on timer 2

// Set up USART in SPI mode (MSPIM)

// baud rate must be zero before enabling the transmitter
UBRR0 = 0; // USART Baud Rate Register
pinMode (MSPIM_SCK, OUTPUT); // set XCK pin as output to enable master mode
UCSR0B = 0;
UCSR0C = bit (UMSEL00) | bit (UMSEL01) | bit (UCPHA0) | bit (UCPOL0); // Master SPI mode

// prepare to sleep between horizontal sync pulses
set_sleep_mode (SLEEP_MODE_IDLE);

} // end of setup

// draw a single scan line
void doOneScanLine ()
{

// after vsync we do the back porch
if (backPorchLinesToGo)
{
backPorchLinesToGo--;
return;
} // end still doing back porch

// if all lines done, do the front porch
if (vLine >= verticalPixels)
return;

// pre-load pointer for speed
const register byte * linePtr = &screen_font [ (vLine >> 1) & 0x07 ] [0];
register char * messagePtr = & (message [messageLine] [0] );

// how many pixels to send
register byte i = horizontalBytes;

// turn transmitter on
UCSR0B = bit (TXEN0); // transmit enable (starts transmitting white)

// blit pixel data to screen
while (i--)
UDR0 = pgm_read_byte (linePtr + (* messagePtr++));

// wait till done
while (!(UCSR0A & bit(TXC0)))
{}

// disable transmit
UCSR0B = 0; // drop back to black

// this line done
vLine++;

// every 16 pixels it is time to move to a new line in our text
// (because we double up the characters vertically)
if ((vLine & 0xF) == 0)
messageLine++;

} // end of doOneScanLine

void loop()
{
// sleep to ensure we start up in a predictable way
sleep_mode ();
doOneScanLine ();
} // end of loop>

You started a topic in the Uncategorised category of the forum when its description explicitly tells you not to

Your topic has been moved to a relevant category. Please be careful in future when deciding where to start new topics

Please follow the advice given in the link below when posting code, in particular the section entitled 'Posting code and common code problems'

Use [color = red]code tags[/color] (the < CODE/ > icon above the compose window) to make it easier to read and copy for examination

https://forum.arduino.cc/t/how-to-get-the-best-out-of-this-forum

In my experience the easiest way to tidy up the code and add the code tags is as follows
Start by tidying up your code by using Tools/Auto Format in the IDE to make it easier to read. Then use Edit/Copy for Forum and paste what was copied in a new reply. Code tags will have been added to the code to make it easy to read in the forum thus making it easier to provide help.

Hi @spuddo.

I'll provide instructions you can follow to install the library:

  1. Click the following link:
    https://gammon.com.au/Arduino/TimerHelpers.zip
    Your web browser will start a file download.
  2. Select Sketch > Include library > Add .ZIP Library from the Arduino IDE menus.
    The "Select the zip file containing the library you'd like to add" dialog will open.
  3. Select the downloaded file from the dialog.
  4. Click the "Open" button.
    The dialog will close.
  5. Wait for the installation process to finish, as indicated by a notification at the bottom right corner of the Arduino IDE window:

    ⓘ Successfully installed library from ...


This is a very old tutorial (but probably still valuable), so there is one change you need to make in order to allow the code to compile with the modern compiler versions:

Change this line of the screenFont.h tab of your sketch:

byte screen_font [8] [256] PROGMEM = {

To this:

const byte screen_font [8] [256] PROGMEM = {

Now try compiling the sketch again. Hopefully this time it will compile without any errors and work as expected.

Hi, thanks for the reply.
I followed the instructions.
The TimerHelper.zip downloaded ok.
When I selected "add .zip Library", I could not open the downloaded file in that window.
This is the error message I got.
"A subfolder of your sketchbook is not a valid library"
Any thoughts.
Best regards.

Which version of Arduino IDE are you using (e.g., "2.0.1")? The version is shown on the window title bar and also in the dialog that opens when you select Help > About (or Arduino IDE > About Arduino IDE for macOS users) from the Arduino IDE menus.

HI, IDE is 1.8.18
The author is on the forum, but he hasn't chimed in yet.
I tried to open a new topic on his own forum , but it was denied because I dared ask about Arduino.
Best regards.

I'm not familiar with this error. I find reports of it on Google, but they seem to be from users of Arduino IDE 1.x, which allows you to install folders via the "Add .ZIP Library" feature in addition to ZIP files (Arduino IDE 2.x only allows installing from a ZIP file).

So I'm stumped about how to fix the problem. However, this is not an obstacle because there is an alternative manual library installation technique we can use to workaround the problem altogether.

I'll provide instructions you can follow to install the library manually:

  1. Select File > Preferences... (or Arduino IDE > Settings... for macOS users) from the Arduino IDE menus.
    The "Preferences" dialog will open.
  2. Take note of the path shown in the "Sketchbook location" field of the dialog.
  3. Click the "CANCEL" button.
    The "Preferences" dialog will close.
  4. Click the following link:
    https://gammon.com.au/Arduino/TimerHelpers.zip
    Your web browser will start a file download.
  5. Wait for the download to finish.
  6. Extract the downloaded file.
  7. Copy the TimerHelpers folder from the extracted folder to the libraries subfolder of the path you saw in the "Sketchbook location" preference.
    The folder structure of the installation must look like this:
    <Sketchbook location>/
    ├── libraries/
    │   ├── TimerHelpers/
    │   │   ├── TimerHelpers.h
    │   │   ...
    │   ...
    ...
    
    (where <Sketchbook location> is the path from the Sketchbook location" preference)
  8. Select File > Quit (or Arduino IDE > Quit Arduino IDE for macOS users) from the Arduino IDE menus.
    All Arduino IDE windows will close.
  9. Start Arduino IDE.

This is a very old tutorial (but probably still valuable), so there is one change you need to make in order to allow the code to compile with the modern compiler versions:

Change this line of the screenFont.h tab of your sketch:

byte screen_font [8] [256] PROGMEM = {

To this:

const byte screen_font [8] [256] PROGMEM = {

Now try compiling the sketch again. Hopefully this time it will compile without any errors and work as expected.

Hi ptillisch, thanks for your patience and help.

I followed your instructions to manually install "TimersHelpers.h" to no avail.

I changed the line of the screenFont.h tab in the Gammon 1.2 sketch.
It seemed to accept the change, but alas still would not compile.
Close but no cigar.

  
/*
 VGA video generation

 Author:   Nick Gammon
 Date:     20th April 2012
 Version:  1.2

 Version 1.0: initial release
 Version 1.1: code cleanups
 Version 1.2: more cleanups, added clear screen (0x0C), added scrolling


 Connections:

 D1 : Pixel output (470 ohms in series to each one of R, G, B)   --> Pins 1, 2, 3 on DB15 socket
 D3 : Horizontal Sync (68 ohms in series) --> Pin 13 on DB15 socket
 D10 : Vertical Sync (68 ohms in series) --> Pin 14 on DB15 socket

 Gnd : --> Pins 5, 6, 7, 8, 10 on DB15 socket

 PERMISSION TO DISTRIBUTE

 Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 and associated documentation files (the "Software"), to deal in the Software without restriction,
 including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
 subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.


 LIMITATION OF LIABILITY

 The software is provided "as is", without warranty of any kind, express or implied,
 including but not limited to the warranties of merchantability, fitness for a particular
 purpose and noninfringement. In no event shall the authors or copyright holders be liable
 for any claim, damages or other liability, whether in an action of contract,
 tort or otherwise, arising from, out of or in connection with the software
 or the use or other dealings in the software.

*/

#include "TimerHelpers.h"
#include <avr/pgmspace.h>
#include "screenFont.h"
#include <avr/sleep.h>
#include <Wire.h>

#define BETA_ARDUINO ARDUINO < 100

const byte pixelPin = 1;     // <------- Pixel data
const byte hSyncPin = 3;     // <------- HSYNC
const byte MSPIM_SCK = 4;    // <-- we aren't using it directly
const byte vSyncPin = 10;    // <------- VSYNC

const int horizontalBytes = 20;  // 160 pixels wide
const int verticalPixels = 480;  // 480 pixels high

const byte i2cAddress = 42;

// Timer 1 - Vertical sync

// output    OC1B   pin 16  (D10) <------- VSYNC

//   Period: 16.64 ms (60 Hz)
//      1/60 * 1e6 = 16666.66 µs
//   Pulse for 64 µs  (2 x HSync width of 32 µs)
//    Sync pulse: 2 lines
//    Back porch: 33 lines
//    Active video: 480 lines
//    Front porch: 10 lines
//       Total: 525 lines

// Timer 2 - Horizontal sync

// output    OC2B   pin 5  (D3)   <------- HSYNC

//   Period: 32 µs (31.25 kHz)
//      (1/60) / 525 * 1e6 = 31.74 µs
//   Pulse for 4 µs (96 times 39.68 ns)
//    Sync pulse: 96 pixels
//    Back porch: 48 pixels
//    Active video: 640 pixels
//    Front porch: 16 pixels
//       Total: 800 pixels

// Pixel time =  ((1/60) / 525 * 1e9) / 800 = 39.68  ns
//  frequency =  1 / (((1/60) / 525 * 1e6) / 800) = 25.2 MHz

// However in practice, it is the SPI speed, namely a period of 125 ns
//     (that is 2 x system clock speed)
//   giving an 8 MHz pixel frequency. Thus the characters are about 3 times too wide.
// Thus we fit 160 of "our" pixels on the screen in what usually takes 3 x 160 = 480

const byte screenFontHeight = 8;
const byte screenFontWidth = 8;
const byte screen_Font [8] [256] PROGMEM = {};

const int verticalLines = verticalPixels / screenFontHeight / 2;  // double-height characters
const int horizontalPixels = horizontalBytes * screenFontWidth;

const byte verticalBackPorchLines = 35;  // includes sync pulse?
const int verticalFrontPorchLines = 525 - verticalBackPorchLines;

volatile int vLine;
volatile int messageLine;
volatile byte backPorchLinesToGo;

enum SEND_COMMANDS { CLRSCR = 1, CLREOL, GOTOXY, ESC = 27 };
enum STATES { NORMAL, GOT_ESCAPE, GOT_GOTOXY, GOT_X };

char message [verticalLines]  [horizontalBytes];
byte column, line;
STATES state = NORMAL;
byte x, y;  // for gotoxy


// ISR: Vsync pulse
ISR (TIMER1_OVF_vect)
  {
  vLine = 0;
  messageLine = 0;
  backPorchLinesToGo = verticalBackPorchLines;
  } // end of TIMER1_OVF_vect

// ISR: Hsync pulse ... this interrupt merely wakes us up
ISR (TIMER2_OVF_vect)
  {
  } // end of TIMER2_OVF_vect

// called by interrupt service routine when incoming data arrives

/*
Expected formats are:
   * ordinary text:           gets displayed
   * carriage-return (0x0D):  returns cursor to start of current line
   * newline (0x0A):          drops down a line and also goes to the start of the line
   * clear screen (0x0C):     clear screen, return cursor to 1,1
   * ESC (0x1B) followed by:
      * 1 : clear screen, return cursor to 1,1
      * 2 : clear to end of current line
      * 3 : go to x,y ... next two bytes are X and then Y: one-relative

  All writing wraps, eg. text wraps at end of line, then end of screen back to line 1, column 1.
  A gotoxy out of range is ignored.
*/

void receiveEvent (int howMany)
 {
  while (Wire.available () > 0)
  {
    byte c;
#if BETA_ARDUINO
    c = Wire.receive ();
#else
    c = Wire.read ();
#endif

    // first check state ... see if we are expecting a command or an x/y position
    switch (state)
      {
      // normal is, well, normal unless we get an ESC character
      case NORMAL:
          switch (c)
            {
            case ESC:
              state = GOT_ESCAPE;
              break;

            // otherwise just display the character
            default:
               message [line] [column] = c;
               if (++column >= horizontalBytes)
                 {
                 column = 0;
                 line++;
                 } // end wrapped line

              if (line < verticalLines)
                  break;
            // if wrapped past end of buffer, fall through to do a newline which will scroll up

            // newline starts a new line, and drops down to do a carriage-return as well
            case '\n':
              // end end? scroll
              if (++line >= verticalLines)
                {
                // move line 2 to line 1 and so on ...
                memmove (& message [0] [0], & message [1] [0], sizeof message - horizontalBytes);
                // clear last line
                memset (&message [verticalLines - 1] [0], ' ', horizontalBytes);
                // put cursor on last line
                line = verticalLines - 1;    // back to last line
                }
            // fall through ...

            // carriage-return returns to start of line
            case '\r':
              column = 0;
              break;

            // clear screen
            case '\f':
              memset (message, ' ', sizeof message);
              line = column = 0;
              break;

             }  // end of switch on incoming character

          break;  // end of NORMAL

        // got ESC previously
        case GOT_ESCAPE:
          switch (c)
            {
            // clear screen ... just do it
            case CLRSCR:
              memset (message, ' ', sizeof message);
              line = column = 0;
              state = NORMAL;
              break;

            // clear to end of line
            case CLREOL:
              memset (&message [line] [column], ' ', horizontalBytes - column);
              state = NORMAL;
              break;

            // gotoxy expects two more bytes (x and y)
            case GOTOXY:
              state = GOT_GOTOXY;
              break;

            // unexpected ... not recognized command
            default:
              state = NORMAL;
              break;
            } // end of switch on command type
          break;  // end of GOT_ESCAPE

        // we got x, now we want y
        case GOT_GOTOXY:
          x = c - 1;  // make zero-relative
          state = GOT_X;
          break;

        // we now have x and y, we can move the cursor
        case GOT_X:
          y = c - 1;  // make zero-relative

          // if possible that is
          if (x < horizontalBytes && y < verticalLines)
            {
            column = x;
            line = y;
            }
          state = NORMAL;
          break;

        // unexpected ... not recognized state
        default:
          state = NORMAL;
          break;
      } // end of switch on state

  }  // end of while available
}  // end of receiveEvent

void setup()
  {

  // initial message ... change to suit
  for (int i = 0; i < verticalLines; i++)
    sprintf (message [i], "Line %03i - hello!", i);

  // disable Timer 0
  TIMSK0 = 0;  // no interrupts on Timer 0
  OCR0A = 0;   // and turn it off
  OCR0B = 0;

  // Timer 1 - vertical sync pulses
  pinMode (vSyncPin, OUTPUT);
  Timer1::setMode (15, Timer1::PRESCALE_1024, Timer1::CLEAR_B_ON_COMPARE);
  OCR1A = 259;  // 16666 / 64 µs = 260 (less one)
  OCR1B = 0;    // 64 / 64 µs = 1 (less one)
  TIFR1 = bit (TOV1);   // clear overflow flag
  TIMSK1 = bit (TOIE1);  // interrupt on overflow on timer 1

  // Timer 2 - horizontal sync pulses
  pinMode (hSyncPin, OUTPUT);
  Timer2::setMode (7, Timer2::PRESCALE_8, Timer2::CLEAR_B_ON_COMPARE);
  OCR2A = 63;   // 32 / 0.5 µs = 64 (less one)
  OCR2B = 7;    // 4 / 0.5 µs = 8 (less one)
  TIFR2 = bit (TOV2);   // clear overflow flag
  TIMSK2 = bit (TOIE2);  // interrupt on overflow on timer 2

  // Set up USART in SPI mode (MSPIM)

  // baud rate must be zero before enabling the transmitter
  UBRR0 = 0;  // USART Baud Rate Register
  pinMode (MSPIM_SCK, OUTPUT);   // set XCK pin as output to enable master mode
  UCSR0B = 0;
  UCSR0C = bit (UMSEL00) | bit (UMSEL01) | bit (UCPHA0) | bit (UCPOL0);  // Master SPI mode

  // prepare to sleep between horizontal sync pulses
  set_sleep_mode (SLEEP_MODE_IDLE);

  // for incoming data to display from I2C
  Wire.begin (i2cAddress);
  Wire.onReceive (receiveEvent);

}  // end of setup

// draw a single scan line
void doOneScanLine ()
  {

  // after vsync we do the back porch
  if (backPorchLinesToGo)
    {
    backPorchLinesToGo--;
    return;
    }  // end still doing back porch

  // if all lines done, do the front porch
  if (vLine >= verticalPixels)
    return;

  // pre-load pointer for speed
  const register byte * linePtr = &screen_font [ (vLine >> 1) & 0x07 ] [0];
  register char * messagePtr =  & (message [messageLine] [0] );

  // how many pixels to send
  register byte i = horizontalBytes;

  // turn transmitter on
  UCSR0B = bit (TXEN0);  // transmit enable (starts transmitting white)

  // blit pixel data to screen
  while (i--)
    UDR0 = pgm_read_byte (linePtr + (* messagePtr++));

  // wait till done
  while (!(UCSR0A & bit(TXC0)))
    {}

  // disable transmit
  UCSR0B = 0;   // drop back to black

  // finished this line
  vLine++;

  // every 16 pixels it is time to move to a new line in our text
  //  (because we double up the characters vertically)
  if ((vLine & 0xF) == 0)
    messageLine++;

  }  // end of doOneScanLine

void loop()
  {
  // sleep to ensure we start up in a predictable way
  sleep_mode ();
  doOneScanLine ();
 }  // end of loop

This is the error message I got:

"Arduino: 1.8.18 (Windows 10), Board: "Arduino Uno"
In file included from C:\Users\minim\AppData\Local\Arduino15\packages\arduino\hardware\avr\1.8.6\cores\arduino/Arduino.h:28:0,

             from sketch\Gammon2.ino.cpp:1:

C:\Users\minim\OneDrive\Documents\Arduino\libraries\VGA_output/screenFont.h:7:28: error: variable 'screen_font' must be const in order to be put into read-only section by means of 'attribute((progmem))'

byte screen_font [8] [256] PROGMEM = {
exit status 1
Error compiling for board Arduino Uno."

  1. Open this file in any text editor:
    C:\Users\minim\OneDrive\Documents\Arduino\libraries\VGA_output/screenFont.h
    
  2. Change line 7 from this:
    byte screen_font [8] [256] PROGMEM = {
    
    to this:
    const byte screen_font [8] [256] PROGMEM = {
    
  3. Save the file.

Now try compiling the sketch again. Hopefully this time it will compile without errors and work as expected.

Eureka.

Compiled it twice to make sure.

Thanks for your help-----now out with the soldering iron and I'll be in my comfort zone.

Best regards.

You are welcome. I'm glad it is working now.

Regards,
Per