# bootloader: SEMC Init, Led dictionary

This commit is contained in:
Dmitry Akimov 2026-07-17 16:49:02 +03:00
parent 12bb949834
commit acb1fed1f2
31 changed files with 1278 additions and 168 deletions

View file

@ -80,7 +80,7 @@ CheckOptions:
- key: readability-identifier-naming.PointerParameterPrefix
value: "p_" # uint8_t *p_buffer
- key: readability-function-size.LineThreshold
value: '60'
value: '90'
- key: readability-function-size.StatementThreshold
value: '30'
- key: readability-magic-numbers.IgnoredIntegerValues

View file

@ -1,24 +1,6 @@
/*
* bsp_button тактовые кнопки TactBut1 / TactBut2
*
* Аппаратура:
* TactBut1 GPIO2 pin 30 (GPIO_B1_14), подтяжка к 3V3 внешняя, нажатие = LOW
* TactBut2 GPIO2 pin 31 (GPIO_B1_15), подтяжка к 3V3 внешняя, нажатие = LOW
*
* Пины инициализированы в BOARD_InitPins() (generated/pin_mux.c).
* bsp_button_init() не трогает GPIO только сбрасывает внутреннее состояние.
*
* Использование (bare-metal):
* bsp_button_init();
* // в tick-коллбэке каждые 5 мс:
* bsp_button_poll();
* // в основном цикле:
* if (bsp_button_get_event_pressed(BSP_BUTTON_1)) { ... }
*
* Использование (FreeRTOS):
* // в таске с vTaskDelay(5):
* bsp_button_poll();
* if (bsp_button_get_event_pressed(BSP_BUTTON_1)) { xQueueSend(...); }
*/
#ifndef BSP_BUTTON_H

View file

@ -2,8 +2,6 @@
* @file display.h
* @brief BSP: ELCDIF display driver TFT4 / TFT7 / TFT8 / TFT10.
*
* Bare-metal compatible: callback-based FRAME_DONE notification.
* No FreeRTOS dependency in this layer.
*/
#ifndef BSP_DISPLAY_DISPLAY_H_

View file

@ -91,6 +91,16 @@ static void board_mpu_init(void)
MPU->RBAR = ARM_MPU_RBAR(10U, 0x40000000U);
MPU->RASR = ARM_MPU_RASR(0U, ARM_MPU_AP_FULL, 2U, 0U, 0U, 0U, 0U, ARM_MPU_REGION_SIZE_4MB);
/* Region 11: Device, NIC-301 GPV (bus-arbitration QoS) 0x41000000, 8 MB.
* Region 10 не покрывает GPV0/SIM_MAIN (0x41000000) и GPV4/SIM_M7
* (0x41400000, "Cortex-M7 read/write_qos") лежат за пределами его 4 MB
* от 0x40000000, попадают только под Region 0 (deny-all, errata-воркэраунд
* выше) запись фолтит, хотя регистры реальные и документированы (i.MX
* RT1050 RM, гл. 29 "Network Interconnect Bus System (NIC-301)"). Нужен
* bsp_sdram_configure() для read_qos/write_qos регионов SDRAM/LCD/M7. */
MPU->RBAR = ARM_MPU_RBAR(11U, 0x41000000U);
MPU->RASR = ARM_MPU_RASR(0U, ARM_MPU_AP_FULL, 2U, 0U, 0U, 0U, 0U, ARM_MPU_REGION_SIZE_8MB);
ARM_MPU_Enable(MPU_CTRL_PRIVDEFENA_Msk);
SCB_EnableDCache();

View file

@ -8,8 +8,6 @@
* LED_HEARTBEAT системный, мигает как признак жизни прошивки
* LED_APP прикладной, управляется из firmware по ситуации
*
* Пины сконфигурированы в generated/pin_mux.h. Этот хедер не знает
* ни про GPIO-порты, ни про NXP SDK.
*/
#include <stdbool.h>

View file

