mangavalkandClaude Opus 5.5 3fcd15ad86 Add photos and datasheet; correct wiring and protocol docs
The 20L203 input is RS-232 (reference build uses a MAX3232), it runs on
5-24 V, and it boots in non-scrolling overwrite mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:44:07 +01:00

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.

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 into your ESPHome config folder. Add the keys from examples/secrets.yaml.example to your secrets.yaml.

Hardware

The board in the photos is a 20L203DA5 (rev A). The included datasheet 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:

    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

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.
- 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). 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.
S
Description
No description provided
Readme
16 MiB
0 Stars 1 Watchers 0 Forks
Languages
C++ 52.3%
OpenSCAD 24.4%
Python 23.3%