PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [ERLEDIGT] Probleme mit IF-Abfrage / Timer



sammler
23.04.2011, 16:15
Hallo Leute,

nach etlichen Versuchen und mittlerweile leicht verzweifelt, muss ich mich wohl geschlagen geben und einfach mal um Hilfe bitten ;-)

Ich versuche derzeit mich nach und nach (wieder) in die Programmierung einzuarbeiten (echt erschreckend, wie viel man in 4 Jahren vergessen kann...).
Soweit komme ich ja ganz gut zurecht, und dank Datenblatt, rn-Wissen und mikrocontroller.net komme ich eigentlich auch mit den Timern und den ganzen anderen Registern klar.
Nur will mir jetzt offenbar eine popelige IF-Abfrage den Spaß verderben.

Folgendes Phänomen kann ich beobachten:
Ich habe einen einfachen CTC-Interrupt auf Timer0 meines AT-Mega32 programmiert, der mir alle Millisekunde eine 16-bit-Variable g_msec inkrementiert.
Wird g_msec > 1000 soll g_sec inkrementiert werden:


void make_time(void){
if (g_msec > 999) {
cli();
if (g_msec < 1000){
g_msec++;
}
g_msec -= 1000;
sei();
g_sec++;
g_send=1;
PORTA ^= (1 << PINA0);
if (g_sec > 59){
g_sec = 0;
g_min++;
PORTA ^= (1 << PINA1);
if (g_min > 59){
g_min = 0;
g_h++;
PORTA ^= (1 << PINA2);
if (g_h > 23){
g_h = 0;
g_d++;
}
}
}
}
}
Blöderweise springt er mir der Befehlspointer aber manchmal auch bei kleineren Werten als 1000 in die Schleife:
18649
Der Aufruf der Funktion erfolgt in der Hauptschleife in main.c:


#include <avr/io.h> // Namen der IO Register
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <util/delay.h> // Funktionen zum Warten
#include <avr/wdt.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>

#include "raupe.h"
#include "uart.h"
#include "scheduler.h"

int main(void)
{
/**
* Stammfunkion.
* Ruft alle anderen Programmteile auf. */

unsigned int c;
char buffer[20];
char command[20];
command[0] = '\0';


startup_diag();
sei();
init_scheduler();

PORTA = ( 1 << PINA3 ); // Pullups auf Eingangs-Pins
DDRA |= ( 1 << PINA0 ); // PINA0 als output, _BV(0) = (1<<0) = 1
DDRA |= ( 1 << PINA1 ); // PINA1 als Output
DDRA |= ( 1 << PINA2 ); // PINA2 als Output

while (1)
{
make_time();

/*
* Get received character from ringbuffer
* uart_getc() returns in the lower byte the received character and
* in the higher byte (bitmask) the last receive error
* UART_NO_DATA is returned when no data is available.
*
*/
c = uart_getc();
if ( c & UART_NO_DATA )
{
/*
* no data available from UART
*/
//i = 0;
}
else
{
/*
* new data available from UART
* check for Frame or Overrun error
*/
if ( c & UART_FRAME_ERROR )
{
/* Framing Error detected, i.e no stop bit detected */
uart_puts_P("UART Frame Error: ");
}
if ( c & UART_OVERRUN_ERROR )
{
/*
* Overrun, a character already present in the UART UDR register was
* not read by the interrupt handler before the next character arrived,
* one or more received characters have been dropped
*/
uart_puts_P("UART Overrun Error: ");
}
if ( c & UART_BUFFER_OVERFLOW )
{
/*
* We are not reading the receive buffer fast enough,
* one or more received character have been dropped
*/
uart_puts_P("Buffer overflow error: ");
}
if (!((c=='\0')||(c=='\n'))&&(i<sizeof(command))){
command[i] = (unsigned char)c;
command[(i+1)] = '\0';
i++;
}
else if (c=='\n') {
i=0;
}
}

if (!strcmp(command, "time") || (g_send)) {
sprintf(buffer, "\nT: %02d:%02d:%02d.%04d\n", g_h, g_min, g_sec, g_msec);
uart_puts(buffer);
command[0]=(int)"\0";
g_send=0;
}

}
// Programm-Ende - sollte nicht erreicht werden...
}
Fuses: 0x09(High) 0x7F(Low); komplettes Programm im Anhang.

