Forum: Mikrocontroller und Digitale Elektronik Interrupt wird ständig aufgerufen


von Sebastian Kaulitz (Gast)


Angehängte Dateien:

Lesenswert?

Hallo

Ich hantiere nun schon ein paar Mal damit herum, aber irgendwie schaffe 
ich es nicht, dass meine Interrupts richtig funktionieren.

Ich habe vor die Digits per Multiplex anzusteuern.
Dazu habe ich mir überlegt.
Jede 500us wird der Systick Interrupt aufgerufen und der digitCounter 
inkrementiert. Die Periode kann ich ja noch anpassen.
Und jede Sekunde wird der Interrupt Timer 6 aufgerufen, der den 
digitValue dekrementiert.
Ich habe 5 Digits und der digitValue startet bei 99999. Die Daten werden 
bei jedem Aufruf der Systick Interrupts gesendet und die Digits 
geswitched.

Jetzt habe ich 2 Probleme:

1. Mein 1s-Takt Interrupt kommt irgendwie öfters als der Systick 
Interrupt.
Eigentlich sollte es ja genau umgekehrt sein. Im Debugger zählt der 
digitValue nämlich schneller ab als der andere digitCounter hoch.

2. Habe ich das Problem, dass immer im Anschluss an den Systick 
Interrupt den Counter hochzählen und gleich die die Daten senden und die 
Digits switchen muss. Das heisst also, dass ich die Funktion 
switchDigit(digitCounter, digitValue, digitCode); in der der Switch-Case 
enthalten ist, ausführen muss.
Aber für den Interrupt ist diese Funktionsausführung zeitlich sicher zu 
lange.

Wie kann ich das irgendwie besser machen?
Mir fällt hierzu leider nichts Besseres ein.

Ich bedanke mich jetzt schon für eure Hilfestellung.

PS: Init Timer 6 ist im tim6.c, für den Systick im delay.c.
Die Funktion die die Daten sendet und digits switchet im digitcommands 
und in definitions sind die Definitionen. Im it.c stecken die 
Interrupts.

von Sebastian Kaulitz (Gast)


Lesenswert?

Es handelt sich hier um den STM32F051R8T6 und KeilV5.
Sorry, das hatte ich noch vergessen.

von Purzel H. (hacky)


Lesenswert?

Ohne jetzt den Code angesehen zu haben... Die Inebtriebnahme von 
Interrups ist nicht trivial. Ich mach das ueblicherweise so.

1. Interruptprozedur deklarieren, und das Trivialste machen lassen,
   bedeutet
   1. die Quelle befriedigen, zB Zuruecksetzen
   2. einen Pin toggeln
   3. interrupt verlassen
2. Interrupt vor dem loop() initialisieren
3. Interrupt vor der loop() enablen
4. main() schreiben, etwas wie

 main() {
  init();
  while (1) {
   togglepin();
   delay()
  }
 }

4. alle interrupt enablen

mit dem ozilloskop die beiden toggel pins nachmessen.

: Bearbeitet durch User
von Peter D. (peda)


Lesenswert?

Sebastian Kaulitz schrieb:
> Aber für den Interrupt ist diese Funktionsausführung zeitlich sicher zu
> lange.

Es gibt auch keinerlei Grund, im Interrupt mehr als nötig zu machen.
Sobald ein neuer Wert vorliegt, berechnet das Main die neuen Digits und 
legt sie in einem Ausgabearray ab. Möglichst nicht schneller als 2..5/s, 
damit es ergonomisch ist (nicht flackert).
Der MUX-Interupt nimmt dann nur das Digit aus dem Array und schaltet ein 
Digit weiter.
Damit kein Ghosting auftritt, schaltet er erst alle Digittreiber aus, 
gibt das neue Digit aus und schaltet den dazu gehörenden Digittreiber 
ein.

von Sebastian Kaulitz (Gast)


Lesenswert?

Im Moment laufen bei mir alle Interrupts durchgehend. Das ist vielleicht 
in diesem Fall doch nicht ganz geschickt.
Vielen Dank also für die Hinweise. Ich werde das mal versuchen so zu 
implementieren wie ihr es vorschlägt.

