Forum: Mikrocontroller und Digitale Elektronik Interrupt in C / ATTiny / WS 2812B


von Fam B. (fambaa)


Angehängte Dateien:

Lesenswert?

Bin noch ein ziemlicher Noob in C

Bisher die Schaltung nur in tinkercad.com simuliert (vormals autodesk 
circuits.io)

Funktioniert soweit alles bis auf timing Problemchen und Farb Glitches.

Aufgabe:

3 x 5 LEDs 120 Grad vesetzt auf einem Rad. Abfrage der Drehzahl über 
Hall Sensor auf PB2 (INT0)

Strips sollen an einer Position recht synchron an einer Stelle 
aufleuchten, auch bei Änderung der Drehzahl.
Drehzahl ist recht gering 1-6 Hz.

Ich zähle per Interrupt die Drehzahl und berechne damit weiterführend 
die Leuchtdauer und Abschaltzeit der LEDs sowie mit einem kleineren 
Delay den Abstand der Strips zueinander.

Problem ist nur das die Drehzahlberechnung anfangs zu hohe Werte 
ausgibt, da sie auf verstrichene Zeit zwischen Interrupts basiert und 
das Ergebnis als Teiler dient.

Wie bekomm ich es hin das ich ab dem zweiten Interrupt anfange den Code 
auszuführen?

Auch rufe ich die Funktion aus dem Interrupt heraus auf. Glaube das ist 
auch problematisch. Läuft aber besser als wenn ich den Code direkt im 
Interrupt ausführe.