Wäre super, wenn ihr mir auf die Sprünge helfen könntet!

Gruß,
sammler

PS: der Wert von g_msec, wenn er fälschlich reinspringt, ist offenbar immer 768 (dez.)...

TobiKa
23.04.2011, 17:44
if (g_msec < 1000){
g_msec++;
Das kann nie zutreffen, wenn du vorher

if (g_msec > 999) {
kontrollierst.

Du hast deine Timerroutine leider nicht gepostet, aber da g_msec signed ist und sie einfach nur hochgezählt wird (so hast du es jedenfalls geschrieben) dann kann der Wert auch weit größer als 1000 und sogar negativ sein, in dem Moment in dem du sie abfragst. Und dann wirst du Probleme kriegen.

Ich würde die ganze Berechnung mit in die Interrupt Routine reinbringen und die Werte ( Stunden, Minuten, Sekunden) in drei volatile Variablen speichern, so kannst du sie jederzeit abrufen. Aber ist nur ein erster Gedanke

sammler
23.04.2011, 18:31
Du hast offenbar etwas übersehen. Die von dir erwähnte abfrage TRIFTT dummerweise zu, wie du dem Screenshot entnehmen kannst. ;-)
Dass
if (g_msec < 1000) {}nach
if (g_msec > 999) {}nicht zutreffen DARF, ist mir schon auch klar...
die Zeile entsprang nur meiner verzweiflung; und da die inkrementierung leider mehr als einmal aufgeführt wurde, versuche ich jetzt herauszufinden, wie es dazu kommen kann...
vielleicht flackert ja ein Bit im RAM, aber ich halte das für ehr unwahrscheinlich...

nachdem ich den ganzen µC jetzt noch einmal komplett gelöscht und auch die Fuses einmal (bis auf SPIEN) nicht gesetzt programmiert habe, bevor ich wieder zurück gewechselt habe, scheint der Fehler nicht mehr sehr häufig aufzutreten...
ich habe die "debug-Prüfung" etwas angepasst:

if (g_msec < 1000){
g_fail++; // volatile, int16
}
g_fail wird jetzt außerdem mit ausgegeben, die entsprechende Zeile (in main.c) ist:

sprintf(buffer, "\nT: %02d:%02d:%02d.%04d %04d\n", g_h, g_min, g_sec, g_msec, g_fail);der Wert ist bei Laufzeit 00:01:04 (hh, mm, ss) des Programms auf 1 gesprungen (ja, wird immer mit 0 initialisiert) und mehr als eine Stunde so geblieben... scheint also ein (mittlerweile) ehr seltenes Phänomen zu sein... (das ist so auch reproduzierbar, er macht das immer wieder zu dem Zeitpunkt und dann geht es zumindest bisher ohne diesen Fehler weiter, bis ich neustarte)

Der Code der Timerroutine ist übrigens im Anhang dabei, ist aber auch nix weltbewegendes:

ISR(TIMER0_COMP_vect)
{
// hier wird der msec-zähler gesetzt. Die übrigen
// Programmteile müssen diese Aenderung "sehen":
// volatile -> aktuellen Wert immer in den Speicher schreiben
g_msec++;
}


zu früh gefreut:
00:01:43.-232 0003

TobiKa
23.04.2011, 18:34
Nein, das habe ich nicht übersehen.

Und wieso ignorierst du alles was ich zum Typ von g_msec und der Möglichkeit das es negativ sein kann geschrieben hab?

sammler
23.04.2011, 19:03
Hi Tobi ;)

Dass g_msec signed ist, stammt daher, dass die Variable zum teil (weil die Abfrage mal wieder gesponnen hat) in den Bereich 65xxx gejagt wurde, als sie noch unsigned war.
Und DA gab es dann natürlich Probleme, weil die Abfrage ständig zutraf, was zu einer ehr ungünstigen dauerinkrementierung der g_sec Variablen geführt hat, wodurch die "Uhr" erstmal gründlich vorging.
Aufgrund der (derzeitigen) Auslastung des µC bekomme ich mit der aktuellen Version faktisch keine Probleme, weil die Routine mehr als 1x in der Sekunde aufgerufen wird...
negative Zahlen sind für die Abfrage grundsätzlich unproblematisch - es dauert nur länger, bis sie wieder zuschlägt (bei mir meist 232 ms, wenn die vorherige Runde wieder einmal schiefging).

