Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen Revision Vorhergehende Überarbeitung
arm_interrupts_in_c [2019/02/04 14:10] – Externe Bearbeitung 127.0.0.1arm_interrupts_in_c [2019/07/22 19:52] (aktuell) huwi
Zeile 5: Zeile 5:
 Um die Interrupt-Programmierung des ARM kennen zu lernen, nehmen wir uns eine verhältnissmäßig einfache Aufgabe vor. Lassen wir eine LED blinken. Die LED über einen Digital-Port ein-, aus- und umzuschalten beherrschen wir bereits. Unsere gewünschte Blinkfrequenz soll ein Timer generieren. Der ARM verfügt über unterschiedlich komplexe Timer. Zum Einstieg suchen wir uns am Besten einen einfachen Timer aus. Timer, die nur über elementare Funktionen verfügen, werden beim STM32F4 als //Basic-Timer// bezeichnet. Einer der Basic-Timer ist //TIM7//. Dieser soll uns ein 100-Millisekunden-Ereignis liefern.  Im Ereignishandler //togglen// wir dann unsere blaue LED. Um die Interrupt-Programmierung des ARM kennen zu lernen, nehmen wir uns eine verhältnissmäßig einfache Aufgabe vor. Lassen wir eine LED blinken. Die LED über einen Digital-Port ein-, aus- und umzuschalten beherrschen wir bereits. Unsere gewünschte Blinkfrequenz soll ein Timer generieren. Der ARM verfügt über unterschiedlich komplexe Timer. Zum Einstieg suchen wir uns am Besten einen einfachen Timer aus. Timer, die nur über elementare Funktionen verfügen, werden beim STM32F4 als //Basic-Timer// bezeichnet. Einer der Basic-Timer ist //TIM7//. Dieser soll uns ein 100-Millisekunden-Ereignis liefern.  Im Ereignishandler //togglen// wir dann unsere blaue LED.
  
