Files
2026-09-05 12:09:21 -07:00

505 lines
15 KiB
C

#include "at24cm02.h"
#include <stdbool.h>
#include <stddef.h>
#include "driverlib.h"
#include "device.h"
#include "semphr.h"
#include "task.h"
#define EEPROM_I2C_BASE I2CA_BASE
#define EEPROM_I2C_BIT_RATE 400000UL
#define EEPROM_SDA_PIN 91U
#define EEPROM_SCL_PIN 92U
#define EEPROM_STATUS_ERROR_MASK (I2C_STS_NO_ACK | I2C_STS_ARB_LOST)
#define EEPROM_CLEARABLE_STATUS_MASK (I2C_STS_NO_ACK | I2C_STS_ARB_LOST | \
I2C_STS_REG_ACCESS_RDY | \
I2C_STS_STOP_CONDITION)
// The AT24CM02 tWR maximum is 10 ms. Use 11 RTOS ticks at a 1-kHz tick rate
// so the actual delay cannot be shortened below 10 ms by tick quantization.
#define EEPROM_WRITE_CYCLE_MS 11U
static SemaphoreHandle_t eepromMutex = NULL;
static bool eepromInitialized = false;
static bool EEPROM_hasTimedOut(TickType_t start, TickType_t timeoutTicks)
{
if(timeoutTicks == portMAX_DELAY)
{
return false;
}
return ((TickType_t)(xTaskGetTickCount() - start) >= timeoutTicks);
}
static void EEPROM_yield(void)
{
if(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING)
{
taskYIELD();
}
}
static EEPROM_Status EEPROM_statusToError(uint16_t i2cStatus)
{
if((i2cStatus & I2C_STS_ARB_LOST) != 0U)
{
return EEPROM_STATUS_ARBITRATION_LOST;
}
if((i2cStatus & I2C_STS_NO_ACK) != 0U)
{
return EEPROM_STATUS_NO_ACK;
}
return EEPROM_STATUS_SUCCESS;
}
static void EEPROM_abortTransaction(void)
{
I2C_sendStopCondition(EEPROM_I2C_BASE);
I2C_clearStatus(EEPROM_I2C_BASE, EEPROM_CLEARABLE_STATUS_MASK);
}
static EEPROM_Status EEPROM_waitForStatus(uint16_t requestedStatus,
TickType_t start,
TickType_t timeoutTicks)
{
uint16_t status;
EEPROM_Status error;
for(;;)
{
status = I2C_getStatus(EEPROM_I2C_BASE);
error = EEPROM_statusToError(status);
if(error != EEPROM_STATUS_SUCCESS)
{
return error;
}
if((status & requestedStatus) != 0U)
{
return EEPROM_STATUS_SUCCESS;
}
if(EEPROM_hasTimedOut(start, timeoutTicks))
{
return EEPROM_STATUS_TIMEOUT;
}
EEPROM_yield();
}
}
static EEPROM_Status EEPROM_waitForBusIdle(TickType_t start,
TickType_t timeoutTicks)
{
while(I2C_getStopConditionStatus(EEPROM_I2C_BASE) ||
I2C_isBusBusy(EEPROM_I2C_BASE))
{
if(EEPROM_hasTimedOut(start, timeoutTicks))
{
return EEPROM_STATUS_BUS_BUSY;
}
EEPROM_yield();
}
return EEPROM_STATUS_SUCCESS;
}
static uint16_t EEPROM_targetAddress(uint16_t deviceAddress,
uint32_t memoryAddress)
{
return (uint16_t)(deviceAddress | ((memoryAddress >> 16U) & 0x3U));
}
static bool EEPROM_deviceAddressIsValid(uint16_t deviceAddress)
{
// Bits A17:A16 are supplied by the memory address for this device.
return ((deviceAddress <= 0x7CU) && ((deviceAddress & 0x3U) == 0U));
}
static EEPROM_Status EEPROM_writeChunk(const uint16_t *data,
uint32_t address,
uint16_t deviceAddress,
uint16_t byteCount,
TickType_t timeoutTicks)
{
uint16_t i;
TickType_t start = xTaskGetTickCount();
EEPROM_Status status;
status = EEPROM_waitForBusIdle(start, timeoutTicks);
if(status != EEPROM_STATUS_SUCCESS)
{
return status;
}
I2C_clearStatus(EEPROM_I2C_BASE, EEPROM_CLEARABLE_STATUS_MASK);
I2C_setTargetAddress(EEPROM_I2C_BASE,
EEPROM_targetAddress(deviceAddress, address));
I2C_setDataCount(EEPROM_I2C_BASE, (uint16_t)(byteCount + 2U));
I2C_setConfig(EEPROM_I2C_BASE, I2C_CONTROLLER_SEND_MODE);
I2C_sendStartCondition(EEPROM_I2C_BASE);
I2C_sendStopCondition(EEPROM_I2C_BASE);
status = EEPROM_waitForStatus(I2C_STS_TX_DATA_RDY, start, timeoutTicks);
if(status == EEPROM_STATUS_SUCCESS)
{
I2C_putData(EEPROM_I2C_BASE, (uint16_t)((address >> 8U) & 0xFFU));
}
if(status == EEPROM_STATUS_SUCCESS)
{
status = EEPROM_waitForStatus(I2C_STS_TX_DATA_RDY, start,
timeoutTicks);
}
if(status == EEPROM_STATUS_SUCCESS)
{
I2C_putData(EEPROM_I2C_BASE, (uint16_t)(address & 0xFFU));
}
for(i = 0U; (i < byteCount) && (status == EEPROM_STATUS_SUCCESS); i++)
{
status = EEPROM_waitForStatus(I2C_STS_TX_DATA_RDY, start,
timeoutTicks);
if(status == EEPROM_STATUS_SUCCESS)
{
I2C_putData(EEPROM_I2C_BASE, data[i] & 0xFFU);
}
}
if(status == EEPROM_STATUS_SUCCESS)
{
status = EEPROM_waitForStatus(I2C_STS_STOP_CONDITION, start,
timeoutTicks);
}
if(status != EEPROM_STATUS_SUCCESS)
{
EEPROM_abortTransaction();
return status;
}
I2C_clearStatus(EEPROM_I2C_BASE, I2C_STS_STOP_CONDITION);
return EEPROM_STATUS_SUCCESS;
}
static EEPROM_Status EEPROM_readChunk(uint16_t *data, uint32_t address,
uint16_t deviceAddress,
uint16_t byteCount,
TickType_t timeoutTicks)
{
uint16_t i;
TickType_t start = xTaskGetTickCount();
EEPROM_Status status;
status = EEPROM_waitForBusIdle(start, timeoutTicks);
if(status != EEPROM_STATUS_SUCCESS)
{
return status;
}
I2C_clearStatus(EEPROM_I2C_BASE, EEPROM_CLEARABLE_STATUS_MASK);
I2C_setTargetAddress(EEPROM_I2C_BASE,
EEPROM_targetAddress(deviceAddress, address));
I2C_setDataCount(EEPROM_I2C_BASE, 2U);
I2C_setConfig(EEPROM_I2C_BASE, I2C_CONTROLLER_SEND_MODE);
I2C_sendStartCondition(EEPROM_I2C_BASE);
status = EEPROM_waitForStatus(I2C_STS_TX_DATA_RDY, start, timeoutTicks);
if(status == EEPROM_STATUS_SUCCESS)
{
I2C_putData(EEPROM_I2C_BASE, (uint16_t)((address >> 8U) & 0xFFU));
}
if(status == EEPROM_STATUS_SUCCESS)
{
status = EEPROM_waitForStatus(I2C_STS_TX_DATA_RDY, start,
timeoutTicks);
}
if(status == EEPROM_STATUS_SUCCESS)
{
I2C_putData(EEPROM_I2C_BASE, (uint16_t)(address & 0xFFU));
}
if(status == EEPROM_STATUS_SUCCESS)
{
status = EEPROM_waitForStatus(I2C_STS_REG_ACCESS_RDY, start,
timeoutTicks);
}
if(status == EEPROM_STATUS_SUCCESS)
{
I2C_clearStatus(EEPROM_I2C_BASE, I2C_STS_REG_ACCESS_RDY);
I2C_setDataCount(EEPROM_I2C_BASE, byteCount);
I2C_setConfig(EEPROM_I2C_BASE, I2C_CONTROLLER_RECEIVE_MODE);
I2C_sendStartCondition(EEPROM_I2C_BASE);
I2C_sendStopCondition(EEPROM_I2C_BASE);
}
for(i = 0U; (i < byteCount) && (status == EEPROM_STATUS_SUCCESS); i++)
{
status = EEPROM_waitForStatus(I2C_STS_RX_DATA_RDY, start,
timeoutTicks);
if(status == EEPROM_STATUS_SUCCESS)
{
data[i] = I2C_getData(EEPROM_I2C_BASE) & 0xFFU;
}
}
if(status == EEPROM_STATUS_SUCCESS)
{
status = EEPROM_waitForStatus(I2C_STS_STOP_CONDITION, start,
timeoutTicks);
}
if(status != EEPROM_STATUS_SUCCESS)
{
EEPROM_abortTransaction();
return status;
}
I2C_clearStatus(EEPROM_I2C_BASE, I2C_STS_STOP_CONDITION);
return EEPROM_STATUS_SUCCESS;
}
EEPROM_Status EEPROM_init(void)
{
if(eepromMutex == NULL)
{
eepromMutex = xSemaphoreCreateMutex();
if(eepromMutex == NULL)
{
return EEPROM_STATUS_NO_MEMORY;
}
}
GPIO_setMasterCore(EEPROM_SDA_PIN, GPIO_CORE_CPU1);
GPIO_setPinConfig(GPIO_91_SDAA);
GPIO_setDirectionMode(EEPROM_SDA_PIN, GPIO_DIR_MODE_IN);
GPIO_setPadConfig(EEPROM_SDA_PIN, GPIO_PIN_TYPE_PULLUP);
GPIO_setQualificationMode(EEPROM_SDA_PIN, GPIO_QUAL_ASYNC);
GPIO_setMasterCore(EEPROM_SCL_PIN, GPIO_CORE_CPU1);
GPIO_setPinConfig(GPIO_92_SCLA);
GPIO_setDirectionMode(EEPROM_SCL_PIN, GPIO_DIR_MODE_IN);
GPIO_setPadConfig(EEPROM_SCL_PIN, GPIO_PIN_TYPE_PULLUP);
GPIO_setQualificationMode(EEPROM_SCL_PIN, GPIO_QUAL_ASYNC);
I2C_disableModule(EEPROM_I2C_BASE);
I2C_initController(EEPROM_I2C_BASE, DEVICE_SYSCLK_FREQ,
EEPROM_I2C_BIT_RATE, I2C_DUTYCYCLE_33);
I2C_setBitCount(EEPROM_I2C_BASE, I2C_BITCOUNT_8);
I2C_setAddressMode(EEPROM_I2C_BASE, I2C_ADDR_MODE_7BITS);
I2C_setEmulationMode(EEPROM_I2C_BASE, I2C_EMULATION_FREE_RUN);
I2C_disableFIFO(EEPROM_I2C_BASE);
I2C_disableInterrupt(EEPROM_I2C_BASE, I2C_INT_ARB_LOST |
I2C_INT_NO_ACK | I2C_INT_REG_ACCESS_RDY |
I2C_INT_RX_DATA_RDY | I2C_INT_TX_DATA_RDY |
I2C_INT_STOP_CONDITION | I2C_INT_ADDR_TARGET |
I2C_INT_RXFF | I2C_INT_TXFF);
I2C_clearStatus(EEPROM_I2C_BASE, EEPROM_CLEARABLE_STATUS_MASK);
I2C_enableModule(EEPROM_I2C_BASE);
eepromInitialized = true;
return EEPROM_STATUS_SUCCESS;
}
EEPROM_Status EEPROM_read(uint16_t *data, uint32_t address,
uint16_t deviceAddress, uint32_t byteCount,
TickType_t timeoutTicks)
{
EEPROM_Status status;
uint32_t chunkSize;
if(!eepromInitialized)
{
return EEPROM_STATUS_NOT_INITIALIZED;
}
if((data == NULL) || (byteCount == 0UL) ||
!EEPROM_deviceAddressIsValid(deviceAddress))
{
return EEPROM_STATUS_INVALID_ARGUMENT;
}
if((address >= AT24CM02_CAPACITY_BYTES) ||
(byteCount > (AT24CM02_CAPACITY_BYTES - address)))
{
return EEPROM_STATUS_OUT_OF_RANGE;
}
if(xSemaphoreTake(eepromMutex, timeoutTicks) != pdTRUE)
{
return EEPROM_STATUS_MUTEX_TIMEOUT;
}
status = EEPROM_STATUS_SUCCESS;
while((byteCount != 0UL) && (status == EEPROM_STATUS_SUCCESS))
{
// I2CCNT is 16-bit; keep chunks moderate and avoid a block-address
// change in the middle of an I2C transaction.
chunkSize = 0x10000UL - (address & 0xFFFFUL);
if(chunkSize > byteCount)
{
chunkSize = byteCount;
}
if(chunkSize > 0xFFFFUL)
{
chunkSize = 0xFFFFUL;
}
status = EEPROM_readChunk(data, address, deviceAddress,
(uint16_t)chunkSize, timeoutTicks);
address += chunkSize;
data += chunkSize;
byteCount -= chunkSize;
}
xSemaphoreGive(eepromMutex);
return status;
}
EEPROM_Status EEPROM_write(const uint16_t *data, uint32_t address,
uint16_t deviceAddress, uint32_t byteCount,
TickType_t timeoutTicks)
{
EEPROM_Status status;
uint32_t chunkSize;
if(!eepromInitialized)
{
return EEPROM_STATUS_NOT_INITIALIZED;
}
if((data == NULL) || (byteCount == 0UL) ||
!EEPROM_deviceAddressIsValid(deviceAddress))
{
return EEPROM_STATUS_INVALID_ARGUMENT;
}
if((address >= AT24CM02_CAPACITY_BYTES) ||
(byteCount > (AT24CM02_CAPACITY_BYTES - address)))
{
return EEPROM_STATUS_OUT_OF_RANGE;
}
if(xSemaphoreTake(eepromMutex, timeoutTicks) != pdTRUE)
{
return EEPROM_STATUS_MUTEX_TIMEOUT;
}
status = EEPROM_STATUS_SUCCESS;
while((byteCount != 0UL) && (status == EEPROM_STATUS_SUCCESS))
{
chunkSize = AT24CM02_PAGE_SIZE_BYTES -
(address & (AT24CM02_PAGE_SIZE_BYTES - 1U));
if(chunkSize > byteCount)
{
chunkSize = byteCount;
}
status = EEPROM_writeChunk(data, address, deviceAddress,
(uint16_t)chunkSize, timeoutTicks);
if(status == EEPROM_STATUS_SUCCESS)
{
if(xTaskGetSchedulerState() == taskSCHEDULER_RUNNING)
{
vTaskDelay(pdMS_TO_TICKS(EEPROM_WRITE_CYCLE_MS));
}
else
{
DEVICE_DELAY_US((uint32_t)EEPROM_WRITE_CYCLE_MS * 1000UL);
}
}
address += chunkSize;
data += chunkSize;
byteCount -= chunkSize;
}
xSemaphoreGive(eepromMutex);
return status;
}
EEPROM_Status EEPROM_pageNumberToAddress(uint16_t pageNumber,
uint32_t *pageAddress)
{
if(pageAddress == NULL)
{
return EEPROM_STATUS_INVALID_ARGUMENT;
}
if((pageNumber == 0U) || (pageNumber > AT24CM02_PAGE_COUNT))
{
return EEPROM_STATUS_OUT_OF_RANGE;
}
*pageAddress = ((uint32_t)pageNumber - 1UL) *
AT24CM02_PAGE_SIZE_BYTES;
return EEPROM_STATUS_SUCCESS;
}
EEPROM_Status EEPROM_readPage(uint16_t *data, uint16_t deviceAddress,
uint16_t pageNumber, uint16_t byteCount,
TickType_t timeoutTicks)
{
uint32_t address;
EEPROM_Status status;
if((byteCount == 0U) || (byteCount > AT24CM02_PAGE_SIZE_BYTES))
{
return EEPROM_STATUS_INVALID_ARGUMENT;
}
status = EEPROM_pageNumberToAddress(pageNumber, &address);
if(status != EEPROM_STATUS_SUCCESS)
{
return status;
}
return EEPROM_read(data, address, deviceAddress, byteCount, timeoutTicks);
}
EEPROM_Status EEPROM_writePage(const uint16_t *data, uint16_t deviceAddress,
uint16_t pageNumber, uint16_t byteCount,
TickType_t timeoutTicks)
{
uint32_t address;
EEPROM_Status status;
if((byteCount == 0U) || (byteCount > AT24CM02_PAGE_SIZE_BYTES))
{
return EEPROM_STATUS_INVALID_ARGUMENT;
}
status = EEPROM_pageNumberToAddress(pageNumber, &address);
if(status != EEPROM_STATUS_SUCCESS)
{
return status;
}
return EEPROM_write(data, address, deviceAddress, byteCount, timeoutTicks);
}
const char *EEPROM_statusString(EEPROM_Status status)
{
static const char *const descriptions[] =
{
"success",
"not initialized",
"invalid argument",
"address out of range",
"FreeRTOS heap exhausted",
"mutex timeout",
"I2C bus busy",
"I2C timeout",
"I2C no acknowledgement",
"I2C arbitration lost"
};
if((uint16_t)status >=
(uint16_t)(sizeof(descriptions) / sizeof(descriptions[0])))
{
return "unknown status";
}
return descriptions[(uint16_t)status];
}