Close

How to approach the project

A project log for Pi Pico PAL TV Pong

Final project of the Raspberry Pi Pico and RP2040 Deep Dive course

Uri ShakedUri Shaked 06/25/2021 at 18:100 Comments

The Pi Pong project is not easy, and can even be overwhelming. It takes time and practice to wrap your head around the programming model of the Pi Pico's PIO, and it could be difficult to swallow this project in a single bite.

You can find here a step-by-step breakdown, which can guide you through the implementation of the project. It's totally optional - use it if you want, or just go on your own.

Step 1 - White Screen

Fill the screen with white color. At minimum, you'll need to drive the DATA signal high, and generate at least one Long Vertical pulse, followed by a series of Horizontal sync pulses.

And in pseudo code:

SYNC ← HIGH
Repeat forever:
  SYNC ← LOW
  wait 30us
  SYNC ← HIGH
  wait 2us
  repeat 300 (or a similar number) times:
    SYNC ← LOW
    wait 4us
    SYNC ← HIGH
    wait 60us

I suggest coding the SYNC signal driving logic into a PIO machine, so you get precise timing. 

Step 2 - Half white

It's time to take control of the DATA line too! Make it go HIGH about 6 microseconds after the end of the Horizontal Sync signal, then LOW again after ~22 microseconds.

You can start with controlling both DATA and SYNC using a single PIO machine, and then move on to controlling them with two distinct machines that synchronize using the IRQ and WAIT instructions.

Pseudo code:

DATA ← LOW
SYNC ← HIGH
Repeat forever:
  SYNC ← LOW
  wait 30us
  SYNC ← HIGH
  wait 2us
  repeat 300 (or a similar number) times:
    SYNC ← LOW
    wait 4us
    SYNC ← HIGH
    wait 10us
    DATA ← HIGH
    wait 22us 
    DATA ← LOW
    wait 28us

Step 3 - Frame buffer

Instead of hard-coding the data line to stay HIGH for the first half of each line, we'll let the program feed the data. I suggest starting with 32 bits (pixels) per line (not so great resolution, but still usable), then going up to 128 or even 256 bits.

The idea is to have two PIO machines: one will drive the SYNC signal, and the second one will pull the data from the program one 32-bit word at a time, and then drive it to the DATA signal. Using the "Autopull" PIO setting is highly recommended!

Your program will start both PIO machines, then feed the data to the Data machine. I suggest creating a buffer with the screen contents (frame buffer), and then repeatedly copy data from this buffer into the TX FIFO of the DATA-driving PIO machine. You can feed the data using an interrupt handler (either TXNFULL interrupt/IRQ triggered explicitly by the PIO machine code).

Note: I recommend using C++ (Arduino core) for the game. But if you do use MicroPython, define your interrupt handler as a hardware interrupt (pass "hard=true" when defining the handler). Note that there are some limitations for what you can do inside an interrupt handler: for instance, memory allocation isn't allowed.

Note 2Ideally, you can use the DMA controller to automatically feed data from memory into your Data machine. DMA is currently not supported in the emulator, but this will probably change in a few weeks. Until then, you can try the DMA approach with a physical Pi Pico and PAL TV (or logic analyzer).

Sync machine pseudo code:

SYNC ← HIGH
Repeat forever:
  SYNC ← LOW
  wait 30us
  SYNC ← HIGH
  wait 2us
  repeat 300 (or a similar number) times:
    SYNC ← LOW
    wait 4us
    SYNC ← HIGH
    Trigger IRQ (to notify the Data machine)
    wait 60us

Data machine pseudo code:

Repeat forever:
  DATA ← LOW
  Optionally trigger an IRQ to notify the code we're ready for more data
  Wait for an IRQ from the Sync machine
  PULL 32-bit word from the TX FIFO
  Shift the one bit at a time through the DATA pins

Step 4 (optional) - Perfection

At this stage, the PAL TV driver should be ready for you to use in your code! All you have to do is to write the pixel data to the framebuffer, and the PIO machines will take care of generating the right signal.

However, the SYNC signal is not perfect yet. It's working in the simulator and is good enough for this assignment. But if you want to get it perfect (perhaps you want to try it with a real TV), you are definitely invited to do so! I included here some pseudo code to help with that:

Sync machine pseudo code:

SYNC ← HIGH
Repeat forever:
  If odd frame:
    Repeat shortSync() six times
    Repeat longSync() five times
    Repeat shortSync() five times
  Else (even frame):
    Repeat shortSync() five times
    Repeat longSync() five times
    Repeat shortSync() four times
  Repeat horizontalSync() 305 times

shortSync():
  SYNC ← LOW
  wait 2us
  SYNC ← HIGH
  wait 30us

longSync():
  SYNC ← LOW
  wait 30us
  SYNC ← HIGH
  wait 2us

horizontalSync():
  SYNC ← LOW
  wait 4us
  SYNC ← HIGH
  wait 60us

Note: it can be challenging to pack all this functionality into the 32 instruction space of the PIO machine, especially when you also have the Data machine that should fit into the same space. But it's possible - I managed to encode the Sync machine with just 20 PIO instructions.

Step 5 - Pong

Now, with the framebuffer at your disposal - you are ready to write the actual game. Good luck!

Discussions