Low Cost VGA Terminal Module

This is a low cost module to add VGA and keyboard connectivity to retro computer, development board or embedded computer via serial port.

Similar projects worth following
This is a VGA and keyboard interface module for embedded computers that need text output on a monitor. It can be connected to a VGA monitor, PS/2 keyboard and act as a VT100 terminal via TTL serial port. It boots up instantaneously so it doesn't miss critical power on messages from the host.

While there are a lot of ARM based VGA projects out there, this is an exercise to see what can be done with the low end STM32F030F4 that has only 4K of RAM and 16K of FLASH.

I named it ChibiTerm for its size: 0.7"x1.0" (17.78 mm x 25.4 mm)
(Chibi = Small in Japanese).

Small embedded microcontrollers do not usually come with a video output. The aim of this project is design a low cost stamp sized module that can be used to provide a VGA text display rendered at full 640x400 or 640x480 and interface with a PS/2 keyboard. The module acts as an embeddable VT100 terminal to the host via serial port. It takes care of the video generation, critical timing issues and simplifies the driver for the embedded host. The host only need to deal with sending/receiving characters and control code via serial port.

This is an DIY project with readily available parts. It can boot up instantaneously to catch console messages and consumes ~25mA at 3.3V for monochrome video (without the PS/2 keyboard). The module is small enough (0.7"x1.0") and cheap enough to be embedded in a design.


640 x 400/480 VGA text mode uses a 25.175MHz pixel clock. Most of the video monitors can accommodate 25MHz which is commonly used for Ethernet and the crystal/oscillators are easily available at a much lower price.

There is a bit of overclocking involved in this project, so YMMV.

Over-clocking analysis

I have tested the STM32F030 SPI at 25MHz for three chips with different date code. It works fine for the SCK and MOSI signals in 8-bit master mode.

I am trying to deduce where the SPI 18MHz limitation on the datasheet comes from. The APB peripherals STM32F0 value line are qualified to work at 48MHz. The I/O (clock to data out) timing on the datasheet actually very tight and have plenty of margins meant for a much higher frequency.

The SPI clock frequency are at 18MHz for some reasons.

Here is what I think happens: Someone copy the numbers from a different product line.

The following is from their 72MHz STM32F10X line. 72MHz is probably a bit too fast for the SPI block, so they divide the frequency down to 36MHz. There is a further divide by 2 inside the SPI module for the SCK clock. Hence the maximum data rate is: PCLK/2 = 36MHz/2 = 18MHz.

Video generation:

Most of the other VGA projects uses a mid range ARM Cortex M with a lot more RAM and/or renders the output in a lower resolution. It is easier to ignore cost and throw resources at a problem. I am trying to use the "value" line STM32F0 for cost reason and as a technical challenge. The part is available here at $0.44 at QTY 10.

My challenge is to implement a working subset of a VT100 terminal while emulating the missing hardware using a high level language and working within similar memory constraints of the original VT100. For VT100 compatibility verification, I am using a standard open source test suite called VTTest.

Initially I planned to use external SPI RAM as the frame buffer for a full graphic + colour display, but I scale back and try to work with only on chip resources. The amount of RAM in this part is only 4KB which is not sufficient to store the entire frame buffer. The alternative is to generate all the scan lines at full resolution (640x400 or 640x480) on the fly for each frame.

The block diagram is actually very similar to the "6845 CRTC" inside old terminals. It is all implemented inside the STM32F030F4 microcontroller.

Above is a modified version of typical usage example from the MC6845 datasheet.

  • The ARM M0 core is the "Processor"
  • The internal RAM is used as "Refresh RAM" that holds a Character buffer as well as a scan line buffer. Thebuffer is used to decouple the (faster than real time) rendering process from the video generation.
  • Hardware timer is used to generate the HS (Horizontal sync) and an IRQ is used to keep track of the current scan line "Refresh Memory Address" for rendering, VS (Vertical sync.) generation and Ping-pong buffer management.
  • DMA transfer rendered scan line from the buffer to the SPI acting as the "Shift Register"
  • A firmware rendering routine described below is used to implement the "ROM Character Generator" function.

The rendering process

  • A text buffer is used to store the characters to be displayed.
  • For each scanline, for each of the character in the...
Read more »

text with HSI.mp4

This is how jittery the video output is with internal RC oscillator. Kind of like old VCR tapes. This is why I use crystal oscillator for this project..

MPEG-4 Video - 2.39 MB - 03/20/2016 at 23:05


video buffer.asc

LTSpice simulation file for the video buffer circuit. Permission: CC BY4.0

