Forum: Mikrocontroller und Digitale Elektronik Interrupt-Implementierung ARM


von Daniel L. (ifdowhile)


Angehängte Dateien:

Lesenswert?

Guten Tag,
ich bin Student und sollte mich für eine Hilfstätigkeit bei einem 
Professor mit Mikrocontrollern beschäftigen. Als Testobjekt wurde mir 
ein STM32F100RB in die Hand gegeben. Meine jetzige Aufgabe ist es, mich 
mit Timern, speziell dem Aufrufen von ISR's durch Timer, 
auseinanderzusetzen.

Als Aufgabenstellung setzte ich mir, die LED zu togglen, wann immer eine 
gewisse Zeitvorbei ist.
Dieses Togglen wollte ich in eine ISR packen, welche jedoch in meinem 
Programm nie aufgerufen wird.
Habe ich beim Initialisiern des Interruptes irgendetwas 
vergessen/übersehen?
Der Programmcode besteht teilweise aus Programmcodes, die hier 
hochgeladen wurden.

Mein Programmcode, bzw. ein Screenshot davon,ist angehängt.
Die IDE ist µVision5.



Würde mich sehr über Hilfe freuen

Daniel

von Oliver S. (oliverso)


Lesenswert?

Daniel L. schrieb:
> Die IDE ist µVision5.

Und die benutzt tatsächlich jpg-Dateien?

Oliver

von Jim M. (turboj)


Lesenswert?

Code als Bild posten ist böse. Dieses Forum kann *.c Dateien ordentlich 
darstellen.

Dein Code hat vielzuviele magische Konstanten. Dafür gibt es sicher 
Definitionen im Header vom µC Hersteller.

Dein Code setzt zwar die NVIC_InitSt member aber er eigentliche Aufruf 
von NVIC_Init() fehlt. Damit bleibt der Interrupt im NVIC disabled.

TIM_ITConfig() vor der Initialisierung der entsprechenden Clock kann 
interessante Nebeneffekte haben - nur nicht die die man erwartet.

von Michael Engel (Gast)


Angehängte Dateien:

Lesenswert?

Hier könnten einige Dinge schiefgehn. Interrupt-Routinen werden bei Keil 
üblicherweise mit dem Qualifier __irq versehen - und es ist nirgendwo 
ersichtlich, wo der Vektor für den Interrupt-Handler eingetragen wird.