@ -77,8 +77,9 @@ target_compile_definitions(firmware_test PRIVATE
/* Инициализация — вызвать до bsp_tick_init() */
bsp_status_t bsp_qspi_init(void);
/* Идентификация */
/* Идентификация — bsp_qspi_read_jedec_id() работает и без успешного init() */
bsp_status_t bsp_qspi_read_jedec_id(bsp_qspi_jedec_t *p_jedec);
const char *bsp_qspi_decode_chip(uint8_t cap_byte, uint32_t *p_size_mb); /* device_id & 0xFF → "W25Q128" + МБ */
uint32_t bsp_qspi_flash_size(void); /* доступно после init() */
/* Стирание */
@ -117,9 +118,13 @@ board_hw_init();
bsp_qspi_init(); /* ← до bsp_tick_init() */
bsp_tick_init();
/* Идентификация чипа */
/* Идентификация чипа — работает даже если bsp_qspi_init() выше вернула
ошибку (LUT для JEDEC грузится безусловным первым шагом внутри неё) */
bsp_qspi_jedec_t jedec;
bsp_qspi_read_jedec_id(&jedec);
uint32_t size_mb;
const char *chip_name = bsp_qspi_decode_chip((uint8_t) (jedec.device_id & 0xFF), &size_mb);
/* chip_name = "W25Q128", size_mb = 16 — либо "UNKNOWN"/0, если чип не опознан */
/* Стереть сектор и записать страницу */
bsp_qspi_erase_sector(0x00010000);

View file

@ -90,9 +90,29 @@ bsp_status_t bsp_qspi_init(void);
*
* @param[out] p_jedec Результат. Не NULL.
* @retval BSP_OK / BSP_ERR.
*
* @note Работает и после проваленного bsp_qspi_init() LUT-слот для чтения
* JEDEC ID грузится безусловным первым шагом внутри него, до любой из
* проверок, на которых init() мог отвалиться. Полезно для диагностики
* "что именно распаяно", когда чип не опознан/не тот.
*/
bsp_status_t bsp_qspi_read_jedec_id(bsp_qspi_jedec_t *p_jedec);
/**
* @brief Человекочитаемое имя и ёмкость чипа по capacity byte JEDEC ID.
*
* Тот же байт, что различает поддерживаемые чипы в bsp_qspi_init() вынесен
* отдельно, чтобы потребитель мог опознать чип из уже прочитанного
* bsp_qspi_jedec_t.device_id, не завися от успеха bsp_qspi_init().
*
* @param[in] cap_byte Байт ёмкости (device_id & 0xFF).
* @param[out] p_size_mb Ёмкость чипа, МБ. 0, если байт не распознан.
* Может быть NULL, если размер не нужен.
* @return "W25Q64"/"W25Q128"/"W25Q256"/"W25Q512", либо "UNKNOWN" для
* нераспознанного байта.
*/
const char *bsp_qspi_decode_chip(uint8_t cap_byte, uint32_t *p_size_mb);
/**
* @brief Стирание сектора 4 KB.
*

View file

@ -657,6 +657,42 @@ AT_QUICKACCESS_SECTION_CODE(static bsp_status_t qspi_detect_chip(uint8_t cap_byt
return BSP_OK;
}
const char *bsp_qspi_decode_chip(uint8_t cap_byte, uint32_t *p_size_mb)
{
const char *p_name;
uint32_t size_mb;
switch (cap_byte)
{
case BSP_QSPI_CAP_64MBIT:
p_name = "W25Q64";
size_mb = 8U;
break;
case BSP_QSPI_CAP_128MBIT:
p_name = "W25Q128";
size_mb = 16U;
break;
case BSP_QSPI_CAP_256MBIT:
p_name = "W25Q256";
size_mb = 32U;
break;
case BSP_QSPI_CAP_512MBIT:
p_name = "W25Q512";
size_mb = 64U;
break;
default:
p_name = "UNKNOWN";
size_mb = 0U;
break;
}
if (p_size_mb != NULL)
{
*p_size_mb = size_mb;
}
return p_name;
}
/**
* @brief Общая реализация erase-операций.
*

View file

@ -1,9 +1,9 @@
# bsp_sdram — внешняя SDRAM MT48LC16M16A2 (32 МБ)
Минимальная верификация доступности внешней SDRAM, подключённой к SEMC.
Подробное тестирование (паттерны, шина адреса/данных, retention) выполняется
в тест-модуле `firmware_test/test_sdram.c`, который использует константы
и API этого модуля.
Подъём SEMC (для прошивок без DCD) и минимальная верификация доступности
внешней SDRAM, подключённой к SEMC. Подробное тестирование (паттерны, шина
адреса/данных, retention) выполняется в тест-модуле `firmware_test/test_sdram.c`,
который использует API этого модуля (путь с DCD, см. ниже).
---
@ -29,16 +29,45 @@
Тестовая база смещена на 2 МБ от начала — гарантированно выше `.data`/`.bss`
прошивки и ниже non-cacheable региона.
**Важно:** SEMC инициализируется через DCD **до вызова `main()`**. Этот модуль
не настраивает SEMC и не трогает его регистры. Если DCD не отработал —
`bsp_sdram_init()` вернёт ошибку, но исправить ситуацию из модуля нельзя.
---
## Контракт: кто поднимает SEMC
Два независимых пути, в зависимости от того, есть ли у прошивки DCD:
- **С DCD** (`firmware_test`, `bsp_boot_xip`): SEMC поднят DCD **до вызова
`main()`**. Этот модуль в этом случае регистры SEMC не трогает — только
`bsp_sdram_init()` для верификации.
- **Без DCD** (bootloader, `bsp_boot_xip_no_dcd`; в будущем `tft_app`): SEMC
не поднимает никто, пока не будет явно вызван `bsp_sdram_configure()`
побитовый порт проверенной в производстве DCD-последовательности
(`tools/host/dcd/dcd.bin`, «блок 2» — первый блок там мёртвый код,
полностью перезаписывается вторым до какого-либо использования, поэтому
не переносился). Источник истины — сам DCD, а не пересчёт по формулам SDK
из наносекунд: значения регистров контроллера SEMC скопированы дословно.
**Почему `bsp_sdram_configure()` сама поднимает тактирование.** Штатный
`BOARD_BootClockRUN()` (его вызывает `board_hw_init()` в каждой прошивке) НЕ
настраивает PLL2 → PFD2 → делитель SEMC — этот блок в `clock_config.c`
выключен макросом `SKIP_SYSCLK_INIT`, который определён для **всех** таргетов
сборки (`bsp/CMakeLists.txt`), включая `bsp_boot_xip_no_dcd`. Смысл макроса —
не глитчить PLL, пока на нём уже висит поднятая DCD SDRAM (случай
`firmware_test`); но для пути без DCD это побочно означает, что тактирование
SEMC не настраивает вообще никто, кроме `bsp_sdram_configure()`. Результат —
SEMC ≈135.77 МГц (PLL2 528 МГц → PFD2 `FRAC=35` ≈271.54 МГц → `SEMC_PODF` ÷2),
под эту частоту тюнингованы все timing-регистры DCD.
Вызывать `bsp_sdram_configure()` в пути с DCD не нужно и не имеет смысла —
DCD уже сделала эту работу раньше, а `bsp_sdram_configure()` дублировала бы
её же на живой памяти.
---
## API
```c
bsp_status_t bsp_sdram_init(void);
bsp_status_t bsp_sdram_configure(void); /* поднять SEMC: тактирование → пины → контроллер → init-команды SDRAM */
bsp_status_t bsp_sdram_init(void); /* верифицировать SDRAM (SEMC уже поднят — DCD либо bsp_sdram_configure()) */
```
**Публичные константы:**
@ -55,6 +84,37 @@ bsp_status_t bsp_sdram_init(void);
Константы размеров — для потребителей; `bsp_sdram` не запускает по ним
внутренних проходов.
**Поведение `bsp_sdram_configure()`:**
1. Тактирование: `CLOCK_InitSysPll()` (PLL2, 528 МГц) → `CLOCK_InitSysPfd()`
(PFD2, `FRAC=35`) → `CLOCK_SetMux`/`CLOCK_SetDiv` (SEMC ← alt ← PFD2, ÷2).
2. IOMUX: пины `GPIO_EMC_00..41` → ALT0 (функция SEMC), `SW_PAD_CTL_PAD` =
`0x000110F9`; `GPIO_EMC_39` (`SEMC_DQS`) дополнительно получает `SION`
(иначе SEMC не сможет читать собственный read-strobe).
3. Регистры контроллера SEMC (`MCR`, `BMCR0/1`, `BR[0..8]`, `IOCR`,
`SDRAMCR0..3`, `DBICR0/1`, `IPCR1/2`) — побитово из DCD.
4. Командная последовательность SDRAM через `SEMC_SendIPCommand()`:
precharge-all → 2×auto-refresh → mode-set (`0x33` = BL8/sequential/CL3,
согласуется с `SDRAMCR0` и даташитом MT48LC16M16A2) → включение
авто-refresh (`SDRAMCR3 = 0x50210A09`).
5. AXI-QoS приоритеты SDRAM-мастеров (`0x41044100/104` LCD, `0x41442100/104`
Cortex-M7 read/write_qos) — хвост DCD. Это не SEMC и не в заголовках
`sdk/devices/MIMXRT1052` (NXP не заворачивает ARM'овский NIC-301 IP в
CMSIS-структуру), но регистры реальные и документированы (i.MX RT1050 RM,
гл. 29 "Network Interconnect Bus System (NIC-301)" — адреса сверены день-в-
день: `0x41044100` = `GPV0_BASE(0x41000000)+0x44000+0x100` =
`SIM_MAIN.LCD.read_qos`). Для bootloader инертны (нет конкуренции LCD/DMA
vs CPU за шину), но входят в проверенную последовательность и понадобятся
`tft_app`.
**Требует MPU Region 11** (`board_mpu_init()`, `bsp/generated/board.c`,
`0x41000000`, 8 МБ) — штатный Region 10 (периферия, только 4 МБ от
`0x40000000` = 4 домена AIPSTZ) NIC-301 GPV не покрывает; без Region 11 шаг 5
фолтит (deny-all errata-регион 0 перехватывает всё, что не покрыто более
специфичным регионом — `PRIVDEFENA` тут не спасает, т.к. регион 0 покрывает
весь диапазон 0x0..0xFFFFFFFF и потому всегда matched). DCD это переживает —
ROM пишет регистры до включения MPU. Уже добавлен в `board_mpu_init()`.
**Поведение `bsp_sdram_init()`:**
1. Ждёт перехода SEMC в IDLE (`SEMC->STS0 & SEMC_STS0_IDLE_MASK`),
@ -66,11 +126,13 @@ bsp_status_t bsp_sdram_init(void);
**Коды возврата:**
| Код | Условие |
| ----------------- | ------------------------------------------- |
| `BSP_OK` | SDRAM доступна, оба паттерна совпали |
| `BSP_ERR_TIMEOUT` | SEMC не перешёл в IDLE за 10 мс |
| `BSP_ERR_INIT` | Хотя бы один паттерн не совпал при readback |
| Функция | Код | Условие |
| -------------------------- | ----------------- | -------------------------------------------- |
| `bsp_sdram_configure()` | `BSP_OK` | Тактирование/пины/регистры/команды прошли |
| `bsp_sdram_configure()` | `BSP_ERR_INIT` | IP-команда SEMC вернула ошибку (precharge/refresh/mode-set) |
| `bsp_sdram_init()` | `BSP_OK` | SDRAM доступна, оба паттерна совпали |
| `bsp_sdram_init()` | `BSP_ERR_TIMEOUT` | SEMC не перешёл в IDLE за 10 мс |
| `bsp_sdram_init()` | `BSP_ERR_INIT` | Хотя бы один паттерн не совпал при readback |
---
@ -79,10 +141,19 @@ bsp_status_t bsp_sdram_init(void);
```c
#include "bsp/sdram.h"
/* Путь С DCD (firmware_test) — SEMC уже поднят до main(): */
if (bsp_sdram_init() != BSP_OK) {
handle_critical_error();
}
/* Путь БЕЗ DCD (bootloader smoke-test и т.п.) — сначала поднять SEMC сами: */
if (bsp_sdram_configure() != BSP_OK) {
handle_semc_bringup_error();
}
if (bsp_sdram_init() != BSP_OK) {
handle_sdram_error();
}
/* Работа с памятью по адресам внутри
[BSP_SDRAM_BASE_ADDR, BSP_SDRAM_BASE_ADDR + BSP_SDRAM_SIZE_BYTES) */
```
@ -91,6 +162,10 @@ if (bsp_sdram_init() != BSP_OK) {
## CMake
Сегодня линкует только `firmware_test` (путь с DCD). Для пути без DCD
потребитель (например, bootloader) добавляет зависимость сам — модуль
никого за собой не тянет:
```cmake
target_link_libraries(firmware_test PRIVATE bsp_sdram)
```
@ -98,8 +173,8 @@ target_link_libraries(firmware_test PRIVATE bsp_sdram)
**Зависимости модуля:**
| Зависимость | Тип | Описание |
| ------------ | ------- | -------------------------------------------------- |
| ------------ | ------- | ----------------------------------------------------------------|
| `bsp_status` | PUBLIC | `bsp_status_t` в публичном API |
| `bsp_tick` | PRIVATE | `bsp_tick_get_ms()` для таймаута SEMC IDLE |
| `sdk_semc` | PRIVATE | `fsl_semc.h``SEMC->STS0`, `SEMC_STS0_IDLE_MASK` |
| `bsp_board` | PRIVATE | Общие board-уровневые символы |
| `sdk_semc` | PRIVATE | `fsl_semc.h`регистры `SEMC->`, `SEMC_SendIPCommand()` |
| `bsp_board` | PRIVATE | Транзитивно даёт `sdk_clock` (`CLOCK_Init*()`) и `sdk_device` (`IOMUXC`) — отдельных PRIVATE-строк на них не заводили |

View file

@ -2,9 +2,15 @@
* @file sdram.h
* @brief BSP для внешней SDRAM MT48LC16M16A2 (32 МБ, шина 16 бит).
*
* Архитектурное ограничение [DECISION]:
* SEMC инициализируется DCD до вызова main(). Этот модуль не трогает
* регистры SEMC только верифицирует работоспособность памяти.
* Два пути в зависимости от того, кто использует SEMC:
*
* - Прошивки С DCD (firmware_test): SEMC поднят DCD до main().
* bsp_sdram_init() только верифицирует доступность памяти.
*
* - Прошивки БЕЗ DCD (bootloader smoke-test; в будущем tft_app под свой
* XIP): bsp_sdram_configure() сам поднимает SEMC (порт проверенной
* DCD-последовательности в C, см. tools/host/dcd/dcd.bin), затем
* bsp_sdram_init() верифицирует, как и в первом случае.
*
* Карта памяти:
* 0x80000000 начало SDRAM (SEMC BR0)
@ -14,16 +20,10 @@
* Тестовый регион (не пересекается с .data/.bss и non-cacheable):
* 0x80200000 начало (2 MB offset от базы)
*
* Использование:
* Использование (путь без DCD):
* @code
* bsp_sdram_result_t result;
*
* if (bsp_sdram_init() != BSP_OK) { // DCD не отработал
* handle_critical_error();
* }
*
* bsp_sdram_test_fast(&result); // ~50 мс, 64 KB
* bsp_sdram_test_full(&result); // ~25 с, 1 MB
* if (bsp_sdram_configure() != BSP_OK) { handle_semc_bringup_error(); }
* if (bsp_sdram_init() != BSP_OK) { handle_sdram_error(); }
* @endcode
*/
@ -65,17 +65,51 @@
/* ── Public API ────────────────────────────────────────────────────────── */
/**
* @brief Поднять SEMC и инициализировать внешнюю SDRAM (для вызывателей без DCD).
*
* Побитово-точный порт проверенной в производстве DCD-последовательности
* (tools/host/dcd/dcd.bin, «блок 2») в вызываемый C-код:
* 1. тактирование SEMC (PLL2 528 МГц PFD2 271.54 МГц ÷2 = 135.77 МГц);
* 2. IOMUX/PAD пинов GPIO_EMC (функция SEMC, DQS с SION);
* 3. регистры контроллера SEMC (MCR/BR/IOCR/SDRAMCR0..3/), значения из DCD;
* 4. командная последовательность SDRAM: precharge-all 2×auto-refresh
* mode-set включение авто-refresh;
* 5. AXI-QoS приоритеты SDRAM-мастеров (LCD/Cortex-M7) NIC-301 GPV,
* отдельный IP вне карты SEMC (i.MX RT1050 RM, гл. 29).
*
* Прошивки С DCD (firmware_test) получают то же самое до main() и эту функцию
* НЕ вызывают только bsp_sdram_init().
*
* @note Безопасно вызывать в рантайме после board_hw_init(): CPU тактируется от
* ARM PLL, FlexSPI-XIP от USB1 PLL; PLL2/PFD2 поднимаются с нуля и не
* задевают ни то, ни другое (под SKIP_SYSCLK_INIT штатный
* BOARD_BootClockRUN() эту цепочку не трогает).
*
* @note Требует MPU Region 11 (board_mpu_init(), bsp/generated/board.c)
* шаг 5 пишет NIC-301 GPV (0x41000000+), который Region 10
* (периферия, только 4 МБ от 0x40000000) не покрывает; без Region 11
* запись фолтит (deny-all errata-регион 0 перехватывает всё
* непокрытое). Уже добавлен заметка для будущих правок MPU-таблицы.
*
* @retval BSP_OK SEMC поднят, SDRAM инициализирована.
* @retval BSP_ERR_INIT Командная последовательность SDRAM не завершилась
* (IP-команда SEMC вернула ошибку).
*/
bsp_status_t bsp_sdram_configure(void);
/**
* @brief Верифицировать доступность SDRAM.
*
* Проверяет что SEMC контроллер инициализирован DCD и SDRAM отвечает
* выполняет минимальный write/read/verify на первых 4 байтах тестового
* региона. Не затрагивает .data/.bss прошивки.
* Проверяет что SEMC контроллер инициализирован (DCD или bsp_sdram_configure())
* и SDRAM отвечает выполняет минимальный write/read/verify на первых 4 байтах
* тестового региона. Не затрагивает .data/.bss прошивки.
*
* @note Не реинициализирует SEMC DCD уже сделал это до main().
* @note Не (ре)инициализирует SEMC предполагает, что DCD либо
* bsp_sdram_configure() уже это сделали.
*
* @retval BSP_OK SDRAM доступна и отвечает корректно.
* @retval BSP_ERR_INIT SEMC не готов (DCD не отработал).
* @retval BSP_ERR_INIT SEMC не готов (инициализация не отработала).
* @retval BSP_ERR_TIMEOUT SEMC занят дольше ожидаемого.
*/
bsp_status_t bsp_sdram_init(void);

View file

@ -1,9 +1,14 @@
/**
* @file sdram.c
* @brief Верификация внешней SDRAM MT48LC16M16A2 (32 МБ, шина 16 бит).
* @brief Подъём SEMC и верификация внешней SDRAM MT48LC16M16A2 (32 МБ, 16 бит).
*
* SEMC инициализируется DCD до main() этот модуль только проверяет
* доступность памяти. Регистры SEMC не модифицируются.
* Два независимых куска:
*
* bsp_sdram_configure() поднимает SEMC. Побитовый порт проверенной DCD-последовательности
* (tools/host/dcd/dcd.bin)
*
* bsp_sdram_init() верификация уже поднятой памяти (DCD или configure).
* Регистры SEMC не модифицирует.
*
* Кэш: SDRAM настроена как Normal Write-Back cacheable (MPU Region 8).
* Верификация требует явного cache maintenance перед readback иначе
@ -13,12 +18,13 @@
#include "bsp/sdram.h"
#include "bsp/tick.h"
#include "fsl_clock.h"
#include "fsl_semc.h"
#include <stdbool.h>
#include <stdint.h>
/* ── Константы ─────────────────────────────────────────────────────────── */
/* ── Константы верификации ─────────────────────────────────────────────── */
/** @brief Таймаут ожидания готовности SEMC контроллера, мс. */
#define SDRAM_SEMC_IDLE_TIMEOUT_MS 10U
@ -38,11 +44,202 @@
/** @brief Инверсия эталонного паттерна. */
#define SDRAM_VERIFY_PATTERN_B 0x5A5A5A5AUL
/* ── Константы SEMC (значения из DCD, см. tools/host/dcd/dcd.bin) */
/** @brief PFD2 FRAC из DCD (PFD_528=0x00230000): 528 МГц ×18/35 ≈ 271.54 МГц. */
#define SDRAM_SEMC_PFD2_FRAC 35U
/**
* @brief Индекс пина GPIO_EMC_39 (SEMC_DQS) в массивах IOMUXC.
*
* SW_MUX_CTL_PAD[]/SW_PAD_CTL_PAD[] начинаются с GPIO_EMC_00 (индекс 0), шаг 1.
* DQS единственный пин, которому DCD ставит SION (вход strobe должен быть
* принудительно включён, чтобы SEMC читал собственный строб).
*/
#define SDRAM_EMC_PAD_FIRST 0U
#define SDRAM_EMC_PAD_LAST 41U /* GPIO_EMC_41 — последний EMC-пин */
#define SDRAM_DQS_PAD_INDEX 39U /* GPIO_EMC_39 = SEMC_DQS */
/** @brief SION (Software Input On), бит 4 SW_MUX_CTL_PAD — только для DQS. */
#define SDRAM_MUX_SION 0x00000010UL
/** @brief SW_PAD_CTL для всех EMC-пинов (DCD: 0x000110F9 на каждый). */
#define SDRAM_PAD_CTL_VALUE 0x000110F9UL
/** @brief Адрес SDRAM для IP-команд SEMC (совпадает с BR0 = базой SDRAM). */
#define SDRAM_SEMC_IPCMD_ADDR 0x80000000UL
/**
* @brief Значение mode-register SDRAM (DCD IPTXDAT=0x33).
*
* M[2:0]=011 (burst length 8), M3=0 (sequential), M[6:4]=011 (CAS latency 3)
* согласуется с SDRAMCR0 (BL8/CL3) и даташитом MT48LC16M16A2.
*/
#define SDRAM_SEMC_MODE_REG 0x00000033UL
/*
* AXI-QoS регистры арбитража доступа к SDRAM NIC-301 GPV (Global
* Programmer's View). Не влияют на корректность самой SDRAM только на арбитраж при
* конкуренции за шину (LCD/CPU vs SDRAM). */
#define SDRAM_QOS_LCD_READ (*(volatile uint32_t *) 0x41044100UL)
#define SDRAM_QOS_LCD_WRITE (*(volatile uint32_t *) 0x41044104UL)
#define SDRAM_QOS_M7_READ (*(volatile uint32_t *) 0x41442100UL)
#define SDRAM_QOS_M7_WRITE (*(volatile uint32_t *) 0x41442104UL)
/* ── Состояние модуля ──────────────────────────────────────────────────── */
static bool s_initialised = false;
static bool g_s_initialised = false;
/* ── Внутренние функции ────────────────────────────────────────────────── */
/**
* @brief Поднять тактовую цепочку SEMC (порт секции «Clock Init» DCD).
*
*/
static void sdram_configure_clock(void)
{
/* DCD PLL_SYS=0x00002001 → Fout = 24 МГц × (20 + 2×loopDivider + num/denom)
* = 24 × (20 + 2 + 0) = 528 МГц. */
static const clock_sys_pll_config_t SYS_PLL = {
.loopDivider = 1, .numerator = 0, .denominator = 1, .src = 0, /* 24 МГц OSC */
};
/* Гейтим SEMC на время переключения его источника (идиома clock_config.c). */
CLOCK_DisableClock(kCLOCK_Semc);
CLOCK_InitSysPll(&SYS_PLL);
CLOCK_InitSysPfd(kCLOCK_Pfd2, SDRAM_SEMC_PFD2_FRAC);
/* DCD CBCDR=0x00010D40 → SEMC_CLK_SEL=1 (alt), SEMC_ALT_CLK_SEL=0 (→PFD2),
* SEMC_PODF=1 (÷2). */
CLOCK_SetMux(kCLOCK_SemcAltMux, 0); /* alt = PLL2 PFD2 */
CLOCK_SetMux(kCLOCK_SemcMux, 1); /* SEMC clock = alt (PFD2), не periph_clk */
CLOCK_SetDiv(kCLOCK_SemcDiv, kCLOCK_SemcDivBy2);
CLOCK_EnableClock(kCLOCK_Semc);
}
/* ── SEMC: пины ─────────────────────────────────────────────────── */
/**
* @brief Замуксить пины GPIO_EMC на функцию SEMC + PAD-настройки (порт DCD).
*
*/
static void sdram_configure_pins(void)
{
for (uint32_t i = SDRAM_EMC_PAD_FIRST; i <= SDRAM_EMC_PAD_LAST; i++)
{
IOMUXC->SW_MUX_CTL_PAD[i] = 0UL; /* ALT0 = функция SEMC */
IOMUXC->SW_PAD_CTL_PAD[i] = SDRAM_PAD_CTL_VALUE;
}
/* DQS: ALT0 + SION (DCD пишет сюда 0x10 вместо 0x00). */
IOMUXC->SW_MUX_CTL_PAD[SDRAM_DQS_PAD_INDEX] = SDRAM_MUX_SION;
}
/* ── SEMC: регистры контроллера ─────────────────────────────────── */
/**
* @brief Записать регистры контроллера SEMC (дословно «блок 2» DCD).
*
*/
static void sdram_configure_controller(void)
{
SEMC->MCR = 0x10000004UL; /* модуль вкл (MDIS=0), DQSMD=1 (DQS с пина), BTO=16 */
SEMC->BMCR0 = 0x00000081UL; /* веса AXI-очереди A */
SEMC->BMCR1 = 0x00000081UL; /* веса AXI-очереди B */
/* Базовые регистры регионов SEMC. BR0 — сама SDRAM (0x80000000, 32 МБ,
* VLD=1). BR1..BR8 прочие регионы из проверенного DCD, переносятся как
* есть (bootloader их не использует, но конфиг источника истины не режем). */
SEMC->BR[0] = 0x8000001BUL;
SEMC->BR[1] = 0x8200001BUL;
SEMC->BR[2] = 0x8400001BUL;
SEMC->BR[3] = 0x8600001BUL;
SEMC->BR[4] = 0x90000021UL;
SEMC->BR[5] = 0xA0000019UL;
SEMC->BR[6] = 0xA8000017UL;
SEMC->BR[7] = 0xA900001BUL;
SEMC->BR[8] = 0x00000021UL;
SEMC->IOCR = 0x000079A8UL; /* внутренний pinmux SEMC */
/* Геометрия и тайминги SDRAM. SDRAMCR0: PS=16бит, BL=8, COL=9бит, CL=3 —
* MT48LC16M16A2 (даташит: 512 колонок = 9 адресных бит). */
SEMC->SDRAMCR0 = 0x00000F31UL;
SEMC->SDRAMCR1 = 0x00652922UL;
SEMC->SDRAMCR2 = 0x00020201UL;
SEMC->SDRAMCR3 = 0x08193D0FUL; /* тайминги refresh; REN включим в конце */
/* DBICR0/DBICR1 — DCD их пишет, хотя DBI-устройства на плате нет и
* SEMC_ConfigureSDRAM() их не трогает. Инертны (регион DBI не включён),
* но переносятся дословно ради полного соответствия проверенному DCD. */
SEMC->DBICR0 = 0x00000021UL;
SEMC->DBICR1 = 0x00888888UL;
/* Параметры IP-команд: DATSZ=2 байта (запись mode-register 16-бит шиной). */
SEMC->IPCR1 = 0x00000002UL;
SEMC->IPCR2 = 0x00000000UL;
}
/* ── SEMC: командная последовательность инициализации SDRAM ──────── */
/**
* @brief Прогнать init-последовательность SDRAM через IP-команды SEMC.
*
* precharge-all 2×auto-refresh mode-set включение авто-refresh.
*
* @retval BSP_OK все команды завершились успешно.
* @retval BSP_ERR_INIT IP-команда SEMC вернула ошибку.
*/
static bsp_status_t sdram_issue_init_sequence(void)
{
const uint32_t ADDR = SDRAM_SEMC_IPCMD_ADDR;
if (SEMC_SendIPCommand(SEMC, kSEMC_MemType_SDRAM, ADDR, (uint32_t) kSEMC_SDRAMCM_Prechargeall,
0, NULL) != kStatus_Success)
{
return BSP_ERR_INIT;
}
for (uint32_t i = 0U; i < 2U; i++)
{
if (SEMC_SendIPCommand(SEMC, kSEMC_MemType_SDRAM, ADDR,
(uint32_t) kSEMC_SDRAMCM_AutoRefresh, 0, NULL) != kStatus_Success)
{
return BSP_ERR_INIT;
}
}
if (SEMC_SendIPCommand(SEMC, kSEMC_MemType_SDRAM, ADDR, (uint32_t) kSEMC_SDRAMCM_Modeset,
SDRAM_SEMC_MODE_REG, NULL) != kStatus_Success)
{
return BSP_ERR_INIT;
}
/* Включить авто-refresh + перейти на рабочие параметры refresh. DCD пишет
* SDRAMCR3 повторно другим значением (не только бит REN) переносим как
* есть; это финальный «operational» refresh-конфиг после инициализации. */
SEMC->SDRAMCR3 = 0x50210A09UL;
return BSP_OK;
}
/* ── SEMC: AXI-QoS арбитраж ─ */
/**
* @brief Настроить приоритеты доступа мастеров к SDRAM
*
* Требуют board_mpu_init() Region 11
*/
static void sdram_configure_axi_qos(void)
{
SDRAM_QOS_LCD_READ = 6UL;
SDRAM_QOS_LCD_WRITE = 6UL;
SDRAM_QOS_M7_READ = 7UL;
SDRAM_QOS_M7_WRITE = 7UL;
}
/* ── Внутренние функции верификации ────────────────────────────────────── */
/**
* @brief Дождаться перехода SEMC в состояние IDLE.
@ -72,10 +269,10 @@ static bsp_status_t wait_semc_idle(void)
*/
static void flush_cache_at_test_base(void)
{
uint32_t *const p_addr = (uint32_t *) BSP_SDRAM_TEST_BASE_ADDR;
uint32_t *const P_ADDR = (uint32_t *) BSP_SDRAM_TEST_BASE_ADDR;
SCB_CleanDCache_by_Addr(p_addr, (int32_t) SDRAM_CACHE_LINE_BYTES);
SCB_InvalidateDCache_by_Addr(p_addr, (int32_t) SDRAM_CACHE_LINE_BYTES);
SCB_CleanDCache_by_Addr(P_ADDR, (int32_t) SDRAM_CACHE_LINE_BYTES);
SCB_InvalidateDCache_by_Addr(P_ADDR, (int32_t) SDRAM_CACHE_LINE_BYTES);
__DSB();
}
@ -87,16 +284,33 @@ static void flush_cache_at_test_base(void)
*/
static bsp_status_t verify_word(uint32_t pattern)
{
volatile uint32_t *const p_test = (volatile uint32_t *) BSP_SDRAM_TEST_BASE_ADDR;
volatile uint32_t *const P_TEST = (volatile uint32_t *) BSP_SDRAM_TEST_BASE_ADDR;
*p_test = pattern;
*P_TEST = pattern;
flush_cache_at_test_base();
return (*p_test == pattern) ? BSP_OK : BSP_ERR_INIT;
return (*P_TEST == pattern) ? BSP_OK : BSP_ERR_INIT;
}
/* ── Public API ────────────────────────────────────────────────────────── */
bsp_status_t bsp_sdram_configure(void)
{
sdram_configure_clock();
sdram_configure_pins();
sdram_configure_controller();
bsp_status_t status = sdram_issue_init_sequence();
if (status != BSP_OK)
{
return status;
}
#if defined(__NIC301_EXPERIMENTS_)
sdram_configure_axi_qos();
#endif
return BSP_OK;
}
bsp_status_t bsp_sdram_init(void)
{
bsp_status_t status = wait_semc_idle();
@ -117,6 +331,6 @@ bsp_status_t bsp_sdram_init(void)
return BSP_ERR_INIT;
}
s_initialised = true;
g_s_initialised = true;
return BSP_OK;
}

View file

@ -228,6 +228,9 @@ recovery-индикация это показывают), лечится SD-фи
### Светодиоды
Полный словарь (установка, «железо не в порядке», ошибка образа, приоритет между ними) —
[LED_PATTERNS.md](LED_PATTERNS.md). Кратко:
| Состояние | Паттерн |
| ---------------------- | ------------------------------------------------ |
| Загрузчик жив, ждёт SD | Один LED: короткий импульс ~50 мс, пауза ~450 мс |

View file

@ -0,0 +1,11 @@
# LED-индикация bootloader
Два светодиода: **HEARTBEAT** (системный) и **APP** (прикладной).
| Индикация | Значение |
| ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| HEARTBEAT мигает: 50 мс горит / 450 мс не горит. APP не горит вообще. | Загрузчик работает, ждёт карту microSD |
| HEARTBEAT мигает: 50 мс горит / 450 мс не горит. APP мигает: 250 мс горит / 250 мс не горит. | Идёт установка образа с SD-карты — питание не выключать |
| HEARTBEAT мигает: 50 мс горит / 450 мс не горит. APP часто мигает без остановки: 100 мс горит / 100 мс не горит. | Неисправность платы (QSPI flash или SDRAM) — плата не годна, отложить |
| APP быстро мигает 4 раза подряд (80 мс горит / 80 мс не горит), затем возвращается к тому виду, что был до этого. | Образ на SD-карте отклонён (битый файл или неверная подпись) — заменить файл на карте |
| Оба светодиода горят и гаснут одновременно: 100 мс горят / 100 мс не горят. | Режим восстановления (Recovery Mode) |

View file

@ -30,6 +30,7 @@ add_executable(
src/slot_version.c
src/sd_update.c
src/recovery.c
src/led_status.c
mcuboot_port/flash_map_backend.c
mcuboot_port/keys.c
${MCUBOOT_BOOTUTIL_SOURCES}
@ -49,12 +50,26 @@ target_compile_definitions(
# -----------------------------------------------------------------------------
# Зависимости. bsp_button — downgrade-override (удержание BSP_BUTTON_1).
# bootloader_fatfs — чтение TFT_APP.BIN с SD (Фаза 3, firmware/bootloader/fatfs/).
# bsp_sdram — smoke-test SDRAM/SEMC (Фаза 4, bsp_sdram_configure()+_init()) —
# bootloader без DCD, сам поднимает SEMC на время диагностики.
# -----------------------------------------------------------------------------
target_link_libraries(${TARGET_NAME} PRIVATE bsp_board bsp_led bsp_tick
bsp_usb_cdc bsp_qspi_flash
bsp_boot_xip_no_dcd bsp_button
bsp_wdog bsp_boot_state
bootloader_fatfs)
bsp_sdram bootloader_fatfs)
# -----------------------------------------------------------------------------
# [DEV-ONLY] Диагностика Фазы 4 — CLI-команда "sdram_test" (dev_sdram_test.c):
# 4-фазный тест SDRAM (адресная шина/шина данных/sequential/retention),
# портирован из firmware_test/src/tests/test_sdram.c, прогоняется через
# bsp_sdram_configure() вместо DCD. НЕ для production: только Debug —
# Release/HAB-бинарь этот файл не содержит и о команде не знает.
# -----------------------------------------------------------------------------
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_sources(${TARGET_NAME} PRIVATE src/dev_sdram_test.c)
target_compile_definitions(${TARGET_NAME} PRIVATE BOOTLOADER_DEV_DIAGNOSTICS)
endif()
# -----------------------------------------------------------------------------
# Linker script — вариант flexspi_nor с m_text, ограниченным бюджетом

View file

@ -6,7 +6,7 @@
USB CDC ACM (JSON-строки).
**Принцип работы** (карта памяти, выбор образа, обновление, версии, даунгрейд, recovery) —
[BOOT_FLOW.md](BOOT_FLOW.md).
[BOOT_FLOW.md](../../docs/bootloader/BOOT_FLOW.md). LED-индикация — [LED_PATTERNS.md](../../docs/bootloader/LED_PATTERNS.md).
---

View file

@ -2,28 +2,10 @@
#
# Общий список файлов bootutil (MCUboot) + TinyCrypt + ASN.1-парсер,
# используемых и реальным ARM-таргетом (firmware/bootloader), и host-тестами
# (tests/host/mcuboot_port/) чтобы не дублировать ~13 путей в двух местах.
#
# Состав определён вручную (не через .cmake файлы NXP-порта см.
# firmware/bootloader/PLAN.md, Фаза 2) под конкретный режим:
# MCUBOOT_DIRECT_XIP + MCUBOOT_DIRECT_XIP_REVERT, MCUBOOT_IMAGE_NUMBER=1,
# MCUBOOT_SIGN_EC256 + MCUBOOT_USE_TINYCRYPT, без encryption, без
# measured-boot/shared-data (поэтому НЕ включены: boot_record.c, caps.c,
# encrypted.c, image_rsa.c, image_ed25519.c, swap_move.c, swap_misc.c,
# fault_injection_hardening_delay_rng_mbedtls.c последний нужен только
# профилю MCUBOOT_FIH_PROFILE_HIGH, мы используем LOW).
#
# swap_scratch.c ВКЛЮЧЁН, несмотря на название: boot_read_image_header()
# внутри него обёрнут в `#if !defined(MCUBOOT_SWAP_USING_MOVE)` то есть
# это и есть дефолтная (не swap-move) реализация чтения заголовка слота,
# которую loader.c вызывает безусловно для ЛЮБОГО режима, включая
# Direct-XIP. swap_move.c (со своей версией той же функции под
# MCUBOOT_SWAP_USING_MOVE) не нужен. Остальные функции swap_scratch.c
# (собственно swap-логика) не достижимы в Direct-XIP и вырезаются линкером
# ARM-таргета через --gc-sections; для host-тестов мёртвый, но безобидный
# код.
# (tests/host/mcuboot_port/)
set(MCUBOOT_OPENSOURCE_DIR ${CMAKE_SOURCE_DIR}/sdk/middleware/mcuboot_opensource)
set(MCUBOOT_OPENSOURCE_DIR
${CMAKE_SOURCE_DIR}/sdk/middleware/mcuboot_opensource)
set(MCUBOOT_BOOTUTIL_DIR ${MCUBOOT_OPENSOURCE_DIR}/boot/bootutil)
set(MCUBOOT_EXT_DIR ${MCUBOOT_OPENSOURCE_DIR}/ext)
@ -48,26 +30,14 @@ set(MCUBOOT_BOOTUTIL_INCLUDES
${MCUBOOT_BOOTUTIL_DIR}/src
${MCUBOOT_EXT_DIR}/tinycrypt/lib/include
${MCUBOOT_EXT_DIR}/mbedtls-asn1/include
${CMAKE_CURRENT_LIST_DIR} # sysflash.h, mcuboot_config.h, flash_map.h, flash_map_backend.h
${CMAKE_CURRENT_LIST_DIR} # sysflash.h, mcuboot_config.h, flash_map.h,
# flash_map_backend.h
)
# ------------------------------------------------------------------------
# Опции компиляции только для вендоренных исходников (не для нашего кода
# main.c/cli.c/boot_select.c и т.д. компилируются с обычными warnings/ASan
# консьюмера):
# -w вендоренный код, не наш стиль/lint
# -fno-sanitize=address,undefined обход бага clang 22.1.8 (Homebrew):
# -fsanitize=address,undefined ломает генерацию CFI-директив на
# некоторых больших функциях bootutil (напр. loader.c::context_boot_go)
# "invalid CFI advance_loc expression" на этапе ассемблирования. Без
# санитайзеров те же файлы собираются чисто похоже на баг конкретной
# версии тулчейна. Не применимо к arm-none-eabi-gcc (ARM-таргет не
# использует ASan) гейтим по Clang.
# ------------------------------------------------------------------------
set(MCUBOOT_VENDORED_COMPILE_OPTIONS -w)
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
list(APPEND MCUBOOT_VENDORED_COMPILE_OPTIONS -fno-sanitize=address,undefined)
endif()
set_source_files_properties(${MCUBOOT_BOOTUTIL_SOURCES}
PROPERTIES COMPILE_OPTIONS
set_source_files_properties(
${MCUBOOT_BOOTUTIL_SOURCES} PROPERTIES COMPILE_OPTIONS
"${MCUBOOT_VENDORED_COMPILE_OPTIONS}")

View file

@ -4,10 +4,6 @@
* flash_map_backend.c (реальный, над bsp_qspi_flash) или
* fake_flash_map_backend.c (host-тесты, in-memory буфер).
*
* Урезано под наш случай относительно оригинального Apache-2.0 заголовка
* MCUboot (sdk/middleware/mcuboot_opensource/boot/nxp_mcux_sdk/include/flash_map.h):
* без sblconfig.h, без encrypted_xip externs, без boot_image_names[] этого
* NXP-специфичного слоя у нас нет.
*/
#ifndef FLASH_MAP_H_

View file

@ -17,6 +17,7 @@
#include "bsp/qspi_flash.h"
#include "bsp/wdog.h"
#include "flash_map.h"
#include "led_status.h"
#include "sysflash/sysflash.h"
#include <string.h>
@ -152,6 +153,11 @@ int flash_area_erase(const struct flash_area *area, uint32_t off, uint32_t len)
* Зависание самого стирания флеша всё равно ловится: refresh по
* ЗАВЕРШЕНИИ блока, а не перед ним. */
bsp_wdog_refresh();
/* Прогресс-хук индикации — no-op вне окна установки (см.
* led_status.h). Нужен, чтобы APP не замирал на ~5 c стирания слота
* при установке; на revert/recovery-стирании (тоже зовут эту
* функцию) ничего не рисует. */
led_status_tick_install();
if (bsp_qspi_erase_block_64k(block_addr) != BSP_OK)
{
return -1;

View file

@ -11,6 +11,17 @@
* Входящие типы (Фаза 1):
* "cmd" handle_cmd() protocol_send_pong() / protocol_send_version_response()
*
* [Фаза 4] Команды "smoke_status"/"qspi_info" переспросить результат
* boot-time smoke-теста SDRAM/SEMC и опознанный чип QSPI flash в любой
* момент сессии (сами события шлются один раз рано, до открытия хостом
* порта, и почти наверняка теряются см. protocol.h).
*
* [DEV-ONLY, Фаза 4] Команда "sdram_test" под BOOTLOADER_DEV_DIAGNOSTICS
* (только Debug, см. CMakeLists.txt): запускает dev_sdram_test_run(), которая
* блокирует главный цикл на ~4 с (пройденный прогон на железе: configure ~0,
* address_bus <1 мс, data_bus ~130 мс, sequential ~3.7 с, retention ~400 мс).
* Отсутствует в Release/HAB-бинаре.
*
* Добавление новой команды типа "cmd":
* 1. Добавить ветку if (strcmp(cmd_name, "FOO") == 0) в handle_cmd().
*/
@ -20,6 +31,10 @@
#include "bsp/usb_cdc.h"
#include "protocol.h"
#ifdef BOOTLOADER_DEV_DIAGNOSTICS
#include "dev_sdram_test.h"
#endif
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
@ -141,6 +156,26 @@ static void handle_cmd(const char *p_line)
return;
}
if (strcmp(cmd_name, "smoke_status") == 0)
{
protocol_send_smoke_status();
return;
}
if (strcmp(cmd_name, "qspi_info") == 0)
{
protocol_send_qspi_info();
return;
}
#ifdef BOOTLOADER_DEV_DIAGNOSTICS
if (strcmp(cmd_name, "sdram_test") == 0)
{
dev_sdram_test_run();
return;
}
#endif
protocol_send_error("UNKNOWN_CMD");
}

View file

@ -0,0 +1,282 @@
/**
* @file dev_sdram_test.c
* @brief [DEV-ONLY] Реализация глубокого теста SDRAM (см. dev_sdram_test.h).
*
* Фазы 1:1 портированы из firmware/test/src/tests/test_sdram.c (тот же
* алгоритм, то же покрытие) не переизобретаются, чтобы результат был
* сопоставим с уже доверенным тестом DCD-пути. Отличия от оригинала:
* - sdram_test_init() там предполагал DCD; здесь сама зовёт
* bsp_sdram_configure() это и есть предмет проверки.
* - test_module_t/test_result_t (инфраструктура тест-раннера firmware_test)
* не используются bootloader её не имеет; результат каждой фазы уходит
* отдельным CDC-событием по ходу прогона, не одним отчётом в конце.
* - добавлено кормление watchdog (bsp_wdog_refresh()) на той же частоте,
* что и опрос CDC без него более медленный прогон рисковал бы не
* пережить таймаут WDOG (10 с, main.c) и словить сброс посреди фазы
* sequential (по факту прогон ~4 с, запас большой см. dev_sdram_test.h).
*/
#include "dev_sdram_test.h"
#include "bsp/sdram.h"
#include "bsp/tick.h"
#include "bsp/usb_cdc.h"
#include "bsp/wdog.h"
#include "fsl_common.h"
#include "protocol.h"
#include <stdbool.h>
#include <stdint.h>
/* ── Константы (те же значения, что в firmware_test/test_sdram.c) ───────── */
/** @brief Интервал вызова pump() при записи/чтении, байт. */
#define SDRAM_USB_POLL_INTERVAL_BYTES 0x1000U
/** @brief Ширина walking ones паттерна, бит. */
#define SDRAM_WALKING_ONES_BITS 8U
/** @brief Количество адресных бит теста address_bus (13 row+9 col+2 bank). */
#define SDRAM_ADDR_BUS_BITS 24U
/** @brief Размер кэш-линии Cortex-M7, байт. */
#define SDRAM_CACHE_LINE_BYTES 32U
/** @brief Размер фазы sequential, байт (2 MB). */
#define SDRAM_SEQUENTIAL_SIZE 0x00200000UL
/** @brief Размер фазы retention, байт (256 KB). */
#define SDRAM_RETENTION_SIZE 0x00040000UL
/** @brief Задержка фазы retention, мс (MT48LC16M16A2: авто-refresh 64 мс, ≈3 периода). */
#define SDRAM_RETENTION_DELAY_MS 200U
/** @brief Шаг ожидания в фазе retention (для pump()), мс. */
#define SDRAM_RETENTION_POLL_STEP_MS 10U
/* ── Локальные типы ────────────────────────────────────────────────────── */
/** @brief Информация о первой ошибке паттерн-прохода. fail_addr==0 — нет ошибки. */
typedef struct
{
uint32_t fail_addr;
uint8_t expected;
uint8_t got;
} sdram_fail_info_t;
typedef uint8_t (*pattern_fn_t)(uint32_t offset);
/* ── Паттерн-функции ───────────────────────────────────────────────────── */
static uint8_t pattern_walking_ones(uint32_t offset)
{
return (uint8_t) (1U << (offset % SDRAM_WALKING_ONES_BITS));
}
static uint8_t pattern_walking_ones_inv(uint32_t offset)
{
return (uint8_t) (~(1U << (offset % SDRAM_WALKING_ONES_BITS)) & 0xFFU);
}
static uint8_t pattern_addr(uint32_t offset)
{
return (uint8_t) (offset & 0xFFU);
}
static uint8_t pattern_addr_inv(uint32_t offset)
{
return (uint8_t) (~offset & 0xFFU);
}
/* ── Вспомогательные функции ───────────────────────────────────────────── */
/** @brief Опрос CDC + кормление watchdog. Вызывать не реже раза в ~4 KB. */
static void pump(void)
{
bsp_usb_cdc_poll();
bsp_wdog_refresh();
}
static void flush_dcache(uint32_t size)
{
uint32_t *const P_BASE = (uint32_t *) BSP_SDRAM_TEST_BASE_ADDR;
SCB_CleanDCache_by_Addr(P_BASE, (int32_t) size);
SCB_InvalidateDCache_by_Addr(P_BASE, (int32_t) size);
__DSB();
}
/** @brief Сброс одной кэш-линии по произвольному байтовому адресу (для address_bus). */
static void flush_dcache_line_at(uint32_t byte_addr)
{
uint32_t *const P_LINE = (uint32_t *) (byte_addr & ~(SDRAM_CACHE_LINE_BYTES - 1U));
SCB_CleanDCache_by_Addr(P_LINE, (int32_t) SDRAM_CACHE_LINE_BYTES);
SCB_InvalidateDCache_by_Addr(P_LINE, (int32_t) SDRAM_CACHE_LINE_BYTES);
__DSB();
}
static void delay_with_pump(uint32_t ms)
{
const uint32_t START_MS = bsp_tick_get_ms();
while ((bsp_tick_get_ms() - START_MS) < ms)
{
bsp_delay(SDRAM_RETENTION_POLL_STEP_MS);
pump();
}
}
static void write_pattern(uint32_t size, pattern_fn_t p_fn)
{
volatile uint8_t *const P_BASE = (volatile uint8_t *) BSP_SDRAM_TEST_BASE_ADDR;
for (uint32_t i = 0U; i < size; i++)
{
if ((i & (SDRAM_USB_POLL_INTERVAL_BYTES - 1U)) == 0U)
{
pump();
}
P_BASE[i] = p_fn(i);
}
}
/** @brief Верифицировать паттерн. Останавливается на первом несовпадении. */
static bool verify_pattern(uint32_t size, pattern_fn_t p_fn, sdram_fail_info_t *p_fail)
{
volatile const uint8_t *const P_BASE = (volatile const uint8_t *) BSP_SDRAM_TEST_BASE_ADDR;
for (uint32_t i = 0U; i < size; i++)
{
if ((i & (SDRAM_USB_POLL_INTERVAL_BYTES - 1U)) == 0U)
{
pump();
}
const uint8_t EXPECTED = p_fn(i);
const uint8_t GOT = P_BASE[i];
if (GOT != EXPECTED)
{
p_fail->fail_addr = BSP_SDRAM_TEST_BASE_ADDR + i;
p_fail->expected = EXPECTED;
p_fail->got = GOT;
return false;
}
}
return true;
}
/** @brief Один паттерн-проход: запись → flush → верификация. */
static bool run_pass(uint32_t size, pattern_fn_t p_fn, sdram_fail_info_t *p_fail)
{
write_pattern(size, p_fn);
flush_dcache(size);
return verify_pattern(size, p_fn, p_fail);
}
/* ── Фазы теста (см. dev_sdram_test.h) ───────────────────────────────────── */
/** @brief Фаза address_bus: 24 адресных бита, точечный флаш на каждую запись. */
static bool run_phase_address_bus(sdram_fail_info_t *p_fail)
{
volatile uint8_t *const P_BASE = (volatile uint8_t *) BSP_SDRAM_TEST_BASE_ADDR;
for (uint32_t bit = 0U; bit < SDRAM_ADDR_BUS_BITS; bit++)
{
const uint32_t OFFSET = (1UL << bit);
const uint8_t PATTERN = (uint8_t) (bit + 1U);
P_BASE[OFFSET] = PATTERN;
flush_dcache_line_at(BSP_SDRAM_TEST_BASE_ADDR + OFFSET);
const uint8_t GOT = P_BASE[OFFSET];
if (GOT != PATTERN)
{
p_fail->fail_addr = BSP_SDRAM_TEST_BASE_ADDR + OFFSET;
p_fail->expected = PATTERN;
p_fail->got = GOT;
return false;
}
}
return true;
}
/** @brief Фаза data_bus: walking ones + инверсия, 64 KB. */
static bool run_phase_data_bus(sdram_fail_info_t *p_fail)
{
return run_pass(BSP_SDRAM_TEST_FAST_SIZE, pattern_walking_ones, p_fail) &&
run_pass(BSP_SDRAM_TEST_FAST_SIZE, pattern_walking_ones_inv, p_fail);
}
/** @brief Фаза sequential: address pattern + инверсия, 2 MB. */
static bool run_phase_sequential(sdram_fail_info_t *p_fail)
{
return run_pass(SDRAM_SEQUENTIAL_SIZE, pattern_addr, p_fail) &&
run_pass(SDRAM_SEQUENTIAL_SIZE, pattern_addr_inv, p_fail);
}
/** @brief Фаза retention: запись → flush → 200 мс → verify, 256 KB. */
static bool run_phase_retention(sdram_fail_info_t *p_fail)
{
write_pattern(SDRAM_RETENTION_SIZE, pattern_addr);
flush_dcache(SDRAM_RETENTION_SIZE);
delay_with_pump(SDRAM_RETENTION_DELAY_MS);
return verify_pattern(SDRAM_RETENTION_SIZE, pattern_addr, p_fail);
}
/* ── Оркестрация ──────────────────────────────────────────────────────── */
static void report_phase(const char *p_name, bool pass, uint32_t start_ms, const sdram_fail_info_t *p_fail)
{
const uint32_t DURATION_MS = bsp_tick_get_ms() - start_ms;
const uint32_t FAIL_ADDR = (p_fail != NULL) ? p_fail->fail_addr : 0U;
const uint8_t EXPECTED = (p_fail != NULL) ? p_fail->expected : 0U;
const uint8_t GOT = (p_fail != NULL) ? p_fail->got : 0U;
protocol_send_sdram_test_phase(p_name, pass, DURATION_MS, FAIL_ADDR, EXPECTED, GOT);
}
void dev_sdram_test_run(void)
{
const uint32_t CONFIGURE_START_MS = bsp_tick_get_ms();
const bool CONFIGURED = (bsp_sdram_configure() == BSP_OK) && (bsp_sdram_init() == BSP_OK);
report_phase("configure", CONFIGURED, CONFIGURE_START_MS, NULL);
if (!CONFIGURED)
{
return;
}
const uint32_t OVERALL_START_MS = bsp_tick_get_ms();
sdram_fail_info_t fail = { 0U, 0U, 0U };
bool is_ok;
uint32_t phase_start_ms;
phase_start_ms = bsp_tick_get_ms();
is_ok = run_phase_address_bus(&fail);
report_phase("address_bus", is_ok, phase_start_ms, is_ok ? NULL : &fail);
if (is_ok)
{
phase_start_ms = bsp_tick_get_ms();
is_ok = run_phase_data_bus(&fail);
report_phase("data_bus", is_ok, phase_start_ms, is_ok ? NULL : &fail);
}
if (is_ok)
{
phase_start_ms = bsp_tick_get_ms();
is_ok = run_phase_sequential(&fail);
report_phase("sequential", is_ok, phase_start_ms, is_ok ? NULL : &fail);
}
if (is_ok)
{
phase_start_ms = bsp_tick_get_ms();
is_ok = run_phase_retention(&fail);
report_phase("retention", is_ok, phase_start_ms, is_ok ? NULL : &fail);
}
report_phase("summary", is_ok, OVERALL_START_MS, NULL);
}

View file

@ -0,0 +1,45 @@
/**
* @file dev_sdram_test.h
* @brief [DEV-ONLY] Глубокий тест SDRAM через bsp_sdram_configure() (Фаза 4).
*
* НЕ для production компилируется только в Debug
* (BOOTLOADER_DEV_DIAGNOSTICS, см. CMakeLists.txt); отсутствует в HAB
* Release-бинаре.
*
* Цель не заменить лёгкий boot-time smoke-test (main.c, "smoke_pass"/
* "smoke_fail"), а дать ту же глубину проверки, что уже доверена
* firmware_test/src/tests/test_sdram.c (4 фазы, покрывают адресную шину,
* шину данных, coupling между ячейками и refresh-timing), но против нового
* C-порта DCD (bsp_sdram_configure()), а не против DCD напрямую чтобы
* убедиться, что порт поднимает память так же надёжно, как проверенный
* годами в производстве DCD.
*
* Вызывается по CDC-команде {"type":"cmd","cmd":"sdram_test"} (см. cli.c).
* Блокирует главный цикл на время прогона (~4 с, см. таймингы ниже реально
* измерено на железе, а не оценка) сама кормит watchdog и опрашивает CDC по
* ходу, вызывающему коду ничего дополнительно делать не нужно. Каждая фаза и
* итог репортятся отдельным событием по CDC синхронно по ходу прогона (см.
* protocol_send_sdram_test_phase()).
*/
#ifndef DEV_SDRAM_TEST_H_
#define DEV_SDRAM_TEST_H_
/**
* @brief Поднять SEMC (bsp_sdram_configure()) и прогнать 4-фазный тест SDRAM.
*
* Фазы (портированы из firmware/test/src/tests/test_sdram.c без изменений
* логики меняется только то, кто поднимает SEMC до них). Тайминги
* реальный прогон на железе (не оценка из test_sdram.c, та оказалась
* консервативнее раз в 6 запись садится в D-Cache почти мгновенно, реальная
* задержка SDRAM только на flush_dcache() и на чтение при верификации):
* 1. address_bus 24 адресных бита (13 row + 9 col + 2 bank), <1 мс.
* 2. data_bus walking ones + инверсия, 64 KB, ~130 мс.
* 3. sequential address pattern + инверсия, 2 MB, ~3.7 с.
* 4. retention запись flush 200 мс verify, 256 KB, ~400 мс.
*
* Если bsp_sdram_configure()/bsp_sdram_init() не прошли репортится фаза
* "configure" с pass=false, дальнейшие фазы не запускаются.
*/
void dev_sdram_test_run(void);
#endif /* DEV_SDRAM_TEST_H_ */

View file

@ -0,0 +1,113 @@
/**
* @file led_status.c
* @brief Реализация словаря LED-паттернов см. led_status.h и
* docs/bootloader/LED_PATTERNS.md.
*/
#include "led_status.h"
#include "bsp/led.h"
#include "bsp/tick.h"
#include "bsp/wdog.h"
#include <stdbool.h>
#include <stdint.h>
/* ── Периоды паттернов, мс (СИНХРОНИЗИРОВАТЬ с LED_PATTERNS.md) ─────────── */
/** @brief Heartbeat «жив»: 50 мс горит / 450 мс не горит (период 500). */
#define LED_HEARTBEAT_ON_MS 50U
#define LED_HEARTBEAT_PERIOD_MS 500U
/** @brief Неисправность железа: APP 100/100 (период 200). */
#define LED_HW_FAULT_ON_MS 100U
#define LED_HW_FAULT_PERIOD_MS 200U
/** @brief Recovery: оба LED синхронно 100/100 (период 200). */
#define LED_RECOVERY_ON_MS 100U
#define LED_RECOVERY_PERIOD_MS 200U
/** @brief Установка: APP 250/250 (период 500). */
#define LED_INSTALL_ON_MS 250U
#define LED_INSTALL_PERIOD_MS 500U
/** @brief «Образ отклонён»: 4 вспышки по 80 мс вкл / 80 мс выкл. */
#define LED_REJECT_ON_MS 80U
#define LED_REJECT_COUNT 4U
/* ── Состояние модуля ──────────────────────────────────────────────────── */
/** @brief Окно установки открыто — см. led_status_tick_install(). */
static bool g_s_installing = false;
/* ── Внутренние помощники ──────────────────────────────────────────────── */
/** @brief true, если по текущему тику LED в фазе «горит» для период/on. */
static bool phase_on(uint32_t period_ms, uint32_t on_ms)
{
return (bsp_tick_get_ms() % period_ms) < on_ms;
}
/** @brief Отрисовать системный heartbeat (общий для waiting/hw_fault/install). */
static void draw_heartbeat(void)
{
bsp_led_set(LED_HEARTBEAT, phase_on(LED_HEARTBEAT_PERIOD_MS, LED_HEARTBEAT_ON_MS));
}
/* ── Public API ────────────────────────────────────────────────────────── */
void led_status_draw_background(led_bg_t bg)
{
switch (bg)
{
case LED_BG_RECOVERY:
{
/* Оба LED — один и тот же фазовый расчёт → строго синхронно. */
const bool ON = phase_on(LED_RECOVERY_PERIOD_MS, LED_RECOVERY_ON_MS);
bsp_led_set(LED_HEARTBEAT, ON);
bsp_led_set(LED_APP, ON);
break;
}
case LED_BG_HW_FAULT:
draw_heartbeat(); /* heartbeat в своём ритме 50/450 — не синхронен с APP */
bsp_led_set(LED_APP, phase_on(LED_HW_FAULT_PERIOD_MS, LED_HW_FAULT_ON_MS));
break;
case LED_BG_WAITING:
default:
draw_heartbeat();
bsp_led_off(LED_APP);
break;
}
}
void led_status_install_begin(void)
{
g_s_installing = true;
}
void led_status_install_end(void)
{
g_s_installing = false;
}
void led_status_tick_install(void)
{
if (!g_s_installing)
{
return; /* не установка (revert/recovery-стирание) — ничего не трогаем */
}
draw_heartbeat();
bsp_led_set(LED_APP, phase_on(LED_INSTALL_PERIOD_MS, LED_INSTALL_ON_MS));
}
void led_status_flash_image_rejected(void)
{
for (uint32_t i = 0U; i < LED_REJECT_COUNT; i++)
{
bsp_wdog_refresh(); /* ~640 мс блокирующей вспышки — держим watchdog сытым */
bsp_led_on(LED_APP);
bsp_delay(LED_REJECT_ON_MS);
bsp_led_off(LED_APP);
bsp_delay(LED_REJECT_ON_MS);
}
}

View file

@ -0,0 +1,63 @@
/**
* @file led_status.h
* @brief Индикация bootloader на двух LED единый словарь паттернов.
*
* Полное человекочитаемое описание (для сервисных инженеров)
* docs/bootloader/LED_PATTERNS.md. Здесь программный контракт; периоды
* в мс заданы в led_status.c и обязаны совпадать с тем документом.
*
* Разделение на два вида вызова не случайно:
* - Фоновые (устойчивые) состояния рисует главный цикл main.c каждую
* итерацию: led_status_draw_background().
* - Паттерн «идёт установка» рисуется ИЗНУТРИ блокирующей установки (главный
* цикл в это время не исполняется): led_status_tick_install(), обёрнутый в
* окно led_status_install_begin()/_end(). Вне окна tick no-op, поэтому
* его безопасно звать из flash_area_erase(), который дёргается и на
* revert/recovery-стирании, а не только на установке.
*/
#ifndef LED_STATUS_H_
#define LED_STATUS_H_
/** @brief Фоновое (устойчивое) состояние индикации. */
typedef enum
{
LED_BG_WAITING, /**< Норма: HEARTBEAT 50/450, APP выкл — ждём microSD. */
LED_BG_HW_FAULT, /**< Неисправность: HEARTBEAT 50/450, APP 100/100. */
LED_BG_RECOVERY, /**< Recovery (Фаза 6): оба LED синхронно 100/100. */
} led_bg_t;
/**
* @brief Нарисовать фоновый паттерн по текущему тику.
*
* Вызывать каждую итерацию главного цикла. Приоритет разрешается на стороне
* вызывателя (recovery > неисправность > норма) сюда приходит уже
* выбранное состояние.
*/
void led_status_draw_background(led_bg_t bg);
/** @brief Открыть окно «идёт установка» — с этого момента tick рисует паттерн. */
void led_status_install_begin(void);
/** @brief Закрыть окно «идёт установка» — tick снова становится no-op. */
void led_status_install_end(void);
/**
* @brief Обновить APP/HEARTBEAT под паттерн установки (HEARTBEAT 50/450,
* APP 250/250). No-op вне окна install.
*
* Звать из ВСЕХ блокирующих циклов установки и поблочного стирания слота
* (flash_area_erase, ~5 c на 2 МБ), и копирования чанков (sd_update), иначе
* APP замирал бы на время стирания.
*/
void led_status_tick_install(void);
/**
* @brief Разовая индикация «образ с SD отклонён»: APP мигает 4×(80/80).
*
* Блокирующая (~640 мс), кормит watchdog по ходу. Оставляет APP выключенным
* фоновый паттерн восстановит главный цикл на следующей итерации.
*/
void led_status_flash_image_rejected(void);
#endif /* LED_STATUS_H_ */

View file

@ -32,10 +32,14 @@
* 6. bsp_button_init() для проверки удержания BSP_BUTTON_1/2
* 7. bsp_qspi_init() доступ к Slot A/Б
* 8. bsp_usb_cdc_init() не блокирует, см. выше
* 9. attempt_boot() SD-скан + recovery-гейт + прыжок; при
* 9. qspi_info (Фаза 4) идентификация чипа QSPI (JEDEC имя +
* ёмкость), не зависит от qspi_ok, только CDC
* 10. bsp_sdram_configure()+ smoke-test SDRAM/SEMC (Фаза 4): диагностика,
* bsp_sdram_init() не блокирует, результат только на CDC
* 11. attempt_boot() SD-скан + recovery-гейт + прыжок; при
* успехе не возвращается
* 10. Цикл ожидания CDC ping/pong + LED + периодический
* пере-скан SD (шаг 9 повторно)
* 12. Цикл ожидания CDC ping/pong + LED + периодический
* пере-скан SD (шаг 11 повторно)
*
* Watchdog (bsp_wdog): единственная защита от бесконечных зависаний в
* блокирующих вызовах SDMMC-стека, не возвращающих управление в наш код
@ -46,8 +50,10 @@
* копирования, перед прыжком) НЕ перед f_mount/SD_Init, иначе watchdog
* перестаёт защищать именно от них.
*
* Bootloader без SDRAM (см. docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md) DCD
* не используется (bsp_boot_xip_no_dcd).
* Bootloader не зависит от SDRAM (см. docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md)
* DCD не используется (bsp_boot_xip_no_dcd), XIP только из W25Q. SEMC/SDRAM
* трогаются только диагностически, шагом 9 (bsp_sdram_configure(), см.
* bsp/sdram/README.md) bootloader сам эту память ни для чего не использует.
*/
#include "board.h"
#include "boot_select.h"
@ -55,11 +61,13 @@
#include "bsp/button.h"
#include "bsp/led.h"
#include "bsp/qspi_flash.h"
#include "bsp/sdram.h"
#include "bsp/tick.h"
#include "bsp/usb_cdc.h"
#include "bsp/wdog.h"
#include "cli.h"
#include "flash_map.h"
#include "led_status.h"
#include "protocol.h"
#include "recovery.h"
#include "sd_update.h"
@ -170,18 +178,16 @@ int main(void)
{
const uint32_t ERROR_BLINK_MS = 250U;
const uint32_t SD_RETRY_PERIOD_MS = 1500U;
const uint32_t HEARTBEAT_ON_MS = 50U;
const uint32_t HEARTBEAT_PERIOD_MS = 500U;
/* Recovery-паттерн (Фаза 6b): оба LED синхронно, 100 мс вкл/100 мс выкл —
* чётко отличается от heartbeat (50/450, один LED) и app (500/250,
* один LED, см. test_stub) визуально, без дополнительной телеметрии. */
const uint32_t RECOVERY_BLINK_ON_MS = 100U;
const uint32_t RECOVERY_BLINK_PERIOD_MS = 200U;
/* Таймаут WDOG. С запасом над самым долгим НАКОРМЛЕННЫМ участком: между
* соседними refresh худший легитимный интервал одиночное стирание 64 КБ
* блока (~0.15..2 c по даташиту W25Q) либо цепочка bsp_sd_init+f_mount+пик
* слотов (~2-2.5 c). 10 c даёт кратный запас; зависание ловится 10 c. */
const uint32_t WDOG_TIMEOUT_S = 10U;
/* Минимум по docs/mimxrt1052/BOOTLOADER_FLASH_MAP.md §1 — карта
* (bootloader+Slot A+Slot Б+запас под ФС ассетов) рассчитана на W25Q128
* (16 МБ) и выше; W25Q64 драйвер технически поддерживает, но для этой
* платы это неверный BOM, а не "чуть меньше запас". */
const uint32_t QSPI_MIN_FLASH_SIZE_MB = 16U;
board_hw_init();
@ -211,6 +217,44 @@ int main(void)
bool qspi_ok = (bsp_qspi_init() == BSP_OK);
bool cdc_ok = (bsp_usb_cdc_init() == BSP_OK);
/* Единый признак «плата не годна» (LED_BG_HW_FAULT, см. LED_PATTERNS.md) —
* накапливается по обоим boot-time чекам ниже (QSPI + SDRAM smoke).
* Детали, что именно не так, всегда есть по CDC (qspi_info/smoke_status);
* LED показывает лишь факт неисправности. */
bool hw_fault = false;
/* Идентификация QSPI-чипа (Фаза 4) — не зависит от qspi_ok:
* bsp_qspi_read_jedec_id() отрабатывает и после проваленного
* bsp_qspi_init() (см. её @note), так что "чип не тот"/"чип не опознан"
* репортится с деталями, а не просто as "не сработало". */
{
bsp_qspi_jedec_t jedec = { 0U, 0U };
uint32_t qspi_size_mb = 0U;
const char *p_qspi_chip = "UNKNOWN";
uint8_t qspi_cap_byte = 0U;
if (bsp_qspi_read_jedec_id(&jedec) == BSP_OK)
{
qspi_cap_byte = (uint8_t) (jedec.device_id & 0xFFU);
p_qspi_chip = bsp_qspi_decode_chip(qspi_cap_byte, &qspi_size_mb);
}
const bool QSPI_PASS = (qspi_size_mb >= QSPI_MIN_FLASH_SIZE_MB);
hw_fault = (!qspi_ok) || (!QSPI_PASS); /* чип не отвечает / не тот / мал */
protocol_set_qspi_info(jedec.manufacturer_id, p_qspi_chip, qspi_cap_byte, qspi_size_mb, QSPI_PASS);
protocol_send_qspi_info(); /* лучший случай — хост уже слушает; см. protocol.h */
}
/* Smoke-test SDRAM/SEMC (Фаза 4) — диагностический, неблокирующий: не
* влияет на attempt_boot() ниже (bootloader SDRAM ни для чего не
* использует, см. docstring файла), результат только репортится по CDC.
* Цель поймать неисправность SEMC/SDRAM на плате раньше, чем её
* унаследует tft_app (см. bsp/sdram/README.md). */
bool sdram_ok = (bsp_sdram_configure() == BSP_OK) && (bsp_sdram_init() == BSP_OK);
hw_fault = hw_fault || (!sdram_ok);
protocol_set_smoke_result(sdram_ok);
protocol_send_smoke_status(); /* лучший случай — хост уже слушает; см. protocol.h */
/* Отслеживает recovery-состояние между попытками — main-loop использует
* его для LED-паттерна каждую итерацию, не только на попытках прыжка
* (attempt_boot() зовётся раз в SD_RETRY_PERIOD_MS, LED должен обновляться
@ -251,24 +295,11 @@ int main(void)
bsp_usb_cdc_poll();
cli_process();
if (in_recovery)
{
bool recovery_led_on = (bsp_tick_get_ms() % RECOVERY_BLINK_PERIOD_MS) < RECOVERY_BLINK_ON_MS;
bsp_led_set(LED_HEARTBEAT, recovery_led_on);
bsp_led_set(LED_APP, recovery_led_on);
}
else
{
uint32_t heartbeat_phase_ms = bsp_tick_get_ms() % HEARTBEAT_PERIOD_MS;
if (heartbeat_phase_ms < HEARTBEAT_ON_MS)
{
bsp_led_on(LED_HEARTBEAT);
}
else
{
bsp_led_off(LED_HEARTBEAT);
}
}
/* Фоновый паттерн: recovery > неисправность железа > норма (ждём SD).
* Паттерн «установка» здесь не участвует он рисуется изнутри самой
* (блокирующей) установки, см. led_status_tick_install(). */
led_bg_t bg = in_recovery ? LED_BG_RECOVERY : (hw_fault ? LED_BG_HW_FAULT : LED_BG_WAITING);
led_status_draw_background(bg);
if (qspi_ok && ((int32_t) (bsp_tick_get_ms() - next_sd_retry_ms) >= 0))
{

View file

@ -12,10 +12,33 @@
#include <stdio.h>
#ifdef BOOTLOADER_DEV_DIAGNOSTICS
#include <inttypes.h>
#endif
/* ── Константы ─────────────────────────────────────────────────────────── */
/** @brief Размер внутреннего TX-буфера. */
#define PROTO_BUF_SIZE 128U
/**
* @brief Размер внутреннего TX-буфера.
*
* 192, не 128: {"type":"sdram_test",...} с именем фазы "sequential" и
* fail_addr/expected/got самое длинное сообщение протокола, ~140 байт.
*/
#define PROTO_BUF_SIZE 192U
/* ── Состояние модуля ──────────────────────────────────────────────────── */
/** @brief Кэш результата smoke-теста SDRAM/SEMC — см. protocol_send_smoke_status(). */
static bool s_smoke_result_known = false;
static bool s_smoke_result_pass = false;
/** @brief Кэш информации о QSPI-чипе — см. protocol_send_qspi_info(). */
static bool s_qspi_info_known = false;
static bool s_qspi_pass = false;
static uint8_t s_qspi_mfr_id = 0U;
static uint8_t s_qspi_cap_byte = 0U;
static uint32_t s_qspi_size_mb = 0U;
static const char *s_p_qspi_chip = ""; /* строковый литерал из bsp_qspi_decode_chip() — статичен */
/* ── Public API ────────────────────────────────────────────────────────── */
@ -47,6 +70,46 @@ void protocol_send_status(const char *p_state)
cli_send(buf);
}
void protocol_set_smoke_result(bool pass)
{
s_smoke_result_known = true;
s_smoke_result_pass = pass;
}
void protocol_send_smoke_status(void)
{
if (s_smoke_result_known)
{
protocol_send_status(s_smoke_result_pass ? "smoke_pass" : "smoke_fail");
}
}
void protocol_set_qspi_info(uint8_t mfr_id, const char *p_chip_name, uint8_t cap_byte, uint32_t size_mb, bool pass)
{
s_qspi_info_known = true;
s_qspi_mfr_id = mfr_id;
s_p_qspi_chip = p_chip_name;
s_qspi_cap_byte = cap_byte;
s_qspi_size_mb = size_mb;
s_qspi_pass = pass;
}
void protocol_send_qspi_info(void)
{
if (!s_qspi_info_known)
{
return;
}
char buf[PROTO_BUF_SIZE];
(void) snprintf(buf, sizeof(buf),
"{\"type\":\"qspi_info\",\"chip\":\"%s\",\"mfr\":\"0x%02X\","
"\"cap_byte\":\"0x%02X\",\"size_mb\":%u,\"pass\":%s}\n",
s_p_qspi_chip, (unsigned) s_qspi_mfr_id, (unsigned) s_qspi_cap_byte,
(unsigned) s_qspi_size_mb, s_qspi_pass ? "true" : "false");
cli_send(buf);
}
void protocol_send_wdog_status(void)
{
char buf[PROTO_BUF_SIZE];
@ -60,3 +123,16 @@ void protocol_send_wdog_status(void)
(unsigned) RECOVERY_DEFAULT_THRESHOLD);
cli_send(buf);
}
#ifdef BOOTLOADER_DEV_DIAGNOSTICS
void protocol_send_sdram_test_phase(
const char *p_phase, bool pass, uint32_t duration_ms, uint32_t fail_addr, uint8_t expected, uint8_t got)
{
char buf[PROTO_BUF_SIZE];
(void) snprintf(buf, sizeof(buf),
"{\"type\":\"sdram_test\",\"phase\":\"%s\",\"pass\":%s,\"duration_ms\":%" PRIu32
",\"fail_addr\":\"0x%08" PRIX32 "\",\"expected\":\"0x%02X\",\"got\":\"0x%02X\"}\n",
p_phase, pass ? "true" : "false", duration_ms, fail_addr, (unsigned) expected, (unsigned) got);
cli_send(buf);
}
#endif /* BOOTLOADER_DEV_DIAGNOSTICS */