sternst
24.04.2011, 00:46
if (g_msec > 999)
...
PS: der Wert von g_msec, wenn er fälschlich reinspringt, ist offenbar immer 768 (dez.)Du hast schlicht ein Atomizitäts-Problem. g_msec ist größer als ein Byte, und damit kann der Vergleich nicht atomar sein. Er besteht aus mehreren Maschinenbefehlen und der Interrupt kann da mitten rein fallen. Was ganz genau passiert, ist folgendes:
- g_msec ist 767 (0x02ff) wenn der Vergleich beginnt.
- Zuerst wird das Low-Byte genommen (0xff).
- Jetzt kommt der Interrupt und ändert g_msec von 767 (0x02ff) auf 768 (0x0300).
- Der Vergleich macht weiter mit dem High-Byte (0x03).
- Ergebnis des Vergleichs: True, weil 1023 (0x03ff) größer ist als 999.

TobiKa
24.04.2011, 10:39
Du meinst also das cli(); sollte besser noch eine Zeile früher kommen?

sternst
24.04.2011, 12:07
Du meinst also das cli(); sollte besser noch eine Zeile früher kommen?Könnte man so machen, aber dann muss man auch das sei() aus dem if raus ziehen, weil ja sonst die Interrupts im False-Fall abgeschaltet blieben. Ich persönlich würde das nicht machen, weil dann nämlich im True-Fall die Interrupts viel länger abgeschaltet wären, als nötig. Das muss keine Auswirkungen haben (hätte es in diesem einfachen Fall auch nicht), aber besser man gewöhnt sich gleich an, Interrupts immer nur so kurz wie möglich abzuschalten. Bei mir sähe es in etwa so aus:

#include <util/atomic.h>


void make_time (void) {

int16_t tmp_msec;

ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
tmp_msec = g_msec;
}

if (tmp_msec > 999) {

ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
g_msec -= 1000;
}

g_sec++;
g_send=1;
PORTA ^= (1 << PINA0);
if (g_sec > 59){
g_sec = 0;
g_min++;
PORTA ^= (1 << PINA1);
if (g_min > 59){
g_min = 0;
g_h++;
PORTA ^= (1 << PINA2);
if (g_h > 23){
g_h = 0;
g_d++;
}
}
}
}
}

TobiKa
24.04.2011, 12:20
Ja, das ist schon klar.

Aber wie gesagt, ich würde alles in den Interrupt rein packen.

sammler
24.04.2011, 21:15
Hi ihr beiden!

Danke euch für eure Unterstützung!

@Sternst: Vielen, vielen dank für diese Erklärung! Das wird mich vermutlich in Zukunft vor solchen Problemen bewahren. ;-)
ich hab befürchtet, dass ich was prinzipiell einfaches übersehen hab...



ATOMIC_BLOCK(ATOMIC_FORCEON){
...
}ist das selbe wie
cli();
...
sei();oder? Nur, dass ich nicht noch "util/atomic.h" include (wenn ich die Datei richtig lese...).

@TobiKa: An der Hochschule hat man mir eingebleut, dass Interruptroutinen so kurz wie nur irgendmöglich sein sollen. Daher hab' ich die (unkritischen) Teile aus der Routine herausgelöst...
Und wenn ich es in den Interrupt lege sehe ich auch keinen echten Vorteil...

sternst
24.04.2011, 22:36
ist das selbe wie Nun, fast.
ATOMIC_BLOCK bietet einen besseren Schutz gegen Code-Reordering. Und es stellt sicher, dass die Interrupts wieder eingeschaltet werden, egal auf welchem Exit-Pfad der Code-Block verlassen wird (du kannst sogar ohne Probleme "return" innerhalb des Blockes verwenden).

TobiKa
25.04.2011, 12:41
@TobiKa: An der Hochschule hat man mir eingebleut, dass Interruptroutinen so kurz wie nur irgendmöglich sein sollen. Daher hab' ich die (unkritischen) Teile aus der Routine herausgelöst...
Und wenn ich es in den Interrupt lege sehe ich auch keinen echten Vorteil...
Ja da hast du schon recht, aber 3 Abfragen und 3 Zuweisungen sollten kein Problem sein.
Ich sehe bei deiner Variante Probleme falls "UART" mal länger braucht und g_msec irgendwann überläuft.