Forum: Compiler & IDEs PWM: ASM-Code in C-Code umwandeln


von Tobias Paul (Gast)


Lesenswert?

Hallo,
es geht um folgenden ASM Code:
http://www.mikrocontroller.net/articles/AVR-Tutorial:_PWM
Abschnitt: PWM in Software

Leider funktioniert mein Programm nicht so ganz wie ich es mir 
vorstelle.
Um den Fehler einzugrenzen, wollte ich mal fragen ob mal jemand kurz 
einen Blick auf meinen C-Code werfen kann und mir sagen kann, ob ich die 
ASM Befehle richtig nach C portiert hab.

Interrupt-Routine:
1
timer0_overflow:                      ; Timer 0 Overflow Handler
2
        inc     PWMCount              ; den PWM Zähler von 0 bis
3
        cpi     PWMCount, 128         ; 127 zählen lassen
4
        brne    WorkPWM
5
        clr     PWMCount
1
SIGNAL(SIG_OVERFLOW0) // Timer0 Overflow Interrupt
2
{
3
  PWMCount++;
4
  if (PWMcounter <= 128) 
5
   { PWMwork(); }
6
  else
7
    { PWMcounter = 0; }
8
  
9
}


PWM ausgeben:
ASM-Code steht im Artikel, den ich verlinkt hab.
1
void PWMwork()
2
{
3
 LEDstat = 0;
4
 
5
 if (PWMcounter <= 20)
6
 { LEDstat = LEDstat + 0x01; }
7
 
8
 if (PWMcounter <= 60)
9
 { LEDstat = LEDstat + 0x02; }
10
 
11
 if (PWMcounter <= 80)
12
 { LEDstat = LEDstat + 0x04; }
13
 
14
 PORTB = LEDstat;
15
}

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Auf die Schnelle würde ich das hier als C-Code draus machen:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <avr/sleep.h>
4
5
register uint8_t PWMCount asm("r2");
6
static uint8_t ocrs[6] =
7
{
8
    0, 1, 10, 20, 80, 127
9
};
10
11
static void
12
ioinit(void)
13
{
14
    DDRB = 0xff;
15
    TCCR0 = _BV(CS00);
16
    TIMSK = _BV(TOIE0);
17
    sei();
18
}
19
20
21
ISR(TIMER1_OVF_vect)
22
{
23
    if (++PWMCount == 128)
24
    {
25
        PWMCount = 0;
26
    }
27
    uint8_t temp = 0b11000000;
28
    uint8_t i, j;
29
    for (i = 0, j = 1; i < 6; i++, j <<= 1)
30
    {
31
        if (PWMCount > ocrs[i])
32
        {
33
            temp |= j;
34
        }
35
    }
36
    PORTB = temp;
37
}
38
39
int
40
main(void)
41
{
42
    ioinit();
43
    for (;;)
44
        sleep_mode();
45
}

Ist natürlich ungetestet, die Ergänzung, die CPU schlafen zu legen,
darfst du auch weglassen. ;-)

Wenn du das mit -O3 compilierst, hast du das loop enrolling in der ISR
ebenfalls mit drin, d. h. das Zusammenbasteln der Bits ist genauso
effektiv wie in der Assemblerversion.  Wenn du das sleep_mode()
weglässt, darfst du statt ISR() auch ISR_NAKED() schreiben, dann
werden Prolog und Epilog der ISR noch weggelassen.  Theoretisch leben
meine ocr[]-Daten zwar im RAM, aber der Optimizer erkennt den
`degraded case', dass sie konstant sind :) und ersetzt die Zahlen
gleich komplett...  Das wird in der Realität sicher anders werden.

von Karl H. (kbuchegg)


Lesenswert?

Die direkte Umsetzung ist eher sowas:
1
SIGNAL(SIG_OVERFLOW0) // Timer0 Overflow Interrupt
2
{
3
  uint8_t LEDstat;
4
5
  PWMcounter++;
6
  if (PWMcounter == 128) 
7
    PWMcounter = 0;
8
9
  LEDstat = 0;
10
11
  if (PWMcounter > 20)
12
    LEDstat |= 0x01;
13
14
  if (PWMcounter > 60)
15
    LEDstat |= 0x02;
16
 
17
  if (PWMcounter > 80)
18
    LEDstat |= 0x04;
19
 
20
  PORTB = LEDstat;
21
}

von Tobias Paul (Gast)


Lesenswert?

Hab meinen Fehler gefunden. Hatte ein paar ASM Befehle falsch 
interpretiert.

Ich hätte da noch ein paar Fragen zu PWM. Wie PWM vom Prinzip her 
funktioniert ist mir klar, nur bei der Umsetzung in der Software sind 
mir einige Dinge noch nicht so ganz klar.

Ich zähle mit dem Timer0 bei jedem Overflow den PWMCounter eins hoch. 
Bei 128 setze ich den Zähler wieder auf Null.
Warum eigendlich genau bei 128? Hat das einen bestimmten Grund, ich 
könnte ja genau so gut bei 100 oder 200.

if (PWMcounter > 0)
 LEDstat |= 0x01;

Da der Zähler immer größer als 0 ist, wäre die LED in dem Fall immer 
100% eingeschaltet. Richtig?


if (PWMcounter > 64)
 LEDstat |= 0x01;

In dem Fall hätte ich dann eine PWM von 50:50. Auch Richtig?

Bei (PWMcounter > 32) dann 72:25 (ein:aus) usw.

von Karl H. (kbuchegg)


Lesenswert?

Tobias Paul wrote:
>
> Ich zähle mit dem Timer0 bei jedem Overflow den PWMCounter eins hoch.
> Bei 128 setze ich den Zähler wieder auf Null.
> Warum eigendlich genau bei 128? Hat das einen bestimmten Grund, ich
> könnte ja genau so gut bei 100 oder 200.

Könntest du.
Du musst nur einen Kompromiss finden, bei dem
* du noch genügend viele Updates in der Sekunde zustande
  bringst
* du noch genügend hohe PWM-Auflösung bekommst.
  Eine PWM mit 3 Bit (also 8 Stufen) wird dir beim Dimmen
  einer LED nicht viel bringen, weil nach der Stufe 0
  (LED aus), die nächste Stufe schon so hell ist, dass da ein
  enormer Helligkeitssprung stattfindet, während beim Übergang
  von Suufe 1 auf 2, 2 auf 3 usw. die Helligkeit nur mehr sehr
  wenig zunimmt.


>
> if (PWMcounter > 0)
>  LEDstat |= 0x01;
>
> Da der Zähler immer größer als 0 ist,

Sagt wer?
Ganz im Gegenteil: Hier
1
  if (PWMcounter == 128) 
2
    PWMcounter = 0;
wird der Counter explizit auf 0 gesetzt.

> wäre die LED in dem Fall immer
> 100% eingeschaltet.

Oder 100% ausgeschaltet. Kommt drauf an, wie deine
LED an den Port angeschlossen ist.

> if (PWMcounter > 64)
>  LEDstat |= 0x01;
>
> In dem Fall hätte ich dann eine PWM von 50:50. Auch Richtig?

Ja.

>
> Bei (PWMcounter > 32) dann 72:25 (ein:aus) usw.

Siehe den 100% Fall: kann auch anders rum sein.
Aber vom Prinzip her: Stimmt schon.

von Tobias Paul (Gast)


Lesenswert?

Danke für eure Hilfe. Habs jetzt verstanden und auch hinbekommen.

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.