Как я купил кота в мешке: реверс-инжиниринг электронных ценников. Часть 1. Знакомство с nrf52832

от автора

Всем доброго времени суток! Это моя первая статья, прошу не критиковать сильно.

Глава первая. Как я оказался тут.
TL;DR всем тем, кто не хочет читать предисторию из жизни от первого лица, листайте сразу на вторую главу

Как-то давным-давно я заинтересовался дешевым железом, ибо был студентом-ардуинщиком, который ОООЧЕНЬ хотел экономить, готовый был поступиться удобством использования экранов, микроконтроллеров и железа в целом. И как-то раз, пришла идея — купить E-INK дисплей. Глянул на али — цены за 700 руб за малый дисплей 2.9″. А я студент, без стипендии, с ЗП с подработок(200$ за мобилку, которую делал недели 2, за месяц не более 400-600$).
Тратиться на дисплей ради «побаловаться» — не хотелось.
Поэтому, решаю зайти на авито и начать его шерстить. Искал, искал. Да и вроде нет ничего. Забил на это на время. Решил вернуться к повседневности. Однако через пару дней, просто зайдя в авито, нахожу объявление о продаже электронных ценников. О чудо! Подумал я. Там было 250 рублей за ценник, где есть плата с контроллером, есть корпус и оно даже работает.. скорее всего.
Решил заказать партию из 5 штук сначала. Продавец быстро отправил их по Авито доставке. Я жду, уже копаю информацию на них, но ничего не нахожу.

Проходит неделя, иду я с зубного, с замороженным лицом, зимой, вижу — статус посылки, доставлен в постамат. Прохожие только и видели мои сверкающие тапки, ибо я буквально чуть ли не бежал, при этом на моем онемевшем лице виднелась улыбка до ушей, вот настолько я ждал посылку.

Прихожу в пятерочку, что одновременно является постаматом для авито доставки, получаю посылку, на месте же вскрываю(отойдя от кассы), в коробке лежат заветные 5 ценников. Где 2 из них разбиты(один сразу увидел, а другой только дома, как разобрал). Что ещё замечаю, что на них… Цена рыбы, хлеба и другой продукции магнита или пятерочки или другого магазина. Ладно, подумал я, в зубы дарёного коня не смотрят, да и дисплеи 2.7 дюймовые.

Ценники
Вид ценника

Вид ценника
Разбитые экраны. Показывают все так же, но... Уже не способны будут выводить что-то

Разбитые экраны. Показывают все так же, но… Уже не способны будут выводить что-то

Придя домой, обнаруживаю, что чуть ли не все из них были залиты то ли водой, то ли электролитом, что один ещё разбит дисплей, а батарейки залили всю плату окислив её. Т.е партия битая, где живо всего 3 дисплея, возможно микроконтроллеры СС22 что-то там.

Как оно выглядит под капотом
фото платы спереди

фото платы спереди
Фото платы сзади

Фото платы сзади

Пишу продавцу, выясняю отношения. Он говорит, мол, ок, понимаю, не проверял, на складе валялись. Могу компенсировать, отправить поврежденные по новой, но оплати посылку, мол, купи ещё их, докину. Ну а я что. Купил 10 сразу, он скидку сделал, отправил вообще 17 ценников и не зря. По получению, 6-7 из них были так же разбиты, 4-5 неисправимо в коррозии и только парочку с нормальными платами и дисплеями.

Часть этой партии

Часть этой партии

Окей, подумал я. Попытался, искал как прошить их, не нашел на этих платах сначала как прошивать(ничего не было, а пятаки разные и непонятные вообще). Потом обнаружил, что в зависимости от ревизии ценника(хоть они и одинаковые вроде внешне, но платы разных ревизий) у них разная плата и разводка. Нашел на одной 4 пина выведенных, площадки для пого джамперов. Но на тот момент я умел только в ардуинку, и сериала на этих пинах не было.
Ценники упали в долгий ящик, собирать пыль.

Проходит буквально пол месяца, на авито я нахожу предложение, мимо которого я не мог отказаться никак. В этот день мы с девушкой планировали пойти на свидание(ресторан, прогулка, все дела), но как вы поняли моя любовь к железу не слабее любви к девушке. Поэтому, я уместил ВКУСНУЮ встречу с продавцом ДО свидания(буквально по пути заехал на автобусе и рюкзаком).
Что я купил. Купил я, КОРОБКУ ЦЕЛУЮ ЗАБИТУЮ ДО КРАЕВ ЦЕННИКОВ ЭЛЕКТРОННЫХ, за 500 рублей. Халява чистая, ибо дисплей один стоил на али 700, а тут ЯЩИК с такими дисплеями. Еле еле поместив его в рюкзак, пересыпав и разложив его до размеров ручной клади.

