ATtiny85 VGA Sync

May 3, 2020 at 8:52 pm (computers, maker, Uncategorized) (, )

I’ve been reading Ben Eater’s inspiring series on producing a VGA video output from breadboards containing basic logic gates implementing counters, so wanted to see for myself how to create the timing signals for a VGA output.  There are loads of tutorials for how to produce VGA using an Arduino, with the main source appearing to be from Nick Gammon.  So for the sake of variety, I decided to see how it might work on an ATtiny85 (although naturally, many have also been there before too).

I’ve only implemented the sync signals to see if a monitor would recognise my signal.  I might see if I can get some data out at some point at some relatively low-level resolution.

The key decision is all centred around what clock frequency the ATtiny can support and how that relates to VGA video timings.  After a bit of messing around with a calculator, I settled on the following:

VESA Signal 640×480 @ 75 Hz and used the 64 MHz PLL clock mode of the ATtiny85 using a /8 prescaler – so a 8MHz clock, with each “tick” being 0.125uS.

So taking the timing values from the above linked tinyvga site, and translating that to “ticks” gives:

Scanline Pixels Time (uS) ‘Ticks’ @ 8Mhz
Visible Area 640 20.31746031746 16.546
Front Porch 16 0.50793650793651 4.063
Sync Pulse 64 2.031746031746 16.254
Back Porch 120 3.8095238095238 30.478
Whole Line 840 26.666666666667 213.333

Naturally there will have to be some rounding – I won’t be able to get fractions of a “tick” out of the ATtiny85 Timers.

This all rounds up to using the ATtiny85 Timer1 in PWM mode providing an output on OC1A to provide the H_sync pulse, then manually counting lines in the overflow interrupt routine to manually drive the V_sync pulse.

I originally wanted to use OC1B which made the pin assignments on the ATtiny85 much simpler, but ended losing an afternoon attempting to work out why I was getting no signal on OC1B. Turns out there is a bug in the ATtiny85 Compare on B for Timer1 – the compare mode bits in GTCCR are ignored unless the same bits are set for Compare on A in TCCR1.  Having found reference to this on the Internet, this kind of sounds familiar so I’m wondering if I’ve fallen foul of this before! Anyway, OC1A it is.

Pin assignments are as follows:

  • H_sync = PB1 (pin 6) OC1A
  • V_sync = PB4 (pin 3)

And the Timer1 settings are as follows:

  // Run a timer interrupt off a 64 MHz clock
  PLLCSR |= (1<<PLLE);            // Enable 64 MHz PLL clock
  while (!(PLLCSR & (1<<PLOCK))); // Wait for the PLL lock
  PLLCSR |= (1<<PCKE);            // Enable PLL as source for Timer 1

  // Timer 1 for CTC mode; set on compare with OCR1A; overflow interrupt
  TCCR1 = (1<<CTC1) |                // Reset on match with OCR1C
          (1<<CS12) |                // Prescale /8
          (1<<PWM1A) |               // PWM mode
          (1<<COM1A1) | (1<<COM1A0); // Set OC1A on compare; clear on rest
  GTCCR = 0;
  OCR1A = 15;
  OCR1C = 218;

There was some experimentation required to adjust the timings from the theoretical 213 ticks – 218 seems to give the most accurate horizontal sync frequency for some reason.  I haven’t done the maths to see why.  I was assuming I’d use 213 with a possible “leap tick” required every now and again to account for the lost 0.333 ticks to keep things in check, but turns out this worked ok for me instead.

Note that VESA 640×480@75Hz requires negative pulses, hence using “Set on Compare” rather than “Clear on Compare”.  Some modes require positive pulses so this would have to change.

The rest is just counting lines for the vertical refresh (using the line timings from tinyvga: 480-1-3-16=500), which is all done using the Timer 1 overflow interrupt, which signals the end (or in my case, start) of each horizontal line.

The final code is shown below, and sure enough, hooking this up to a monitor does indeed bring up the required mode to be recognised.

Next up, I’ll have a think about sending some data over.  Although if I want RGB outputs, that basically uses up the rest of the ATtiny85 I/O pins. That is an experiment for another day.

Kevin

// Use a ATtiny85 to output a VGA signal using
// its 5 output pins
//
//    (RST) ---------- | 1   8 | --------- (Vcc)
//    V_sync -- PB3 -- | 2   7 | -- PB2 -- R (0-0.7v)
//(0-0.7v) G -- PB4 -- | 3   6 | -- PB1 -- H_sync
//    (GND) ---------- | 4   5 | -- PB0 -- B (0-0.7v)
//
// NOTE: Wanted to use OC1B but there seems to be a bug in many ATtiny85 that
//       means that OC1B doesn't work properly unless OC1A has the same mode.
//       Consequently, using OC1A on its own to serve the purpose required.
//
// Note: Use ATtiny85 PORTB numbers
#define H_SYNC 1  // OC1A
#define V_SYNC 3
#define VGA_R  2
#define VGA_G  4
#define VGA_B  0

