ESP8266 Cheap Heads Up Display – C.H.U.D. II
This post is a branch from the earlier ESP32 based heads up display. Instead of using the frame buffer as a canvas to write text and variables or draw graphics, this post will use NTSC encoding.
See this Wikipedia link for more details about NTSC, https://en.wikipedia.org/wiki/NTSC. The code used in this post is a deviation from Matthias Goebl’s Github repo, https://github.com/matgoebl/esp8266-ntsc-c64-emulator with credit given to Jan Ostman, https://www.hackster.io/janost. I’m grateful to both of them for providing the foundation for what is presented here.
Most all other examples of NTSC or PAL use the Arduino Uno or ESP32. The Uno required 2 pins whereas the ESP32 used a pin not available on the ESP8266. Yes, ChatGPT was used in the effort because most every other human has abandoned developing on the ESP8266. I have a few hundred of these devices, so it’s a viable effort for me at least. This video will provide some background for anyone curious about the topic of NTSC, the basis of Analog FPV.
The specific hardware behind this project is the ESP8266 Wemos D1 mini clone. The module uses only one pin for the video out feed using the UART RX GPIO3 Pin. It is fed through a series of resisters with a tap that feeds a filtering capacitor out to the video input of the NTSC compatible device. There is an additional capacitor to filter high frequency noise to ground.
I also used the RunCam Mini FPV DVR to record the NTSC feed from the ESP8266, https://shop.runcam.com/runcam-mini-fpv-dvr/. I used another RunCam for the background video.
The code was compiled using the Arduino IDE with these board settings, LOLIN(WEMOS) D1 mini (clone), 4MB (FS:none OTA:~1019KB). The bulk of the code uses native libraries from Jan’s code, which was available here https://hacksterio.s3.amazonaws.com/uploads/attachments/650768/ESP8266-NTSC-C64.zip. This list indicates the code changes made.
file – cpu.c
I had to add change this line, prog_uchar BIOS[16384] PROGMEM = {
to this, const unsigned char BIOS[16384] PROGMEM = {
These files remained unchanged.
dmsstuff.h
generate_video.c
pin_mux_register.h
slc_register.h
Finding the character ROM was a real joy, not really.
There was a lot of trial and error only to find that it was staring me in the face. All I had to do was count from the upper left corner to the right and down.
See this site https://cbmsteve.ca/cbmchr/index.html, much thanks to Steve J. Gray for bringing back memories of simpler times. Once I had the binary ROM file, I used this bash and python script to extract the array.
Bash
#!/bin/sh python 2-generate_petscii.py 901439-08.bin > 901439-08_petscii_font.h exit
Python
#!/usr/bin/env python3 import sys def main(rom_file): data = open(rom_file, "rb").read(2048) print("// Generated PETSCII font bitmap (256 chars × 8 bytes each)") print("static const uint8_t petscii_font[256][8] = {") for i in range(256): chunk = data[i*8:(i+1)*8] hexes = ",".join(f"0x{b:02X}" for b in chunk) print(f" /* 0x{i:02X} */ {{{hexes}}},") print("};") if __name__ == "__main__": if len(sys.argv) != 2: print("Usage: generate_petscii.py <chargen.bin>") sys.exit(1) main(sys.argv[1])
Creating the example video above was from some FFMpeg commands to take video from an action camera, crop it, and layer the NTSC video on top.
Bash
#!/bin/sh # Crop the Runcam 1080p video to 480p ffmpeg -i 20230205_133707_01.MP4 -filter:v "crop=640:480" 20230205_133707_01_cropped.MP4 # Alpha channel the Runcam 480p video and layer both ffmpeg -i 20230205_133707_01_cropped.MP4 -i HUD-Demo.AVI -filter_complex \ "[1:v]colorkey=0x000000:0.3:0.1[ckout]; \ [0:v][ckout]overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2" \ -t 60 ESP8266 Wemos NTSC HUD-Demo Overlay.mp4 exit
Here is the Arduino code used in the H.U.D. example video above. It uses counters for the changing values. This is a demo of the NTSC feed, not interfacing sensors with the microcontroller and displaying those values. I’ll defer to the audience to move forward in that regard.
#include <Arduino.h> #define FREQUENCY 160 extern "C" { #include "user_interface.h" void exec6502(int32_t tickcount); void reset6502(); } static int z = 0; const char* text; int i; int RunOnce = 0; // CBM Textset1 ROM - see https://cbmsteve.ca/cbmchr/index.html unsigned char charROM [256][8] = { /* 0x00 */ {0x1C,0x22,0x4A,0x56,0x4C,0x20,0x1E,0x00}, /* 0x01 */ {0x18,0x24,0x42,0x7E,0x42,0x42,0x42,0x00}, /* 0x02 */ {0x7C,0x22,0x22,0x3C,0x22,0x22,0x7C,0x00}, /* 0x03 */ {0x1C,0x22,0x40,0x40,0x40,0x22,0x1C,0x00}, /* 0x04 */ {0x78,0x24,0x22,0x22,0x22,0x24,0x78,0x00}, /* 0x05 */ {0x7E,0x40,0x40,0x78,0x40,0x40,0x7E,0x00}, /* 0x06 */ {0x7E,0x40,0x40,0x78,0x40,0x40,0x40,0x00}, /* 0x07 */ {0x1C,0x22,0x40,0x4E,0x42,0x22,0x1C,0x00}, /* 0x08 */ {0x42,0x42,0x42,0x7E,0x42,0x42,0x42,0x00}, /* 0x09 */ {0x1C,0x08,0x08,0x08,0x08,0x08,0x1C,0x00}, /* 0x0A */ {0x0E,0x04,0x04,0x04,0x04,0x44,0x38,0x00}, /* 0x0B */ {0x42,0x44,0x48,0x70,0x48,0x44,0x42,0x00}, /* 0x0C */ {0x40,0x40,0x40,0x40,0x40,0x40,0x7E,0x00}, /* 0x0D */ {0x42,0x66,0x5A,0x5A,0x42,0x42,0x42,0x00}, /* 0x0E */ {0x42,0x62,0x52,0x4A,0x46,0x42,0x42,0x00}, /* 0x0F */ {0x18,0x24,0x42,0x42,0x42,0x24,0x18,0x00}, /* 0x10 */ {0x7C,0x42,0x42,0x7C,0x40,0x40,0x40,0x00}, /* 0x11 */ {0x18,0x24,0x42,0x42,0x4A,0x24,0x1A,0x00}, /* 0x12 */ {0x7C,0x42,0x42,0x7C,0x48,0x44,0x42,0x00}, /* 0x13 */ {0x3C,0x42,0x40,0x3C,0x02,0x42,0x3C,0x00}, /* 0x14 */ {0x3E,0x08,0x08,0x08,0x08,0x08,0x08,0x00}, /* 0x15 */ {0x42,0x42,0x42,0x42,0x42,0x42,0x3C,0x00}, /* 0x16 */ {0x42,0x42,0x42,0x24,0x24,0x18,0x18,0x00}, /* 0x17 */ {0x42,0x42,0x42,0x5A,0x5A,0x66,0x42,0x00}, /* 0x18 */ {0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x00}, /* 0x19 */ {0x22,0x22,0x22,0x1C,0x08,0x08,0x08,0x00}, /* 0x1A */ {0x7E,0x02,0x04,0x18,0x20,0x40,0x7E,0x00}, /* 0x1B */ {0x3C,0x20,0x20,0x20,0x20,0x20,0x3C,0x00}, /* 0x1C */ {0x00,0x40,0x20,0x10,0x08,0x04,0x02,0x00}, /* 0x1D */ {0x3C,0x04,0x04,0x04,0x04,0x04,0x3C,0x00}, /* 0x1E */ {0x00,0x08,0x1C,0x2A,0x08,0x08,0x08,0x08}, /* 0x1F */ {0x00,0x00,0x10,0x20,0x7F,0x20,0x10,0x00}, /* 0x20 */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* 0x21 */ {0x08,0x08,0x08,0x08,0x00,0x00,0x08,0x00}, /* 0x22 */ {0x24,0x24,0x24,0x00,0x00,0x00,0x00,0x00}, /* 0x23 */ {0x24,0x24,0x7E,0x24,0x7E,0x24,0x24,0x00}, /* 0x24 */ {0x08,0x1E,0x28,0x1C,0x0A,0x3C,0x08,0x00}, /* 0x25 */ {0x00,0x62,0x64,0x08,0x10,0x26,0x46,0x00}, /* 0x26 */ {0x30,0x48,0x48,0x30,0x4A,0x44,0x3A,0x00}, /* 0x27 */ {0x04,0x08,0x10,0x00,0x00,0x00,0x00,0x00}, /* 0x28 */ {0x04,0x08,0x10,0x10,0x10,0x08,0x04,0x00}, /* 0x29 */ {0x20,0x10,0x08,0x08,0x08,0x10,0x20,0x00}, /* 0x2A */ {0x08,0x2A,0x1C,0x3E,0x1C,0x2A,0x08,0x00}, /* 0x2B */ {0x00,0x08,0x08,0x3E,0x08,0x08,0x00,0x00}, /* 0x2C */ {0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x10}, /* 0x2D */ {0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00}, /* 0x2E */ {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00}, /* 0x2F */ {0x00,0x02,0x04,0x08,0x10,0x20,0x40,0x00}, /* 0x30 */ {0x3C,0x42,0x46,0x5A,0x62,0x42,0x3C,0x00}, /* 0x31 */ {0x08,0x18,0x28,0x08,0x08,0x08,0x3E,0x00}, /* 0x32 */ {0x3C,0x42,0x02,0x0C,0x30,0x40,0x7E,0x00}, /* 0x33 */ {0x3C,0x42,0x02,0x1C,0x02,0x42,0x3C,0x00}, /* 0x34 */ {0x04,0x0C,0x14,0x24,0x7E,0x04,0x04,0x00}, /* 0x35 */ {0x7E,0x40,0x78,0x04,0x02,0x44,0x38,0x00}, /* 0x36 */ {0x1C,0x20,0x40,0x7C,0x42,0x42,0x3C,0x00}, /* 0x37 */ {0x7E,0x42,0x04,0x08,0x10,0x10,0x10,0x00}, /* 0x38 */ {0x3C,0x42,0x42,0x3C,0x42,0x42,0x3C,0x00}, /* 0x39 */ {0x3C,0x42,0x42,0x3E,0x02,0x04,0x38,0x00}, /* 0x3A */ {0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00}, /* 0x3B */ {0x00,0x00,0x08,0x00,0x00,0x08,0x08,0x10}, /* 0x3C */ {0x0E,0x18,0x30,0x60,0x30,0x18,0x0E,0x00}, /* 0x3D */ {0x00,0x00,0x7E,0x00,0x7E,0x00,0x00,0x00}, /* 0x3E */ {0x70,0x18,0x0C,0x06,0x0C,0x18,0x70,0x00}, /* 0x3F */ {0x3C,0x42,0x02,0x0C,0x10,0x00,0x10,0x00}, /* 0x40 */ {0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00}, /* 0x41 */ {0x08,0x1C,0x3E,0x7F,0x7F,0x1C,0x3E,0x00}, /* 0x42 */ {0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10}, /* 0x43 */ {0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00}, /* 0x44 */ {0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00}, /* 0x45 */ {0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00}, /* 0x46 */ {0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00}, /* 0x47 */ {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20}, /* 0x48 */ {0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04}, /* 0x49 */ {0x00,0x00,0x00,0x00,0xE0,0x10,0x08,0x08}, /* 0x4A */ {0x08,0x08,0x08,0x04,0x03,0x00,0x00,0x00}, /* 0x4B */ {0x08,0x08,0x08,0x10,0xE0,0x00,0x00,0x00}, /* 0x4C */ {0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xFF}, /* 0x4D */ {0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01}, /* 0x4E */ {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}, /* 0x4F */ {0xFF,0x80,0x80,0x80,0x80,0x80,0x80,0x80}, /* 0x50 */ {0xFF,0x01,0x01,0x01,0x01,0x01,0x01,0x01}, /* 0x51 */ {0x00,0x3C,0x7E,0x7E,0x7E,0x7E,0x3C,0x00}, /* 0x52 */ {0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00}, /* 0x53 */ {0x36,0x7F,0x7F,0x7F,0x3E,0x1C,0x08,0x00}, /* 0x54 */ {0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40}, /* 0x55 */ {0x00,0x00,0x00,0x00,0x03,0x04,0x08,0x08}, /* 0x56 */ {0x81,0x42,0x24,0x18,0x18,0x24,0x42,0x81}, /* 0x57 */ {0x00,0x3C,0x42,0x42,0x42,0x42,0x3C,0x00}, /* 0x58 */ {0x08,0x1C,0x2A,0x77,0x2A,0x08,0x08,0x00}, /* 0x59 */ {0x02,0x02,0x02,0x02,0x02,0x02,0x02,0x02}, /* 0x5A */ {0x08,0x1C,0x3E,0x7F,0x3E,0x1C,0x08,0x00}, /* 0x5B */ {0x08,0x08,0x08,0x08,0xFF,0x08,0x08,0x08}, /* 0x5C */ {0xA0,0x50,0xA0,0x50,0xA0,0x50,0xA0,0x50}, /* 0x5D */ {0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08}, /* 0x5E */ {0x00,0x00,0x01,0x3E,0x54,0x14,0x14,0x00}, /* 0x5F */ {0xFF,0x7F,0x3F,0x1F,0x0F,0x07,0x03,0x01}, /* 0x60 */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* 0x61 */ {0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0}, /* 0x62 */ {0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF}, /* 0x63 */ {0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* 0x64 */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF}, /* 0x65 */ {0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80}, /* 0x66 */ {0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55}, /* 0x67 */ {0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01}, /* 0x68 */ {0x00,0x00,0x00,0x00,0xAA,0x55,0xAA,0x55}, /* 0x69 */ {0xFF,0xFE,0xFC,0xF8,0xF0,0xE0,0xC0,0x80}, /* 0x6A */ {0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03}, /* 0x6B */ {0x08,0x08,0x08,0x08,0x0F,0x08,0x08,0x08}, /* 0x6C */ {0x00,0x00,0x00,0x00,0x0F,0x0F,0x0F,0x0F}, /* 0x6D */ {0x08,0x08,0x08,0x08,0x0F,0x00,0x00,0x00}, /* 0x6E */ {0x00,0x00,0x00,0x00,0xF8,0x08,0x08,0x08}, /* 0x6F */ {0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF}, /* 0x70 */ {0x00,0x00,0x00,0x00,0x0F,0x08,0x08,0x08}, /* 0x71 */ {0x08,0x08,0x08,0x08,0xFF,0x00,0x00,0x00}, /* 0x72 */ {0x00,0x00,0x00,0x00,0xFF,0x08,0x08,0x08}, /* 0x73 */ {0x08,0x08,0x08,0x08,0xF8,0x08,0x08,0x08}, /* 0x74 */ {0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0}, /* 0x75 */ {0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0}, /* 0x76 */ {0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07}, /* 0x77 */ {0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00}, /* 0x78 */ {0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00}, /* 0x79 */ {0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF}, /* 0x7A */ {0x01,0x01,0x01,0x01,0x01,0x01,0x01,0xFF}, /* 0x7B */ {0x00,0x00,0x00,0x00,0xF0,0xF0,0xF0,0xF0}, /* 0x7C */ {0x0F,0x0F,0x0F,0x0F,0x00,0x00,0x00,0x00}, /* 0x7D */ {0x08,0x08,0x08,0x08,0xF8,0x00,0x00,0x00}, /* 0x7E */ {0xF0,0xF0,0xF0,0xF0,0x00,0x00,0x00,0x00}, /* 0x7F */ {0xF0,0xF0,0xF0,0xF0,0x0F,0x0F,0x0F,0x0F}, /* 0x80 */ {0x1C,0x22,0x4A,0x56,0x4C,0x20,0x1E,0x00}, /* 0x81 */ {0x18,0x24,0x42,0x7E,0x42,0x42,0x42,0x00}, /* 0x82 */ {0x7C,0x22,0x22,0x3C,0x22,0x22,0x7C,0x00}, /* 0x83 */ {0x1C,0x22,0x40,0x40,0x40,0x22,0x1C,0x00}, /* 0x84 */ {0x78,0x24,0x22,0x22,0x22,0x24,0x78,0x00}, /* 0x85 */ {0x7E,0x40,0x40,0x78,0x40,0x40,0x7E,0x00}, /* 0x86 */ {0x7E,0x40,0x40,0x78,0x40,0x40,0x40,0x00}, /* 0x87 */ {0x1C,0x22,0x40,0x4E,0x42,0x22,0x1C,0x00}, /* 0x88 */ {0x42,0x42,0x42,0x7E,0x42,0x42,0x42,0x00}, /* 0x89 */ {0x1C,0x08,0x08,0x08,0x08,0x08,0x1C,0x00}, /* 0x8A */ {0x0E,0x04,0x04,0x04,0x04,0x44,0x38,0x00}, /* 0x8B */ {0x42,0x44,0x48,0x70,0x48,0x44,0x42,0x00}, /* 0x8C */ {0x40,0x40,0x40,0x40,0x40,0x40,0x7E,0x00}, /* 0x8D */ {0x42,0x66,0x5A,0x5A,0x42,0x42,0x42,0x00}, /* 0x8E */ {0x42,0x62,0x52,0x4A,0x46,0x42,0x42,0x00}, /* 0x8F */ {0x18,0x24,0x42,0x42,0x42,0x24,0x18,0x00}, /* 0x90 */ {0x7C,0x42,0x42,0x7C,0x40,0x40,0x40,0x00}, /* 0x91 */ {0x18,0x24,0x42,0x42,0x4A,0x24,0x1A,0x00}, /* 0x92 */ {0x7C,0x42,0x42,0x7C,0x48,0x44,0x42,0x00}, /* 0x93 */ {0x3C,0x42,0x40,0x3C,0x02,0x42,0x3C,0x00}, /* 0x94 */ {0x3E,0x08,0x08,0x08,0x08,0x08,0x08,0x00}, /* 0x95 */ {0x42,0x42,0x42,0x42,0x42,0x42,0x3C,0x00}, /* 0x96 */ {0x42,0x42,0x42,0x24,0x24,0x18,0x18,0x00}, /* 0x97 */ {0x42,0x42,0x42,0x5A,0x5A,0x66,0x42,0x00}, /* 0x98 */ {0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x00}, /* 0x99 */ {0x22,0x22,0x22,0x1C,0x08,0x08,0x08,0x00}, /* 0x9A */ {0x7E,0x02,0x04,0x18,0x20,0x40,0x7E,0x00}, /* 0x9B */ {0x3C,0x20,0x20,0x20,0x20,0x20,0x3C,0x00}, /* 0x9C */ {0x00,0x40,0x20,0x10,0x08,0x04,0x02,0x00}, /* 0x9D */ {0x3C,0x04,0x04,0x04,0x04,0x04,0x3C,0x00}, /* 0x9E */ {0x00,0x08,0x1C,0x2A,0x08,0x08,0x08,0x08}, /* 0x9F */ {0x00,0x00,0x10,0x20,0x7F,0x20,0x10,0x00}, /* 0xA0 */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* 0xA1 */ {0x08,0x08,0x08,0x08,0x00,0x00,0x08,0x00}, /* 0xA2 */ {0x24,0x24,0x24,0x00,0x00,0x00,0x00,0x00}, /* 0xA3 */ {0x24,0x24,0x7E,0x24,0x7E,0x24,0x24,0x00}, /* 0xA4 */ {0x08,0x1E,0x28,0x1C,0x0A,0x3C,0x08,0x00}, /* 0xA5 */ {0x00,0x62,0x64,0x08,0x10,0x26,0x46,0x00}, /* 0xA6 */ {0x30,0x48,0x48,0x30,0x4A,0x44,0x3A,0x00}, /* 0xA7 */ {0x04,0x08,0x10,0x00,0x00,0x00,0x00,0x00}, /* 0xA8 */ {0x04,0x08,0x10,0x10,0x10,0x08,0x04,0x00}, /* 0xA9 */ {0x20,0x10,0x08,0x08,0x08,0x10,0x20,0x00}, /* 0xAA */ {0x08,0x2A,0x1C,0x3E,0x1C,0x2A,0x08,0x00}, /* 0xAB */ {0x00,0x08,0x08,0x3E,0x08,0x08,0x00,0x00}, /* 0xAC */ {0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x10}, /* 0xAD */ {0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00}, /* 0xAE */ {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00}, /* 0xAF */ {0x00,0x02,0x04,0x08,0x10,0x20,0x40,0x00}, /* 0xB0 */ {0x3C,0x42,0x46,0x5A,0x62,0x42,0x3C,0x00}, /* 0xB1 */ {0x08,0x18,0x28,0x08,0x08,0x08,0x3E,0x00}, /* 0xB2 */ {0x3C,0x42,0x02,0x0C,0x30,0x40,0x7E,0x00}, /* 0xB3 */ {0x3C,0x42,0x02,0x1C,0x02,0x42,0x3C,0x00}, /* 0xB4 */ {0x04,0x0C,0x14,0x24,0x7E,0x04,0x04,0x00}, /* 0xB5 */ {0x7E,0x40,0x78,0x04,0x02,0x44,0x38,0x00}, /* 0xB6 */ {0x1C,0x20,0x40,0x7C,0x42,0x42,0x3C,0x00}, /* 0xB7 */ {0x7E,0x42,0x04,0x08,0x10,0x10,0x10,0x00}, /* 0xB8 */ {0x3C,0x42,0x42,0x3C,0x42,0x42,0x3C,0x00}, /* 0xB9 */ {0x3C,0x42,0x42,0x3E,0x02,0x04,0x38,0x00}, /* 0xBA */ {0x00,0x00,0x08,0x00,0x00,0x08,0x00,0x00}, /* 0xBB */ {0x00,0x00,0x08,0x00,0x00,0x08,0x08,0x10}, /* 0xBC */ {0x0E,0x18,0x30,0x60,0x30,0x18,0x0E,0x00}, /* 0xBD */ {0x00,0x00,0x7E,0x00,0x7E,0x00,0x00,0x00}, /* 0xBE */ {0x70,0x18,0x0C,0x06,0x0C,0x18,0x70,0x00}, /* 0xBF */ {0x3C,0x42,0x02,0x0C,0x10,0x00,0x10,0x00}, /* 0xC0 */ {0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00}, /* 0xC1 */ {0x00,0x00,0x38,0x04,0x3C,0x44,0x3A,0x00}, /* 0xC2 */ {0x40,0x40,0x5C,0x62,0x42,0x62,0x5C,0x00}, /* 0xC3 */ {0x00,0x00,0x3C,0x42,0x40,0x42,0x3C,0x00}, /* 0xC4 */ {0x02,0x02,0x3A,0x46,0x42,0x46,0x3A,0x00}, /* 0xC5 */ {0x00,0x00,0x3C,0x42,0x7E,0x40,0x3C,0x00}, /* 0xC6 */ {0x0C,0x12,0x10,0x7C,0x10,0x10,0x10,0x00}, /* 0xC7 */ {0x00,0x00,0x3A,0x46,0x46,0x3A,0x02,0x3C}, /* 0xC8 */ {0x40,0x40,0x5C,0x62,0x42,0x42,0x42,0x00}, /* 0xC9 */ {0x08,0x00,0x18,0x08,0x08,0x08,0x1C,0x00}, /* 0xCA */ {0x04,0x00,0x0C,0x04,0x04,0x04,0x44,0x38}, /* 0xCB */ {0x40,0x40,0x44,0x48,0x50,0x68,0x44,0x00}, /* 0xCC */ {0x18,0x08,0x08,0x08,0x08,0x08,0x1C,0x00}, /* 0xCD */ {0x00,0x00,0x76,0x49,0x49,0x49,0x49,0x00}, /* 0xCE */ {0x00,0x00,0x5C,0x62,0x42,0x42,0x42,0x00}, /* 0xCF */ {0x00,0x00,0x3C,0x42,0x42,0x42,0x3C,0x00}, /* 0xD0 */ {0x00,0x00,0x5C,0x62,0x62,0x5C,0x40,0x40}, /* 0xD1 */ {0x00,0x00,0x3A,0x46,0x46,0x3A,0x02,0x02}, /* 0xD2 */ {0x00,0x00,0x5C,0x62,0x40,0x40,0x40,0x00}, /* 0xD3 */ {0x00,0x00,0x3E,0x40,0x3C,0x02,0x7C,0x00}, /* 0xD4 */ {0x10,0x10,0x7C,0x10,0x10,0x12,0x0C,0x00}, /* 0xD5 */ {0x00,0x00,0x42,0x42,0x42,0x46,0x3A,0x00}, /* 0xD6 */ {0x00,0x00,0x42,0x42,0x42,0x24,0x18,0x00}, /* 0xD7 */ {0x00,0x00,0x41,0x49,0x49,0x49,0x36,0x00}, /* 0xD8 */ {0x00,0x00,0x42,0x24,0x18,0x24,0x42,0x00}, /* 0xD9 */ {0x00,0x00,0x42,0x42,0x46,0x3A,0x02,0x3C}, /* 0xDA */ {0x00,0x00,0x7E,0x04,0x18,0x20,0x7E,0x00}, /* 0xDB */ {0x08,0x08,0x08,0x08,0xFF,0x08,0x08,0x08}, /* 0xDC */ {0xA0,0x50,0xA0,0x50,0xA0,0x50,0xA0,0x50}, /* 0xDD */ {0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08}, /* 0xDE */ {0xCC,0xCC,0x33,0x33,0xCC,0xCC,0x33,0x33}, /* 0xDF */ {0xCC,0x66,0x33,0x99,0xCC,0x66,0x33,0x99}, /* 0xE0 */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* 0xE1 */ {0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0}, /* 0xE2 */ {0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF}, /* 0xE3 */ {0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* 0xE4 */ {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF}, /* 0xE5 */ {0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80}, /* 0xE6 */ {0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55}, /* 0xE7 */ {0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01}, /* 0xE8 */ {0x00,0x00,0x00,0x00,0xAA,0x55,0xAA,0x55}, /* 0xE9 */ {0x99,0x33,0x66,0xCC,0x99,0x33,0x66,0xCC}, /* 0xEA */ {0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03}, /* 0xEB */ {0x08,0x08,0x08,0x08,0x0F,0x08,0x08,0x08}, /* 0xEC */ {0x00,0x00,0x00,0x00,0x0F,0x0F,0x0F,0x0F}, /* 0xED */ {0x08,0x08,0x08,0x08,0x0F,0x00,0x00,0x00}, /* 0xEE */ {0x00,0x00,0x00,0x00,0xF8,0x08,0x08,0x08}, /* 0xEF */ {0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF}, /* 0xF0 */ {0x00,0x00,0x00,0x00,0x0F,0x08,0x08,0x08}, /* 0xF1 */ {0x08,0x08,0x08,0x08,0xFF,0x00,0x00,0x00}, /* 0xF2 */ {0x00,0x00,0x00,0x00,0xFF,0x08,0x08,0x08}, /* 0xF3 */ {0x08,0x08,0x08,0x08,0xF8,0x08,0x08,0x08}, /* 0xF4 */ {0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0}, /* 0xF5 */ {0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0}, /* 0xF6 */ {0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07}, /* 0xF7 */ {0xFF,0xFF,0x00,0x00,0x00,0x00,0x00,0x00}, /* 0xF8 */ {0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0x00}, /* 0xF9 */ {0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF}, /* 0xFA */ {0x01,0x02,0x44,0x48,0x50,0x60,0x40,0x00}, /* 0xFB */ {0x00,0x00,0x00,0x00,0xF0,0xF0,0xF0,0xF0}, /* 0xFC */ {0x0F,0x0F,0x0F,0x0F,0x00,0x00,0x00,0x00}, /* 0xFD */ {0x08,0x08,0x08,0x08,0xF8,0x00,0x00,0x00}, /* 0xFE */ {0xF0,0xF0,0xF0,0xF0,0x00,0x00,0x00,0x00}, /* 0xFF */ {0xF0,0xF0,0xF0,0xF0,0x0F,0x0F,0x0F,0x0F}, }; uint8_t asciiToCharROM(char c) { switch (c) { // case '@': return 0; case 'A': return 1; case 'B': return 2; case 'C': return 3; case 'D': return 4; case 'E': return 5; case 'F': return 6; case 'G': return 7; case 'H': return 8; case 'I': return 9; case 'J': return 10; case 'K': return 11; case 'L': return 12; case 'M': return 13; case 'N': return 14; case 'O': return 15; case 'P': return 16; case 'Q': return 17; case 'R': return 18; case 'S': return 19; case 'T': return 20; case 'U': return 21; case 'V': return 22; case 'W': return 23; case 'X': return 24; case 'Y': return 25; case 'Z': return 26; case '[': return 27; case '\\': return 28; case ']': return 29; case '^': return 30; case ' ': return 32; case '!': return 33; case '“': return 34; case '#': return 35; // case '$': return 36; case '%': return 37; case '&': return 38; case '\'': return 39; case '(': return 40; case ')': return 41; case '*': return 42; case '+': return 43; case ',': return 44; case '-': return 45; case '.': return 46; case '/': return 47; case '0': return 48; case '1': return 49; case '2': return 50; case '3': return 51; case '4': return 52; case '5': return 53; case '6': return 54; case '7': return 55; case '8': return 56; case '9': return 57; case ':': return 58; // case ';': return 59; case '<': return 60; case '=': return 61; case '>': return 62; case '?': return 63; case '|': return 66; case ';': return 86; // Cross glyph case '$': return 87; // Circle glyph case '@': return 90; // Diamond glyph case 'a': return 193; case 'b': return 194; case 'c': return 195; case 'd': return 196; case 'e': return 197; case 'f': return 198; case 'g': return 199; case 'h': return 200; case 'i': return 201; case 'j': return 202; case 'k': return 203; case 'l': return 204; case 'm': return 205; case 'n': return 206; case 'o': return 207; case 'p': return 208; case 'q': return 209; case 'r': return 210; case 's': return 211; case 't': return 212; case 'u': return 213; case 'v': return 214; case 'w': return 215; case 'x': return 216; case 'y': return 217; case 'z': return 218; default: return 32; // fallback or space } } // Custom Glyphs - see, https://dotmatrixtool.com/# const uint_8 FriendGlyph[] = { 0x00, 0x22, 0x14, 0x08, 0x14, 0x22, 0x00, 0x00 }; const uint_8 FoeGlyph[8] = { { 0x08, 0x14, 0x22, 0x41, 0x22, 0x14, 0x08, 0x00 }; uint8_t screenmem[1000]; uint8_t colormem[1000]; uint8_t RAM[16384]; uint8_t BOARDER; uint8_t BGCOLOR; uint8_t VIC_D020; uint8_t VIC_D021; extern "C" { void videoinit(); } void Background() { RunOnce = 1; // Mark that it's been run // The formatting of the array is lost in this WordPress post, it is missing spaces. // You will need to ensure that there are 40 character slots for each line. // I suppose this is why Github is recommended. const char* messages[] = { " Lat 47.6795 Lon -122.3821 ", " ", " Alt XXX Spd YY ", " ", " - - ", " 20 - - 20 ", " - - ", " 10 - - 10 ", " - - ", " 0 -- -- 0 ", " - - ", " - - ", " - - ", " - - ", " - - ", " N E S ", " | | | | ^ | | | | ", " $ @ ", " ", " @ Hom 00.00 - HH:MM:SS.S ", " $ Way 10.51 N ", " ", " ESP8266-Wemos_NTSC-ChatGPT-Demo3 ", " ", " CloudACM July 28th 2025 ", }; const int numMessages = sizeof(messages) / sizeof(messages[0]); const int screenWidth = 40; // Clear screen once memset(screenmem, 32, 40 * 25); for (int m = 0; m < numMessages; m++) { const char* text = messages[m]; int len = strlen(text); int base = m * screenWidth; for (int i = 0; i < len && i < screenWidth; i++) { screenmem[base + i] = asciiToCharROM(text[i]); } } } void updateAltSpd(int altValue, int spdValue) { int base = 2 * 40; // base = x * 40, where x is the line number (top line is 0) for a 40 char line width // Format values into 3-character strings char altStr[4]; char spdStr[3]; snprintf(altStr, sizeof(altStr), "%3d", altValue); // Right-aligned snprintf(spdStr, sizeof(spdStr), "%2d", spdValue); // Write to screenmem at fixed positions screenmem[base + 6] = asciiToCharROM(altStr[0]); screenmem[base + 7] = asciiToCharROM(altStr[1]); screenmem[base + 8] = asciiToCharROM(altStr[2]); screenmem[base + 33] = asciiToCharROM(spdStr[0]); screenmem[base + 34] = asciiToCharROM(spdStr[1]); } void updateTime() { int base = 19 * 40; // Row 19 (zero-based), so actual 20th line // Get uptime in milliseconds unsigned long ms = millis(); unsigned long total_seconds = ms / 1000; unsigned long hours = (total_seconds / 3600) % 100; // Keep within 2-digit range unsigned long minutes = (total_seconds / 60) % 60; unsigned long seconds = total_seconds % 60; unsigned long tenths = (ms % 1000) / 100; // 0–9 (1 digit) // Format time as HH:MM:SS.S char timeStr[11]; // "HH:MM:SS.S" snprintf(timeStr, sizeof(timeStr), "%02lu:%02lu:%02lu.%1lu", hours, minutes, seconds, tenths); // Write to screenmem at fixed position: starts at column 27 for (int i = 0; i < 10; i++) { screenmem[base + 28 + i] = asciiToCharROM(timeStr[i]); } } void setup() { delay(1); system_update_cpu_freq(FREQUENCY); videoinit(); reset6502(); memset(screenmem, 32, 40 * 25); } void loop() { static int counterX = 0; static int counterY = 0; if (RunOnce == 0) { Background(); // Run only once at startup } else { updateAltSpd(counterX, counterY); updateTime(); counterX++; counterY += 2; if (counterX >= 1000) { counterX = 0; } if (counterY >= 100) { counterY = 0; } delay(50); // Control update rate } }
As always, be cautious of your surroundings when your head is in the C.H.U.D.