Фото этих ценников в пакете, в котором хранились последние 2 года.

Фото этих ценников в пакете, в котором хранились последние 2 года.
Разобранный вид(и разложенный, плата обычно ЗА дисплеем)

Разобранный вид(и разложенный, плата обычно ЗА дисплеем)

Вы не поверите, что я тогда испытывал))
По пути, в автобусе, я уже ломая ногти, разбирал один из ценников. Добравшись до внутренностей(дисплей да плата с батарейкой).
Я увидел во первых 4 выведенных пина пого(площадки на плате). Во вторых, что батарейки на проводах отсоединяются штекером. И, самое ключевое, я увидел надпись на чипе, nRF52832.
И у меня в голове, что-то щелкнуло, ведь, у меня дома валялись nrf51, а это, вроде бы был передатчик радио???
ЧТО?
Не прошло и 5 секунд, как я дрожащими руками от предвкушения гуглил этот чип. И да, я нашел его. Наверное, такое не стоит говорить, но этой находке я был рад больше чем свиданию(хотя наверное совру). Мне очень хотелось домой потыкаться, но себя я пересилил, проведя хорошо время с девушкой и проводя её до дома(в 23:30).
Придя домой, я не снимая куртки, побежал в свою комнату, включив паяльник и пытаясь подпаяться ардуинкой… Потому что очень верил в её возможности всемогущие. Но увы.
Расстроенным пошел в кровать, где провалялся пол ночи в телефоне, изучая все это. Пока изучал, услышал про SWD какой-то и JTAG от того же производителя за более чем 7000… Тратить такую сумму в планы не входило. Почитал про другие, кто имеет SWD. В очередной раз увидел информацию про STM32. На месте заказал с али bluepill(stm32f103) и st-link v2. Это уже стоило мне состояния на тот момент, но хотелось разобраться и изучить, что такое эти ваши STM32 и с чем их есть. Они шли, но долго.

TL;DR: Здесь я провел несколько месяцев, пытаясь реверсить стек Bluetooth и выкачивая прошивку через ESP32(удачно, но бессмысленно), а также безуспешно пытался управлять дисплеем напрямую. И даже пытался писать производителю, мол, дайте наработки/код или хотя бы как общаться с ценниками, рассказав, что купил их на Авито и что они все равно не поддерживаются больше — был проигнорирован. Кому интересны эти грабли — напишите в комментариях, сделаю вторую часть.

Так как у меня не получилось как либо завести контроллер, было принято решение, выпаять его и попытаться управлять вручную всем этим дисплеем, используя плату как драйвер питания и разъем для дисплея. Аккуратно сдув феном чип, я подпаялся к контактам идущим от дисплея(5 дорожкам), перебирая их, SPI 4 line, 3 line через ESP32 и библиотеку для работы с E-Ink дисплеями(разных контроллеров дисплея). Но… В итоге ничего не удалось, ни запустить, ни добиться хоть какого-то результата, кроме платы без чипа с распаянными контактами всего.

Руки опускались, пыль собиралась. Я решил все таки пойти по пути самому логичному с самого начала. К тому моменту STM32 пришли как месяца два назад. Я уже чутка умел работать с STM, cubeIDE, разобрался в st-link и том, как работает он.
Но т.к интерфейсы по сути swd у st-link я попробовал подключить их к платке ценника. Но… Увы. Он отказывался видеть что угодно кроме STM32.
Где-то нашел утилитку, которая могла превратить st-link v2 в поддерживаемый программатор jtag, нашел патч для китайской болванки(st-link v2 моего), пропатчил ручками бинарь, по инструкции через notepad++ ииии…. Получил кирпич программатор. Единственный кстати программатор, что у меня был..
Кажется, тупик. Без фирменного программатора за миллион деняк это просто груда хлама…
Заказал новый китайский st-link(новые 2 штуки, на всякий).

Глава Вторая. Озарение и получаемый опыт.

