attach/detachInterrupt Problem

Es geht um das Laden eines Akkus, der Ladestrom wird über ein Poti eingestellt und über einen PWM-Pin ausgegeben. Problem ist das Messen der "echten" Ladespannung am Akku! Um halbwegs gleiche Spannung zu messen habe ich mit

attachInterrupt(0, Messung, FALLING);

versucht das Messen nur auf eine fallende Flanke am PWM-Ausgang (Pin9), der dazu direkt mit Digital-Pin2 (Interrupt0) verbunden ist zu beschränken. Es gibt aber immer wieder "Ausreisser" der gemessenen Spannung, Auszug des 'Serial Monitor':

631 631 754 633 755 755 633 631 631
631 631 631
631 631 631
631 755 608 632 633 633
631 631 632
631 755 756 632 632 633
633 632 632

3 Werte hintereinander werden auf ca 1%ige Gleichheit geprüft, sonst wird neu gemessen. Ausreisser sind hier die Werte über 700, so als würde die Spannung hier und da auch mal während der positiven Flanke gemessen, der Code in Kurzform:

attachInterrupt(0, Messung, FALLING);
delay(5); // 5ms warten, Wartezeit muß >2mS (kleinste PWM-Zeit) sein?
void Messung() {
  //detachInterrupt(0); // Interrupt abschalten
  Wert_IN_tmp = analogRead(Pin_Akku_IN); // Spannung des Akkus messen
  detachInterrupt(0); // Interrupt abschalten
}

Wo ich das 'detachInterrupt(0)' auch hinschreibe, vor oder nach dem Messen, die Ausreisser bleiben. ...in der Hoffnung, mein Problem halbwegs verständlich beschrieben zu haben

Ich verstehe nicht wieso Du überhaupt einen Interrupt für sowas brauchst. Ich würde einmal pro Sekunde messen und gut.

Der ADC hat von sich aus ein Rauschen, aber das eigentliche Problem sind die Akkus. Direkt nachdem Du die Spannung abschaltest (und auch noch einige Zeit danach) können die sehr merkwürdige Sachen machen. Das liegt daran, daß es eben reale und keine idealen Bauteile sind.

Da hilft nur kräftig Filtern und Glätten.

Gruß, Udo

Ich würde einmal pro Sekunde messen und gut. Das geht leider gar nicht, messe ich während PWM auf + liegt, habe ich (wie beschrieben) komplett andere Werte, als in den Pausen (PWM auf GND). Wenn es keine andere Lösung gibt, bleibt mir wohl nichts anderes übrig, als den PWM-Ausgang zur Zeit des Messens komplett abzuschalten. (Gehofft hatte ich eigentlich auf eine Erklärung warum es so nicht funktioniert)

billigstes 2 kanal oszi, vor der messung ein pin auf HIGH, nach der messung ein pin auf LOW.

kanal 1: PWM pin (trigger) kanal 2: debug pin

so laesst sich nachmessen, wann das teil den interrupt ausloest und was die batterie so treibt.

Naja, Akkus messen ist nicht trivial. Vieleicht wäre es eine gute Idee den Akku kurz vor der Messung zu belasten. Genaugenommen ist es vermutlich sogar eine sehr gute Idee. Stichwort "Reflexlader".

Was genau hast Du eigentlich vor? Akkulader (und auch richtig gute) gibt es ja günstig zu kaufen. Was soll Deiner besser oder anders machen?

Gruß, Udo

Noch ein Nachtrag. Auf was für eine Frequenz ist Deine PWM eingestellt? Der ADC braucht ja relativ lange um zu konvertieren. Wenn die PWM zu schnell ist, dann ist sowieso klar, daß Du immer Probleme bekommen wirst.

Gruß, Udo

Akkulader (und auch richtig gute) gibt es ja günstig zu kaufen. Es ist ein Lernprojekt, was später auch als "standalone" laufen soll, dazu wird ein vorhandenes aber defektes Ladegerät umgebaut. Was einfach anfing, gipfelt nun mittlerweile in Interruptsteuerung. :)

Zunächst war es nur das Laden über USB, dann für höhere Ströme Anschluß eines externen Netzteils, dann 3 LEDs zur optischen Überwachung des Ladens, dann um den Ladestrom einzustellen, kamen noch ein Poti und die PWM-Steuerung dazu.

Ab diesem Zeitpunkt ist die Spannung am Akku nicht mehr gescheit zu messen, Darum der Versuch die Messung Interrupt gesteuert ausschl. in die Off-Phase des PWMs zu legen. Der PWM tackert mit 500Hz, kleinste Off-Zeit wäre bei Ansteuerung (0-255) also 4mS, mit 'delay(5);' ist eigentlich genügend Wartezeit für den Interrupt und zum Messen da. Wo ist mein Denkfehler?

Ich tippe auf den Akku. Du bist 5ms hinter der fallenden Flanke der PWM wenn ich Dich richtig verstehe. Und der Akku wird von Dir vor und während der Messung nicht belastet, oder?

Wenn dem so ist, dann würde ich darauf tippen, daß der Akku sich schlicht und ergreifend nicht so verhält wie Du das erwartest. Ein Akku ist kein Kondensator der sich in 5ms garantiert beruhigt. Wenn Du der Sache richtig auf den Grund gehen willst, dann hilft nur ein Speicheroszi.

Schau erst einmal das Verhalten an ohne PWM. D.h. Ladespannung für 1 Minute anlegen und dann 1 Minute messen. Einmal mit und einmal ohne Last. Danach weißt Du wo das Problem liegt ;)

Da Deine Frequenzen mit 500 Hz aber sehr niedrig sind würde ein Arduino als Speicheroszi schon völlig ausreichen. Oder was anderes aus der untersten Preisklasse:

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1223639021

http://www.youritronics.com/arduino-oscilloscope/

Du bist 5ms hinter der fallenden Flanke der PWM wenn ich Dich richtig verstehe... Nein, in den 5mS soll auf das Auslösen des Interrupt gewartet werden.

Hab aber auch mal @madworms Idee mit Oszi und Debug-Pin aufgegriffen, hier ist dann halbwegs klar zu sehen, das der Interrupt zwar meistens, aber nicht immer an der fallenden Flanke ist, ab und zu eben auch während PWM auf Plus ist, das gibt dann im 'Serial Monitor' gleich die Ausreisser. Werde mal den 2ten Interrupt versuchen, ob es da genauso ist.

OK, dann ist die Sache klar. Im Datenblatt steht in Abschnitt 12:

The INT0 and INT1 interrupts can be triggered by a falling or rising edge or a low level. This is set up as indicated in the specification for the External Interrupt Control Register A – EICRA. When the INT0 or INT1 interrupts are enabled and are configured as level triggered, the inter- rupts will trigger as long as the pin is held low. Note that recognition of falling or rising edge interrupts on INT0 or INT1 requires the presence of an I/O clock, described in ”Clock Systems and their Distribution” on page 26. Low level interrupt on INT0 and INT1 is detected asynchro- nously. This implies that this interrupt can be used for waking the part also from sleep modes other than Idle mode. The I/O clock is halted in all sleep modes except Idle mode.

Du hast die Interrupts auf die Flanke gesetzt. Also kommt die Verzögerung dadurch, daß die Flanke auftritt während ein anderer Interrupt gerade aktiv ist. Typischerweise wird das wohl der Timerinterrupt sein.

Wenn Du den Timer0 anhältst bevor Du int0 aktivierst sollte das Problem weggehen. Also

cbi(TIMSK0, TOIE0);

vor der Messung und

sbi(TIMSK0, TOIE0);

hinterher.

Natürlich darfst Du Dich dann nicht mehr auf Timer0 verlassen. D.h. Delay funktioniert solange nicht (delayMicroseconds aber schon).

Gruß, Udo

P.S. Sag bescheid ob's das wirklich war

