Problem beim Schreiben von Registerzuordnungen unter Verwendung der C-Struktur für einen TI-Mikrocontroller

Ich schreibe Registerkarten in C für ein TI ARM-basiertes Mikrocontroller-Board. Hier der Link zum Datenblatt .

Ich verwende die folgenden Richtlinien, wie Register Maps in C geschrieben werden sollten: Register Maps . Diese Richtlinien ähneln ARMs CMSIS (Cortex Microcontroller Software Interface Standard) zum Schreiben von C-Code.

Ich habe ein Problem mit dem Schreiben von System Control Register Maps (siehe Abschnitt 5.5 Seite 237 ff. im Datenblatt) mit C struct. Alle Register haben eine Größe von 32 Bit

  • Die Basisadresse ist 0x400F.E000.
  • Register 1: Geräteidentifikation 0 (DID0), Offset 0x000
  • Register 2: Geräteidentifikation 1 (DID1), Offset 0x004
  • Register 3: Brown-Out Reset Control (PBORCTL), Offset 0x030
  • Register 4: Raw Interrupt Status (RIS), Offset 0x050
  • usw..

Wenn ich jetzt so eine Struktur schreibe:

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
}

Das Hauptproblem, mit dem ich konfrontiert bin, ist, wie PBORCTL und RIS in der Struktur platziert werden, da sie sich in Bezug auf die Basisadresse der Struktur bei Offset 0x030 und 0x050 befinden. Ich habe noch nicht zu viel Bit-Level-C-Programmierung gemacht, daher könnte diese Frage zu einfach sein, aber ich weiß nicht, wie ich das machen soll.

Sie müssten die Struktur mit genügend ungenutzten Bytes auffüllen, um das Loch zu füllen.
Normalerweise ist es hilfreich, zusammen mit dem Datenblatt für den uC selbst eine Kopie des Compiler-Benutzerhandbuchs zu haben und einige Zeit damit zu verbringen, die Header-Dateien zu durchsuchen, die der Hersteller für den für diesen Chip spezifischen Compiler bereitstellt. Sie stellen normalerweise vordefinierte Makros (#defines) und andere Compiler-spezifische Konstrukte bereit, die definieren, wie auf alle Systemkonfigurationsregister zugegriffen wird. Es sieht so aus, als ob das, was Sie schreiben, bereits vom Anbieter in einer Header-Datei bereitgestellt wurde?
@mixed_signal_old: Ja, es sind bereits Registerzuordnungen in der Datei <device>.h definiert, aber das Problem ist, dass die Registerdefinitionen für einige ältere Geräte gültig zu sein scheinen und daher für mein aktuelles Gerät nicht verwendbar sind (stimmt nicht genau mit dem aktuellen Gerätedatenblatt überein, das ich habe ). Ich kann es an mein aktuelles Gerät anpassen, aber ich schreibe lieber von Grund auf neu

Antworten (2)

Strukturen sind zum Schreiben von Registerzuordnungen ungeeignet, da in einer Struktur zu Ausrichtungszwecken an beliebiger Stelle Füllbytes hinzugefügt werden können. Dies hängt vom Compiler ab - Sie müssen sicherstellen, dass kein Padding verwendet wird (z. B. durch ein statisches Assert).

Außerdem kann man beim Schreiben großer Strukturen, die eine Registerkarte widerspiegeln sollen, leicht Fehler machen, man muss jedes einzelne Byte richtig machen, sonst geht alles kaputt. Dies macht die Struktur während der Wartung angreifbar.

Ich würde Ihnen dringend empfehlen, Registerkarten über Makros zu schreiben, wie zum Beispiel:

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

Dies hat auch den Vorteil, dass es zu 100 % auf jeden C-Compiler portierbar ist.

Alternativ könnten Sie etwas Komplizierteres tun, wie zum Beispiel:

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                   */

Wenn Sie dann Ihren Treiber schreiben, können Sie folgendermaßen vorgehen:

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

Dies ist sehr nützlich, wenn Sie viele identische Peripheriegeräte auf der MCU mit denselben Registern haben, aber nur einen Code zur Steuerung wünschen.

Beachten Sie, dass Sie, um den Compiler zu bekämpfen, spezifische Typattribute einführen können, z. B. in GCC__attribute__((packed))
@Dzarda Es ist besser, dafür statische Asserts zu verwenden, da sie portabel sind. Zum Beispielstatic_assert(sizeof(mystruct) == sizeof(mystruct.obj1) + sizeofmystruct.obj2) + ..., "Struct padding detected");
Einverstanden. Ich habe gerade eine Alternative erwähnt.
@Lundin: Ich verstehe Ihren Standpunkt zum Packen. Aber der C-Compiler fügt Padding entsprechend der Zielarchitektur hinzu (von ISA festgelegte Ausrichtungsanforderung, nicht der Compiler). Beispielsweise sollten LLVM-, gcc- oder Intel-C-Compiler nicht die Art und Weise ändern, wie die Struktur für eine bestimmte ISA, z. B. ARM, gepackt wird. Die Art und Weise, wie die Struktur aufgefüllt wird, würde sich aufgrund unterschiedlicher Ausrichtungsanforderungen in zwei verschiedenen ISA von einer ISA zu einer anderen ISA ändern. Zweitens: Was ist, wenn ich die Typen uint8_t, uint16_t, uint_32t usw. verwende, um sicherzustellen, dass die Struktur richtig gepackt ist, dann tritt das Problem mit unbekanntem Padding nicht auf?
@nurabha Alle Register in Registerkarten haben nicht unbedingt die gleiche Breite wie die Ausrichtungsanforderung. Wenn Sie also Ihre Registerzuordnung als Struktur von sagen wir mal uint32_t schreiben und 999 von 1000 MCU-Registern 32-Bit sind, aber eine 16-Bit ... dann wird Ihre gesamte Registerzuordnung beschädigt, wenn Sie sie als Struktur implementieren und die Compiler fügt Füllbytes hinzu.

