После того как этот электронный ценник приобрёл известность в DIY-сообществе, опубликую некоторые свои наработки по G-Tag 6.
Куча бесполезных электронных ценников G-Tag 6 — YouTube автор shojkeee
Telegram-группа по SES-imagotag G-Tag 6: https://t.me/g_tag6 владелец ReallSeriously
Update: Коментариев уже больше сотни, часть по умолчанию скрыта под "Load earlier comments...", подгружайте если нужно. И пожалуйста, большие фотки и т.п. размещаем под спойлером.
| Параметр | Значение |
|---|---|
| Размеры, мм | 69 × 35 × 10,5 |
| Дисплей | LCD, 256 × 128 |
| Активная область, мм | 52 × 25 |
| Количество пикселей | 32 768 |
| Плотность пикселей | 127 dpi |
| Цвет пикселей | Чёрный / белый |
| Количество страниц | 6* |
| Максимальное количество обновлений в час | 150 000 |
| Время работы от аккумулятора | 5 лет — 2 обновления в день* |
На плате есть две чёрные капли компаунда в форме восьмёрки — это бескорпусные микросхемы (COB, Chip-on-Board). Одна капля, скорее всего, скрывает RF-приёмник, а вторая — драйвер дисплея.
Антенна довольно крупная, с ферритовым сердечником. Есть информация, что RF-часть работает на частоте 36 кГц — Radio Force X4 (RFX4). Отреверсить протокол, скорее всего, маловероятно: нужна базовая станция от производителя и лицензионное программное обеспечение.
Самое полезное в этом устройстве — дисплей. Он потребляет мало энергии: ток потребления всей электроники составляет около 10 мкА ±.
Частота обновления — около 1 Гц. Забегая вперёд, можно разогнать её до нескольких десятков герц, но дисплей начинает заметно мерцать.
При загрузке дисплей долго инициализируется. Сначала он выводит изображение Busy..., а затем — изображение SES с логотипом производителя.
У дисплея, если не ошибаюсь, 33 36 выводов. В основном они используются для подключения конденсаторов подкачки и питания. Дисплей приклеен токопроводящим скотчем. Однако можно разрезать дорожки на задней стороне платы и подпаяться к достаточно крупным тестовым площадкам.
Некоторые полезные выводы дисплея, начиная с верхнего края:
| Вывод | Назначение | Тестпоинт |
|---|---|---|
| 1, 2 | GND | GND |
| 3 | DisplayCLK = 29 кГц — вход |
S1 |
| 4 | 1 Hz — выход |
4 |
| 5 | DIO |
S10 |
| 6 | CLK |
S11 |
| 7 | CS |
7 |
| 8 | RES |
8 |
Обновление: Возникло некоторое недопонимание по вопросу питания дисплея. Для него нужно дополнительное +3(3.3) В, выводы выше это только часть подключения. Для питания можно подключить к цепям батарейки или где удобно. Дисплей может иногда работать на паразитном питании от цифровых входов, но это лотерея и не правильно.
Есть сходство с дисплеями ST730x: некоторые команды совпадают. Размер памяти экрана больше, чем 256 × 128, поэтому в некоторых пределах можно прокручивать изображение большего размера.
Также похоже, что есть несколько экранных страниц (6???). Командой 0x4D можно менять изображения Busy... и SES.
Очень странный момент: при экспериментах с разными командами и питанием иногда на несколько секунд начинает проявляться изображение карточки товара из магазина. Оно достаточно чёткое — можно успеть прочитать даже мелкий шрифт.
Сначала я думал, что это микроконтроллер ценника отправляет что-то на дисплей при сбое. Однако аналогичный эффект возникает даже при полностью перерезанных дорожках SPI. Возможно, в контроллере дисплея есть энергонезависимая память? Это маловероятно. Скорее всего, произошла деградация кристаллов LCD из-за длительного отображения последней цены.
Изображение обновляется с частотой 1 Гц, однако этот сигнал 1 Гц является выходным сигналом дисплея и одновременно работает как сторожевой таймер. Если сигнал пропадает, микроконтроллер ценника заново запускает инициализацию.
Таким образом, сигнал 29 кГц — это входной тактовый сигнал. Его можно спокойно увеличить примерно в 10 раз: дисплей начнёт обновляться быстрее, но появится мерцание. Возможно, нужно еще менять настройки чтобы избавится от мерцания.
Протокол — двухсторонний 3-wire SPI, 9 бит на слово. В первом бите передаётся флаг «команда / данные». В драйвере, однако, можно обойтись только операциями записи.
Дисплею требуется инициализация. Впрочем, можно дождаться полной инициализации от штатного микроконтроллера ценника — конечного изображения с логотипом SES — и затем просто передавать свои данные. Полный экран занимает 4096 байт. Для такого варианта достаточно установить резисторы сопротивлением около 1 кОм в разрыв линий SPI между штатным микроконтроллером и дисплеем, а свой микроконтроллер подключить непосредственно к дисплею.
Для полноценного управления дисплеем со своего MCU нужно полностью перерезать дорожки SPI, RES и DisplayCLK.
SPI можно разогнать до 20 МГц и выше, но это никак не повлияет на FPS. Я пытался использовать DMA, но отказался от этой идеи из-за усложнения кода и аппаратной зависимости.
Мне кажется привлекательным использование нескольких дисплеев в одном устройстве, поэтому работа над драйвером велась с учётом возможности подключения нескольких дисплеев к одной шине.
Я написал драйвер этого дисплея для библиотеки olikraus/U8g2 версии ^2.36.18.
Однако драйвер пока сильно захардкожен и специфичен для ESP32-C3. Выложу его позднее.
Ниже приведён тестовый код на MicroPython для экспериментов( Raspberry Pi Pico - rp2040). Работать с ним гораздо удобнее, чем с вариантами на C-подобных языках:
Нажмите, чтобы развернуть MicroPython код
import rp2
import machine
import time
# Назначение GPIO пинов Raspberry Pi Pico
PIN_MOSI = 3
PIN_CLK = 2
PIN_CS = 5
PIN_RESET = 6 # Вывод сброса (Active LOW)
PIN_SYSCLOCK = 7 # Вывод генерации частоты накачки ~29 кГц (PWM)
# PIO программа для 9-битного SPI с паузой после 1-го бита
@rp2.asm_pio(
out_shiftdir=rp2.PIO.SHIFT_LEFT,
autopull=True,
pull_thresh=9,
out_init=rp2.PIO.OUT_LOW,
sideset_init=rp2.PIO.OUT_LOW
)
def spi_9bit_with_delay():
# Отправка 1-го бита (D/C)
out(pins, 1) .side(0)
nop() .side(1) [4]
nop() .side(0) [7]
# Цикл для оставшихся 8 бит
set(x, 7)
label("bit_loop")
out(pins, 1) .side(0)
nop() .side(1) [1]
jmp(x_dec, "bit_loop") .side(0)
# Инициализация конечного автомата PIO
sm = rp2.StateMachine(
0,
spi_9bit_with_delay,
freq=2000000,
out_base=machine.Pin(PIN_MOSI),
sideset_base=machine.Pin(PIN_CLK)
)
sm.active(1)
# Настройка пинов управления
cs = machine.Pin(PIN_CS, machine.Pin.OUT, value=1)
reset_pin = machine.Pin(PIN_RESET, machine.Pin.OUT, value=1)
# Функция отправки 9-битного пакета
def write_9bit(is_data, byte_value):
packet = ((1 if is_data else 0) << 8) | (byte_value & 0xFF)
cs.value(0)
sm.put(packet << 23)
while sm.tx_fifo() > 0:
pass
time.sleep_us(30)
cs.value(1)
# Функция сброса и инициализации
def reset_and_init():
print("init...")
reset_pin.value(0)
time.sleep_us(50)
reset_pin.value(1)
print(" end init 1")
time.sleep_ms(50)
write_9bit(is_data=False, byte_value=0x11) # Выход из сна. Нужна только эта команда для запуска 1Гц?
# Очистка дисплея, можно не делать - будет мусор
write_9bit(is_data=False, byte_value=0x2a) # c
write_9bit(is_data=True, byte_value=0x00) # d
write_9bit(is_data=True, byte_value=0x00) # d
write_9bit(is_data=False, byte_value=0x2c) # d
for i in range(4096):
write_9bit(is_data=True, byte_value=0xff)
print(" end init 2")
time.sleep_ms(10) # !
write_9bit(is_data=False, byte_value=0x4c) # c
write_9bit(is_data=True, byte_value=0x0c) # d
write_9bit(is_data=True, byte_value=0x00) # d
write_9bit(is_data=True, byte_value=0x00) # d
time.sleep_ms(4)
write_9bit(is_data=False, byte_value=0x4d) # c
write_9bit(is_data=True, byte_value=0xff) # d
write_9bit(is_data=True, byte_value=0x00) # d
write_9bit(is_data=True, byte_value=0x7f) # d
time.sleep_ms(1)
write_9bit(is_data=False, byte_value=0x4e) # c
write_9bit(is_data=True, byte_value=0x60) # d
print(" end init 3")
# =================================================================
# ЗАПУСК ПЕРИФЕРИИ И ДЕМОНСТРАЦИЯ
# =================================================================
# 1. Запуск постоянной генерации ~29 кГц на GPIO7
sysclock_pwm = machine.PWM(machine.Pin(PIN_SYSCLOCK))
sysclock_pwm.freq(29000) # Установка частоты 29 кГц 29000 29000*5(быстрее в 5 раз, начинает мерцать)
sysclock_pwm.duty_u16(32768) # 50% заполнение (меандр)
print("29 kHz GPIO7")
# 2. Вызов функции инициализации дисплея
reset_and_init()
# 3. Выводим тестовые полосы
write_9bit(is_data=False, byte_value=0x2a)
write_9bit(is_data=True, byte_value=0x00)
write_9bit(is_data=True, byte_value=0x00)
write_9bit(is_data=False, byte_value=0x2c)
for i in range(4096):
write_9bit(is_data=True, byte_value=0x0f)
# Некоторые рабочие команды
# write_9bit(is_data=False, byte_value=0x28) # c display off
# write_9bit(is_data=False, byte_value=0x29) # c display on
# write_9bit(is_data=False, byte_value=0x10) # c Sleep in
# write_9bit(is_data=False, byte_value=0x11) # c Sleep out
# write_9bit(is_data=False, byte_value=0x01) # c Software reset Из этого кода можно полностью понять процес инициализации и отображения тестовой картинки на дисплее.
У меня есть две версии платы 2012 и 2014 года, возможно есть и другие. Можно допустить что какие-то команды в вашем ценнике окажутся другими.
Помимо дисплея-донора, из ценника, скорее всего, можно сделать RF-приёмник, потребляющий единицы микроампер и работающий на частоте 36 кГц.
Две чёрные капли на обратной стороне платы связаны между собой, и одна из них явно является приёмником. Но это уже другая история.



















