Forum: Mikrocontroller und Digitale Elektronik uint32_t division atmega328p


von Christian G. (Firma: GDV-IT) (gdv-it)


Lesenswert?

Hallo Forum,

ich habe hier ein kleines Verständnisproblem zur 32-Bit Division.
Ab und zu, in vieleicht 5% der Fälle, kommt es zu fehlerhaften 
Berechnungen wenn ich 2 UINT32_T Zahlen miteinander dividiere. Hier mein 
Auszug aus Code und Log, vieleicht fällt jemanden was dazu ein. 
Dankeschön.

CODE:

declaration:
1
// ADC
2
static uint32_t AdcSamples[] = {0,0,0,0,0,0};
3
static uint32_t AdcSum[] = {0,0,0,0,0,0};
4
static bool CalculationInProgress = false;

interrupt:
1
ISR(TIMER0_OVF_vect) {
2
        // dont count on calculation
3
        if(!CalculationInProgress) {
4
                // read ADC for each sensor input
5
                // and sumarize ADC values
6
                // PINCx = ADCx
7
                for(int t = 0; t < 6; t++) {
8
                        if(SwitchLayout[t] == 3) {
9
                                AdcSamples[t]++;
10
                                AdcSum[t] = AdcSum[t] + (uint32_t) ReadADC(t);
11
                        }
12
                }
13
        }
14
}

main:
1
                        // every minute (64x60)
2
                        if (AdcSamples[t] >= (64*60)) {
3
4
                                CalculationInProgress = true;
5
6
                                uint32_t roundedVal = AdcSum[t] / AdcSamples[t];
7
                                sendGenericUint32Message(9634, AdcSum[t]);
8
                                sendGenericUint32Message(9635, AdcSamples[t]);
9
                                sendGenericUint32Message(9636, roundedVal);
10
11
                                AdcSamples[t] = 0;
12
                                AdcSum[t] = 0;
13
14
                                CalculationInProgress = false;
15
16
                      }

ERKLÄRUNG:
Ein Timer führt alle paar millisekunden die ISR durch und summiert Werte 
u.
Anzahl Zählungen um am ende einen Mittelwert bilden zu können.

RICHTIG:

9634 with len 4  0x00 0x19 0x8f 0x02 
9635 with len 4  0x00 0x00 0x0f 0x00 
9636 with len 4  0x00 0x00 0x01 0xb4 

1675010 / 3840 = 436,2005 => 436

FALSCH:

9634 with len 4  0x00 0x19 0x90 0x4c 
9635 with len 4  0x00 0x00 0x0f 0x00 
9636 with len 4  0x00 0x00 0x01 0x99 

1675340 / 3840 = 436,2864 => 436
aber berechnet wird 409

: Bearbeitet durch User
von Franz (Gast)


Lesenswert?

"CalculationInProgress" sollte "volatile" sein.

von Walter T. (nicolas)


Lesenswert?

Christian G. schrieb:
> Hier mein
> Auszug aus Code und Log, vieleicht fällt jemanden was dazu ein.

Wenn bei einer Division ein unerwartetes Ergebnis herauskommt, ist es in 
90% der Fälle so, dass nicht die Zahlen durcheinander verrechnet werden, 
von denen man es glaubt. In den restlichen 10% ist das Ergebnis, das man 
sich ansieht, nicht das Ergebnis der genannten Operation.

Sprich: Guck, ob wirklich im Array in dem Moment das drin steht, was 
erwartet wird.

Franz schrieb:
> "CalculationInProgress" sollte "volatile" sein.

Und mindestens AdcSamples[] oder AdcSum[], ansonsten finden die 
Operationen auf den Arrays nicht unbedingt zwischen den beiden 
Operationen auf das bool statt.

Ansonsten ist der Code äquivalent zu:
1
                                CalculationInProgress = true;
2
                                CalculationInProgress = false;
3
                                uint32_t roundedVal = AdcSum[t] / AdcSamples[t];
4
                                sendGenericUint32Message(9634, AdcSum[t]);
5
                                sendGenericUint32Message(9635, AdcSamples[t]);
6
                                sendGenericUint32Message(9636, roundedVal);
7
                                AdcSamples[t] = 0;
8
                                AdcSum[t] = 0;

: Bearbeitet durch User
von mitlesa (Gast)


Lesenswert?

Christian G. schrieb:
> AdcSum[t] = AdcSum[t] + (uint32_t) ReadADC(t);

ReadADC () ist hier nicht zu sehen, könnte bisschen länger
dauern als das was man in der ISR tun sollte.

von Christian G. (Firma: GDV-IT) (gdv-it)


Lesenswert?

War mir garnicht bewusst, ich werde es mal so umsetzen und beobachten.

@mitlesa : ich war davon ausgegangen,dass es keine Rolle spielt, da die 
Zahlen für die Division ja die "richtigen" sind
1
uint16_t ReadADC(uint8_t ADCchannel) {
2
  //select ADC channel with safety mask
3
  ADMUX = (ADMUX & 0xF0) | (ADCchannel & 0x0F);
4
  //single conversion mode
5
  ADCSRA |= (1<<ADSC);
6
  // wait until ADC conversion is complete
7
  while( ADCSRA & (1<<ADSC) );
8
  return ADC;
9
}

und natürlich werde ich das auslagern in die main :)

: Bearbeitet durch User
von QQ (Gast)


Lesenswert?

mitlesa schrieb:
> Christian G. schrieb:
>> AdcSum[t] = AdcSum[t] + (uint32_t) ReadADC(t);
>
> ReadADC () ist hier nicht zu sehen, könnte bisschen länger
> dauern als das was man in der ISR tun sollte.

vor Allem, da das ja 6x ausgeführt wird.

Christian G. schrieb:
> while( ADCSRA & (1<<ADSC) );

Funktionsaufrufe können lange dauern, vor Allem, wenn viel auf den Stack 
muss. Außerdem sind solche Schleifen im Interrupt ein no-go.

Schaue dir mal den "ADC conversion complete" interrupt an!

Verkleinere mal die Datentypen, wo es möglich ist. Ist uint32_t wirklich 
nötig???

Außerdem sorgt "CalculationInProgress" dafür, dass sicherlich nicht 
jeder timer interrupt den ADC bedient. Gegebenenfalls kann das auch zu 
Problemen führen.

von Christian G. (Firma: GDV-IT) (gdv-it)


Lesenswert?

Vielen Dank für eure Mühe,

CalculationInProgress hatte ich nur testhalber eingebaut um genau das zu 
erreichen was volatile scheinbar macht. Dass die ISR nicht in den Werten 
herumschreibt. Bis jetzt scheint es zu laufen, mir waren die Eigenheiten 
von volatile in C nicht bekannt. Ich und C werden eh keine Freunde mehr 
;)

uint32_t ist leider aufgrund der mess- und kalkulationsgenauigkeit 
nötig.

Ich beobachte mal obs noch ausreißer gibt, bis dahin hab ich erstmal 
viel anderen Code zu fixen ...

von Christian G. (Firma: GDV-IT) (gdv-it)


Lesenswert?

kurze Rückmeldung, keine Fehler mehr, DANKESCHÖN!

von (prx) A. K. (prx)


Lesenswert?

"volatile" ist keine Memory Barrier. Die Zugriffe auf dieses Flag sind 
per Sprachdefinition zwar in sich geordnet, müssen das aber nicht 
gegenüber den Zugriffen auf die Arrays sein. Auch wenn das nun 
wahrscheinlich so rauskommt.

https://www.nongnu.org/avr-libc/user-manual/group__avr__cpufunc.html

: Bearbeitet durch User
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.