Erstens empfiehlt ARM die Verwendung __packed, um die minimale Verpackung zu erhalten. Zweitens, wenn Sie wirklich große Lücken in Ihrer Struktur brauchen (und das können aus irgendeinem Grund nicht zwei Strukturen sein), können Sie einfach so etwas wie ein char[30] reserved1Array dazwischen setzen und so weiter für die anderen großen Lücken. Stellen Sie sicher, dass Sie die richtigen Größen für diese verwenden. Sie könnten mit einem Assert noch einmal überprüfen, ob der Offset des nächsten Felds wirklich dort ist, wo Sie denken; offsetof() gibt Ihnen den Offset eines Feldes.

Ich habe das etwas in Eile geschrieben, wenn Sie weitere Details benötigen, fragen Sie unten und ich werde es erweitern (wenn auch nicht für einige Stunden).

Übrigens sieht es so aus, als hätte TI die betreffende Header-Datei bereits für Sie geschrieben . Sie scheinen es zu mögen, #definefür alles zu verwenden.

Übrigens, wie ich vermutet habe, besteht die typische Praxis bei einem großen Unternehmen wie TI, das Hunderte, wenn nicht Tausende von uC-Modellen in seinem Portfolio hat, mit ziemlicher Sicherheit darin, dass diese C-Header-Datei aus einer Master-HDL-Datei (oder einer ähnlichen Datei) generiert wird ). Googeln Sie ein wenig, es gibt sicher EDA-Softwareanbieter, die mit einer solchen Funktion auftrumpfen . TI verwendet wahrscheinlich etwas Ähnliches, das möglicherweise intern entwickelt wurde, wenn man bedenkt, wie groß sie sind.
Es gibt auch ein Buch , das sich mit dem Thema HDL/C-Header/Dokumentationssynchronisation für Registerkarten aus der Sicht von IC-Herstellern befasst.