// Basic operating principle:
//  Timer configured to automatically create the H_sync pulses by using Timer 1
//  compare values.  H_sync needs to drop negative for a short pulse at the end
//  of each line.
//
//    <----- H pixel display ------><HFP><H_sync><HBP>
//
//  Or alternatively:
//    <H_sync><HBP><----- H pixel display -----><HFP>
//
//  Use Timer 1 to signal frequency of H_sync based on OCR1A.
//  Use Timer 1 OCR1A to specify duration of H_sync.
//  So attach H_sync signal to OC1A output.
//  Configure OC1A output as Clear or Set on Match as required
//
//    V_sync pulses are created by counting horizontal lines.
//    V scanning will be in one of the following modes:
//         In Display Area - hence H-scan is drawing pixels
//         In V front porch or back porch areas
//         In V_sync area
//
// VGA Timing Modes are taken from: http://www.tinyvga.com/vga-timing
//
// Based on the option of a 64 Mhz timing signal, choosing VESA 640x480 @ 75Hz
// gives us the following timing parameters
//
//     Screen refresh rate = 75 Hz
//     Verticle refresh    = 37.5 kHz
//     Pixel frequency     = 31.5 MHz
//
//     Timings are as follows
//      H visible area        640     20.31746031746 uS
//      H front porch          16      0.50793650793651 uS
//      H sync pulse           64      2.031746031746 uS
//      H back porch          120      3.8095238095238 uS
//      Total H line          840     26.666666666667 uS
//
//      H pulse is negative.
//
//      V visible area        480     12.8 mS
//      V front porch           1     0.026666666666667 mS
//      V sync pulse            3     0.08 mS
//      V back porch           16     0.426666666666667 mS
//      Total V frame         500     13.333333333333 mS
//
//      V pulse is negative
//
//  Using 64MHz PLL Clock as source for Timer 1 and setting Prescale to /8 gives
//  a frequency of 8 MHz and a single "tick" of 0.125 uS.
//
//  Total horizontal line timing is thus 26.666666666667/0.125 = 213.333 "ticks"
//  use 212 for OCR1C to define the line duration (same as the Timer 1 period).
//
//  At this resolution, H_sync is 2.031746031746/0.125 = 16.254 "ticks"
//  so use 15 for OCR1B to define the H_sync pulse.
//
//  As the required pulses are negative, need to use Set on Compare Match.

int V_Sync_Line;

void setup() {
  // Configure Output I/O pins
  PORTB = 0;
  DDRB = (1<<H_SYNC) | (1<<V_SYNC);

  // Run a timer interrupt off a 64 MHz clock
  PLLCSR |= (1<<PLLE);            // Enable 64 MHz PLL clock
  while (!(PLLCSR & (1<<PLOCK))); // Wait for the PLL lock
  PLLCSR |= (1<<PCKE);            // Enable PLL as source for Timer 1

  // Timer 1 for CTC mode and set on compare with OCR1A and overflow interrupt
  TCCR1 = (1<<CTC1) |                // Reset on match with OCR1C
          (1<<CS12) |                // Prescale /8
          (1<<PWM1A) |               // PWM mode
          (1<<COM1A1) | (1<<COM1A0); // Set OC1A on compare; clear on rest
  GTCCR = 0;
  OCR1A = 15;
  OCR1C = 218;  // Experimentation shows this is the best value

  // Enable Overflow Interrupt
  TIMSK = (1<<TOIE1);

  V_Sync_Line = 0;
}

void loop() {
  // put your main code here, to run repeatedly:
}

// Timer 1 Match with OCR1A interrupt
ISR (TIMER1_OVF_vect) {
   // Triggered every H_Sync line
   //
   // It automatically outputs the H_Sync pulse, so need to complete the line:
   // <---- H line Pixel output ---->
   //
   // From timings,  is approx 5.8 uS i.e. approx 23 ticks
   // So read the timer register and ensure it is > 23
   //
   while (TCNT1 < 24) {};

   if (V_Sync_Line <= 479) {
      // Visible line output
      // todo next!
    } else if (V_Sync_Line == 480) {
      // V Front Porch
    } else if ((V_Sync_Line >= 481) && (V_Sync_Line <= 483)) {
      // V Sync Pulse
     PORTB &= ~(1<<V_SYNC);
   } else { // 484 onwards
      // V Back Porch
     PORTB |= (1<<V_SYNC);
   }

   V_Sync_Line++;
   if (V_Sync_Line >= 500) {
     V_Sync_Line = 0;
   }
}

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: