# Портирование SPI NOR на другую платформу ## 1. Добавить переносимое ядро В сборку новой цели включите `Src/spi_nor.c`, а `Inc` добавьте в include path. Это единственные обязательные файлы. Они используют только C99-заголовки `stddef.h`, `stdint.h` и `string.h`. ## 2. Реализовать platform callbacks Создайте собственный контекст шины и заполните `spi_nor_io_t`: - `transfer` — полный синхронный SPI-обмен при уже активном CS; - `chip_select` — `active=1` опускает CS, `active=0` поднимает его; - `tick_ms` — монотонный 32-битный счётчик миллисекунд; - `delay_ms` — задержка выхода из power-down и пауза BUSY polling; - `lock/unlock` — необязательная пара для RTOS, оба либо заданы, либо `NULL`. `transfer` должен поддерживать следующие варианты: | `tx` | `rx` | Действие | |---|---|---| | не `NULL` | `NULL` | передать command или payload | | `NULL` | не `NULL` | передавать dummy и сохранить входящие байты | | не `NULL` | не `NULL` | full-duplex обмен, допустим для порта | Callback не должен самостоятельно переключать CS. Ядро может вызвать transfer дважды под одним CS: сначала command, затем data. Ошибка должна возвращаться как `SPI_NOR_IO_ERROR` или `SPI_NOR_IO_TIMEOUT`. ## 3. Настроить SPI Для W25Q/SST25 используйте master, 8 бит, MSB first и режим, разрешённый datasheet. Частота не должна превышать предел микросхемы и возможности разводки. CS перед `spi_nor_init()` должен быть настроен output push-pull и находиться в 1. Проверьте питание, общую землю и отсутствие конфликта MISO с другими CS. ## 4. Инициализировать и выполнить probe ```c spi_nor_t device; spi_nor_io_t io = MakeMyPlatformIo(); spi_nor_config_t config = spi_nor_default_config(); config.erase_timeout_ms = 10000U; /* Пример для медленной микросхемы. */ if (spi_nor_init(&device, &io, &config) != SPI_NOR_OK) { HandleConfigurationError(); } if (spi_nor_probe(&device) != SPI_NOR_OK) { HandleAbsentOrUnsupportedFlash(); } ``` До успешного probe read/program/erase отклоняются. Для диагностики неизвестной памяти вызовите `spi_nor_get_last_jedec()` после неуспешного probe. ## 5. Подключить FlashStorage при необходимости Добавьте в сборку: - `Libraries/FlashStorage/Src/flash_storage.c`; - `Libraries/FlashStorage/Adapter/SPI_NOR/Src/flash_storage_spi_nor_adapter.c`; - include paths `FlashStorage/Inc` и `FlashStorage/Adapter/SPI_NOR/Inc`. После probe создайте `flash_storage_spi_nor_adapter_t`, вызовите `flash_storage_spi_nor_adapter_init()` и передайте `flash_storage_spi_nor_ops` в `flash_storage_init()` или `eeprom_store_init()`. Layout должен лежать внутри фактической `capacity_bytes` и быть выровнен по 4096 байтам. ## 6. STM32 HAL Для другого STM32 можно использовать существующий порт, если семейство имеет совместимый HAL API. Подключите `spi_nor_stm32f4_hal.c`, замените HAL include на заголовок нужного семейства и переименуйте порт. Имена конкретных `hspi`, GPIO и pin передавайте из приложения; не добавляйте их как `extern` в библиотеку. Если используется DMA или RTOS, простой STM32F4 порт следует заменить: - transfer обязан дождаться завершения DMA до возврата; - lock/unlock должны использовать mutex, не semaphore из ISR; - mutex должен быть общим для всех устройств на одной SPI-шине; - callback delay должен освобождать CPU через RTOS delay, если это допустимо. ## 7. Приёмочные проверки 1. На пустой шине получить `SPI_NOR_E_NOT_FOUND` и CS=1. 2. Прочитать ожидаемый JEDEC. 3. Проверить чтение первого и последнего адреса. 4. В тестовом секторе выполнить erase и полный verify `0xFF`. 5. Записать данные через границу страницы и сравнить readback. 6. Имитировать SPI error/timeout и убедиться, что CS поднят. 7. Проверить отказ для диапазона за концом памяти и невыровненного erase. 8. При RTOS параллельно запустить два клиента и проверить mutex-анализатором. Для текущего репозитория host mock запускается через `python -m unittest tests.test_spi_nor_host` и не требует STM32 HAL. ## 8. Перенос read-only command service Если нужен сервисный просмотр команд, добавьте в сборку `Diagnostics/Src/spi_nor_command_service.c` и include path `Diagnostics/Inc`. Передавайте ему уже успешно probed `spi_nor_t`; отдельный HAL-порт не нужен: ```c spi_nor_command_service_request_t request = {0}; spi_nor_command_service_response_t response; request.sequence = 1U; request.tx_length = 1U; request.rx_length = 3U; request.tx[0] = 0x9FU; (void)spi_nor_command_service_process(&device, &request, &response); ``` Не расширяйте существующий allowlist ради записи. Если продукту когда-нибудь потребуются изменяющие операции, они должны иметь отдельный API, threat model, защиту диапазонов и аппаратные тесты. Текущий сервис не содержит compile-time или runtime переключателя, снимающего блокировку. Проверьте mock-тестом своей платформы: TX/RX порядок под одним CS, освобождение CS при timeout каждого transfer, общий mutex SPI bus, строгие максимумы 4/64 и отказ для `06`, `02`, всех erase, status/protection writes, power/reset и неизвестных opcode.