From 698632ebf32c575327f10bd258676c0ae70c587e Mon Sep 17 00:00:00 2001 From: Andrey Kruchinkin Date: Tue, 15 Sep 2026 15:57:06 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=82=D0=B5=D0=BA=D1=83=D1=89=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20NAND?= =?UTF-8?q?=20=D0=B8=20LED=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=20=D0=BE=D0=B1?= =?UTF-8?q?=D1=8A=D0=B5=D0=B4=D0=B8=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=82=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NEW_PROJECT.md | 22 ++ README.md | 3 +- c/led-indicator/CMakeLists.txt | 30 +++ c/led-indicator/README.md | 126 +++++++++ c/led-indicator/led_indicator.c | 246 ++++++++++++++++++ c/led-indicator/led_indicator.h | 117 +++++++++ .../ports/stm32-hal/led_indicator_stm32_hal.c | 65 +++++ .../ports/stm32-hal/led_indicator_stm32_hal.h | 39 +++ ...d_indicator_stm32_hal_config.f1.template.h | 6 + ...d_indicator_stm32_hal_config.f4.template.h | 6 + ...d_indicator_stm32_hal_config.g4.template.h | 6 + .../fakes/led_indicator_stm32_hal_config.h | 27 ++ c/led-indicator/tests/test_led_indicator.c | 84 ++++++ c/led-indicator/tests/test_stm32_port.c | 52 ++++ c/parallel-nand/README.md | 37 ++- c/parallel-nand/docs/EMPTY_BOARD_PINOUT.md | 6 +- c/parallel-nand/docs/MEMORY_GAS.md | 42 ++- c/parallel-nand/include/parallel_nand.h | 27 +- c/parallel-nand/include/parallel_nand_gas.h | 13 +- c/parallel-nand/include/parallel_nand_port.h | 2 + c/parallel-nand/ports/stm32f103rc/README.md | 51 ++++ .../parallel_nand_port_stm32f103rc.c | 143 ++++++++++ .../parallel_nand_port_stm32f103rc.h | 16 ++ c/parallel-nand/ports/stm32f103ze/README.md | 62 +++++ .../parallel_nand_port_stm32f103ze.c | 184 +++++++++++++ .../parallel_nand_port_stm32f103ze.h | 16 ++ c/parallel-nand/ports/stm32f407ve/README.md | 5 + .../parallel_nand_port_stm32f407ve.c | 3 +- c/parallel-nand/ports/stm32g474ce/README.md | 3 + .../parallel_nand_port_stm32g474ce.c | 3 +- c/parallel-nand/ports/tms320f2812/README.md | 7 +- .../parallel_nand_port_tms320f2812.c | 3 +- c/parallel-nand/src/parallel_nand.c | 117 ++++++++- c/parallel-nand/src/parallel_nand_gas.c | 124 ++++++++- c/parallel-nand/tests/test_parallel_nand.c | 179 ++++++++++++- 35 files changed, 1841 insertions(+), 31 deletions(-) create mode 100644 c/led-indicator/CMakeLists.txt create mode 100644 c/led-indicator/README.md create mode 100644 c/led-indicator/led_indicator.c create mode 100644 c/led-indicator/led_indicator.h create mode 100644 c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal.c create mode 100644 c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal.h create mode 100644 c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal_config.f1.template.h create mode 100644 c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal_config.f4.template.h create mode 100644 c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal_config.g4.template.h create mode 100644 c/led-indicator/tests/fakes/led_indicator_stm32_hal_config.h create mode 100644 c/led-indicator/tests/test_led_indicator.c create mode 100644 c/led-indicator/tests/test_stm32_port.c create mode 100644 c/parallel-nand/ports/stm32f103rc/README.md create mode 100644 c/parallel-nand/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c create mode 100644 c/parallel-nand/ports/stm32f103rc/parallel_nand_port_stm32f103rc.h create mode 100644 c/parallel-nand/ports/stm32f103ze/README.md create mode 100644 c/parallel-nand/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c create mode 100644 c/parallel-nand/ports/stm32f103ze/parallel_nand_port_stm32f103ze.h diff --git a/NEW_PROJECT.md b/NEW_PROJECT.md index 8e5bc49..e6137d3 100644 --- a/NEW_PROJECT.md +++ b/NEW_PROJECT.md @@ -62,6 +62,27 @@ G431 и G474 намеренно используют один исходник этой задачи совместимы. Раздельными остаются конфигурация платы, startup, linker script и выбранный CMSIS device define. +## Выбор готового порта parallel NAND + +| Целевой МК | Реализация шины | Каталог порта | +|---|---|---| +| TMS320F2812 | XINTF Zone 6 | `c/parallel-nand/ports/tms320f2812` | +| STM32F103RCT6 | GPIO bit-bang, поскольку FSMC недоступен в LQFP64 | `c/parallel-nand/ports/stm32f103rc` | +| STM32F103ZET6 | FSMC NAND Bank 3 | `c/parallel-nand/ports/stm32f103ze` | +| STM32F407VET6 | FSMC NAND Bank 2 | `c/parallel-nand/ports/stm32f407ve` | +| STM32G474CEU6 | GPIO bit-bang для UFQFPN48 | `c/parallel-nand/ports/stm32g474ce` | + +Ядро поддерживает профили Micron `MT29F1G08ABADA` и Hynix +`HY27UF084G2M`. В сборку добавляются `src/parallel_nand.c`, при необходимости +`src/parallel_nand_gas.c` или `src/parallel_nand_pcan_gas.c`, и ровно один +аппаратный порт. Конкретная распиновка, требуемые модули HAL и пример +инициализации приведены в README выбранного порта. + +Для `PROGRAM`/`ERASE` линия `WP#` должна управляться портом. Готовые порты +STM32F103RC, STM32F103ZE и STM32G474CE объявляют поддержку записи. Базовые +схемы TMS320F2812 и STM32F407VE фиксируют `WP#` аппаратно и безопасно +возвращают `PNAND_ERROR_WRITE_PROTECTED`, пока плата и порт не доработаны. + Рабочий пример полноценного G474-порта находится в соседнем проекте `candleLight_fw`: `src/device/device_g4.c` отвечает за clock/GPIO, `src/can/m_can.c` — за FDCAN, а `include/config.h` — за привязку платы @@ -77,6 +98,7 @@ USB gs_usb и конкретной разводки адаптера; перен | дисплей ST7789 | `c/st7789` | SPI, DC, CS, RESET, подсветка и задержка | | кнопки | `c/keypad` | настройка GPIO и чтение уровней | | меню | `c/menu` | только привязка painter к дисплею | +| индикация состояний | `c/led-indicator` | готовый STM32 HAL-порт или callbacks GPIO и выбранного таймера | | EEPROM FT24C256 | `c/eeprom-ft24c256` | I2C write/write-read и задержка | | CAN SETTINGS | `c/can-sensor` | bxCAN для F103 либо FDCAN для G431/G474 | diff --git a/README.md b/README.md index b8eb5f8..4b68a35 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ templates/ | [`c/st7789`](c/st7789) | TFT ST7789V по SPI, RGB565, текст без кадрового буфера | `stdint.h` | SPI-запись, линия DC, задержка | | [`c/keypad`](c/keypad) | шесть кнопок: антидребезг, автоповтор, удержание, очередь событий | `stdint.h` | чтение уровня кнопки, время в мс | | [`c/menu`](c/menu) | экранное меню: стек экранов, курсор, прокрутка, тема | `stdint.h` | заливка прямоугольника, вывод строки | +| [`c/led-indicator`](c/led-indicator) | неблокирующие режимы LED: работа, активность, предупреждение и ошибка | C99 | логическая запись канала, время выбранного таймера — **STM32F1/F4/G4 HAL-порт в комплекте** | | [`c/eeprom-ft24c256`](c/eeprom-ft24c256) | EEPROM 24Cxx по I²C с нарезкой записи по страницам | `stdint.h` | две I²C-транзакции, задержка | | [`c/can-sensor`](c/can-sensor) | однокадровые SETCAN SETTINGS для 64-битных ROM | ядро: `stdint.h`; порт F1: CMSIS | callbacks либо готовый bxCAN STM32F1 | | [`c/ds18b20`](c/ds18b20) | термометры DS18B20 поверх программной 1-Wire | `stdint.h` | Init, DelayUs, Reset, WriteBit, ReadBit — **порты STM32F103, STM32G431 и STM32G474 в комплекте** | @@ -40,7 +41,7 @@ templates/ | [`c/rs485-boot`](c/rs485-boot) | прошивка по RS-485 в формате SETGUI v1: потоковый parser, CRC32 и resume | C99 | UART TX/RX, DE, Flash — **порты STM32F103 и STM32G474VET в комплекте** | | [`c/rtc-service`](c/rtc-service) | RTC с резервированным backup-томом | `stdint.h` | доступ к RTC и backup-памяти — **порт K1921VK028 в комплекте** | | [`c/firmware-info`](c/firmware-info) | SemVer, build stamp и git build ID работающего образа | C99 | config-порты STM32F1/F4/G4 и К1921ВК028; упаковка для Modbus/SETGUI | -| [`c/parallel-nand`](c/parallel-nand) | parallel NAND x8: raw main/OOB, bad-block marker и банковое окно GAS для SETGUI | C99, опционально `set-protocol/pcan_gas` | **порты TMS320F2812, STM32F407VE и STM32G474CEU6 в комплекте** | +| [`c/parallel-nand`](c/parallel-nand) | parallel NAND x8: raw read/program/erase, профили Micron MT29F1G08 и Hynix HY27UF084G2M, bad-block marker и банковое окно GAS для SETGUI | C99, опционально `set-protocol/pcan_gas` | **порты TMS320F2812, STM32F103RC/ZE, STM32F407VE и STM32G474CEU6; запись требует управляемого WP#** | ### Python diff --git a/c/led-indicator/CMakeLists.txt b/c/led-indicator/CMakeLists.txt new file mode 100644 index 0000000..ffabcdf --- /dev/null +++ b/c/led-indicator/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.13) +project(led_indicator C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) + +add_library(led_indicator STATIC led_indicator.c) +target_include_directories(led_indicator PUBLIC .) + +if(MSVC) + target_compile_options(led_indicator PRIVATE /W4) +else() + target_compile_options(led_indicator PRIVATE -Wall -Wextra -Wpedantic) +endif() + +option(LED_INDICATOR_BUILD_TESTS "Собирать тесты LED indicator" ON) +if(LED_INDICATOR_BUILD_TESTS) + enable_testing() + add_executable(test_led_indicator tests/test_led_indicator.c) + target_link_libraries(test_led_indicator PRIVATE led_indicator) + add_test(NAME led_indicator COMMAND test_led_indicator) + + add_executable(test_led_indicator_stm32_port + tests/test_stm32_port.c + ports/stm32-hal/led_indicator_stm32_hal.c) + target_include_directories(test_led_indicator_stm32_port PRIVATE + . ports/stm32-hal tests/fakes) + target_link_libraries(test_led_indicator_stm32_port PRIVATE led_indicator) + add_test(NAME led_indicator_stm32_port COMMAND test_led_indicator_stm32_port) +endif() diff --git a/c/led-indicator/README.md b/c/led-indicator/README.md new file mode 100644 index 0000000..7b36a85 --- /dev/null +++ b/c/led-indicator/README.md @@ -0,0 +1,126 @@ +# LED Indicator + +Неблокирующая C99-библиотека для индикации состояний `STARTUP`, `WORK`, +`ACTIVITY`, `WARNING`, `ERROR` и `CRITICAL` с разными рисунками и частотами. +Один экземпляр обслуживает несколько светодиодов. + +Ядро не знает о GPIO, PWM, сдвиговом регистре, RTOS и модели таймера. Порт +передаёт логическое состояние канала наружу и читает монотонное время в +миллисекундах. В библиотеке нет задержек, динамической памяти, прерываний и +изменяемого глобального состояния. + +```text +приложение -> LedIndicator_SetMode/Process -> ядро -> write/now_ms -> GPIO/PWM/expander + TIM +``` + +## Файлы + +| Файл | Назначение | Зависимости | +|---|---|---| +| `led_indicator.h/.c` | режимы, шаблоны и планировщик | C99, `stdint.h` | +| `ports/stm32-hal` | GPIO и выбранный аппаратный TIM | STM32 HAL | +| `tests/test_led_indicator.c` | host-тесты, включая переполнение `uint32_t` | libc | + +## Встроенные режимы + +| Режим | Сигнал | +|---|---| +| `OFF` / `ON` | постоянно выключен / включён | +| `STARTUP` | три коротких импульса, затем постоянно включён | +| `WORK` | 100 мс включён, 900 мс выключен | +| `ACTIVITY` | 50/50 мс | +| `WARNING` | 250/250 мс | +| `ERROR` | два импульса и пауза | +| `CRITICAL` | три импульса и пауза | + +Частоты не зашиты в алгоритм. Скопируйте встроенную таблицу и измените +`duration_ms` до инициализации: + +```c +static LedIndicator_Pattern patterns[LED_INDICATOR_MODE_COUNT]; + +LedIndicator_CopyDefaultPatterns(patterns, LED_INDICATOR_MODE_COUNT); +patterns[LED_INDICATOR_MODE_WORK].duration_ms[0] = 50U; +patterns[LED_INDICATOR_MODE_WORK].duration_ms[1] = 1950U; +config.patterns = patterns; +config.pattern_count = LED_INDICATOR_MODE_COUNT; +``` + +Таблица должна существовать всё время работы экземпляра. До восьми шагов +описываются длительностями и битовой маской `levels`; шаблон может повторяться +или после одного прохода перейти в `final_level`. + +## Контракт порта + +```c +void write(void *context, uint8_t channel, uint8_t logical_on); +uint32_t now_ms(void *context); +``` + +`write` получает именно логический уровень. Инверсию active-low, управление +PWM или запись общего регистра расширителя выполняет порт. Переполнение +32-битной миллисекундной метки обработано разностью беззнаковых чисел. + +Если время уже есть в планировщике приложения, `now_ms` можно оставить `NULL` +и вызывать варианты `LedIndicator_SetModeAt`/`LedIndicator_ProcessAt`. + +## Быстрый старт без привязки к HAL + +```c +static LedIndicator led; +static LedIndicator_Channel state[2]; +LedIndicator_Config cfg; +LedIndicator_Port port = {Board_LedWrite, Board_TimerMs, &board}; + +LedIndicator_ConfigDefault(&cfg); +LedIndicator_Init(&led, state, 2U, &port, &cfg); +LedIndicator_SetMode(&led, 0U, LED_INDICATOR_MODE_WORK); +LedIndicator_SetMode(&led, 1U, LED_INDICATOR_MODE_ERROR); + +for (;;) { + LedIndicator_Process(&led); +} +``` + +`SetMode` идемпотентен: повторный вызов того же режима в каждом проходе цикла +не начинает рисунок заново. Для нового импульса события служит +`LedIndicator_RestartMode`. + +## STM32F103, STM32F4, STM32G431 и STM32G474 + +Порт `ports/stm32-hal` принимает конкретный `TIM_HandleTypeDef *`; библиотека +не использует `HAL_GetTick()` и не занимает SysTick. Настройте TIM как +free-running, запустите его и передайте частоту счётчика после prescaler. + +Скопируйте подходящий `led_indicator_stm32_hal_config.*.template.h` в каталог +платы под именем `led_indicator_stm32_hal_config.h`. + +```c +static const LedIndicator_Stm32HalOutput outputs[] = { + {STATUS_GPIO_Port, STATUS_Pin, 0U}, + {ERROR_GPIO_Port, ERROR_Pin, 1U} +}; +static LedIndicator_Stm32HalPort hw; +static LedIndicator_Port port; + +HAL_TIM_Base_Start(&htim6); /* CNT = 1 кГц в данном примере. */ +LedIndicator_Stm32HalPortInit(&hw, &htim6, 1000U, + outputs, 2U, &port); +``` + +`LedIndicator_Process()` должен вызываться чаще, чем переполняется выбранный +TIM. Для 16-битного CNT на 1 кГц это не реже одного раза за 65 секунд. Сам +таймер и его prescaler/period задаются в CubeMX или board-порте, а не в ядре. + +Для К1921ВК028 и C28x используется тот же основной порт: `now_ms` возвращает +счётчик, увеличиваемый обработчиком выбранного TIMER/CPU Timer, а `write` +обращается к GPIO SDK. Такое разделение оставляет номер таймера и выводы в +проекте конкретной платы. + +## Проверка + +```sh +cmake -S c/led-indicator -B build/led-indicator +cmake --build build/led-indicator +ctest --test-dir build/led-indicator --output-on-failure +``` diff --git a/c/led-indicator/led_indicator.c b/c/led-indicator/led_indicator.c new file mode 100644 index 0000000..930ff21 --- /dev/null +++ b/c/led-indicator/led_indicator.c @@ -0,0 +1,246 @@ +#include "led_indicator.h" + +#include + +#define LEVELS_1 0x01U +#define LEVELS_10 0x01U +#define LEVELS_1010 0x05U +#define LEVELS_101010 0x15U + +static const LedIndicator_Pattern g_default_patterns[LED_INDICATOR_MODE_COUNT] = { + {{0U}, 0U, 1U, 1U, 0U}, + {{0U}, LEVELS_1, 1U, 1U, 1U}, + {{100U, 100U, 100U, 100U, 100U, 100U}, LEVELS_101010, 6U, 0U, 1U}, + {{100U, 900U}, LEVELS_10, 2U, 1U, 0U}, + {{50U, 50U}, LEVELS_10, 2U, 1U, 0U}, + {{250U, 250U}, LEVELS_10, 2U, 1U, 0U}, + {{100U, 100U, 100U, 700U}, LEVELS_1010, 4U, 1U, 0U}, + {{100U, 100U, 100U, 100U, 100U, 700U}, LEVELS_101010, 6U, 1U, 0U} +}; + +static uint8_t pattern_valid(const LedIndicator_Pattern *pattern) +{ + size_t i; + uint32_t total = 0U; + + if ((pattern == NULL) || (pattern->step_count == 0U) || + (pattern->step_count > LED_INDICATOR_MAX_STEPS)) { + return 0U; + } + if (pattern->step_count == 1U) { + return 1U; + } + for (i = 0U; i < pattern->step_count; ++i) { + if ((pattern->duration_ms[i] == 0U) || + (UINT32_MAX - total < pattern->duration_ms[i])) { + return 0U; + } + total += pattern->duration_ms[i]; + } + return (uint8_t)(total != 0U); +} + +static uint32_t pattern_period(const LedIndicator_Pattern *pattern) +{ + size_t i; + uint32_t total = 0U; + + for (i = 0U; i < pattern->step_count; ++i) { + total += pattern->duration_ms[i]; + } + return total; +} + +static uint8_t pattern_level(const LedIndicator_Pattern *pattern, uint32_t elapsed) +{ + uint32_t position = elapsed; + uint32_t period; + size_t i; + + if (pattern->step_count == 1U) { + return (uint8_t)(pattern->levels & 1U); + } + + period = pattern_period(pattern); + if (pattern->repeat != 0U) { + position %= period; + } else if (position >= period) { + return (uint8_t)(pattern->final_level != 0U); + } + + for (i = 0U; i < pattern->step_count; ++i) { + if (position < pattern->duration_ms[i]) { + return (uint8_t)((pattern->levels >> i) & 1U); + } + position -= pattern->duration_ms[i]; + } + return (uint8_t)(pattern->final_level != 0U); +} + +static void write_if_changed(LedIndicator *instance, size_t channel, uint8_t level, + uint8_t force) +{ + LedIndicator_Channel *state = &instance->channels[channel]; + + level = (uint8_t)(level != 0U); + if ((force != 0U) || (state->output_level != level)) { + state->output_level = level; + instance->port.write(instance->port.context, (uint8_t)channel, level); + } +} + +void LedIndicator_ConfigDefault(LedIndicator_Config *config) +{ + if (config != NULL) { + config->patterns = g_default_patterns; + config->pattern_count = LED_INDICATOR_MODE_COUNT; + } +} + +size_t LedIndicator_CopyDefaultPatterns(LedIndicator_Pattern *patterns, size_t capacity) +{ + size_t i; + size_t count = (size_t)LED_INDICATOR_MODE_COUNT; + + if (patterns == NULL) { + return 0U; + } + if (capacity < count) { + count = capacity; + } + for (i = 0U; i < count; ++i) { + patterns[i] = g_default_patterns[i]; + } + return count; +} + +uint8_t LedIndicator_ValidateConfig(const LedIndicator_Config *config) +{ + size_t i; + + if ((config == NULL) || (config->patterns == NULL) || + (config->pattern_count == 0U) || + (config->pattern_count > (size_t)LED_INDICATOR_MODE_COUNT)) { + return 0U; + } + for (i = 0U; i < config->pattern_count; ++i) { + if (pattern_valid(&config->patterns[i]) == 0U) { + return 0U; + } + } + return 1U; +} + +uint8_t LedIndicator_Init(LedIndicator *instance, + LedIndicator_Channel *channels, + size_t channel_count, + const LedIndicator_Port *port, + const LedIndicator_Config *config) +{ + size_t i; + + if ((instance == NULL) || (channels == NULL) || (channel_count == 0U) || + (channel_count > 256U) || (port == NULL) || (port->write == NULL) || + (LedIndicator_ValidateConfig(config) == 0U)) { + return 0U; + } + + instance->port = *port; + instance->patterns = config->patterns; + instance->pattern_count = config->pattern_count; + instance->channels = channels; + instance->channel_count = channel_count; + instance->initialized = 1U; + + for (i = 0U; i < channel_count; ++i) { + channels[i].mode = LED_INDICATOR_MODE_OFF; + channels[i].started_ms = 0U; + channels[i].output_level = 0U; + channels[i].initialized = 1U; + write_if_changed(instance, i, 0U, 1U); + } + return 1U; +} + +uint8_t LedIndicator_SetModeAt(LedIndicator *instance, size_t channel, + LedIndicator_Mode mode, uint32_t now_ms) +{ + if ((instance == NULL) || (instance->initialized == 0U) || + (channel >= instance->channel_count) || ((size_t)mode >= instance->pattern_count)) { + return 0U; + } + if (instance->channels[channel].mode == mode) { + return 1U; + } + return LedIndicator_RestartModeAt(instance, channel, mode, now_ms); +} + +uint8_t LedIndicator_RestartModeAt(LedIndicator *instance, size_t channel, + LedIndicator_Mode mode, uint32_t now_ms) +{ + LedIndicator_Channel *state; + const LedIndicator_Pattern *pattern; + + if ((instance == NULL) || (instance->initialized == 0U) || + (channel >= instance->channel_count) || ((size_t)mode >= instance->pattern_count)) { + return 0U; + } + + state = &instance->channels[channel]; + state->mode = mode; + state->started_ms = now_ms; + pattern = &instance->patterns[(size_t)mode]; + write_if_changed(instance, channel, pattern_level(pattern, 0U), 0U); + return 1U; +} + +uint8_t LedIndicator_SetMode(LedIndicator *instance, size_t channel, + LedIndicator_Mode mode) +{ + if ((instance == NULL) || (instance->port.now_ms == NULL)) { + return 0U; + } + return LedIndicator_SetModeAt(instance, channel, mode, + instance->port.now_ms(instance->port.context)); +} + +uint8_t LedIndicator_RestartMode(LedIndicator *instance, size_t channel, + LedIndicator_Mode mode) +{ + if ((instance == NULL) || (instance->port.now_ms == NULL)) { + return 0U; + } + return LedIndicator_RestartModeAt(instance, channel, mode, + instance->port.now_ms(instance->port.context)); +} + +void LedIndicator_ProcessAt(LedIndicator *instance, uint32_t now_ms) +{ + size_t i; + + if ((instance == NULL) || (instance->initialized == 0U)) { + return; + } + for (i = 0U; i < instance->channel_count; ++i) { + const LedIndicator_Channel *state = &instance->channels[i]; + const LedIndicator_Pattern *pattern = &instance->patterns[(size_t)state->mode]; + const uint32_t elapsed = now_ms - state->started_ms; + write_if_changed(instance, i, pattern_level(pattern, elapsed), 0U); + } +} + +void LedIndicator_Process(LedIndicator *instance) +{ + if ((instance != NULL) && (instance->port.now_ms != NULL)) { + LedIndicator_ProcessAt(instance, instance->port.now_ms(instance->port.context)); + } +} + +LedIndicator_Mode LedIndicator_GetMode(const LedIndicator *instance, size_t channel) +{ + if ((instance == NULL) || (instance->initialized == 0U) || + (channel >= instance->channel_count)) { + return LED_INDICATOR_MODE_OFF; + } + return instance->channels[channel].mode; +} diff --git a/c/led-indicator/led_indicator.h b/c/led-indicator/led_indicator.h new file mode 100644 index 0000000..b63b07e --- /dev/null +++ b/c/led-indicator/led_indicator.h @@ -0,0 +1,117 @@ +/** + * @file led_indicator.h + * @brief Неблокирующая индикация состояния устройства на одном или нескольких LED. + */ + +#ifndef LED_INDICATOR_H +#define LED_INDICATOR_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define LED_INDICATOR_MAX_STEPS 8U + +typedef enum { + LED_INDICATOR_MODE_OFF = 0, + LED_INDICATOR_MODE_ON, + LED_INDICATOR_MODE_STARTUP, + LED_INDICATOR_MODE_WORK, + LED_INDICATOR_MODE_ACTIVITY, + LED_INDICATOR_MODE_WARNING, + LED_INDICATOR_MODE_ERROR, + LED_INDICATOR_MODE_CRITICAL, + LED_INDICATOR_MODE_COUNT +} LedIndicator_Mode; + +/** Один период сигнала. Бит N в levels задаёт уровень шага N. */ +typedef struct { + uint32_t duration_ms[LED_INDICATOR_MAX_STEPS]; + uint8_t levels; + uint8_t step_count; + uint8_t repeat; + uint8_t final_level; +} LedIndicator_Pattern; + +/** + * Аппаратный порт. write получает логический уровень, поэтому active-low, + * GPIO, PWM и регистры расширителя обрабатываются за границей ядра. + */ +typedef struct { + void (*write)(void *context, uint8_t channel, uint8_t on); + uint32_t (*now_ms)(void *context); + void *context; +} LedIndicator_Port; + +typedef struct { + LedIndicator_Mode mode; + uint32_t started_ms; + uint8_t output_level; + uint8_t initialized; +} LedIndicator_Channel; + +typedef struct { + const LedIndicator_Pattern *patterns; + size_t pattern_count; +} LedIndicator_Config; + +typedef struct { + LedIndicator_Port port; + const LedIndicator_Pattern *patterns; + LedIndicator_Channel *channels; + size_t pattern_count; + size_t channel_count; + uint8_t initialized; +} LedIndicator; + +/** Заполняет конфигурацию встроенными шаблонами режимов. */ +void LedIndicator_ConfigDefault(LedIndicator_Config *config); + +/** Копирует встроенные шаблоны в изменяемую таблицу; возвращает число записей. */ +size_t LedIndicator_CopyDefaultPatterns(LedIndicator_Pattern *patterns, size_t capacity); + +/** Проверяет таблицу шаблонов. */ +uint8_t LedIndicator_ValidateConfig(const LedIndicator_Config *config); + +/** + * Инициализирует экземпляр и немедленно выключает все его каналы. + * Массив channels принадлежит приложению и должен жить столько же, сколько instance. + */ +uint8_t LedIndicator_Init(LedIndicator *instance, + LedIndicator_Channel *channels, + size_t channel_count, + const LedIndicator_Port *port, + const LedIndicator_Config *config); + +/** Назначает режим, используя время порта. Повтор того же режима не сбрасывает фазу. */ +uint8_t LedIndicator_SetMode(LedIndicator *instance, size_t channel, + LedIndicator_Mode mode); + +/** Назначает режим с явно переданной меткой времени. Повтор не сбрасывает фазу. */ +uint8_t LedIndicator_SetModeAt(LedIndicator *instance, size_t channel, + LedIndicator_Mode mode, uint32_t now_ms); + +/** Принудительно запускает режим с первого шага, используя время порта. */ +uint8_t LedIndicator_RestartMode(LedIndicator *instance, size_t channel, + LedIndicator_Mode mode); + +/** Принудительно запускает режим с первого шага в указанное время. */ +uint8_t LedIndicator_RestartModeAt(LedIndicator *instance, size_t channel, + LedIndicator_Mode mode, uint32_t now_ms); + +/** Обновляет выходы, используя время порта. Вызывать в главном цикле. */ +void LedIndicator_Process(LedIndicator *instance); + +/** Обновляет выходы с явно переданной меткой времени. */ +void LedIndicator_ProcessAt(LedIndicator *instance, uint32_t now_ms); + +LedIndicator_Mode LedIndicator_GetMode(const LedIndicator *instance, size_t channel); + +#ifdef __cplusplus +} +#endif + +#endif /* LED_INDICATOR_H */ diff --git a/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal.c b/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal.c new file mode 100644 index 0000000..fb44778 --- /dev/null +++ b/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal.c @@ -0,0 +1,65 @@ +#include "led_indicator_stm32_hal.h" + +static void stm32_write(void *context, uint8_t channel, uint8_t on) +{ + LedIndicator_Stm32HalPort *adapter = (LedIndicator_Stm32HalPort *)context; + const LedIndicator_Stm32HalOutput *output; + GPIO_PinState state; + + if ((adapter == NULL) || ((size_t)channel >= adapter->output_count)) { + return; + } + output = &adapter->outputs[channel]; + state = ((on != 0U) ^ (output->active_low != 0U)) ? GPIO_PIN_SET : GPIO_PIN_RESET; + HAL_GPIO_WritePin(output->gpio, output->pin, state); +} + +static uint32_t stm32_now_ms(void *context) +{ + LedIndicator_Stm32HalPort *adapter = (LedIndicator_Stm32HalPort *)context; + const uint32_t current = __HAL_TIM_GET_COUNTER(adapter->timer); + const uint32_t reload = __HAL_TIM_GET_AUTORELOAD(adapter->timer); + uint32_t delta; + uint64_t scaled; + + if (current >= adapter->last_counter) { + delta = current - adapter->last_counter; + } else if (reload == UINT32_MAX) { + delta = current - adapter->last_counter; + } else { + delta = (reload - adapter->last_counter) + 1U + current; + } + adapter->last_counter = current; + + scaled = (uint64_t)adapter->remainder + ((uint64_t)delta * 1000ULL); + adapter->accumulated_ms += (uint32_t)(scaled / adapter->timer_tick_hz); + adapter->remainder = (uint32_t)(scaled % adapter->timer_tick_hz); + return adapter->accumulated_ms; +} + +uint8_t LedIndicator_Stm32HalPortInit(LedIndicator_Stm32HalPort *adapter, + TIM_HandleTypeDef *timer, + uint32_t timer_tick_hz, + const LedIndicator_Stm32HalOutput *outputs, + size_t output_count, + LedIndicator_Port *port) +{ + if ((adapter == NULL) || (timer == NULL) || (timer_tick_hz == 0U) || + (outputs == NULL) || (output_count == 0U) || (output_count > 256U) || + (port == NULL)) { + return 0U; + } + + adapter->timer = timer; + adapter->outputs = outputs; + adapter->output_count = output_count; + adapter->timer_tick_hz = timer_tick_hz; + adapter->last_counter = __HAL_TIM_GET_COUNTER(timer); + adapter->accumulated_ms = 0U; + adapter->remainder = 0U; + + port->write = stm32_write; + port->now_ms = stm32_now_ms; + port->context = adapter; + return 1U; +} diff --git a/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal.h b/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal.h new file mode 100644 index 0000000..28b4aca --- /dev/null +++ b/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal.h @@ -0,0 +1,39 @@ +/** + * @file led_indicator_stm32_hal.h + * @brief Порт LED Indicator для STM32F1/F4/G4 HAL и выбранного TIM. + */ + +#ifndef LED_INDICATOR_STM32_HAL_H +#define LED_INDICATOR_STM32_HAL_H + +#include "led_indicator.h" +#include "led_indicator_stm32_hal_config.h" + +typedef struct { + GPIO_TypeDef *gpio; + uint16_t pin; + uint8_t active_low; +} LedIndicator_Stm32HalOutput; + +typedef struct { + TIM_HandleTypeDef *timer; + const LedIndicator_Stm32HalOutput *outputs; + size_t output_count; + uint32_t timer_tick_hz; + uint32_t last_counter; + uint32_t accumulated_ms; + uint32_t remainder; +} LedIndicator_Stm32HalPort; + +/** + * Создаёт порт на уже настроенном и запущенном таймере. + * timer_tick_hz — частота изменения CNT после prescaler, например 1000 Гц. + */ +uint8_t LedIndicator_Stm32HalPortInit(LedIndicator_Stm32HalPort *adapter, + TIM_HandleTypeDef *timer, + uint32_t timer_tick_hz, + const LedIndicator_Stm32HalOutput *outputs, + size_t output_count, + LedIndicator_Port *port); + +#endif /* LED_INDICATOR_STM32_HAL_H */ diff --git a/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal_config.f1.template.h b/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal_config.f1.template.h new file mode 100644 index 0000000..85038d3 --- /dev/null +++ b/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal_config.f1.template.h @@ -0,0 +1,6 @@ +#ifndef LED_INDICATOR_STM32_HAL_CONFIG_H +#define LED_INDICATOR_STM32_HAL_CONFIG_H + +#include "stm32f1xx_hal.h" + +#endif diff --git a/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal_config.f4.template.h b/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal_config.f4.template.h new file mode 100644 index 0000000..10e25b1 --- /dev/null +++ b/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal_config.f4.template.h @@ -0,0 +1,6 @@ +#ifndef LED_INDICATOR_STM32_HAL_CONFIG_H +#define LED_INDICATOR_STM32_HAL_CONFIG_H + +#include "stm32f4xx_hal.h" + +#endif diff --git a/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal_config.g4.template.h b/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal_config.g4.template.h new file mode 100644 index 0000000..d157754 --- /dev/null +++ b/c/led-indicator/ports/stm32-hal/led_indicator_stm32_hal_config.g4.template.h @@ -0,0 +1,6 @@ +#ifndef LED_INDICATOR_STM32_HAL_CONFIG_H +#define LED_INDICATOR_STM32_HAL_CONFIG_H + +#include "stm32g4xx_hal.h" + +#endif diff --git a/c/led-indicator/tests/fakes/led_indicator_stm32_hal_config.h b/c/led-indicator/tests/fakes/led_indicator_stm32_hal_config.h new file mode 100644 index 0000000..6e9cf41 --- /dev/null +++ b/c/led-indicator/tests/fakes/led_indicator_stm32_hal_config.h @@ -0,0 +1,27 @@ +#ifndef LED_INDICATOR_STM32_HAL_CONFIG_H +#define LED_INDICATOR_STM32_HAL_CONFIG_H + +#include + +typedef enum { + GPIO_PIN_RESET = 0, + GPIO_PIN_SET +} GPIO_PinState; + +typedef struct { + uint16_t last_pin; + GPIO_PinState last_state; + uint32_t writes; +} GPIO_TypeDef; + +typedef struct { + uint32_t counter; + uint32_t autoreload; +} TIM_HandleTypeDef; + +#define __HAL_TIM_GET_COUNTER(handle) ((handle)->counter) +#define __HAL_TIM_GET_AUTORELOAD(handle) ((handle)->autoreload) + +void HAL_GPIO_WritePin(GPIO_TypeDef *gpio, uint16_t pin, GPIO_PinState state); + +#endif diff --git a/c/led-indicator/tests/test_led_indicator.c b/c/led-indicator/tests/test_led_indicator.c new file mode 100644 index 0000000..b1aa9c6 --- /dev/null +++ b/c/led-indicator/tests/test_led_indicator.c @@ -0,0 +1,84 @@ +#include "led_indicator.h" + +#include + +typedef struct { + uint32_t now; + uint8_t outputs[3]; + uint32_t writes[3]; +} FakePort; + +static void fake_write(void *context, uint8_t channel, uint8_t on) +{ + FakePort *fake = (FakePort *)context; + fake->outputs[channel] = on; + fake->writes[channel]++; +} + +static uint32_t fake_now(void *context) +{ + return ((FakePort *)context)->now; +} + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "check failed at line %d: %s\n", __LINE__, \ + #condition); \ + return 1; \ + } \ + } while (0) + +int main(void) +{ + LedIndicator indicator; + LedIndicator_Channel channels[3]; + LedIndicator_Config config; + LedIndicator_Pattern patterns[LED_INDICATOR_MODE_COUNT]; + FakePort fake = {0}; + LedIndicator_Port port = {fake_write, fake_now, &fake}; + + LedIndicator_ConfigDefault(&config); + CHECK(LedIndicator_ValidateConfig(&config) != 0U); + CHECK(LedIndicator_CopyDefaultPatterns(patterns, LED_INDICATOR_MODE_COUNT) == + LED_INDICATOR_MODE_COUNT); + patterns[LED_INDICATOR_MODE_WORK].duration_ms[0] = 25U; + CHECK(patterns[LED_INDICATOR_MODE_WORK].duration_ms[0] == 25U); + CHECK(LedIndicator_Init(&indicator, channels, 3U, &port, &config) != 0U); + CHECK(fake.writes[0] == 1U && fake.writes[1] == 1U && fake.writes[2] == 1U); + + CHECK(LedIndicator_SetMode(&indicator, 0U, LED_INDICATOR_MODE_WORK) != 0U); + CHECK(fake.outputs[0] == 1U); + fake.now = 99U; + CHECK(LedIndicator_SetMode(&indicator, 0U, LED_INDICATOR_MODE_WORK) != 0U); + LedIndicator_Process(&indicator); + CHECK(fake.outputs[0] == 1U); + fake.now = 100U; + LedIndicator_Process(&indicator); + CHECK(fake.outputs[0] == 0U); + fake.now = 1000U; + LedIndicator_Process(&indicator); + CHECK(fake.outputs[0] == 1U); + fake.now = 1050U; + CHECK(LedIndicator_RestartMode(&indicator, 0U, LED_INDICATOR_MODE_WORK) != 0U); + fake.now = 1150U; + LedIndicator_Process(&indicator); + CHECK(fake.outputs[0] == 0U); + + CHECK(LedIndicator_SetModeAt(&indicator, 1U, LED_INDICATOR_MODE_ERROR, + 0xFFFFFFF0UL) != 0U); + LedIndicator_ProcessAt(&indicator, 0x00000054UL); + CHECK(fake.outputs[1] == 0U); /* 100 ms после старта, переход во второй шаг. */ + LedIndicator_ProcessAt(&indicator, 0x000000B8UL); + CHECK(fake.outputs[1] == 1U); /* 200 ms после старта, второй импульс. */ + + CHECK(LedIndicator_SetModeAt(&indicator, 2U, LED_INDICATOR_MODE_STARTUP, 10U) != 0U); + LedIndicator_ProcessAt(&indicator, 610U); + CHECK(fake.outputs[2] == 1U); /* Однократный startup завершён постоянным ON. */ + + CHECK(LedIndicator_SetMode(&indicator, 3U, LED_INDICATOR_MODE_ON) == 0U); + CHECK(LedIndicator_GetMode(&indicator, 0U) == LED_INDICATOR_MODE_WORK); + + puts("led indicator tests passed"); + return 0; +} diff --git a/c/led-indicator/tests/test_stm32_port.c b/c/led-indicator/tests/test_stm32_port.c new file mode 100644 index 0000000..134565d --- /dev/null +++ b/c/led-indicator/tests/test_stm32_port.c @@ -0,0 +1,52 @@ +#include "led_indicator_stm32_hal.h" + +#include + +void HAL_GPIO_WritePin(GPIO_TypeDef *gpio, uint16_t pin, GPIO_PinState state) +{ + gpio->last_pin = pin; + gpio->last_state = state; + gpio->writes++; +} + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "check failed at line %d: %s\n", __LINE__, \ + #condition); \ + return 1; \ + } \ + } while (0) + +int main(void) +{ + GPIO_TypeDef gpio_a = {0}; + GPIO_TypeDef gpio_b = {0}; + TIM_HandleTypeDef timer = {100U, 65535U}; + const LedIndicator_Stm32HalOutput outputs[] = { + {&gpio_a, 0x0001U, 0U}, + {&gpio_b, 0x0080U, 1U} + }; + LedIndicator_Stm32HalPort adapter; + LedIndicator_Port port; + + CHECK(LedIndicator_Stm32HalPortInit(&adapter, &timer, 10000U, + outputs, 2U, &port) != 0U); + port.write(port.context, 0U, 1U); + CHECK(gpio_a.last_pin == 0x0001U && gpio_a.last_state == GPIO_PIN_SET); + port.write(port.context, 1U, 1U); + CHECK(gpio_b.last_pin == 0x0080U && gpio_b.last_state == GPIO_PIN_RESET); + + timer.counter = 109U; + CHECK(port.now_ms(port.context) == 0U); + timer.counter = 110U; + CHECK(port.now_ms(port.context) == 1U); + + timer.counter = 65530U; + (void)port.now_ms(port.context); + timer.counter = 4U; + CHECK(port.now_ms(port.context) == 6544U); + + puts("led indicator STM32 port tests passed"); + return 0; +} diff --git a/c/parallel-nand/README.md b/c/parallel-nand/README.md index ebc4a27..d257563 100644 --- a/c/parallel-nand/README.md +++ b/c/parallel-nand/README.md @@ -1,13 +1,16 @@ # parallel-nand Переносимый C99-драйвер асинхронной parallel NAND x8 и окно управления через -общее адресное пространство GAS. Первый профиль — Micron `MT29F1G08ABADA`: -2048+64 байта, 64 страницы в блоке, 1024 блока. +общее адресное пространство GAS. Есть профили Micron `MT29F1G08ABADA` и +Hynix `HY27UF084G2M` (512 MiB, 2048+64 байта, 64 страницы в блоке, +4096 блоков, ID `AD DC 80 95`). Подготовлены аппаратные порты: - `TMS320F2812` — XINTF Zone 6; - `STM32F407VET6` — аппаратный FSMC NAND Bank 2; +- `STM32F103RCT6` — GPIO bit-bang, так как в LQFP64 FSMC недоступен; +- `STM32F103ZET6` — аппаратный FSMC NAND Bank 3; - `STM32G474CEU6` — GPIO bit-bang для корпуса UFQFPN-48. Ядро не зависит от HAL и транспорта GUI. STM32 HAL используется только в @@ -55,6 +58,12 @@ void memory_init(void) } ``` +Для Hynix на STM32F103ZET6 используйте +`parallel_nand_port_stm32f103ze()` и +`parallel_nand_hy27uf084g2m_geometry`; распиновка и ограничения приведены в +[`ports/stm32f103ze/README.md`](ports/stm32f103ze/README.md). Порт RCT6 +с GPIO bit-bang сохранён как отдельный вариант для корпуса LQFP64. + Диспетчер общего адресного пространства вызывает `parallel_nand_gas_read()`/`parallel_nand_gas_write()` для адресов `0xE000..0xE51F`. Для STM32 с `c/set-protocol/pcan_gas` дополнительно включить @@ -67,10 +76,28 @@ void memory_init(void) [docs/MEMORY_GAS.md](docs/MEMORY_GAS.md). Электрические соединения — в README соответствующего порта и [docs/EMPTY_BOARD_PINOUT.md](docs/EMPTY_BOARD_PINOUT.md). +## PROGRAM и ERASE + +- `parallel_nand_program_page()` записывает полную main-область и, при + необходимости, полную OOB-область страницы. +- `parallel_nand_erase_block()` стирает один физический блок. +- Перед изменением ядро проверяет заводской bad-block marker, на время операции + снимает `WP#`, ждёт `R/B#`, проверяет fail-бит статуса и снова включает + защиту при любом результате. +- Порт обязан объявить `write_supported = 1`; иначе возвращается + `PNAND_ERROR_WRITE_PROTECTED`. Это защищает платы, где `WP#` постоянно + соединён с GND. +- Через GAS разрушительные команды требуют записи `0xA55A` в `CONFIRM` + непосредственно перед `PROGRAM_PAGE` или `ERASE_BLOCK`. + +NAND можно программировать только из `1` в `0`; для повторной записи сначала +стирается весь блок. Не стирайте заводские bad-блоки и не используйте raw-запись +как файловую систему без ECC, wear leveling и защиты загрузочных блоков. + ## Текущие границы -- Реализовано безопасное чтение, RESET, READ ID, STATUS и bad-block marker. -- PROGRAM/ERASE намеренно отсутствуют; ядро включает аппаратную защиту `WP#`. +- Реализованы чтение, программирование страницы, стирание блока, RESET, + READ ID, STATUS и проверка bad-block marker. - ECC пока не исправляет данные. GUI получает физические main/OOB и должен помечать дамп как raw. - В полном дампе bad-блоки не пропускаются: сохраняется физический порядок. @@ -86,3 +113,5 @@ void memory_init(void) 5. Записать номер страницы в `0xE005/0xE006`, затем `3` в `0xE004`. 6. Дождаться `READY | PAGE_VALID` в `0xE002`. 7. Прочитать main из `0xE100..0xE4FF`, OOB из `0xE500..0xE51F`. +8. PROGRAM/ERASE проверять только на заведомо расходном блоке после сохранения + полного raw-дампа. diff --git a/c/parallel-nand/docs/EMPTY_BOARD_PINOUT.md b/c/parallel-nand/docs/EMPTY_BOARD_PINOUT.md index 5094b1f..e0f72de 100644 --- a/c/parallel-nand/docs/EMPTY_BOARD_PINOUT.md +++ b/c/parallel-nand/docs/EMPTY_BOARD_PINOUT.md @@ -30,7 +30,7 @@ TSOP-48 (суффикс `WP`). На второй фотографии NAND — | `CLE` | 16 | фиксация команды | | `ALE` | 17 | фиксация адреса | | `WE#` | 18 | строб команды/адреса от МК | -| `WP#` | 19 | для read-only 10 кОм на GND; для записи — GPIO и pull-down | +| `WP#` | 19 | GPIO МК и pull-down 10 кОм; только для чтения допустим GND через 10 кОм | | `I/O0` | 29 | двунаправленная шина, бит 0 | | `I/O1` | 30 | двунаправленная шина, бит 1 | | `I/O2` | 31 | двунаправленная шина, бит 2 | @@ -69,10 +69,12 @@ TSOP-48 (суффикс `WP`). На второй фотографии NAND — ## Первый запуск 1. Не устанавливать NAND и проверить 3,3 В, отсутствие КЗ и уровни `CE#/WE#/RE#`. -2. Установить NAND, оставить `WP#` в нуле и выполнить RESET `FFh`. +2. Установить NAND, проверить pull-down и уровень `WP# = 0`, выполнить RESET `FFh`. 3. Прочитать ID `90h`: ожидаемые первые байты для профиля — `2C F1`. 4. Считать одну страницу два раза и сравнить main/OOB побайтно. 5. Только после устойчивого чтения запускать полный дамп через GAS. +6. PROGRAM/ERASE проверять на расходном блоке после сохранения raw-дампа; + убедиться осциллографом, что `WP#` поднимается только на время операции. ## Источники для проверки footprint diff --git a/c/parallel-nand/docs/MEMORY_GAS.md b/c/parallel-nand/docs/MEMORY_GAS.md index b760002..a91be51 100644 --- a/c/parallel-nand/docs/MEMORY_GAS.md +++ b/c/parallel-nand/docs/MEMORY_GAS.md @@ -18,6 +18,8 @@ parallel_nand_gas ▼ общее ядро parallel_nand ├─ TMS320F2812: XINTF + ├─ STM32F103RC: GPIO + ├─ STM32F103ZE: FSMC ├─ STM32F407VE: FSMC └─ STM32G474CE: GPIO ``` @@ -77,7 +79,7 @@ ARM. | Адрес | Доступ | Название | Значение | |---:|:---:|---|---| | `E000` | R | `SIGNATURE` | `0x4E44` (`ND`) | -| `E001` | R | `VERSION` | версия окна, сейчас 1 | +| `E001` | R | `VERSION` | версия окна, сейчас 2 | | `E002` | R | `STATUS` | состояние операции | | `E003` | R | `ERROR` | код `PNAND_*`, 0 = нет ошибки | | `E004` | W | `COMMAND` | команда окна | @@ -92,8 +94,9 @@ ARM. | `E00D` | R | `BAD_BLOCK` | 1 для физически плохого блока | | `E00E` | R | `GENERATION_LO` | счётчик загруженных страниц, low | | `E00F` | R | `GENERATION_HI` | счётчик загруженных страниц, high | -| `E100..E4FF` | R | `PAGE_DATA` | 1024 регистра main, два байта LE | -| `E500..E51F` | R | `PAGE_OOB` | 32 регистра OOB, два байта LE | +| `E010` | W | `CONFIRM` | `0xA55A` непосредственно перед разрушительной командой | +| `E100..E4FF` | R/W | `PAGE_DATA` | 1024 регистра main, два байта LE | +| `E500..E51F` | R/W | `PAGE_OOB` | 32 регистра OOB, два байта LE | Между управляющими регистрами и окнами данных есть зарезервированные адреса. Чтение блока через них не выполняется: каждый запрос должен начинаться внутри @@ -107,6 +110,8 @@ ARM. | 1 | `0x0002` | `BUSY` | | 2 | `0x0004` | `PAGE_VALID` | | 3 | `0x0008` | `BAD_BLOCK` | +| 4 | `0x0010` | `WRITE_SUPPORTED`, порт способен управлять `WP#` | +| 5 | `0x0020` | `BUFFER_DIRTY`, буфер изменён после чтения/очистки | | 15 | `0x8000` | `ERROR`, подробность в `E003` | GUI читает окно данных только при `READY=1`, `PAGE_VALID=1`, `ERROR=0`. @@ -118,6 +123,9 @@ GUI читает окно данных только при `READY=1`, `PAGE_VALI | 1 | `RESET` | сброс NAND и ожидание Ready | | 2 | `READ_ID` | обновляет `E00B/E00C` | | 3 | `READ_PAGE` | загружает main/OOB выбранной страницы | +| 4 | `CLEAR_BUFFER` | заполняет main/OOB значением `0xFF` для подготовки записи | +| 5 | `PROGRAM_PAGE` | программирует выбранную страницу из буфера | +| 6 | `ERASE_BLOCK` | стирает блок, содержащий выбранную страницу | Запись номера страницы и запуск — две отдельные операции. Сначала GUI пишет `PAGE_LO/PAGE_HI`, затем записывает `READ_PAGE` в `COMMAND`. Нельзя отправлять @@ -172,6 +180,28 @@ GUI хранит номер последней полностью записан ## PROGRAM/ERASE -Запись и стирание не включены намеренно. Они требуют отдельного разрешения, -проверки ECC, защиты загрузочных блоков и двухфазного подтверждения через GAS. -До добавления этих функций `WP#` удерживается активным аппаратным портом. +Разрушительные команды доступны только если установлен флаг +`WRITE_SUPPORTED`. Ядро проверяет bad-block marker и отказывается изменять +заводской плохой блок. `WP#` снимается только внутри операции и включается +обратно после проверки status fail-бита, тайм-аута или другой ошибки. + +Для программирования очищенной страницы: + +1. Записать `PAGE_LO/PAGE_HI`. +2. Выполнить `CLEAR_BUFFER`. +3. Записать необходимые main/OOB-регистры. Незаписанные байты останутся + `0xFF`; OOB лучше не менять без определённой ECC-разметки. +4. Записать `0xA55A` в `CONFIRM`. +5. Следующей записью без промежуточных операций отправить `PROGRAM_PAGE`. +6. Проверить `READY`, отсутствие `ERROR` и изменение `GENERATION`. + +Для стирания записать любую страницу целевого блока в `PAGE_LO/PAGE_HI`, затем +`0xA55A` в `CONFIRM` и сразу `ERASE_BLOCK`. Изменение страницы, буфера или +другого управляющего регистра сбрасывает подтверждение. После успешного +стирания `PAGE_VALID` очищается. + +Повторное программирование не может превратить `0` обратно в `1`: для этого +требуется стирание всего блока. Перед первым тестом сохраните полный raw-дамп, +исключите загрузочные блоки и используйте заведомо расходный исправный блок. +Библиотека не реализует ECC, wear leveling, журналирование и восстановление +после пропадания питания. diff --git a/c/parallel-nand/include/parallel_nand.h b/c/parallel-nand/include/parallel_nand.h index 8773ede..615ab30 100644 --- a/c/parallel-nand/include/parallel_nand.h +++ b/c/parallel-nand/include/parallel_nand.h @@ -16,13 +16,31 @@ extern "C" { #define PNAND_MT29F1G08_PAGE_TOTAL \ (PNAND_MT29F1G08_PAGE_MAIN + PNAND_MT29F1G08_PAGE_OOB) +#define PNAND_HY27UF084G2M_PAGE_MAIN 2048UL +#define PNAND_HY27UF084G2M_PAGE_OOB 64UL +#define PNAND_HY27UF084G2M_PAGES_PER_BLOCK 64UL +#define PNAND_HY27UF084G2M_BLOCKS 4096UL +#define PNAND_HY27UF084G2M_COLUMN_CYCLES 2U +#define PNAND_HY27UF084G2M_ROW_CYCLES 3U +#define PNAND_HY27UF084G2M_MANUFACTURER_ID 0xADU +#define PNAND_HY27UF084G2M_DEVICE_ID 0xDCU +#define PNAND_HY27UF084G2M_ID3 0x80U +#define PNAND_HY27UF084G2M_ID4 0x95U +#define PNAND_HY27UF084G2M_PAGE_TOTAL \ + (PNAND_HY27UF084G2M_PAGE_MAIN + PNAND_HY27UF084G2M_PAGE_OOB) + typedef enum { PNAND_OK = 0, PNAND_ERROR_ARGUMENT = -1, PNAND_ERROR_PORT = -2, PNAND_ERROR_TIMEOUT = -3, PNAND_ERROR_RANGE = -4, - PNAND_ERROR_BUFFER = -5 + PNAND_ERROR_BUFFER = -5, + PNAND_ERROR_WRITE_PROTECTED = -6, + PNAND_ERROR_PROGRAM_FAILED = -7, + PNAND_ERROR_ERASE_FAILED = -8, + PNAND_ERROR_BAD_BLOCK = -9, + PNAND_ERROR_CONFIRMATION = -10 } parallel_nand_result_t; typedef struct { @@ -41,6 +59,7 @@ typedef struct { } parallel_nand_t; extern const parallel_nand_geometry_t parallel_nand_mt29f1g08_geometry; +extern const parallel_nand_geometry_t parallel_nand_hy27uf084g2m_geometry; int parallel_nand_init(parallel_nand_t *nand, const parallel_nand_port_t *port, @@ -57,6 +76,12 @@ int parallel_nand_read_page(parallel_nand_t *nand, unsigned long page, int parallel_nand_read_page_slice(parallel_nand_t *nand, unsigned long page, unsigned long column, pnand_octet_t *out, unsigned long count); +int parallel_nand_program_page(parallel_nand_t *nand, unsigned long page, + const pnand_octet_t *main_data, + unsigned long main_count, + const pnand_octet_t *oob, + unsigned long oob_count); +int parallel_nand_erase_block(parallel_nand_t *nand, unsigned long block); int parallel_nand_is_bad_block(parallel_nand_t *nand, unsigned long block, int *is_bad); unsigned long parallel_nand_page_count(const parallel_nand_t *nand); diff --git a/c/parallel-nand/include/parallel_nand_gas.h b/c/parallel-nand/include/parallel_nand_gas.h index 50238cc..261f0e9 100644 --- a/c/parallel-nand/include/parallel_nand_gas.h +++ b/c/parallel-nand/include/parallel_nand_gas.h @@ -30,20 +30,28 @@ enum { PNAND_GAS_REG_DEVICE_ID = 0x000C, PNAND_GAS_REG_BAD_BLOCK = 0x000D, PNAND_GAS_REG_GENERATION_LO = 0x000E, - PNAND_GAS_REG_GENERATION_HI = 0x000F + PNAND_GAS_REG_GENERATION_HI = 0x000F, + PNAND_GAS_REG_CONFIRM = 0x0010 }; enum { PNAND_GAS_CMD_NONE = 0, PNAND_GAS_CMD_RESET = 1, PNAND_GAS_CMD_READ_ID = 2, - PNAND_GAS_CMD_READ_PAGE = 3 + PNAND_GAS_CMD_READ_PAGE = 3, + PNAND_GAS_CMD_CLEAR_BUFFER = 4, + PNAND_GAS_CMD_PROGRAM_PAGE = 5, + PNAND_GAS_CMD_ERASE_BLOCK = 6 }; +#define PNAND_GAS_CONFIRM_MAGIC 0xA55AU + #define PNAND_GAS_STATUS_READY 0x0001U #define PNAND_GAS_STATUS_BUSY 0x0002U #define PNAND_GAS_STATUS_PAGE_VALID 0x0004U #define PNAND_GAS_STATUS_BAD_BLOCK 0x0008U +#define PNAND_GAS_STATUS_WRITE_SUPPORTED 0x0010U +#define PNAND_GAS_STATUS_BUFFER_DIRTY 0x0020U #define PNAND_GAS_STATUS_ERROR 0x8000U typedef struct { @@ -56,6 +64,7 @@ typedef struct { unsigned long cached_block; int cached_bad_block; int block_cache_valid; + int destructive_armed; pnand_octet_t id[8]; pnand_octet_t main_data[PNAND_MT29F1G08_PAGE_MAIN]; pnand_octet_t oob[PNAND_MT29F1G08_PAGE_OOB]; diff --git a/c/parallel-nand/include/parallel_nand_port.h b/c/parallel-nand/include/parallel_nand_port.h index a9e73c1..8bf93e3 100644 --- a/c/parallel-nand/include/parallel_nand_port.h +++ b/c/parallel-nand/include/parallel_nand_port.h @@ -19,6 +19,8 @@ typedef struct { int (*is_ready)(void); void (*set_write_protect)(int enabled); void (*delay_us)(unsigned long us); + /* Nonzero only when set_write_protect(0) physically releases WP#. */ + int write_supported; } parallel_nand_port_t; #ifdef __cplusplus diff --git a/c/parallel-nand/ports/stm32f103rc/README.md b/c/parallel-nand/ports/stm32f103rc/README.md new file mode 100644 index 0000000..21ce9b6 --- /dev/null +++ b/c/parallel-nand/ports/stm32f103rc/README.md @@ -0,0 +1,51 @@ +# Порт STM32F103RCT6 для HY27UF084G2M + +У `STM32F103RCT6` корпус LQFP64. Согласно datasheet STM32F103xC/D/E, функция +FSMC в этом корпусе недоступна, поэтому порт выполняет асинхронные NAND-циклы +через GPIO. + +## Соединения + +| HY27UF084G2M | STM32F103RCT6 | LQFP64 | +|---|---|---:| +| `I/O0..I/O7` | `PB0..PB7` | 26, 27, 28, 55, 56, 57, 58, 59 | +| `CLE` | `PC0` | 8 | +| `ALE` | `PC1` | 9 | +| `WE#` | `PC2` | 10 | +| `RE#` | `PC3` | 11 | +| `CE#` | `PC4` | 24 | +| `WP#` | `PC5` | 25 | +| `R/B#` | `PC6` | 37 | + +Питание NAND — 3,3 В. На каждый вывод питания нужен 100 нФ, рядом с чипом — +1–4,7 мкФ. `R/B#` имеет открытый сток и требует внешней подтяжки 4,7–10 кОм +к 3,3 В. Не оставляйте `WP#` неподключённым. + +`PB2` также является `BOOT1`: NAND держит I/O в высокоимпедансном состоянии +до командного цикла, но штатную конфигурацию загрузки платы всё равно нужно +обеспечить подтяжкой. `PB3/PB4` заняты JTAG; порт отключает JTAG и сохраняет +SWD на `PA13/PA14`. + +## Подключение к проекту Cube/HAL + +Добавьте `parallel_nand_port_stm32f103rc.c`, путь к его заголовку и включите +GPIO HAL. Инициализация: + +```c +#include "parallel_nand.h" +#include "parallel_nand_port_stm32f103rc.h" + +static parallel_nand_t nand; + +int nand_init(void) +{ + return parallel_nand_init( + &nand, + parallel_nand_port_stm32f103rc(), + ¶llel_nand_hy27uf084g2m_geometry); +} +``` + +После успешной инициализации прочитайте четыре байта ID и ожидайте +`AD DC 80 95`. Порт поддерживает PROGRAM/ERASE: ядро держит `WP#` в активном +низком уровне между операциями и поднимает его только на время изменения NAND. diff --git a/c/parallel-nand/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c b/c/parallel-nand/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c new file mode 100644 index 0000000..cfa5f39 --- /dev/null +++ b/c/parallel-nand/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c @@ -0,0 +1,143 @@ +#include "parallel_nand_port_stm32f103rc.h" +#include "stm32f1xx_hal.h" + +/* STM32F103RCT6 LQFP64: FSMC is unavailable, so use GPIO bit-bang. */ +#define DATA_MASK 0x00FFU +#define CLE_PIN GPIO_PIN_0 +#define ALE_PIN GPIO_PIN_1 +#define WE_PIN GPIO_PIN_2 +#define RE_PIN GPIO_PIN_3 +#define CE_PIN GPIO_PIN_4 +#define WP_PIN GPIO_PIN_5 +#define RB_PIN GPIO_PIN_6 + +static int data_is_output; + +static void data_input(void) +{ + if (data_is_output) { + GPIOB->CRL = 0x44444444UL; /* PB0..PB7: floating input. */ + data_is_output = 0; + } +} + +static void data_output(void) +{ + if (!data_is_output) { + GPIOB->CRL = 0x33333333UL; /* PB0..PB7: 50 MHz push-pull. */ + data_is_output = 1; + } +} + +static void short_delay(void) +{ + __NOP(); __NOP(); __NOP(); __NOP(); +} + +static int f103rc_init(void) +{ + GPIO_InitTypeDef io; + + __HAL_RCC_AFIO_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOC_CLK_ENABLE(); + + /* Latch inactive control levels while pins are still inputs. */ + GPIOC->BSRR = WE_PIN | RE_PIN | CE_PIN | + ((uint32_t)(CLE_PIN | ALE_PIN | WP_PIN) << 16); + + /* PB3/PB4 carry I/O3/I/O4. Keep SWD, release only the JTAG pins. */ + __HAL_AFIO_REMAP_SWJ_NOJTAG(); + + GPIOB->BSRR = DATA_MASK; + GPIOB->CRL = 0x33333333UL; + data_is_output = 1; + data_input(); + + io.Pin = CLE_PIN | ALE_PIN | WE_PIN | RE_PIN | CE_PIN | WP_PIN; + io.Mode = GPIO_MODE_OUTPUT_PP; + io.Pull = GPIO_NOPULL; + io.Speed = GPIO_SPEED_FREQ_HIGH; + HAL_GPIO_Init(GPIOC, &io); + + io.Pin = RB_PIN; + io.Mode = GPIO_MODE_INPUT; + io.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOC, &io); + return 0; +} + +static void f103rc_select(void) { GPIOC->BRR = CE_PIN; } +static void f103rc_deselect(void) { GPIOC->BSRR = CE_PIN; } + +static void write_cycle(pnand_octet_t value) +{ + data_output(); + GPIOB->BRR = DATA_MASK; + GPIOB->BSRR = (uint32_t)value & DATA_MASK; + short_delay(); + GPIOC->BRR = WE_PIN; + short_delay(); + GPIOC->BSRR = WE_PIN; + short_delay(); +} + +static void f103rc_write_command(pnand_octet_t value) +{ + GPIOC->BSRR = CLE_PIN; + GPIOC->BRR = ALE_PIN; + write_cycle(value); + GPIOC->BRR = CLE_PIN; +} + +static void f103rc_write_address(pnand_octet_t value) +{ + GPIOC->BSRR = ALE_PIN; + GPIOC->BRR = CLE_PIN; + write_cycle(value); + GPIOC->BRR = ALE_PIN; +} + +static void f103rc_write_data(pnand_octet_t value) +{ + GPIOC->BRR = CLE_PIN | ALE_PIN; + write_cycle(value); +} + +static pnand_octet_t f103rc_read_data(void) +{ + pnand_octet_t value; + data_input(); + GPIOC->BRR = CLE_PIN | ALE_PIN | RE_PIN; + short_delay(); + value = (pnand_octet_t)(GPIOB->IDR & DATA_MASK); + GPIOC->BSRR = RE_PIN; + short_delay(); + return value; +} + +static int f103rc_is_ready(void) { return (GPIOC->IDR & RB_PIN) != 0U; } + +static void f103rc_set_wp(int enabled) +{ + if (enabled) GPIOC->BRR = WP_PIN; + else GPIOC->BSRR = WP_PIN; +} + +static void f103rc_delay_us(unsigned long us) +{ + uint32_t cycles = (SystemCoreClock / 3000000UL) * (uint32_t)us; + while (cycles-- != 0U) __NOP(); +} + +static const parallel_nand_port_t f103rc_port = { + f103rc_init, f103rc_select, f103rc_deselect, + f103rc_write_command, f103rc_write_address, f103rc_write_data, + f103rc_read_data, f103rc_is_ready, f103rc_set_wp, f103rc_delay_us, + 1 +}; + +const parallel_nand_port_t *parallel_nand_port_stm32f103rc(void) +{ + return &f103rc_port; +} diff --git a/c/parallel-nand/ports/stm32f103rc/parallel_nand_port_stm32f103rc.h b/c/parallel-nand/ports/stm32f103rc/parallel_nand_port_stm32f103rc.h new file mode 100644 index 0000000..7d76f27 --- /dev/null +++ b/c/parallel-nand/ports/stm32f103rc/parallel_nand_port_stm32f103rc.h @@ -0,0 +1,16 @@ +#ifndef PARALLEL_NAND_PORT_STM32F103RC_H +#define PARALLEL_NAND_PORT_STM32F103RC_H + +#include "parallel_nand_port.h" + +#ifdef __cplusplus +extern "C" { +#endif + +const parallel_nand_port_t *parallel_nand_port_stm32f103rc(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/c/parallel-nand/ports/stm32f103ze/README.md b/c/parallel-nand/ports/stm32f103ze/README.md new file mode 100644 index 0000000..115e7cd --- /dev/null +++ b/c/parallel-nand/ports/stm32f103ze/README.md @@ -0,0 +1,62 @@ +# Порт STM32F103ZET6 для HY27UF084G2M + +Порт использует аппаратный контроллер `FSMC NAND Bank 3`, 8-битную шину и +окно памяти `0x70000000`. Сигналы `CE#`, `WE#`, `RE#`, `CLE` и `ALE` +формируются FSMC. Защита записи `WP#` управляется отдельным GPIO `PG8`. + +## Соединения + +| HY27UF084G2M | STM32F103ZET6 | LQFP144 | Функция FSMC | +|---|---|---:|---| +| `I/O0` | `PD14` | 85 | `D0` | +| `I/O1` | `PD15` | 86 | `D1` | +| `I/O2` | `PD0` | 114 | `D2` | +| `I/O3` | `PD1` | 115 | `D3` | +| `I/O4` | `PE7` | 58 | `D4` | +| `I/O5` | `PE8` | 59 | `D5` | +| `I/O6` | `PE9` | 60 | `D6` | +| `I/O7` | `PE10` | 63 | `D7` | +| `CLE` | `PD11` | 80 | `A16/CLE` | +| `ALE` | `PD12` | 81 | `A17/ALE` | +| `WE#` | `PD5` | 119 | `NWE` | +| `RE#` | `PD4` | 118 | `NOE` | +| `CE#` | `PD7` | 123 | `NCE2` | +| `R/B#` | `PD6` | 122 | вход GPIO (`NWAIT`) | +| `WP#` | `PG8` | 93 | выход GPIO | + +`R/B#` имеет открытый сток и требует внешней подтяжки 4,7–10 кОм к 3,3 В. +Питание NAND — 3,3 В; возле каждой пары питания нужен 100 нФ, рядом с +микросхемой — 1–4,7 мкФ. Все номера выводов MCU указаны для вида сверху. + +## Подключение к STM32Cube HAL + +В `stm32f1xx_hal_conf.h` должны быть включены `HAL_NAND_MODULE_ENABLED` и +`HAL_GPIO_MODULE_ENABLED`. В проект добавляются: + +- `parallel_nand_port_stm32f103ze.c`; +- `stm32f1xx_hal_nand.c`; +- `stm32f1xx_ll_fsmc.c`. + +Инициализация переносимой библиотеки: + +```c +#include "parallel_nand.h" +#include "parallel_nand_port_stm32f103ze.h" + +static parallel_nand_t nand; + +int nand_init(void) +{ + return parallel_nand_init( + &nand, + parallel_nand_port_stm32f103ze(), + ¶llel_nand_hy27uf084g2m_geometry); +} +``` + +Начальные тайминги намеренно консервативны. После запуска проверьте +осциллографом длительности `WE#`/`RE#` и время установки данных. Ожидаемый ID +HY27UF084G2M: `AD DC 80 95`. + +Порт поддерживает PROGRAM/ERASE. `WP#` на `PG8` остаётся активным между +операциями и снимается ядром только на время программирования или стирания. diff --git a/c/parallel-nand/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c b/c/parallel-nand/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c new file mode 100644 index 0000000..83134ef --- /dev/null +++ b/c/parallel-nand/ports/stm32f103ze/parallel_nand_port_stm32f103ze.c @@ -0,0 +1,184 @@ +#include "parallel_nand_port_stm32f103ze.h" + +#include "stm32f1xx_hal.h" +#include "stm32f1xx_hal_nand.h" + +/* STM32F103 names this block FSMC NAND Bank 3. Its HAL memory window is + * NAND_DEVICE1; each access makes FSMC assert PD7/FSMC_NCE2 automatically. */ +#define PNAND_F103ZE_DATA_ADDRESS 0x70000000UL +#define PNAND_F103ZE_COMMAND_AREA (1UL << 16U) +#define PNAND_F103ZE_ADDRESS_AREA (1UL << 17U) + +/* WP# is not generated by FSMC, so it is controlled as an ordinary GPIO. */ +#define PNAND_F103ZE_WP_PORT GPIOG +#define PNAND_F103ZE_WP_PIN GPIO_PIN_8 + +/* HAL owns the FSMC registers through this handle during initialization. */ +static NAND_HandleTypeDef nand_handle; + +/** Configure all FSMC signals that belong to GPIO port D. */ +static void configure_fsmc_gpio_d(void) +{ + GPIO_InitTypeDef pin = {0}; + + /* D0..D3, NOE, NWE, NCE2, CLE and ALE are driven by FSMC. On STM32F1 + * alternate-function output mode is selected without an AF number. */ + pin.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 | + GPIO_PIN_7 | GPIO_PIN_11 | GPIO_PIN_12 | + GPIO_PIN_14 | GPIO_PIN_15; + pin.Mode = GPIO_MODE_AF_PP; + pin.Pull = GPIO_NOPULL; + pin.Speed = GPIO_SPEED_FREQ_HIGH; + HAL_GPIO_Init(GPIOD, &pin); + + /* R/B# is read directly. Its open-drain output needs an external + * 4.7..10 kOhm pull-up to 3.3 V. */ + pin.Pin = GPIO_PIN_6; + pin.Mode = GPIO_MODE_INPUT; + pin.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOD, &pin); +} + +/** Configure FSMC data bits D4..D7 on GPIO port E. */ +static void configure_fsmc_gpio_e(void) +{ + GPIO_InitTypeDef pin = {0}; + + pin.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10; + pin.Mode = GPIO_MODE_AF_PP; + pin.Pull = GPIO_NOPULL; + pin.Speed = GPIO_SPEED_FREQ_HIGH; + HAL_GPIO_Init(GPIOE, &pin); +} + +/** Configure the explicit write-protect output and assert protection. */ +static void configure_write_protect(void) +{ + GPIO_InitTypeDef pin = {0}; + + /* Set the safe active-low level before changing the pad to an output. */ + HAL_GPIO_WritePin(PNAND_F103ZE_WP_PORT, PNAND_F103ZE_WP_PIN, + GPIO_PIN_RESET); + + pin.Pin = PNAND_F103ZE_WP_PIN; + pin.Mode = GPIO_MODE_OUTPUT_PP; + pin.Pull = GPIO_NOPULL; + pin.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(PNAND_F103ZE_WP_PORT, &pin); +} + +/** Initialize the STM32F1 HAL NAND layer with conservative 72 MHz timings. */ +static int f103ze_init(void) +{ + FSMC_NAND_PCC_TimingTypeDef common_timing = {0}; + FSMC_NAND_PCC_TimingTypeDef attribute_timing = {0}; + + __HAL_RCC_GPIOD_CLK_ENABLE(); + __HAL_RCC_GPIOE_CLK_ENABLE(); + __HAL_RCC_GPIOG_CLK_ENABLE(); + __HAL_RCC_FSMC_CLK_ENABLE(); + + configure_fsmc_gpio_d(); + configure_fsmc_gpio_e(); + configure_write_protect(); + + nand_handle.Instance = FSMC_NAND_DEVICE; + nand_handle.Init.NandBank = FSMC_NAND_BANK3; + nand_handle.Init.Waitfeature = FSMC_NAND_PCC_WAIT_FEATURE_DISABLE; + nand_handle.Init.MemoryDataWidth = FSMC_NAND_PCC_MEM_BUS_WIDTH_8; + nand_handle.Init.EccComputation = FSMC_NAND_ECC_DISABLE; + nand_handle.Init.ECCPageSize = FSMC_NAND_ECC_PAGE_SIZE_2048BYTE; + nand_handle.Init.TCLRSetupTime = 2U; + nand_handle.Init.TARSetupTime = 2U; + + /* One FSMC tick is about 13.9 ns at HCLK=72 MHz. These deliberately + * relaxed values are suitable for first bring-up and signal probing. */ + common_timing.SetupTime = 2U; + common_timing.WaitSetupTime = 5U; + common_timing.HoldSetupTime = 3U; + common_timing.HiZSetupTime = 2U; + attribute_timing = common_timing; + + return HAL_NAND_Init(&nand_handle, &common_timing, &attribute_timing) == + HAL_OK + ? 0 + : -1; +} + +static void f103ze_select(void) +{ + /* A memory-window access asserts NCE2; no separate software GPIO exists. */ +} + +static void f103ze_deselect(void) +{ + /* NCE2 is released automatically when the FSMC access finishes. */ +} + +static void f103ze_write_command(pnand_octet_t value) +{ + *(__IO uint8_t *)(PNAND_F103ZE_DATA_ADDRESS | + PNAND_F103ZE_COMMAND_AREA) = (uint8_t)value; + __DSB(); /* Complete the external bus write before the next NAND cycle. */ +} + +static void f103ze_write_address(pnand_octet_t value) +{ + *(__IO uint8_t *)(PNAND_F103ZE_DATA_ADDRESS | + PNAND_F103ZE_ADDRESS_AREA) = (uint8_t)value; + __DSB(); +} + +static void f103ze_write_data(pnand_octet_t value) +{ + *(__IO uint8_t *)PNAND_F103ZE_DATA_ADDRESS = (uint8_t)value; + __DSB(); +} + +static pnand_octet_t f103ze_read_data(void) +{ + uint8_t value = *(__IO uint8_t *)PNAND_F103ZE_DATA_ADDRESS; + __DSB(); + return (pnand_octet_t)value; +} + +static int f103ze_is_ready(void) +{ + return HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_6) == GPIO_PIN_SET; +} + +static void f103ze_set_write_protect(int enabled) +{ + /* NAND WP# is active low: RESET protects, SET permits program/erase. */ + HAL_GPIO_WritePin(PNAND_F103ZE_WP_PORT, PNAND_F103ZE_WP_PIN, + enabled ? GPIO_PIN_RESET : GPIO_PIN_SET); +} + +static void f103ze_delay_us(unsigned long us) +{ + /* Approximate delay used only around reset/status polling. The factor + * accounts for loop instructions in addition to the NOP. */ + uint32_t cycles = (SystemCoreClock / 3000000UL) * (uint32_t)us; + while (cycles-- != 0U) { + __NOP(); + } +} + +static const parallel_nand_port_t f103ze_port = { + f103ze_init, + f103ze_select, + f103ze_deselect, + f103ze_write_command, + f103ze_write_address, + f103ze_write_data, + f103ze_read_data, + f103ze_is_ready, + f103ze_set_write_protect, + f103ze_delay_us, + 1 +}; + +const parallel_nand_port_t *parallel_nand_port_stm32f103ze(void) +{ + return &f103ze_port; +} diff --git a/c/parallel-nand/ports/stm32f103ze/parallel_nand_port_stm32f103ze.h b/c/parallel-nand/ports/stm32f103ze/parallel_nand_port_stm32f103ze.h new file mode 100644 index 0000000..d5483a4 --- /dev/null +++ b/c/parallel-nand/ports/stm32f103ze/parallel_nand_port_stm32f103ze.h @@ -0,0 +1,16 @@ +#ifndef PARALLEL_NAND_PORT_STM32F103ZE_H +#define PARALLEL_NAND_PORT_STM32F103ZE_H + +#include "parallel_nand_port.h" + +#ifdef __cplusplus +extern "C" { +#endif + +const parallel_nand_port_t *parallel_nand_port_stm32f103ze(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/c/parallel-nand/ports/stm32f407ve/README.md b/c/parallel-nand/ports/stm32f407ve/README.md index 0decb57..0ec2c78 100644 --- a/c/parallel-nand/ports/stm32f407ve/README.md +++ b/c/parallel-nand/ports/stm32f407ve/README.md @@ -24,3 +24,8 @@ `stm32f4xx_ll_fsmc.c`, включить FSMC и не создавать второй `NAND_HandleTypeDef` для Bank 2. Начальные тайминги консервативны; финальные значения подтвердить осциллографом. + +В базовой схеме `WP#` аппаратно зафиксирован в активном уровне, поэтому порт +объявляет `write_supported = 0`, а PROGRAM/ERASE возвращают +`PNAND_ERROR_WRITE_PROTECTED`. Для записи назначьте отдельный GPIO с внешним +pull-down, реализуйте управление `WP#` и включите признак поддержки порта. diff --git a/c/parallel-nand/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c b/c/parallel-nand/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c index 50290c2..d2fab45 100644 --- a/c/parallel-nand/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c +++ b/c/parallel-nand/ports/stm32f407ve/parallel_nand_port_stm32f407ve.c @@ -84,7 +84,8 @@ static void f407_delay_us(unsigned long us) static const parallel_nand_port_t f407_port = { f407_init, f407_select, f407_deselect, f407_write_command, f407_write_address, f407_write_data, - f407_read_data, f407_is_ready, f407_set_wp, f407_delay_us + f407_read_data, f407_is_ready, f407_set_wp, f407_delay_us, + 0 }; const parallel_nand_port_t *parallel_nand_port_stm32f407ve(void) diff --git a/c/parallel-nand/ports/stm32g474ce/README.md b/c/parallel-nand/ports/stm32g474ce/README.md index 24e07e6..8a938ad 100644 --- a/c/parallel-nand/ports/stm32g474ce/README.md +++ b/c/parallel-nand/ports/stm32g474ce/README.md @@ -19,3 +19,6 @@ HAL только для инициализации, а циклы NAND выпо Выбранные `PB3/PB4` нельзя одновременно использовать для trace/JTAG; SWD на `PA13/PA14` остаётся доступен. Если эти пины нужны проекту, измените таблицу и операции шины: текущая оптимизация рассчитана на непрерывные `PB0..PB7`. + +Порт поддерживает PROGRAM/ERASE через управляемый `WP#` на `PA5`. Внешний +pull-down сохраняет защиту до инициализации GPIO и при сбросе МК. diff --git a/c/parallel-nand/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c b/c/parallel-nand/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c index 3f18b64..b9819b6 100644 --- a/c/parallel-nand/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c +++ b/c/parallel-nand/ports/stm32g474ce/parallel_nand_port_stm32g474ce.c @@ -125,7 +125,8 @@ static void g474_delay_us(unsigned long us) static const parallel_nand_port_t g474_port = { g474_init, g474_select, g474_deselect, g474_write_command, g474_write_address, g474_write_data, - g474_read_data, g474_is_ready, g474_set_wp, g474_delay_us + g474_read_data, g474_is_ready, g474_set_wp, g474_delay_us, + 1 }; const parallel_nand_port_t *parallel_nand_port_stm32g474ce(void) diff --git a/c/parallel-nand/ports/tms320f2812/README.md b/c/parallel-nand/ports/tms320f2812/README.md index 7a33632..db1dfd1 100644 --- a/c/parallel-nand/ports/tms320f2812/README.md +++ b/c/parallel-nand/ports/tms320f2812/README.md @@ -13,9 +13,10 @@ Адреса C28x задаются в 16-битных словах: data `0x100000`, command `0x100001`, address `0x100002`. Используется только младший байт XINTF. -`WP#` в базовом варианте аппаратный: для режима только чтения соединить с GND -через 10 кОм; если позже понадобится PROGRAM/ERASE, заменить на GPIO и реализовать -`tms_set_wp()`. +`WP#` в базовом варианте аппаратный и соединён с GND через 10 кОм. Поэтому +порт объявляет `write_supported = 0`, а PROGRAM/ERASE возвращают +`PNAND_ERROR_WRITE_PROTECTED`. Для записи нужно перенести `WP#` на GPIO, +реализовать `tms_set_wp()` и установить признак поддержки в структуре порта. Задержка в порте приблизительная. После выбора частоты CPU проверить импульсы осциллографом и при необходимости изменить `PNAND_TMS_CPU_MHZ`. diff --git a/c/parallel-nand/ports/tms320f2812/parallel_nand_port_tms320f2812.c b/c/parallel-nand/ports/tms320f2812/parallel_nand_port_tms320f2812.c index b1dda6d..d82f325 100644 --- a/c/parallel-nand/ports/tms320f2812/parallel_nand_port_tms320f2812.c +++ b/c/parallel-nand/ports/tms320f2812/parallel_nand_port_tms320f2812.c @@ -56,7 +56,8 @@ static void tms_set_wp(int enabled) static const parallel_nand_port_t tms_port = { tms_init, tms_select, tms_deselect, tms_write_command, tms_write_address, tms_write_data, - tms_read_data, tms_is_ready, tms_set_wp, tms_delay_us + tms_read_data, tms_is_ready, tms_set_wp, tms_delay_us, + 0 }; const parallel_nand_port_t *parallel_nand_port_tms320f2812(void) diff --git a/c/parallel-nand/src/parallel_nand.c b/c/parallel-nand/src/parallel_nand.c index 62158c4..90d28b1 100644 --- a/c/parallel-nand/src/parallel_nand.c +++ b/c/parallel-nand/src/parallel_nand.c @@ -4,8 +4,15 @@ #define PNAND_CMD_READ_2 0x30U #define PNAND_CMD_READ_ID 0x90U #define PNAND_CMD_READ_STATUS 0x70U +#define PNAND_CMD_PROGRAM_1 0x80U +#define PNAND_CMD_PROGRAM_2 0x10U +#define PNAND_CMD_ERASE_1 0x60U +#define PNAND_CMD_ERASE_2 0xD0U #define PNAND_CMD_RESET 0xFFU +#define PNAND_STATUS_FAIL 0x01U +#define PNAND_STATUS_WP 0x80U + const parallel_nand_geometry_t parallel_nand_mt29f1g08_geometry = { PNAND_MT29F1G08_PAGE_MAIN, PNAND_MT29F1G08_PAGE_OOB, @@ -16,6 +23,16 @@ const parallel_nand_geometry_t parallel_nand_mt29f1g08_geometry = { 1000000UL }; +const parallel_nand_geometry_t parallel_nand_hy27uf084g2m_geometry = { + PNAND_HY27UF084G2M_PAGE_MAIN, + PNAND_HY27UF084G2M_PAGE_OOB, + PNAND_HY27UF084G2M_PAGES_PER_BLOCK, + PNAND_HY27UF084G2M_BLOCKS, + PNAND_HY27UF084G2M_COLUMN_CYCLES, + PNAND_HY27UF084G2M_ROW_CYCLES, + 1000000UL +}; + static int port_valid(const parallel_nand_port_t *port) { return port != 0 && port->init != 0 && port->select != 0 && @@ -57,6 +74,8 @@ int parallel_nand_reset(parallel_nand_t *nand) nand->port->select(); nand->port->write_command(PNAND_CMD_RESET); nand->port->deselect(); + /* R/B# may not fall until tWB after the command cycle. */ + nand->port->delay_us(1UL); return wait_ready(nand); } @@ -70,7 +89,7 @@ int parallel_nand_init(parallel_nand_t *nand, nand->geometry = *geometry; if (port->init() != 0) return PNAND_ERROR_PORT; port->deselect(); - /* Ядро пока только читает: аппаратно запрещаем PROGRAM/ERASE. */ + /* WP# stays asserted except for the shortest possible mutation window. */ port->set_write_protect(1); return parallel_nand_reset(nand); } @@ -122,6 +141,7 @@ static int begin_page_read(parallel_nand_t *nand, unsigned long page, } nand->port->write_command(PNAND_CMD_READ_2); nand->port->deselect(); + nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */ return wait_ready(nand); } @@ -167,6 +187,101 @@ int parallel_nand_read_page(parallel_nand_t *nand, unsigned long page, return PNAND_OK; } +static void write_address_cycles(parallel_nand_t *nand, + unsigned long value, + unsigned int count) +{ + unsigned int i; + for (i = 0U; i < count; i++) { + nand->port->write_address((pnand_octet_t)(value & 0xFFUL)); + value >>= 8; + } +} + +static int mutation_supported(const parallel_nand_t *nand) +{ + return nand != 0 && port_valid(nand->port) && + nand->port->write_data != 0 && + nand->port->write_supported != 0; +} + +static int finish_mutation(parallel_nand_t *nand, int failure_result) +{ + pnand_octet_t status = 0U; + int result = wait_ready(nand); + if (result == PNAND_OK) + result = parallel_nand_read_status(nand, &status); + nand->port->set_write_protect(1); + if (result != PNAND_OK) return result; + if ((status & PNAND_STATUS_WP) == 0U) + return PNAND_ERROR_WRITE_PROTECTED; + return (status & PNAND_STATUS_FAIL) != 0U ? failure_result : PNAND_OK; +} + +int parallel_nand_program_page(parallel_nand_t *nand, unsigned long page, + const pnand_octet_t *main_data, + unsigned long main_count, + const pnand_octet_t *oob, + unsigned long oob_count) +{ + unsigned long i; + unsigned long block; + int bad = 0; + int result; + if (nand == 0 || main_data == 0) return PNAND_ERROR_ARGUMENT; + if (!mutation_supported(nand)) return PNAND_ERROR_WRITE_PROTECTED; + if (page >= parallel_nand_page_count(nand)) return PNAND_ERROR_RANGE; + if (main_count != nand->geometry.page_main_size) + return PNAND_ERROR_BUFFER; + if ((oob == 0 && oob_count != 0UL) || + (oob != 0 && oob_count != nand->geometry.page_oob_size)) + return PNAND_ERROR_BUFFER; + + block = page / nand->geometry.pages_per_block; + result = parallel_nand_is_bad_block(nand, block, &bad); + if (result != PNAND_OK) return result; + if (bad) return PNAND_ERROR_BAD_BLOCK; + + nand->port->set_write_protect(0); + nand->port->delay_us(1UL); + nand->port->select(); + nand->port->write_command(PNAND_CMD_PROGRAM_1); + write_address_cycles(nand, 0UL, nand->geometry.column_cycles); + write_address_cycles(nand, page, nand->geometry.row_cycles); + for (i = 0UL; i < main_count; i++) + nand->port->write_data(main_data[i] & 0xFFU); + for (i = 0UL; i < oob_count; i++) + nand->port->write_data(oob[i] & 0xFFU); + nand->port->write_command(PNAND_CMD_PROGRAM_2); + nand->port->deselect(); + nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */ + return finish_mutation(nand, PNAND_ERROR_PROGRAM_FAILED); +} + +int parallel_nand_erase_block(parallel_nand_t *nand, unsigned long block) +{ + unsigned long first_page; + int bad = 0; + int result; + if (nand == 0) return PNAND_ERROR_ARGUMENT; + if (!mutation_supported(nand)) return PNAND_ERROR_WRITE_PROTECTED; + if (block >= nand->geometry.block_count) return PNAND_ERROR_RANGE; + result = parallel_nand_is_bad_block(nand, block, &bad); + if (result != PNAND_OK) return result; + if (bad) return PNAND_ERROR_BAD_BLOCK; + + first_page = block * nand->geometry.pages_per_block; + nand->port->set_write_protect(0); + nand->port->delay_us(1UL); + nand->port->select(); + nand->port->write_command(PNAND_CMD_ERASE_1); + write_address_cycles(nand, first_page, nand->geometry.row_cycles); + nand->port->write_command(PNAND_CMD_ERASE_2); + nand->port->deselect(); + nand->port->delay_us(1UL); /* Cover tWB before polling R/B#. */ + return finish_mutation(nand, PNAND_ERROR_ERASE_FAILED); +} + int parallel_nand_is_bad_block(parallel_nand_t *nand, unsigned long block, int *is_bad) { diff --git a/c/parallel-nand/src/parallel_nand_gas.c b/c/parallel-nand/src/parallel_nand_gas.c index 461374b..996d24d 100644 --- a/c/parallel-nand/src/parallel_nand_gas.c +++ b/c/parallel-nand/src/parallel_nand_gas.c @@ -6,6 +6,20 @@ static unsigned int pack_pair(const pnand_octet_t *data, unsigned int index) ((data[index * 2U + 1U] & 0xFFU) << 8)); } +static void unpack_pair(pnand_octet_t *data, unsigned int index, + unsigned int value) +{ + data[index * 2U] = (pnand_octet_t)(value & 0xFFU); + data[index * 2U + 1U] = (pnand_octet_t)((value >> 8) & 0xFFU); +} + +static unsigned int persistent_status(const parallel_nand_gas_t *gas) +{ + return gas->nand->port->write_supported != 0 + ? PNAND_GAS_STATUS_WRITE_SUPPORTED + : 0U; +} + parallel_nand_gas_result_t parallel_nand_gas_read( parallel_nand_gas_t *gas, unsigned int address, unsigned int *value) { @@ -13,11 +27,16 @@ parallel_nand_gas_result_t parallel_nand_gas_read( const parallel_nand_geometry_t *g; if (gas == 0 || value == 0 || address < gas->base) return PNAND_GAS_REJECTED; + /* Confirmation authorizes exactly the immediately following write. */ + gas->destructive_armed = 0; g = &gas->nand->geometry; offset = address - gas->base; if (offset >= PNAND_GAS_REGION_REGS) return PNAND_GAS_NO_REG; if (offset >= PNAND_GAS_DATA_OFFSET && offset < PNAND_GAS_DATA_OFFSET + PNAND_GAS_DATA_REGS) { + if ((unsigned long)(offset - PNAND_GAS_DATA_OFFSET) * 2UL >= + g->page_main_size) + return PNAND_GAS_NO_REG; if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U) return PNAND_GAS_REJECTED; *value = pack_pair(gas->main_data, @@ -26,6 +45,9 @@ parallel_nand_gas_result_t parallel_nand_gas_read( } if (offset >= PNAND_GAS_OOB_OFFSET && offset < PNAND_GAS_OOB_OFFSET + PNAND_GAS_OOB_REGS) { + if ((unsigned long)(offset - PNAND_GAS_OOB_OFFSET) * 2UL >= + g->page_oob_size) + return PNAND_GAS_NO_REG; if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U) return PNAND_GAS_REJECTED; *value = pack_pair(gas->oob, @@ -34,7 +56,7 @@ parallel_nand_gas_result_t parallel_nand_gas_read( } switch (offset) { case PNAND_GAS_REG_SIGNATURE: *value = 0x4E44U; break; /* ND */ - case PNAND_GAS_REG_VERSION: *value = 0x0001U; break; + case PNAND_GAS_REG_VERSION: *value = 0x0002U; break; case PNAND_GAS_REG_STATUS: *value = gas->status; break; case PNAND_GAS_REG_ERROR: *value = (unsigned int)gas->error & 0xFFFFU; break; case PNAND_GAS_REG_COMMAND: *value = PNAND_GAS_CMD_NONE; break; @@ -50,6 +72,7 @@ parallel_nand_gas_result_t parallel_nand_gas_read( *value = (gas->status & PNAND_GAS_STATUS_BAD_BLOCK) != 0U; break; case PNAND_GAS_REG_GENERATION_LO: *value = (unsigned int)gas->generation & 0xFFFFU; break; case PNAND_GAS_REG_GENERATION_HI: *value = (unsigned int)(gas->generation >> 16) & 0xFFFFU; break; + case PNAND_GAS_REG_CONFIRM: *value = 0U; break; default: return PNAND_GAS_NO_REG; } *value &= 0xFFFFU; @@ -60,7 +83,7 @@ static void finish(parallel_nand_gas_t *gas, int result) { gas->error = result; gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_BUSY); - gas->status |= PNAND_GAS_STATUS_READY; + gas->status |= PNAND_GAS_STATUS_READY | persistent_status(gas); if (result != PNAND_OK) gas->status |= PNAND_GAS_STATUS_ERROR; } @@ -69,7 +92,11 @@ static parallel_nand_gas_result_t run_command(parallel_nand_gas_t *gas, { int result = PNAND_OK; int bad = 0; - gas->status = PNAND_GAS_STATUS_BUSY; + int armed = gas->destructive_armed; + unsigned int i; + unsigned int previous_status = gas->status; + gas->destructive_armed = 0; + gas->status = PNAND_GAS_STATUS_BUSY | persistent_status(gas); if (command == PNAND_GAS_CMD_RESET) { result = parallel_nand_reset(gas->nand); } else if (command == PNAND_GAS_CMD_READ_ID) { @@ -101,8 +128,59 @@ static parallel_nand_gas_result_t run_command(parallel_nand_gas_t *gas, if (result == PNAND_OK && bad) gas->status |= PNAND_GAS_STATUS_BAD_BLOCK; } + } else if (command == PNAND_GAS_CMD_CLEAR_BUFFER) { + for (i = 0U; i < PNAND_MT29F1G08_PAGE_MAIN; i++) + gas->main_data[i] = 0xFFU; + for (i = 0U; i < PNAND_MT29F1G08_PAGE_OOB; i++) + gas->oob[i] = 0xFFU; + gas->status |= PNAND_GAS_STATUS_PAGE_VALID | + PNAND_GAS_STATUS_BUFFER_DIRTY; + gas->generation++; + } else if (command == PNAND_GAS_CMD_PROGRAM_PAGE) { + if (!armed || + (previous_status & PNAND_GAS_STATUS_PAGE_VALID) == 0U) { + gas->status |= previous_status & + (PNAND_GAS_STATUS_PAGE_VALID | + PNAND_GAS_STATUS_BUFFER_DIRTY | + PNAND_GAS_STATUS_BAD_BLOCK); + finish(gas, PNAND_ERROR_CONFIRMATION); + return PNAND_GAS_REJECTED; + } + result = parallel_nand_program_page( + gas->nand, gas->selected_page, + gas->main_data, gas->nand->geometry.page_main_size, + gas->oob, gas->nand->geometry.page_oob_size); + gas->status |= PNAND_GAS_STATUS_PAGE_VALID; + if (result == PNAND_OK) { + gas->generation++; + gas->block_cache_valid = 0; + } else { + gas->status |= PNAND_GAS_STATUS_BUFFER_DIRTY; + } + } else if (command == PNAND_GAS_CMD_ERASE_BLOCK) { + unsigned long block; + if (!armed || + gas->selected_page >= parallel_nand_page_count(gas->nand)) { + finish(gas, !armed ? PNAND_ERROR_CONFIRMATION : + PNAND_ERROR_RANGE); + return PNAND_GAS_REJECTED; + } + block = gas->selected_page / gas->nand->geometry.pages_per_block; + result = parallel_nand_erase_block(gas->nand, block); + if (result == PNAND_OK) { + gas->status &= (0xFFFFU ^ (PNAND_GAS_STATUS_PAGE_VALID | + PNAND_GAS_STATUS_BUFFER_DIRTY | + PNAND_GAS_STATUS_BAD_BLOCK)); + gas->generation++; + gas->block_cache_valid = 0; + } else { + gas->status |= previous_status & + (PNAND_GAS_STATUS_PAGE_VALID | + PNAND_GAS_STATUS_BUFFER_DIRTY | + PNAND_GAS_STATUS_BAD_BLOCK); + } } else { - gas->status = PNAND_GAS_STATUS_READY; + gas->status = PNAND_GAS_STATUS_READY | persistent_status(gas); return PNAND_GAS_REJECTED; } finish(gas, result); @@ -119,18 +197,47 @@ parallel_nand_gas_result_t parallel_nand_gas_write( if ((gas->status & PNAND_GAS_STATUS_BUSY) != 0U) return PNAND_GAS_REJECTED; value &= 0xFFFFU; + if (offset >= PNAND_GAS_DATA_OFFSET && + offset < PNAND_GAS_DATA_OFFSET + PNAND_GAS_DATA_REGS) { + unsigned int index = offset - PNAND_GAS_DATA_OFFSET; + if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U || + (unsigned long)index * 2UL >= gas->nand->geometry.page_main_size) + return PNAND_GAS_REJECTED; + gas->destructive_armed = 0; + unpack_pair(gas->main_data, index, value); + gas->status |= PNAND_GAS_STATUS_BUFFER_DIRTY; + return PNAND_GAS_OK; + } + if (offset >= PNAND_GAS_OOB_OFFSET && + offset < PNAND_GAS_OOB_OFFSET + PNAND_GAS_OOB_REGS) { + unsigned int index = offset - PNAND_GAS_OOB_OFFSET; + if ((gas->status & PNAND_GAS_STATUS_PAGE_VALID) == 0U || + (unsigned long)index * 2UL >= gas->nand->geometry.page_oob_size) + return PNAND_GAS_REJECTED; + gas->destructive_armed = 0; + unpack_pair(gas->oob, index, value); + gas->status |= PNAND_GAS_STATUS_BUFFER_DIRTY; + return PNAND_GAS_OK; + } switch (offset) { case PNAND_GAS_REG_COMMAND: return run_command(gas, value); + case PNAND_GAS_REG_CONFIRM: + gas->destructive_armed = value == PNAND_GAS_CONFIRM_MAGIC; + return gas->destructive_armed ? PNAND_GAS_OK : PNAND_GAS_REJECTED; case PNAND_GAS_REG_PAGE_LO: + gas->destructive_armed = 0; gas->selected_page = (gas->selected_page & 0xFFFF0000UL) | value; gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID); return PNAND_GAS_OK; case PNAND_GAS_REG_PAGE_HI: + gas->destructive_armed = 0; gas->selected_page = (gas->selected_page & 0x0000FFFFUL) | ((unsigned long)value << 16); gas->status &= (0xFFFFU ^ PNAND_GAS_STATUS_PAGE_VALID); return PNAND_GAS_OK; - default: return PNAND_GAS_READ_ONLY; + default: + gas->destructive_armed = 0; + return PNAND_GAS_READ_ONLY; } } @@ -140,12 +247,17 @@ void parallel_nand_gas_init(parallel_nand_gas_t *gas, unsigned int i; gas->nand = nand; gas->base = base; - gas->status = PNAND_GAS_STATUS_READY; + gas->status = PNAND_GAS_STATUS_READY | persistent_status(gas); gas->error = PNAND_OK; gas->selected_page = 0UL; gas->generation = 0UL; gas->cached_block = 0UL; gas->cached_bad_block = 0; gas->block_cache_valid = 0; + gas->destructive_armed = 0; for (i = 0U; i < 8U; i++) gas->id[i] = 0U; + for (i = 0U; i < PNAND_MT29F1G08_PAGE_MAIN; i++) + gas->main_data[i] = 0xFFU; + for (i = 0U; i < PNAND_MT29F1G08_PAGE_OOB; i++) + gas->oob[i] = 0xFFU; } diff --git a/c/parallel-nand/tests/test_parallel_nand.c b/c/parallel-nand/tests/test_parallel_nand.c index 572f129..82dc797 100644 --- a/c/parallel-nand/tests/test_parallel_nand.c +++ b/c/parallel-nand/tests/test_parallel_nand.c @@ -5,7 +5,7 @@ #include "parallel_nand_gas.h" typedef struct { char kind; unsigned int value; } event_t; -static event_t events[64]; +static event_t events[256]; static unsigned int event_count; static pnand_octet_t input_data[32]; static unsigned int input_count; @@ -33,17 +33,35 @@ static void fake_delay(unsigned long us) { (void)us; } static const parallel_nand_port_t fake_port = { fake_init, fake_select, fake_deselect, fake_cmd, fake_addr, fake_write, - fake_read, fake_ready, fake_wp, fake_delay + fake_read, fake_ready, fake_wp, fake_delay, 1 }; static void clear_events(void) { event_count = 0U; } +static unsigned int find_event(unsigned int start, char kind, + unsigned int value) +{ + unsigned int i; + for (i = start; i < event_count; i++) { + if (events[i].kind == kind && events[i].value == value) return i; + } + return event_count; +} + int main(void) { parallel_nand_t nand; pnand_octet_t id[2]; pnand_octet_t bytes[2]; + pnand_octet_t main_data[2] = {0xAAU, 0x55U}; + pnand_octet_t oob[2] = {0xFFU, 0xFEU}; parallel_nand_gas_t gas; + parallel_nand_geometry_t small_geometry = { + 2UL, 2UL, 2UL, 4UL, 2U, 2U, 100UL + }; + parallel_nand_port_t read_only_port; + parallel_nand_t read_only_nand; + unsigned int pos; unsigned int reg; assert(parallel_nand_init(&nand, &fake_port, @@ -76,9 +94,116 @@ int main(void) assert(events[5].kind == 'A' && events[5].value == 0x12U); assert(events[6].kind == 'C' && events[6].value == 0x30U); + assert(parallel_nand_init(&nand, &fake_port, + ¶llel_nand_hy27uf084g2m_geometry) == PNAND_OK); + assert(parallel_nand_page_count(&nand) == 262144UL); + assert(PNAND_HY27UF084G2M_MANUFACTURER_ID == 0xADU); + assert(PNAND_HY27UF084G2M_DEVICE_ID == 0xDCU); + + clear_events(); + input_data[0] = 0xABU; + input_count = 1U; + input_pos = 0U; + assert(parallel_nand_read_page_slice(&nand, 0x23456UL, 0x78UL, + bytes, 1UL) == PNAND_OK); + assert(bytes[0] == 0xABU); + assert(events[2].kind == 'A' && events[2].value == 0x78U); + assert(events[3].kind == 'A' && events[3].value == 0x00U); + assert(events[4].kind == 'A' && events[4].value == 0x56U); + assert(events[5].kind == 'A' && events[5].value == 0x34U); + assert(events[6].kind == 'A' && events[6].value == 0x02U); + assert(events[7].kind == 'C' && events[7].value == 0x30U); + + assert(parallel_nand_init(&nand, &fake_port, &small_geometry) == + PNAND_OK); + clear_events(); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x80U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_program_page(&nand, 3UL, main_data, 2UL, + oob, 2UL) == PNAND_OK); + pos = find_event(0U, 'P', 0U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'C', 0x80U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'W', 0xAAU); + assert(pos < event_count); + pos = find_event(pos + 1U, 'W', 0x55U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'W', 0xFFU); + assert(pos < event_count); + pos = find_event(pos + 1U, 'W', 0xFEU); + assert(pos < event_count); + pos = find_event(pos + 1U, 'C', 0x10U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'C', 0x70U); + assert(pos < event_count); + assert(events[event_count - 1U].kind == 'P' && + events[event_count - 1U].value == 1U); + + clear_events(); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x80U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_erase_block(&nand, 2UL) == PNAND_OK); + pos = find_event(0U, 'P', 0U); + pos = find_event(pos + 1U, 'C', 0x60U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'A', 0x04U); + assert(pos < event_count); + pos = find_event(pos + 1U, 'C', 0xD0U); + assert(pos < event_count); + assert(events[event_count - 1U].kind == 'P' && + events[event_count - 1U].value == 1U); + + clear_events(); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x81U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_program_page(&nand, 0UL, main_data, 2UL, + 0, 0UL) == + PNAND_ERROR_PROGRAM_FAILED); + assert(events[event_count - 1U].kind == 'P' && + events[event_count - 1U].value == 1U); + + clear_events(); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x00U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_program_page(&nand, 0UL, main_data, 2UL, + 0, 0UL) == + PNAND_ERROR_WRITE_PROTECTED); + assert(events[event_count - 1U].kind == 'P' && + events[event_count - 1U].value == 1U); + + clear_events(); + input_data[0] = 0x00U; + input_count = 1U; + input_pos = 0U; + assert(parallel_nand_erase_block(&nand, 0UL) == + PNAND_ERROR_BAD_BLOCK); + assert(find_event(0U, 'P', 0U) == event_count); + + read_only_port = fake_port; + read_only_port.write_supported = 0; + assert(parallel_nand_init(&read_only_nand, &read_only_port, + &small_geometry) == PNAND_OK); + assert(parallel_nand_erase_block(&read_only_nand, 0UL) == + PNAND_ERROR_WRITE_PROTECTED); + parallel_nand_gas_init(&gas, &nand, PNAND_GAS_DEFAULT_BASE); assert(parallel_nand_gas_read(&gas, 0xE000U, ®) == PNAND_GAS_OK); assert(reg == 0x4E44U); + assert(parallel_nand_gas_read(&gas, 0xE001U, ®) == PNAND_GAS_OK); + assert(reg == 0x0002U); assert(parallel_nand_gas_write(&gas, 0xE005U, 0x1234U) == PNAND_GAS_OK); assert(parallel_nand_gas_write(&gas, 0xE006U, 0x0001U) == @@ -92,6 +217,56 @@ int main(void) assert(parallel_nand_gas_write(&gas, 0xE000U, 0U) == PNAND_GAS_READ_ONLY); + assert((gas.status & PNAND_GAS_STATUS_WRITE_SUPPORTED) != 0U); + assert(parallel_nand_gas_write(&gas, 0xE005U, 0U) == PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE006U, 0U) == PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_CLEAR_BUFFER) == + PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE100U, 0x1234U) == + PNAND_GAS_OK); + assert(gas.main_data[0] == 0x34U && gas.main_data[1] == 0x12U); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_PROGRAM_PAGE) == + PNAND_GAS_REJECTED); + assert(gas.error == PNAND_ERROR_CONFIRMATION); + + assert(parallel_nand_gas_write(&gas, 0xE010U, + PNAND_GAS_CONFIRM_MAGIC) == + PNAND_GAS_OK); + assert(parallel_nand_gas_read(&gas, 0xE002U, ®) == PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_PROGRAM_PAGE) == + PNAND_GAS_REJECTED); + assert(gas.error == PNAND_ERROR_CONFIRMATION); + + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x80U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_gas_write(&gas, 0xE010U, + PNAND_GAS_CONFIRM_MAGIC) == + PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_PROGRAM_PAGE) == + PNAND_GAS_OK); + assert((gas.status & PNAND_GAS_STATUS_BUFFER_DIRTY) == 0U); + + assert(parallel_nand_gas_write(&gas, 0xE005U, 2U) == PNAND_GAS_OK); + input_data[0] = 0xFFU; + input_data[1] = 0xFFU; + input_data[2] = 0x80U; + input_count = 3U; + input_pos = 0U; + assert(parallel_nand_gas_write(&gas, 0xE010U, + PNAND_GAS_CONFIRM_MAGIC) == + PNAND_GAS_OK); + assert(parallel_nand_gas_write(&gas, 0xE004U, + PNAND_GAS_CMD_ERASE_BLOCK) == + PNAND_GAS_OK); + assert((gas.status & PNAND_GAS_STATUS_PAGE_VALID) == 0U); + puts("parallel_nand: OK"); return 0; }