Files
templates/c/eeprom-ft24c256/ft24c256.c
Andrey Kruchinkin 11bf8b6c11 feat(eeprom-ft24c256): драйвер EEPROM 24Cxx по I2C
Перенесён из KONOR_ds18b20/lib/eeprom; в OpticalTester лежала такая же копия.

Запись режется по границам страниц внутри библиотеки: микросхема
принимает до 64 байт за транзакцию, но при переходе через границу
страницы счётчик адреса заворачивается и затирает уже принятые байты.
Вызывающему коду об этом думать не нужно.
2026-08-23 01:15:12 +03:00

379 lines
11 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @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));
}