Low Cost NTSC Gaming with ARM Cortex M

This is a spin off from my "Low Cost VGA Terminal Module" project. I'll look into do the design for NTSC Colour "Retro" style Gaming.

Similar projects worth following
This is a spin off from my "Low Cost VGA Terminal Module" project. The low level code will be reworked for the NTSC as there are a lot of timing changes. I'll be implementing audio support.

I want to make a retro style game. I'll focus on the engineering approach and the documenting the process is an important part of the project.

I have recently picked up and repaired a car DVD player with an auxiliary NTSC LCD monitor. I have been looking into the possibility of generating NTSC video. RAM availability, data rate and I/O limited my Low Cost VGA Terminal Module project to monochrome. The lower resolution of NTSC relaxes some of the constraints and a different trade off can be explored.

I have decided to try to reimplement the classic Pacman arcade game. This is a reimplementation as the hardware specs is a limiting factor and not trying to emulate the game.


Here is the hardware specs of Pacman arcade. There are specialize hardware to implement the video and audio functions.

I have picked the STM32F030F4 for this project:

This ARM microcontroller has similar amount memory. I'll be programming in C. It is going to be an interesting project to try to reimplement the specialized hardware in software.

Video Generation

NTSC encodes colour information using phase angle of a 3.57MHz colour carrier. The colour carrier is synchronized to a local crystal during the horizontal retrace. The video signal, Colour Burst and the sync signals are combined into Composite video (aka CVBS). See logs for the electrical characteristics and timing parameters.

The phase angle defines the colour (hue) while amplitude of the AC component defines saturation, and the DC offset sets the brightness level. The more control you have on the phase angles and the amplitudes, the richer the colour palette.

Video data rate in this project will be at 21.477Mbps (6X colour carrier frequency). The bit pattern of 6 sub-pixels at 0, 60, 120, 180, 240, 300 degrees are used to define the on screen colour.

No idea if I am using the Vectorscope picture correctly. I drew on the amplitude and phase angle that the firmware can produce. The green circuit is where it lands on the target of a predefine colour and red is where I missed. This looks better than the 4X colour burst.

Don't expect too much out of this as the video output is like the saturated colours with limited palette of the old video games era. Have you ever notice the colours in Pacman and how similar they are to the colour bars?

STM32F0 DMA doesn't write to GPIO space, so it'll be will be very CPU intensive to hang off a R-2R DAC on the GPIO. On a STM32F103, the dual SPI might be ganged up in master/slave mode to drive a 2-bit DAC.

Vector sum can be used to select the intermediate phase angles and amplitude very much like driving the poles of a stepper. Without amplitude controls, you can only do half-stepping. See here for my colour experiment using PWM.


There are some trade off with the very low price and the lack of RAM in the STM32F030F4 for a frame buffer. The video generation will have to be rendered on the fly similar to the "Text Mode" in my VGA terminal to fit into the RAM footprint.

For example, the classic Pacman game can be implemented by using Tile rendering. Each of the elements in the game are pre-rendered bitmap tiles that are indexed and referenced by a 2D array (Tile map).

The NPC and the Player character are Sprites are simple tiles that are "drawn" over the background. Simple sprite animation can be achieved by sequencing the tiles. By drawing them over the background in sequence, I can create a layering effect.

Using the tile map makes it easy to detect the interactions between spites and the background. e.g. A sprite is blocked by the background or picks up objects from the background, or sprites can run into another.

Audio Generation

There are a few ways of generating audio.

  • play back of digitized/compressed audio
  • generate audio using DDS and a wave table. (See here for HaD article by Elliot Williams)

I am going to be looking at DDS synthesis.

The PWM duty cycle of a hardware timer is used as a crude DAC for this play back. It is updated on the horizontal refresh rate of 15.75kHz. A low pass filter is used to filter the digital output into an analog voltage and remove high frequency contents.

Multiple voice channels can...