asc - 1.89 kB - 03/04/2016 at 15:01


  • 1 × STM32F030F4P6 Microcontrollers, ARM, RISC-Based Microcontrollers, 48MHz 16K/4K FLASH/RAM
  • 1 × Full BOM on GitHub

  • VT100 - VTTest

    K.C. Lee06/02/2016 at 03:07 0 comments

    My priority for this project is to get the core VT100 control code to work. Correctness is a very important part of the project. VTTEST is an open source compatibility test for VT100. I am using it as a verification suite for the next mile stone.

    Blank Score card available from Kermit Project: here See here for VTTEST score of some of the programs from 1996.

    The lost points are from missing features and not because of incorrect implementation: 132 column, double width, extra character attributes, VT52 mode (for backward compatibility).

    There are still some bugs in the scrolling code. Right now I am a bit distracted by other projects that are still in running for the contest.

    ---- Work in progress -----

    VTTEST VT100/VT102 Compatibility Test Score Sheet (reformatted)

    Program and version: VGA Terminal Date: _________

    Score: 34 (so far) + Extra credit: 2 = Final score: ___________

    Check box if test passed. Score 1 point per check mark.
    Perfect score = 100 points. Extra credit at end.

    ☑: pass, ☒ Not implemented, ☐: Work in progress

    1. Test of cursor movements

    - Soft scroll and 132 columns not implemented.

    ☑ 1. Text inside frame of E's inside frame of *'s and +'s, 80 columns
    ☑ 3. Cursor-control chars inside ESC sequences
    ☑ 4. Leading 0's in ESC sequences

    2. Test of screen features

    - Soft scroll and 132 columns not implemented.

    ☑ 5. Three identical lines of *'s (test of wrap mode)
    ☑ 6. Test of tab setting/resetting
    ☑ 8. 80-column mode, light background
    ☑ 10. 80-column mode, dark background
    ☑ 13. Jump scroll down
    ☑ 14. Jump scroll up / down
    ☑ 15. Origin mode test (2 parts)

    Graphic Rendition test pattern, dark background

    - Plain VT100, Other attributes not implemented.

    ☑ 16. Normal ("vanilla")
    ☑ 20. Normal reverse ("negative") distinct from all above

    Graphic Rendition test pattern, light background

    - Plain VT100, Other attributes not implemented.

    ☑ 32. Normal ("vanilla")
    ☑ 36. Normal reverse ("negative") distinct from all above

    Save/Restore Cursor

    ☑ 48. AAAA's correctly placed
    ☑ 49. Lines correctly rendered (middle of character cell)
    ☑ 50. Diamonds correctly rendered

    3. Test of character sets

    ☑ 51. UK/National shows Pound Sterling sign in 3rd position
    ☑ 52. US ASCII shows number sign in 3rd position
    ☑ 53. SO/SI works (right columns identical with left columns)
    ☑ 54. True special graphics & line drawing chars, not simulated by ASCII

    4. Test of double-sized chars

    - Not implemented

    5. Test of keyboard

    - LED, Auto-repeat keys, VT52 Support not implemented

    ☑ 67. "Press each key" (ability to send each ASCII graphic char)
    ☑ 68. Arrow keys (ANSI/Cursor key mode reset)
    ☑ 69. Arrow keys (ANSI/Cursor key mode set)
    ☑ 71. PF keys numeric mode
    ☑ 72. PF keys application mode
    ☑ 75. Send answerback message from keyboard
    ☑ 76. Ability to send every control character

    6. Test of Terminal Reports

    ☑ 77. Respond to ENQ with answerback
    ☑ 78. Newline mode set
    ☑ 79. Newline mode reset
    ☑ 80. Device status report 5
    ☑ 81. Device status report 6
    ☑ 82. Device attributes report
    ☐ 83. Request terminal parameters 0
    ☐ 84. Request terminal parameters 1

    7. Test of VT52 submode

    - Not implemented

    8. VT102 Features

    - Not Implemented

    9. Extra credit

    ☑107. Send BREAK (250 msec) (*)
    ☑108. Send Long BREAK (1.5 sec) (*)

    Test Set up

    I have set up bodhi x64 linux on a vm. I set up a serial console in Linux using instruction from

    I use Synaptic to install vttest at /usr/bin . I can run it from the serial console on the terminal.

    I connect the terminal with a level shifter to connect to COM1 on my PC. COM2 is set up to snoop the RxD line of the serial port, so that I have a side by side screen of what the display should look like.


    Screenshots of VTTEST:

  • VT100 - Testing with Linux utils

    K.C. Lee05/29/2016 at 21:43 0 comments

    There are bugs that vttest doesn't catch. Errata so far:

    • top works now - with some weird behaviour on scrolling up on the dopckstar
    • vi sort of work now. still some weird stuff
    • nano has messed up scrolling.

    top seems to work now with full 80x40 screen. The screen get messed up sometimes on scrolling up when I hold down the cursor key. I don't know if my not implementing XON/XOFF on my transmit side might be messing it up. It works fine for lLinux running in a VM on a PC.

    Found a bug in the cursor positioning code. Some how I didn't pass the parameter correctly. nano looks less messed up.

    Test Setup

    I am running my test on Arch Linux on my Dockstar set up to test out the VT100 implementation.

    To set terminal type and use 40 rows:

    export TERM = vt100

    stty rows 40

    What I have done is to split off the serial RxD signal to the RxD on my PC. I can use and compare the results inside a terminal program or log the raw control codes to a file.

    Programmer's Notepad does a good job formatting the control codes in the log files. I can use copy/paste to send the control codes to the VGA terminal.

    I can compare the results side by side. This would let me look at the individual escape sequences and see which one(s) I didn't code correctly.

    I have notice that my new code starts to miss characters at the edges during scrolls. Increasing the RxD FIFO or decreasing it doesn't seem to be an effect.

    Right now the blinking cursor and the position of where the next character are tied together, so there is some last minute patches to get the cursor to stay on column 80. Given that the blinking cursor is actually another task that only get run when cursor changes, it might be better to move that messy code there and not waste CPU cycles.

    Right now the priority is to make sure that the control code are handled correctly, so I'll need to come back to this as part of the code clean up.

  • Vt100 implementation

    K.C. Lee05/23/2016 at 23:54 0 comments

    I have been avoiding this part as I read about Terminal Emulation on It scares me a bit as the ANSI standard is too open ended.

    "However, X3.64 defines many implementation-dependent features and error conditions without defining recovery procedures."

    I found Joe Smith's Ansi code article, there is a section for " Minimum requirements for VT100 emulation" at the bottom. I guess I'll use that as a starting point to nail down the basic for a milestone.

    1. To act as a passive display, implement the 4 cursor commands, the 2 erase commands, direct cursor addressing, and at least inverse characters. The software should be capable of handling strings with 16 numeric parameters with values in the range of 0 to 255.
    2. To enter data in VT100 mode, implement the 4 cursor keys and the 4 PF keys. It must be possible to enter ESC, TAB, BS, DEL, and LF from the keyboard.
    3. When doing full-screen editing on a VT100, implement directed erase, the numeric keypad in applications mode, and the limited scrolling region. The latter is needed to do insert/delete line functions without rewriting the screen.
    4. If the hardware is capable of double width/double height:
    5. If the terminal emulator is capable of insert/delete characters, insert/delete lines, insert/replace mode, and can do a full-screen dump to the printer (in text mode), then it should identify itself as a VT102.

    I took the advice of writing a parser for the CSI sequence before trying to implement the commands. I wrote a simple parser based on the following from Joe's article.

    General rules for interpreting a Control Sequence:

    1. It starts with CSI, the Control Sequence Introducer.
    2. It contains any number of parameter characters (0123456789:;<=>?).
    3. It terminates with an alphabetic character.
    4. Intermediate characters (if any) immediately precede the terminator.

    I do not know enough about error recovery or badly coded ANSI sequence. I can't and won't expect every single piece of code out there would work.


    "VT100" isn't too well defined as there are various implementations that do different things. Just for the irony, here is my list. I might try vttest at some point to see how bad my implementation is.

    • The hardware can only support the 7-bit ASCII table, monochrome and inverse video attribute. It is a baseline VT100 without any processor/graphic attachments nor printers, no scroll back buffer, no LED, no Bell, no fancy graphics/extra character sets/132 columns/double width etc.
    • One need to use the stty command to take advantage of the larger screen size.

    stty rows <number of rows>

    • Since the terminal only transmit keystrokes or small report packets, I am tempted to not implement flow control for transmit. The user can use XON/XOFF to pause/resume the data coming from the host. The firmware is surprisingly fast and doesn't requiring handshaking.
    • The keypad is under user control via Num Lock key.

    Here is how I mapped the keys on a PC keyboard base on how things work on a PC.

    • PF1-PF4 are mapped to function keys.
    • [ - ] and [ , ] are mapped to [ - ] and [ + ]
    • User has control of numeric keypad vs application key mapping by using Num Lock key.


    • The firmware handles the keyboard LED updates and mapping, so someone could in theory redefine/reassign a different key for the Num Lock function.
    • Please ignore the cursor key labels on the keypad as the application keys might be mapped differently by host software.

    Progress so far based on Joe Smith's Minimal VT100 requirement list

    List 1: Implemented

      • [A Move cursor up one row, stop if a top of screen
      • [B Move cursor down one row, stop if at bottom of screen
      • [C Move cursor forward one column, stop if at right edge of screen
      • [D Move cursor backward one column, stop if at left edge of screen
      • [H Home to row 1 column 1 (also [1;1H)
      • [J Clear from current position to bottom of screen
      • [K Clear from current position to end of line
      • [24;80H Position to line 24 column 80 (any line 1...
    Read more »

  • An alternative to ping pong buffering in rendering

    K.C. Lee04/25/2016 at 21:05 1 comment

    Just before I took a cat nap, I was reading over NTSC generation. As I was still hazy getting up, I had an idea...

    Instead of using a ping pong buffer to render the scan line, why can't I use a single buffer? The rendering code is about 2x the SPI data rate, so all it need is some head start rendering a small part of the line at the horizontal blanking IRQ. The code is a bit more messy and I have to do some trial & error to see how much I can render before putting the core to sleep for jitter free DMA from IRQ.

    This will free up 80 byte of RAM.

    So it looks like it is a bit more messy than I think. It looks kind of weird with the first few character shifted down.

    Basically there was only enough cycle to render 1 character before the sleep. The core wakes up for DMA, DMA prefetch and filled up the 4 bytes FIFO with what was in the buffer from last scan line as there wasn't enough of a lead in the buffer. After a few characters, the rendering caught up and the correct data was fetched.

    I think I have to do some more ugly coding.

    • I render first 8 characters of the first line before the first active display line. This buys time for the rendering loop to start going. Once it started, the loop will be fast enough to stay ahead of the DMA.
    • For each line, starts the DMA.
      • render the remaining characters in the line. (DMA would reaches half way by then.)
      • render the 8 character for the next line.

    Old code:

    New code:

    It is working now. Free RAM: 4096 - 3920 - 48 = 128 bytes. I free up 88 bytes. Feels like I am working on 8-bit uC. :(

    I had a lot of problems trying to keep the values in a struct as the different sections of my IRQ service routine actually get called in separate IRQ instances. It turns out that using local variables save more RAM. The slightly bigger code size isn't that bad as I won't it'll fill the 16k for this project. (The chip actually has 32k as it can be seen from the debugger.)

  • ChibiTerm in a dongle

    K.C. Lee04/24/2016 at 17:34 0 comments

    I found a couple of DB9 to RJ45 break out adapter along time ago in a garbage bin. They seem to be the right size for housing the PCB. These are quite expensive dongles and a few times the cost of ChibiTerm. ;) The cheap grey ones from China is narrower, but the PCB might fit diagonally. I don't like the cheap plastic look however.

    The (ABS?) case is very nicely welded together and parts of it is seamless. I don't want to break it into halves for easier access.

    I cut the plastic at the back with large/small box cutters, hand filed the inside. I filed the access slot on the bottom. I also heated I shaped transformer laminated core to melt the plastic.

    I am thinking of making a small PCB with a 4-pin header to sit in the slot and have the wire exit in the bottom opening. Here is how the Serial and PS/2 connectors fits at the back. The pins have to be trimmed to fit in the case. (If I had the RJ45 insert,it would be easier to make a breakout cable)

    The PS/2 connector is just the right height except for the pins and the extra bits of plastic standoffs at the bottom for wave solder. There is a cover at the back side of the connector that can be slid open.

    The individual stamped contacts are inserted into the housing and the back cover hold them in place. The pins exits at 90 degrees through the cover.

    So looks like I'll need to mod the connector. I might be using fine magnetic wires to solder to the pins inside and breakout to a PCB at the top horizontal slot and solder wires from there. I'll hold off the rest of the mod until my parts arrives from China to assemble more PCB.

    Parts from China (ordered on Mar 17) arrived today (May 12). That's close to 2 months now. The Canada Custom still haven't cleared their backlogs. :(

    I soldered up a PCB. This one has Crystal, resistor network, resistors, transistor, 3.3V LDO (XC6206P332MR) and ARM chip from China. I program in a working firmware and scoped out the sync and the video signals and it seems to be alive!

    I soldered up the serial port header connector and glue it into the slot with epoxy. I use the connector to line the header up until the epoxy is set.

    I make a breakout for the Mini DIN connector with magnetic wires and a small piece of PCB for the breakout to wires. Reassemble the connector before trimming ff the leads.

    Soldering everything up. Have to curl the wires so that they fit better. I wired the bootstrap pin to an unused pin on the VGA connector so that I can update the firmware without taking things apart. The connector is snapped into place.

    PS/2 and TTL serial port. Power can be fed from the serial port or from PS/2 with a splitter.

    Just like the demo video. The serial port is connected to my Dockstar. I have to turn off the AutoLF in my code as Linux uses linefeed for new line.

    Size comparison: (Key from an old lock that no longer exists.)

    The dongle when fitted with a male connector could be plugged directly onto the monitor. (Mock up shown below)

    I use my AT keyboard from my old luggable PC and this used monitor from a Thrift shop to make a usable terminal.

  • The Plane! The Plane! - PCB arrives

    K.C. Lee04/12/2016 at 11:10 3 comments

    I have moved the video waveform to the video buffer log.

    I have uploaded a snapshot of what I have on GitHub. I am still a noob at git.

    There hasn't been much feature changes from the dumb terminal demo. I haven't quite get to implementing the ANSI Escape sequence. The changes are mostly structural. Now that the PS/2 code are a bit more tested, I think I should post a snapshot just in time for the first HaD 2016 prize deadline.

    A new PCB incorporating the mod has been uploaded to OSH Park. I have not ordered/tested the new layout, but the changes are minimal so it should work. I have done some component consolidation.

    Still waiting for the rest of my parts to show up from China. :(

    I did some code clean up and found the bug in the way I am sending the PS/2 commands. I have initialize my state variable for the IRQ incorrectly. I forgot to update part of the code as the bit counting was at one point counting down is now counting up. The keyboard then sends an ACK in respond to the command sent, but the IRQ still thinks it is still sending so it misses that. Eventually the timeout kicks in to reset things.

    That bug hangs most of the keyboards as they expect a second parameter to come in. This unfortunately didn't happen on the KVM which I did most of my testing on as its own uC handles things differently.

    The keyboard LED update routines are now working for my keyboards including IBM M2, USB keyboard from the dollar store, Digital Equipment PS/2, IR based wireless PS/2 keyboard and my KVM switch. I had to tweak some timing parameters e.g. add a slight time delay before sending PS/2 commands as sometimes that overwhelms the uC in the KVM and IR keyboard as they have extra work load.

    I know I am on the right track when the new code works and is smaller. Now I can work on the high level PS/2 decoding routine some more.

    The PCB arrived yesterday. I'll try to assemble it later on today. My parts from China won't arrive for a while, but I'll make do with what I have. They managed to include the 16 mils REFDES and have perfect alignment. I usually turn those off for Chinese PCB house.

    The PCB was assembled and the blinking cursor shows up. :) I am making some minor changes to the PS/2 hardware as I totally forgot about USB keyboard support. I also need to do some more work with the PS/2 driver.

    Size comparison: 28-pin EEPROM, 28-pin ATMega8 and ChibiTerm over fake credit card from junk mail.

    V.1 mods and changes:

    • Originally the pull down helps to lower these signals as a cheat for the pins not being 5V tolerant. I need to change the 4.7K into pull ups as USB keyboards look at logic level on power up to change into PS/2 mode.
    • The zeners drags down the voltages too much. I have removed them. The new design uses Schottky diodes to clamp the PS/2 signals to 3.3V+0.3V.
    • For now, I am keeping the zener diodes on the USART lines for testing.

    The current flowing through each of the diodes is around 0.79mA which is well within what the STM32F030 spec. I could have used 3.9V or higher Zener diodes, but they are not on sale on Aliexpress. Zeners should be used under pre-biased.

    This is what the mod looks like. D3 is BAT54C or equivalent common cathode Schottky diodes.

    I'll release a new layout after debugging is done.

    Aside from the PS/2 firmware issue and the mod, the PCB works as expected.

  • Milestone: ChibiTerm live demo - Dockstar serial console

    K.C. Lee03/28/2016 at 20:18 3 comments

    Modded my Dockstar to add a console serial port here. It is made by Seagate as a NAS. I have installed Arch Linux on it - see instruction here.

    I spliced the power connection to have the 5V (instead of 3.3V) needed for the PS/2 keyboard. The epoxy is barely cured before I have to hook it up to the ChibiTerm for this demo.

    Dockstar's serial port is connected powers the ChibiTerm breadboard. ChibiTerm drives my monitor and a PS/2 keyboard. Right now it is a dumb terminal as none of the ANSI sequences nor the non-character keys are implemented, but it is sufficient to work as a serial console. The cursor is working too. :)

    The following demo shows the Dockstar booting up, a few commands were typed on my PS/2 keyboard on a 80x40 line VGA screen. Youtube demo is here:

    Since ChibiTerm boots up instantly, it can catch early boot messages.

    BTW, the serial port is running at 115,200bps with no handshaking. :)

  • PS/2 keyboard and terminal emulation

    K.C. Lee03/26/2016 at 04:41 0 comments

    I have rewrote most of the low level PS/2 routines for the event handling loop for this project. It is a pain as it was a lot easier to code as a thread in my RTOS code with message queues, mutex and non-blocking I/O than a finite state machine that have to remember where it was every time a new byte from the PS/2 shows up. Kind of like those mystery amnesia story having to reconstruct what has happened every time the MC wakes up and he has to scribble a note for himself before going to bed.

    The error handling and time out logic was a bit of a nightmare, but I think I have most of it working with exception of hot plugging recovery of a keyboard. (Neither can some of the older PC.) Hot-plugging the keyboard might introduce glitches at the PS/2 clock line, the IRQ code might get out of sync. The IRQ code only checks on for parity errors and unexpected inputs, so it might take close to a dozen keys (depending on luck) before realizing it is out of sync and resets the keyboard.

    I have added the following piece of code at the top of the IRQ that checks for an idle time out for the incoming clock bit and reinitializes the variables. It works really well as I can type right after hot-plugging in a keyboard. MicroTimer1 is decremented at each horizontal sync.

      PS2_IF.Init = 0;
    MicroTimer1 = us_to_Tick(PS2_BIT_TIMEOUT);

    I got some of the PS/2 raw scan code translated to ASCII code. The keyboard recognizes the shift/control/caps lock/num lock keys and can update the keyboard LED accordingly. There are still quite a bit of work to be done as I have to figure out the key mapping of the function keys/cursor keys and numeric keypad etc. Terminal programs all seems to do different things.

    Since the VGA text is really limited to monochrome and 8-bit buffer for characters, I am probably only going to have 2 character sets - normal+inverted ASCII 7-bit characters. I'll try to implement a baseline "No options" VT100 terminal and something enough to use a Linux console for full screen file editing.

    Only going to worry about US keyboard for now as that's what I use. The PS/2 raw keycode translation is done in a table and the fonts can be imported, so hopefully people can localize that.

    Right now I have yet to implement the support for a cursor efficiently. Seems like taking snapshots and swapping character in the text buffer is going to be a bit of a pain, but better that than complicating the rendering code.

    Before I knew it, I have implemented a very simple dumb terminal. I have yet to implement the non-text keys and the fancy ANSI escape sequence.

    I was thinking too much as updating the cursor in an IRQ as it is not an easy thing to do without some synchronization mechanism. I overlook the possibility of updating the cursor in another task. Since the tasks run all the way to its end, sharing variables between them do not need synchronization.

    "On the standard VGA, the blink rate is dependent on the vertical frame rate. The on/off state of the cursor changes every 16 vertical frames, which amounts to 1.875 blinks per second at 60 vertical frames per second."

    The cursor task get called every 16 frames alternating between the cursor character or the text under it. That actually works quite nicely as the new text simply overwrites the cursor until the next time the task get called again and pops up at the new location without leaving a trail.

    Trying to conserve RAM as much as I can right now as I have only 40 bytes of RAM left.

    I have refractored some of my code to use packed bitfields instead of bitwise logic operations. While the amount of RAM usage is the same, ARMCC seems to be using slightly more code space than my original code. ARMCC is pretty good as I have refractored my code a different way in the past only to find out it uses the same amount of memory. The code is a bit more readable for the extra tens of bytes of code space and the compiler will allocate more space transparently when more bitfields are needed. This is both good...

    Read more »

  • Layout status

    K.C. Lee03/20/2016 at 13:31 0 comments

    l;dr version: Canada Post finally open their UPS package and it only take them 1 week. Now it is Canada Custom's turn to sit on it. They cleared it! So looks like Custom is treating this as a first class mail. That means that Canada Post might try to deliver this in 2-3 days instead of the usual 7-10 days.

    I finally got the 2nd batch of STM32F030F4 that I ordered back and shipped back in Feb 2015. It took 2 months vs last year's 3-4 weeks. I'll try to solder up one from this batch onto the PCB if/when it arrives.

    PCB is on order. Hardware files on github
    I am expecting some minor mods/layout tweaks for the PCB, so please be patient until the board is tested. Going to take the OSH Park PCB offline for the meanwhile.
    Please use the board file for REFDES as they are too small for proper silkscreen.
    PCB: 0.7"x1.0" $3.50 for 3 copies from these purple guys. :)

    The PCB is the same size as the little green breakout board with all the parts from the breadboard too.

    It takes 5 working days to just cross the border and put in the mailbox!? Let's hope that this is treated as a first class mail as they are supposed to clear custom and deliver a lot faster than "low priority" packages from China. Things worked a lot faster last December.

    Log as follows:

    I have been doing a board layout for the past few days. I was on rev 3 of the layout and I thought I finally got everything routed and have a layout that I like. I notice that the board is 0.05" too wide to fit on a protoboard on 0.1" grid. Guess I have to squeeze some more.I'll have to go the other direction to make the board slightly bigger. Rev. 4 is now bigger.

    Looks like it won't work without having to put a lot of the component on the back side. Rev 3 layout have optional components on the back side to cut manufacturing cost. In the end, it looks like a bigger board makes more engineering sense.

    I managed to make a few layout improvements because of the extra space.

    • I managed to get rid of the 0402 cap and relocate the decoupling caps to the front side of the PCB.
    • Oscillator - no vias and rotate crystal so that the OSC_In side (high impedance input) is now close to the microcontroller.

    Made some minor changes to the schematic:
    • Changed the regulator to MCP1700, but might try out XC6206P332MR (with same pin out) when they arrive from Aliexpress in about a month and a half. :( Those are very cheap 3.3V LDO with support for ceramic caps. They trade off transient response for very low quiescent current.
    • Change oscillator to 25MHz crystal for cost saving.
    • 3.3V zener diodes on the PS/2 and serial interface. These are located on the back side of the PCB. They are optional, but may save the STM32F030 from accidents. Waiting for shipment from China in about a month and a half.
    • The USART registers has option to flip the signal polarity, so might be able to talk to 12V serial port by adding a couple of series resistors (with the zener) at much reduced distances like a few meters.

    Going to procrastinate a bit and when I am happy with the PCB, I'll send the file off to OSH Park and post a link for ordering.

    Here is what a (single sided) breakout board might look like.

    I have done a bit more cleaning up on the board and design.

    • The buffer video signal has 0.7V drop, so I use a separate series resistor on BACKGROUND signal and for tweaking intensity.
    • Rerouted a bit for better signal integrity. High speed signals have solid ground under them.

    After much procrastination, I have decided to give it a rest and order the darn thing. Order link is up top. The parts on the bottom are optional zener diodes for protection.

    This PCB is the same size as the small green breakout PCB in my prototype. :)

  • Matrix Clock - Demo for the VGA Text

    K.C. Lee03/17/2016 at 01:25 2 comments

      Source code for this demo is now on github along with schematic, PCB layout, 3D model if you can't wait for the rest of the project.

      I whipped together a little demo for the VGA Text library I am working on for this project. It was inspired by The Matrix digital "Rain" sequence. It was a bit impulsive, but to be honest I think it is more interesting than seeing linux boot message flying off the screen.

      I wrote the rip off effects myself using the VGA Text library. I haven't found a suitable Katakana font, so I stuck with the same terminal font. The font was reversed by telling the SPI to send out the video LSB first.

      Spoiler: You are viewing this on a screen inside the computer, so everything is flipped.

      The clock, rain effects and a shell are cooperative "task" in a big event loop. A callback program from vertical blanking is used to trigger events.

      Would have loved to do this on the screen using PS/2, but not there yet.

      (The long exposure time captured multiple frames and that's why the "rain drops " are smeared in block of 4)

      The schematic is the basic VGA output one with an external 25MHz oscillator. Pin 18 (PA10) is RxD, Pin 17 (PA9) is TxD. It is hooked up to a 3.3V TTL serial port (115,200, 8-bit no parity) on a PC for setting the time using a shell.

      Haven't ported the code to GCC. Probably going to take a while for source code release as I ound to find something I need to change. For now there is firmware (Hex file) for the demo: here

      The time is done by keeping track of vertical blanking. However the vertical blanking is not an integer. 31250Hz/525 = 59.5238Hz

      This leaded to the clock not keeping time. I should have thought of using DDS technique for the timing.

      Clock frequency = 31250/525 = 1250/21

      1. Keep adding 21 to an accumulator until it reaches above 1250. That's 1 second.
      2. Subtract off 1250 and continue counting. The remainder keeps track of the timing error

      Sometimes it takes 60 vertical blanking and sometimes it takes 59, but on average the frequency is 1Hz. This trick can also be extended to correct for the tolerance of the crystal.

      #define TICK_INC       21
      #define TICK_THRESHOLD 1250
      // keep track of time
      void VerticalBlank_CB(void)
        phase_accumulator += TICK_INC;
          { second = 0;
      	{ minute = 0;
      	  if(hour++ > HOUR_FORMAT)
      	    hour = 0;
      // code for Matrix stuff here....

View all 21 project logs

  • 1
    Step 1

    This instruction is for hand soldering. The board is relatively tight and I use fine pitch parts, so it is not for beginners. Parts are cheap, so get some extras.

    The trick is that you want to have clearance for soldering the components. I have divided the board into regions 1-6 that are to be soldered sequentially.

  • 2
    Step 2
    Solder discrete in regions 1.
    • The crystal is a tall part, so it gets in the way of these discrete.
    • The left and right of the edge of the TSSOP is in the way of resistor network and the crystal, so it is soldered after the crystal.
  • 3
    Step 3

    Solder in the crystal. Remove excess solder if necessary as they can interfere with the placement of the TSSOP or short to the 4 vias. OSH Park for some reasons do not like solder mask on vias. :(

View all 10 instructions

Enjoy this project?



Madis Kaal wrote 10/24/2017 at 23:22 point

I've branched the code and added VT100 emulation, see

  Are you sure? yes | no

Just4Fun wrote 02/11/2018 at 12:48 point

Interesting. I was searching an easy to build VGA terminal for the Z80-MBC ( May be I'll give it a try...

  Are you sure? yes | no

K.C. Lee wrote 04/28/2016 at 16:26 point

>Hackaday Prize - please respond to get your seed money

Thanks for all the "likes".  I didn't individually thank you because I don't want to spam people's profiles.  

The tax man completely wiped my bank account earlier this week, this helps a bit on my future projects this year.  Feels like a cat trapped in a box  to have money and yet no money at the same time.

  Are you sure? yes | no

Yann Guidon / YGDES wrote 04/28/2016 at 23:27 point

well I believe you deserve the bucks. You are a damn smart man who does cool thing so I'm happy you get a fair share :-)

  Are you sure? yes | no

Montassar wrote 03/22/2016 at 14:42 point

There is this board on ebay that uses the same MCU and is quiet similar to what you have in here.. it's super small , I got it today from France ,, why not use it ??

  Are you sure? yes | no

K.C. Lee wrote 03/22/2016 at 15:00 point

I started off with a breakout board (green in my prototype) that I designed and built in here:

and I add parts around it.  i.e. Video buffer, 25MHz crystal, 5V tolerant circuit on a breadboard.  There are the parts that you don't get on your MCU board.  

Now I converted my design to a module.

  Are you sure? yes | no

esot.eric wrote 03/23/2016 at 01:40 point

Hah, I think I see the power-connector removed from an old floppy-drive...?

  Are you sure? yes | no

K.C. Lee wrote 03/23/2016 at 01:46 point

That's same type - I have standardize it on my designs for SWD hardware debugging.

  Are you sure? yes | no

K.C. Lee wrote 03/22/2016 at 15:50 point

FYI: My little green PCB has a BOM of less than $1 in QTY 10 including PCB vs $6 each for the MCU board.

  Are you sure? yes | no

laurens.weyn wrote 03/21/2016 at 07:16 point

There were lots of times where something like this could've been useful in some of my projects, nice to see you're making your own!

If you have some spare memory, perhaps you can also have a background and foreground colour for each character (say, 4 bit background + 4 bit foreground = 1 extra byte per character) which can be used to make BIOS-like UI interfaces, or colour coded output for errors and debug info etc.

  Are you sure? yes | no

K.C. Lee wrote 03/21/2016 at 12:25 point

This is a tall order for a $0.44 chip.  You are asking for features on a $10 chip.

- Each bit requires bandwidth to shift out.  Available total bandwidth is 25Mbps on 1 SPI.  You are asking for 100Mbps  - that needs a 4-bit SDIO interface. 
- There is 4K RAM on the device. Right now I only have 800+ bytes left.  
- The rendering routine alone uses 50% of CPU for monochrome.

The 4-bit idea was explored here.  It was actually what started this project  I quickly realize that it wasn't thhe direction.  is the project you want.  It would be silly for me to make a more expensive model considering that RPi0 exists at $5 (if you can find one).

  Are you sure? yes | no

Ishiyakazuo wrote 03/16/2016 at 10:44 point

This is pretty awesome.  I could seriously make use of these at work all day long.

One question -- I know that green is all the rage for terminals (for simplicity's sake), but have you looked at what it would take to have a B/W output on VGA?  It'd need to be able to drive 3x the current and you'd need to assume you've got what amounts to a 25 ohm resistor to GND in the sink side, but it would be a cool option.

I'm also wondering what would need to be done in order to make this drive a TTL LCD panel directly.  I think some 800x480 TFTs would be able to deal with only receiving 640 active video clocks if there's a way to send the data enable signal during the active video period.  Who knows what will end up in the remaining 160 pixels, but that's when you cover up a part of the LCD with a box. ;)  If this could directly drive one of those, you could make a very tiny terminal in a box.  I'd DEFINITELY have uses for that.

  Are you sure? yes | no

K.C. Lee wrote 03/16/2016 at 13:24 point

I have looked at that getting the extra drive current on my first project log:

Each video inputs needs 0.7V/75R, so roughly 30mA for white.  Haven't built it yet. 

White on blue - latest project log:

Getting black on white (or grey) needs to invert the background colour.  Actually was thinking about that just prior to answer the question.  Probably easiest to just use a MUX for that and just jumper the logic levels for the background/foreground colours - using the video out as select, active signal as '1'.  A 74LVC157 Quad mux have 4 channels and each can handle 24mA.  I even have those in my parts bin.

As to driving LCD, some of those panels are splitted into 2 halves and/or LVDS.  Haven't looked into it as I don't have one.  A wide aspect ratio also mean that the dot clock rate is going to be higher as with the same horizontal sync rate, each pixels gets a smaller amount of time slots *whether or not* you feed it valid pixel into.  

This microcontroller is wrong part for it.  There are always those LCD to VGA boards from China.  It is outside the scope for keeping the cost low.  Hey you can get a RPi 0 and do that.  

I am in Canada just next door to US, but can't get walk into a local store and get those at the $5 prices.  :(

  Are you sure? yes | no

Ishiyakazuo wrote 03/16/2016 at 20:31 point

Oops!  I saw that project log but didn't notice the comment in there about the separate resistors for R, G, B.  And I probably saw the white on blue one as well, come to think of it... wow.  OK, glad you've got all of that covered ;)

Actually, the TTL LCDs (which run about $10 each, but also need some bizarre power supplies) don't need a very high dot clock for a good resolution.  It will just decrease the refresh rate.  You could definitely drive one with a 25MHz clock (I do exactly that in one of my designs I've done -- it only refreshes at about 45Hz rather than 60, but it looks just fine).  You're probably right that just the power supplies needed alone would double or triple the BOM cost, unfortunately.

Anyway, I'm very impressed with this, and I'd love to try one of these out when possible.  Keep up the good work!

P.S. - My local store (which is about 30km away) that carries RPis has been pretty much out of stock of the Zero since its release, and when they do have them in, they're first-come-first-serve and sell out immediately, so don't feel too bad.

  Are you sure? yes | no

K.C. Lee wrote 03/16/2016 at 22:30 point

BTW there is one thing I want to do with green on black... Coding it right now.  I am surprise at how easy it is.  I'll make a log when it is done.

  Are you sure? yes | no

esot.eric wrote 03/22/2016 at 06:03 point

haha, many LCDs I've worked with (LVDS-even) can handle refresh-rates of as low as 1-2Hz... TFT's amazing :)

  Are you sure? yes | no

Tom Van den Bon wrote 03/15/2016 at 08:27 point

Wow, this is awesome and very handy. Can't wait to get my hands on some code to play with this :) I love these stm32f0 tssop 20 packages. I've been using them in various projects. I'll be following this closely :)

  Are you sure? yes | no

K.C. Lee wrote 03/15/2016 at 11:50 point

Thanks.  I am still getting used to this chip and writing bare metal code.  I'll try to release something when I done more debugging and more coding done.

Right now the code is for Keil C.  I am tempted to port it to GCC at some later point.

  Are you sure? yes | no

Tom Van den Bon wrote 03/15/2016 at 12:03 point

Cool, looking forward to it. I'm willing to help with the testing as well (I have the necessary hardware), so feel free to let me know if you need someone to test for you ;)

  Are you sure? yes | no

Tom Van den Bon wrote 03/21/2016 at 04:46 point

btw. Keil has released a free to use license for the stm32l0 and stm32f0

  Are you sure? yes | no

K.C. Lee wrote 03/21/2016 at 05:05 point

Very nice about the license.  I hear about it previously, but couldn't find details.
The STM32F0 part is small enough to fit the eval,  but the eval license limits a few things.
Thank you for this.

  Are you sure? yes | no

Tom Van den Bon wrote 03/21/2016 at 13:25 point

hehe, cool. Just wanted to make sure you didn't feel you had to have a gcc version before you can release something I can play with ;

  Are you sure? yes | no

K.C. Lee wrote 03/21/2016 at 23:31 point

Matrix Clock source code on github along with latest schematic/board/3D model

The VGA Text code is still a shooting target as there as things I want to add etc. The Matrix code is working but a bit rough on the edges.  It uses Vertical Blanking instead of the RTC (wasn't priority), so time is a bit off.

Go have fun playing with the code.  I have compiled with the version 5.18.  The code is for 25MHz crystal.  You can change the multiplier or use the internal RC via a #define in vga-min.h - a bit jittery but might add to the cheap Matrix effects.  :)

  Are you sure? yes | no

Tom Van den Bon wrote 03/23/2016 at 13:15 point

Awesome! Thanks. Definitely playing with it this weekend :)

  Are you sure? yes | no

K.C. Lee wrote 03/05/2016 at 05:37 point

Actually this uses something similar to your buffer idea except the rendering is similar to VGA text mode in a PC.  Each character from  the text buffer is rendered by looking it up in the font table and copy into the scan line buffer on the fly.  Unfortunately there isn't enough memory for more than a handful of scanlines.

My gut feelings tells me it would be very close. I don't know for sure until all the pieces are in as the setup code overheads, IRQ or DMA might uses up additional cycles.  Not knowing ahead of time whether it would work or not is part of the fun.

The SPI RAM version would work as the rendering is done only once during vertical retrace and stored in the external SPI RAM which has enough storage for the entire frame.

  Are you sure? yes | no

esot.eric wrote 03/05/2016 at 04:20 point

Cool, limit-pushing is kinda fun... One thing I did for a low-memory video project was a "row-segment-buffer" wherein instead of storing a full-on framebuffer, instead store segments of information like (white, 1 pixel) and (black, 20 pixels). 'course that was recalculated each time, otherwise it could be quite complicated to change a pixel here or there. And, as far as text is concerned, it uses way fewer "segments" to do it vertically rather'n horizontally... I just rotated the display 90 degrees ;)

Yeah, maybe a realtime bitmap-lookup is a better idea for text...

  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