diff --git a/c/eeprom-ft24c256/README.md b/c/eeprom-ft24c256/README.md new file mode 100644 index 0000000..61e739d --- /dev/null +++ b/c/eeprom-ft24c256/README.md @@ -0,0 +1,65 @@ +# eeprom-ft24c256 + +Драйвер последовательной EEPROM **FT24C256** и совместимого семейства +24Cxx по шине I²C. + +Ядро на C99: не включает заголовки периферии, не обращается к регистрам, +не пользуется прерываниями. Обмен идёт через таблицу `FT24C256_Io`, +которую заполняет порт платы. + +``` + приложение + │ + ft24c256.c нарезка записи по страницам, ожидание цикла записи, + двухбайтовый адрес слова, счётчики ошибок + │ + FT24C256_Io write, write_read, delay_ms, set_write_protect + │ + порт платы I2C +``` + +## Состав + +| Файл | Что делает | Зависимости | +|---|---|---| +| `ft24c256.h`, `ft24c256.c` | чтение и запись произвольных блоков, стирание, защита записи, опрос готовности | `stdint.h` | + +FT24C256 — 32768 байт (256 кбит), адрес слова двухбайтовый, страница записи +64 байта. Микросхема принимает до 64 байт за транзакцию, но только внутри +страницы: при переходе через границу счётчик адреса заворачивается на начало +той же страницы и затирает уже принятые байты. Поэтому `FT24C256_Write()` +сама режет запрос по границам страниц — вызывающему коду об этом думать не надо. + +## Что нужно от платформы + +```c +uint8_t write(void *ctx, uint8_t addr, const uint8_t *data, uint16_t size); +uint8_t write_read(void *ctx, uint8_t addr, const uint8_t *tx, uint16_t tx_size, + uint8_t *rx, uint16_t rx_size); /* с повторным стартом */ +void delay_ms(void *ctx, uint32_t ms); +``` + +Адрес передаётся семибитным, без бита направления — сдвиг делает порт. +`set_write_protect` необязателен: если вывод WP не разведён, поле оставляется нулём. + +## Быстрый старт + +```c +FT24C256 eeprom; +FT24C256_Io io = { .write = i2c_write, .write_read = i2c_write_read, + .delay_ms = board_delay, .context = &board }; +FT24C256_Config config; +FT24C256_ConfigDefault(&config); /* 32 КБ, страница 64, адрес 0x50 */ + +FT24C256_Init(&eeprom, &io, &config); + +uint8_t settings[100]; +FT24C256_Write(&eeprom, 0x0030, settings, sizeof settings); +FT24C256_Read(&eeprom, 0x0030, settings, sizeof settings); +``` + +До восьми кристаллов на шине: адрес `0x50 + index`, `FT24C256_AddressOf()`. + +## Проверено в проектах + +`KONOR_ds18b20`, `OpticalTester` — I2C1 на STM32F103C8T6. diff --git a/c/eeprom-ft24c256/ft24c256.c b/c/eeprom-ft24c256/ft24c256.c new file mode 100644 index 0000000..b02ab7b --- /dev/null +++ b/c/eeprom-ft24c256/ft24c256.c @@ -0,0 +1,378 @@ +/** + * @file ft24c256.c + * @brief Постраничная запись, сплошное чтение и ожидание готовности FT24C256. + * + * Реализация не хранит буфера всего массива: единственная крупная переменная — + * кадр одной транзакции записи на стеке, длиной адрес слова плюс страница + * (@ref FT24C256_ADDRESS_SIZE + @ref FT24C256_PAGE_MAX байт). Времени + * библиотека не измеряет: выдержку цикла записи даёт порт, а готовность + * определяется откликом самой микросхемы. + */ + +#include "ft24c256.h" + +/** Кадр записи: адрес слова и страница данных. */ +#define FT24C256_FRAME_MAX (FT24C256_ADDRESS_SIZE + FT24C256_PAGE_MAX) + +/** Длина куска, которым FT24C256_Update() сверяет содержимое. */ +#define FT24C256_COMPARE_MAX 32U + +/** + * @brief Проверяет пригодность состояния к обмену. + * + * @param device Состояние кристалла. + * @return 1, если состояние настроено и обратные вызовы на месте, иначе 0. + */ +static uint8_t ft24c256_valid(const FT24C256 *device) +{ + if (device == 0) { + return 0U; + } + if (device->ready == 0U) { + return 0U; + } + return (uint8_t)((device->io.write != 0) && (device->io.write_read != 0)); +} + +/** + * @brief Проверяет, что область целиком помещается в массив. + * + * @param device Состояние кристалла. + * @param address Адрес первого байта. + * @param size Число байтов. + * @return 1, если область допустима, иначе 0. + */ +static uint8_t ft24c256_in_range(const FT24C256 *device, uint32_t address, + uint32_t size) +{ + if (address >= device->config.size) { + return 0U; + } + return (uint8_t)(size <= (device->config.size - address)); +} + +/** + * @brief Раскладывает адрес слова в два байта, старший первым. + * + * @param address Адрес байта в массиве. + * @param frame Приёмник кадра длиной не менее @ref FT24C256_ADDRESS_SIZE. + */ +static void ft24c256_put_address(uint32_t address, uint8_t *frame) +{ + frame[0] = (uint8_t)((address >> 8U) & 0xFFU); + frame[1] = (uint8_t)(address & 0xFFU); +} + +/** + * @brief Выдерживает паузу, если порт умеет её отсчитывать. + * + * @param device Состояние кристалла. + * @param milliseconds Длительность паузы. + */ +static void ft24c256_delay(FT24C256 *device, uint32_t milliseconds) +{ + if (device->io.delay_ms != 0) { + device->io.delay_ms(device->io.context, milliseconds); + } +} + +/** + * @brief Записывает часть страницы одной транзакцией и ждёт готовности. + * + * @param device Состояние кристалла. + * @param address Адрес первого байта; не пересекает границу страницы. + * @param data Записываемые данные. + * @param size Число байтов, не больше размера страницы. + * @return 1 при успехе, иначе 0. + */ +static uint8_t ft24c256_write_page(FT24C256 *device, uint32_t address, + const uint8_t *data, uint16_t size) +{ + uint8_t frame[FT24C256_FRAME_MAX]; + uint16_t index; + + if (size > FT24C256_PAGE_MAX) { + return 0U; + } + ft24c256_put_address(address, frame); + for (index = 0U; index < size; index++) { + frame[FT24C256_ADDRESS_SIZE + index] = data[index]; + } + if (device->io.write(device->io.context, device->config.address, frame, + (uint16_t)(FT24C256_ADDRESS_SIZE + size)) == 0U) { + device->write_errors++; + return 0U; + } + device->written_bytes += size; + return FT24C256_WaitReady(device); +} + +void FT24C256_ConfigDefault(FT24C256_Config *config) +{ + if (config == 0) { + return; + } + config->size = FT24C256_SIZE; + config->page_size = FT24C256_PAGE_SIZE; + config->address = FT24C256_BASE_ADDRESS; + config->write_ms = FT24C256_WRITE_MS; + config->poll_retries = FT24C256_POLL_RETRIES; +} + +uint8_t FT24C256_Init(FT24C256 *device, const FT24C256_Io *io, + const FT24C256_Config *config) +{ + if ((device == 0) || (io == 0)) { + return 0U; + } + if ((io->write == 0) || (io->write_read == 0)) { + return 0U; + } + + device->io = *io; + if (config != 0) { + device->config = *config; + } else { + FT24C256_ConfigDefault(&device->config); + } + if (device->config.size == 0U) { + device->config.size = FT24C256_SIZE; + } + if (device->config.page_size == 0U) { + device->config.page_size = FT24C256_PAGE_SIZE; + } + if (device->config.page_size > FT24C256_PAGE_MAX) { + device->config.page_size = FT24C256_PAGE_MAX; + } + if (device->config.address == 0U) { + device->config.address = FT24C256_BASE_ADDRESS; + } + if (device->config.write_ms == 0U) { + device->config.write_ms = FT24C256_WRITE_MS; + } + /* Без опроса готовности единственная мера времени — выдержка порта. */ + if ((device->config.poll_retries == 0U) && (device->io.delay_ms == 0)) { + return 0U; + } + + device->ready = 1U; + device->written_bytes = 0U; + device->read_bytes = 0U; + device->write_errors = 0U; + device->read_errors = 0U; + return 1U; +} + +uint8_t FT24C256_IsPresent(FT24C256 *device) +{ + if (ft24c256_valid(device) == 0U) { + return 0U; + } + return device->io.write(device->io.context, device->config.address, 0, 0U); +} + +uint8_t FT24C256_WaitReady(FT24C256 *device) +{ + uint8_t attempt; + + if (ft24c256_valid(device) == 0U) { + return 0U; + } + if (device->config.poll_retries == 0U) { + ft24c256_delay(device, device->config.write_ms); + return 1U; + } + for (attempt = 0U; attempt < device->config.poll_retries; attempt++) { + if (device->io.write(device->io.context, device->config.address, 0, + 0U) != 0U) { + return 1U; + } + ft24c256_delay(device, 1U); + } + device->write_errors++; + return 0U; +} + +uint8_t FT24C256_Read(FT24C256 *device, uint32_t address, void *buffer, + uint16_t size) +{ + uint8_t frame[FT24C256_ADDRESS_SIZE]; + + if ((ft24c256_valid(device) == 0U) || (buffer == 0)) { + return 0U; + } + if (size == 0U) { + return 1U; + } + if (ft24c256_in_range(device, address, size) == 0U) { + return 0U; + } + + ft24c256_put_address(address, frame); + if (device->io.write_read(device->io.context, device->config.address, frame, + FT24C256_ADDRESS_SIZE, (uint8_t *)buffer, + size) == 0U) { + device->read_errors++; + return 0U; + } + device->read_bytes += size; + return 1U; +} + +uint8_t FT24C256_Write(FT24C256 *device, uint32_t address, const void *data, + uint16_t size) +{ + const uint8_t *source = (const uint8_t *)data; + uint32_t position = address; + uint16_t left = size; + + if ((ft24c256_valid(device) == 0U) || (data == 0)) { + return 0U; + } + if (size == 0U) { + return 1U; + } + if (ft24c256_in_range(device, address, size) == 0U) { + return 0U; + } + + while (left != 0U) { + const uint32_t page = device->config.page_size; + const uint32_t tail = page - (position % page); + uint16_t chunk = (tail < left) ? (uint16_t)tail : left; + + if (ft24c256_write_page(device, position, source, chunk) == 0U) { + return 0U; + } + position += chunk; + source += chunk; + left = (uint16_t)(left - chunk); + } + return 1U; +} + +uint8_t FT24C256_Update(FT24C256 *device, uint32_t address, const void *data, + uint16_t size) +{ + const uint8_t *source = (const uint8_t *)data; + uint32_t position = address; + uint16_t left = size; + + if ((ft24c256_valid(device) == 0U) || (data == 0)) { + return 0U; + } + if (size == 0U) { + return 1U; + } + if (ft24c256_in_range(device, address, size) == 0U) { + return 0U; + } + + while (left != 0U) { + uint8_t stored[FT24C256_COMPARE_MAX]; + const uint16_t chunk = (left < FT24C256_COMPARE_MAX) + ? left + : (uint16_t)FT24C256_COMPARE_MAX; + uint16_t index; + uint8_t differs = 0U; + + if (FT24C256_Read(device, position, stored, chunk) == 0U) { + return 0U; + } + for (index = 0U; index < chunk; index++) { + if (stored[index] != source[index]) { + differs = 1U; + break; + } + } + if ((differs != 0U) + && (FT24C256_Write(device, position, source, chunk) == 0U)) { + return 0U; + } + position += chunk; + source += chunk; + left = (uint16_t)(left - chunk); + } + return 1U; +} + +uint8_t FT24C256_ReadByte(FT24C256 *device, uint32_t address, uint8_t *value) +{ + if (value == 0) { + return 0U; + } + return FT24C256_Read(device, address, value, 1U); +} + +uint8_t FT24C256_WriteByte(FT24C256 *device, uint32_t address, uint8_t value) +{ + return FT24C256_Write(device, address, &value, 1U); +} + +uint8_t FT24C256_Fill(FT24C256 *device, uint32_t address, uint8_t value, + uint32_t size) +{ + uint8_t page[FT24C256_PAGE_MAX]; + uint32_t position = address; + uint32_t left = size; + uint16_t index; + + if (ft24c256_valid(device) == 0U) { + return 0U; + } + if (size == 0U) { + return 1U; + } + if (ft24c256_in_range(device, address, size) == 0U) { + return 0U; + } + + for (index = 0U; index < device->config.page_size; index++) { + page[index] = value; + } + while (left != 0U) { + const uint32_t tail = + device->config.page_size - (position % device->config.page_size); + const uint32_t chunk = (tail < left) ? tail : left; + + if (ft24c256_write_page(device, position, page, (uint16_t)chunk) == 0U) { + return 0U; + } + position += chunk; + left -= chunk; + } + return 1U; +} + +uint8_t FT24C256_Erase(FT24C256 *device) +{ + if (ft24c256_valid(device) == 0U) { + return 0U; + } + return FT24C256_Fill(device, 0U, 0xFFU, device->config.size); +} + +uint8_t FT24C256_Protect(FT24C256 *device, uint8_t enabled) +{ + if (device == 0) { + return 0U; + } + if (device->io.set_write_protect == 0) { + return 0U; + } + device->io.set_write_protect(device->io.context, (uint8_t)(enabled != 0U)); + return 1U; +} + +uint32_t FT24C256_Size(const FT24C256 *device) +{ + if ((device == 0) || (device->ready == 0U)) { + return 0U; + } + return device->config.size; +} + +uint8_t FT24C256_AddressOf(uint8_t index) +{ + return (uint8_t)(FT24C256_BASE_ADDRESS + (index & 0x7U)); +} diff --git a/c/eeprom-ft24c256/ft24c256.h b/c/eeprom-ft24c256/ft24c256.h new file mode 100644 index 0000000..fd0d75e --- /dev/null +++ b/c/eeprom-ft24c256/ft24c256.h @@ -0,0 +1,277 @@ +/** + * @file ft24c256.h + * @brief Портируемый драйвер последовательной EEPROM FT24C256 и её семейства. + * + * Библиотека не привязана к микроконтроллеру: она не включает заголовки + * периферии, не обращается к регистрам и не пользуется прерываниями. Обмен + * идёт через таблицу обратных вызовов FT24C256_Io, которую заполняет порт + * платы (для этой сборки — @c src/eeprom.c поверх I2C1 STM32F103C8T6). + * + * FT24C256 — 32768 байт (256 кбит) с двухбайтовым адресом слова и страницей + * записи 64 байта. Запись выполняется постранично: микросхема принимает до + * 64 байт в одной транзакции, но только внутри страницы — при переходе через + * её границу счётчик адреса заворачивается на начало той же страницы и + * затирает уже принятые байты. Поэтому FT24C256_Write() сама режет запрос по + * границам страниц: + * + * @code + * запись 100 байт с адреса 0x0030 + * + * 0x0000 0x0040 0x0080 0x00C0 + * | страница 0 | страница 1 | страница 2 | + * +---------------+---------------+---------------+ + * [ 16 ][ 64 ][ 20 ] + * 1-я 2-я 3-я транзакция + * @endcode + * + * После каждой транзакции кристалл уходит во внутренний цикл записи (до 5 мс) + * и не отвечает на шину. Готовность определяется опросом подтверждения + * (ACK polling): библиотека шлёт адресный байт без данных, пока микросхема не + * ответит. Порт, который не умеет передавать транзакцию нулевой длины, + * выставляет @c poll_retries в ноль — тогда используется выдержка + * @c write_ms. + * + * Кристалл выбирается тремя адресными выводами A0...A2, поэтому на одной шине + * живут до восьми микросхем с адресами 0x50...0x57. Задавая @c size и + * @c page_size, тем же кодом обслуживаются младшие члены семейства с + * двухбайтовым адресом: FT24C32 (4 КБ, страница 32), FT24C64 (8 КБ, 32), + * FT24C128 (16 КБ, 64), FT24C512 (64 КБ, 128). Микросхемы до FT24C16 + * включительно адресуют слово одним байтом и этой библиотекой не + * поддерживаются. + */ + +#ifndef FT24C256_H +#define FT24C256_H + +#include + +/** Объём FT24C256 в байтах (256 кбит). */ +#define FT24C256_SIZE 32768UL + +/** Размер страницы записи FT24C256 в байтах. */ +#define FT24C256_PAGE_SIZE 64U + +/** Предельная страница семейства (FT24C512); задаёт размер буфера транзакции. */ +#define FT24C256_PAGE_MAX 128U + +/** Базовый адрес на шине при A0 = A1 = A2 = 0. */ +#define FT24C256_BASE_ADDRESS 0x50U + +/** Число микросхем семейства на одной шине. */ +#define FT24C256_MAX_DEVICES 8U + +/** Длительность внутреннего цикла записи по даташиту, мс. */ +#define FT24C256_WRITE_MS 5U + +/** Число опросов подтверждения после записи по умолчанию. */ +#define FT24C256_POLL_RETRIES 20U + +/** Длина адреса слова в байтах: FT24C32 и старше адресуют двумя байтами. */ +#define FT24C256_ADDRESS_SIZE 2U + +/** + * @brief Доступ библиотеки к шине I2C. + * + * Обязательны @c write и @c write_read. Адрес в вызовах — семибитный, без + * бита направления: сдвиг и добавление бита выполняет порт. + */ +typedef struct { + /** + * Передаёт транзакцию «старт, адрес, данные, стоп». + * Вызов с @c size, равным нулю, передаёт один адресный байт и применяется + * для опроса готовности; 1 — микросхема ответила подтверждением. + */ + uint8_t (*write)(void *context, uint8_t address, const uint8_t *data, + uint16_t size); + /** + * Передаёт @c tx_size байт, затем без освобождения шины (повторный старт) + * читает @c rx_size байт; 1 — обмен завершён подтверждениями. + */ + uint8_t (*write_read)(void *context, uint8_t address, const uint8_t *tx, + uint16_t tx_size, uint8_t *rx, uint16_t rx_size); + /** Выдержка в миллисекундах; обязателен, если @c poll_retries равен нулю. */ + void (*delay_ms)(void *context, uint32_t milliseconds); + /** Управление выводом WP: 1 — запись запрещена. Допускает значение 0. */ + void (*set_write_protect)(void *context, uint8_t enabled); + void *context; /**< Контекст порта, передаётся вызовам без изменений. */ +} FT24C256_Io; + +/** + * @brief Параметры кристалла. + * + * Нулевые поля заменяются значениями по умолчанию вызовом + * FT24C256_ConfigDefault() или самой FT24C256_Init(). + */ +typedef struct { + uint32_t size; /**< Объём в байтах; 0 — @ref FT24C256_SIZE. */ + uint16_t page_size; /**< Страница записи; 0 — @ref FT24C256_PAGE_SIZE. */ + uint8_t address; /**< Семибитный адрес; 0 — @ref FT24C256_BASE_ADDRESS. */ + uint8_t write_ms; /**< Выдержка цикла записи; 0 — @ref FT24C256_WRITE_MS. */ + uint8_t poll_retries; /**< Опросов готовности; 0 — только выдержка. */ +} FT24C256_Config; + +/** + * @brief Состояние кристалла: настройки, обратные вызовы и счётчики обмена. + */ +typedef struct { + FT24C256_Io io; /**< Обратные вызовы порта. */ + FT24C256_Config config; /**< Параметры кристалла. */ + uint8_t ready; /**< 1, если FT24C256_Init() прошла успешно. */ + uint32_t written_bytes; /**< Байтов, отправленных на запись. */ + uint32_t read_bytes; /**< Байтов, полученных чтением. */ + uint32_t write_errors; /**< Транзакций записи, оставшихся без ответа. */ + uint32_t read_errors; /**< Транзакций чтения, оставшихся без ответа. */ +} FT24C256; + +/** + * @brief Заполняет параметры значениями FT24C256 с адресными выводами на GND. + * + * @param config Параметры, принадлежащие вызывающему коду. + */ +void FT24C256_ConfigDefault(FT24C256_Config *config); + +/** + * @brief Готовит кристалл к работе. + * + * Обмена с шиной не выполняет: наличие микросхемы проверяется отдельно + * вызовом FT24C256_IsPresent(). + * + * @param device Состояние, принадлежащее вызывающему коду. + * @param io Обратные вызовы порта; копируются внутрь состояния. + * @param config Параметры либо 0 для значений по умолчанию. + * @return 1 при успешной настройке, 0 при неполных аргументах. + */ +uint8_t FT24C256_Init(FT24C256 *device, const FT24C256_Io *io, + const FT24C256_Config *config); + +/** + * @brief Проверяет отклик микросхемы на её адрес. + * + * @param device Состояние кристалла. + * @return 1, если получено подтверждение, иначе 0. + */ +uint8_t FT24C256_IsPresent(FT24C256 *device); + +/** + * @brief Ожидает завершения внутреннего цикла записи. + * + * При ненулевом @c poll_retries шлёт адресные байты, пока микросхема не + * ответит; иначе выдерживает паузу @c write_ms. + * + * @param device Состояние кристалла. + * @return 1, если микросхема готова, иначе 0. + */ +uint8_t FT24C256_WaitReady(FT24C256 *device); + +/** + * @brief Читает произвольное число байтов подряд. + * + * Чтение границами страниц не ограничено: счётчик адреса проходит весь + * массив, поэтому запрос выполняется одной транзакцией. + * + * @param device Состояние кристалла. + * @param address Адрес первого байта. + * @param buffer Приёмник данных. + * @param size Число байтов. + * @return 1 при успехе; 0 при выходе за границы массива или отказе шины. + */ +uint8_t FT24C256_Read(FT24C256 *device, uint32_t address, void *buffer, + uint16_t size); + +/** + * @brief Записывает произвольное число байтов, разрезая запрос по страницам. + * + * После каждой страницы выполняется ожидание готовности, поэтому вызов + * блокирующий: запись всего массива занимает около 2.6 с. + * + * @param device Состояние кристалла. + * @param address Адрес первого байта. + * @param data Записываемые данные. + * @param size Число байтов. + * @return 1 при успехе; 0 при выходе за границы массива или отказе шины. + */ +uint8_t FT24C256_Write(FT24C256 *device, uint32_t address, const void *data, + uint16_t size); + +/** + * @brief Записывает только те страницы, содержимое которых отличается. + * + * Ресурс кристалла — миллион циклов записи на страницу, поэтому сохранение + * настроек, которые меняются редко, выгоднее вести этой функцией: неизменные + * страницы не переписываются. + * + * @param device Состояние кристалла. + * @param address Адрес первого байта. + * @param data Записываемые данные. + * @param size Число байтов. + * @return 1 при успехе, иначе 0. + */ +uint8_t FT24C256_Update(FT24C256 *device, uint32_t address, const void *data, + uint16_t size); + +/** + * @brief Читает один байт. + * + * @param device Состояние кристалла. + * @param address Адрес байта. + * @param value Приёмник значения. + * @return 1 при успехе, иначе 0. + */ +uint8_t FT24C256_ReadByte(FT24C256 *device, uint32_t address, uint8_t *value); + +/** + * @brief Записывает один байт. + * + * @param device Состояние кристалла. + * @param address Адрес байта. + * @param value Записываемое значение. + * @return 1 при успехе, иначе 0. + */ +uint8_t FT24C256_WriteByte(FT24C256 *device, uint32_t address, uint8_t value); + +/** + * @brief Заполняет область одинаковым значением. + * + * @param device Состояние кристалла. + * @param address Адрес первого байта. + * @param value Записываемое значение. + * @param size Число байтов. + * @return 1 при успехе, иначе 0. + */ +uint8_t FT24C256_Fill(FT24C256 *device, uint32_t address, uint8_t value, + uint32_t size); + +/** + * @brief Заполняет весь массив значением 0xFF. + * + * @param device Состояние кристалла. + * @return 1 при успехе, иначе 0. + */ +uint8_t FT24C256_Erase(FT24C256 *device); + +/** + * @brief Включает или снимает аппаратную защиту записи выводом WP. + * + * @param device Состояние кристалла. + * @param enabled 1 — запись запрещена, 0 — разрешена. + * @return 1, если порт управляет выводом WP, иначе 0. + */ +uint8_t FT24C256_Protect(FT24C256 *device, uint8_t enabled); + +/** + * @brief Возвращает объём кристалла. + * + * @param device Состояние кристалла. + * @return Число байтов; 0 для ненастроенного состояния. + */ +uint32_t FT24C256_Size(const FT24C256 *device); + +/** + * @brief Возвращает адрес кристалла на шине для указанных выводов A0...A2. + * + * @param index Значение адресных выводов, 0...7. + * @return Семибитный адрес 0x50...0x57. + */ +uint8_t FT24C256_AddressOf(uint8_t index); + +#endif /* FT24C256_H */