Support every datasheet command and map text to the CG-ROM fonts
- Brightness, blink, screen on/off, annunciators, cursor control, display modes, user-defined characters, code table, international charset, peripheral select, self-test, raw writes and reinit - UTF-8 text is encoded with the selected code table (tables generated from Python codecs by tools/gen_charmaps.py), falling back to accent-stripping - Settings survive reinit and the self-test - Example gains brightness select and display switch, and uses the git source on mangavalk.com Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3fcd15ad86
commit
95686a3608
8 files changed
+1322
-148
No files matched your search
@@ -1,12 +1,16 @@
|
||||
# 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.
|
||||
(vacuum fluorescent display) pole display, driven over a single serial wire.
|
||||
|
||||
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.
|
||||
- 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 a clock doesn't flicker.
|
||||
- Supports every command in the datasheet: brightness, blinking, screen
|
||||
on/off, the triangle markers, cursor control, scroll modes, user-defined
|
||||
characters, code tables and the self-test.
|
||||
- Converts UTF-8 text to the display's built-in font, so `é`, `ß`, `°`, `½`,
|
||||
`█`, Greek and Cyrillic come out as real glyphs.
|
||||
|
||||
<p>
|
||||
<img src="docs/front.jpg" alt="Front of the display" height="300">
|
||||
@@ -14,10 +18,11 @@ so the clock doesn't flicker.
|
||||
</p>
|
||||
|
||||
```
|
||||
components/samsung_vfd/ the component (Python schema + C++)
|
||||
examples/samsung-vfd.yaml full example: clock + two Home Assistant text entities
|
||||
components/samsung_vfd/ the component (Python schema + C++)
|
||||
examples/samsung-vfd.yaml full example device config
|
||||
examples/secrets.yaml.example
|
||||
docs/ photos and the 20L203DA12 datasheet
|
||||
docs/ photos and the 20L203DA12 datasheet
|
||||
tools/gen_charmaps.py regenerates components/samsung_vfd/charmaps.h
|
||||
```
|
||||
|
||||
## Using it from your ESPHome server
|
||||
@@ -27,16 +32,19 @@ compiles.
|
||||
|
||||
```yaml
|
||||
external_components:
|
||||
- source: github://REPO_OWNER/REPO_NAME@main
|
||||
- source:
|
||||
type: git
|
||||
url: https://mangavalk.com/mangavalk/esphome-samsung-vfd
|
||||
ref: main
|
||||
components: [samsung_vfd]
|
||||
refresh: 1d
|
||||
|
||||
uart:
|
||||
id: uart_vfd
|
||||
tx_pin: GPIO17
|
||||
baud_rate: 9600
|
||||
baud_rate: 9600 # match the display's jumpers (factory default: 9600, no parity)
|
||||
|
||||
time: # needed by the component (used by samsung_vfd.printf)
|
||||
time: # required by the component (used by samsung_vfd.printf)
|
||||
- platform: sntp
|
||||
id: sntp_time
|
||||
|
||||
@@ -102,18 +110,41 @@ 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.
|
||||
The 2×8 pin header on the board selects the following. Jumpers are read only
|
||||
at power-on. All 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 |
|
||||
| Jumper 1, 2 | Baud rate |
|
||||
|---|---|
|
||||
| open, open | **9600** |
|
||||
| open, short | 4800 |
|
||||
| short, open | 2400 |
|
||||
| short, short | 19200 |
|
||||
|
||||
This component expects 9600 baud and no parity, which ESPHome's UART uses by
|
||||
default.
|
||||
| Jumper 3, 4 | Parity |
|
||||
|---|---|
|
||||
| open, any | **none** |
|
||||
| short, open | odd |
|
||||
| short, short | even |
|
||||
|
||||
| Jumper 5 | |
|
||||
|---|---|
|
||||
| open | **normal** |
|
||||
| short | self-test at power-on (about 20 s) |
|
||||
|
||||
| Jumper 6, 7, 8 | Font for 0x80–0xFF |
|
||||
|---|---|
|
||||
| open, open, open | **PC437** |
|
||||
| short, open, open | PC850 |
|
||||
| open, short, open | PC866 |
|
||||
| short, short, open | PC737 |
|
||||
| open, open, short | PC775 |
|
||||
| short, open, short | CP10 for DP86-20 |
|
||||
| open, short, short | PC864 |
|
||||
| short, short, short | PC852 |
|
||||
|
||||
Set `baud_rate` and `parity` (`NONE`, `ODD` or `EVEN`) under `uart:` to match
|
||||
jumpers 1–4. The component sends the font from its `code_table` setting at
|
||||
start-up, so jumpers 6–8 don't matter.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -121,83 +152,183 @@ default.
|
||||
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
|
||||
cols: 20 # default 20 (1–80)
|
||||
rows: 2 # default 2 (1–8)
|
||||
brightness: 100% # 40/60/80/100 %; other values are rounded
|
||||
code_table: PC437 # font for 0x80–0xFF, see "Characters"
|
||||
international_charset: USA # see "Characters"
|
||||
send_newlines: false # leave off for this display
|
||||
user_characters: # up to 4 custom glyphs
|
||||
- code: "~" # the ASCII character it replaces
|
||||
pattern: # 7 rows × 5 pixels, top row first
|
||||
- ".#.#." # '#', 'X', '1' or '*' = on
|
||||
- "#####" # '.', '-', '0', '_' or ' ' = off
|
||||
- "#####"
|
||||
- "#####"
|
||||
- ".###."
|
||||
- "..#.."
|
||||
- "....."
|
||||
```
|
||||
|
||||
The UART must be 9600 baud with a `tx_pin`. The component requires a `time:`
|
||||
component in the config.
|
||||
The UART needs a `tx_pin`. The component requires a `time:` component in the
|
||||
config.
|
||||
|
||||
The component remembers brightness, blink, on/off, triangle markers, code
|
||||
table, character set and user characters. It sends them again after
|
||||
`samsung_vfd.reinit` or a self-test.
|
||||
|
||||
## Actions
|
||||
|
||||
`id:` is optional in every action when there is only one display.
|
||||
|
||||
### Text
|
||||
|
||||
| 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";'
|
||||
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)`.
|
||||
### Display settings
|
||||
|
||||
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.
|
||||
| Action | Options |
|
||||
|--------|---------|
|
||||
| `samsung_vfd.set_brightness` | `brightness:` (templatable percentage). Short form: `samsung_vfd.set_brightness: 60%` |
|
||||
| `samsung_vfd.set_blink` | `interval:` (templatable time): on for that long, off for that long, in 13 ms steps up to 3.3 s. `0ms` stops blinking. |
|
||||
| `samsung_vfd.turn_off` / `turn_on` | Blanks the screen and brings it back without losing its contents. |
|
||||
| `samsung_vfd.set_annunciator` | `column:` 1–20, or 0 for all; `state:` true/false (both templatable). Switches the triangle under that column. |
|
||||
| `samsung_vfd.reinit` | Resets the display, re-applies all settings and redraws the screen. Use it when the display was power-cycled without the ESP. |
|
||||
| `samsung_vfd.self_test` | Runs the built-in self-test (about 20 s), then restores settings and the screen. |
|
||||
|
||||
### Raw control
|
||||
|
||||
The text actions above assume overwrite mode and always redraw from the
|
||||
top-left. The commands below give direct access to the cursor. After raw
|
||||
writes, the next `print` redraws the whole screen.
|
||||
|
||||
| Action | Options |
|
||||
|--------|---------|
|
||||
| `samsung_vfd.home` | Moves the cursor to the top-left. |
|
||||
| `samsung_vfd.set_cursor` | `column:` and `line:` (0-based, templatable). |
|
||||
| `samsung_vfd.write` | Writes at the cursor. Either `text:` (templatable, converted like `print`) or `data:`, which sends bytes unchanged, e.g. `[0x1F, 0x58, 0x02]` or `"abc"`. |
|
||||
| `samsung_vfd.set_display_mode` | `OVERWRITE` (default), `VERTICAL_SCROLL` or `HORIZONTAL_SCROLL`. The scroll modes are for raw `write`s, e.g. a ticker; switch back to `OVERWRITE` before using `print` again. |
|
||||
|
||||
### Lambda API
|
||||
|
||||
Every datasheet command (section 5.4) has a method on `id(vfd)`:
|
||||
|
||||
| # | Command | Bytes | Method |
|
||||
|---|---------|-------|--------|
|
||||
| 1 | BS | `08` | `cursor_left()` |
|
||||
| 2 | HT | `09` | `cursor_right()` |
|
||||
| 3 | LF | `0A` | `cursor_down()` |
|
||||
| 4 | US LF | `1F 0A` | `cursor_up()` |
|
||||
| 5 | HOM | `0B` | `home()` |
|
||||
| 6 | CR | `0D` | `cursor_line_start()` |
|
||||
| 7 | US CR | `1F 0D` | `cursor_line_end()` |
|
||||
| 8 | US B | `1F 42` | `cursor_bottom()` |
|
||||
| 9 | US $ n m | `1F 24 n m` | `set_cursor(column, line)` (0-based) |
|
||||
| 10 | CLR | `0C` | `clear()` |
|
||||
| 11 | CAN | `18` | `clear_cursor_line()` |
|
||||
| 12 | ESC @ | `1B 40` | `reinit()` |
|
||||
| 13 | ESC % n | `1B 25 n` | sent automatically by `define_char()` |
|
||||
| 14 | ESC & … | `1B 26 01 n n 05 p1…p5` | `define_char(code, {p1, p2, p3, p4, p5})` |
|
||||
| 15 | ESC ? n | `1B 3F n` | `cancel_char(code)` |
|
||||
| 16 | ESC = n | `1B 3D n` | `select_peripheral(n)` (1 = pass-through only, 2 = display, 3 = both) |
|
||||
| 17 | ESC R n | `1B 52 n` | `set_international_charset(samsung_vfd::CHARSET_GERMANY)` |
|
||||
| 18 | ESC t n | `1B 74 n` | `set_code_table(samsung_vfd::CODE_TABLE_PC850)` |
|
||||
| 19–21 | US MD1/2/3 | `1F 01`/`02`/`03` | `set_display_mode(samsung_vfd::MODE_OVERWRITE)` |
|
||||
| 22 | US E n | `1F 45 n` | `set_blink(ms)`, `turn_off()`, `turn_on()` |
|
||||
| 23 | US X n | `1F 58 n` | `set_brightness(0.0–1.0)` |
|
||||
| 24 | US # n m | `1F 23 n m` | `set_annunciator(column, state)` |
|
||||
| 25 | US @ | `1F 40` | `self_test()` |
|
||||
|
||||
The buffered text API is `print(text, centered)`, `print_line(line, text, centered)`
|
||||
and `line_text(line)`. For anything else, use `write_text(text)`,
|
||||
`write_raw(bytes)` and `encode(text)`, which returns the display bytes.
|
||||
|
||||
## Characters
|
||||
|
||||
Text is UTF-8, and each character becomes one byte:
|
||||
|
||||
1. ASCII (0x20–0x7E) passes through unchanged. Control characters become
|
||||
spaces.
|
||||
2. Other characters are looked up in the selected `code_table`, the display's
|
||||
font for bytes 0x80–0xFF. With PC437, for example, `é` → 0x82, `°` → 0xF8,
|
||||
`½` → 0xAB and `█` → 0xDB. The pages at the end of the datasheet show
|
||||
every table.
|
||||
3. Accented letters missing from the table lose their accent (`ő` → `o`).
|
||||
Anything else becomes `?`.
|
||||
|
||||
| `code_table` | Covers |
|
||||
|---|---|
|
||||
| `PC437` | US / Western European, box drawing, some Greek and maths |
|
||||
| `PC850` | Western European (more accented capitals) |
|
||||
| `PC852` | Central European (Polish, Czech, Hungarian …) |
|
||||
| `PC866` | Cyrillic |
|
||||
| `PC737` | Greek |
|
||||
| `PC775` | Baltic |
|
||||
| `PC864` | Arabic |
|
||||
| `CP10` | DP86-20 compatible; no conversion table, accents are stripped |
|
||||
|
||||
The conversion tables in
|
||||
[charmaps.h](components/samsung_vfd/charmaps.h) are generated from Python's
|
||||
standard code page codecs by [tools/gen_charmaps.py](tools/gen_charmaps.py).
|
||||
|
||||
`international_charset` (`USA`, `FRANCE`, `GERMANY`, `UK`, `DENMARK1`, `SWEDEN`,
|
||||
`ITALY`, `SPAIN1`, `JAPAN`, `NORWAY`, `DENMARK2`) replaces 12 ASCII characters
|
||||
such as `#`, `[`, `\`, `]` and `{` with national letters. For example, with
|
||||
`GERMANY`, `[` shows as `Ä`. The text conversion doesn't account for this, so
|
||||
keep `USA` unless you want those swaps.
|
||||
|
||||
User characters replace an ASCII character: with the example config, every
|
||||
`~` in your text shows the heart.
|
||||
|
||||
## What the example config does
|
||||
|
||||
- On boot: clears the screen, shows `Hello World!` for 3 s, then restores line 1.
|
||||
- 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.
|
||||
- Home Assistant entities:
|
||||
- **VFD line 1** (text, max 20 chars) is shown on the top line and kept
|
||||
across reboots.
|
||||
- **VFD status** (text, 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.
|
||||
- **VFD brightness** (select: 40/60/80/100 %).
|
||||
- **VFD display** (switch) blanks the screen and brings it 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 |
|
||||
## How updates are sent
|
||||
|
||||
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.
|
||||
characters. Nothing is sent when the content didn't change.
|
||||
|
||||
## 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).
|
||||
[Signal levels](#signal-levels)). Check that `baud_rate` and `parity` match
|
||||
jumpers 1–4.
|
||||
- **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.
|
||||
- **The display was power-cycled and lost its settings or text:** call
|
||||
`samsung_vfd.reinit`, for example from a Home Assistant button.
|
||||
Reference in new issue
Block a user