Saltar a contenido
← Volver a OPRobots.org

Protocolos y API — SRAM Ring Buffer

Dependencias externas

Librería Proveedor Propósito
libopencm3 libopencm3.org Capa de abstracción hardware para STM32 (SPI, GPIO, RCC, USART, SysTick, NVIC)
newlib-nano ARM GNU Toolchain printf, snprintf, syscalls (_write)

El proyecto no depende de librerías de terceros más allá de libopencm3 y la toolchain estándar de ARM.


API del ring buffer SRAM

La librería sram.h implementa un ring buffer de registros heterogéneos sobre la SRAM externa 23AA04M. Cada registro puede tener un tipo y tamaño distinto, identificado por un type_id de 1 byte.

Tipos de datos

typedef uint8_t sram_record_type_t;
enum {
  SRAM_TYPE_ANY     = 0x00,   // Wildcard: solo lectura (iterador)
  SRAM_TYPE_SENSOR  = 0x01,   // sensor_log_t
  SRAM_TYPE_CIRCUIT = 0x02,   // circuit_cell_t
  /* ── USER TYPES (0x03 – 0xFD) ── */
  SRAM_TYPE_INVALID = 0xFE,
};
Rango Uso
0x00 Wildcard para iteración (SRAM_TYPE_ANY)
0x010x02 Tipos predefinidos de la demo
0x030xFD Reservado para tipos de usuario
0xFE Tipo inválido (marcador de fin)
0xFF Marcador interno de WRAP (nunca usar como tipo de registro)

Códigos de retorno

Código Valor Significado
SRAM_OK 0 Operación exitosa
SRAM_ERR_FULL -1 Buffer lleno sin registros que sobrescribir (num_records == 0)
SRAM_ERR_NOT_INIT -2 Cabecera mágica no válida (SRAM no formateada o corrompida)
SRAM_ERR_PARAM -3 Parámetro inválido (tipo no válido, puntero nulo, tamaño 0, registro > 524272 B)

Nota: SRAM_ERR_FULL solo puede ocurrir si se intenta escribir un registro de más de 524272 bytes y el buffer está vacío. En la práctica, sram_write_record siempre sobrescribe los registros más antiguos para hacer hueco.

Funciones de la API

Inicialización

void sram_init(void);

Activa el modo secuencial SPI de la SRAM (comando WRMR + 0x40). Verifica la cabecera mágica; si no es válida (primer uso o corrupción), ejecuta sram_format() automáticamente. Imprime el estado de ocupación por USART1.

void sram_format(void);

Resetea la cabecera a valores iniciales sin tocar el área de datos: - magic = 0x6D17 - num_records = 0 - head_pos = SRAM_RECORDS_START (0x000010) - tail_pos = SRAM_RECORDS_START - bytes_used = 0

⚠️ Advertencia: sram_format() borra el índice de registros. Los datos antiguos en el área de registros quedan inaccesibles pero no se sobreescriben físicamente.

Escritura

sram_result_t sram_write_record(sram_record_type_t type,
                                 const void *data,
                                 uint16_t size);

Escribe size bytes del struct apuntado por data como un registro del tipo type.

Algoritmo de escritura:

  1. Valida parámetros (tipo, puntero, tamaño)
  2. Lee la cabecera y verifica el magic number
  3. Si el nuevo registro no cabe antes del final de la SRAM → escribe WRAP marker
  4. Mientras no haya espacio suficiente → sobreescribe los registros más antiguos
  5. Escribe cabecera del registro (type + payload_size) y payload
  6. Actualiza la cabecera (posición, contador, bytes usados)

Ejemplo:

sensor_log_t log = {
    .timestamp_ms  = get_clock_ticks(),
    .line_sensors  = {100, 200, 300, 400},
    .speed_left    = 120,
    .speed_right   = 118,
    .battery_pct   = 79,
};
sram_result_t r = sram_write_record(SRAM_TYPE_SENSOR, &log, sizeof(log));
if (r != SRAM_OK) {
    printf("Error: %d\n", r);
}

