CYD/1 is a bidirectional binary protocol between a PureBasic application and the ESP32-CYD firmware. Multi-byte integers are little-endian.
| Offset | Size | Field |
|---|---|---|
| 0 | 1 | Magic 0x43 (C) |
| 1 | 1 | Magic 0x59 (Y) |
| 2 | 1 | Protocol version (1) |
| 3 | 1 | Message type |
| 4 | 2 | Sequence number |
| 6 | 2 | Payload length, 0..4096 |
| 8 | N | Payload |
| 8+N | 2 | CRC-16/CCITT-FALSE over bytes 2..7 and payload |
CRC parameters: polynomial 0x1021, initial value 0xFFFF, no reflection,
no final XOR.
The length field makes all byte values legal inside images and audio. A
receiver that encounters an invalid header, length, or CRC searches for the
next CY magic pair.
| Type | Name | Payload |
|---|---|---|
0x01 |
HELLO | empty |
0x02 |
PING | empty |
0x10 |
SET_ORIENTATION | orientation:u8 |
0x11 |
SET_BRIGHTNESS | brightness:u8 |
0x12 |
CLEAR | rgb565:u16 |
0x13 |
FILL_RECT | x:u16 y:u16 w:u16 h:u16 rgb565:u16 |
0x14 |
BLIT_RGB565 | x:u16 y:u16 w:u16 h:u16 pixels:w*h*u16 |
0x15 |
DRAW_PIXEL | x:i16 y:i16 color:u16 |
0x16 |
DRAW_LINE | x1:i16 y1:i16 x2:i16 y2:i16 color:u16 |
0x17 |
DRAW_RECT | x:i16 y:i16 w:u16 h:u16 color:u16 filled:u8 |
0x18 |
DRAW_CIRCLE | x:i16 y:i16 radius:u16 color:u16 filled:u8 |
0x19 |
DRAW_TRIANGLE | x1:i16 y1:i16 x2:i16 y2:i16 x3:i16 y3:i16 color:u16 filled:u8 |
0x1A |
DRAW_TEXT | x:i16 y:i16 color:u16 background:u16 scale:u8 opaque:u8 text:utf8 |
0x1B |
SCROLL_START | x:i16 y:i16 w:u16 h:u16 color:u16 background:u16 scale:u8 direction:u8 speed:u16 text:utf8 |
0x1C |
SCROLL_STOP | clear_viewport:u8 |
0x1D |
DRAW_BUTTON | x:i16 y:i16 w:u16 h:u16 radius:u8 color:u16 highlight:u16 shadow:u16 pressed:u8 |
0x1E |
DRAW_UTF8_TEXT | x:i16 y:i16 w:u16 h:u16 color:u16 background:u16 font_height:u8 flags:u8 text:utf8 |
0x1F |
UPDATE_BAR_CHART | x:i16 y:i16 w:u16 h:u16 background:u16 marker_color:u16 flags:u8 marker_height:u8 count:u16 entries |
0x20 |
TOUCH_CONFIG | enabled:u8 move_interval_ms:u16 |
0x21 |
TOUCH_CALIBRATE | empty |
0x22 |
TOUCH_RESET_CALIBRATION | empty |
0x23 |
TOUCH_CANCEL_CALIBRATION | empty |
0x24 |
TOUCH_RAW | empty |
0x25 |
MATRIX_START | speed:u8 density:u8 tail_length:u8 |
0x26 |
MATRIX_STOP | clear_screen:u8 |
0x27 |
MATRIX_CLOCK_START | speed:u8 density:u8 tail_length:u8 style:u8 show_seconds:u8 language:u8 year:u16 month:u8 day:u8 hour:u8 minute:u8 second:u8 |
0x28 |
CLOCK_SAVER_START | style:u8 show_seconds:u8 language:u8 year:u16 month:u8 day:u8 hour:u8 minute:u8 second:u8 |
0x30 |
AUDIO_TONE | frequency_hz:u16 duration_ms:u16 volume:u8 |
0x31 |
AUDIO_BEGIN | sample_rate:u16 volume:u8 |
0x32 |
AUDIO_DATA | unsigned 8-bit mono PCM |
0x33 |
AUDIO_END | empty |
0x34 |
AUDIO_STOP | empty |
0x35 |
AUDIO_MELODY | volume:u8 notation:utf8 |
0x40 |
SD_INFO | empty |
0x41 |
SD_UPLOAD_BEGIN | size:u32 overwrite:u8 path:utf8 |
0x42 |
SD_UPLOAD_DATA | sequential file bytes |
0x43 |
SD_UPLOAD_END | empty |
0x44 |
GIF_PLAY | x:i16 y:i16 w:u16 h:u16 speed_percent:u16 loops:u16 background:u16 fit_mode:u8 path:utf8 |
0x45 |
GIF_PAUSE | empty |
0x46 |
GIF_RESUME | empty |
0x47 |
GIF_STOP | clear_viewport:u8 |
0x48 |
MP3_PLAY | volume:u8 loop:u8 path:utf8 |
0x49 |
MP3_PAUSE | empty |
0x4A |
MP3_RESUME | empty |
0x4B |
MP3_STOP | empty |
0x4C |
MEDIA_STATUS | empty |
0x4D |
SD_LIST | start_index:u16 directory:utf8 |
0x4E |
SD_READ | offset:u32 max_bytes:u16 path:utf8 |
0x4F |
SD_DELETE | path:utf8 |
0x50 |
RGB_SET | red:u8 green:u8 blue:u8 |
0x60 |
WIFI_SCAN_START | show_hidden:u8 |
0x61 |
WIFI_SCAN_RESULTS | start_index:u16 |
0x70 |
CLOCK_START | year:u16 month:u8 day:u8 hour:u8 minute:u8 second:u8 x:i16 y:i16 scale:u8 color:u16 background:u16 |
0x71 |
CLOCK_STOP | clear_viewport:u8 |
0x72 |
DATE_START | same payload as CLOCK_START |
0x73 |
DATE_STOP | clear_viewport:u8 |
0x74 |
TIMER_START | duration_seconds:u32 x:i16 y:i16 scale:u8 color:u16 background:u16 |
0x75 |
TIMER_STOP | clear_viewport:u8 |
Orientation values:
0: landscape, 320x240;1: reverse landscape, 320x240;2: portrait, 240x320;3: reverse portrait, 240x320.
RGB565 uses the conventional layout: red bits 15..11, green bits 10..5, blue bits 4..0. The ST7789 BGR wiring is handled exclusively by the firmware.
BLIT_RGB565 is deliberately limited to 4096 payload bytes. A host sends a
large image as several horizontal strips. The firmware does not require a
full-screen framebuffer.
QR codes are generated by CYD_DrawQRCode() in the PureBasic host
framebuffer and transferred with the existing BLIT_RGB565 command. CYD/1
therefore needs no separate QR command.
The DRAW_* commands execute directly through Adafruit GFX and therefore do
not transfer a host framebuffer. DRAW_TEXT uses the built-in 5x7 Adafruit
font and remains the fastest ASCII path. DRAW_UTF8_TEXT uses bundled U8g2
fonts, decodes UTF-8, and includes Cyrillic. Its flags bit 0 centers text in
the rectangle and bit 1 clears the rectangle with background before drawing.
Supported requested font heights are 4..53 pixels and are mapped to the nearest
bundled Cyrillic font.
UPDATE_BAR_CHART transfers all vertical bars in one acknowledged command.
Each entry is value:u8 color:u16; values are clamped to 0..100. Flag bit 0
enables one-pixel gaps when slots are wide enough, bit 1 draws the preceding
sample as a marker, and bit 2 forces a complete redraw. The firmware retains
the preceding values and incrementally updates changed bar regions. The count
is limited to 320 and cannot exceed the chart width.
MATRIX_START clears the display and starts a firmware-rendered Matrix-style
effect that requires no continuing host traffic. speed and density accept
1..100; tail_length accepts 4..30 character cells. MATRIX_STOP optionally
clears the display. A later display-owning command stops the effect
automatically, while brightness and non-display service commands leave it
running. These commands are available in firmware 0.15.0 and newer.
MATRIX_CLOCK_START (firmware 0.18.0+) starts the same autonomous effect and
seeds its local calendar in one command. style=0 selects a digital face and
style=1 a flip face; language=0 selects Russian and language=1 English.
The ESP32 updates time, date, and weekday locally. Matrix glyphs do not enter
the clock regions, and no touch instruction is drawn at the bottom.
CLOCK_SAVER_START (firmware 0.19.0+) starts the same digital or flip clock on
a black background without Matrix. For both commands, show_seconds=0 removes
seconds from the face and changes the time only once per minute. MATRIX_STOP
also stops either autonomous clock-saver mode.
DRAW_BUTTON draws a filled rounded face with a directional highlight and
shadow. With pressed=1, only the bevel is inverted; the face and a
host-rendered label inside it remain untouched.
CLOCK_START and DATE_START seed the ESP32 with local calendar components
from the host. The two elements have independent position and style state,
while sharing the device-maintained calendar. They render HH:MM:SS and
YYYY-MM-DD without further host traffic. TIMER_START renders a countdown
as HH:MM:SS; durations are limited to 1..359999 seconds.
SCROLL_START allocates a local RGB565 canvas for the requested viewport and
updates it at approximately 30 FPS. The viewport is limited to 30720 pixels
and text to 255 UTF-8 bytes. After the command is acknowledged, no host
traffic is needed to keep the animation running.
SD paths are UTF-8 absolute paths beginning with /; .. is rejected.
Uploads are sequential, limited to 4 GB, and every data packet is
acknowledged. In GIF_PLAY, loops=0 means infinite repeat, x/y=-1
centers the image, and a zero width/height selects the full display. The
fit_mode byte follows background: 0=contain, 1=cover, 2=stretch.
GIF and MP3 are mutually exclusive and are decoded locally from SD.
SD_LIST is paginated. The host starts at index zero and continues with the
returned next index until done=1. SD_READ is request/response chunking,
which preserves serial flow control while downloading files.
| Type | Name | Payload |
|---|---|---|
0x80 |
ACK | request_type:u8 status:u8 |
0x81 |
ERROR | request_type:u8 error:u8 |
0x90 |
TOUCH_EVENT | state:u8 x:u16 y:u16 pressure:u16 time_ms:u32 |
0x91 |
CALIBRATION_EVENT | stage:u8 point:u8 total:u8 x:u16 y:u16 error:u16 |
0x92 |
INFO | width:u16 height:u16 orientation:u8 capabilities:u16 name:utf8 |
0x93 |
SD_INFO | mounted:u8 card_type:u8 total_mb:u32 used_mb:u32 |
0x94 |
MEDIA_STATUS | type:u8 state:u8 error:u8 value:u8 path:utf8 |
0x95 |
SD_DATA | total_size:u32 offset:u32 eof:u8 bytes |
0x96 |
SD_LIST | next_index:u16 done:u8 records |
0x97 |
WIFI_SCAN_LIST | status:u8 next_index:u16 done:u8 records |
0x98 |
TOUCH_RAW | pressed:u8 raw_x:u16 raw_y:u16 pressure:u16 |
0x99 |
TIMER_EVENT | state:u8 duration_seconds:u32 |
0x9A |
MELODY_EVENT | state:u8 duration_ms:u32 |
Each SD list record is
directory:u8 size:u32 name_length:u8 name:utf8.
Each Wi-Fi record is
rssi:i16 channel:u8 encryption:u8 bssid_length:u8 bssid:utf8 ssid_length:u8 ssid:utf8. Wi-Fi status values are 0=idle, 1=scanning,
2=complete, and 3=error. Results are paginated like SD lists.
Touch states are 1=down, 2=move, and 3=up.
Timer state 1 means that the countdown reached zero. The event is
asynchronous and uses sequence number zero.
Melody state 1 means playback completed and state 2 means it was stopped.
The asynchronous event uses sequence number zero. AUDIO_MELODY notation starts
with BPM=20..400: and contains up to 128 notes separated by spaces or
commas. Notes use [duration][.][note][#|b][octave]; duration defaults to 4
and may be 1, 2, 4, 8, 16, or 32. Notes are C, D, E, F, G, A, B/H, and P/R
for a rest. Pitched notes require octave 1 through 7. A melody is limited to
1024 UTF-8 bytes and 300000 ms.
Calibration stages are:
1: target displayed;2: calibration completed;3: calibration failed.4: calibration cancelled.5: raw calibration sample accepted (x/ycontain raw ADC values).
Capability bits:
- bit 0: touch;
- bit 1: host-guided touch calibration is required and supported;
- bit 2: tone output;
- bit 3: unsigned 8-bit PCM output;
- bit 4: device-side drawing commands;
- bit 5: autonomous device-side scrolling;
- bit 6: FAT32 SD storage and upload;
- bit 7: autonomous GIF playback;
- bit 8: MP3 playback from SD;
- bit 9: PWM control of the onboard RGB LED;
- bit 10: Wi-Fi network scanning;
- bit 11: autonomous Clock, Date, and Timer;
- bit 12: autonomous RTTTL-like melody playback;
- bit 13: device-side primitive colors use BGR panel order; exchange the red and blue fields of host RGB565 colors before sending primitive commands;
- bit 14: device-side UTF-8 text with bundled Cyrillic fonts;
- bit 15: one-packet incremental vertical bar charts;
HELLO produces INFO. PING, control commands, and every BLIT_RGB565 strip produce ACK or ERROR. Waiting for each bitmap ACK provides flow control and prevents a PC serial driver from queuing an entire frame faster than the ESP32 can consume it. AUDIO_DATA is paced by the host and is not acknowledged. CRC errors are discarded because a damaged packet may not contain a trustworthy sequence number.
The CYD/1 project implementation and this specification are copyright (C)
CheshirCa 2026, https://t.me/cheshircanest, and are distributed for
non-commercial use under the complete terms in LICENSE.
CYD/1 — двусторонний бинарный протокол между приложением PureBasic и прошивкой ESP32-CYD. Многобайтовые целые передаются в порядке little-endian. Таблицы типов, имён и payload в английской части выше являются общей нормативной частью обеих языковых версий.
Заголовок начинается байтами 0x43 0x59 (CY), за которыми следуют версия
протокола, тип сообщения, номер последовательности и длина payload. После
payload передаётся CRC-16/CCITT-FALSE по байтам заголовка 2…7 и payload.
Полином — 0x1021, начальное значение — 0xFFFF, отражение и финальный XOR
не применяются.
Длина payload находится в диапазоне 0…4096. Благодаря отдельному полю длины
изображения и звук могут содержать любые байты. После неправильного заголовка,
длины или CRC приёмник ищет следующую сигнатуру CY.
Полная нормативная таблица команд находится в разделе Host commands. Ориентации имеют значения:
0— landscape, 320×240;1— reverse landscape, 320×240;2— portrait, 240×320;3— reverse portrait, 240×320.
RGB565 использует обычную раскладку: красный — биты 15…11, зелёный — 10…5, синий — 4…0. Особенность BGR-подключения ST7789 полностью обрабатывает прошивка.
BLIT_RGB565 ограничен payload размером 4096 байт. Большое изображение хост
передаёт горизонтальными полосами; полноэкранный framebuffer в прошивке не
требуется.
QR-коды создаются функцией CYD_DrawQRCode() в host framebuffer PureBasic и
передаются существующей командой BLIT_RGB565. Отдельная команда QR в
протоколе CYD/1 не требуется.
Команды DRAW_* исполняются непосредственно через Adafruit GFX и не передают
host framebuffer. DRAW_TEXT использует встроенный шрифт Adafruit 5×7,
предназначенный прежде всего для ASCII. DRAW_UTF8_TEXT использует встроенные
в прошивку шрифты U8g2, декодирует UTF-8 и содержит кириллицу. В flags bit 0
центрирует текст в прямоугольнике, bit 1 очищает прямоугольник цветом
background перед рисованием. Запрошенная высота 4…53 пикселя сопоставляется
с ближайшим встроенным кириллическим шрифтом.
UPDATE_BAR_CHART передаёт все вертикальные столбцы одной подтверждаемой
командой. Формат элемента — value:u8 color:u16, значение ограничивается
диапазоном 0…100. В flags bit 0 включает промежутки шириной один пиксель,
когда ширина слота это позволяет; bit 1 рисует предыдущее измерение линией;
bit 2 принудительно перерисовывает весь график. Прошивка запоминает прошлые
значения и обновляет только изменившиеся части столбцов. Допускается до 320
столбцов, причём их количество не может превышать ширину графика.
MATRIX_START очищает экран и запускает локальный Matrix-эффект прошивки, не
требующий дальнейшего трафика с хоста. speed и density принимают значения
1…100, tail_length — 4…30 знакомест. MATRIX_STOP при необходимости очищает
экран. Следующая команда, использующая дисплей, автоматически останавливает
эффект; изменение яркости и служебные команды его не прерывают. Команды
доступны начиная с прошивки 0.15.0.
MATRIX_CLOCK_START (прошивка 0.18.0 и новее) одной командой запускает тот же
автономный эффект и синхронизирует его локальный календарь. style=0 выбирает
цифровой циферблат, style=1 — перекидной; language=0 задаёт русский язык,
language=1 — английский. ESP32 самостоятельно обновляет время, дату и день
недели. Символы Matrix не заходят в области часов, нижняя подсказка о нажатии
не выводится.
CLOCK_SAVER_START (прошивка 0.19.0 и новее) запускает такие же цифровые или
перекидные часы на чёрном фоне без Matrix. Для обеих команд show_seconds=0
убирает секунды с циферблата, после чего время меняется только раз в минуту.
MATRIX_STOP также останавливает любой автономный часовой скринсейвер.
DRAW_BUTTON рисует закруглённую кнопку со светлой и тёмной гранями. При
pressed=1 инвертируется только фаска; поверхность кнопки и заранее
отрисованная хостом подпись не изменяются.
CLOCK_START и DATE_START передают локальные компоненты даты и времени.
Clock и Date имеют независимые координаты и стиль, но используют общий
календарь ESP32. Они выводят HH:MM:SS и YYYY-MM-DD без дальнейшего трафика
с хостом. TIMER_START выводит обратный отсчёт HH:MM:SS; допустимая
длительность — 1…359999 секунд.
SCROLL_START создаёт локальный RGB565 canvas заданной области и обновляет
его примерно с частотой 30 FPS. Область ограничена 30720 пикселями, текст —
255 байтами UTF-8. После подтверждения команды дальнейший трафик для анимации
не нужен.
Пути SD имеют кодировку UTF-8, начинаются с /, а .. запрещён. Загрузка
последовательная, ограничена 4 ГБ, каждый пакет данных подтверждается. Для
GIF_PLAY: loops=0 означает бесконечный цикл, x/y=-1 центрирует
изображение, нулевые ширина и высота выбирают весь экран. fit_mode:
0=contain, 1=cover, 2=stretch. GIF и MP3 взаимоисключающие и
декодируются локально с SD.
SD_LIST использует страницы: хост начинает с нулевого индекса и продолжает
с возвращённого индекса до done=1. SD_READ передаёт файл блоками
request/response, сохраняя управление потоком последовательного порта.
Нормативная таблица находится в разделе
Device messages. Запись списка SD имеет формат
directory:u8 size:u32 name_length:u8 name:utf8.
Запись Wi-Fi имеет формат
rssi:i16 channel:u8 encryption:u8 bssid_length:u8 bssid:utf8 ssid_length:u8 ssid:utf8. Статусы: 0=idle, 1=scanning, 2=complete,
3=error. Результаты разбиваются на страницы аналогично списку SD.
Состояния touch: 1=down, 2=move, 3=up. Состояние Timer 1 означает,
что отсчёт достиг нуля. Это асинхронное событие использует sequence number 0.
Состояние Melody 1 означает нормальное завершение, 2 — остановку.
Асинхронное событие использует sequence number 0. Строка AUDIO_MELODY
начинается с BPM=20..400: и содержит до 128 нот, разделённых пробелами или
запятыми. Формат ноты: [длительность][.][нота][#|b][октава].
Длительность по умолчанию 4; разрешены 1, 2, 4, 8, 16 и 32. Ноты:
C, D, E, F, G, A, B/H, пауза P/R. Для звучащей ноты обязательна октава 1…7.
Ограничения мелодии: 1024 UTF-8 байта и 300000 мс.
Стадии калибровки:
1— показана очередная мишень;2— калибровка завершена;3— ошибка калибровки;4— калибровка отменена;5— принят сырой образец,x/yсодержат значения ADC.
Биты capabilities:
- bit 0 — touch;
- bit 1 — требуется и поддерживается управляемая хостом калибровка touch;
- bit 2 — генерация тона;
- bit 3 — unsigned 8-bit PCM;
- bit 4 — аппаратные команды рисования;
- bit 5 — автономный скролл;
- bit 6 — FAT32 SD и загрузка файлов;
- bit 7 — автономный GIF;
- bit 8 — MP3 с SD;
- bit 9 — PWM штатного RGB LED;
- bit 10 — сканирование Wi-Fi;
- bit 11 — автономные Clock, Date и Timer.
- bit 12 — автономное воспроизведение RTTTL-подобной мелодии.
- bit 13 — цвета аппаратных примитивов используют порядок панели BGR; перед отправкой команды примитива поменять местами красное и синее поля RGB565.
- bit 14 — аппаратный UTF-8-текст со встроенными кириллическими шрифтами.
- bit 15 — инкрементальный вертикальный график, передаваемый одним пакетом.
HELLO возвращает INFO. PING, управляющие команды и каждая полоса BLIT_RGB565 возвращают ACK или ERROR. Ожидание ACK после каждой полосы обеспечивает flow control и не позволяет драйверу ПК поставить в очередь весь кадр быстрее, чем ESP32 способен его принять. AUDIO_DATA задаёт темп со стороны хоста и не подтверждается. Пакеты с ошибкой CRC отбрасываются, поскольку повреждённый пакет может содержать недостоверный sequence number.
Реализация проекта CYD/1 и эта спецификация защищены авторским правом:
Copyright (C) CheshirCa 2026, https://t.me/cheshircanest. Они
распространяются только для некоммерческого использования на полных условиях,
приведённых в LICENSE.