Read more »

  • 1 × Crystal, 14.318MHz 50ppm
  • 1 × STM32F030F4P6 Microprocessors, Microcontrollers, ARM 16kB FLASH/4kB RAM

  • Reverse engineering Audio - part 1

    K.C. Lee05/14/2016 at 17:28 0 comments

    I am trying to reverse engineer part of the pacman audio algorithm. I use a PIC18 assembly code as a starting point. Unfortunately, I have not found a C implementation or one that doesn't uses digitized samples. So I guess I have to write one up from scratch. I am new to the audio/music, so I'll need to learn this.

    Legal stuff


    I am doing this part for educational/verification purpose and once the algorithm is completed, I'll need to find a different set of waveforms, songs and effects due to copyright reasons.

    There is a waveform table that consists of eight 4-bit waveforms with 32 entries. These are used by the DDS to generate audio output. I imported the table into excel, converted into decimal and plot out the 8 series.

    I have decided to recreated them from scratch to better utilize the 0-60 resolution I have available to improve the SNR. A few of them are recognizable and I work out their mathematical equation and regenerate them. Some I have to work out from their shapes and try to duplicate how they stitch bits of waveforms together or modulate the envelope.

    This is what I got so far with the left side the original wave table and the right side mine. There won't be any improvement in SNR with the triangular waves, but the sine waves would have less artefacts.

    I still have to work out the remaining two. I draw in the red lines trying to make sense out of the data points. This one seems to have no structure and was randomly drawn. Probably nothing I could do except scaling the data points.

    I can probably work out the various sine waves that this one is spliced together and replace them with higher quality ones.

  • Colour experiments - Mixing Shades

    K.C. Lee05/11/2016 at 03:06 0 comments

    Even though I do not have a DAC on the video circuit, I can still produce different shades of the same colour by varying duty cycle on the sub-carrier waveform.

    The following are the waveforms for the different shades of the same colour. The first subpixel position sets the colour.

    I can do some very limited mixing of the colours by widening or narrowing the waveform which produces some intermediate phase shifts.

    On my TV, I forced monochrome progressive scan by messing with the colour burst code to show what it see in the area with 4 pixels area. It has 2 solid grey bars and some of the pixels are round!

    In the 2 pixels wide area:

    They kind of reminds me of half toning.

  • PCB layout+Build

    K.C. Lee05/11/2016 at 00:48 3 comments

    nce the circuit now works for my DVD monitor, the hardware is in good enough shape for me to order a set of purple PCB. I'll revisit the colour issues with my TV and analog monitor at a later point.

    I did some additional work on improving the quality of the Colour Burst signal after sending the board to OSH Park, but that didn't solve the lack of colour issues with my TV and analog monitor. There is something more fundamental that I am missing.

    Stuck at Canada Custom for last couple of days. It took 11 days same as last time.

    I have switched to a regulator with a large input range: 5V-12V. This let me power this module with same power source as the DVD monitor. This old regulator is known to have stability issues with low ESR ceramic cap, so I have increased ESR by adding R2.

    The audio part of the circuit is not tested, so the values R8 and C9 might be tweaked.

    PCB all soldered up. It is working with the DVD monitor, but still got the same no colours issue with my TV. I can't blame it on the breadboard nor the crystal.
    The white connector is SWD. I put in extra grounds to minimize crosstalk - return paths for critical signals. The ground signal can also act as shields against capacitive coupling between adjacent rows of contacts in the breadboard.

    Remember that I said the regulator 1117-3.3 part has stability issues with 10uF ceramic output capacitor because its ESR is too low (series R2 = 0R.) It oscillates! I did this test before the rest of the board was populated.

    Here is what happens when I used R2 = 1R in series with the 10uF output capacitor to makes it looks like the ESR of the capacitor of that era.

    Don't mix old regulator with new MLCC without reading the datasheet.

  • Accidental Progressive Mode!

    K.C. Lee05/09/2016 at 22:18 1 comment

    Still messing around the Vsync code. There is a bug in my code that made something interesting on my TV - super high res monochrome progressive scan mode. On the DVD monitor, it shows up as colour bars but without the dot crawls associated with an interlaced display.

    The TV can't sync to the Colour Burst, so the subpixels that are used to construct the sub-carrier signal shows up as individual pixels. They are running at 6X sub carrier frequency. They are used to simulate the sub carrier frequency with discrete phase shift to make colour.

    I can clearly see groupings of regular groupings of 2 and 4 between colour bars/White and adjacent colour bars. The reason why the checker box pattern is that there is 180 degrees phase shift between the scan lines.

    My (very) Odd Vsync:

    and my equally messed up Even Vsync:

    I guess I have more debugging to do.

    This is what the odd Vsync looks like now:

    This is what the even Vsync looks like now:

    I think they are correct.

    Still no luck with the colour bars on the TV. I did see a single frame flashed by the screen when I was testing one timing parameter, but I cannot reproduce it. When I mess around flipping the polarity of the colour burst at the start of field 2, the TV went into the progressive mode.

    I am out of idea for now. Might have to move on and revisit this.

  • Colour Burst Revisited

    K.C. Lee05/06/2016 at 03:17 2 comments

    I played with LTSpice a bit more. I have tweaked the buffer circuit to make a better Colour Burst.

    It is the same design as before, but with R7 added. V4 (from GPIO) clamps the video output and change the DC offset so that the fake Colour Burst is more symmetrical about the porch. This is about the limit of what a simple circuit can do. I can play around with the width of V4 and overall timing.

    The DVD monitor no longer recognize the colour burst for some reason and the TV thinks it is a PAL. Yes, I actually make it worse.

    I rearranged part of the IRQ code and all of a sudden there is no longer interference for IRQ for starting the DMA. Things can get quite weird when I try to do too much in an IRQ that blocks the other events.

    These are my Vsync signals for the odd/even fields. I'll need to sit down and verify the sequences to figure out where the problems are. The Vsync sequence timing is controlled by a table of constants that drives the timer compare GPIO, so that makes tweaking easy.

    I use the DVD player as a NTSC reference. On my storage scope, I saved the waveform (top trace), then I looked at the signal generated by my circuit. On some of my scan lines, the Colour Burst are offset by 90 degrees! That's the reason why my TV thinks it is PAL as each scan line is offset by 90 degrees.

    This is when the timing alignment should look like.

    I guess IRQ to DMA trigger jitters at play here. I thought this was solved in my VGA code that lines up DMA from IRQ exactly to a single pixel. The sync pulses come from timer compare, so they are exact.

    Note: The DVD player is unterminated, so the amplitude is about twice as high.

    I rework on the quick & dirty colour bar code to get a solid pattern.

    The DVD monitor shows colour bar in between flickering, blanking and trying to sync up. My TV and analog monitor shows B&W only. The colour at the top is a bit messed up. This is likely timing related. The Vsync is certainly off.

    The interesting part is that the DVD monitor becomes stable when I added a 33pF cap to one of the oscillator pin. The colour at the top improved when I added both caps. I measured the two capacitors from the graphic card and they are the same values as I am using. The graphic card crystal is a bit fast and needed extra load capacitance to work.

    When I swapped the crystal from another one (TXC 9B series +/-30ppm) pulled from a motherboard, the DVD monitor shows the colour bars without needing the extra caps.

    Neither of my TV nor the analog monitor shows any colours. It is a 480x236 (?) LCD, you can really see those pixels.

    Here are what these colours related to their phase angles. White is DC.

    My attempt to generate Vsync from DMA is a failure. Not going to put in more efforts into that for a while.

    Have to compare waveforms on the DVD monitor. I got to learn how to use the waveform storage on my regular scope as it has video triggering and longer samples. Vsync code now uses DMA to update new Timer Compare values.

    I did some more reading on Colour Burst. So far, I have only seen people using a DAC to generate the signal. I looked into the Colour Burst circuit on Apple II schematic page 3. They used gate S02 to gate the colour burst signal which is fed into LC filter C32/L3 which is then mixed into the composite video.

    Q1 & Q2 are two voltage followers with PNP followed by NPN. The VBE sort of cancel themselves out. I had used this trick before a long time ago on video circuits to avoid AC coupling cap.

    I thought they must know something I don't as they cut all kinds of corners back in the days. I played around a bit in LTSpice and came up with my own version. They intentionally detuned the filter to cause a phase shift. I used their original values in my simulation. Blue trace shows the input and green is after the filter. The waveform is distorted and that's how they introduce a leading phase shift.

    V3: Hsync pulse, V2: video, V4 colour burst. The LC filter seems to work quite well as it removed some of the upper harmonics and produce...

    Read more »

  • Vsync generation

    K.C. Lee05/02/2016 at 19:01 0 comments

    Because of the analog nature of the video signal and the field 2 scan line 263.5 split, the NTSC Vsync generation is very messy. Here are the different types of signals to be generated.

    After trying a few different approaches, this is what I came up with:

    What I have done is to build a generic routine that takes a table of the entire sequence of vsync edges for the timer compare function. Unfortunately, the STM32F030 part I am doesn't have DMA channel allocated for this, so I had to implement it in the IRQ.

    const uint16_t VSYNC_ODD[ ] = 

    So looks like I am not entirely successful on the scan line before the Vsync. I'll also need to do something on line 263.5.

    Waveform still wrong, but my TV recognize it as NTSC, but still no colours. I hope it is not the colour burst. The DVD monitor has colours, but no sync.

  • First video output

    K.C. Lee05/01/2016 at 05:19 1 comment

    The monitor is having a lot of issues when the non-standard Colour Burst are present. I turned it off and the fake interlace stuff as well.

    I got a few B & W bars by filling the scan line buffer with fixed 0x55.

    That means that the firmware seems to send out the fake progressive vertical sync pulse, horizontal sync and the DMA is blasting out the video from the scan line.

    I'll need to look if the Colour Burst level is well too high is the cause of the screen issue and find a way to correct that. Just have to take it a step at a time.

    My "LED" TV is having vertical sync issues with either of the fake Vsync. It doesn't like the colour burst and messes up with its AGC. It also think it is seeing PAL.

    Here is the video signal that my scope see when the TV is connected. It looks exactly like my 75 ohms dummy load I used for testing.

    Here is the same set up with RCA monitor with either power on or off. There is something weird at the input.

    I think I am better off following the NTSC specs and buffering the video signal.

    The video signal I generate has a lot of high frequency components that the DVD monitor wasn't expected to see. At very high frequency, the parasitics inside is no longer matching impedance and a lot of the reflections come back.

    I modified the buffer circuit I built for the VGA module prototype.

    The video signal is V2, sync is V3. They are level shifted up by R5 to 0.73V (Blue trace) to compensate for the VBE drop of Q1. Q1 is an emitter follower (buffer) and its output is the green trace.

    C1 acts as a low pass filter. The BW is around 20MHz which is still way beyond need for NTSC video. I might tweak the values later.

    Here is much improved signal looks like at the source side with the DVD monitor.

    The new buffer circuit is built on a small perf board.

    In retrospect, I could have lower the slew rate of the I/O on the ARM to 10MHz or even to 1MHz.

    I saw some dot clawing on the DVD monitor when I use the fake interlace sync. Colour Burst still messes up video. I am going to look at that after I have implemented a proper Vsync.

    I have an idea of how to reduce the level of Colour Burst

    V4 is a GPIO pin. When the pin is cleared, it clamps the input to the emitter follower by 2 diode drops. The Colour Burst amplitude is now 0.317V which is pretty close to the 40 IRE in the spec.

    The Clamp signal is driven to low once DMA started and it polls (about 3us) until the TIM3 reaches a preset count and set again in the IRQ. I have to reserve a PWM channel for audio, so I have to do it this way.

    GPIOA->BSRR = PIN_CLR(PA5);			
      /* busy wait for 3us */ ;
    Here is what the ARM output looks like.

    This is what the composite video looks like.

    It is enough to fool the DVD monitor to have some colours. My TV still thinks it is PAL, but at least the AGC doesn't fade the entire screen to black. My old CRT monitor doesn't seem to have an issue with the sync pulses, but there isn't colours.

    I still need to clean up the code as there are something funny that make it loses sync once in a while. I have yet to implement a proper Vsync with the fancy waveforms in the specs.

    Lessons for the day:

    • Short cuts sometimes lead to pitfalls.
    • Go to real specs and don't copy/paste from unknown sources.

  • Colour burst generation

    K.C. Lee04/29/2016 at 16:55 0 comments

    Maxim Integrated: TUTORIAL 734 Video Basics has a nice timing diagram:

    Going to shift the Colour Burst towards the right by 200ns: (new)

    The Colour Burst data (MSB first)

    Since each line is 227.5 Colour Burst cycles, the clock phase is inverter for every line. The firmware has to replace these values after each line is done. It can be done as two 32-bit memory copy which doesn't take long for an ARM chip or I can use a different sequence for each of ping-pong buffers.

    • follow by 30 bits of padding i.e. 5 carrier cycles to video.
    • Video - First byte is now aligned to 0 or 180 degrees of the carrier. This is the reason for the timing adjustment.

    I have capture the Csync (Composite Sync) and the Video signal coming out of the ARM chip.

    ☑ The Colour timing are correct.

    ☒ Following what rossub did in RBox: A diy 32 bit game console for the price of a latte, I also generate Colour Burst as if it were part of the video. As a result, the Colour Burst level isn't 40 IRE per RS170A spec. This causes AGC issues in my LCD monitors! (See Colour Burst Revisited for a fix)

    The following is the composite video signal measured on a scope.

    ☑ The voltage level for video is 1V.

    This is what the breadboard looks like now. Cable on left is the SWD debugger connection, cable on right is for Logic Analyzer and cable to the botton is composite video output.

  • NTSC Timing Parameters

    K.C. Lee04/28/2016 at 03:24 0 comments

      This is a work sheet for the NTSC timing that I am deriving the programming parameter from.

      Maxim Integrated: TUTORIAL 734 Video Basics has a nice timing diagram:

      Horizontal Timing

      I have rearranged the timing such that a scan line starts at the falling edge of the sync. This is the only common point to all the scan lines. The last column is what I'll be programming into hardware timer to generate pulses, IRQ for starting DMA etc. I rounded off the clock cycles.

      Vertical Timing

      There is a lot of info to digest. The receiver uses an integrator to derive the vertically sync. In doing so, they introduces quite a bit of a mess that I have to implement in firmware.

      From TI LMH1980 "Auto-Detecting SD/HD/PC Video Sync Separator" datasheet:


      Here are the parameters:


      Field 1 : Lines 21 - 263.5
      Field 2 : Lines 283.5 - 525

      Right in the middle of line 263 is where the split between even and odd. Lines 1-263.5 are in the even field and lines 263.5-525 are in the odd field.

      ☑ regular Horizontal sync

      This is actually the easy part, but still took a few tries.
      Basically crystal, PLL, Hsync parameter and resistor values are correctly.

      These doesn't work on my LCD monitors!!!

      Found something interesting on Sagar's blog: NTSC demystified - Cheats - Part 6

      1. "fake" progressive scan instead of interlaced scan. Write 262 lines per frame. This makes it much easier to get a stable image on the TV. Only even lines are written / drawn. The odd lines in between remain as black lines. This technique has been used in NES, SNES, uzebox and a number of older game consoles. [1]
      2. "simple v-sync". The NTSC spec states that half-scanlines should be used for v-sync. This includes 3 kinds of pulses pre-equalization, serration and post-equalization. All of these are unnecessary. One "long-sync" is sufficient to perform V-Sync. Of 262 lines in a frame, 261 lines have "short sync" lasting 4.7uS, one line (usually line number 248) has a "long sync" lasting about 58.8555uS. [2]

      Note: The references [1] & [2] are broken.

      From NTSC demystified - Color palette demo ith simplified progressive and interlace scanning - Part 7

  • STM32F030F4 Prototype for breadboard

    K.C. Lee04/27/2016 at 22:08 0 comments

    I soldered up a STM32F030F4 modules for this project. (It is part of a batch I build today.)

    I might do a proper PCB as the project becomes a bit more mature. I put on a 14.318MHz crystal from a dead PCI graphic card.

    I leave out the header pin 2 & 3 as they are the oscillator pins - analog circuits where the breadboard parasitic may interferes with the timing. NTSC monitors is picky on the Colour Burst frequency tolerances for decoding colours, so this is not a job for internal RC oscillator.

    This is the schematic: I am starting with a clean slate as the requirements are a bit different than my VGA project.

    SWD debugger sees it!

    Here are my parts:

    In my case, this can be built for about $1.

