Problema al escribir mapas de registro usando estructura C para un Microcontrolador TI

Estoy escribiendo mapas de registro en C para una placa de microcontrolador basada en TI ARM. Aquí está el enlace a la hoja de datos .

Estoy usando las siguientes pautas sobre cómo se deben escribir los mapas de registro en C: Mapas de registro . Estas pautas son similares a ARM CMSIS (Estándar de interfaz de software de microcontrolador de Cortex) para escribir código C.

Tengo un problema al escribir mapas de registro de control del sistema (consulte la sección 5.5, página 237 en adelante en la hoja de datos) usando la estructura C. Todos los registros tienen un tamaño de 32 bits.

  • La dirección base es 0x400F.E000.
  • Registro 1: Identificación del dispositivo 0 (DID0), compensación 0x000
  • Registro 2: Identificación de dispositivo 1 (DID1), compensación 0x004
  • Registro 3: control de restablecimiento de caída de tensión (PBORCTL), compensación 0x030
  • Registro 4: estado de interrupción sin procesar (RIS), compensación 0x050
  • etc..

Si ahora escribo una estructura como esta:

typedef struct
{
   uint32_t DID0;      // offset 0x000. distance: 0
   uint32_t DID1;      // offset 0x004. distance: 4
   uint32_t PBORCTL;   // **HOW to place this at offset 0x030 ?**
   uint32_t RIS;       // **HOW  to place this at offset 0x050 ?**
   // ...and so on
 }PER_REG_MAP_T;

#define PERIPH_BASE ((uint32_t)0x400FE000)
#define MY_PERIPH_A ((PER_REG_MAP_T*)PERIPH_BASE)

void Reset(PER_REG_MAP_T* PERIPH_A)
{
  PERIPH_A->DID0 = 0;
  PERIPH_A->DID1= 0;
  PERIPH_A->PBORCTL= 1;
  PERIPH_A->RIS= 0;
  // .. and so on
}

El problema principal al que me enfrento es cómo colocar PBORCTL y RIS dentro de la estructura, ya que están en el desplazamiento 0x030 y 0x050 con respecto a la dirección base de la estructura. No he hecho demasiada programación en C a nivel de bits antes, por lo que esta pregunta puede ser demasiado simple, pero no sé cómo hacerlo.

