Pi Pico Pong on PAL TV

You are going to implement a Pong game with PAL TV output. The idea is to use the Pi Pico's PIO peripheral to generate the composite PAL signals.

Deliverables

You'll need to submit a working pong game that takes advantage of the PIO peripheral to generate the composite PAL signal. You don't need to test it against a physical TV - if it works in the simulation, that's good enough.

In addition, please include your ARM assembly implementation of gcd() and square() - see class 3 homework for details.

You can choose whether to write your solution in C++ (i.e. using the Arduino Core for the Pi Pico) or in MicroPython. From my experience, MicroPython can be a challenging as you need to quickly feed the data into the PIO peripheral. 

The pong game can be very basic. Two paddles, one ball, bouncing and moving the paddle with the buttons is enough. But feel free to add fancy features such as sound, 2-player support, scoring, etc. You can find some inspiration here.

Please create a Hackaday project page for your submission and add the "PI-PONG" tag so we can easily find it.

If you have any questions, you're invited to ask on the #rp2040js discord channel.

Simulation Template

You can use this simulation template for your project. The project pinout is as follows:

Pi Pico Pin

Description

0

UART TX - for debugging with the Serial Monitor

2

PAL SYNC

3

PAL DATA

4

Player 1 - Up button

5

Player 1 - Down button

6

Player 2 - Up button

7

Player 2 - Down button

Notes:

  1. Player 2 is optional. You can also let the computer control the second player.
  2. The buttons have keyboard shortcuts: w/s for player 1 up/down buttons, and up arrow/down arrow for player 2 up/down buttons.

Composite PAL Signal

PAL works at 25 frames per second. However, each frame is split into two parts, called "fields". The first field contains the odd lines, and the second field contains the even lines. This is called interlacing. Each frame takes 40ms (so you get 25 frames/second), and each field takes 20ms (half that time).

Your code would need to generate and send these frames to the TV. I strongly recommend to prototype the code in the simulator. Testing on a physical TV is totally optional, but if you are interested, I included the instructions below.

To generate the frames, you'll need to drive two signals: SYNC and DATA. In the simulation, the DATA signal is connected to the IN pin of the wokwi-tv element.

The SYNC signal indicates the start of a new frame or the start of a new line, depending on the length of the signal. This is how the TV knows when to move to the next line, or draw the next frame.

The DATA signal indicates the color of the current pixel. Drive it high for white, low for black. The faster you drive the DATA signal, the better horizontal resolution that you get. For instance, if you change the DATA signal 2 million times per second (2MHz), you get about 128 pixels/line resolution (each line is 64us, so 500ns per pixel gives at 128 pixels/line).

Sync Signals

Note: You can skip to the next section if you just want to get it to work in the simulator.

In general, there are three types of synchronization signals:

Short Vertical 

SYNC goes low for 2us, then high for 30us

Long Vertical 

SYNC goes low for 30us, then high for 2us

Horizontal

SYNC goes low for 4us, then high for 60us (rest of the scanline)

Whenever the SYNC goes low, the DATA signal also has to be kept low (see below, "Composite signal", to know why).

Each field starts with a sequence of several Short Vertical and Long Vertical sync pulses. The amount of the Vertical Short pulses varies between even and odd fields:

Odd field

6 Short Vertical pulses, then 5 Long Vertical pulses, then 5 Short Vertical pulses

Even field

5 Short Vertical pulses, then 5 Long Vertical pulses, then 4 Short Vertical pulses

After sending the sequence of vertical synchronization sequence, you need to send 305 horizontal synchronization pulses, one per scan line. Then rinse and repeat for the next field.

To summarize, each frame, consisting of two fields, requires you to send the following sequence of pulses on the SYNC line:

Field 1 (odd)

6 Short Vertical pulses

5 Long Vertical pulses

5 Short Vertical pulses

305 Horizontal pulses

Field 2 (even)

5 Short Vertical pulses

5 Long Vertical pulses

4 Short Vertical pulses

305 Horizontal pulses

You can find a good summary of the PAL standard here.

Feeding the Pixels

Once you have the SYNC signal ready, you can finally feed the data into your TV! Send the data serially, one bit at a time, using the DATA pin. Low for black, high for white.

There are a few caveats though:

  1. You have to make sure DATA is low during the horizontal/vertical synchronization pulses. Otherwise, it will mess up with those pulses.
  2. Each line starts with a 4us horizontal synchronization pulse. You have to wait an additional 8us before starting to send the DATA. These 8us are called the "back porch" and are normally used for color data. Since we don't have any color data, we'll just keep the DATA line low during this period.

To summarize: After each 4us horizontal sync pulse, wait 8us, and then send the bit stream for the line for the next 52us. The faster you go, the higher horizontal resolution that you get. Then drive data low before the horizontal sync pulse for the next scanline.

Simulator Behavior - Make your life easier

The simulator is pretty lax about the signals. You can send just a single Long Vertical pulse before each field, followed by the 305 Horizontal pulses, and it'll still work fine. In other words:

  1. Drive SYNC low for 30us, high for 2us (Long Vertical sync)
  2. Repeat 305 times: drive SYNC low for 4us, high for 60us (Horizontal sync)

Then repeat for each field, that is 50 times per second. If you don't want to worry about the interlacing, you can simply reduce your resolution to 305 lines, and just send every frame twice. It will halve the vertical resolution, but it'll still be good enough for Pong!

How to approach this?

I prepared a guide that breaks down the project for you into smaller steps.

In general, the idea is to use 2 PIO machines: 

A sync-pulse machine that will generate sync pulses on the SYNC pin, and a data-feeding machine that will read the pixel data from the RX FIFO (using the PULL command) and feed it to the DATA pin. 

You can synchronize the machines using the IRQ instruction: the sync-pulse machine will generate an IRQ after every horizontal sync pulse. The data-feeding machine will use the WAIT IRQ instruction to wait for this IRQ signal, and then start feeding the pixels for the next line.

Testing on a physical PAL TV

To feed the signal into a composite port of an actual TV, you'd need to convert it to an analog signal first. You'll find all the details you need in the Connecting to a Real TV Guide that I wrote for you. This is totally optional and not a part of the project!