Richtige Methode zum Speichern einer Registeradresse auf ARM Cortex M4?

Ich schreibe ein Programm auf STM32F4 Discovery, wo ich die Adresse des CCR-Registers des Timers in einem Zeiger speichern und dann das Register über diesen Zeiger ändern muss, dh

volatile uint32_t* tim_ccr_reg = reinterpret_cast<uint32_t*>(TIM4_BASE + 0x34);
// 0x34 = offset of CCR1 register
*tim_ccr_reg = 1999; // Set CCR1 to 1999

Wenn ich dies allein in einem "leeren Programm" teste, funktioniert es, aber wenn ich mein gesamtes Programm (zusammen mit allen anderen von mir implementierten Funktionen) verwende, verursacht es bei einer dieser Zuweisungen einen schweren Fehler. Jetzt finde ich heraus, ob es mein anderer Code ist, der etwas im Speicher verdrängt, oder ich speichere die Registeradresse falsch und es wird nur angezeigt, wenn ein komplexeres Programm geladen wird. In der Dokumentation habe ich gelesen

Der einfachste Weg, speicherabgebildete Variablen zu implementieren, besteht darin, Zeiger auf feste Adressen zu verwenden.

#define PORTBASE 0x40000000
unsigned int volatile * const port = (unsigned int *) PORTBASE;

Der variable Port ist ein konstanter Zeiger auf eine flüchtige Ganzzahl ohne Vorzeichen, sodass wir auf das speicherabgebildete Register zugreifen können mit:

*port = value; /* write to port */
value = *port; /* read from port */