Hier ist mein Programm.
1
#include <Adafruit_NeoPixel.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
5
#define NUMPIXELSA 5
6
#define NUMPIXELSB 5
7
#define NUMPIXELSC 5
8
9
Adafruit_NeoPixel strip_A = Adafruit_NeoPixel(NUMPIXELSA, 0, NEO_GRB + NEO_KHZ800); // 3 Strips designed to be offset by 120 degrees on a wheel
10
Adafruit_NeoPixel strip_B = Adafruit_NeoPixel(NUMPIXELSB, 1, NEO_GRB + NEO_KHZ800);
11
Adafruit_NeoPixel strip_C = Adafruit_NeoPixel(NUMPIXELSC, 3, NEO_GRB + NEO_KHZ800);
12
13
//uint32_t whiteA = strip_A.Color(255, 255, 255); disabled for testing purposes
14
//uint32_t whiteB = strip_B.Color(255, 255, 255);
15
//uint32_t whiteC = strip_C.Color(255, 255, 255);
16
17
uint32_t offA = strip_A.Color(0, 0, 0);
18
uint32_t offB = strip_B.Color(0, 0, 0);
19
uint32_t offC = strip_C.Color(0, 0, 0);
20
21
uint32_t whiteA = strip_A.Color(0, 0, 255); //Testcolor to better see it in the simulator ;)
22
uint32_t whiteB = strip_B.Color(0, 0, 255);
23
uint32_t whiteC = strip_C.Color(0, 0, 255);
24
25
volatile unsigned long time = 0;
26
volatile unsigned long time_last = 0;
27
volatile unsigned int rpm_array[3] = {0, 0, 0}; // 3 samples averaged
28
unsigned long refreshInterval = 10; // in ms, for LCD refresh timing
29
unsigned long previousMillis = 0;
30
volatile unsigned int rpm = 0;
31
volatile unsigned int last_rpm = 0;
32
unsigned int rotDelay = 0; // Calculated time based variable to control the duration and delay between LEDs switching on and off, integer instead of float to test and save processing time
33
unsigned int rotDelayA = 0;
34
void setup()
35
{
36
  GIMSK = 0b01000000;  // Enable INT0 External Interrupt
37
  MCUCR = 0b00000011;  // Rising-Edge Triggered INT0
38
            
39
  DDRB &= ~(1<<PB5); //Set unused pins as input.
40
  DDRB &= ~(1<<PB4);
41
  PORTB |= (1 << PB5); // Enable pullup resistor
42
  PORTB |= (1 << PB4);
43
  
44
  strip_A.begin();         // Initialize Neopixel Strips
45
  strip_B.begin();
46
  strip_C.begin();
47
  strip_A.setBrightness(230);   // Set Strips to approx 90% Brightness
48
  strip_B.setBrightness(230);
49
  strip_C.setBrightness(230);
50
  strip_A.show();       // Send empty data to strips to reset floating output turning all Neopixels off
51
  strip_B.show();
52
  strip_C.show();
53
54
//  for (int a = 0; a < NUMPIXELSA; a++) {  // Strip Test
55
//    strip_A.setPixelColor(a, whiteA );
56
//    strip_A.show();             // Send data
57
//    delay(100);               // Delay to test each strip
58
//  }
59
//  for (int a = 0; a < NUMPIXELSA; a++) {
60
//    strip_A.setPixelColor(a, offA );
61
//    strip_A.show();
62
//  }
63
//  for (int b = 0; b < NUMPIXELSB; b++) {
64
//    strip_B.setPixelColor(b, whiteB);
65
//    strip_B.show();
66
//    delay(100);
67
//  }
68
//  for (int b = 0; b < NUMPIXELSA; b++) {
69
//    strip_B.setPixelColor(b, offB );
70
//    strip_B.show();
71
//  }
72
//  for (int c = 0; c < NUMPIXELSC; c++) {
73
//    strip_C.setPixelColor(c, whiteC);
74
//    strip_C.show();
75
//    delay(100);
76
//  }
77
//  for (int c = 0; c < NUMPIXELSA; c++) {
78
//    strip_C.setPixelColor(c, offC );
79
//    strip_C.show();
80
//  }
81
sei(); // Enable Interrupts
82
}
83
void update_rpm() {
84
  rpm = 60 * (1000000 / (time * 1));
85
  rotDelay = 360000 / (rpm * 20);
86
  rotDelayA = ((rotDelay / 3) / 3);
87
}
88
void strips () {
89
  for (int a = 0; a < NUMPIXELSA; a++) {
90
    strip_A.setPixelColor(a, whiteA );
91
    strip_A.show();
92
    delay(rotDelay); 
93
  }
94
  for (int a = 0; a < NUMPIXELSA; a++) {
95
    strip_A.setPixelColor(a, offA );
96
    strip_A.show();
97
    delay(rotDelay);  for (int a = 0; a < NUMPIXELSA; a++) {
98
    strip_A.setPixelColor(a, whiteA );
99
    strip_A.show();
100
    delay(rotDelay); 
101
  }
102
  for (int a = 0; a < NUMPIXELSA; a++) {
103
    strip_A.setPixelColor(a, offA );
104
    strip_A.show();
105
    delay(rotDelay);
106
  }
107
  delay(rotDelayA);
108
  for (int b = 0; b < NUMPIXELSB; b++) {
109
    strip_B.setPixelColor(b, whiteB );
110
    strip_B.show();
111
    delay(rotDelay); 
112
  }
113
  for (int b = 0; b < NUMPIXELSB; b++) {
114
    strip_B.setPixelColor(b, offB );
115
    strip_B.show();
116
    delay(rotDelay);
117
  }
118
  delay(rotDelayA);
119
  for (int c = 0; c < NUMPIXELSC; c++) {
120
    strip_C.setPixelColor(c, whiteC );
121
    strip_C.show();
122
    delay(rotDelay); 
123
  }
124
  for (int c = 0; c < NUMPIXELSC; c++) {
125
    strip_C.setPixelColor(c, offC );
126
    strip_C.show();
127
    delay(rotDelay);
128
  }
129
    delay(rotDelayA);
130
  }
131
  delay(rotDelayA);
132
  for (int b = 0; b < NUMPIXELSB; b++) {
133
    strip_B.setPixelColor(b, whiteB );
134
    strip_B.show();
135
    delay(rotDelay); 
136
  }
137
  for (int b = 0; b < NUMPIXELSB; b++) {
138
    strip_B.setPixelColor(b, offB );
139
    strip_B.show();
140
    delay(rotDelay);
141
  }
142
  delay(rotDelayA);
143
  for (int c = 0; c < NUMPIXELSC; c++) {
144
    strip_C.setPixelColor(c, whiteC );
145
    strip_C.show();
146
    delay(rotDelay); 
147
  }
148
  for (int c = 0; c < NUMPIXELSC; c++) {
149
    strip_C.setPixelColor(c, offC );
150
    strip_C.show();
151
    delay(rotDelay);
152
  }
153
  delay(rotDelayA);
154
}
155
void loop() {
156
  update_rpm(); // sensor ISR will look for time period
157
  if (last_rpm - rpm !=  0 ) { // update LCD only if there is a change of rpm
158
    last_rpm = rpm;
159
    // need to check the case that there is 0 rpm (full stop)
160
    unsigned long currentMillis = millis();
161
    if (currentMillis - previousMillis > refreshInterval) { // at a decent refresh rate
162
      previousMillis = currentMillis;
163
    }
164
  }
165
166
}
167
168
ISR(INT0_vect)  //Capture Hall via Interrupt
169
{
170
  time = (micros() - time_last);
171
  time_last = micros();
172
  strips();
173
    
174
}