View all 11 project logs

Enjoy this project?



Freire wrote 06/13/2017 at 12:29 point

Some update and where download the source code?

  Are you sure? yes | no

K.C. Lee wrote 06/13/2017 at 13:52 point

I don't release until project meets some milestones.  As far as I am concern the code hasn't reach a usable state.  What you see is what you get.

Software is always bane of my projects. My software skill is below what I need.

There are no updates because project isn't going anywhere with the current approach and/or lack of general interest/motivation/time etc.  That's life for working on unpaid hobbies.

  Are you sure? yes | no

danjovic wrote 05/05/2016 at 13:47 point

I did once a video generation with a cortex M0 from NXP. The sync signals used some primitives that combined (inline) generated the serration as the NTSC standard requires. It took me some time though to learn that timing loop routines should be ran from RAM otherwise the machine clocks did not correspond to crystal clocks due to wait states introduced when reading (running) code from flash. This is the directive used to make code run from RAM.

void delay_loop(int vezes) __attribute__ ((__section__(".data.ramfunc")));

Here's a video with the code in action performing overlay over a video signal

  Are you sure? yes | no

K.C. Lee wrote 05/05/2016 at 14:06 point

That's neat!

I am trying to generate the sync pulses with hardware timer compare, so the timing should be deterministic even when running from FLASH.  Still trying to clean up the code structure as there are 3 IRQ from the same timer and same vector (due to hardware limitations) that can potentially interfere with each other.

  Are you sure? yes | no