Время идет… прошел в общей сложности наверное почти год, как я пытаю эти ценники. Я обзавожусь ch32v003 посмотрев видео bitluni на ютубе про кластер из микроконтроллеров за 10 центов!!!
Заказываю с десяток чипов, заказываю набор с программатором wch linkE китайским для risc-v этого.
Балуюсь, пишу уже уверенно код, написал 3д демку с выводом на i2c экран, где есть реальный рендеринг в 3д и какая-то демо сцена в 32КБ памяти.
И пока баловался, я вижу какой-то режим странный у программатора может быть. CMSIS-DAP.
И тут у меня всплывает в голове теория, которую я начитался и наобщался с ИИ.
Что это СТАНДАРТ для общения с ARM. А какое ядро у nrf52?? arm cortex m4. БИНГО!!
Бросаю все то, что я делал до этого, включаю паяльник, быстро запаиваю гребенку(на этот раз). И относительно быстрым перебором подключаю верно.
Но теперь вопрос. как проверить что оно работает?
Тут же я вспоминаю, что есть openocd. Немного повозившись, я клепаю при помощи ИИ простенький конфиг(я бы его с радостью вставил, но не смог его найти, ибо статью я пишу где-то спустя год с этого момента).
И, о, чудо, спустя пару попыток и с десяток исправлений ошибок, у меня удается запустить чип, подключиться по telnet к gdb и сделать halt процессора! Ура! Я смог ХОТЬ ЧТО-ТО сделать. Слегка побаловавшись, я думаю, как бы написать программу. Ищу sdk в интернете, но нормально не нахожу ничего. Ловлю опять состояние тильта, ухожу делать другие проекты.

Параллельно с этим, начинаю заниматься перепрошивкой своего осциллографа, убивая прошивку на нем. Приходится учится работать с прошивкой «взрослых» микроконтроллеров(GD32f407VET6).
У меня мак, cubeIDE не хочет ставиться старый, где программатор проглатывал клоны STM32.
Увы, я в тупике. Но погуглив, нахожу прошивку открытую, open5012H, для этого МК и этого осциллографа.
И какое чудо, что он прошивался прямо собирался и линковался без всяких IDE, через make.
Тогда, работая с прошивкой осциллографа я научился тому, что всякие IDE это лишь шелуха и собирать можно без неё.

Спустя время, понабравшись опыта, я опять возвращаюсь к этому проекту, с прошивкой чертовых ценников, которые я мучаю уже 2-ой год.
На этот раз я почти не трачу свое время на то, чтобы как-то понять, мне уже все понятно стало. Часик-второй с ИИ(claude sonnet) и знаниями, что у меня есть и я уже запускаю hello world на чипе, отлаживая через vscode и cortex debug расширение, где могу читать состояния, переменные. Пока без SDK, но загрузив произвольный код, который я сам написал, собрал и слинковал, загрузив в чип. Маленькая победа.
Чуть посидев подольше, я обнаружил, что SDK nordic есть в vscode….. Давно ли он там был или появился относительно недавно, сказать не могу, но могу лишь описать, каким я себя тупым ощущал тогда, что нашел только сейчас это, хотя я искал в прошлый раз везде…

Чуть погодя, я потыкался в разные примеры, запустил с BT стеком пример, собрал проект, нашел прошивку merged.hex в файлах, где уже было все нужное, подключился так же по telnet к gdb:

telnet localhost 4444

И выполнил внутри:

program /Users/user/temp/nrf52/exc/peripheral_uart/build/merged.hex verify reset

И как итог — запустив на телефоне «serial Bluetooth terminal» я нашел свой злосчастный ценник с названием Nordic_UART_Service. Подключившись, я увидел.. Ничего. он не отвечал. А все почему? Потому что это рестранслятор UART физического..
Чуть повозившись, я смог запустить эхо. Он повторял все то, что я ему отправлял по сериал порту(далее просто буду говорить, что по BT/BLE).
Чутка почистив код от мусорных для меня кусков(а это почти 80% всего кода примера), я оставил только нужное, без чего BT не заводился.
И тут я начал баловаться просто с очередной «игрушкой» в виде микроконтроллера. Побаловавшись, я решил запустить дисплей, пошел гуглить примеры, нашел даже парочку прямо в примерах sdk, но там не было драйверов для дисплея, да и не знал я как подключить дисплей… Как он подключен и через какие контакты.

Вы не поверите, сколько я времени провел под микроскопом с тестером с прозвонкой, чтобы только примерно накидать разводку платы не снося компоненты(это я делал главу назад, когда декомпилировал прошивку). Но в какой-то момент это надоело, подумал, у меня же есть донор уже, где я снес чип, чтобы подключиться напрямую. Чего бы мне не снести совершенно все, оставив только текстолит? А идея оказалась славной