Aber es könnte erstmal ein viel schwerwiegenderes Problem vorliegen - 
laut dem Datenblatt 
(http://www.st.com/resource/en/datasheet/stm32f100v8.pdf) besitzen die 
STM32F100Rx den TIM4-Timer nicht (S. 11, siehe Screenshot), den du 
verwendest. Hast du mal im Debugger geschaut, ob der Code in der 
Endlos-while-Schleife landet oder nicht doch im HardFault-Handler?

Vielleicht hilft das ja schonmal ein wenig weiter...

-- Michael

von H.Joachim S. (crazyhorse)


Lesenswert?

NVIC_SetVector(..);
fehlt auch noch.

von Michael Engel (Gast)


Lesenswert?

Ah, genau lesen hilft. Wenn das Board das STM32VL-Discovery ist (die 
Angabe fehlt leider), hat es einen STM32F100RBT6B mit 128 KB Flash und 8 
KB RAM. Der hat dann laut Datenblatt den Timer TIM4. Warum auch immer 
man den einspart, bei den Teilen wäre ich davon ausgegangen, dass das 
Silizium bei allen gleich ist.

Also ist's vermutlich eine Kombination der anderen im Thread genannten 
Probleme...

-- Michael

von W.S. (Gast)


Lesenswert?

Daniel L. schrieb:
> Meine jetzige Aufgabe ist es, mich
> mit Timern, speziell dem Aufrufen von ISR's durch Timer,
> auseinanderzusetzen.

Na, dann tu es doch einfach! Lies die grundlegenden Papiere über die 
Cortexe (deiner ist m.W. ein M3) und lies das Referenzmaual zum Chip - 
wenigstens auszugsweise. Ebenso guck dir mal einen Startupcode an - aber 
keinen solchen, der in C zusammengebraten ist, sondern einen in 
Assembler.

Aber mit so einem Code, wie du gepostet hast? Damit wird das nichts. Laß 
diese ST-Lib weg und befasse dich DIREKT mit der Hardware, denn genau 
DAS war wohl das eigentliche Anliegen des Prof's.

Und nochwas zur Struktur deines mageren main's:
Sowas macht man im Allgemeinen so:
1
int main (void)
2
{ SetzeMeineClocksAuf();
3
  SetzeMeinePinsAuf();
4
  SetzeMeinenTaktAuf();
5
  SetzeMeinSonstigesZeugsAuf();
6
7
immerzu:
8
  MacheHierWasVernünftiges();
9
  MacheHierNochwasvernünftiges();
10
goto immerzu;
11
}
Und wenn du einen Horror vor dem Goto hast, dann eben das alberne 
while(1).

In jedem Falle solltest du deine main-Quelle nicht zumüllen, sondern du 
solltest deine Quelle in vernünftige Teile aufteilen.

Also eine separate Quelle für das Aufsetzen des Chips (Clocks, also 
Taktversorgung der gewünschten Peripherie-Cores), Pins (also welche 
Pinfunktionen), Takt (also PLL, Oszillator(en) und Frequenzen)

Ebenso eine separate Quelle für die Systemuhr (sowas braucht man 
eigentlich fast immer), dann eine für eventuelle UART's, dann eine für 
Kommandoauswertungen (sofern kommuniziert werden soll), eine für diverse 
I/O-Konvertierungen und so weiter.

Also, bringe mal ne Systematik rein. Das hilft ungemein.

W.S.

von W.S. (Gast)


Lesenswert?

Michael Engel schrieb:
> und es ist nirgendwo
> ersichtlich, wo der Vektor für den Interrupt-Handler eingetragen wird.

Der steht im Startupcode - und zwar an genau DER Stelle, die der 
chipabhängigen Interruptnummer entspricht.

W.S.

von Jim M. (turboj)


Lesenswert?

H.Joachim S. schrieb:
> NVIC_SetVector(..);
> fehlt auch noch.

Bei den Cortex-M µC normalerweise unnötig, da die eine feste 
Vektortabelle im Flash haben.

Dafür müssen die Handler Funktionen einen korrekten Namen inklusive 
Groß-u. Kleinschreibung haben. Dekorationen wie __irq sind AFAIK nicht 
erforderlich, da im ABI die Interrupt Handler wie normale C Funktionen 
aussehen.

von Christopher J. (christopher_j23)


Lesenswert?

Um einen Interrupt eines Timers nutzen zu können musst du das 
entsprechende Bit im CR des Timers setzen, z.B. so:
1
    /* enable update interrupt */
2
    TIM4->DIER |= TIM_DIER_UIE;

Die ganze Interruptinitialisierung kannst du ebenfalls der Einfachheit 
halber auf eine einzige Funktion herunterbrechen, die dann für alle 
Cortex-M herstellerübergreifend immer gleich lautet, z.B.:
1
  /* enable interrupt for timer 4*/
2
    NVIC_EnableIRQ(TIM4_IRQn);

Damit läuft der Interrupt mit Standardpriorität, was gleichzeitig die 
höchste Priorität darstellt. Legst du keine Prioritäten fest ist alles 
so simpel wie beim AVR.

In deinem Startup-Code findest du den passenden Namen für deine 
Interrupt-Routine, wobei TIM4_IRQHandler eigentlich passen sollte.

Außerdem musst du im RCC nicht nur den Takt des Timers einschalten 
sondern auch noch den Takt des GPIO und du musst diesen noch als Ausgang 
konfigurieren bevor da was blinken kann.

Hier hast du ein Bare-Metal-Beispiel für einen STM32F103C8 mit LED an 
PC13:
https://github.com/ChristianRinn/bare_metal_stm32f103c8/blob/master/src/example02_gpio-timer-interrupt/main.c

Sieh das als Leitfaden, wo du siehst was du grundsätzlich brauchst um 
einen Pin per Timer zu togglen. Ich empfehle dir grundsätzlich dir das 
passende Reference Manual zu deinem Chip zu schnappen und das von Null 
auf selber zu schreiben. Nur so lernst die Register kennen und verstehst 
wie der Timer funktioniert, was der alles kann, usw.

von Stefan F. (Gast)


Lesenswert?

Ich benutze die System Workbench for STM32. Wenn ich damit ein Projekt 
vom Typ "No Firmware" (also ohne die alte SPL und ohne HAL) beginne, 
erzeugt mir die IDE eine unvollständig befüllte Interrupt-Vektor 
Tabelle. Sie enthält nur eine Hand voll Einträge, die man allerdings 
leicht "manuell" erhänzen kann.

Du hast zwar eine andere IDE, aber möglicherweise das selbe 
Fehlerursache.

von Christopher J. (christopher_j23)


Lesenswert?

Stefan U. schrieb:
> Du hast zwar eine andere IDE, aber möglicherweise das selbe
> Fehlerursache.

Auch wenn ich es abgrundtief Verabscheue wenn irgendeine IDE im 
Hintergrund ohne mein Wissen oder Zutun irgendetwas macht, ist das 
Problem in diesem Fall in erster Linie das weder ein Takt am GPIO 
anliegt, noch der Pin als Ausgang konfiguriert ist und wenn man das DIER 
des Timers nicht richtig konfiguriert wird er auch niemals einen 
Interrupt auslösen.

von Daniel L. (ifdowhile)


Lesenswert?

Vielen Dank an jeden hier,
nach dem Implementieren von den Vorschlägen hier funktioniert es 
einwandfrei.
1
void TIM4_IRQHandler(void){    
2
  TIM_ClearITPendingBit(TIM4,TIM_IT_Update); //Säubern des EventFlags
3
  if(GPIO_PinRead(GPIOC,8)){         //Wenn Nulldurchlauf und Pin-Nr 8 gesetzt
4
      GPIO_PinWrite(GPIOC,8,0);      //Rücksetzen des Pins (LED rechts unten, Blau?)
5
    }
6
    else{                           //Wenn Nulldurchlauf und Pin-Nr 8 nicht gesetzt
7
        GPIO_PinWrite(GPIOC,8,1);    //Setzen des Pins
8
    }
9
}
10
11
void Timer_Init(void){
12
  TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
13
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
14
  TIM4->ARR=0x9895;//Hexadezimal für 39061=40MHz/1024
15
  TIM4->PSC=0x0400; //Hexadezimal für 1024
16
  TIM4->SR = 0;
17
  TIM4->DIER |= TIM_DIER_UIE;
18
  NVIC_EnableIRQ(TIM4_IRQn);
19
  TIM4->CR1 |= TIM_CR1_CEN;//T4 -> Run
20
}
21
22
void GPIO_Init(void){
23
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
24
  GPIO_PortClock(GPIOC,1);
25
  GPIOC->BSRR = (1UL << 8);         // set
26
  GPIO_PinConfigure(GPIOC, 8, GPIO_OUT_PUSH_PULL, GPIO_MODE_OUT10MHZ); //Config des Pins Nr8, LED rechts unten
27
}
28
29
int main (void){  
30
  
31
  Timer_Init();
32
  
33
  GPIO_Init();
34
  
35
  while(1);
36
}

Der wahrscheinlich ausschlaggebene Punkt war das Einfügen von
1
TIM4->DIER |= TIM_DIER_UIE;
2
NVIC_EnableIRQ(TIM4_IRQn);
,also dem richtigen Initialisieren des Interrupts.

Vielen Dank

von W.S. (Gast)


Lesenswert?

Jim M. schrieb:
> Dekorationen wie __irq sind AFAIK nicht
> erforderlich, da..

.. ja? Was?

Mag sein, daß der GCC da keine Unterschiede macht, aber mein 
dringendster Rat in dieser Sache ist:

Sei nicht so faul und schreib das __irq einfach dazu.

Das wird schon deine Finger nicht gar zu sehr abnützen.

W.S.

von Gebhard R. (Firma: Raich Gerätebau & Entwicklung) (geb)


Lesenswert?

Gerade solche Sachen lassen sich wunderbar am Keil-Simulator 
nachvollziehen. man sieht wie der Zähler eingestellt ist, wie er zählt, 
welche Flags gesetzt werden und, und...
Einfach mal ausprobieren.

Grüsse

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.