Lectura mediante iterador

void sram_iterator_init(sram_iterator_t *iterator, sram_record_type_t type);
bool sram_iter_next(sram_iterator_t *iterator, void *buf,
                    uint16_t buf_size, uint16_t *out_size);

Recorre los registros del tipo indicado (o todos con SRAM_TYPE_ANY) sin necesitar un buffer completo en RAM.

Parámetro Significado
iterator Estructura de estado del iterador (puntero a variable local)
type Tipo de registros a iterar (SRAM_TYPE_ANY = todos)
buf Buffer donde copiar el payload (puede ser NULL para solo contar)
buf_size Tamaño máximo del buffer (truncación segura si < payload)
out_size Recibe el payload_size real del registro leído

Retorno: true si se leyó un registro, false si no hay más.

Ejemplo:

sram_iterator_t iterator;
sram_iterator_init(&iterator, SRAM_TYPE_SENSOR);

sensor_log_t log;
uint16_t stored_size;
while (sram_iter_next(&iterator, &log, sizeof(log), &stored_size)) {
    if (stored_size != sizeof(log)) {
        continue;  // versión distinta del struct, saltar
    }
    printf("t=%lums L=%d R=%d bat=%u%%\n",
           log.timestamp_ms, log.speed_left,
           log.speed_right, log.battery_pct);
}

Estadísticas

uint16_t  sram_get_total_records(void);         // Total de registros
uint16_t  sram_count_records(sram_record_type_t type);  // Por tipo
char     *sram_free_bytes(void);                // String "usado/total (xx.xx%)"
Función Retorno Nota
sram_get_total_records() 0 – 65535 Lectura directa de la cabecera (O(1))
sram_count_records(type) 0 – 65535 Itera la SRAM entera (O(n))
sram_free_bytes() Cadena estática Buffer interno de 32 bytes — no reentrante

💡 Consejo: Usa sram_get_total_records() en lugar de sram_count_records(SRAM_TYPE_ANY) para obtener el total — la primera es O(1) leyendo la cabecera, la segunda itera toda la SRAM.


Layout de memoria en SRAM

┌──────────────────────────────────────────────────────┐
│ SRAM 23AA04M — 512 KB (0x000000 → 0x07FFFF)          │
├──────────────┬───────────────────────────────────────┤
│ 0x000000     │ Cabecera — 16 bytes                   │
│              │   magic       : uint16_t  (0x6D17)    │
│              │   num_records : uint16_t              │
│              │   head_pos    : uint32_t              │
│              │   tail_pos    : uint32_t              │
│              │   bytes_used  : uint32_t              │
├──────────────┼───────────────────────────────────────┤
│ 0x000010     │ Área de registros — 524 272 bytes     │
│              │  ┌─[type:1][size:2][payload:size]─┐   │
│              │  ├─[type:1][size:2][payload:size]─┤   │
│              │  │  ...                           │   │
│              │  └─[0xFF ][gap :2]  ← WRAP marker │   │
└──────────────┴───────────────────────────────────────┘

Cada registro ocupa 3 bytes de cabecera (1 byte type_id + 2 bytes payload_size) seguidos del payload.

Mecanismo WRAP

Cuando head_pos está demasiado cerca del final físico para albergar el siguiente registro, se escribe un WRAP marker:

  • type = 0xFF (valor reservado, no válido como tipo de registro)
  • payload_size = bytes restantes hasta el final de la SRAM

Tras escribir el WRAP: 1. bytes_used se incrementa en el espacio consumido por el marcador 2. head_pos salta a SRAM_RECORDS_START (0x000010)

Cuando el proceso de sobreescritura (sram_overwrite_oldest) encuentra un WRAP en tail_pos, salta igualmente al inicio del área de registros. Esto garantiza que en todo momento hay como máximo un único WRAP activo.

Sobreescritura (ring buffer)