Peter D. schrieb:
> Sebastian Kaulitz schrieb:

> Sobald ein neuer Wert vorliegt, berechnet das Main die neuen Digits und
> legt sie in einem Ausgabearray ab. Möglichst nicht schneller als 2..5/s,
> damit es ergonomisch ist (nicht flackert).


Könntest du mir kurz noch erklären, was du mit 2...5/s meinst?
2..5mal pro Sekunde?
Ich habe leider noch nicht verstanden, wann ich die Berechnung im Main 
durchführen soll. Die Berechnung sollte ja nur durchgeführt werden, wenn 
sich auch der Wert geändert hat, sprich der TIM6 Interrupt aktiv war und 
der Counter um 1 dekrementiert wurde.
Wenn die Main das aber in fixen Zeitabständen immer berechnen soll, 
heisst das ja, dass ich ein Delay einbauen muss, was ja schlecht ist. 
Oder meinst du eher, dass ich in der while eine if-Anweisung einbauen 
und überprüfen muss ob sich das digitValue geändert hat?

von Peter D. (peda)


Lesenswert?

Sebastian Kaulitz schrieb:
> Könntest du mir kurz noch erklären, was du mit 2...5/s meinst?

2..5 Werte/s kann der Mensch bequem ablesen. Daher haben auch viele 
Meßgeräte diese Wandlerrate. Man sollte also nicht schneller neue Werte 
anzeigen.

von Sebastian Kaulitz (Gast)


Lesenswert?

Hallo zusammen

Ich habe den Code nun fertig gebracht und funktioniert auch recht gut.
Im Systick Handler behandle ich das Multiplexen und schalte alle 1ms 
weiter.
Nun würde ich das gerne noch weiter auflösen können.

Wenn ich bei der SystemClock_Config nun den SysTickDevider von 1000 auf 
1000000 ändere, sprich 1us, dann funktionert mein Programm nicht mehr.. 
Das mit den Teilern ist von ST selbst irgendwie definiert.
Warum funktioniert mit dem Wert 1000000 das Programm nicht mehr? Ist die 
Anweisung im Interrupt dann zu lange und der nächste steht bereits an?
Mir geht es dabei nur um das Verständnis, warum das Eine funktioniert 
und das Andere nicht.
1
void SysTick_Handler(void)
2
{
3
  TIM6->DIER &= ~TIM_DIER_UIE;
4
5
  /* USER CODE BEGIN SysTick_IRQn 0 */
6
  if(counter >= SYSTICK_MULTIPLICATOR)
7
  {
8
    counter=0;
9
    digitCounter= ((++digitCounter) % NUMBEROFMUXMEMBERS);   // increments first digitCounter and calculates modulo 
10
                                                             // e.g digitCounter = 4, then digitCounter will be 0
11
    switchDigit(digitCounter);
12
13
  }
14
  /* USER CODE END SysTick_IRQn 0 */
15
    HAL_IncTick();
16
    HAL_SYSTICK_IRQHandler();
17
  /* USER CODE BEGIN SysTick_IRQn 1 */
18
    TIM6->DIER |= TIM_DIER_UIE;
19
    counter++;
20
  /* USER CODE END SysTick_IRQn 1 */
21
}
1
void SystemClock_Config(uint32_t sysTickDevider)
2
{
3
4
  RCC_OscInitTypeDef RCC_OscInitStruct;
5
  RCC_ClkInitTypeDef RCC_ClkInitStruct;
6
  
7
  __HAL_RCC_SYSCFG_CLK_ENABLE();
8
9
    /**Initializes the CPU, AHB and APB busses clocks 
10
    */
11
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSI14;
12
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
13
  RCC_OscInitStruct.HSI14State = RCC_HSI14_ON;
14
  RCC_OscInitStruct.HSICalibrationValue = 16;
15
  RCC_OscInitStruct.HSI14CalibrationValue = 16;
16
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
17
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
18
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL12;
19
  RCC_OscInitStruct.PLL.PREDIV = RCC_PREDIV_DIV1;
20
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
21
  {
22
    _Error_Handler(__FILE__, __LINE__);
23
  }
24
25
    /**Initializes the CPU, AHB and APB busses clocks 
26
    */
27
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
28
                              |RCC_CLOCKTYPE_PCLK1;
29
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
30
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
31
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                   // no devider for Systick clock
32
33
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_1) != HAL_OK)
34
  {
35
    _Error_Handler(__FILE__, __LINE__);
36
  }