Selbstest auskommentiert, der Simulator ist mir zu langsam, vllt lass 
ihn auch ganz weg.
"Läuft" simuliert auf einem ATTiny 85, passt Speichertechnisch ganz gut, 
3900 Bytes belegt.
Weiss leider nicht mit welcher Mhz der Simulator für den MC nimmt, die 
Beschreibung sagt 8 Mhz, also läuft der Code mit 1 Mhz richtig?

So wie es jetzt ist spucken die LEDs ab und zu andere Farben aus die 
nicht programmiert sind.

Hab leider noch keine Möglichkeit es selbst zusammenzulöten, mir fehlen 
noch viele Komponenten, unter anderem der MC und die LEDs ༼ つ ◕_◕ ༽つ

: Bearbeitet durch User
von M. K. (sylaina)


Lesenswert?

Uh, also ich habs nur überflogen und als ich die ISR sah...also 
Funktionen in einer ISR würde ich zu vermeiden versuchen und du ruftst 
direkt mehrere bzw. mehrmals Funktionen in ner ISR auf.

Du könntest z.B. einen Timer parallel mitlaufen lassen und immer in der 
INT0-ISR auswerten/zurücksetzen. Damit kannst du den Abstand von Int0- 
zu Int0-Interrupt messen und von da aus auf die Drehzahl schließen.

Eine andere Idee wäre es, einen Input-Capture-Interrupt (Timer) hierfür 
zu verwenden. Der erste Capture-Interrupt startet den Timer, der zweite 
stoppt ihn. Auch hier kann man dann auf die Drehzahl schließen.

Die Abarbeitung deiner Funktion strips(); würde ich entweder komplett 
neu überdenken oder aber Flag-gesteuert in den Mainloop legen.

von Fam B. (fambaa)


Lesenswert?

Ich hatte strips() erst garnicht, das stand direkt so im main loop, aber 
dann läufts ja einfach los.

Mit flags mach ich zb einfach eine bool variable die ich über den 
Interrupt auf high setze und im main loop solange warte bis die auf high 
ist?

Das Zählen funktioniert ja so ganz gut, und bei den Variablen muss ich 
mich auchnicht ums zurücksetzen kümmern. Der interne Timer ist ja quasi 
schon drinne mit micros() oder?

Hatte schon Mühe den Interrupt zu programmieren, viel copy paste und 
lesen :D
Wie gesagt bin ein Vollnoob, bissl Basic (C64, ewig her), bissl Pascal 
und nie was in java oder python, html zählt ja nich unbedingt :P

Die LEDs werden ja jetzt billiger, quasi Auslaufmodell, die neuen 
heissen ATA 101 soweit ich gelesen habe, 4 strippig, die in der Lage 
sind kaputte LEDs in der Mitte zu überspringen^^

Dann les ich mich mal in states und Timer ein. Grösste Hindernis ist und 
bleibt wohl der Simulator :D

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Fam B. schrieb:
> Bin noch ein ziemlicher Noob in C

Macht nix, der Code ist ja C++ ;-)

von M. K. (sylaina)


Lesenswert?

Fam B. schrieb:
> Mit flags mach ich zb einfach eine bool variable die ich über den
> Interrupt auf high setze und im main loop solange warte bis die auf high
> ist?

Richtig, und wenn die Funktion in der Main-Loop abgearbeitet ist setzt 
du das Flag wieder auf Low. Könnte z.B. so aussehen:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
// Flag "konfigurieren"
5
volatile uint8_t myFlag = 0;
6
7
int main (void) {
8
 // Ausgang einstellen
9
 DDRB |= (1 << PB0);
10
 // Timer0 einstellen
11
 TIMSK0 |= (1 << TOIE0);
12
 // und starten
13
 TCCR0B |= (1 << CS00);
14
 // Globale Interrupts aktivieren
15
 sei();
16
 // main-loop, endlos-schleife
17
 for(;;){
18
  if(myFlag == 1){
19
    PORTB ^= (1 << PB0);
20
    myFlag = 0;
21
  }
22
 }
23
 // wird nie erreicht
24
 return 0;
25
}
26
//Overflow-Interrupt-Vektor für Timer0
27
ISR(TIMER0_OVF_vect){
28
 myFlag = 1;
29
}
Kurzbeispiel für ne Flagnutzung. Timer0 läuft mit CPU-Takt, 
Overflow-Interrupt für Timer0 eingeschaltet, in dessen ISR wird 
lediglich das Flag gesetzt. Ist das Flag gesetzt wird der Ausgang PB0 
getoggelt. Klar, hier könnte man auch im ISR den Ausgang toggeln, das 
Beispiel soll aber nur das Prinzip von Flags zeigen. Man könnte hier das 
Flag auch so ändern, dass nur bei jeden 3 Overflow der Ausgang getoggelt 
wird, kannst du dir ja mal überlegen wie man dann den Code ändern 
müsste. ;)