Cuando el área de registros está llena: 1. sram_overwrite_oldest() lee el registro en tail_pos 2. Resta su tamaño de bytes_used 3. Avanza tail_pos al siguiente registro 4. Decrementa num_records 5. Si el registro eliminado libera suficiente espacio, se escribe el nuevo 6. Si no, se repite el proceso hasta tener espacio suficiente


Protocolo SPI de la 23AA04M

Formato de comando

Cada transacción SPI sigue la secuencia:

[CS low] → [CMD (1 byte)] → [ADDR (3 bytes)] → [DATA (N bytes)] → [CS high]

Comandos utilizados

Comando Código Descripción
READ 0x03 Lectura de N bytes desde dirección A
WRITE 0x02 Escritura de N bytes desde dirección A
WRMR 0x01 Escritura del registro de modo
RDMR 0x05 Lectura del registro de modo

Registro de modo

Modo Valor Comportamiento
Byte 0x00 Cada operación lee/escribe 1 byte (reenviar dirección)
Secuencial 0x40 Ráfagas continuas sin reenviar dirección (usado en este proyecto)
Página 0x80 Ráfagas de hasta 32 bytes por página
sequenceDiagram
    participant MCU as STM32F401
    participant SRAM as 23AA04M

    Note over MCU,SRAM: Inicialización — activar modo secuencial
    MCU->>SRAM: CS low
    MCU->>SRAM: WRMR (0x01)
    MCU->>SRAM: Mode byte (0x40)
    MCU->>SRAM: CS high

    Note over MCU,SRAM: Escritura de registro
    MCU->>SRAM: CS low
    MCU->>SRAM: WRITE (0x02)
    MCU->>SRAM: Address [23:0]
    MCU->>SRAM: type (1B) + size (2B) + payload (N B)
    MCU->>SRAM: CS high

    Note over MCU,SRAM: Lectura de registro
    MCU->>SRAM: CS low
    MCU->>SRAM: READ (0x03)
    MCU->>SRAM: Address [23:0]
    MCU->>SRAM: dummy (0x00) × N → read N bytes
    MCU->>SRAM: CS high

Funciones internas de bajo nivel

Función Descripción Archivo
sram_cs_select() PA15 low + delay 1 µs sram.c:35
sram_cs_deselect() PA15 high + delay 1 µs sram.c:40
sram_spi_byte(b) Transfiere 1 byte por SPI1 sram.c:45
sram_send_addr(addr) Envía dirección de 24 bits sram.c:49
sram_write_bytes(addr, buf, len) Escribe N bytes en dirección A sram.c:55
sram_read_bytes(addr, buf, len) Lee N bytes desde dirección A sram.c:66

Añadir un nuevo tipo de registro

  1. Definir el ID en sram.h:

    enum {
        // ... existing types ...
        SRAM_TYPE_MI_DATO = 0x03,   // nuevo tipo
    };
    

  2. Definir el struct con __attribute__((packed)) para evitar padding:

    typedef struct __attribute__((packed)) {
        uint32_t campo_a;
        uint16_t campo_b;
        bool     campo_c;
    } mi_dato_t;
    

  3. Escribir y leer:

    // Escritura
    mi_dato_t dato = { .campo_a = 42, .campo_b = 7, .campo_c = true };
    sram_write_record(SRAM_TYPE_MI_DATO, &dato, sizeof(dato));
    
    // Lectura con iterador
    sram_iterator_t it;
    sram_iterator_init(&it, SRAM_TYPE_MI_DATO);
    mi_dato_t record;
    uint16_t stored_size;
    while (sram_iter_next(&it, &record, sizeof(record), &stored_size)) {
        if (stored_size != sizeof(record)) continue;  // skip version mismatch
        printf("a=%lu b=%u c=%d\n", record.campo_a, record.campo_b, record.campo_c);
    }
    

⚠️ Advertencia: Si cambias la definición del struct después de haber escrito registros, usa out_size de sram_iter_next para detectar tamaños antiguos y evitar leer datos mal alineados o truncados.


Documento generado el 2026-06-27. Ver también Arquitectura Software, Known Issues.