Плата в развернутом виде
верхняя часть с сдутыми компонентами и подчищенными пятаками для прозвонки

верхняя часть с сдутыми компонентами и подчищенными пятаками для прозвонки
Нижняя часть платы

Нижняя часть платы

Я за пару минут вычислил что дисплей работает по 3-line SPI шине(т.е MOSI и MOSO объединены), ибо шло только 5 контактов от дисплея, где 2 из них должны идти на busy и rst, т.е остается всего 3 контакта на линии данных. И ещё один отдельный контакт на «транзистор», для подачи питания(не от дисплея, но в контуре формирования «высоковольтной» части питания).
Для тех, кто потом не будет копаться в исходниках, просто скажу, если вдруг у вас откуда-то те же ценники.

Даташит и плата, сопоставление дорожек
Подписанное и обведенное что куда идет

Подписанное и обведенное что куда идет
Картинка даташита чипа

Картинка даташита чипа

Набор пинов такой:

//пины формата: P.08, P.11 и тд//spi#define PIN_CS   8#define PIN_CLK  11#define PIN_MOSI 12//display controls#define PIN_BUSY 6#define PIN_RST  7#define PIN_VCC  19

И как я уже писал выше, я находил документацию, но на похожий контроллер только, но не этого дисплея, но с ним дисплей уже заводился, тогда почему бы нам просто не портировать эту библиотеку с питона на СИ?
Поскольку у нас SPI оказался нестандартным (MOSI и MISO объединены), использовать аппаратный SPI контроллера nRF было бы той еще болью. Поэтому я быстро набросал программный SPI (SoftSPI) и написал базовые обертки для отправки команд и данных, опираясь на вызвоненные пины. Выглядело это до безобразия просто:

/* soft_spi.c */// 9-bit transmission: 1 bit (C/D) + 8 bits datavoid soft_spi_write_9bit(uint8_t data, uint8_t is_data) {    if (!spi_gpio_dev) return;    gpio_pin_set(spi_gpio_dev, PIN_CS, 0);    // 1. Command/Data Bit (0=Cmd, 1=Data)    gpio_pin_set(spi_gpio_dev, PIN_MOSI, is_data ? 1 : 0);    gpio_pin_set(spi_gpio_dev, PIN_CLK, 1);    k_busy_wait(1);     gpio_pin_set(spi_gpio_dev, PIN_CLK, 0);    // 2. 8 Data Bits (MSB first)    for (int i = 0; i < 8; i++) {        gpio_pin_set(spi_gpio_dev, PIN_MOSI, (data & 0x80) ? 1 : 0);        gpio_pin_set(spi_gpio_dev, PIN_CLK, 1);        k_busy_wait(1);         gpio_pin_set(spi_gpio_dev, PIN_CLK, 0);        data <<= 1;    }    gpio_pin_set(spi_gpio_dev, PIN_CS, 1);}/* end soft_spi.c */static void send_cmd(uint8_t cmd) {    soft_spi_write_9bit(cmd, SPI_CMD);}static void send_data(uint8_t data) {    soft_spi_write_9bit(data, SPI_DATA);}void ssd1675a_wait_busy(void) {    // Дисплей тугодум, поэтому ждем, пока пин BUSY не отпустит    int timeout = 10000;     while (gpio_pin_get(eink_gpio_dev, PIN_BUSY) == 1 && timeout > 0) {        k_msleep(2);        timeout--;    }}

Потратив один вечер и изнасиловав свою подписку Antropic, я родил переделанное чудо. От легкой либы SoftSPI для нужд дисплея и до мини либы для ssd1675a, которая использовала те же LUT таблицы, что и оригинальная либа и полностью была калькой её.
Дисплей с первого раза не ожил, но поколдовав с отладкой, проверкой сигналов, обнаружилось, что на транзистор нужно подавать перевернутый сигнал. Окей. И опять что-то не так. Смотрю, ищу проблему, долго анализирую, что не так. Решаю промерить линии SPI, там пусто… Почему? Внимательно перечитываю код в main, и обнаруживаю досадную ошибку. Я вызываю инициализацию, даже вроде шлю информацию на дисплей(буферы), но не произвожу отправку конфигурации регистров(из python либы):