Tendría que rellenar la estructura con suficientes bytes no utilizados para llenar el agujero.
Por lo general, junto con la hoja de datos del propio uC, es útil tener una copia de la guía del usuario del compilador y pasar algún tiempo rastreando los archivos de encabezado que el proveedor proporciona para el compilador específico para este chip. Por lo general, proporcionan macros predefinidas (#defines) y otras construcciones específicas del compilador que definen cómo acceder a todos los registros de configuración del sistema. ¿Parece que lo que está escribiendo ya lo proporcionó el proveedor en un archivo de encabezado?
@mixed_signal_old: Sí, ya hay asignaciones de registro definidas dentro del archivo <dispositivo>.h, pero el problema es que las definiciones de registro parecen válidas para algunos dispositivos más antiguos y, por lo tanto, no se pueden utilizar para mi dispositivo actual (no coincide exactamente con la hoja de datos del dispositivo actual que tengo ). Puedo adaptarlo a mi dispositivo actual pero prefiero escribir desde cero

Respuestas (2)

Las estructuras no son adecuadas para escribir mapas de registro, porque una estructura puede tener bytes de relleno agregados en cualquier lugar dentro de ella, con fines de alineación. Esto depende del compilador: debe asegurarse de que no se use relleno (por ejemplo, a través de una afirmación estática).

Además, es fácil cometer errores cuando se escriben estructuras grandes que se supone que deben reflejar un mapa de registro, tienes que acertar cada byte o romperás todo. Esto hace que la estructura sea vulnerable durante el mantenimiento.

Le sugiero encarecidamente que escriba mapas de registro a través de macros, como:

#define PERIPH_A_DID0 (*(volatile uint32_t*)0x400FE000u))
#define PERIPH_A_DID1 (*(volatile uint32_t*)0x400FE004u))

Esto también tiene la ventaja de ser 100% portátil a cualquier compilador de C.

Alternativamente, podrías hacer algo más complejo como esto:

typedef volatile uint8_t* SCI_port;
#ifndef SCI0
  #define SCI0 (&SCI0BDH)
  #define SCI1 (&SCI1BDH)
#endif

#define SCIBDH(x)   (*((SCI_port)x + 0x00))   /* SCI Baud Rate Register High             */
#define SCIBDL(x)   (*((SCI_port)x + 0x01))   /* SCI Baud Rate Register Low              */
#define SCICR1(x)   (*((SCI_port)x + 0x02))   /* SCI Control Register1                   */
#define SCICR2(x)   (*((SCI_port)x + 0x03))   /* SCI Control Register 2                  */
#define SCISR1(x)   (*((SCI_port)x + 0x04))   /* SCI Status Register 1                   */
#define SCISR2(x)   (*((SCI_port)x + 0x05))   /* SCI Status Register 2                   */
#define SCIDRH(x)   (*((SCI_port)x + 0x06))   /* SCI Data Register High                  */
#define SCIDRL(x)   (*((SCI_port)x + 0x07))   /* SCI Data Register Low                   */

Luego, al escribir su controlador, puede hacer lo siguiente:

void sci_init (SCI_port port, ...)
{
  SCICR1(port) = THIS | THAT;
  SCICR2(port) = SOMETHING_ELSE;
  ...
}

Esto es muy útil cuando tiene muchos periféricos idénticos en la MCU, con los mismos registros, pero solo quiere un código para controlarlos.

Tenga en cuenta que para luchar contra el compilador, puede introducir atributos de tipo específicos, por ejemplo, en GCC__attribute__((packed))
@Dzarda Es mejor usar afirmaciones estáticas para eso, ya que son portátiles. Por ejemplostatic_assert(sizeof(mystruct) == sizeof(mystruct.obj1) + sizeofmystruct.obj2) + ..., "Struct padding detected");
Acordado. Acabo de mencionar una alternativa.
@Lundin: entiendo su punto sobre el embalaje. Pero el compilador C agregará relleno de acuerdo con la arquitectura de destino (ISA decidió el requisito de alineación, no el compilador). Por ejemplo, el compilador LLVM, gcc o intel C no debería cambiar la forma en que se empaqueta la estructura para un ISA específico, por ejemplo, ARM. La forma en que se rellena la estructura cambiaría de una ISA a otra ISA debido a los diferentes requisitos de alineación en dos ISA diferentes. Segundo: ¿Qué pasa si uso los tipos uint8_t, uint16_t, uint_32t, etc. para asegurarme de que la estructura esté empaquetada correctamente, entonces no surge el problema con el relleno desconocido?
@nurabha Todos los registros en un mapa de registro no tienen necesariamente el mismo ancho que el requisito de alineación. Entonces, si escribe su mapa de registro como una estructura de digamos uint32_t, y 999 de 1000 registros MCU son de 32 bits pero uno es de 16 bits... entonces todo su mapa de registro se romperá si lo implementa como una estructura y el el compilador agrega bytes de relleno.

En primer lugar, ARM recomienda usar __packedpara obtener el embalaje mínimo. En segundo lugar, si realmente necesita grandes espacios en su estructura (y estos no pueden ser dos estructuras por alguna razón), puede simplemente poner algo como una char[30] reserved1matriz en el medio y así sucesivamente para los otros grandes espacios. Asegúrate de poner los tamaños correctos para estos. Puede verificar dos veces con una afirmación que el desplazamiento del siguiente campo realmente está donde piensa; offsetof() le dará el desplazamiento de un campo.

Escribí esto con un poco de prisa, si necesita más detalles, pregunte a continuación y lo ampliaré (aunque no por varias horas).

Por cierto, parece que TI ya ha escrito el archivo de encabezado en cuestión para usted. Parece que les gusta usar #definepara todo.

Por cierto, como sospechaba, la práctica típica en una gran empresa como TI, que tiene cientos, si no miles, de modelos uC en su cartera, es casi seguro que el archivo de encabezado C se genere a partir de algún archivo HDL maestro (o un archivo similar). ). Buscando un poco en Google, seguro que hay proveedores de software EDA que cuentan con esa característica . TI probablemente usa algo similar, posiblemente desarrollado internamente dado lo grandes que son.
También hay un libro que toca el tema de sincronización de HDL/C-header/documentación para mapas de registros, desde la perspectiva de los fabricantes de circuitos integrados.