201. Тест в производстве и прошивка на линии
Пока плат три, каждую прошивают со стола: воткнул ST-LINK, нажал кнопку, посмотрел глазами. На пятидесятой плате это перестаёт работать, на пятисотой — становится главным узким местом. Производственный тест — не «отладка, только быстрее»: это другой жанр. Никто не ищет ошибку в коде — код проверен до линии (раздел 184). Линия отвечает на один вопрос по каждой плате: собрана ли она правильно — пропаян ли кварц, не перепутан ли номинал, отвечает ли датчик, — и делает это за секунды, скриптом, с вердиктом «годен» или «брак» и строкой в журнале. Этот раздел — про то, как такой пост устроить: стенд, тестовая прошивка, серийный номер, байты конфигурации и журнал выпуска. Инструменты командной строки, на которых всё держится, разобраны в разделе 35б — здесь они собираются в конвейер.
Числа в разделе проверяемы: времена Flash — из таблиц характеристик даташитов
STM32F103x8/xB и STM32F405/407 в паках поддержки; синтаксис и частоты
STM32_Programmer_CLI 2.23.0 — из его --help и его же базы устройств
(сохранены в _verify/ptest201/); адреса OTP и UID — из заголовка
stm32f407xx.h и справочных руководств; расчёт времени — calc_time.py;
самопроверка и журнал собраны и прогнаны на компьютере (gcc и python в
Docker), ядро самопроверки скомпилировано и под Cortex-M3. Против железа
ничего не исполнялось и как вывод не цитируется. Сверяет
_verify/ptest201_check.py.
Как это называется в документации
Оригинал Перевод production test / end-of-line test тест в производстве, на выходе линии bed of nails «ложе с иглами»: оснастка с подпружиненными контактами pogo pin подпружиненный игольчатый контакт test point / test pad контрольная площадка на плате под иглу или щуп DUT (device under test) испытуемое устройство in-circuit test (ICT) внутрисхемный тест: цепи и номиналы до включения functional test (FCT) функциональный тест: собранное устройство работает flying probe «летающие щупы»: тест подвижными иглами, без оснастки golden sample эталонный образец: по нему сверяют сам стенд go / no-go «годен — брак»: вердикт без промежуточных градаций
201.1 Чем тест на линии отличается от отладки
За столом инженер час крутит одну плату; на линии оператор за минуту должен получить вердикт по очередной. Отсюда всё остальное:
| Отладка | Тест на линии | |
|---|---|---|
| вопрос | почему не работает | правильно ли собрано |
| кто | инженер | оператор или автомат |
| время | часы на одну плату | секунды—минуты на каждую |
| инструмент | среда, GDB, осциллограф | стенд + скрипт, никакого GDB |
| результат | понимание | «годен / брак» и строка в журнале |
Арифметика времени жёсткая: если линия собирает 200 плат в смену — 8 часов, — на плату приходится 144 секунды такта, и тест обязан укладываться вместе с установкой платы на стенд и снятием. Всё, что не влезает, убирают из теста или распараллеливают на второй стенд.
Классический конвейер контроля двухступенчатый. Внутрисхемный тест (ICT) проверяет плату ещё выключенной: иглами по площадкам меряют сопротивления и ёмкости цепей, ищут короткие замыкания и непропаи — на больших тиражах этим занимается отдельный автомат, на малых первую половину заменяют «летающие щупы» или вовсе визуальный контроль. Функциональный тест (FCT) — то, что разберём здесь: плату включают, прошивают и заставляют саму себя проверить. Стенд при этом тоже надо проверять: раз в смену через него прогоняют эталонный образец — заведомо годную плату; если «эталон» вдруг стал браком, чинят стенд, а не партию.
201.2 Стенд: ложе с иглами и что вывести на площадки
Разъём на каждую плату ставить дорого, поэтому серийный стенд — это ложе с иглами: оснастка, в которую плата ложится контрольными площадками на подпружиненные контакты (pogo pins). Прижал крышкой — все связи есть; поднял — следующая плата. На малой серии то же самое делают проще: технологический разъём, прижим рукой, даже пайка трёх проводов — ложе окупается на сотнях плат, но набор сигналов от этого не меняется.
Какие площадки закладывать ещё при разводке платы (контролепригодность — из тех требований, что вспоминают на этапе испытаний, раздел 79):
- SWD полностью:
SWDIO,SWCLK,NRST, земля. Через них идёт и прошивка, и чтение UID, и байты конфигурации. - UART
TX(лучше иRX): по нему тестовая прошивка отдаёт вердикт, а на живом изделии он же — канал диагностики. - Питание. Плату питает стенд, а не наоборот: тогда первый тест происходит до всякой прошивки — подать 3,3 В и посмотреть ток. Короткое замыкание или перевёрнутый стабилизатор видны по току за доли секунды, пока ничего не сгорело.
Площадки выводят на одну сторону платы (обычно нижнюю), не под деталями, и сеткой с зазорами под стандартные иглы — это те решения, которые в разводке стоят ноль, а после сборки не стоят уже ничего: дорисовать площадку на готовой плате нельзя.
⚠️
NRSTна площадку — обязательно. Камень, у которого прошивка сразу уходит в сон или переназначает выводы SWD, без линии сброса недоступен: нужен приём «подключение под сбросом» (раздел 35б.2). У STM32_Programmer_CLI это режим соединенияmode=UR— но держатьNRSTему физически нечем, если вывод не разведён на иглу. На линии это не экзотика: после включения защиты и сторожа (201.6) сервисные заходы делаются именно так.
201.3 Прошивка на линии: CLI из скрипта и сколько она стоит секунд
Прошивают на линии те же программы, что в разделе 35б, только зовёт их не человек, а скрипт стенда. Фирменная строка целиком:
STM32_Programmer_CLI -c port=SWD freq=4000 sn=57FF6A06... -q \
-e all -w app.elf -v -rst -log line.log
Всё по собственной справке программы: freq= — частота SWD в килогерцах
(по умолчанию 4000 у ST-LINK/V2 и 8000 у ST-LINK/V3), sn= — серийный номер
самого программатора, чтобы стенд с двумя ST-LINK не перепутал платы, -e all
— стереть всё, -w — записать, -v — сверить чтением, -rst — сброс,
-q убирает индикатор хода, -log пишет подробный протокол в файл. Скрипт
смотрит на код возврата команды — как с любой программой (тот же приём, что
в конвейере сборки, раздел 110). Открытая альтернатива одной строкой —
openocd … -c "program fw.elf verify reset exit" (раздел 35б.3); а когда SWD
недоступен вовсе, остаётся заводской загрузчик по UART или USB DFU через
BOOT0 (разделы 4.9 и 35б.7).
Теперь арифметика: сколько секунд стоит мегабайт по SWD. Пакет записи одного 32-битного слова в протоколе SWD — 8 бит запроса, разворот шины, 3 бита подтверждения, разворот, 32 бита данных и бит чётности: 46 тактов; чтение — те же 46. На 4 МГц это 11,5 мкс на слово; в мегабайте 262 144 слова — не меньше 3,01 с чистой передачи (на 8 МГц — 1,51 с). Это нижняя граница: холостые такты, ответы «подожди» и накладные расходы USB её только поднимают. Вторая составляющая — сама Flash: у F407 программирование слова занимает типично 16 мкс (максимум 100), у F103 полуслова — 52,5 мкс (40—70). Передача и программирование могут идти внахлёст (данные едут порциями через буфер в ОЗУ), поэтому честная нижняя оценка — большая из этих двух составляющих, а не их сумма. И третья — стирание перед записью:
| Составляющая | F103CB, 128 КБ | F407, 1 МБ |
|---|---|---|
| стереть | вся Flash: 20—40 мс | вся Flash при x32: 8 тип. / 16 макс. с |
| передать по SWD, 4 МГц | не меньше 0,38 с | не меньше 3,01 с |
| записать (сама Flash) | 65 536 полуслов × 52,5 мкс = 3,44 с | 262 144 слова × 16 мкс = 4,19 с |
| сверить (чтение назад) | ещё ~0,4 с | ещё ~3 с |
| итого, нижняя оценка | ≈4 с | ≈15 с |
Расклад поучительный. У F1 стирание мгновенное, а узкое место — медленная
запись полусловами. У F4 наоборот: запись при x32 быстрая, зато стирание
всей Flash — 8 секунд типично, и обойти их нельзя: стирание секторами по
одному ещё дольше (4 × 0,25 + 0,55 + 7 × 1,0 = 8,55 с по типичным временам),
так что -e all — не лень, а оптимизация. Прошивка мегабайта — заметный
кусок 144-секундного такта, и это стоит знать до того, как выбрать камень
с большой Flash для массового изделия.
🔧 Тестовую прошивку — маленькой. На линии камень прошивается дважды: сначала прошивка-самопроверка (201.4), после её вердикта — боевая. Самопроверка в десятки килобайт заливается за доли секунды и почти ничего не добавляет к такту; тянуть в неё HAL со всем проектом незачем.
201.4 Прошивка-самопроверка: что проверяет и как отдаёт вердикт
Самопроверка — отдельная маленькая прошивка, которая меряет то, что могло
быть собрано неправильно, и печатает числа в UART. Ядро — таблица шагов с
допусками; из _verify/ptest201/selftest201.c, который собран и прогнан:
struct check {
const char *name; /* имя шага — уходит в журнал */
uint32_t (*fn)(void); /* измерение */
uint32_t lo, hi; /* допуск: годен, если lo <= v <= hi */
};
static const struct check TAB[] = {
{ "VDD_MV", meas_vdd_mv, 3135, 3465 }, /* 3,3 В +-5 % */
{ "HSE_HZ", meas_hse_hz, 7920000, 8080000 }, /* 8 МГц +-1 % */
{ "UART_N", meas_uart_echo, 16, 16 }, /* вернулись все 16 байт */
{ "I2C_ID", meas_i2c_id, 0x68, 0x68 }, /* датчик назвал свой код */
};
Каждый шаг — про сборку, не про код. Питание меряется АЦП по внутреннему
опорному источнику VREFINT (раздел 14): просевшие 3,0 В вместо 3,3 — плохая
пайка стабилизатора. Кварц проверяется счётом его импульсов таймером от
внутреннего RC (раздел 6): непропаянный или треснувший кварц даёт ноль, а не
8 МГц ± 1 %. UART гоняет 16 байт через петлю на стенде — заодно проверены
дорожки до площадок. Датчик на шине I2C должен назвать свой код в регистре
«кто я» (раздел 17). Сюда же обычно добавляют шаги «глазами»: светодиоды
поморгать, оператор нажимает кнопку — но всё, что можно померить числом,
лучше мерить числом.
Вердикт печатается построчно, машинно-разборным форматом:
for (unsigned i = 0; i < sizeof TAB / sizeof TAB[0]; i++) {
uint32_t v = TAB[i].fn();
int pass = v >= TAB[i].lo && v <= TAB[i].hi;
printf("%02u %-6s %lu %s\r\n", i + 1u, TAB[i].name,
(unsigned long)v, pass ? "OK" : "FAIL");
if (!pass && !bad)
bad = (int)(i + 1u);
}
Логика вердикта — обычный C без регистров, поэтому её можно собрать на компьютере и проверить прогоном (тот же приём, что в разделах 79 и 184). Годная плата в опыте:
SELFTEST v1.2 UID 066DFF3033564E4343125623
01 VDD_MV 3312 OK
02 HSE_HZ 8001234 OK
03 UART_N 16 OK
04 I2C_ID 104 OK
=== PASS
Первой строкой прошивка называет свою версию и 96-битный заводской номер кристалла — он сразу попадает в журнал (201.5). Числа печатаются десятичными (104 — это 0x68), и печатаются всегда, а не только при провале: журнал с числами позволит потом увидеть, что «годные» платы новой партии все у нижней границы допуска. Плата с непропаянным кварцем:
02 HSE_HZ 0 FAIL
=== FAIL 02
Хост-сборка возвращает номер провалившегося шага кодом возврата (в опыте
exit=2) — скрипту стенда даже разбирать текст не обязательно. На плате
роль кода возврата играют строка === PASS/=== FAIL и светодиод — зелёный
или красный, чтобы вердикт видел оператор без экрана. Само ядро крошечное:
под Cortex-M3 оно скомпилировалось в 343 байта кода.
⚠️ Самопроверка не должна уехать к заказчику. Это отдельная прошивка, и после её вердикта на плату пишется боевая (201.3) — со сверкой. Если финальный шаг стенда не проверяет, что именно записано, однажды коробка изделий уедет с тестовой прошивкой внутри; страхует графа «версия» в журнале.
201.5 Серийный номер: UID камня или свой в OTP
У каждого кристалла уже есть номер: 96-битный UID, прошитый на заводе
(раздел 2а.4). Справочное руководство прямо говорит, что он уникален для
любого кристалла в любом контексте и что изменить его нельзя; лежит он
по 0x1FFFF7E8 у F1 и по 0x1FFF7A10 у F4, рядом — регистр размера Flash
(0x1FFFF7E0 и 0x1FFF7A22). Как ключ прослеживаемости UID идеален:
бесплатный, уникальный, неподделываемый. Но как «номер изделия» неудобен:
24 шестнадцатеричных знака, никакой сквозной нумерации — на коробку и в
гарантийный талон хочется «№ 000042», а не 066DFF30….
Поэтому на линии делают оба: UID читают и пишут в журнал, а свой короткий номер записывают в кристалл. Куда — зависит от семейства:
- F4: область OTP — one-time programmable, единственная по-настоящему
однократная память в камне. 512 байт данных с адреса
0x1FFF7800— 16 блоков по 32 байта — плюс 16 байт замков (0x1FFF7A00—0x1FFF7A0F, итого 528). Стереть OTP нельзя; блок можно дописывать, пока в его замке не записан0x00, после этого — только чтение. В замке допустимы лишь значения0xFFи0x00. - F1: OTP нет — ни в справочном руководстве, ни в заголовках его не
найти. Свой номер кладут в последнюю страницу Flash (1 КБ у F103,
раздел 11.5) и закрывают её защитой записи WRP (раздел 11.6) — прошивка
и случайный код её не перепишут, но полное стирание при снятии RDP её
сотрёт, это надо помнить в сервисе. Пара байт есть и в самих байтах
конфигурации —
Data0/Data1, — на серийник их не хватит, а на номер ревизии платы хватает.
🔧 Чтение и запись из CLI — по адресам выше, командами из справки
(-r32 <адрес> <размер>, -w32 <адрес> <слова через пробел>, -w8 для
байта замка):
STM32_Programmer_CLI -c port=SWD -r32 0x1FFF7A10 12 # UID: три слова
STM32_Programmer_CLI -c port=SWD -w32 0x1FFF7800 0x0000002A # № 42 в блок 0 OTP
STM32_Programmer_CLI -c port=SWD -w8 0x1FFF7A00 0x00 # замок блока 0
Прошивке номер доступен обычным чтением по тем же адресам — OTP и страница Flash отображены в память (раздел 11.5).
⚠️ Замок OTP необратим — запирать после сверки. Порядок строго: записать номер → прочитать назад и сравнить → только потом
0x00в замок. Блок, запертый с опечаткой, не исправить никогда — 32 байта потеряны, изделие живёт с «чужим» номером или переезжает в следующий блок (их всего 16).
201.6 Байты конфигурации на выпуске: RDP, сторож, BOR
На отладке байты конфигурации (разделы 11.6 и 35б.7) обычно нетронуты. В
изделии — нет: последним шагом линия переводит камень в «боевое» состояние.
Имена и значения ниже — из базы устройств самого CubeProgrammer для F405/407
(файл 0x413) и F1 medium-density (0x410):
| Байт | В бою | Зачем |
|---|---|---|
RDP |
уровень 1: F4 — любое значение, кроме 0xAA и 0xCC (например 0xBB); F1 — любое, кроме 0xA5 |
отладчик не прочитает прошивку и калибровки; снятие — только с полным стиранием |
WDG_SW |
0 — аппаратный сторож |
IWDG (раздел 12.2) стартует сам с включения питания: зависшее изделие перезапустится, даже если прошивка сторож включить забыла |
BOR_LEV (только F4) |
порог по питанию платы; для 3,3 В — 0 (уровень 3: сброс ниже 2,70—3,60 В) |
просадка питания даёт чистый сброс, а не работу на недопустимом напряжении; у F1 порог фиксированный, настраивать нечего |
nRST_STOP, nRST_STDBY |
1, если изделие использует сон (раздел 10) |
иначе первый же вход в Stop/Standby — сброс |
STM32_Programmer_CLI -c port=SWD -ob displ # показать все байты
STM32_Programmer_CLI -c port=SWD -ob WDG_SW=0 # сторож — аппаратный
STM32_Programmer_CLI -c port=SWD -ob BOR_LEV=0 # сброс ниже 2,70—3,60 В
STM32_Programmer_CLI -c port=SWD -ob RDP=0xBB # защита чтения — последней
RDP ставится последней командой последнего шага: после уровня 1 отладчик
Flash больше не читает, так что ни сверить прошивку, ни дочитать OTP уже
нельзя. Для сервиса уровень 1 обратим: -rdu переводит защиту в уровень 0,
по справке CLI — «shifting the RDP level from level 1 to level 0», и это
всегда полное стирание Flash (раздел 11.6).
⚠️ Уровень 2 (
RDP=0xCC) — решение без кнопки «назад». Отладка и заводской загрузчик отключаются навсегда; перепрошить возврат из ремонта будет нечем, кроме как через собственный загрузчик изделия (раздел 48), если он есть и жив. На линию уровень 2 ставят осознанно и не в первый месяц выпуска.
201.7 Журнал выпуска: строка на плату — навсегда
Вердикт без журнала забыт через минуту. Журнал выпуска — простой файл (или
таблица в базе), куда стенд дописывает по строке на каждую плату — и годную,
и брак. Из скрипта стенда journal201.py, прогнанного в опыте:
row = ";".join([f"{serial:06d}", ts, station, uid, "1.4.0", crc] + meas + [verdict])
with open(path, "a", encoding="utf-8") as f:
f.write(row + "\n")
000042;2026-09-24T10:41:07;ST-01;066DFF3033564E4343125623;1.4.0;96D91CA4;3312;8001234;16;104;PASS
Что в строке: серийный номер, время, номер стенда, UID кристалла, версия боевой прошивки и её CRC32, все измеренные числа и вердикт. Следующий серийник скрипт берёт из самого журнала — последний плюс один: журнал и есть единственный источник нумерации. Хранить это дёшево: строка в опыте — 98 байт, сто тысяч плат — меньше 10 МБ; заводить «ротацию логов» для журнала выпуска не надо, он пишется один раз и хранится всю жизнь изделия.
Зачем числа, а не только «годен»: по журналу видно то, чего не видно на одной
плате. Партия, у которой VDD_MV сползла с 3312 к 3200, — это меняющийся
стабилизатор у поставщика; рост брака по шагу 02 после смены катушки кварцев —
разговор с закупкой, а не с программистом. И когда изделие вернётся из поля
через год, строка журнала по его серийнику расскажет, какой прошивкой, каким
стендом и с какими числами оно уходило. Возвраты и разбор отказов — часть
поддержки (раздел 79.8); журнал — то, что делает их разбираемыми.
201.8 Грабли
На столе плата прошивается, на стенде — нет. Между ST-LINK и камнем
теперь иглы, провода ложа и наводки соседнего оборудования: частота SWD, на
которой отлаживались за столом, на стенде даёт ошибки подключения. Лечится
freq=1000 (или ниже) в строке соединения и проверкой прижима игл; симптом
«первые платы шли, потом посыпались ошибки» — стёршиеся или загрязнённые
иглы, они расходный материал.
Коробка изделий уехала с тестовой прошивкой. Камень прошивается дважды,
и если стенд после самопроверки не сверяет записанную боевую прошивку (-v)
и не пишет её версию в журнал, подмена шага незаметна. Финальная проверка —
прочитать версию/CRC из камня, а не поверить скрипту на слово.
RDP поставили раньше сверки. После уровня 1 отладчик Flash не читает:
-v, чтение OTP и дочитывание журнала невозможны, а снятие защиты стирает
камень целиком. Байты защиты — строго последний шаг, после всех проверок.
Замок OTP записали до сверки номера. 0x00 в байте замка необратим:
блок с опечаткой остаётся навсегда. Сначала записать, прочитать назад,
сравнить — потом запирать (201.5).
Два стенда выдали один серийник. Каждый стенд вёл свой счётчик — номера
разъехались при первом же параллельном запуске. Источник номера должен быть
один: общий журнал или диапазоны по стендам (ST-01 — с 1, ST-02 —
с 500 000), и номер стенда — в строке журнала.
Аппаратный сторож перезапускает самопроверку. Если WDG_SW=0 записан
до теста (или пришёл с предыдущей попытки), IWDG стартует с включения, и
самопроверка, которая его не гладит, сбрасывается на середине — «плата
перезагружается, брак», хотя плата годная. Байты конфигурации — после
самопроверки; а сервисному стенду помнить: у выпущенных плат сторож уже
аппаратный, подключаться — под сбросом (mode=UR, 201.2).
201.9 Итоги
- Линия отвечает не «правилен ли код», а «собрана ли плата»; вердикт — годен/брак плюс строка в журнале. Такт при 200 платах в смену — 144 с на всё: установить, протестировать, снять.
- Стенд — ложе с иглами; на площадки при разводке выводят SWD с
NRST, UART и питание; плату питает стенд и первым делом меряет ток. - Цена прошивки по SWD считается: пакет 46 тактов на слово → мегабайт на
4 МГц — не меньше 3 с передачи; плюс сама Flash (F4: 4,2 с запись и 8 с
стирание; F1: 3,4 с запись полусловами при мгновенном стирании). Итого
128 КБ на F1 — ≈4 с, 1 МБ на F4 — ≈15 с; на F4
-e allбыстрее стирания секторами. - Самопроверка — таблица шагов с допусками (3,3 В ± 5 %, кварц 8 МГц ± 1 %, эхо 16 байт, код датчика); вердикт — числами в UART и кодом возврата; ядро — 343 байта, логика проверена прогоном на компьютере.
- UID (96 бит, изменить нельзя) — ключ прослеживаемости; свой номер: F4 —
OTP 512 + 16 байт замков, замок
0x00необратим; у F1 OTP нет — страница Flash под WRP. - Байты конфигурации — последним шагом:
WDG_SW=0,BOR_LEVпо питанию (для 3,3 В — сброс ниже 2,70—3,60 В),RDPуровень 1 в самом конце; уровень 2 необратим. - Журнал: 98 байт на плату, сто тысяч плат — меньше 10 МБ; писать числа, а не только вердикты, — по ним видно дрейф партии; серийники — из одного источника.
Полные исходники
selftest201.c — прошивка-самопроверка: шаги, допуски, вердикт.
/* selftest201.c — тестовая прошивка-самопроверка, раздел 201.4.
*
* Ядро одно и то же для платы и для компьютера: таблица шагов, допуски,
* вердикт. На плате meas_* читают ADC, таймер и периферию, а printf
* уходит в UART; на компьютере (-DHOST) те же шаги получают числа из
* сценария — логика вердикта проверяется прогоном без железа, как
* в разделе 184.
*/
#include <stdint.h>
#include <stdio.h>
uint32_t meas_vdd_mv(void); /* питание: ADC по внутреннему опорному */
uint32_t meas_hse_hz(void); /* кварц: таймер считает HSE против HSI */
uint32_t meas_uart_echo(void); /* UART: 16 байт в петлю стенда и обратно */
uint32_t meas_i2c_id(void); /* датчик платы: регистр «кто я» по I2C */
const uint32_t *uid(void); /* 96 бит заводского номера (раздел 2а.4) */
struct check {
const char *name; /* имя шага — уходит в журнал */
uint32_t (*fn)(void); /* измерение */
uint32_t lo, hi; /* допуск: годен, если lo <= v <= hi */
};
static const struct check TAB[] = {
{ "VDD_MV", meas_vdd_mv, 3135, 3465 }, /* 3,3 В +-5 % */
{ "HSE_HZ", meas_hse_hz, 7920000, 8080000 }, /* 8 МГц +-1 % */
{ "UART_N", meas_uart_echo, 16, 16 }, /* вернулись все 16 байт */
{ "I2C_ID", meas_i2c_id, 0x68, 0x68 }, /* датчик назвал свой код */
};
int selftest_run(const char *ver)
{
const uint32_t *u = uid();
int bad = 0;
printf("SELFTEST %s UID %08lX%08lX%08lX\r\n", ver,
(unsigned long)u[2], (unsigned long)u[1], (unsigned long)u[0]);
for (unsigned i = 0; i < sizeof TAB / sizeof TAB[0]; i++) {
uint32_t v = TAB[i].fn();
int pass = v >= TAB[i].lo && v <= TAB[i].hi;
printf("%02u %-6s %lu %s\r\n", i + 1u, TAB[i].name,
(unsigned long)v, pass ? "OK" : "FAIL");
if (!pass && !bad)
bad = (int)(i + 1u);
}
if (bad)
printf("=== FAIL %02d\r\n", bad);
else
printf("=== PASS\r\n");
return bad; /* на плате по вердикту зажигается зелёный или красный */
}
#ifdef HOST
/* Сценарии: 0 — все измерения в норме; 1 — кварц молчит, таймер
* насчитал ноль. Числа «в норме» — обычные для платы с 3,3 В и 8 МГц. */
static const uint32_t S[2][4] = {
{ 3312, 8001234, 16, 0x68 },
{ 3308, 0, 16, 0x68 },
};
static int sc;
uint32_t meas_vdd_mv(void) { return S[sc][0]; }
uint32_t meas_hse_hz(void) { return S[sc][1]; }
uint32_t meas_uart_echo(void) { return S[sc][2]; }
uint32_t meas_i2c_id(void) { return S[sc][3]; }
static const uint32_t UID_HOST[3] = { 0x43125623u, 0x33564E43u, 0x066DFF30u };
const uint32_t *uid(void) { return UID_HOST; }
int main(int argc, char **argv)
{
sc = argc > 1 && argv[1][0] == '1';
return selftest_run("v1.2"); /* код возврата читает скрипт стенда */
}
#endif
journal201.py — скрипт стенда: журнал выпуска, строка на плату.
# -*- coding: utf-8 -*-
"""Раздел 201.7: журнал выпуска — одна строка на плату.
Скрипт стенда: запускает самопроверку (в опыте — её HOST-сборку),
разбирает вывод и дописывает строку в journal.csv. Серийный номер —
следующий за последним в журнале; CRC32 считается от исходника
самопроверки (на линии — от прошитого образа)."""
import os
import re
import subprocess
import sys
import zlib
exe, sc, ts, station = sys.argv[1:5]
out = subprocess.run([exe, sc], capture_output=True, text=True).stdout
uid = re.search(r"UID ([0-9A-F]{24})", out).group(1)
meas = re.findall(r"^\d\d \S+\s+(\d+) ", out, re.M)
m = re.search(r"=== FAIL (\d+)", out)
verdict = ("FAIL " + m.group(1)) if m else "PASS"
crc = "%08X" % (zlib.crc32(open("selftest201.c", "rb").read()) & 0xFFFFFFFF)
path = "journal.csv"
serial = 42 # первый номер партии
if os.path.exists(path):
with open(path, encoding="utf-8") as f:
for line in f:
if line.strip():
serial = int(line.split(";")[0]) + 1
row = ";".join([f"{serial:06d}", ts, station, uid, "1.4.0", crc] + meas + [verdict])
with open(path, "a", encoding="utf-8") as f:
f.write(row + "\n")
print(row)
print("bytes=%d" % (len(row.encode("utf-8")) + 1))
Дальше: раздел 201а «Выпуск прошивки: версия, метаданные и один файл на производство» — откуда берутся версия и CRC, которые стенд пишет в журнал, и как собрать один файл на линию.