BE-U1000 на плате EVU-BA-2.1
Знание об этой плате и этом кристалле, которое иначе собирается по RM,
техспецификации, ТО платы и неделе отладки. Почти все отказы здесь тихие:
плата работает, просто не так — или молчит, будучи исправной. Поэтому детали
лежат в справочных файлах, и их следует читать, а не додумывать.
reference/board-hardware.md — полный справочник по железу: карта выводов и
разъёмов, питание, сброс, режимы загрузки, карта памяти, тактирование, обзор
SDK, плюс часть II: система сборки, прерывания, поваренная книга по
блокам, прошивка и таблица «признак → причина → лечение».
reference/recipes.md — готовый код, извлечённый из template/: Makefile,
консоль, временная база, захват, импульс, АЦП+DMA, CAN FD, SPI, eFlash, WDT.
template/ — работающий проект: собран в четырёх сочетаниях
(--full/--minimal × eFlash/TCM) и прогнан на плате 2026-08-31 —
светодиод, консоль, меню, захват, импульс, АЦП. Плюс сценарий развёртывания.
Смотри template/README.md.
Ориентация
| |
|---|
| Кристалл | BE-U1000, QFN-88. Три ядра RISC-V: 0 и 1 — CloudBEAR BR-350 200 МГц (ilp32f), 2 — BM-310 100 МГц, без FPU (ilp32) |
| Память | eFlash 256 КБ 0xA0000000, TCMA 64 КБ 0x40010000, TCMB 96 КБ 0x40020000, SRAM 32 КБ 0x70000000, QSPI1 (плата, 16 МБ) 0x90000000 |
| Тактирование | генератор G1 25 МГц на CLKI (кварца у кристалла нет). Перемычка CLK на XP1 переключает на внутренний RC-генератор. PLL по умолчанию не включена |
| Светодиод / кнопка | LD1 PC0 активен высоким / SB1 PC13 нажата = ноль. SB2 — аппаратный сброс |
| Консоль и прошивка | UART0 PA6/PA7, АФ #1, разъём XS2 «JTAG UART0» (FT2232HL). Это единственный UART, выведенный на USB |
| Аналоговые входы | 8 штук, VIN0…VIN7 — выделенные выводы, XP8 конт. 28, 30, 32, 34, 36, 38, 35, 37; «AREF» — 7. Три АЦП 12 бит 1 MSps делят их |
| Импульсные блоки | 4 × PWMA (16 бит, захват, одиночный импульс, dead-time), 2 × PWMG, 2 × TIM (32 бита, 4 канала) |
| Шины | 2 × CAN FD (трансивера на плате нет), 4 × SPI (ведущими умеют только SPI1 и SPI3), 4 × I2C, до 7 UART, USB OTG (XS1) |
| Отладка | JTAG отладчиком на плате (тот же FT2232HL) либо внешним через XP6; JTAG разведён на PA0…PA4 |
| Порты на гребёнках | XP9 — весь порт A, XP10 — весь порт B, XP8 — весь порт C плюс питание и входы АЦП |
Сначала — режим загрузки, потом всё остальное
Режим защёлкивается перемычками XP1 до сброса; ни одна программная
настройка его не меняет. Половина «плата не работает» — это не тот режим.
XP1 DBG-M2-M1-M0 | Режим | Когда нужен |
|---|
0000 (все сняты) | EFLASH | боевой: запуск программы из eFlash |
0001 (только M0) | UART | прошивка и BootROM CLI по UART0 |
0011 (M0+M1) | USB | прошивка по DFU либо CLI по USB CDC (кабель в XS1) |
0010 | QSPI | запуск из внешней QSPI flash |
0100 / 1100 | JTAG EXT / JTAG INT | отладчик на плате или внешний / встроенный |
0110 | MULTI | перебор EFLASH → QSPI → USB CDC → UART |
Отдельная перемычка CLK: снята — генератор 25 МГц (норма), установлена —
внутренний RC-генератор. ⚠ Его диапазон в документах разный: техспецификация
кристалла v1.5.5 трижды называет 12…32 МГц, ТО платы v2.1.2 — 20…40 МГц.
Кому верить, по бумагам не решить; на плате не мерено. Питание выбирается
перемычкой XP11: USB / EXT5V / VIN (10…48 В). Полные таблицы и рисунки —
reference/board-hardware.md §3.
Правила, которые экономят дни (rules & pitfalls)
Каждое — из отказа, который выглядел не тем, чем был.
- Подтверждение приёма прерывания у каждого блока своё, и найти его надо
до отладки, а не после. У PWMA/PWMG это
PWMA_ClearIT() — разряд 15
регистра разрешений DIER, и он обязан быть последней строкой
обработчика; гашение флагов состояния линию запроса не снимает. У ADC —
CR1.CLRINTRPT. У CAN FD — запись единицы в ICR. У TIM — TIM_ClearIT().
У UART подтверждения нет вовсе. Без него при запуске по уровню плата
«зависает» (обработчик вызывается бесконечно, очередь UART перестаёт
уходить), при запуске по фронту срабатывает ровно один раз, а регистр
состояния внутри обработчика читается нулём. Ни один пример SDK 2.1
PWMA_ClearIT() не вызывает — искать нечего, надо знать.
- Сбор с АЦП по прерыванию невозможен — только DMA. Запрос снимается
единственно записью в
CR1, а любая запись в CR1 обрывает идущую
последовательность (там же ADON и SWSTART). Подтверждать на каждом
ранге — терять преобразование (измерено: 3 ранга из 6), не подтверждать —
вешать плату. С DMA всё сходится: 0 потерь из 500, 18 мкс на канал.
Прерывание от самого АЦП не разрешается вовсе.
ADC_StartCalibration() — не калибровка. Она выставляет ASER.CAL_SEL,
а ADC_IsCalibrationOnGoing() читает тот же разряд обратно; железо его не
снимает, поэтому ожидание вечно по построению и плата встаёт молча. По
RM единица подменяет вход внутренним нулём, и «ноль» этот равен 1400…1700
отсчётов — середина шкалы. Вычитать его нельзя: данные превращаются в
правдоподобные нули («датчики не подключены»).
CANFD_RequestMode() из SDK молча не срабатывает. Она снимает SRR.CEN
и тут же проверяет режим; не успев — пропускает настройку, а CEN уже снят.
Узел нем: не передаёт, не принимает, ошибок не считает, и выглядит это как
«кадры уходят, никто не отвечает». Повторять запрос, пока GetMode() не
подтвердит.
- Тактовая CAN — 12,5 МГц, а не 25. На 500 кбит/с приходится ровно 25
квантов на бит; привычные фиксированные 16 не делят это нацело, и шина не
открывается вовсе. Число квантов считать, точка выборки ~75 %.
- SPI0 и SPI2 — ведомые. В именах регистров, в структурах и в HAL
разницы нет:
SPI_Init() проходит без ошибок, а обмен даёт таймаут. Улика —
BAUDR и SER читаются нулями при записанных значениях. Различить можно
только по таблице выводов RM: MSPI1, MSPI3 против SSPI0, SSPI2.
Отсюда правило шире одного блока: прочитать обратно то, что записал.
- С обычного ОЗУ (
0x70000000) выборка команд не идёт. Исполняемое ОЗУ
здесь одно — TCMA 0x40010000. Функция, положенная в .data, просто не
возвращается; ядро уходит в трап с запрещёнными прерываниями, и наружу это
выглядит как обрыв вывода на середине строки.
- Код, который пишет eFlash, обязан исполняться из TCM. Массив недоступен,
пока идёт его собственная операция, — и для данных, и для команд: холодный
проход по 4 КиБ кода во время стирания сектора занял 3408 мкс против 85
вхолостую. Отказ не падение, а простой ядра на 3,3–4,1 мс и потерянные байты
UART. Удобный перенос — секция с именем без точек (
beu_nvmfunc): для
таких компоновщик сам заводит __start_…/__stop_….
common.mk не следит за заголовками. DEPS собирается как main.d, а
компилятор пишет main.o.d, и -include молча берёт пустоту. Правка общего
заголовка не вызывает пересборку, а на плате потом отказывает не то, что
менялось (у нас так объект устарел на 12 байт структуры, и плата умирала на
первом куске прошивки). Лечение — -include $(wildcard $(OBJ_DIR)/*.o.d) в
своём Makefile либо make clean после каждой правки заголовка.
- SDK переиспользует уже сгенерированный
output/debug/generated.ld
(LD_SCRIPT := firstword $(wildcard $(PRJ_DIR)/*.ld) $(wildcard $(OUT_DIR)/*.ld)).
Поэтому MEM_REG_ROM=TCMA после обычной сборки молча не действует:
предупреждений нет, образ уезжает не в ту память, на плате это «прошилось,
но не стартует». Вместе с ORIGIN не меняется и кодовое слово для
BootROM — а оно у каждой области своё (см. «Прошивка»), поэтому плата
молча уходит в режим USB. Перед сменой области — make clean; проверять
ORIGIN в generated.ld (под TCM должно быть 0x40010000).
- Открытие
/dev/ttyUSB0 сбрасывает плату, и софтом это не обходится.
Вывод ADBUS4 микросхемы FT2232HL заведён на цепь сброса; проверены обе
полярности DTR и обе RTS — сбрасывают все четыре, даже когда порт уже держит
открытым другой процесс. Следствие полезное: сброс перед прошивкой можно
отдавать линией DTR, кнопка SB2 не нужна. Следствие вредное: всякая правка,
живущая в ОЗУ, теряется при каждом подключении.
- В режиме
EFLASH BootROM не поднимает UART0 и не печатает. Его
printf по 0x4000b082 работает только там, где он сам ведёт консоль, то
есть в режимах UART и USB CDC. Программа, полагающаяся на него, в боевом
режиме молчит при полной исправности — а отлаживать это в режиме UART
бесполезно, там отказ не воспроизводится. Порт поднимать самому.
- Прерывания не вкладываются. Обработчик GCC (
__attribute__((interrupt)))
входит с закрытым mstatus.MIE и наружу его не открывает; уровни CLIC задают
лишь порядок выбора среди ожидающих. Значит бюджет считается по самому
длинному обработчику в системе, а не по своему: измеренный провал главного
цикла — 850…1300 мкс. И float в обработчике опасен: GCC сохраняет только
те регистры, которые использует сам.
CLIC_Config(1, 1) даёт по одному разряду на уровень и приоритет.
Значение 2 в CLIC_ConfigIRQ() при этом не «понизится» — прерывание не
сработает вовсе. И __enable_irq() забыть так же легко: обе ошибки дают
молчащую плату, неотличимую от зависшей.
- Потолок сторожевого таймера — 335 мс (2^23−1 такта на 25 МГц), то есть
670 мс до перезагрузки в режиме с прерыванием. ⚠ В заголовке SDK список
периодов набран с опечаткой начиная с четвёртого значения (
0xFFFF вместо
0xFFF) и завышает потолок в 16 раз. Регистра причины сброса у кристалла
нет — отметку держать в ОЗУ (например по _heap_start).
- Страповые выводы
PA10, PB10, PC4, PC6, PC8 нельзя подтягивать
вверх — ни драйвером, ни преобразователем уровня. Внутри они подтянуты к
земле через 42 кОм, и это и есть режим EFLASH; внешняя единица уводит
собранную плату в чужой режим загрузки молча. MODE[0] при этом сидит на
SPI1_MOSI, а MODE[1] — на SPI3_MOSI: у обоих ведущих SPI линия MOSI
страповая, обойти нечем.
- Восемь аналоговых входов — предел кристалла, а не платы.
VIN0…VIN7
выделенные, у всех трёх АЦП общие, альтернативной функцией портов АЦП не
бывает ни на одном выводе. Нужно больше — только внешний мультиплексор
(CD74HC4051 + буферный ОУ: Ron 100…200 Ω требует низкого импеданса
источника).
- Тактирование порта GPIO включается отдельно от тактирования периферии,
и без него вывод к ней не подключается.
CRU_PIN_Init() при этом
отрабатывает молча, регистры таймера верны, провод исправен — а захват не
видит ни одного фронта. Измерено на плате 2026-08-31: захват на PB8 не
считал ничего, пока не добавили CRU_APB1_EnableClock(CRU_APB1_PERIPH_GPIO1);
выход на PC5 при этом работал — но лишь потому, что порт C уже
тактировался ради светодиода. Порты сидят на разных шинах:
A — CRU_APB0_PERIPH_GPIO0, B — CRU_APB1_PERIPH_GPIO1,
C — CRU_APB2_PERIPH_GPIO2.
- DMA не имеет доступа к TCM — ни к TCMA, ни к TCMB. Буфер DMA обязан
лежать в SRAM (
0x70000000). Измерено 2026-08-31 на трёх адресах: с
приёмником в TCMB (0x40020400) и в TCMA (0x4001F000) канал включается,
делает одну исходную транзакцию, ловит ошибку шины (RAW_ERR = 1) и
сам выключается, не подняв флага завершения; с приёмником в SRAM те же
четыре канала АЦП собираются за 66 мкс. Ловушка тихая вдвойне: наружу это
«АЦП не отдаёт результат», а конфигурация DMA и АЦП при этом верна. И она
подстерегает ровно там, где кажется безопасно: при сборке под TCM
(MEM_REG_ROM=TCMA) переменная MEM_REG_RAM по умолчанию тоже уводит
данные в TCM — для DMA нужно MEM_REG_RAM=SRAM.
- Ядро 2 (BM-310) — без FPU и практически без libc. Тулчейн SDK собран без
мультибиблиотеки
rv32imc/ilp32, и <string.h> там просто нет: сборка
падает с fatal error: string.h: No such file or directory, тогда как
хостовая и оба других ядра проходят. TCM у него свой и крошечный: 2 КБ + 8 КБ.
Когда задача — импульсы и захват (угловой домен, ШИМ, энкодер)
Это то, ради чего берут PWMA, и наивный подход здесь неверен.
Импульс складывается из двух совпадений. У PWMA на канал один регистр
сравнения: CCR = t_on в режиме ACTIVE даёт фронт включения, затем в
обработчике CCR = t_off в режиме INACTIVE — фронт выключения. Оба фронта
делает компаратор, задержка обработчика в джиттер не превращается — она лишь
ограничивает снизу длительность импульса. Измерено: ошибка ширины 0 нс,
джиттер 120…320 нс, постоянный сдвиг −1,8 мкс (одинаков для обоих фронтов,
ширины не касается).
Счётчик 16-разрядный: на 25 МГц окно всего 2,62 мс. Цель дальше окна
требует прицельного пробуждения — совпадения на заведомо достижимой точке, по
которому остаток пересчитывается. Ожидание переполнения вместо этого даёт
слепую полосу: цель чуть дальше окна перепрыгивает через ноль и оказывается «в
прошлом». И при перезарядке обязательно переставлять значение сравнения: старое,
оставшееся от предыдущего фронта, срабатывает как фронт выключения — импульс 5 мс
выходил короче ровно на период счётчика.
Предзагрузка регистра сравнения (preload) — противоположные решения для
разных задач. Для события (впрыск, искра) её надо выключить: цель,
записанная за 100 мкс до срабатывания, вступила бы в силу только через 2,6 мс.
Для ШИМ — включить: иначе новая скважность действует посреди импульса, и это
скачок тока в нагрузке на каждое обновление регулятора.
Захват и вывод — на разных блоках PWMA. У PWMA один вектор прерывания на
все каналы блока: на одном блоке перезарядка компаратора вывода исполнялась бы
внутри обработчика захвата. Выбор к тому же задан выводами: на порту B (XP10)
из блоков PWMA есть только PWMA1, PWMA2/PWMA3 сидят на порту C (XP8).
Числа, по которым считается запас: захват даёт джиттер 80 нс через
прерывание; оба фронта на одном канале — не чаще одного фронта в 100 мкс
(полярность переключается в обработчике), причём пропущенный фронт даёт не отказ,
а правдоподобный мусор — период кратный, скважность произвольная; зарядка
канала вывода стоит 38 мкс. Проверять это можно без генератора: перемычка с
выхода PWMA2 на вход PWMA1 (PC5 XP8.6 → PB8 XP10.13) — плата меряет себя
сама, и честно, потому что блоки разные.
Когда задача — аналоговые входы
Кроме правил 2, 3, 17 и 19 (буфер DMA только в SRAM): у этого АЦП нет
одновременной выборки. Секвенсор
идёт по рангам, и последний канал группы отстаёт от первого на 90 мкс при
шести каналах. Взаимозависимые величины (например положение и ток) ставить
соседними рангами. Две группы можно вести параллельно — контроллеры ADC0 и ADC1
независимы, входы у них общие. Линии DMA выделенные: у DMA_0 линия 13 —
ADC_0 RX, линия 15 — ADC_1 RX (RM табл. 8-1).
Новый проект
Руками не собирать. template/ — проверенный работающий проект:
~/.claude/skills/beu1000-evuba/template/variants/new-project.sh <каталог> [--full|--minimal]
cd <каталог> && make BAIKAL_SDK=$HOME/путь/к/SDK_2_1
--full (по умолчанию) — консоль, временная база, захват, импульс, АЦП+DMA,
меню по UART0. 25 536 Б из 256 КБ eFlash, ОЗУ 536 Б.
--minimal — только мигание LD1. 8 484 Б, ОЗУ 424 Б. С него начинать на
незнакомой плате: он отделяет отказ тракта прошивки от отказа своего кода.
Обе сборки проверены, как и вариант под TCM. ⚠ Под TCM собирать так:
MEM_REG_ROM=TCMA MEM_REG_RAM=SRAM make — данные именно в SRAM, иначе
буфер DMA окажется в TCM и АЦП замолчит (правило 19). И не забыть make clean
при смене области — правило 10.
template/README.md расписывает файлы по подсистемам. Если проект у
пользователя уже есть — приводить в порядок его Makefile по образцу шаблона,
а не переписывать код.
Прошивка (flashing)
Путь по умолчанию — BootROM CLI по UART0, тем же кабелем, что и консоль.
Перемычки XP1: установлена только M0 (режим UART), кабель USB Type-C в XS2,
питание выбрано на XP11.
d вход в CLI (ROM отвечает баннером и приглашением @)
14000018:1w снять защиту eFlash от стирания и записи
a0000000:40000:0:100m адрес, максимальный размер, настройки тактирования
:::101m стереть eFlash
:::3L ждать образ; ROM отвечает 'C' — запрос XMODEM/CRC
<XMODEM 128-байтными блоками, CRC-16>
a0000000g запуск с адреса eFlash
В TCM (0x40010000) — та же последовательность без снятия защиты и
стирания, команда загрузки l. Запись в TCM не трогает энергонезависимую
память и пропадает при снятии питания, поэтому первую проверку платы разумно
начинать с неё.
Почему залитый образ обязан быть собран под ту же область. BootROM в режиме
EFLASH ищет по адресу 0xA0000020 кодовое слово 0x0BEF5262 и, найдя,
передаёт управление на 0xA0000030; не найдя — уходит в режим USB
(руководство пользователя §3.2.1). Кодовое слово кладёт система сборки SDK, и
оно разное для каждой области: EFLASH 0x0BEF5262, TCMA 0x0B10D522,
TCMB 0x0B10DDA2, QSPI1 0x00000000 (Tools/build/service.mk). Заголовок
образа — 48 байт: по смещению 0 стоит jal x0, 48 (перепрыгнуть заголовок),
0x20 — кодовое слово, 0x28/0x2C — адрес и размер копирования.
Отсюда два следствия. Собранный чужой системой сборки «голый» бинарник плата
сама не запустит — стартовать он будет только командой g из CLI. И образ,
собранный под другую область памяти, тоже не запустится, даже если залит по
верному адресу: кодовое слово не то. Поведение при этом обманчиво мягкое —
плата не виснет, а молча уходит в режим USB.
Кнопку RESET нажимать не нужно: DTR# FT2232HL заведён на цепь DTR_RST и
формирует сброс наравне с кнопкой (правило 11). Скрипт нашего проекта —
tools/flasher/baikal_uart_flash.py.
Второй путь — USB DFU: XP1 в 0011, кабель в XS1 (USB OTG). Кристалл
поднимает DFU-устройство на стеке TinyUSB с идентификаторами cafe:2303
(проверять lsusb), заливка — dfu-util --alt 2 --download образ.bin. По
умолчанию устройство принадлежит root, и dfu-util падает с
LIBUSB_ERROR_ACCESS; лечится правилом udev
(SUBSYSTEM=="usb", ATTR{idVendor}=="cafe", ATTR{idProduct}=="2303", TAG+="uaccess").
Путь нужен для больших образов (256 КБ по UART идут около 25 секунд), но требует
второго кабеля в XS2 для сброса: питание FT2232HL идёт оттуда, и без него
линия DTR мертва.
Третий путь — JTAG: XP1 в 0100 (отладчик на плате, XS2) либо внешний
отладчик в XP6 при любом питании, кроме XS2. Нужен для точек останова и для
подъёма платы, у которой испорчено всё остальное.
Оживление. Испортить плату прошивкой нельзя: режим загрузки задаётся
перемычками до сброса и от содержимого eFlash не зависит. Оборванная заливка
означает лишь негодный образ — вернуть XP1 в UART и залить заново.
Отчётность
Говорить прямо, что проверено на железе, а что взято из документации. Здесь это
разделено так:
| Происхождение | Что именно |
|---|
| Измерено на плате шаблоном этого скила (2026-08-31, ядро 0, 25 МГц, образ в TCM, данные в SRAM) | мигание LD1 из TCM; консоль и меню по UART0; тактовые CCLK/PCLK0/1/2/HCLK = 25 МГц; импульс PWMA2 доходит до конца, каждый ловится захватом PWMA1 через перемычку PC5→PB8; АЦП+DMA — 4 канала за 66 мкс (16,5 мкс на канал); тактирование порта GPIO обязательно (правило 18); DMA не достаёт до TCM (правило 19) |
| Измерено на плате (порт EHAL проекта ЭБУ, EVU-BA-2.1, ядро 0, 25 МГц без PLL) | джиттер захвата 80 нс; предел обоих фронтов 100 мкс; ширина импульса и джиттер вывода; зарядка канала 38 мкс; провал главного цикла 850…1300 мкс; АЦП 18 мкс на канал и 0 потерь из 500; смещение нуля АЦП; времена и зерно eFlash; тактовая CAN 12,5 МГц; сброс платы при открытии порта; потолок и поведение WDT |
| Проверено сборкой | размеры и области шаблона (25 536 Б и 8 484 Б, ORIGIN под TCM), поведение generated.ld и *.o.d, кодовое слово в готовом образе (xxd -l 0x30: 0x0BEF5262 под eFlash, 0x0B10D522 под TCMA) |
| Сверено с первоисточником | карта «вывод → контакт» разъёмов XP8/XP9/XP10 (ТО платы, рис. 3-6…3-8, сверено по рисункам); номера альтернативных функций (RM, рис. 5-20); страповые выводы и режимы загрузки (техспец. табл. 3-5, ТО платы табл. 3-3); карта памяти (Tools/build/platform/BE_U1000/mem_map.mk); заголовок образа и кодовые слова (ldscript_template.ld, service.mk); поведение BootROM (руководство пользователя §3.2.1) |
| Не проверено ничем | выводы CAN PA14/PA15 и уровни на них — трансивера на плате нет, проверялась только внутренняя петля; диапазон внутреннего RC-генератора (документы расходятся); заливка шаблона в eFlash (проверялась только TCM: в eFlash платы лежит чужая прошивка, и стирать её ради опыта незачем) |
Отдельно держать в уме нерешённое: времена eFlash из приложения вдвое больше
измеренных из TCM (8253 мкс против 4061 на стирание блока), причина не
установлена, обе цифры приведены рядом намеренно; флаг повторного захвата
CC0OF взводится на каждом захвате при нулевых потерях, расхождение с RM не
выяснено.