View file

@ -17,13 +17,30 @@
*
* Типы исходящих событий (Фаза 3):
* status top-level состояние bootloader (waiting_for_sd,
* installing, update_skipped, recovery_mode см.
* protocol_send_status())
* installing, update_skipped, recovery_mode,
* smoke_pass/smoke_fail (Фаза 4, SDRAM/SEMC smoke-test,
* см. main.c) см. protocol_send_status())
* wdog статус аппаратного watchdog (armed/timeout/recovered,
* см. protocol_send_wdog_status())
* qspi_info опознанный чип QSPI flash + вписывается ли в минимум
* карты флеша (Фаза 4, см. protocol_send_qspi_info())
*
* Полный словарь состояний status (smoke_pass/smoke_fail/booting/...)
* появится в Фазе 4.
* smoke_pass/smoke_fail и qspi_info шлются один раз сразу после
* bsp_usb_cdc_init()/bsp_qspi_init() хост почти наверняка не успевает
* открыть порт к этому моменту (USB enumeration), cli_send() неблокирующий и
* теряет запись, если TX ещё не готов. Решение для обоих одинаковое:
* protocol_set_smoke_result()/protocol_set_qspi_info() кэширует исход,
* protocol_send_smoke_status()/protocol_send_qspi_info() переспрашивает его в
* любой момент сессии по командам "smoke_status"/"qspi_info" (тот же приём,
* что уже был у wdog).
*
* Ещё не реализовано (Фаза 4): состояние "booting" и словарь LED-паттернов
* на LED_APP для всех перечисленных состояний см. firmware/bootloader/PLAN.md.
*
* [DEV-ONLY, Фаза 4] Тип sdram_test под BOOTLOADER_DEV_DIAGNOSTICS
* (компилируется только в Debug, см. CMakeLists.txt и dev_sdram_test.h):
* sdram_test результат одной фазы глубокого теста SDRAM, см.
* protocol_send_sdram_test_phase() и dev_sdram_test.h
*/
#ifndef PROTOCOL_H_
@ -31,6 +48,9 @@
#include "version.h"
#include <stdbool.h>
#include <stdint.h>
/** @brief Строка версии bootloader, вставляемая в version_response. */
#define BOOTLOADER_VERSION BOOTLOADER_VERSION_STR
@ -63,6 +83,55 @@ void protocol_send_error(const char *p_code);
*/
void protocol_send_status(const char *p_state);
/**
* @brief Сохранить результат smoke-теста SDRAM/SEMC (Фаза 4) для последующих
* protocol_send_smoke_status().
*
* Вызывать один раз из main.c сразу после bsp_sdram_configure()+_init().
*
* @param[in] pass true, если оба вызова вернули BSP_OK.
*/
void protocol_set_smoke_result(bool pass);
/**
* @brief Отправить status с последним сохранённым результатом smoke-теста.
*
* Формат: {"type":"status","state":"smoke_pass"} / "smoke_fail". Ничего не
* делает, если protocol_set_smoke_result() ещё ни разу не вызывался (не
* должно происходить в штатной последовательности main.c, но на команду
* "smoke_status" в этом случае лучше промолчать, чем соврать результат).
*/
void protocol_send_smoke_status(void);
/**
* @brief Сохранить информацию об обнаруженном QSPI flash-чипе (Фаза 4) для
* последующих protocol_send_qspi_info().
*
* Вызывать один раз из main.c сразу после bsp_qspi_init(). Значения
* читаются напрямую по JEDEC ID (bsp_qspi_decode_chip()), не зависят от
* успеха bsp_qspi_init() так неопознанный/не тот чип тоже репортится с
* деталями, а не просто "не сработало".
*
* @param[in] mfr_id Сырой manufacturer byte JEDEC ID.
* @param[in] p_chip_name Имя чипа ("W25Q128", "UNKNOWN") см. bsp_qspi_decode_chip().
* @param[in] cap_byte Сырой capacity byte JEDEC ID.
* @param[in] size_mb Обнаруженная ёмкость, МБ (0, если чип не опознан).
* @param[in] pass true, если Winbond + известная ёмкость + ёмкость не
* меньше минимума карты флеша (см. main.c).
*/
void protocol_set_qspi_info(
uint8_t mfr_id, const char *p_chip_name, uint8_t cap_byte, uint32_t size_mb, bool pass);
/**
* @brief Отправить event с последней сохранённой информацией о QSPI-чипе.
*
* Формат: {"type":"qspi_info","chip":"W25Q128","mfr":"0xEF","cap_byte":"0x18",
* "size_mb":16,"pass":true}
*
* Ничего не делает, если protocol_set_qspi_info() ещё не вызывался.
*/
void protocol_send_qspi_info(void);
/**
* @brief Отправить статус аппаратного watchdog и счётчика попыток загрузки
* (Фаза 6).
@ -82,4 +151,27 @@ void protocol_send_status(const char *p_state);
*/
void protocol_send_wdog_status(void);
#ifdef BOOTLOADER_DEV_DIAGNOSTICS
/**
* @brief [DEV-ONLY] Отправить результат одной фазы dev_sdram_test (Фаза 4).
*
* Формат: {"type":"sdram_test","phase":"data_bus","pass":true,"duration_ms":812,
* "fail_addr":"0x00000000","expected":"0x00","got":"0x00"}
*
* fail_addr/expected/got осмысленны только при pass=false при pass=true
* передавать 0/0/0. phase="summary" итог всего прогона (все фазы пройдены
* И до summary дошло см. dev_sdram_test.c).
*
* @param[in] p_phase "configure"/"address_bus"/"data_bus"/"sequential"/
* "retention"/"summary".
* @param[in] pass Результат фазы.
* @param[in] duration_ms Длительность фазы, мс.
* @param[in] fail_addr Адрес первой ошибки (0, если pass=true).
* @param[in] expected Ожидаемый байт при ошибке.
* @param[in] got Прочитанный байт при ошибке.
*/
void protocol_send_sdram_test_phase(
const char *p_phase, bool pass, uint32_t duration_ms, uint32_t fail_addr, uint8_t expected, uint8_t got);
#endif /* BOOTLOADER_DEV_DIAGNOSTICS */
#endif /* PROTOCOL_H_ */