37
38
    /**Configure the Systick interrupt time 
39
    */
40
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / sysTickDevider);
41
42
    /**Configure the Systick 
43
    */
44
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
45
  
46
    /* System interrupt init*/
47
  /* SVC_IRQn interrupt configuration */
48
  HAL_NVIC_SetPriority(SVC_IRQn, 0, 0);
49
  /* PendSV_IRQn interrupt configuration */
50
  HAL_NVIC_SetPriority(PendSV_IRQn, 0, 0);
51
  /* SysTick_IRQn interrupt configuration */
52
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
53
}
1
void switchDigit(uint8_t digitCounter)
2
{ 
3
4
  switch(digitCounter)
5
  {
6
        case 0:
7
            HAL_GPIO_WritePin(FLAMELED.port, FLAMELED.pin, FLAME_LEDS_OFF); 
8
            HAL_GPIO_WritePin(DIGIT1.port, DIGIT1.pin, DIGIT_ON);     
9
            break;
10
11
        case 1:
12
            HAL_GPIO_WritePin(DIGIT1.port, DIGIT1.pin, DIGIT_OFF);   
13
            HAL_GPIO_WritePin(DIGIT2.port, DIGIT2.pin, DIGIT_ON);              
14
            break;
15
16
        case 2:
17
            HAL_GPIO_WritePin(DIGIT2.port, DIGIT2.pin, DIGIT_OFF);  
18
            HAL_GPIO_WritePin(DIGIT3.port, DIGIT3.pin, DIGIT_ON);         
19
            break;
20
        
21
        case 3:
22
            HAL_GPIO_WritePin(DIGIT3.port, DIGIT3.pin, DIGIT_OFF); 
23
            HAL_GPIO_WritePin(DIGIT4.port, DIGIT4.pin, DIGIT_ON);                 
24
            break;
25
        
26
        case 4:
27
            HAL_GPIO_WritePin(DIGIT4.port, DIGIT4.pin, DIGIT_OFF);
28
            HAL_GPIO_WritePin(FLAMELED.port, FLAMELED.pin, FLAME_LEDS_ON);       // here as DIGIT 5 in Testing phase 
29
            break;
30
  }
31
32
}

von Peter D. (peda)


Lesenswert?

Sebastian Kaulitz schrieb:
> Ist die
> Anweisung im Interrupt dann zu lange und der nächste steht bereits an?

Sehr warscheinlich. Schau mal ins Assemblerlisting, wieviel Code erzeugt 
wird.
Z.B.:

Sebastian Kaulitz schrieb:
> digitCounter= ((++digitCounter) % NUMBEROFMUXMEMBERS);

Hat Dein MC eine Hardwaredivision?
Sparsamer ist ein Vergleich mit dem Endwert und auf 0 setzen.

Sebastian Kaulitz schrieb:
> digitCounter= ((++digitCounter) % NUMBEROFMUXMEMBERS);

Dieser Code ist außerdem unbestimmt. Er enthält 2 Zuweisungen auf die 
selbe Variable ohne Sequence Point. Stell mal den Warnlevel des 
Compilers so ein, daß er bei sowas warnt.

: Bearbeitet durch User
von Peter D. (peda)


Angehängte Dateien:

Lesenswert?

Hier mal ein einfaches MUX-Beispiel für den AVR:
1
#include <avr/io.h>
2
3
#define DIGITPORT       PORTB
4
#define SEGMENTPORT     PORTC
5
#define DIGITNUM        8       // 2 .. 8
6
7
uint8_t muxram[DIGITNUM];
8
9
uint8_t *muxptr = muxram;
10
11
void muxer( void )
12
{
13
  SEGMENTPORT = 0xFF;           // segments off (low active)
14
  DIGITPORT >>= 1;              // next digit on
15
  if( DIGITPORT == 0 ){
16
    DIGITPORT = 1<<DIGITNUM-1;
17
    muxptr = muxram;
18
  }
19
  SEGMENTPORT = *muxptr++;      // next segment pattern on 
20
}

