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>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-31 17:44:07 +01:00
1 parent bd9389e04a
commit 3fcd15ad86
7 files changed
+113 -52

No files matched your search

+11 -4
View File
@@ -1,7 +1,7 @@
# CLAUDE.md # CLAUDE.md
ESPHome external component for a Samsung 2×20 serial VFD (5 V TTL, 9600 8N1, ESPHome external component for a Samsung 20L203 2×20 serial VFD (RS-232
write-only). It is consumed from git by an ESPHome server through input, 9600 8N1, write-only). It is consumed from git by an ESPHome server through
`external_components: source: github://...`, so the layout must stay `external_components: source: github://...`, so the layout must stay
`components/samsung_vfd/` at the repo root. `components/samsung_vfd/` at the repo root.
@@ -13,6 +13,8 @@ write-only). It is consumed from git by an ESPHome server through
and the templated action classes. and the templated action classes.
- `components/samsung_vfd/samsung_vfd.cpp`: the shadow buffer, word wrap, - `components/samsung_vfd/samsung_vfd.cpp`: the shadow buffer, word wrap,
UTF-8 → single-byte sanitising and `flush_()`. UTF-8 → single-byte sanitising and `flush_()`.
- `docs/20L203DA12-datasheet.pdf`: the command set (section 5.4), jumpers,
wiring and the character tables. Check it before adding protocol features.
- `examples/samsung-vfd.yaml`: the full reference device config. It pulls the - `examples/samsung-vfd.yaml`: the full reference device config. It pulls the
component from GitHub; for local work, switch to the commented component from GitHub; for local work, switch to the commented
`type: local, path: ../components` source. `type: local, path: ../components` source.
@@ -27,8 +29,13 @@ write-only). It is consumed from git by an ESPHome server through
`last_payload_` so the next flush retransmits. `last_payload_` so the next flush retransmits.
- Text sent to the display is one byte per character; always pass it through - Text sent to the display is one byte per character; always pass it through
`sanitize_()`. `sanitize_()`.
- Keep the protocol constants in `samsung_vfd.h`. Only `ESC @`, `0C` and `0B` - Keep the protocol constants in `samsung_vfd.h`. The display boots in
are known to work. overwrite mode: the cursor wraps from the end of line 1 to line 2 and from
the end of line 2 back home, and nothing scrolls. A full-screen redraw is
therefore `0B` plus `rows × cols` characters.
- The RXD input is RS-232. The reference build uses a MAX3232 (no inversion
in config); a directly wired ESP32 pin needs `inverted: true` on the UART TX
pin.
## Checking changes ## Checking changes
+91 -34
View File
@@ -1,16 +1,23 @@
# Samsung serial VFD — ESPHome component # Samsung serial VFD — ESPHome component
An ESPHome external component for the Samsung 2-line × 20-character serial VFD An ESPHome external component for the Samsung 20L203 2-line × 20-character VFD
(vacuum fluorescent display) that takes 5 V TTL serial at 9600 baud, 8N1. (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 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, changes. Updates overwrite the screen in place instead of clearing it first,
so the clock doesn't flicker. so the clock doesn't flicker.
<p>
<img src="docs/front.jpg" alt="Front of the display" height="300">
<img src="docs/back.jpg" alt="Back of the controller board" height="300">
</p>
``` ```
components/samsung_vfd/ the component (Python schema + C++) components/samsung_vfd/ the component (Python schema + C++)
examples/samsung-vfd.yaml full example: clock + two Home Assistant text entities examples/samsung-vfd.yaml full example: clock + two Home Assistant text entities
examples/secrets.yaml.example examples/secrets.yaml.example
docs/ photos and the 20L203DA12 datasheet
``` ```
## Using it from your ESPHome server ## Using it from your ESPHome server
@@ -43,24 +50,70 @@ into your ESPHome config folder. Add the keys from
[examples/secrets.yaml.example](examples/secrets.yaml.example) to your [examples/secrets.yaml.example](examples/secrets.yaml.example) to your
`secrets.yaml`. `secrets.yaml`.
## Wiring ## Hardware
The component only sends to the display. The ESP32's 3.3 V output is enough for The board in the photos is a 20L203DA5 (rev A). The included
most 5 V TTL inputs. ESP32 GPIOs are **not** 5 V tolerant, so never connect the [datasheet](docs/20L203DA12-datasheet.pdf) is for the 20L203DA12, a variant
display's TX back to the ESP32 without a level shifter or divider. with the same command set.
| Display | ESP32 | ### Connector (3-pin Molex 5046-03A)
|---------|-------|
| RX | GPIO17 (UART TX) |
| GND | GND |
| 5 V | external 5 V supply |
The display draws more current than a dev-board regulator can comfortably | Pin | Signal | Connect to |
supply. Power it from its own supply and connect the grounds together. |-----|--------|------------|
| 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 |
To use GPIO1 (TX0, shared with USB logging), set `tx_pin: GPIO1` and ### Power
`logger: baud_rate: 0` so log output doesn't end up on the display. If the
display stays blank at 3.3 V levels, add a 74HCT125 or similar level shifter. 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 ## Configuration
@@ -70,7 +123,7 @@ samsung_vfd:
uart_id: uart_vfd uart_id: uart_vfd
cols: 20 # default 20 (1–80) cols: 20 # default 20 (1–80)
rows: 2 # default 2 (1–8) rows: 2 # default 2 (1–8)
send_newlines: false # default false; send CR LF between lines 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:` The UART must be 9600 baud with a `tx_pin`. The component requires a `time:`
@@ -121,26 +174,30 @@ characters become spaces.
## Protocol ## 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 | | Bytes | Effect |
|-------|--------| |-------|--------|
| `1B 40` (`ESC @`) | init / reset | | `1B 40` (`ESC @`) | initialise: reset settings, clear, cursor home |
| `0C` | clear screen | | `0C` | clear screen, cursor home |
| `0B` | cursor home | | `0B` | cursor home (top-left) |
| text | printed at the cursor; wraps to the next line by itself | | `20`–`FF` | print a character and move the cursor right |
Each update sends `0B` (home) followed by both lines, each padded to exactly At power-on the display is in overwrite mode. After the last column of the top
`cols` characters. 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 ## Troubleshooting
- **Line 2 lands on the wrong row, or text runs together:** try - **Garbage or nothing on the display:** toggle `inverted:` on the TX pin (see
`send_newlines: true`. [Signal levels](#signal-levels)). Check that the baud jumpers 1 and 2 are
- **The screen scrolls up a line after each update:** the display scrolls as open (9600) and that parity jumpers 3 and 4 are open (none).
soon as the last cell is written. Use `cols: 19` together with - **The display runs its self-test for about 20 s after power-on:** jumper 5 is
`send_newlines: true`. shorted.
- **A stray character appears top-left:** the display doesn't treat `0B` as - **Line 2 text lands on line 1:** make sure `send_newlines` is off. With
home. Change `CMD_HOME` in 20-column lines the cursor has already wrapped to line 2, and an extra line
[samsung_vfd.h](components/samsung_vfd/samsung_vfd.h). feed sends it back to line 1.
- **Garbled characters:** check that `uart:` is at 9600 baud and that the
display takes 5 V TTL levels, not RS-232 levels. A real RS-232 input needs a
MAX232 inverter.
+1 -4
View File
@@ -14,10 +14,7 @@ namespace samsung_vfd {
static const char *const TAG = "samsung_vfd"; static const char *const TAG = "samsung_vfd";
// Control bytes, taken from older Arduino sketches that drove this display. // Control bytes, see section 5.4 of docs/20L203DA12-datasheet.pdf.
// The one known to work only ever used ESC @ and 0x0C; 0x0B ("home") came
// from an earlier test sketch. That sketch also sent 0xB0, which is left out:
// it is most likely a printable glyph, not a command.
static const uint8_t CMD_ESC = 0x1B; static const uint8_t CMD_ESC = 0x1B;
static const uint8_t CMD_INIT = 0x40; // ESC @ -> reset / init display static const uint8_t CMD_INIT = 0x40; // ESC @ -> reset / init display
static const uint8_t CMD_CLEAR = 0x0C; // FF -> clear screen (cursor to home) static const uint8_t CMD_CLEAR = 0x0C; // FF -> clear screen (cursor to home)
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

+10 -10
View File
@@ -3,14 +3,13 @@
# Copy this file into your ESPHome config folder (and add the keys below to # Copy this file into your ESPHome config folder (and add the keys below to
# your secrets.yaml), or use it as a reference for your own device config. # your secrets.yaml), or use it as a reference for your own device config.
# #
# Wiring (display is 5V TTL serial, 9600 8N1): # Wiring (3-pin connector on the display, 9600 8N1):
# display RX <- ESP32 GPIO17 (TX) # pin 1 VCC <- 5-24V supply, up to 1.2A at 5V (not the ESP32 regulator)
# display GND <-> ESP32 GND # pin 2 RXD <- MAX3232 T1OUT <- T1IN <- ESP32 GPIO17 (TX)
# display 5V <- 5V supply (the display needs more current than the ESP32 regulator can give) # pin 3 GND <-> supply GND, MAX3232 GND and ESP32 GND
# #
# To drive the display from GPIO1 (TX0, the USB serial pin) instead, set # RXD is an RS-232 input. Without a MAX3232, wire GPIO17 straight to RXD and
# `tx_pin: GPIO1` below and `baud_rate: 0` under `logger:` so log output does # set `inverted: true` under uart.
# not end up on the display.
esphome: esphome:
name: samsung-vfd name: samsung-vfd
@@ -81,7 +80,9 @@ time:
uart: uart:
id: uart_vfd id: uart_vfd
tx_pin: GPIO17 tx_pin:
number: GPIO17
inverted: false # true when wired directly, without a MAX3232
baud_rate: 9600 baud_rate: 9600
# The display is output-only for us; no rx_pin needed. # The display is output-only for us; no rx_pin needed.
@@ -90,8 +91,7 @@ samsung_vfd:
uart_id: uart_vfd uart_id: uart_vfd
cols: 20 cols: 20
rows: 2 rows: 2
# Lines are padded to 20 chars and the display wraps by itself. Set to true # Lines are padded to 20 chars and the display wraps to line 2 by itself.
# only if line 2 ends up on the wrong row (sends CR LF between the lines).
send_newlines: false send_newlines: false
# Bottom line shows the SNTP clock every second, except while a status banner is up. # Bottom line shows the SNTP clock every second, except while a status banner is up.