View file

@ -27,6 +27,7 @@
#include "bsp/wdog.h"
#include "ff.h"
#include "flash_map.h"
#include "led_status.h"
#include "protocol.h"
#include "slot_version.h"
#include "update_policy.h"
@ -106,6 +107,7 @@ static bool erase_and_copy_candidate(const struct flash_area *p_fap, uint32_t fi
{
bsp_usb_cdc_poll();
bsp_wdog_refresh(); /* потоковое копирование — реальный прогресс на чанк */
led_status_tick_install(); /* APP 250/250 всю установку, см. led_status.h */
uint32_t want = file_size - offset;
if (want > SD_UPDATE_CHUNK_SIZE)
@ -176,6 +178,7 @@ static bool run_update(bool button_held, bool recovery_mode)
if (!read_candidate_header(&candidate_ver))
{
protocol_send_error("SD_CANDIDATE_INVALID");
led_status_flash_image_rejected(); /* битый заголовок = негодный файл */
goto cleanup;
}
@ -194,6 +197,7 @@ static bool run_update(bool button_held, bool recovery_mode)
}
protocol_send_status("installing");
led_status_install_begin(); /* открыть окно: tick_install() ниже начинает рисовать */
if (flash_area_open((uint8_t) decision.target_slot, &p_fap) != 0)
{
@ -213,6 +217,7 @@ static bool run_update(bool button_held, bool recovery_mode)
if (!slot_version_get((uint8_t) decision.target_slot, &installed_ver))
{
protocol_send_error("SD_INSTALL_REJECTED");
led_status_flash_image_rejected(); /* записан, но подпись не прошла = негодный файл */
goto cleanup;
}
@ -248,6 +253,7 @@ static bool run_update(bool button_held, bool recovery_mode)
}
cleanup:
led_status_install_end(); /* закрыть окно (идемпотентно, если не открывали) */
(void) f_close(&g_s_file);
(void) f_unmount(SD_UPDATE_MOUNT_POINT);
(void) bsp_sd_deinit();

View file

@ -93,9 +93,9 @@ function(add_mcuboot_stub)
endfunction()
# ─────────────────────────────────────────────────────────────────────────
# Фаза 2/3 — базовые "здоровые" заглушки: подтверждаются сразу, никогда не
# виснут. Slot A мигает раз в 500 мс ("версия 1"), Slot Б — раз в 250 мс
# ("версия 2"). CONFIRM_MODE/HANG_MODE/FEED_WDOG — дефолты (0/0/1).
# Базовые "здоровые" заглушки: подтверждаются сразу, никогда не виснут. Slot A
# мигает раз в 500 мс ("версия 1"), Slot Б — раз в 250 мс ("версия 2").
# CONFIRM_MODE/HANG_MODE/FEED_WDOG — дефолты (0/0/1).
# ─────────────────────────────────────────────────────────────────────────
add_mcuboot_stub(
NAME
@ -116,12 +116,6 @@ add_mcuboot_stub(
OWN_SLOT_ID
1)
# ─────────────────────────────────────────────────────────────────────────
# Фаза 6 — стенд для чек-листа recovery (см. PLAN.md, "Стенд"). Оба варианта
# (Slot A / Slot Б), т.к. в разных сценариях чек-листа "подозреваемый" слот — то
# один, то другой.
# ─────────────────────────────────────────────────────────────────────────
# Класс A: никогда не подтверждается, виснет СРАЗУ (до health-mark) — свежий
# неподтверждённый образ виснет до всякого прогресса. WDOG-сброс → штатный
# MCUboot revert (copy_done без image_ok) → откат, без участия recovery.c.