danjovic wrote 05/05/2016 at 20:01 point

I've suffered with the same constraint - few RAM available , so to save space I've ended up basically with the delay routine in RAM. I don't remember if I've posted the code somewhere (though probably did it), but if you're interested I can send it to you to take a look.

  Are you sure? yes | no

K.C. Lee wrote 05/05/2016 at 22:18 point

That would be great.  Thank you.

  Are you sure? yes | no

danjovic wrote 05/05/2016 at 23:53 point

Here you are the link for the code. Haven't located the OSD code though, as this is a project from 3 years ago.
The SysTick runs at the same period as the horizontal line. In OSD code you use the hsync signal to trigger the video routine and the Vertical Sync to reset the line counter. Simple as that.
Due to the limitation on RAM available I've created 3 drawing areas being one text area at the top of the screen, another at the bottom and a 128x128 area in a square shape on the center of the screen.

  Are you sure? yes | no

esot.eric wrote 05/05/2016 at 06:57 point

Haven't read through it all, yet, but it looks like you're doing an awesome job documenting/explaining NTSC. I'mma keep this page as a resource, for sure! Thanks, yo!

  Are you sure? yes | no

Tom Van den Bon wrote 04/26/2016 at 20:38 point

nice! Love your work. Looking forward to this :)

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates