Cornell University ECE4760
DVI/HDMI
640x480, 16-color, version 1
Pi Pico rp2350

DVI 4-bit color
This project is a first pass at getting the HSTX peripheral running and to understand HDMI generation.
The goal is to make a robust graphics system using HDMI. But the first step is to get the system running a modified HSTX DVI demo code, while bolting on our standard 4-bit drawing routines, enhanced with fonts from RinkyDink Electronics. HDMI connector output is to a HDMI connector (Adafruit) soldered either directly to the Pico2 board, or to a pin header. Do not use a solderless board with junpers. Also, if your board has the small, white debugging port, then you will unable to connect the debugger, unless you plug it in before attaching the HDMI interface. The Pico2W does not have this conector-collision issue.

There is a short video showing animation.

Color is encoded as one red but one blue bit and two green bits.
Color map is in image below.

There are six available mono-fonts chosen to give a range of size and density.
To me, the most useful are the high-densityTiny8 font, the block437 IBM 8x16 font when you need lots of text,
and the Arial16x24 font for titles.

Code, ZIP of project

Internal structure of the driver

The HSTX peripheral sends TMDS generated sync and pixels to a HDMI connector (Adafruit).
There are several steps to this (but I am still figuring this out).

  1. There is a short ISR running twice per monitor scan line. The ISR implents a state machine which sets up two DMA channels to push sync and pixel data to the HSTX. The sync data is in the form of specific TMDS sync-generation commands. One active lines, one of the DMA channels sends the pixel data as eight pixels packed into a 32-bit word. Total ISR time is less than 2% of one cpu. I modified the state machine slightly to send a new frame signal to a thread.
  2. The DVIinit function sets up the HSTX, the TDMS encoder, and the DMA channels. I am still trying to figure out how it works, but the following is my current best guess.
    1. The HSTX code, as written, thinks the clock should be 125 MHz to generate a ~60 Hz video signal. HDMI 2.1 monitors will accept different rates, with 150 MHz generating stable72 frames/sec on monitors I have tested. I added a clock divider function to allow the ARM clock to be higher than the HSTX clock. I tested at a cpu clock of 252 MHz with a HSTX clock divider of 2 (126 MHz) to generate 60.2 frames/sec. Also at a cpu clock of 300 MHz with a HSTX clock divider of 2 (150 MHz) to generate 72 frames/sec.
    2. The TMDS input shifter grabs data from the input FIFO and sets up pixel data input by handling shifts of the incoming 32-bit word, shifting it eight times, with four bits per shift pushed to the output. At the same time, TMDS commands are treated as 32-bits, with no shift. See register description hstx_ctrl_hw->expand_shift for more info.
    3. The TMDS generator puts blue/sync on output lane 0, green on lane 1 and red on lane 2. To do this you specify the number of bits for each lane (minus one! to send one bit, set value zero), then right-rotate the bits to align with the LSB of a 32-bit rigister. I found this bewildering to set up because the shift arithemtic is module 32. For more info see register description hstx_ctrl_hw->expand_tmds. The bits for each of the three lanes is packed into the 32-bit output shifter. The TMDS datasheet linked above, chapter 5, hints at how the encoding is done.
    4. The HSTX output bit mixer is then set up to map the TMDS 10-bit encodings to three sets of differential pins, with a clock on the fourth set of pins.

=====================================================================

List of Functions.

Initialization and signals Description
void DVIinit(void) Call once to set up the HSTX peripherial and DMA channels
int start_frame
Returns non-zero when a new video frame starts.
Erase  
void clearRect(short x1, short y1, short x2, short y2, short color) Clears a region of the draw buffer from (x1,y1) to (x2,y2) to a color . For speed, there is NO error or bounds checking!
void clearLowFrame(short top, short color)
Clears draw buffer below a top y value. Fastest erase routine. Again, NO bounds checking.
void clearRegion(short y1, short y2, short color) Clears draw buffer from y1 to y2. Again, NO bounds checking
Text NOTE that all of the text routines require x to be even.
An odd value of x is modified one pixel to the left to make it even.
int drawTextGLCD(short x, short y, char * str, char color, char bgcolor); Draw one line of text in a 16x16 pixel format with the upper left corner at (x,y), using the string str, and using color, with background bgcolor. Returns the number of characters actually drawn.
int drawTextTiny8(short x, short y, char * str, char color, char bgcolor); Draw one line of text in a 8x8 pixel format with the upper left corner at (x,y), using the string str, and using color, with background bgcolor. Returns the number of characters actually drawn. This is an efficient, small font.
int drawTextVGA437(short x, short y, char * str, char color, char bgcolor); Draw one line of text in a 8x16 pixel format with the upper left corner at (x,y), using the string str, and using color, with background bgcolor. Returns the number of characters actually drawn. This font is copied from the 1981 IBM ROM.
int drawTextArial24(short x, short y, char * str, char color, char bgcolor); Draw one line of text in a 16x24 pixel format with the upper left corner at (x,y), using the string str, and using color, with background bgcolor. Returns the number of characters actually drawn.
int drawTextGrotesk32(short x, short y, char * str, char color, char bgcolor); Draw one line of text in a 16x32 pixel format with the upper left corner at (x,y), using the string str, and using color, with background bgcolor. Returns the number of characters actually drawn.
Outline shapes  
void drawPixel(short x, short y, char color) ;
Draw one pixel of specified color at (x,y). Will not draw outside 640x480 region.
void drawVLine(short x, short y, short h, char color)
Draw a vertical line of specified color from (x, y) to (x, y+h)
void drawHLine(int x, int y, int w, char color) Draw a horizontal line of specified color from (x, y) to (x+h, y)
void drawLine(short x0, short y0, short x1, short y1, char color) Draw a line of specified color between two end points. Uses Bresenham alogrithm.
void drawRect(short x, short y, short w, short h, char color);
Draw a rectangle of specfied color from corner (x, y) to (x+w, y+h)
void drawCircle(short x0, short y0, short r, char color) ;
Draw a circle of specifed color with center (x0,y0) and radius r.
void drawRoundRect(short x, short y, short w, short h, short r, char color) ; Draw a rectangle, but with rounded corners of radius r. For sanity, w>2*r and h>2*r
void drawMultiLine(int num_points,  short point_list[][2], char color) ;
Draws (num_points-1) lines of specified color using an array of x,y points point_list[num_points][2].
void crosshair(short x, short y, short c)
Draws a little cursor-like cross of specifed color at x,y.
Filled shapes  
void fillRect(short x, short y, short w, short h, char color)
Fills a rectangle with specfied color from corner (x, y) to (x+w, y+h)
void fillCircle(short x0, short y0, short r, char color)
Fills a circle with specifed color with center (x0,y0) and radius r.
void fillRoundRect(short x, short y, short w, short h, short r, char color) ; Fills a rectangle with rounded corners of radius r. For sanity, w>2*r and h>2*r
void fillTri(float x0, float y0, float x1, float y1, float x2, float y2, char color)
Fills a triangle specifed by three vertices with the specified color

 


Copyright Cornell University October 30, 2026