Как мы сделали Embedded Controller для ПЛК на Linux

6d32f8349dedd51c608cab7aab57bc78.jpg

Наша компания разрабатывает и производит оборудование для автоматизации и у нас есть флагманский продукт — контроллер Wiren Board на ОС Linux. Его ставят почти в каждую инсталляцию, поэтому мы стараемся его постоянно улучшать: используем более производительные процессоры, добавляем больше оперативной и постоянной памяти, даём пользователям новые возможности по подключению сторонних устройств.

Осенью 2023 года вышел новый Wiren Board 7.4, который почти ничем не отличается от предшественника Wiren Board 7.3, но стал важным шагом на пути к Wiren Board 8 — в нём появился Embedded Controller (EC).

Мы уже рассказывали о том, как приручили робота-пайщика, как делаем устройства и тестируем их. Теперь хотим поделиться опытом разработки полноценного Embedded Controller — какие задачи решает, как устроен и что у него под капотом в прошивке.

Плата контроллера Wiren Board 7.4

Плата контроллера Wiren Board 7.4

Задачи

Конечно, часть задач можно решить без микроконтроллера и программирования на отдельных дискретных и логических элементах, но это усложняет проектирование и отладку, а также увеличивает себестоимость BOM (Bill of Materials, список компонентов).

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

Слева контроллер Wiren Board 7.3.4 в который был вживлен Embedded Controller (EC), использовался в начале разработки прошивки. Справа — новый Wiren Board 7.4 с EC на плате

Слева контроллер Wiren Board 7.3.4 в который был вживлен Embedded Controller (EC), использовался в начале разработки прошивки. Справа — новый Wiren Board 7.4 с EC на плате

Power Sequencer

Основной задачей, которая сложно решалась дискретными компонентами, было управление питанием. Wiren Board 7 может питаться от разных источников: внешнее питание, PoE, два USB-C, встроенный источник резервного питания WBMZ, — и каждый из них имеет свои особенности:

  • Внешний источник поддерживает диапазон от 9 до 48 В, однако на клеммник Vout для питания Modbus-устройств нельзя выдавать более 28 В — многие из них не выдерживают напряжения больше.

  • На USB-C приходит напряжение 5 В. Этого достаточно для питания контроллера, но недостаточно для питания Vout и зарядки модулей WBMZ-*.

  • Модуль WBMZ4-BATTERY имеет встроенный повышающий преобразователь и выдаёт 12 В. Из-за необходимости сохранять совместимость с предыдущими версиями мы не могли изменить это, поэтому пришлось ввести правило: зарядка WBMZ включается только при наличии внешнего питания выше 12 В.

Теперь у нас EС следит за тем, где какое напряжение есть и самостоятельно выбирает оптимальный источник питания контроллера.

Схемы управления питанием в версиях 7.3 и 7.4

Схемы управления питанием в версиях 7.3 и 7.4

Фрагмент кода EC. Проверка наличия внешнего питания

// Проверяем, что питание есть
if (vcc_5v_ok) {
    // Питание есть - включаемся в обычном режиме
    // (просто идём дальше)
    // WBMZ включится отдельным алторитмом, если Vin будет более 11.5 В
} else {
    // Питания нет - включаем WBMZ
    linux_cpu_pwr_seq_enable_wbmz();
}

// Если дошли до этого места, надо включиться в обычном режиме
rtc_disable_periodic_wakeup();
new_state(WBEC_STATE_WAIT_STARTUP);

Так как EC может следить за всеми рейками питания контроллера, то мы реагируем на проблемы с ними. Например, если контроллер питается от отладочного порта через плохой USB-C кабель, то может проседать напряжение 3.3 В. В этом случае пробуем перезапустить питание контроллера, а если такое происходит очень часто, то выключаемся совсем.

Фрагмент кода EC. Проверка наличия 3.3 В

