diff --git a/README.md b/README.md index ebc4a27..b2b585e 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,15 @@ # 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 недоступен; - `STM32G474CEU6` — GPIO bit-bang для корпуса UFQFPN-48. Ядро не зависит от HAL и транспорта GUI. STM32 HAL используется только в @@ -55,6 +57,11 @@ void memory_init(void) } ``` +Для Hynix на STM32F103RCT6 используйте +`parallel_nand_port_stm32f103rc()` и +`parallel_nand_hy27uf084g2m_geometry`; распиновка и ограничения приведены в +[`ports/stm32f103rc/README.md`](ports/stm32f103rc/README.md). + Диспетчер общего адресного пространства вызывает `parallel_nand_gas_read()`/`parallel_nand_gas_write()` для адресов `0xE000..0xE51F`. Для STM32 с `c/set-protocol/pcan_gas` дополнительно включить diff --git a/include/parallel_nand.h b/include/parallel_nand.h index 8773ede..7b6382d 100644 --- a/include/parallel_nand.h +++ b/include/parallel_nand.h @@ -16,6 +16,19 @@ 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, @@ -41,6 +54,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, diff --git a/ports/stm32f103rc/README.md b/ports/stm32f103rc/README.md new file mode 100644 index 0000000..aaba927 --- /dev/null +++ b/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`. Ядро библиотеки работает только на чтение и удерживает `WP#` +в активном низком уровне; PROGRAM/ERASE пока намеренно не реализованы. diff --git a/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c b/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c new file mode 100644 index 0000000..1a4d8f4 --- /dev/null +++ b/ports/stm32f103rc/parallel_nand_port_stm32f103rc.c @@ -0,0 +1,142 @@ +#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 +}; + +const parallel_nand_port_t *parallel_nand_port_stm32f103rc(void) +{ + return &f103rc_port; +} diff --git a/ports/stm32f103rc/parallel_nand_port_stm32f103rc.h b/ports/stm32f103rc/parallel_nand_port_stm32f103rc.h new file mode 100644 index 0000000..7d76f27 --- /dev/null +++ b/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/src/parallel_nand.c b/src/parallel_nand.c index 62158c4..bd182a9 100644 --- a/src/parallel_nand.c +++ b/src/parallel_nand.c @@ -16,6 +16,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 && diff --git a/tests/test_parallel_nand.c b/tests/test_parallel_nand.c index 572f129..d2ae00a 100644 --- a/tests/test_parallel_nand.c +++ b/tests/test_parallel_nand.c @@ -76,6 +76,26 @@ 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); + parallel_nand_gas_init(&gas, &nand, PNAND_GAS_DEFAULT_BASE); assert(parallel_nand_gas_read(&gas, 0xE000U, ®) == PNAND_GAS_OK); assert(reg == 0x4E44U);