von Sebastian Kaulitz (Gast)


Lesenswert?

Peter D. schrieb:
> Hier mal ein einfaches MUX-Beispiel für den AVR:#include
> <avr/io.h>
>
> #define DIGITPORT       PORTB
> #define SEGMENTPORT     PORTC
> #define DIGITNUM        8       // 2 .. 8
>
> uint8_t muxram[DIGITNUM];
>
> uint8_t *muxptr = muxram;
>
> void muxer( void )
> {
>   SEGMENTPORT = 0xFF;           // segments off (low active)
>   DIGITPORT >>= 1;              // next digit on
>   if( DIGITPORT == 0 ){
>     DIGITPORT = 1<<DIGITNUM-1;
>     muxptr = muxram;
>   }
>   SEGMENTPORT = *muxptr++;      // next segment pattern on
> }

Ich muss denmach also noch ein wenig optimieren. Danke, das werde ich 
gleich mal umsetzen.

> Sebastian Kaulitz schrieb:
>> digitCounter= ((++digitCounter) % NUMBEROFMUXMEMBERS);
>
> Dieser Code ist außerdem unbestimmt. Er enthält 2 Zuweisungen auf die
> selbe Variable ohne Sequence Point. Stell mal den Warnlevel des
> Compilers so ein, daß er bei sowas warnt.

Ich verstehe nicht ganz was du meinst hiermit.
Könntest du genauer erklären, was mein Fehler ist? Wieso 2 Zuweisungen?

von Εrnst B. (ernst)


Lesenswert?

Sebastian Kaulitz schrieb:
> Wieso 2 Zuweisungen

eine mit "digitCounter=" und eine mit "++digitCounter".

Schreib halt
1
digitCounter= ((digitCounter + 1) % NUMBEROFMUXMEMBERS);

Dann ist's nur noch eine Zuweisung.

oder z.B.
1
  if (++digitCounter==NUMBEROFMUXMEMBERS)
2
    digitcounter=0;

von Sebastian Kaulitz (Gast)


Lesenswert?

Εrnst B. schrieb:
> Sebastian Kaulitz schrieb:
>> Wieso 2 Zuweisungen
>
> eine mit "digitCounter=" und eine mit "++digitCounter".
>
> Schreib halt
> digitCounter= ((digitCounter + 1) % NUMBEROFMUXMEMBERS);
>
> Dann ist's nur noch eine Zuweisung.
>
> oder z.B.  if (++digitCounter==NUMBEROFMUXMEMBERS)
>     digitcounter=0;

Danke


Und warum genau darf man so etwas in C nicht machen?

von Εrnst B. (ernst)


Lesenswert?

Sebastian Kaulitz schrieb:
> Und warum genau darf man so etwas in C nicht machen?

Weil der C-Compiler die Freiheit hat, die Zuweisungen bis zum nächsten 
Sequence Point so umzusortieren, wie es ihm passt.
Das muss nicht von links-nach-rechts oder von oben-nach-unten sein.

d.H. der Compiler darf in der Theorie aus deiner Zeile Maschinencode 
erzeugen, bei der Werte > NUMBEROFMUXMEMBERS in digitcounter stehen.

Ich denke zwar nicht, dass es einen Compiler gibt, der das machen würde, 
aber trotzdem ist es schlechter Stil sich da auf Implementierungsdetails 
zu verlassen anstatt gleich laut Standard eindeutig definierten und 
damit richtigen Code zu schreiben.

Der GCC warnt, wenn mit "-Wall" oder "-Wsequence-point" aktiviert, vor 
solchen Konstrukten.

von Sebastian Kaulitz (Gast)


Lesenswert?

Εrnst B. schrieb:
> Sebastian Kaulitz schrieb:

> Das muss nicht von links-nach-rechts oder von oben-nach-unten sein.

Entschuldige, dass ich so frage, aber irgendwie kann ich mir das nicht 
vorstellen. Der Compiler kann ja den Code bis zum sequence point ; nicht 
so umsortieren, dass da zB steht:

digitCounter = (NUMBEROFMUXMEMBERS % (++digitCounter));

Wie digitCounter auf einmal grösser werden kann, kann ich mir auch 
irgendwie nicht vorstellen, wenn da steht
digitCounter = ((++digitCounter)%NUMBEROFMUXMEMBERS);

digitCounter mit 0 initialisiert
NUMBEROFMUXMEMBERS = 5
Im Systick Handler beim 1. Mal wird er 1
zählt hinauf bis 4
beim letzten Mal wird ++digitCounter = 5 und 5%NUMBEROFMUXMEMBERS 
ergibt 0

Wie also kann da jemals der Wert > 5 werden?

Vielen Dank für die Erklärung

von S. R. (svenska)


Lesenswert?

Sebastian Kaulitz schrieb:
> Entschuldige, dass ich so frage, aber irgendwie kann ich mir das nicht
> vorstellen.

Der Prozessor kann kein C. Daraus wird also ungefähr sowas:

>> digitCounter= ((++digitCounter) % NUMBEROFMUXMEMBERS);

(1) lade register A mit digitalCounter
(2) addiere 1 zum register A
(3) lade register B mit NUMBEROFMUXMEMBERS
(4) dividiere register A mit register B, schreibe ergebnisse nach 
register C (wert) und D (rest)
(5) schreibe register A mit register D
(6) schreibe register A nach digitalCounter

(Ganz so blöd sind moderne Compiler natürlich nicht.)

Es ist nicht definiert, in welcher Reihenfolge (2) und (4) passieren, 
weil "zwei Schreibzugriffe auf die gleiche Variable ohne Sequenzpunkt" 
laut Standard ungültiges Verhalten ist.

Theoretisch darf der Compiler an der Stelle auch einen Absturz 
produzieren, deinen Controller auf Koreanisch umstellen, deinen 
Schreibtisch grün malen oder den gesamten Code wegschmeißen. Oder "-1" 
(alle Bits gesetzt) in digitCounter schreiben.

von Sebastian Kaulitz (Gast)


Lesenswert?

S. R. schrieb:
>>> digitCounter= ((++digitCounter) % NUMBEROFMUXMEMBERS);
>
> (1) lade register A mit digitalCounter
> (2) addiere 1 zum register A
> (3) lade register B mit NUMBEROFMUXMEMBERS
> (4) dividiere register A mit register B, schreibe ergebnisse nach
> register C (wert) und D (rest)
> (5) schreibe register A mit register D
> (6) schreibe register A nach digitalCounter
>
> (Ganz so blöd sind moderne Compiler natürlich nicht.)
>
> Es ist nicht definiert, in welcher Reihenfolge (2) und (4) passieren,
> weil "zwei Schreibzugriffe auf die gleiche Variable ohne Sequenzpunkt"
> laut Standard ungültiges Verhalten ist.


Danke

von Peter D. (peda)


Lesenswert?

Sebastian Kaulitz schrieb:
> Ich muss denmach also noch ein wenig optimieren.

Ich versuche, möglichst nicht einfach drauflos zu programmieren, sondern 
erstmal zu überlegen, worin sich die einzelnen Aktionen unterscheiden. 
Und beim Multiplexen bietet sich das Schieben für die Digitauswahl 
geradezu an, wenn die Digittreiber auf einem Port liegen.
If/else-Ketten oder switch/case nehme ich nur, wenn sich keine 
gemeinsame Regel finden läßt.
Eine gemeinsame Regel hat auch den Vorteil, daß Fehler nur an einer 
Stelle auftreten und nicht in vielen Copy&Paste Sequenzen. D.h. Fehler 
müssen nur an einer Stelle behoben werden.
Auch kann sie einfach erweitert werden, ohne daß man Copy&Paste Fehler 
hinzufügt.

von Sebastian Kaulitz (Gast)


Lesenswert?

Ich habe die Anweisungen mal im Systick Handler gemessen.

Mit dem Oszilloskop messe ich ca. 68us.
Damit ist klar, warum die Division des Systick Interrupts mit dem Teiler 
1.000.000 für ein 1us Intervall nicht funktioniert.

Danke euch allen für die Unterstützung.

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.