Fam B. schrieb:
> Hatte schon Mühe den Interrupt zu programmieren, viel copy paste und
> lesen :D

Das ist nicht unüblich. Es fällt einem halt nix in den Schoß und Übung 
macht den Meister.

von Carl D. (jcw2)


Lesenswert?

Dieses Flag gibt es schon in Hardware, das muß man nicht vie ISR 
simulieren.
TIFR0 Bit TOV0 zeigt den Zählerüberlauf an, ein Schreiben von "1" in 
dieses Bit löscht es wieder. Latenz:0.

von DraconiX (Gast)


Lesenswert?

Carl D. schrieb:
> TIFR0 Bit TOV0 zeigt den Zählerüberlauf an, ein Schreiben von "1" in
> dieses Bit löscht es wieder. Latenz:0.

Nana...
1
if(TIFR0 & TOV0) 
2
{
3
4
 /* User Code */
5
6
 TIFR0 &= ~(1 << TOV0);
7
}

Das ist nun nicht "0 Latenz" :-D Aber du hast Recht, schneller geht es 
nicht und ist selbstverständlich auch nicht blockend. Sollte man die ISR 
aber dennoch benötigen, wegen anderen Dingen, muss man halt damit aber 
auch aufpassen. Da kann man sich schnell ins Bein schießen damit.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

DraconiX schrieb:
> Carl D. schrieb:
>> TIFR0 Bit TOV0 zeigt den Zählerüberlauf an, ein Schreiben von "1" in
>> dieses Bit löscht es wieder. Latenz:0.
>
> Nana...
>
>
1
> if (TIFR0 & TOV0)
2
> {
3
>  /* User Code */
4
>  TIFR0 &= ~(1 << TOV0);
5
> }

Nein, sondern:
1
if (TIFR0 & TOV0)
2
{
3
   /* User Code */
4
   TIFR0 = 1 << TOV0;
5
}

Außerdem sehe ich nicht wo das IRQ-Latenz erzeugen sollte.

von Karl M. (Gast)


Lesenswert?

Hallo,

hier ist noch eine kleine und wichtige Korrektur:

Beitrag #5061482:
> if (TIFR0 & (1<<TOV0))
> {
>    /* User Code */
>    TIFR0 = (1<<TOV0);
> }

Ich mag die Klammern, in der ersten Zeile zeigen sie, was noch fehlt und 
in der vorletzten, was zusammen evaluiert wird.

von Carl D. (jcw2)


Lesenswert?

Nachtrag:
Die Arduino-Umgebung nutzt den Timer0 schon selber, d.h. versucht es, 
denn wenn dieser bei Setup() umkonfiguriert wird und man selbst eine ISR 
schreibt (die der Linker zuerst findet und dann benutzt), dann werden 
manche Dinge nicht mehr funktionieren. Z.B. millis(), micros() und 
delay().

Der Timer1 wird (ohne Zusatzt-Libs) wohl nur für Servos benutzt, ist 
also einfacher "frei" zu halten.

Wobei man das Eingangsproblem per Timer1 gut lösen kann. PinChangeInt 
auf irgend einen Eingangspin (oder für höhere Genauigkeit InputCapture), 
Timer0 auf 0, OCR1x auf den (zuvor ermittelten Timerwert * gewünschter 
Winkel) /360, Klammern wegen Rundung und in der OC1x-ISR die LEDs 
feuern.

von Fam B. (fambaa)


Angehängte Dateien:

Lesenswert?

Wie kann ich denn PB0 der mein EIngang ist in einen Ausgang verwandeln? 
Wo soll ich dann den Hall Sensor anschliessen :P

Das ist alles over the top für dieses Projekt. Der Interrupt wird 
HÖCHSTENS 10 mal pro Sekunde angesteuert, ich glaub nich das ich da ein 
Latenzproblem bekomme.
Im Normalfall läuft es auf 1-5 mal pro Sekunde.

Ich hab jetzt bisschen was geändert, die rpm Berechnung findet im Loop 
statt.
Aber wo er vorher richtig gerechnet hat spuckt er jetzt wieder völlig 
falsche Werte aus, und zwar ist rpm wieder komplett am integer Anschlag.

