TV Pong game using microcontroller

Thread Starter

Sparsh45

Joined Dec 6, 2021
143
@MrChips

I found your project while searching on the forum Your Project link

I found this quite interesting. As I understand you generated video signal by assembling discrete component on breadboard.

I am trying to understand how this would be implement using any microcontroller.

How would you design system if you want to generate video signal from microcontroller?

There can be many ways to write code for this system. I assume that if you have only two options available, one is the state machine and the other is the scheduling algorithm which option will you use for coding it.

I'm assuming in my option that I don't want to use ARM, the only option I have is PIC or AVR

I found on a forum search that a member had generated video signals from a microcontroller and implemented preemptive scheduling. I don't have a link to that thread I'll try to find it.

Edit Look at post #16 in thread
 

MrChips

Joined Oct 2, 2009
25,919
@TheRB, another member of AAC also demonstrated a proof of concept design of generating VGA signals on a Microchip PIC MCU. I think a low end PIC or AVR does not have the processing power to do this.

I have done a similar project with an STM32F ARM MCU.
I did not implement any time state machine, scheduling or RTOS.
This was straight linear sequential processing using timer interrupts.
 

Thread Starter

Sparsh45

Joined Dec 6, 2021
143
@TheRB, another member of AAC also demonstrated a proof of concept design of generating VGA signals on a Microchip PIC MCU.
I don't know which PIC microcontroller he used to generate the video signal. It is clear that your choice is ARM. You don't want AVR or PIC.

I have done a similar project with an STM32F ARM MCU.
I did not implement any time state machine, scheduling or RTOS.
This was straight linear sequential processing using timer interrupts.
Can you tell which is the hard real-time and soft real-time requirement in pong game?

I read on this page description that video games have hard real time timing requirements
https://en.m.wikipedia.org/wiki/Real-time_computing
 

nsaspook

Joined Aug 27, 2009
9,435
https://forum.allaboutcircuits.com/threads/pic18-q43-ntsc-b-w-video-signal-demo.175611/#post-1586729

A demo of a interrupt driven state machine using SRAM -> I/O port DMA to generate B/W NTSC on a PIC18.

https://github.com/nsaspook/vtouch_v2/tree/eadogs_ntsc/q43_board/q43_ntsc.X
Source code example for signal generation:

If this was a game the game logic would be here with soft hard real time timing in the low-priority ISR and main loop.
C:
void main(void)
{
    uint8_t xlines = 0;

    // Initialize the device
    SYSTEM_Initialize();
    TMR4_Stop();
    TMR5_SetInterruptHandler(led_flash);

    // Enable high priority global interrupts
    INTERRUPT_GlobalInterruptHighEnable();

    // Enable low priority global interrupts.
    INTERRUPT_GlobalInterruptLowEnable();

    SPI1CON0bits.EN = 1;
    init_display();
    sprintf(buffer, "%s ", build_version);
    eaDogM_WriteStringAtPos(0, 0, buffer);
    sprintf(buffer, "%s ", build_date);
    eaDogM_WriteStringAtPos(1, 0, buffer);
    sprintf(buffer, "%s ", build_time);
    eaDogM_WriteStringAtPos(2, 0, buffer);
    BLED_SetLow();

    StartTimer(TMR_DIS, 500);

    TMR6_Stop(); // disable software timers to stop scan-line jitter
    ntsc_init();

    while (true) {
        // Add your application code
        BLED_Toggle(); // application code blink LED
        //                scan_line++; // back from idle ISR
        xlines++;
        if (scan_line > 210) {
            scan_line = 5;
        }
        if (xlines > 210) {
            xlines = 5;
            ntsc_font(1, SL_DOTS-1);
            ntsc_font(1, SL_DOTS-1 + 8);
        }
    }
}
The hard real time timing requirements are here with the DMA and DMA high-priority interrupts in the NTSC DMA state machine.
C:
#include "ntsc.h"

volatile uint32_t vcounts = 0;
volatile uint8_t vfcounts = 0, scan_line = 0, vml = SL_V1;
volatile bool ntsc_vid = true, task_hold = true;

volatile enum s_mode_t s_mode;
volatile uint8_t vsyncu[V_BUF_SIZ] = {0}, *vbuf_ptr;

void vcntd(void);
void vcnts(void);
uint8_t reverse_bit8(uint8_t);

/*
* lsb to msb to display chars correctly
*/
uint8_t reverse_bit8(uint8_t x)
{
    x = ((uint8_t) ((x & (uint8_t) 0x55) << 1)) | ((uint8_t) ((x & (uint8_t) 0xAA) >> 1));
    x = ((uint8_t) ((x & (uint8_t) 0x33) << 2)) | ((uint8_t) ((x & (uint8_t) 0xCC) >> 2));
    return(uint8_t) (x << 4) | (uint8_t) (x >> 4);
}

/*
* setup the data formats and hardware for the DMA engine
*/
void ntsc_init(void)
{
    uint16_t count = 0;
    uint8_t char_c = 0, char_n = 0;

    /*
     * Interrupt driven task manager
     * after the H sync pulse there are V syncs with no video
     * main-line code runs for the duration of one timer 4 interrupt period
     * ~200us for testing, then goes back to idle
     * until re-triggered the next H sync cycle
     */
    TMR4_Stop();
    TMR4_SetInterruptHandler(vcntd);

    /*
     * setup system arbiter to share memory with DMA process
     */
    // This function is dependant on the PR1WAY CONFIG bit
    PRLOCK = 0x55;
    PRLOCK = 0xAA;
    PRLOCKbits.PRLOCKED = 0;
    ISRPR = 1;
    DMA5PR = 2;
    MAINPR = 3;
    PRLOCK = 0x55;
    PRLOCK = 0xAA;
    PRLOCKbits.PRLOCKED = 1;

    /*
     * DMA hardware registers data setup
     */
    DMA5_StopTransfer();
    vbuf_ptr = vsync;
    SLRCONB = 0xff; // reduce PORTB slewrate
    DMA5_SetDCNTIInterruptHandler(vcnts);
    DMASELECT = DMA_M;
    DMAnCON0bits.EN = 0;
    DMAnSSA = (volatile uint24_t) vbuf_ptr;
    DMAnSSZ = DMA_B;
    DMAnDSZ = DMAnSSZ;
    DMAnCON0bits.EN = 1;
    TRISB = vml; // video bit , on

    /*
     * setup the static V, H and video patterns for DMA transfer engine to LATB
     */
    for (count = 0; count < B_START; count++) {
        vsync[count] = SYNC_LEVEL;
        vsyncu[count] = SYNC_LEVEL;
        hsync[count] = SYNC_LEVEL;
    }

    for (count = S_END; count < B_START; count++) {
        vsync[count] = BLANK_LEVEL;
        vsyncu[count] = BLANK_LEVEL;
        hsync[count] = SYNC_LEVEL;
    }

    char_c = 7;
    for (count = V_START; count < V_END; count++) {
        vsync[count] |= BLANK_LEVEL;
        vsyncu[count] |= BLANK_LEVEL;
        hsync[count] = SYNC_LEVEL;

        if (count > SL_DOTS) {
            if (++char_c > 6) {
                ntsc_font(20 + char_n++, count);
                char_c = 0;
            }
        }
    }
    for (count = V_END; count < (V_BUF_SIZ - 1); count++) {
        vsync[count] = BLANK_LEVEL;
        vsyncu[count] = BLANK_LEVEL;
        hsync[count] = SYNC_LEVEL;
    }

    for (count = (DMA_B - 5); count < (V_BUF_SIZ - 1); count++) {
        hsync[count] = BLANK_LEVEL;
    }

    for (count = V_H; count < (V_H + 10); count++) {
        hsync[count] = BLANK_LEVEL; // double speed H pulses
    }

    // default scan mode to all lines
    s_mode = sync1;

    /*
     * kickstart the DMA engine
     */
    DMA5_StartTransfer();
    TMR4_StartTimer();
}

/*
* Encode the font 'rom' into display memory format
* two 256 byte banks for each display line, split into 4 bits per bank
* It's possible to optimize 7 bits for one bank per display line but
* I'm using port B so need to share the pic programmer lines on 6 and 7
*/
void ntsc_font(uint16_t chr, uint16_t count)
{
    uint8_t cbits, i;

    for (i = 0; i < 8; i++) {
        cbits = reverse_bit8(fontv[(chr * 8) + (i)]); // flip bits for proper display
        vsync[count + i] |= ((cbits & 0xf0) >> 3) | BLANK_LEVEL; // mask and shift to upper/lower banks
        vsyncu[count + i] |= ((cbits & 0x0f) << 1) | BLANK_LEVEL;
    }
}

/*
* low priority idle task for timer 4 ISR
* waits checking task_hold until its false (set to false in DMA state machine)
* this task is interruptible from all high pri interrupts and control returns after high pri processing
* when it exits the hold loop, program flow returns to main for application processing
* until another timer 4 interrupt returns program flow to here.
*/
void vcntd(void) // each timer 4 interrupt
{
    S_SET_Toggle();
    TMR4_Stop();
    task_hold = true;
    while (task_hold) {
    };
    S_SET_Toggle();
}

/*
* NTSC DMA state machine
* ISR triggered by the completed DMA transfer of the data buffer to PORTB
* Generates the required HV sync for fake-progressive NTSC scanning on most modern TV sets
* http://people.ece.cornell.edu/land/courses/ece5760/video/gvworks/GV%27s%20works%20%20NTSC%20demystified%20-%20B&W%20Video%20and%20Sync%20-%20Part%201.htm
*
* some code is duplicated for scanline timing adjustments to match for different code paths.
*/
void vcnts(void) // each scan line interrupt, 262 total for scan lines and V sync
{
    uint8_t x;

    x = vfcounts & 0x7; // mask bits for port B bit line and bank selection
    if (x > 3) {
        DMASELECT = DMA_M;
        switch (x) {
        case 4:
            vml = SL_V4;
            DMAnSSA = (volatile uint24_t) vsyncu; // upper bitmap
            break;
        case 5:
            vml = SL_V3;
            DMAnSSA = (volatile uint24_t) vsyncu; // upper bitmap
            break;
        case 6:
            vml = SL_V2;
            DMAnSSA = (volatile uint24_t) vsyncu; // upper bitmap
            break;
        case 7:
            vml = SL_V1;
            DMAnSSA = (volatile uint24_t) vsync; // lower bitmap
            break;
        default:
            vml = SL_V_OFF;
            break;
        }
    } else {
        DMASELECT = DMA_M;
        switch (x) {
        case 0:
            vml = SL_V4;
            DMAnSSA = (volatile uint24_t) vsync; // lower bitmap
            break;
        case 1:
            vml = SL_V3;
            DMAnSSA = (volatile uint24_t) vsync; // lower bitmap
            break;
        case 2:
            vml = SL_V2;
            DMAnSSA = (volatile uint24_t) vsync; // lower bitmap
            break;
        case 3:
            vml = SL_V1;
            DMAnSSA = (volatile uint24_t) vsyncu; // upper bitmap
            break;
        default:
            vml = SL_V_OFF;
            break;
        }
    }
    vfcounts++;


    switch (s_mode) {
    case sync0: // H sync and video, one line
        if (vfcounts >= B_COUNT) {
            s_mode = syncB;
            DMASELECT = DMA_M;
            DMAnCON0bits.EN = 0;
            DMAnSSA = (volatile uint24_t) vbuf_ptr;
            DMAnSSZ = DMA_B;
            DMAnDSZ = DMAnSSZ;
            DMAnCON0bits.EN = 1;
            TRISB = SL_V_OFF;
            /*
             * trigger main task processing using the task manager
             */
            task_hold = false; // clear idle routine run flag
            TMR4_Period8BitSet(TASK_S1);
            TMR4_StartTimer(); // run in main for timer 4 interrupt period then back to idle
        } else {
            if (vfcounts == scan_line) {
                TRISB = vml; // video memory line , on
            } else {
                TRISB = SL_V_OFF; // turn-off all video bits
            }
        }
        break;
    case sync1: // H sync and video, all lines
        if (vfcounts >= B_COUNT) {
            s_mode = syncB;
            DMASELECT = DMA_M;
            DMAnCON0bits.EN = 0;
            DMAnSSA = (volatile uint24_t) vbuf_ptr;
            DMAnSSZ = DMA_B;
            DMAnDSZ = DMAnSSZ;
            DMAnCON0bits.EN = 1;
            TRISB = SL_V_OFF; // turn-off all video bits
            /*
             * trigger main task processing using the task manager
             */
            task_hold = false; // clear idle routine run flag
            TMR4_Period8BitSet(TASK_S1);
            TMR4_StartTimer(); // run in main for timer 4 interrupt period then back to idle
        } else {
            if (ntsc_vid) {
                TRISB = vml; // video bit , on
            } else {
                TRISB = SL_V_OFF; // turn-off all video bits
            }
        }
        break;
    case syncB: // H sync and no video, bottom blank
        if (vfcounts >= S_COUNT) {
            vfcounts = 0;
            s_mode = sync2;
            DMASELECT = DMA_M;
            DMAnCON0bits.EN = 0;
            DMAnSSA = (volatile uint24_t) & hsync;
            DMAnSSZ = DMA_B;
            DMAnDSZ = DMAnSSZ;
            DMAnCON0bits.EN = 1;
        }
        break;
    case sync2: // V sync and no video
        if (vfcounts >= H_SYNC) {
            vfcounts = 0;
            s_mode = sync3;
            DMASELECT = DMA_M;
            DMAnCON0bits.EN = 0;
            DMAnSSA = (volatile uint24_t) vbuf_ptr;
            DMAnSSZ = DMA_B;
            DMAnDSZ = DMAnSSZ;
            DMAnCON0bits.EN = 1;
            TRISB = SL_V_OFF; // turn-off all video bits
            /*
             * trigger main task processing using the task manager
             */
            task_hold = false; // clear idle routine run flag
            TMR4_Period8BitSet(TASK_S2);
            TMR4_StartTimer(); // run in main for timer 4 interrupt period then back to idle
        }
        break;
    case sync3: // H sync and no video
        if (vfcounts >= H_COUNT) {
            vfcounts = 0;
            if ((bool) scan_line) {
                s_mode = sync0;
            } else {
                s_mode = sync1;
            }
            DMASELECT = DMA_M;
            DMAnCON0bits.EN = 0;
            DMAnSSA = (volatile uint24_t) vbuf_ptr;
            DMAnSSZ = DMA_B;
            DMAnDSZ = DMAnSSZ;
            DMAnCON0bits.EN = 1;
        }
        break;
    default:
        vfcounts = 0;
        s_mode = sync1;
        DMASELECT = DMA_M;
        DMAnCON0bits.EN = 0;
        vbuf_ptr = vsync;
        DMAnSSA = (volatile uint24_t) vbuf_ptr;
        DMAnSSZ = DMA_B;
        DMAnDSZ = DMAnSSZ;
        DMAnCON0bits.EN = 1;
        TRISB = vml; // video bit , on
        break;
    }

    /*
     * re-trigger the state machine for a new scanline
     */
    DMA5_StartTransfer();
}
 