static void configure_registers(void) {    send_cmd(0x74); // Set analog block control    send_data(0x54);    send_cmd(0x7E); // Set digital block control    send_data(0x3B);    send_cmd(0x01); // Driver output: 296 gate lines (0x0127 + 1)    send_data(0x27);    send_data(0x01);    send_data(0x00);    send_cmd(0x3A); // Dummy line period, part of panel scan timing    send_data(0x35);    send_cmd(0x3B); // Gate line width, part of panel scan timing    send_data(0x04);    send_cmd(0x3C); // Border waveform control    send_data(0x33);    send_cmd(0x11); // Data entry mode    send_data(0x03); // Auto-increment X and Y after RAM writes    send_cmd(0x44); // RAM X range in bytes: 0..15 = 128 pixels    send_data(0x00); // X start    send_data(0x0F); // X end: 128 / 8 - 1    send_cmd(0x45); // RAM Y range: 0..295    send_data(0x00); // Y start low byte    send_data(0x00); // Y start high byte    send_data(0x27); // Y end low byte: 0x0127 = 295    send_data(0x01); // Y end high byte    send_cmd(0x04); // Source driving voltage settings    send_data(0x41);    send_data(0xA8);    send_data(0x32);    send_cmd(0x2C); // VCOM voltage, affects contrast and ghosting    send_data(vcom_register_value);}void ssd1675a_init(const struct device *gpio_dev) {    eink_gpio_dev = gpio_dev;    if (!device_is_ready(eink_gpio_dev)) return;    gpio_pin_configure(eink_gpio_dev, PIN_VCC, GPIO_OUTPUT_ACTIVE);    gpio_pin_configure(eink_gpio_dev, PIN_RST, GPIO_OUTPUT_ACTIVE);    gpio_pin_configure(eink_gpio_dev, PIN_BUSY, GPIO_INPUT);      soft_spi_init(gpio_dev);      //подаем питание на дисплей.    gpio_pin_set(eink_gpio_dev, PIN_VCC, 0); // ON (Active Low P-MOS assumed)    k_msleep(10);      // Вот тут забыл вызвать функцию, исправляю вызвав её    configure_registers(); }

Ладно, быстро пофиксив эту ошибку…(?) у нас все заработало!!! УРА!
Кстати, самые внимательные сейчас увидят в коде ниже заголовочные файлы <zephyr/...>. Откуда тут взялась Zephyr RTOS, если я до этого ковырял голые регистры? Это тот самый SDK от Nordic (nRF Connect SDK), который я нашел в VSCode, под капотом базируется именно на Zephyr. Потыкавшись в него, я понял, что нет смысла бороться с системой и писать гору платформозависимого бойлерплейта. Device Tree, готовые функции для работы с GPIO и удобные системные задержки вроде k_msleep — зачем от этого отказываться? Как говорится: «Можно, а зачем?»
Попробуем вывести туда простенький квадратик:

#include <zephyr/kernel.h>#include <zephyr/device.h>#include <zephyr/drivers/gpio.h>#include <string.h>#include <drivers/ssd1675a.h>static void draw_filled_square(uint8_t *bw_buffer,                               int x, int y, int size){    // Ширина экрана 128 пикселей, а это 16 байт    int bytes_per_row = 16;     for (int yy = y; yy < y + size; yy++) {        for (int xx = x; xx < x + size; xx++) {            int byte_idx = yy * bytes_per_row + (xx / 8);            int bit_idx = 7 - (xx % 8);            bw_buffer[byte_idx] &= ~(1 << bit_idx);        }    }}static void display_square(void){    static uint8_t bw_buffer[EINK_BUFFER_SIZE];    static uint8_t red_buffer[EINK_BUFFER_SIZE];    memset(bw_buffer, 0xFF, sizeof(bw_buffer));   // White screen    memset(red_buffer, 0x00, sizeof(red_buffer)); // No red pixels    draw_filled_square(bw_buffer, 40, 84, 48);    ssd1675a_display_buffer(bw_buffer, red_buffer);    ssd1675a_load_default_lut(); // Ensure standard LUT is active    send_cmd(0x22);     send_data(0xC7); // Update Control (from Python)    send_cmd(0x20);     ssd1675a_wait_busy();}int main(void){    gpio_dev = DEVICE_DT_GET(DT_NODELABEL(gpio0));    if (!device_is_ready(gpio_dev)) {        return 0;    }        ssd1675a_init(gpio_dev); //инициализируем дисплей      display_square(); // выводим квардат    // дисплей уводим в сон    send_cmd(0x10); // Deep Sleep    send_data(0x01);    // отключаем ему питание, чтобы не потреблял    gpio_pin_set(eink_gpio_dev, PIN_VCC, 1); // OFF (Active Low P-MOS assumed)    k_msleep(10);    // сами уходим в сон, т.к задачу выполнили    while (1) {        k_sleep(K_FOREVER);    }    return 0;}

Мы смогли выводить данные какие-то на дисплей!!
Юху!!

Моя первая победа!!!

Моя первая победа!!!

Глава 3. А что если…

Мы можем выводить изображения на дисплей! Ура! Пока имеем проблемы с цветом, но это исправил тем, что добавил отдельный буфер для красного как и было в python библиотеке.
Писать напрямую в буфер дисплея — то еще удовольствие, как и вызывать SPI команды ручками. Нам нужна абстракция. Сложность в том, что у e-ink нет привычного байта на пиксель. 1 пиксель = 1 бит. Чтобы зажечь конкретную точку, нужно найти правильный байт в массиве и сделать побитовый сдвиг. А про цвета.. Это разные буферы, что передаются разными командами в дисплей. Поэтому это было необходимо, чтобы потом не плодить макаронную фабрику и не тянуть зависимость от дисплея.
Вот пример того, как мы пишем пиксель(абстрактно):

void graphics_draw_pixel(int x, int y, int color) {    // Вычисляем индекс байта в массиве    int idx = y * (active_canvas->width / 8) + (x / 8);        // Вычисляем конкретный бит в этом байте (справа налево)    int bit = 7 - (x % 8);        // Записываем пиксель (1 = Белый, 0 = Черный)    if (color == GFX_BLACK) {         active_canvas->bw_buffer[idx] &= ~(1 << bit);        } else if (color == GFX_WHITE) {        active_canvas->bw_buffer[idx] |= (1 << bit);         } else if (color == GFX_RED) {        active_canvas->bw_buffer[idx] |= (1 << bit);     // Белая подложка        active_canvas->red_buffer[idx] |= (1 << bit);    // Красный пиксель во втором буфере    }}

Имея функцию отрисовки точки, написать линии, квадраты и даже вывод шрифтов было уже делом техники.
Супер! У нас есть теперь есть все инструменты. Теперь самое время написать вывод фрактала Мандельброта!
Пишется он легко, не будем на этом заострять внимание, только возьмем за факт, что будем использовать 3 имеющихся у нас цвета. Черный, красный и белый.

Фрактал на e-ink ценнике с цветом

Фрактал на e-ink ценнике с цветом

Ноо, цвета получаются какие-то блеклые… И обновление занимает аж 7 секунд непрерывной эпилепсии… Это никуда не годится…

Ну чтож, думаю для первой части этого более чем достаточно?) Во второй части расскажу, как e-ink дисплей заставил меня курить даташиты волновых форм, разбор работы LUT и что это такое, как я случайно «сжег» экран неправильными таймингами и как мне удалось ускорить обновление с 7 секунд до 650 миллисекунд. Будет ещё много интересного и сочного.

Заключение(от меня текущего)

Это моя первая статья на Хабре и для меня, этот проект, стал одним из тех, о которых я хотел рассказать и поделится с публикой. Да и многие знакомые просили начать писать статьи, ибо им нравился контент в моем «блоге» в запрещенограмме, где я выкладывал каждый свой этап каждого такого проекта со своими мыслями и эмоциями.
А так же, буду рад конструктивной критике по подаче и содержимому. Может, кто-то уже ковырял ценники подобного характера или статья вам кажется слишком перегруженной кодом(слишком много «лишнего») или её отсутствием. Так же, может стоит чем-то дополнить статью или о чем-то рассказать в следующей — пишите, с радостью обсужу с вами.

А так же, хотел сказать, что эта вся история, что повествуется в статье, была растянута на очень долгом периоде жизни, могут быть неточности во времени или деталях(смутно их помню). По этому прошу за это не цепляться. Вторая часть будет более свежей(по деталям, коду), ведь все то, что я там буду писать, происходило месяцев 4-5 назад.

Github репозиторий выложу в следующей части, так как старого кода нет(о котором повествуется), есть новый, в котором переплетено все очень сильно с новыми данными и вводными, ввиде LUT таблиц, partial update, энергоэффективностью чипа, особенностями оптимизации и «вылизывания» кода с кучей разных фич.

ссылка на оригинал статьи https://habr.com/ru/articles/1044406/