Damit überforderst du mich aber völlig. :-[ Welche libs müssen denn hier "included" werden? Von diesen Feinheiten der Programmierung wollt ich eigentlich Abstand halten. Ob eine "zu Fuß" Programmierung der PWM dann nicht einfacher ist?

Ah Mist,

ich habe einfach in wiring.c nachgeschaut und dort eben gesehen, daß dort Timer 0 mit

      // enable timer 0 overflow interrupt
#if defined(__AVR_ATmega8__)
      sbi(TIMSK, TOIE0);
#else
      sbi(TIMSK0, TOIE0);
#endif

aktiviert wird. Dumm nur, daß sbi / cbi in wiring_private.h definiert sind. Das hatte ich übersehen. Also dann wie folgt: Du inkludierst einfach so:

#include <avr/io.h>
#include <avr/interrupt.h>

und schreibst noch

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

Dann hast Du auch die Makros. Oder Du inkludierst gleich wiring_private.h.

Allerdings stimme ich Dir völlig zu, daß das nicht gerade leicht durchschaubar ist. Am einfachsten wäre es PWM kurz abzuschalten.

Andererseits: Du wolltest ja was lernen. Woher ich mein Wissen habe? Naja, ich baue im Moment eine “Präzisionsstoppuhr” → die Sorte Probleme habe ich dauernd. Deshalb habe ich (1) das Datenblatt durchgelesen und zwar alles was zu den Timern drinsteht und (2) die ganzen Arduino Libs durchgeschaut die hierzu relevant sind, also alles was wiring* oder WInterrupts* heißt. Die Libs stehen bei mir in “arduino-0017/hardware/cores/arduino”.

Da ich sowieso “command line Arduino” - also ohne IDE - nehme habe ich auch relativ wenig Hemmungen in den Libs rumzupfuschen. Der Lerneffekt ist enorm :slight_smile:

Ist zugegeben am Anfang etwas aufwendig, aber wenn man das einmal kapiert hat geht die ganze Timerprogrammierung wesentlich leichter von der Hand.

Gruß, Udo

P.S. was ich gerade auf die harte Tour lerne: der Arduino ist nicht wirklich geeignet für präzise Zeiterfassung. Die Quarze sind das Problem. 16 MHz Quarze scheinen gewaltig zu streuen. Ich habe bis zu 203 Microsekunden (also 203 ppm) relative Abweichung selbst wenn alle Interrupts aus sind :frowning:

Ich hatte heut morgen versucht mich schlau zu machen, was überhaupt gemeint ist mit:

cbi(TIMSK0, TOIE0); 
sbi(TIMSK0, TOIE0);

gefunden hatte ich Dieses: Veraltete Funktionen zum Zugriff auf Bits in Registern Gemeinsame Register Die Definitionen

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

waren auch irgendwo zu finden und hatte sie eingebaut, bekam nur immer Fehler über ein nicht definiertes 'TIMSK0' beim "Trockenkompilieren. Hier hätte ich vielleicht erwähnen sollen, das ich den eingebauten Atmega168 durch einen Atmega8 ersetzt habe. :-[ 'Trockenkompilieren' funktioniert jetzt hiermit:

cbi(TIMSK, TOIE0);
sbi(TIMSK, TOIE0);

Werde das mal morgen früh meinem Arduino beibringen und dann mal sehen ob es Besserung bringt.

Na dann bist Du doch schon tief genug in den Details drin :) Ist ja nicht ganz ohne den Controller zu tauschen. Und die Sache mit den Makros hast Du doch auch rausgekriegt. Ich nehme übrigens einen 644. Viele Pins und schön viel Speicher und passt vor allem in die Mega 32 Boards die man überall günstig kriegt. Die sind viel besser zum Basteln als die "normalen" Arduino Boards :)

Bin ja mal gespannt ob das jetzt bei Dir hinhaut.

Nein nein, bin noch lange nicht in den Details drin, nur dein erster Codeblock hat mich stutzig gemacht: "wenn ATmega8 dann TIMSK, wenn nicht dann TIMSK0", damit komme ich so gerade noch klar. :)

Es hat leider nicht hingehauen, der Interrupt wird noch immer willkürlich gesetzt. Vielleicht sollte ich noch erwähnen, das der Atmega8 hier mit internem Takt läuft (1MHz). Werde mich am Wochenende noch mal intensiver damit auseinandersetzen, bis hierhin zunächst mal vielen Dank an alle Beteiligten.

Hmm, vieleicht ist es dann gar nicht der Interrupt. Als erstes muß auf jeden Fall geklärt werden wo das Problem herkommt. Ich tippe zwar immer noch auf den Interrupt, aber so langsam wird die Ferndiagnose schwierig.

Gruß, Udo

Zum Testen hatte ich den delay zum Warten auf den Interrupt auch mal durch eine Endlosschleife mit while ersetzt, ohne delay. In der Interruptroutine wird dann ausschl. eine Variable geändert, die die Endlosschleife beenden kann. Aber auch so ist dasselbe Phänomen zu beobachten.

Das verstehe ich jetzt nicht ganz. Willst Du sagen, daß Du glaubst der IRQ ist schuld oder nimmst Du an etwas anderes ist schuld?

Kannst Du vieleicht mal die Codestelle ins Forum stellen?

Ich klammere mich an jeden Strohhalm ;)

Der letzte Versuch heut morgen sah so aus, andere stehen noch drin, sind aber als Text markiert.

//cbi(TIMSK, TOIE0);
attachInterrupt(0, Messung, FALLING);
//sbi(TIMSK, TOIE0);
//delayMicroseconds(5000); // 5 mS verzögern

while (DEBUG2 == 0) {
  // auf Interrupt warten
}

detachInterrupt(0); // Interrupt abschalten
digitalWrite(DEBUG, HIGH); // DEBUG für Oszi
Wert_IN_tmp = analogRead(Pin_Akku_IN); // Spannung des Akkus messen
digitalWrite(DEBUG, LOW); // DEBUG für Oszi
DEBUG2 = 0;

void Messung() {
  //detachInterrupt(0); // Interrupt abschalten
  //digitalWrite(DEBUG, HIGH); // DEBUG für Oszi
  //Wert_IN_tmp = analogRead(Pin_Akku_IN); // Spannung des Akkus messen
  //digitalWrite(DEBUG, LOW); // DEBUG für Oszi
  DEBUG2 = 1;
}