KeithWalker

Joined Jul 10, 2017
2,295
If you use Arduino microcontrollers there is a library called TVout which generates PAL or NTSC video output with an AVR.
It supports PAL and NTSC with the max resolution of 128 times 96 pixels.
 

Thread Starter

Sparsh45

Joined Dec 6, 2021
143
Here are two members they use microcontroller to generate video signal.

@MrChips use an 32 bit ARM processor to generate a video signal that runs at a speed of 164 MHz.

nsaspook use 8 bit PIC to generate video signal that runs at speed of 64 MHz.

The thing to learn here is that two device's have been selected for the same purpose for generating videos.

@nsaspook Can you give your suggestion why you chose low speed device to make video where MrChips suggestion says high processing microcontroller is required
 

nsaspook

Joined Aug 27, 2009
9,435
Here are two members they use microcontroller to generate video signal.

@MrChips use an 32 bit ARM processor to generate a video signal that runs at a speed of 164 MHz.

nsaspook use 8 bit PIC to generate video signal that runs at speed of 64 MHz.

The thing to learn here is that two device's have been selected for the same purpose for generating videos.

@nsaspook Can you give your suggestion why you chose low speed device to make video where MrChips suggestion says high processing microcontroller is required
I was mainly testing the DMA speed and memory bus bandwidth of some of the newer PIC18Q43 devices with processing (NTSC signal generation) with fairly tight timing demands.. The system arbiter for memory resources is a bottleneck in systems that have overlapping access to memory on 8-bit systems with CPU independent DMA. The 8-bit device system arbiter is primitive when compared to the capability of most 32-bit devices designed to run a multitasking OS.
Code:
/*
     * setup system arbiter to share memory with DMA process
     */
    // This function is dependant on the PR1WAY CONFIG bit
    PRLOCK = 0x55;
    PRLOCK = 0xAA;
    PRLOCKbits.PRLOCKED = 0;
    ISRPR = 1;
    DMA5PR = 2;
    MAINPR = 3;
    PRLOCK = 0x55;
    PRLOCK = 0xAA;
    PRLOCKbits.PRLOCKED = 1;