Wenn ich die Zeitübergabe im ISR mache sind die RPM immernoch zu hoch 
anstatt.

Hier meine RPM Berechnung
1
void update_rpm() {
2
3
rpm = 60 * (1000 / (time * 1)); 
4
rotDelay = 360000 / (rpm * 20);  
5
rotDelayA = ((rotDelay / 3) / 3); 
6
}

Hier der Teil im Loop
1
void loop() {
2
  T = (millis() - time_last);
3
  time_last = millis();
4
  update_rpm(); 
5
  if (last_rpm - rpm !=  0 ) { 
6
    last_rpm = rpm;
7
  }
8
  // Hier folgen dann die strips mit if (strips == 1)

Hier der ISR
1
ISR(INT0_vect) { 
2
  time = T;
3
  strips = 1;
4
}

Das ganze Programm wieder als Anhang.
Zwischendurch hat alles wunderbar funktioniert, die Pausen zwischen den 
LEDs usw.
Auch die RPM waren korrekt auf 60, doch jetzt läuft alles auf Anschlag 
und anstatt 1000 ms für eine Umrundung läuft es konstant auf ca 150ms 
(schwer zu sehen) und bleibt konstant und ändert sich bei 
Frequenzänderung nichtmehr.

von Fam B. (fambaa)


Lesenswert?

Gut das ich meinen vorherigen Code oben noch habe. Jetzt funktioniert es 
wieder.
Und "strips" Setze ich gleich im Beginn der if Schleife wieder auf 0, 
hatte es erst am Ende aber das hat auch Probleme verursacht.

In der Simulation hängt sich das Programm nach einer Minute bei 1 Hz 
auf, bzw die Delays werden auf Schlag ein paar Sekunden lang.

Muss ich noch genau kucken. Vllt füge ich eine Schleife ein die alle 50 
Interrupts alle relevanten Variablen und den Interrupt Counter wieder 
auf 0 setzt. Ein Reset quasi.

von M. K. (sylaina)


Lesenswert?

Carl D. schrieb:
> Dieses Flag gibt es schon in Hardware, das muß man nicht vie ISR
> simulieren.
> TIFR0 Bit TOV0 zeigt den Zählerüberlauf an, ein Schreiben von "1" in
> dieses Bit löscht es wieder. Latenz:0.

Das weiß ich aber vielleicht liest du meinen Post noch mal genau nach.

Fam B. schrieb:
> Muss ich noch genau kucken. Vllt füge ich eine Schleife ein die alle 50
> Interrupts alle relevanten Variablen und den Interrupt Counter wieder
> auf 0 setzt. Ein Reset quasi.

Suche lieber den Fehler statt ihn so zu umgehen ;)

Fam B. schrieb:
> In der Simulation hängt sich das Programm nach einer Minute bei 1 Hz
> auf, bzw die Delays werden auf Schlag ein paar Sekunden lang.

Läuft da vielleicht was über? Irgendeine Variable, die nach ca. 1 Minute 
über alle Grenzen wächst (z.B. ein uint16_t, der über 2^16 groß wird).
Verändere mal den Clock und schau mal ob sich diese Minute im gleichen 
Verhältnis ändert.

von Patrick J. (ho-bit-hun-ter)


Lesenswert?

Hi

Delays sind im Simulator ein ganz blödes Thema.
Teilweise setze ich mir im Code eine Konstante, daß die Delays nur 'zum 
Teil' abgewartet werden, damit das Simulieren schneller geht - muß man 
dann natürlich für die HEX zum Brennen wieder draußen haben, was öfter 
vergessen wird und dann im Target seltsame Funktionen beobachtet werden 
können ;)

MfG

von Fam B. (fambaa)


Angehängte Dateien:

Lesenswert?

Es gibt wohl soviele Arduino Emulatoren/Simulatoren, aber die die ich 
bis jetzt gefunden habe haben entweder keine Neopixel/LEDs mit Treibern, 
sind webseitenbasiert und man kann nur zur Verfügung gestellte Bauteile 
benutzen und keine libraries importieren.

Der java basierte Simulator den ich benutze läuft halt nicht auf 
konstantem Speed, also kuck ich mit einem Auge oben auf den Timer und 
mit dem anderen Auge auf die LEDs.

Hier ist mein "Spinnennetz". Man kann das Timing recht gut erkennen, 
auch wenn der Simulator stottert. Natürlich funktioniert das nur bei 
niedrigen Frequenzen.

Wenn die Frequenz länger gleich bleibt läuft es etwas ruckelfreier.

https://youtu.be/LI4IeZW22iA


Im Anhang der aktuelle/finale Code

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.