// Если пропало 3.3В - пробуем перезапустить питание, но не более N раз за M минут
// Если питание пропадает слишком часто - выключаемся
// Это происходит, например, при питании через плохой USB кабель.
// В результате PMIC выключается, но питание на линии 5В остаётся.
// Ограничение по числу попыток нужно, чтобы избежать циклического перезапуска.
if (!vmon_get_ch_status(VMON_CHANNEL_V33)) {
    if (!vmon_get_ch_status(VMON_CHANNEL_V50)) {
        // Если при этом нет напряжения на линии 5В - это означает, что выдернули питание
        // и не надо пытаться включиться заново
        linux_cpu_pwr_seq_hard_off();
        new_state(WBEC_STATE_POWER_OFF_SEQUENCE_WAIT);
    } else {
        if (systick_get_time_since_timestamp(wbec_ctx.power_loss_timestamp) < (WBEC_POWER_LOSS_TIMEOUT_MIN * 60 * 1000)) {
            wbec_ctx.power_loss_cnt++;
        } else {
            wbec_ctx.power_loss_cnt = 0;
        }
        wbec_ctx.power_loss_timestamp = systick_get_system_time_ms();
        if (wbec_ctx.power_loss_cnt > WBEC_POWER_LOSS_ATTEMPTS) {
            console_print_w_prefix("Reaching power loss limit, power off and go to standby now\r\n");
            // Чтобы включиться - нужно нажать кнопку или сбросить внешнее питание
            linux_cpu_pwr_seq_hard_off();
            new_state(WBEC_STATE_POWER_OFF_SEQUENCE_WAIT);
        } else {
            console_print_w_prefix("3.3V is lost, try to reset power\r\n");
            console_print_w_prefix("Enable WBMZ to prevent power loss under load\r\n");
            wbec_info.poweron_reason = REASON_PMIC_OFF;
            linux_cpu_pwr_seq_enable_wbmz();
            linux_cpu_pwr_seq_hard_reset();
            new_state(WBEC_STATE_POWER_ON_SEQUENCE_WAIT);
        }
    }
}
break;

Ещё один интересный сценарий: при питании контроллера от USB-C EC задерживает подачу питания на процессор на пять секунд. Это нужно, чтобы на компьютере, к которому подключен контроллер, успели загрузиться драйверы USB-Serial и пользователь увидел в консоли первые строчки логов U-Boot.

Пауза перед стартом контроллера при питании от USB-C, чтобы вывод U-Boot не пропадал

Пауза перед стартом контроллера при питании от USB-C, чтобы вывод U-Boot не пропадал

Watchdog

Другой задачей для EC стал сторожевой таймер Watchdog, который следит за тем, чтобы ПО контроллера не зависало. У нас два сторожевых таймера:

  • аппаратный — отсчитывает заданное время и, если никто его не сбросит, то он перегружает контроллер по питанию;

  • программный — следит за сервисами ОС и сбрасывает аппаратный таймер, чтобы тот не перезапускал контроллер.

Две схемы: сторожевой таймер на компараторе и на EC

Две схемы: сторожевой таймер на компараторе и на EC

Программный реализован на стандартном сервисе watchdog, а вот аппаратный раньше реализовывался на компараторе, и иногда такая простота приносила проблемы. Например, в одной из ревизий контроллера сторожевой таймер мог из-за утечек не сработать во влажной среде, если плата контроллера была загрязнена пылью (ERRWB600011). Мы это починили улучшением схемотехники в новых ревизиях, но осадочек остался.

Теперь аппаратный сторожевой таймер реализован на EC, который перезагружает контроллер по питанию в случае отсутствия сигнала от программного сервиса или загрузчика.

Фрагмент кода EC. Перезагрузка контроллера по питанию

// Если сработал WDT - перезагружаемся по питанию
if (wdt_handle_timed_out()) {
    wbec_info.poweron_reason = REASON_WATCHDOG;
    console_print("\r\n\n");
    console_print_w_prefix("Watchdog is timed out, reset power.\r\n");
    linux_cpu_pwr_seq_hard_reset();
    new_state(WBEC_STATE_POWER_ON_SEQUENCE_WAIT);
}

