# Samsung serial VFD — ESPHome component An ESPHome external component for the Samsung 20L203 2-line × 20-character VFD (vacuum fluorescent display) pole display, driven over a single serial wire at 9600 baud. It keeps a copy of the screen in RAM and only sends data when something changes. Updates overwrite the screen in place instead of clearing it first, so the clock doesn't flicker.

Front of the display Back of the controller board

``` components/samsung_vfd/ the component (Python schema + C++) examples/samsung-vfd.yaml full example: clock + two Home Assistant text entities examples/secrets.yaml.example docs/ photos and the 20L203DA12 datasheet ``` ## Using it from your ESPHome server Add this to your device YAML. ESPHome fetches the component from git when it compiles. ```yaml external_components: - source: github://REPO_OWNER/REPO_NAME@main components: [samsung_vfd] refresh: 1d uart: id: uart_vfd tx_pin: GPIO17 baud_rate: 9600 time: # needed by the component (used by samsung_vfd.printf) - platform: sntp id: sntp_time samsung_vfd: id: vfd uart_id: uart_vfd ``` For a complete device, copy [examples/samsung-vfd.yaml](examples/samsung-vfd.yaml) into your ESPHome config folder. Add the keys from [examples/secrets.yaml.example](examples/secrets.yaml.example) to your `secrets.yaml`. ## Hardware The board in the photos is a 20L203DA5 (rev A). The included [datasheet](docs/20L203DA12-datasheet.pdf) is for the 20L203DA12, a variant with the same command set. ### Connector (3-pin Molex 5046-03A) | Pin | Signal | Connect to | |-----|--------|------------| | 1 | VCC | 5–24 V DC supply | | 2 | RXD | MAX3232 T1OUT (T1IN ← ESP32 GPIO17), see below | | 3 | GND | supply GND, MAX3232 GND **and** ESP32 GND | ### Power The display takes 5–24 V. At 5 V it draws about 0.8 A typical and 1.2 A maximum, and the inrush current at power-on can be up to five times that. A dev-board regulator can't supply this, so give the display its own supply, for example 12 V at 1 A, and connect the grounds together. ### Signal levels The RXD input goes through an RS-232 receiver: it expects ±3 to ±15 V and idles at the negative level (mark). It tolerates ±30 V, so a 3.3 V ESP32 pin can't damage it. There are two ways to connect it: - **Through a MAX3232 (recommended, and how the original build is wired).** Power the MAX3232 from the ESP32's 3.3 V, then wire GPIO17 → T1IN and T1OUT → display RXD. The MAX3232 produces real RS-232 levels and does the inversion itself, so use the plain `tx_pin: GPIO17`. - **Direct from the ESP32.** A plain TTL signal arrives inverted, so wire GPIO17 straight to RXD and invert the pin in the UART config: ```yaml uart: id: uart_vfd tx_pin: number: GPIO17 inverted: true baud_rate: 9600 ``` This relies on the receiver's switching threshold, which on most RS-232 receivers is around 1.3 V. It isn't guaranteed by the spec, which asks for at least 3 V. If the display shows garbage or nothing at all, the inversion setting is the first thing to check: off with a MAX3232, on without one. ### Jumpers The 2×8 pin header on the board selects the following. All jumpers open is the factory default. | Jumpers | Setting | Default (open) | |---------|---------|----------------| | 1, 2 | baud rate: 9600 / 4800 / 2400 / 19200 | 9600 | | 3, 4 | parity: none / odd / even | none | | 5 | self-test at power-on | off | | 6, 7, 8 | font for codes 0x80–0xFF | PC437 | This component expects 9600 baud and no parity, which ESPHome's UART uses by default. ## Configuration ```yaml samsung_vfd: id: vfd uart_id: uart_vfd cols: 20 # default 20 (1–80) rows: 2 # default 2 (1–8) send_newlines: false # default false; leave off for this display ``` The UART must be 9600 baud with a `tx_pin`. The component requires a `time:` component in the config. ## Actions | Action | Options | |--------|---------| | `samsung_vfd.print` | `text` (templatable). Optional `line:` (0-based) replaces one line. Without `line:`, the text is word-wrapped over the whole screen; `\n` forces a new line. Optional `centered: true`. | | `samsung_vfd.printf` | Shows the current time formatted with strftime, e.g. `format: "%H:%M:%S"`. Requires `time_id:`. Takes the same `line:` and `centered:` options. Does nothing until the clock has synced. | | `samsung_vfd.clear` | Clears the screen. | | `samsung_vfd.home` | Moves the cursor home. | ```yaml - samsung_vfd.print: id: vfd line: 0 text: !lambda 'return "Temp " + to_string((int) id(temp).state) + " C";' - samsung_vfd.printf: id: vfd time_id: sntp_time line: 1 centered: true format: "%a %d %b %H:%M" ``` From a lambda you can call `id(vfd).print(...)`, `print_line(line, text, centered)`, `clear()`, `home()` and `line_text(line)`. Text is converted to one byte per character. Accented Latin-1 letters lose their accent (é → e), other non-ASCII characters become `?`, and control characters become spaces. ## What the example config does - On boot: clears the screen, shows `Hello World!` for 3 s, then restores line 1. - Bottom line: a live clock, `%a %d %b %H:%M:%S`, updated every second (SNTP, Europe/Amsterdam). - Two Home Assistant `text` entities: - **VFD line 1** (max 20 chars) is shown on the top line and kept across reboots. - **VFD status** (max 40 chars) shows a full-screen, word-wrapped, centred banner for 10 s. The clock pauses meanwhile, then line 1 and the clock come back. - Wi-Fi with a captive-portal fallback AP, OTA updates and an encrypted native API. ## Protocol These are the commands the component uses. The datasheet, section 5.4, lists the rest, including cursor positioning, brightness, blinking and the annunciator triangles. | Bytes | Effect | |-------|--------| | `1B 40` (`ESC @`) | initialise: reset settings, clear, cursor home | | `0C` | clear screen, cursor home | | `0B` | cursor home (top-left) | | `20`–`FF` | print a character and move the cursor right | At power-on the display is in overwrite mode. After the last column of the top line the cursor continues on the bottom line, and after the last column of the bottom line it goes back to the top-left. Nothing scrolls. Each update therefore sends `0B` (home) followed by both lines, each padded to exactly 20 characters. ## Troubleshooting - **Garbage or nothing on the display:** toggle `inverted:` on the TX pin (see [Signal levels](#signal-levels)). Check that the baud jumpers 1 and 2 are open (9600) and that parity jumpers 3 and 4 are open (none). - **The display runs its self-test for about 20 s after power-on:** jumper 5 is shorted. - **Line 2 text lands on line 1:** make sure `send_newlines` is off. With 20-column lines the cursor has already wrapped to line 2, and an extra line feed sends it back to line 1.