Dieser Ansatz kann verwendet werden, um auf 8-, 16- oder 32-Bit-Register zuzugreifen, aber achten Sie darauf, die Variable mit dem entsprechenden Typ für ihre Größe zu deklarieren, dh unsigned int für 32-Bit-Register, unsigned short für 16-Bit und unsigned char für 8bit. Sie sollten auch sicherstellen, dass die speicherabgebildeten Register an geeigneten Adressgrenzen liegen, z. B. entweder alle wortausgerichtet oder an ihren natürlichen Größengrenzen ausgerichtet sind, dh 16-Bit-Register müssen auf Halbwortadressen ausgerichtet sein (aber beachten Sie, dass ARM empfiehlt, dass alle Register, unabhängig von ihrer Größe, an Wortgrenzen ausgerichtet werden.

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.faqs/ka3750.html

Dann ging ich zur Dokumentation für CCR-Register für Timer und es heißt

Geben Sie hier die Bildbeschreibung ein

Also bin ich mir jetzt nicht sicher, ist das Register immer 32-Bit und nur 16-Bit werden in einigen Timern verwendet oder ist das Register in einigen Fällen nur 16-Bit und ich muss einen 16-Bit-Zeiger verwenden, um darauf zu schreiben?

Auf jeden Fall würde ich gerne wissen, wie das Timer-CCR-Register richtig in einer Variablen gespeichert wird. Wenn ich im folgenden Befehl auf CCR1 klicke

TIM4->CCR1 = 1999;

und klicke auf "go to definition", es bringt mich zu einem seltsamen Stück Code

/** \brief  ITM Send Character

    This function transmits a character via the ITM channel 0.
    It just returns when no debugger is connected that has booked the output.
    It is blocking when a debugger is connected, but the previous character send is not transmitted.

    \param [in]     ch  Character to transmit
    \return             Character to transmit
 */
static __INLINE uint32_t ITM_SendChar (uint32_t ch)
{
  if ((CoreDebug->DEMCR & CoreDebug_DEMCR_TRCENA_Msk)  &&      /* Trace enabled */
      (ITM->TCR & ITM_TCR_ITMENA_Msk)                  &&      /* ITM enabled */
      (ITM->TER & (1UL << 0)        )                    )     /* ITM Port #0 enabled */
  {
    while (ITM->PORT[0].u32 == 0);
    ITM->PORT[0].u8 = (uint8_t) ch;
  }
  return (ch);
}

Ich bin sehr verwirrt.

Wenn Sie die Header-Dateien für die entsprechende Version der STM32-Standardperipheriebibliothek konsultieren, finden Sie eine Arbeitsdefinition des Timer-Registers, an dem Sie interessiert sind und das mit einem C- oder C++-Compiler kompatibel ist. Möglicherweise verlassen Sie sich zu sehr auf die Kontexthilfe Ihrer IDE, Sie sollten die Quellen wahrscheinlich selbst direkt konsultieren.
Bisher sieht bei mir alles gut aus. Ein schwerer Fehler kann auftreten, wenn Sie versuchen, an eine ungültige Speicheradresse zu schreiben. Überprüfen Sie erneut alle Speicherzeiger, die Ihr Code zu dereferenzieren und zu schreiben versucht. Und außerdem: Da STM32F4 einen 32-Bit-Prozessor verwendet, sollten Sie in der Lage sein, 32-Bit-Variablen für alle Lese- und Schreibvorgänge von Registern zu verwenden. Unbenutzte Bits werden normalerweise als Nullen gelesen. (Vertraue dem aber nicht, ohne das Datenblatt zu überprüfen)
Durch das Beobachten von Registern während der schrittweisen Ausführung habe ich festgestellt, dass beim Testen dies allein die richtige IO-Registeradresse geladen hat, aber wenn ich es zusammen mit anderem Code ausführe, ist dies nicht der Fall. Etwas ändert den Wert von Zeigern, obwohl es nur einen Ort gibt, an dem sie gesetzt werden, und als ich dort nachgesehen habe, sind sie richtig gesetzt. Ich werde jetzt den gesamten Code überprüfen. Danke!
@olitsua - tatsächlich verfügt die STM32-Serie über speicherabgebildete Register, auf die nur mit bestimmten Zugriffsbreiten zugegriffen werden kann, die nicht immer 32 Bit betragen. Sie sprechen mit engagierter Logik.
@ChrisStratton Das bedeutet also, dass ich uint32_t * verwende, um auf uint16_t zuzugreifen, da der Timer 4 über 16-Bit-CCR verfügt. Was ich nicht verstehe, ist, warum es keinen schweren Fehler verursacht, wenn es alleine ausgeführt wird. Aber würde das nicht bedeuten, dass die CCR1-Register verschiedener Timer aufgrund unterschiedlicher Größen nicht durch denselben 0x34-Wert versetzt werden können? Großartig, ich dachte, ich hätte das Problem gelöst, wie man universell auf alle CCRs des Timers zugreifen kann, aber es sieht so aus, als hätte ich das nicht ...

Antworten (1)

Im einfachsten Fall wandeln Sie die Adresse in einen "Zeiger auf einen 32-Bit-Wert" um, dereferenzieren sie und erhalten den Wert.

x=*((int*)(0x40000800+0x34))

(int*) ist das Casting, wodurch die Zahl zu einem Zeiger wird, der auf einen int-Typ zeigt.

*(....) soll den Zeiger dereferenzieren.

0x40000800+0x34 steht in Klammern, um einen Additionsfehler zu vermeiden. Casting hat Vorrang vor der Addition, und die Addition einer ganzen Zahl zu einem Zeiger ist nicht dasselbe wie die Addition von ganzen Zahlen.

Um noch einen Schritt weiter zu gehen, definieren Sie die Peripherie als Strukturen. Es muss vom Compiler garantiert werden, dass es so im Speicher stattfindet, wie die von Ihnen geschriebene Sequenz. Vergessen Sie nicht, dass Strukturen virtuelle Definitionen wie „int“ sind. Zum Beispiel:

struct TIMg_st {volatile u32 CR1,bos0,SMCR,DIER,SR,EGR,CCMR1,bos1,CCER,CNT,PSC,ARR,bos2,CCR1,bos3[6],OR;};

Wenn Sie einen Zeiger erstellen, der auf diese Struktur zeigt (also diesmal kein int-Typ), zum Beispiel:

(struct TIMg_st*)0x40000800

Sie können seine Elemente problemlos erreichen. Zum Beispiel:

*((struct TIMg_st*)0x40000800).CCR1

Oder für ein einfacheres Scripting;

((struct TIMg_st*)0x40000800)->CCR1

Um die Dinge einfacher zu machen, wandeln Sie es einfach in eine Definition um:

#define TIM4 ((struct TIMg_st*)0x40000800)

So jetzt ist es eben

TIM4->CCR1

und dies ist einfach ein vorzeichenloser 32-Bit-Wert. Der Wert im Register kann 16 Bit oder 8 Bit sein, das ist kein Problem. Wichtig ist ihre Zuordnungsbreite und Sie gestalten die Struktur entsprechend. Alle Elemente der Struktur sind uint32, da sie alle 32-Bit-zugewiesen sind.

@ Terraviper-5 danke, aber ich habe deine Frage falsch verstanden und das Hauptanliegen völlig verfehlt, oder? Ich denke, Sie sollten es besser nicht akzeptieren.
Nein, hast du nicht, du hast mir gezeigt, wie diese Strukturen funktionieren und wie man die Adresse eines Registers auf die richtige Weise erhält. Auf diese Weise konnte ich sicher sein, dass der Fehler an einer anderen Stelle in meinem Code lag, und tatsächlich habe ich den dümmsten Fehler aller Zeiten gemacht, ein Objekt einer Referenz zugewiesen und als das Objekt den Gültigkeitsbereich verließ, wurde es gelöscht. Dann habe ich Funktionen für diese Referenz aufgerufen und es hat im Speicher herumgewirbelt, deshalb wurde eine falsche Registeradresse von CCR geladen und darauf zugegriffen, was einen schweren Fehler verursachte. Ich hatte Angst, dass ich uint32_t * nicht verwenden kann, um auf das 16-Bit-Register zuzugreifen, aber es sieht so aus, als könnte ich es so machen.
Als Referenz werden ARM-Peripherieregister normalerweise als 32-Bit-Register definiert (mit Ausnahme der 64-Bit-Register, auf die Sie als 2 separate 32-Bit-Register zugreifen können), selbst wenn das Register nur ein oder zwei echte Bits darunter hat. Es gibt Ausnahmen, aber diese sind in der Regel deutlich vermerkt.