Если какой-то из отслеживаемых программным сторожевым таймером сервисов будет работать нестабильно, то контроллер невозможно будет загрузить из-за постоянных срабатываний аппаратного сторожевого таймера. Чтобы пользователь смог загрузить контроллер и устранить неполадку, предусмотрен специальный сигнал WD_OFF. В первую очередь он отключает сторожевой таймер, поэтому мы повесили его на EC_RESET для 100% железной работы — весь ЕС отключается, пины переходят в HiZ, а схемотехника сделана так, что питание на процессор в этом случае продолжает поступать. Как побочный эффект — отключается и всё остальное, сделанное на ЕС, но это не очень критично, так как сторожевой таймер отключается только при поиске неисправностей.

Индикатор работы EC и кнопка отключения аппаратного сторожевого таймера

Индикатор работы EC и кнопка отключения аппаратного сторожевого таймера

Чтобы при отключенном EC контроллер Wiren Board всё равно мог включиться, мы сделали специальную схему задержки подачи питания на процессор. За это отвечает RC-цепочка R38-C32, которая и даёт задержку при включении питания: когда подается питание на клеммник, то питание на процессор не поступает в течение примерно 1 секунды. За это время ЕС решает что делать: перехватить питание и держать отключенным или включить. В то же время, если в ЕС нет прошивки или он ещё как-то не работает, контроллер всё равно включится сам.

Схема формирования задержки подачи питания на процессор

Схема формирования задержки подачи питания на процессор

Найдите отличия в разных версиях сторожевого таймера

На картинке мы собрали выдержки из схем базовых плат контроллера, где мы периодически улучшали аппаратный сторожевой таймер. Описывать изменения не будем, что-то можно почерпнуть из комментариев в схемах, а что-то — заметить самому. Картинка в высоком разрешении.

147988238003f3f13b873282c067ee2b.png

RTC и будильник

У нас обычный Debian Linux, поэтому при работе контроллера он синхронизирует время по NTP, если тот доступен. Но когда контроллер выключен, время отсчитывает аппаратный RTC. Раньше мы делали его на специальной микросхеме, теперь этим занимается EC-контроллер. Это позволило нам предложить пользователю новую фичу — пробуждение по будильнику. 

Контроллеры Wiren Board нередко используют на удалённых объектах: стабильная работа, аппаратный сторожевой таймер, много интерфейсов в одном корпусе и возможность установить 3G/4G-модем для отправки данных в систему верхнего уровня.

Представьте себе кучу метеостанций, которые стоят вдоль реки и питаются от автомобильных АКБ. Раз в несколько месяцев на станции приезжает машина и меняет АКБ на заряженные, поэтому заряд АКБ нужно экономить. Вот на таком объекте теперь тоже можно установить наш контроллер и он будет несколько раз в день просыпаться, делать замеры, отправлять данные на сервер и снова засыпать.

Другой сценарий — работа на удаленном объекте со стационарным питанием. В контроллер можно установить модуль бесперебойного питания WBMZ, от которого он может проработать несколько часов. При пропадании питания на объекте можно сообщить об этом в систему верхнего уровня, передать какую-то телеметрию и заснуть на часок. Потом проснуться, снова сообщить телеметрию и уснуть, — и так до тех пор, пока не появится внешнее питание или пока не сядет встроенный АКБ контроллера. Всё это время оператор будет получать телеметрию с объекта и точный статус внешнего питания.

Будильник работает через стандартный rtcwake. Например, следующая команда выключит контроллер и включит его через 60 секунд:

rtcwake -m off -s 60

Кнопка ON/OFF

Изначально выключатель контроллера появился в виде переключателя в 4-й версии контроллера, но к 5-й стал опцией. В 6-й версии он стал маленьким и аккуратным, но клиенты хотели обычную кнопку. В первых ревизиях 7.4 кнопка была отделена от индикатора и пряталась под наклейкой, сейчас мы используем кнопку с двухцветным индикатором.

Эволюция кнопки включения в контроллерах Wiren Board

Эволюция кнопки включения в контроллерах Wiren Board

Схема сигналов переключателя ON/OFF в Wiren Board 6

Схема сигналов переключателя ON/OFF в Wiren Board 6