Дашборд
Что удалось вытащить про сам РЧ-кадр (захват с S2048, посылка 1980 символов):
Модуляция FSK, 600 бод, частоты ~37.8 и ~34.2 кГц, центр ~36 кГц, девиация ±1.8 кГц. Не QPSK. «Плавание» частоты на дампах это и есть девиация.
Линейный код импульсно-интервальный, поверх тактовой подложки 01. Бит несёт длина паузы между импульсами: 1 символ против 3, порог на 2. Не Манчестер.
Структура кадра: преамбул 324 символа, затем блок 108 символов, повторённый ровно 12 раз (мажоритарная защита важного поля), затем трейлер 360.
Универсальная единица данных это ячейка 27 символов. Полезная нагрузка блока это одна такая ячейка на смещениях 81–107, всё остальное синхронизация.
CRC на хвосте. Несущее поле трейлера это символы 1702–1857, оно обрывается ровно на 1857, дальше избыточный постамбул. Последние 8 символов это замыкающий CRC, ровно там, где патент WO2013072718A1 (того же производителя) кладёт контрольную сумму.
Про валидатор: это не простая контрольная сумма, одиночный флип символа всегда проходит. Поведение «один всплеск помех до 5 символов терпим, любой второй фатален» это следствие порогового квантования пауз, а не отдельного правила.
Где стена. По одному кадру нельзя однозначно перевести паузы в биты, восстановить полином CRC и семантику полей: нет известного эталона для привязки полярности и порядка бит. Дисплей эталон не даёт, порча адреса ведёт к отказу, а не к показу другого адреса.
Что реально сдвинет дело: захват заведомо другой посылки с того же S2048. Идеально нажатие EAN или адресация другого ценника. Сравнение двух кадров привяжет битовую сетку к известному изменению, и вот тогда CRC считается и можно синтезировать свою команду. Если у кого-то есть такой дамп (@dmg244, вроде записывал нажатия EAN), скиньте сырьё, приложу к своим и разберу поля.