->>>{{:tim7blockbild.jpg?600|}}+>{{:tim7blockbild.jpg?600|}}
  
 Der Blick ins [[http://www.mystm32.de/lib/exe/detail.php?id=systemtickc&cache=cache&media=blockbildstm32f4.jpg|Datenblatt]] verrät uns, dass der Basic-Timer TIM7 am Peripherie-Bus APB1 hängt. Somit ist TIM7 der erste Baustein, den wir über einen Peripherie-Bus ansprechen. Ein weiterer neuer Baustein muss diesmal von uns programmiert werden, der Interruptcontroller NVIC. Diesem müssen wir beibringen, dass wir das Timer-Ereignis als Unterbrechungsanforderung behandeln möchten. Fassen wir die Aufgabenstellung kurz zusammen: Der Blick ins [[http://www.mystm32.de/lib/exe/detail.php?id=systemtickc&cache=cache&media=blockbildstm32f4.jpg|Datenblatt]] verrät uns, dass der Basic-Timer TIM7 am Peripherie-Bus APB1 hängt. Somit ist TIM7 der erste Baustein, den wir über einen Peripherie-Bus ansprechen. Ein weiterer neuer Baustein muss diesmal von uns programmiert werden, der Interruptcontroller NVIC. Diesem müssen wir beibringen, dass wir das Timer-Ereignis als Unterbrechungsanforderung behandeln möchten. Fassen wir die Aufgabenstellung kurz zusammen:
Zeile 21: Zeile 21:
 Falls das Tutorial-Projekt nicht offen ist, öffnen Sie dies. Legen Sie bitte ein neues kleines Programm an und laden das Grundgerüst ARM C++ Anwendung. Beachten Sie die Einstellungen für die Zielplattform STM32F4-Discovery. Falls das Tutorial-Projekt nicht offen ist, öffnen Sie dies. Legen Sie bitte ein neues kleines Programm an und laden das Grundgerüst ARM C++ Anwendung. Beachten Sie die Einstellungen für die Zielplattform STM32F4-Discovery.
  
->>>{{:timerintneu.jpg?600|}}+>{{:timerintneu.jpg?600|}}
  
 Erstellen Sie die Programmkopfdokumentation. Übersetzen und Übertragen Sie das noch leere Programm auf den Controller, um die Verbindung zu testen.  Erstellen Sie die Programmkopfdokumentation. Übersetzen und Übertragen Sie das noch leere Programm auf den Controller, um die Verbindung zu testen. 
  
->>><code cpp>+><code cpp>
 //---------------------------------------------------------------------- //----------------------------------------------------------------------
 // Titel     : Beispiel BasicTimer in SiSy STM32 // Titel     : Beispiel BasicTimer in SiSy STM32
Zeile 47: Zeile 47:
 Für das Einschalten des GPIO-Taktes haben wir die Funktion //RCC_AHB1PeriphClockCmd// kennengelernt. Aus der Systematik der Namensgebung sollte sich jetzt auch ohne langes Studium der Treiber-API von ST, der rechte Befehl für unseren //Timer// an //APB1//, finden lassen. Wenn wir **//RCC_AP//** eingeben springt uns die gewünschte Funktion schon ins Auge. Für das Einschalten des GPIO-Taktes haben wir die Funktion //RCC_AHB1PeriphClockCmd// kennengelernt. Aus der Systematik der Namensgebung sollte sich jetzt auch ohne langes Studium der Treiber-API von ST, der rechte Befehl für unseren //Timer// an //APB1//, finden lassen. Wenn wir **//RCC_AP//** eingeben springt uns die gewünschte Funktion schon ins Auge.
  
->>>{{:rcc_ap.jpg?600|}}+>{{:rcc_ap.jpg?600|}}
  
 Die Funktion //RCC_APB1PeriphClockCmd// erwartet wie gehabt zwei Parameter. Zum einen die Identifikation des Gerätes und als Zweites den neuen Takt-Status für das Gerät, in unserem Fall ein //ENABLE//, um den Takt einzuschalten. Die Funktion //RCC_APB1PeriphClockCmd// erwartet wie gehabt zwei Parameter. Zum einen die Identifikation des Gerätes und als Zweites den neuen Takt-Status für das Gerät, in unserem Fall ein //ENABLE//, um den Takt einzuschalten.
  
->>><code cpp>+><code cpp>
 // Takt für Timer 7 einschalten // Takt für Timer 7 einschalten
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);
Zeile 67: Zeile 67:
 Die Struktur für die Basis-Initialisierungen von Timern hat den etwas sperrigen Typbezeichner //TIM_TimeBaseInitTypeDef//. Von dieser Struktur brauchen wir eine Instanz. Die Struktur für die Basis-Initialisierungen von Timern hat den etwas sperrigen Typbezeichner //TIM_TimeBaseInitTypeDef//. Von dieser Struktur brauchen wir eine Instanz.
  
->>><code cpp>+><code cpp>
 TIM_TimeBaseInitTypeDef timInitStruct; TIM_TimeBaseInitTypeDef timInitStruct;
 </code> </code>
Zeile 73: Zeile 73:
 Die Elemente dieser Struktur erlauben uns, bezogen auf den Basis-Takt, für den Timer sehr exakt das gewünschte Ereignis zu konfigurieren. Dazu ist jedoch ein Blick ins Datenblatt (Seite 29) erforderlich: Die Elemente dieser Struktur erlauben uns, bezogen auf den Basis-Takt, für den Timer sehr exakt das gewünschte Ereignis zu konfigurieren. Dazu ist jedoch ein Blick ins Datenblatt (Seite 29) erforderlich:
  
->>>{{:tim7clock.jpg?700|}}+>{{:tim7clock.jpg?700|}}
  
 Der Timer TIM7 wird demnach mit 84 MHz getaktet. Darauf bauen jetzt unsere Überlegungen zum konfigurieren des Timerereignisses auf. Der Timer TIM7 wird demnach mit 84 MHz getaktet. Darauf bauen jetzt unsere Überlegungen zum konfigurieren des Timerereignisses auf.
Zeile 79: Zeile 79:
 Das Strukturelement **TIM_CounterMode** legt fest, wie der Zähler arbeitet. Die beiden einfachsten Modi sind //TIM_CounterMode_Up// oder //TIM_CounterMode_Down//. Der Timer //TIM7// ist laut der eben eingesehen Tabelle ein reiner Up-Conter.  Das Strukturelement **TIM_CounterMode** legt fest, wie der Zähler arbeitet. Die beiden einfachsten Modi sind //TIM_CounterMode_Up// oder //TIM_CounterMode_Down//. Der Timer //TIM7// ist laut der eben eingesehen Tabelle ein reiner Up-Conter. 
  
->>><code cpp>+><code cpp>
 timInitStruct.TIM_CounterMode = TIM_CounterMode_Up; timInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
 </code> </code>
Zeile 85: Zeile 85:
 Das Strukturelement **TIM_ClockDivision** ermöglicht es, bei Bedarf den Basistakt des Timers grob mit den Teilern 2 oder 4 vorzuteilen.  Das Strukturelement **TIM_ClockDivision** ermöglicht es, bei Bedarf den Basistakt des Timers grob mit den Teilern 2 oder 4 vorzuteilen. 
  
->>><code cpp>+><code cpp>
 timInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; timInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
 </code> </code>
Zeile 93: Zeile 93:
 Das Strukturelement **TIM_Prescaler** ist ein 16 Bit Wert und erlaubt laut Hilfe beliebige Werte zwischen 0 und 65535 (0x0000 - 0xFFFF). Mit dem Vorteilerwert 1000 takten wir den Timer schon mal auf 84000 Hz herunter.  Das Strukturelement **TIM_Prescaler** ist ein 16 Bit Wert und erlaubt laut Hilfe beliebige Werte zwischen 0 und 65535 (0x0000 - 0xFFFF). Mit dem Vorteilerwert 1000 takten wir den Timer schon mal auf 84000 Hz herunter. 
  
->>><code cpp>+><code cpp>
 timInitStruct.TIM_Prescaler = 1000; timInitStruct.TIM_Prescaler = 1000;
 </code> </code>
Zeile 99: Zeile 99:
 Um jetzt aus den 84 kHz ein 100 ms Ereignis zu erhalten, lassen wir den Timer von 0 bis 8400 zählen. Das Strukturelement **TIM_Period** erlaubt ebenfalls Werte von 0 bis 65535. Um jetzt aus den 84 kHz ein 100 ms Ereignis zu erhalten, lassen wir den Timer von 0 bis 8400 zählen. Das Strukturelement **TIM_Period** erlaubt ebenfalls Werte von 0 bis 65535.
  
->>><code cpp>+><code cpp>
 timInitStruct.TIM_Period = 8400; timInitStruct.TIM_Period = 8400;
 </code> </code>
Zeile 105: Zeile 105:
 Die so vorbereitete Initialisierungsstruktur übergeben wir mit der Funktion //TIM_TimeBaseInit// an den gewünschten Timer //TIM7// Die so vorbereitete Initialisierungsstruktur übergeben wir mit der Funktion //TIM_TimeBaseInit// an den gewünschten Timer //TIM7//
  
->>><code cpp>+><code cpp>
 TIM_TimeBaseInit(TIM7, &timInitStruct); TIM_TimeBaseInit(TIM7, &timInitStruct);
 </code> </code>
Zeile 118: Zeile 118:
 Das für unseren Fall geeignete Unterbrechungsereignis ist //TIM_IT_Update//. Es wird ausgelöst, wenn die Timerperiode erreicht ist und das Zählregister neu geladen wird. Das für unseren Fall geeignete Unterbrechungsereignis ist //TIM_IT_Update//. Es wird ausgelöst, wenn die Timerperiode erreicht ist und das Zählregister neu geladen wird.
  
->>><code cpp>+><code cpp>
 TIM_ITConfig(TIM7, TIM_IT_Update, ENABLE); TIM_ITConfig(TIM7, TIM_IT_Update, ENABLE);
 </code> </code>
Zeile 128: Zeile 128:
 Die Initialisierungsstuktur für den NVIC hat den Typbezeichner //NVIC_InitTypeDef//. Langsam gewöhnen wir uns an die Systematik der Bezeichner für die Initalisierungsstrukturen. Diese bestehen immer aus //Gerätenamen**_InitTypeDef**//. Die Initialisierungsstuktur für den NVIC hat den Typbezeichner //NVIC_InitTypeDef//. Langsam gewöhnen wir uns an die Systematik der Bezeichner für die Initalisierungsstrukturen. Diese bestehen immer aus //Gerätenamen**_InitTypeDef**//.
  
->>><code cpp>+><code cpp>
 NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitTypeDef NVIC_InitStructure;
 </code> </code>
Zeile 134: Zeile 134:
 Das Strukturelement **NVIC_IRQChannel** legt fest, welcher IRQ jetzt konfiguriert wird. Die Identifikation des Ereignisses setzt sich wie folgt zusammen: //Gerät**_IRQn**//. Wir hatten uns für den Timer //TIM7// entschieden. Das Strukturelement **NVIC_IRQChannel** legt fest, welcher IRQ jetzt konfiguriert wird. Die Identifikation des Ereignisses setzt sich wie folgt zusammen: //Gerät**_IRQn**//. Wir hatten uns für den Timer //TIM7// entschieden.
  
->>><code cpp>+><code cpp>
 NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn; NVIC_InitStructure.NVIC_IRQChannel = TIM7_IRQn;
 </code> </code>
Zeile 140: Zeile 140:
 Das Strukturelement NVIC_IRQChannelCmd legt fest, welchen neuen Zustand der ausgewählte IRQ erhalten soll. Wir möchten diesen einschalten, also weisen wir ein //ENABLE// zu. Das Strukturelement NVIC_IRQChannelCmd legt fest, welchen neuen Zustand der ausgewählte IRQ erhalten soll. Wir möchten diesen einschalten, also weisen wir ein //ENABLE// zu.
  
->>><code cpp>+><code cpp>
 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
 </code> </code>
Zeile 146: Zeile 146:
 Mit dem Strukturelement **NVIC_IRQChannelPreemptionPriority** weisen wir dem Kanal seine Priorität zu. So wichtig ist unsere LED nicht, also geben wir dieser die geringste Priorität. Mit dem Strukturelement **NVIC_IRQChannelPreemptionPriority** weisen wir dem Kanal seine Priorität zu. So wichtig ist unsere LED nicht, also geben wir dieser die geringste Priorität.
  
->>><code cpp>+><code cpp>
 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
 </code> </code>
Zeile 152: Zeile 152:
 Für das Strukturelement **NVIC_IRQChannelSubPriority** gelten die gleichen Überlegungen. Für das Strukturelement **NVIC_IRQChannelSubPriority** gelten die gleichen Überlegungen.
  
->>><code cpp>+><code cpp>
 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
 </code> </code>
Zeile 158: Zeile 158:
 Damit sollten wir alles Notwendige für die Initialisierung festgelegt haben und können diese durchführen. Die Funktionen, in die wir unsere Initialisierungsstrukturen hineinwerfen, folgen auch immer der gleichen Benennungssystematik //Gerät**_Init**//. Damit sollten wir alles Notwendige für die Initialisierung festgelegt haben und können diese durchführen. Die Funktionen, in die wir unsere Initialisierungsstrukturen hineinwerfen, folgen auch immer der gleichen Benennungssystematik //Gerät**_Init**//.
  
->>><code cpp>+><code cpp>
 NVIC_Init(&NVIC_InitStructure); NVIC_Init(&NVIC_InitStructure);
 </code> </code>
Zeile 166: Zeile 166:
 Der Timer hat seinen Takt und ist konfiguriert. Der Interrupt wurde initialisiert und der NVIC angewiesen wie er damit umzugehen hat. Jetzt ist es wohl Zeit auf den Startknopf unserer Stoppuhr zu drücken. Das Einschalten des so vorbereiteten Timers erfolgt mit der Funktion //TIM_Cmd//. Diese erwartet zwei Parameter, den Timer und das Kommando //ENABLE// oder //DISABLE//. Der Timer hat seinen Takt und ist konfiguriert. Der Interrupt wurde initialisiert und der NVIC angewiesen wie er damit umzugehen hat. Jetzt ist es wohl Zeit auf den Startknopf unserer Stoppuhr zu drücken. Das Einschalten des so vorbereiteten Timers erfolgt mit der Funktion //TIM_Cmd//. Diese erwartet zwei Parameter, den Timer und das Kommando //ENABLE// oder //DISABLE//.
  
->>><code cpp>+><code cpp>
 TIM_Cmd(TIM7, ENABLE); TIM_Cmd(TIM7, ENABLE);
 </code> </code>
Zeile 173: Zeile 173:
 Ganz so schnell schießen die Preußen nun aber auch wieder nicht. Es ist natürlich nötig erst noch eine Funktion zur Ereignisbehandlung zu schreiben. Derartige Funktionen  sind als //extern "C" void// zu deklarieren und besitzen fest vorgegebene Namen, die es dem Compiler ermöglichen, die ISR dem richtigen Interrupt zuzuordnen. Die Namen der Eventhandler folgen immer dem Muster //Gerät**_IRQHandler**//. Wenn wir in SiSy die Zeichenfolge //TIM7_// eingeben können wir in der angebotenen Liste den Namen der ISR auswählen. Ganz so schnell schießen die Preußen nun aber auch wieder nicht. Es ist natürlich nötig erst noch eine Funktion zur Ereignisbehandlung zu schreiben. Derartige Funktionen  sind als //extern "C" void// zu deklarieren und besitzen fest vorgegebene Namen, die es dem Compiler ermöglichen, die ISR dem richtigen Interrupt zuzuordnen. Die Namen der Eventhandler folgen immer dem Muster //Gerät**_IRQHandler**//. Wenn wir in SiSy die Zeichenfolge //TIM7_// eingeben können wir in der angebotenen Liste den Namen der ISR auswählen.
  
->>><code cpp>+><code cpp>
 extern "C" void TIM7_IRQHandler() extern "C" void TIM7_IRQHandler()
 {   {  
Zeile 186: Zeile 186:
 Jetzt sollte alles beieinander sein und wir tasten uns an die Lösung der Aufgabe heran. Schreiben Sie zuerst die Kommentare //WAS// in welcher Reihenfolge, also //WANN// zu tun ist. Beachten Sie in welchen Programmbereichen, also //WO// die einzelnen Aktionen zukünftig ausgeführt werden sollen. Jetzt sollte alles beieinander sein und wir tasten uns an die Lösung der Aufgabe heran. Schreiben Sie zuerst die Kommentare //WAS// in welcher Reihenfolge, also //WANN// zu tun ist. Beachten Sie in welchen Programmbereichen, also //WO// die einzelnen Aktionen zukünftig ausgeführt werden sollen.
  
->>><code cpp>+><code cpp>
 //---------------------------------------------------------------------- //----------------------------------------------------------------------
 // Titel     : ENTWURF Beispiel BasicTimer in SiSy STM32 // Titel     : ENTWURF Beispiel BasicTimer in SiSy STM32
Zeile 241: Zeile 241:
 Sie wissen ja, tief durchatmen, noch mal drüber schauen und dann selbst und bewusst die Befehlszeilen eingeben. Sie wissen ja, tief durchatmen, noch mal drüber schauen und dann selbst und bewusst die Befehlszeilen eingeben.
  
->>><code cpp>+><code cpp>
 //---------------------------------------------------------------------- //----------------------------------------------------------------------
 // Titel     : Beispiel BasicTimer in SiSy STM32 // Titel     : Beispiel BasicTimer in SiSy STM32
Zeile 329: Zeile 329:
 Übersetzen und übertragen Sie das Programm. Testen Sie die Anwendung. Übersetzen und übertragen Sie das Programm. Testen Sie die Anwendung.
  
->>>{{:blaueled.jpg?500|}}+>{{:blaueled.jpg?500|}}
  
 ====== Videozusammenfassung ====== ====== Videozusammenfassung ======
 Das war ein ziemlicher Aufwand den wir betrieben haben, um eine LED blinken zu lassen. Beachten sie jedoch, dass wir dies mit einem Interrupt gelöst haben. Dieses Programmiermodell ist zwar  aufwändig, aber auch mächtig. Hier dieser Abschnitt wiederum als Videozusammenfassung. Das war ein ziemlicher Aufwand den wir betrieben haben, um eine LED blinken zu lassen. Beachten sie jedoch, dass wir dies mit einem Interrupt gelöst haben. Dieses Programmiermodell ist zwar  aufwändig, aber auch mächtig. Hier dieser Abschnitt wiederum als Videozusammenfassung.
  
->>><html><iframe width="640" height="400" src="https://www.youtube.com/embed/pGzti_StEtI" frameborder="0" allowfullscreen></iframe></html>+><html><iframe width="640" height="400" src="https://www.youtube.com/embed/pGzti_StEtI" frameborder="0" allowfullscreen></iframe></html>
  
 ====== Nächstes Thema ====== ====== Nächstes Thema ======