Переключатель в старых версиях отключал одновременно модуль резервного питания WBMZ, входной Step-Down преобразователь и блокировал работу сторожевого таймера (Watchdog)

Это решение было простым, но имело недостатки:

  1. При питании от отладочного USB-C контроллер включался независимо от положения переключателя, но в это время не работал сторожевой таймер.

  2. Нельзя было выключить контроллер по команде poweroff: Linux выключался, программный сторожевой таймер переставал дергать аппаратный, и тот перезапускал контроллер. 

С появлением EC мы смогли заменить тумблер на аккуратную кнопку и обрабатывать её как хочется нам или заказчикам OEM-версий контроллера.

Обработка принудительного отключения по кнопке и командам poweroff / rtcwake

// Если флаг нажатой кнопки висит слишком долго (линукс по каким-то причинам не отреагировал)
// Нужно его сбросить, т.к. он влияет на решение для выключения по poweroff
if (wbec_ctx.pwrkey_pressed) {
    if (systick_get_time_since_timestamp(wbec_ctx.pwrkey_pressed_timestamp) > WBEC_LINUX_POWER_OFF_DELAY_MS) {
        wbec_ctx.pwrkey_pressed = false;
    }
}

if (linux_powerctrl_req == LINUX_POWERCTRL_OFF) {
    // Если прилетел запрос из линукса на выключение
    // Это была выполнена команда `poweroff` или `rtcwake -m off`
    console_print("\r\n\n");
    console_print_w_prefix("Power off request from Linux.\r\n");
    bool wbmz = linux_cpu_pwr_seq_is_powered_from_wbmz();
    bool alarm = rtc_alarm_is_alarm_enabled();
    bool btn = wbec_ctx.pwrkey_pressed;

    if (alarm) {
        struct rtc_alarm rtc_alarm;
        rtc_get_alarm(&rtc_alarm);

        console_print_w_prefix("Time now is: ");
        console_print_time_now();
        console_print("\r\n");
        console_print_w_prefix("Alarm set to XXXX-XX-");
        console_print_dec_pad(BCD_TO_BIN(rtc_alarm.days), 2, '0');
        console_print(" ");
        console_print_dec_pad(BCD_TO_BIN(rtc_alarm.hours), 2, '0');
        console_print(":");
        console_print_dec_pad(BCD_TO_BIN(rtc_alarm.minutes), 2, '0');
        console_print(":");
        console_print_dec_pad(BCD_TO_BIN(rtc_alarm.seconds), 2, '0');
        console_print("\r\n");
    } else {
        console_print_w_prefix("Alarm: not set\r\n");
    }

    console_print_w_prefix("Power status: ");
    if (wbmz) {
        console_print("powered from WBMZ\r\n");
    } else {
        console_print("powered from external supply\r\n");
    }

Также мы обрабатываем потенциально опасную ситуацию, когда пользователь мог выключить удалённый контроллер командой poweroff и потерять к нему доступ. Одним из условий для выключения по этой команде — наличие будильника на пробуждение, если его нет, то перезагружаем контроллер.

Запрет выключения контроллера без АКБ по команде poweroff

            // Не должно быть возможности программно выключить контроллер так, чтобы
            // нужно было ехать нажимать кнопку чтобы его включить обратно
            // Поэтому здесь нужно проверить наличие будильника и если он есть - выключиться
            // иначе - перезагрузиться
            // Также разрешено выключаться по poweroff, если питаемся от WBMZ или если
            // выключились по кнопке
            if (wbmz || alarm || btn) {
                console_print_w_prefix("Powering off\r\n");
                linux_cpu_pwr_seq_hard_off();
                new_state(WBEC_STATE_POWER_OFF_SEQUENCE_WAIT);
            } else {
                console_print_w_prefix("Alarm not set, reboot system instead of power off.\r\n\n");
                wbec_info.poweron_reason = REASON_REBOOT_NO_ALARM;
                linux_cpu_pwr_seq_hard_reset();
                new_state(WBEC_STATE_POWER_ON_SEQUENCE_WAIT);
            }

Измерение температуры базовой платы

Раньше стоял датчик LM75, данные с которого считывал процессор контроллера. Теперь используем NTC-термистор с помощью которого EC измеряет температуру, а затем передаёт её в ОС Linux и использует в различных сценариях сам.

Датчик температуры LM75AD в ревизиях 7.2 и 7.3, в ревизии 7.4 используется NTC-термистор 10k. Пользователю доступно значение датчика в MQTT и веб-интерфейсе

Датчик температуры LM75AD в ревизиях 7.2 и 7.3, в ревизии 7.4 используется NTC-термистор 10k. Пользователю доступно значение датчика в MQTT и веб-интерфейсе

С внедрением EC поменялась и логика работы контроллера, завязанная на температуру базовой платы. Например, для защиты eMMC от поломок мы должны запретить старт контроллера при температурах ниже −40 °С. Раньше это делал загрузчик, но для его работы надо было запитать контроллер и eMMC, и только после этого он мог блокировать дальнейший старт. Сейчас EC сразу смотрит текущую температуру базовой платы, и если она ниже −40 °С, то не запитывает процессор и eMMC совсем. 

Блокировка старта контроллера при температуре ниже −40°С

case WBEC_STATE_TEMP_CHECK_LOOP:
    // В этом состоянии линукс выключен, проверяем температуру
    // Сидим тут до тех пор, пока температура не станет выше -40
    if (in_state_time_ms() > 5000) {
        if (adc.temp < WBEC_MINIMUM_WORKING_TEMPERATURE_C_X100) {
            console_print_w_prefix("Board temperature is below -40°C! Rechecking in 5 seconds\r\n");
            new_state(WBEC_STATE_TEMP_CHECK_LOOP);
        } else {
            console_print_w_prefix("Temperature is OK!\r\n");
            console_print_w_prefix("Turning on the main CPU; all future debug messages will originate from the CPU\r\n\n\n");
            linux_cpu_pwr_seq_on();
            new_state(WBEC_STATE_POWER_ON_SEQUENCE_WAIT);
        }
    }
    break;

В новом Wiren Board 8 мы добавим нагреватель, который будет греть плату контроллера при низких температурах, что позволит нам сделать версию, работающую от −55 °С.

Некоторые модели индустриальных eMMC выходят из строя, если попытаться с ними работать при температуре ниже −40 °С, поэтому сейчас мы с помощью EC запрещаем старт контроллера при низких температурах, а в будущем сделаем подогрев

Некоторые модели индустриальных eMMC выходят из строя, если попытаться с ними работать при температуре ниже −40 °С, поэтому сейчас мы с помощью EC запрещаем старт контроллера при низких температурах, а в будущем сделаем подогрев

Управление выходом Vout

В контроллерах Wiren Board есть два встроенных порта RS-485 и ещё три можно добавить модулями расширения.

Возле клемм встроенных портов есть выходы Vout с максимальным током до 1 А на оба, программным отключением и защитой от короткого замыкания. Их удобно использовать в небольших инсталляциях для питания устройств.

Клеммы RS-485 и выходы Vout в контроллерах Wiren Board

Клеммы RS-485 и выходы Vout в контроллерах Wiren Board

Внутри нет никаких преобразователей и питание на выход Vout подается напрямую с клемм V+ контроллера, но в этом кроется опасность: максимальное напряжение питание устройств 28 В постоянного тока, а контроллера — 48 В.

Так как в большинстве инсталляций напряжение питания не превышает 24 В, то у пользователей этот нюанс не вызывал дискомфорта. Но иногда пользователи запитывали контроллеры от блоков питания, которые удавалось найти в щите, и у тех, кто невнимательно читал документацию, это могло «выстрелить» и сжечь устройства на шине. Поэтому при внедрении EC мы сделали защиту, которая отключит выход при питании от напряжения, выше 28 В.

Также внедрение EC в ревизии 7.4 позволило нам упростить схему управления выходом Vout. В контроллерах Wiren Board 6.3…6.8 выход Vout управлялся программно сигналом с процессора, поэтому при перезагрузке ОС контроллера питание на нём пропадало. Чтобы этого избежать, с ревизии 6.9 мы добавили в схему два транзистора и конденсатор, которые удерживали ключ открытым, пока контроллер перезагружается и GPIO находятся в состоянии Hi-Z. Эта схема перекочевала и в ревизии 7.2…7.3. С версии 7.4 там просто мосфет, которым управляет EC.

Управление выходом Vout в старых ревизиях, а справа в новых

Управление выходом Vout в старых ревизиях, а справа в новых

АЦП на A1-A4

У контроллера есть универсальные входы/выходы Ax, которые в режиме аналогового входа измеряют напряжение. 

В Wiren Board 6 у нас стоял процессор NXP i.MX 6ULL и измерение напряжений на этих входах делалось его встроенным АЦП. В процессоре нового Wiren Board 7 свободных АЦП-входов оказалось всего три, а разрабатывался он в период дефицита компонентов, поэтому мы решили не ставить отдельную микросхему для измерения напряжения. Так выход A4 остался без измерения напряжения и был переименован в D1.

Универсальные входы контроллера Ax в ревизии 7.4, вернулся выход A4

Универсальные входы контроллера Ax в ревизии 7.4, вернулся выход A4

Схемотехника универсальных входов контроллера Ax и D1 в ревизиях 7.2…7.3

Схемотехника универсальных входов контроллера Ax и D1 в ревизиях 7.2…7.3

Но 12-битный АЦП процессоре Allwinner A40i оказался с сюрпризом. В документации мы нашли, что у него есть специальный настроечный регистр RTP reg с адресом 0×01C25000, где по смещению 0×001C (Common Data Reg offset) лежит число 0×800 — оно вычитается из измеренного значения, что позволяет калибровать АЦП.

Например, замкнули мы вход на GND и получили на входе 0 В, при записанном в регистре значении 0×800 получили измеренное 0 В. Если записать туда, например 0×600, то в теории мы сместим точку отсчёта в минус.

Разобравшись с алгоритмом калибровки, сделали на производстве процедуру:

  1. Замыкаем вход Ax на GND.

  2. Пишем в регистр отклонение вниз 0×600 и получаем нужный оффсет по формуле offset= U_измеренное — (0×800 — 0×600) = U_измеренное — 0×200.

  3. Запись 0×800 + offset в регистр устанавливает 0 в АЦП.

Но при калибровке серийных экземпляров неожиданную обнаружили проблему: АЦП не отдает отрицательные значения. Например, если чип занижает на условные 200 мВ и мы впишем поправку в +200 мВ, то при подтяжке к GND (0 В на входе АЦП) чип будет показывать 200 мВ. Поэтому точность АЦП на универсальных выходах в ревизиях 7.2…7.3 ниже, чем нам хотелось бы, но достаточная для прикладных задач.

С внедрением EC мы вернули четвертый аналоговый вход, а так как точности встроенного в STM32 АЦП для нас достаточно, то вместо калибровки мы просто проверяем, что измеренное значение попадает в заданные границы.

Гистограммы

Распределение отклонения встроенного в процессор АЦП на WB7.3.4 и старше, данные из БД проверок устройств

Распределение отклонения встроенного в процессор АЦП на WB7.3.4 и старше, данные из БД проверок устройств

Примеры гистограмм с распределением напряжений для WB 7.4.3, данные из БД проверок устройств. Напряжение подаётся от внутренних источников питания контроллера, поэтому в гистограмму попала их точность

Примеры гистограмм с распределением напряжений для WB 7.4.3, данные из БД проверок устройств. Напряжение подаётся от внутренних источников питания контроллера, поэтому в гистограмму попала их точность

Ожидание и реальность от калибровки АЦП Allwinner A40i

Ожидание и реальность от калибровки АЦП Allwinner A40i

Отладочный вывод до включения процессора

В контроллере есть отладочный порт Debug Console, куда выводится отладочная информация о процессах отключения/включения контроллера, а после загрузки ОС Linux можно получить доступ к её консоли.

Отладочный порт в контроллерах Wiren Board

Отладочный порт в контроллерах Wiren Board

Раньше в Debug Console писали сперва загрузчик, а потом Linux. Теперь мы можем узнать и о том, что происходило на этапе инициализации платформы и до подачи питания на процессор:

  • узнать причину отключения и включения — пришла команда из Linux, нажали на кнопку, сработал сторожевой таймер, внезапно пропало питание процессора и т. п.;

  • увидеть статус будильника — пользователь мог поставить и забыть о нём;

  • при включении узнать текущее напряжение питания, что поможет отловить неисправность преобразователей питания, из-за которых контроллер может не запуститься;

  • узнать температуру платы, что расскажет нам о неудачной попытке запуска при очень низких температурах;

  • другую служебную информацию, вроде версий прошивки EC и U-Boot.

Фрагмент отладочного вывода в Debug Console

Фрагмент отладочного вывода в Debug Console

Как устроен Embedded Controller

EC построен на микроконтроллере STM32G030C8 — с его помощью мы реализуем любые задуманные фичи программно.

Мы пока не используем операционную систему, поэтому весь код выполняется в основном цикле программы. Такому подходу есть исторические причины: изначально на EC-контроллере предполагалось выполнять мало задач, а значит исходный код без ОС получался компактным и исключал дополнительную точку отказа в виде самой ОС. Однако в процессе разработки аппетиты росли, функций стало больше, но внедрять ОС и переписывать весь код на конечном этапе разработке мы не стали. Возможно, переедем на ОС в будущем при добавлении новых функций.

Embedded Controller в контроллере Wiren Board 7.4

Embedded Controller в контроллере Wiren Board 7.4

Драйверы для Linux

Центральный процессор контроллера Wiren Board общается с EC-контроллером по шине SPI через запись и чтение регистров. SPI использовали, потому что он обеспечивает более высокую скорость передачи данных, чем I2C. Высокая скорость нам понадобится в новом Wiren Board 8, так как на используемом там процессоре не хватает UART-ов и мы планируем реализовать мост SPI <-> 2xUART на EC. То есть использование SPI — задел на будущее.

Чтобы управлять EC-контроллером и забирать с него данные из Linux, мы написали для ядра mfd драйвер (Multi Function Device Driver), который реализует интерфейс regmap для доступа к регистрам EC. Также в ядро добавлены драйверы для RTC, watchdog и остальных подсистем EC. В итоге в userspace ничего не изменилось и доступ к подсистемам ЕС осуществляется стандартными для Linux способами. Описание и исходный код драйверов можно найти в нашем репозитории.

Блок-схема доступа к EC из Linux

Блок-схема доступа к EC из Linux

Обновление прошивки пользователем

Мы любим, чтобы прошивки всех наших устройств пользователи могли обновлять без программатора, поэтому в EC используется встроенный загрузчик микроконтроллера. Сама прошивка загружается через интерфейс I2C, так как наш STM32G0 грузиться по SPI не умеет. Для запуска процесса обновления в Linux сначала необходимо применить device tree overlay, который настраивает I2C и GPIO для работы с загрузчиком. После этого можно обновить прошивку с помощью утилиты stm32flash. Для пользователей мы написали скрипт wb-ec-firmware-update, который обновляет прошивку автоматически.

Прошивка поставляется в deb-пакете wb-ec-firmware, исходный код вы найдёте в репозитории wb-embedded-controller.

Процесс обновления прошивки EC в контроллере Wiren Board

Процесс обновления прошивки EC в контроллере Wiren Board

Заключение

В статье мы рассказали, как сделали Embedded Controller с нуля без использования специализированных микросхем: таким образом, мы улучшили флагманский продукт и по пути сэкономили на некоторых дискретных компонентах, что в масштабе десятков тысяч штук весьма ощутимо.

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

Если вы разработчик и хотите делать устройства, которыми пользуются десятки компаний и сотни тысяч людей, приходите к нам работать. Мы ищем Linux-программистов, Embedded-программистов, инженеров по внедрению, инженеров техподдержки и продакт оунеров. Присылайте резюме на info@wirenboard.com с пометкой в теме «Я с Хабра, хочу работать».

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

Фотографии с прошлой выставки

Фотографии с прошлой выставки

© Habrahabr.ru