Forum: Mikrocontroller und Digitale Elektronik ATmega328P - ADC Conversion: Unterschied der Befehlverarbeitung innerhalb ISR


von LeDay (Gast)


Angehängte Dateien:

Lesenswert?

Guten Tag.

Ich habe eine mir nicht erklärbare Anomalie bei einem ATmega328P, der 
mit einem 16MHz Oszillator getaktet wird, feststellt. Es geht dabei um 
die Durchführung einer ADC-Conversion und das Umschalten eines Ausgangs 
hinsichtlich seines digitalen Levels.
Ich habe dabei zwei unterschiedliche Codevarianten implementiert, die 
eigentlich das Umschalten des PD1 Pins auf die selbe Art durchführen 
sollten. Anbei habe ich dann die Spannungsverläufe des PD1 Pins 
angehängt.
Man kann erkennen, dass beim ersten Fall der Pin jeweils nur eine 
Taktdauer auf dem gesetzten Pegel ist. Im zweiten Fall ist aber der Pin 
für die erste Portanweisung für zwei Taktperioden auf dem Low-Level.

Weiß möglicherweise jemand, wie das zustande kommt? Ich habe mir 
überlegt, dass es irgendwas mit dem Springen zur ISR zu tun haben 
könnte. Das sollte ja aber eigentlich schon bis zur dieser Codeposition 
erfolgt sein, weshalb ich nicht wirklich weiß, was die Ursache ist.
Ich würde mich freuen, wenn jemand etwas dazu sagen könnte.

MfG LeDay


Code Fall 1:
1
#include <avr/io.h>
2
volatile unsigned char Test;
3
int main(void)
4
{
5
  DDRD = 0x02;
6
  PORTD = 0x02;
7
  ADMUX = (1 << REFS0) | (1 << ADLAR);
8
  ADCSRA = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (0 << ADIE) | (1 << ADEN);
9
  ADCSRA |= (1 << ADSC);
10
  while(1)
11
  {
12
    while(!(ADCSRA & (1 << ADIF)));
13
    Test = ADCH;
14
    PORTD = 0x00;
15
    PORTD = 0x02;
16
    PORTD = 0x00;
17
    PORTD = 0x02;
18
    PORTD = 0x00;
19
    PORTD = 0x02;
20
    PORTD = 0x00;
21
    PORTD = 0x02;
22
    ADCSRA |= (1 << ADSC);
23
  }
24
}

Code Fall 2:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
volatile unsigned char Test;
4
int main(void)
5
{
6
  DDRD = 0x02;
7
  PORTD = 0x02;
8
  ADMUX = (1 << REFS0) | (1 << ADLAR);
9
  ADCSRA = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADIE) | (1 << ADEN);
10
  sei();
11
  ADCSRA |= (1 << ADSC);
12
  while(1)
13
  {
14
  }
15
}
16
ISR(ADC_vect)
17
{
18
  Test = ADCH;
19
  PORTD = 0x00;
20
  PORTD = 0x02;
21
  PORTD = 0x00;
22
  PORTD = 0x02;
23
  PORTD = 0x00;
24
  PORTD = 0x02;
25
  PORTD = 0x00;
26
  PORTD = 0x02;
27
  ADCSRA |= (1 << ADSC);
28
}

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Am Besten erstmal den disassemblierten Code ansehen und posten. Nicht 
dass der Compiler irgendwas zwischen die beiden ersten 
PORTD-Schreibzugriffe in der ISR packt.

von S. Landolt (Gast)


Lesenswert?

> irgendwas zwischen die beiden ersten
> PORTD-Schreibzugriffe in der ISR packt
Zum Beispiel ein ldi. Vielleicht auch mal die Compiler-Optimierungen 
umschalten.

von Norbert (Gast)


Lesenswert?

Fand ich spannend, hab's mal compiliert.
Mag nicht der richtige chip sein, zeigt aber genau das Problem.

$ avr-g++ -mmcu=atmega32u4 -O2 code2.c -S
1
.global  __vector_29
2
  .type  __vector_29, @function
3
__vector_29:
4
  push r1
5
  push r0
6
  in r0,__SREG__
7
  push r0
8
  clr __zero_reg__
9
  push r24
10
  push r30
11
  push r31
12
/* prologue: Signal */
13
/* frame size = 0 */
14
/* stack size = 6 */
15
.L__stack_usage = 6
16
  lds r24,121
17
  sts Test,r24
18
  out 0xb,__zero_reg__
19
20
HIER ISSER:
21
  ldi r24,lo8(2)
22
23
  out 0xb,r24
24
  out 0xb,__zero_reg__
25
  out 0xb,r24
26
  out 0xb,__zero_reg__
27
  out 0xb,r24
28
  out 0xb,__zero_reg__
29
  out 0xb,r24
30
  ldi r30,lo8(122)
31
  ldi r31,0
32
  ld r24,Z
33
  ori r24,lo8(64)
34
  st Z,r24
35
/* epilogue start */
36
  pop r31
37
  pop r30
38
  pop r24
39
  pop r0
40
  out __SREG__,r0
41
  pop r0
42
  pop r1
43
  reti

von LeDay (Gast)


Lesenswert?

Wow, danke euch.

Also helfen würde dann wahrscheinlich, vor dem ersten Low-Befehl, den 
Port wiederholt auf High zu setzen, damit das Register vorgeladen wird.
Ich muss mal überprüfen, ob das noch einen Performancegewinn im 
Vergleich zur Polling-Variante darstellt.

Oder einfach ASM lernen :).

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

LeDay schrieb:
> Oder einfach ASM lernen :).

Das ist hier die einzig richtige Antwort, denn nur so kann man solche 
Taktgenauen Sachen bauen. Der C-Compiler kann einem immer irgendwas 
umsortieren. Oder noch besser das Signal-Timing per Timer/PWM o.ä. in 
Hardware erzeugen.

LeDay schrieb:
> Also helfen würde dann wahrscheinlich, vor dem ersten Low-Befehl, den
> Port wiederholt auf High zu setzen, damit das Register vorgeladen wird.

So etwas kann vielleicht helfen, aber wenn du irgendwo irgendwas anderes 
änderst, klappt's dann vielleicht nicht mehr.

von Einer K. (Gast)


Lesenswert?

LeDay schrieb:
> Also helfen würde dann wahrscheinlich, ...
Statt der Brachialmethode die Pin Toggle Fähigkeit zu nutzen.

Statt:
1
  PORTD = 0x00;
2
  PORTD = 0x02;
3
  PORTD = 0x00;
4
  PORTD = 0x02;
5
  PORTD = 0x00;
6
  PORTD = 0x02;
7
  PORTD = 0x00;
8
  PORTD = 0x02;


Dieses:
1
  PIND = (1 << PD1);
2
  PIND = (1 << PD1);
3
  PIND = (1 << PD1);
4
  PIND = (1 << PD1);
5
  PIND = (1 << PD1);
6
  PIND = (1 << PD1);
7
  PIND = (1 << PD1);
8
  PIND = (1 << PD1);
9
  PIND = (1 << PD1);

von LeDay (Gast)


Lesenswert?

Ja, das hat tatsächlich geholfen. Meine Intention ist nicht die ganze 
Zeit zu togglen, aber generell kann ja dann eine entsprechende 
Verzögerung implementiert werden bis es zur nächsten gewünschten 
Änderung kommt, und dann mit dem Toggle-Befehl der Pin umgeschaltet 
werden.
Danke für den Tipp!

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.