https://onlinedocs.microchip.com/pr...tml?GUID-7F322DA3-6BD0-4D0C-AFBB-014970CD0FDA
The system arbiter resolves memory access between the system level selections (i.e., Main, Interrupt Service Routine) and peripheral selection (e.g., DMA and Scanner) based on user-assigned priorities. A block diagram of the system arbiter can be found below. Each of the system level and peripheral selections has its own priority selection registers. Memory access priority is resolved using the number written to the corresponding Priority registers, 0 being the highest priority selection and the maximum value being the lowest priority. All system level and peripheral level selections default to the lowest priority configuration. If the same value is in two or more Priority registers, priority is given to the higher-listed selection according to the following table.
I use a few tricks to get around 8-bit DMA shortcomings.
The remaining problem is dynamic updates to the video memory. All of the memory bandwidth (something in short supply with the simple DMA architectures seen in most 8-bit controllers) is being used by DMA so excessive processing during DMA will affect H/V timing causing tearing and loss of sync. The fix for that is to only process mainline code during the scanlines with no video around the H sync pulse. For that we need a simple task manager that uses the high/low priority interrupt structure and a 'hold' flag.

Setup a Low priority timer ISR as the 'idle' loop; This idle loop can be interrupted by High priority DMA completion and other module interrupt requests so those time critical processes get the cpu they need during the NTSC FSM. The 'idle' loop can also set a task time duration for the main task that will interrupt the main task back to the low priority idle loop during the critical timing periods. This means I can step into the main application code execution when I want it to run it and control how long I want one processing time slice to be.
 
Last edited:

Thread Starter

Sparsh45

Joined Dec 6, 2021
143
This consists of three signals:
  1. Horizontal sync
  2. Vertical sync
  3. Video content
I am trying to understand how microcontroller generates video signal. If I'm right, the starting lines of the scan line show the sync pulses, the middle lines show the video content and the last lines show the sync pulses.

But I still don't understand which peripheral of microcontroller generates the sync pulses for TV input ?

How microcontroller generates digital content like dot for TV input ?

Edit : correction video instead of digital
 
Last edited:

MrChips

Joined Oct 2, 2009
25,919
I am trying to understand how microcontroller generates video signal. If I'm right, the starting lines of the scan line show the sync pulses, the middle lines show the video content and the last lines show the sync pulses.

But I still don't understand which peripheral of microcontroller generates the sync pulses for TV input ?

How microcontroller generates digital content like dot for TV input ?
The timer module is used to generate sync pulses.
 

MrChips

Joined Oct 2, 2009
25,919
I am trying to understand how microcontroller generates video signal. If I'm right, the starting lines of the scan line show the sync pulses, the middle lines show the video content and the last lines show the sync pulses.

But I still don't understand which peripheral of microcontroller generates the sync pulses for TV input ?

How microcontroller generates digital content like dot for TV input ?

Edit : correction video instead of digital
Digital video content like a dot is generated from the output of a shift register. The SPI module (serial peripheral interface) can do that.
 

nsaspook

Joined Aug 27, 2009
9,435
For the 8-bit demo I used a two bit DAC on PORTB for the video dot and sync levels.
1642551620063.png1642552113294.png




1642551835102.png
The transfer of 1 byte of SRAM (a C array) to a 1 byte SFR register (PORTB) is the hard dynamic signal resolution limit on this chip using DMA . That's about 100ns per signal transition with a 64MHz FOSC to the processor controlling a NSTC FSM (scanline state machine). Calculate the needed bit patterns (timing, blanks dots and sync) into a C uint8_t array and DMA that directly to PORTB.


1642551788586.png
Yellow: NTSC camera video as a signal standard.
Red: Q43 DMA generated video.
Green: CPU debugging timing pulses.

My classic 8080 NTSC graphic adapter had a shift register dot clock interface.
https://forum.allaboutcircuits.com/...ramming-experience.180316/page-2#post-1646882
https://forum.allaboutcircuits.com/...ideo-signals-from-scratch.130859/post-1080081
1642552868894.png1642552884282.png
One of my old design 8080/TTL S100 video graphics systems used something like Don Lancaster's composite video circuit to generate the required signals using old Archer RS-2031 transistors for the video level drivers.
 
Last edited:
Top