Forum: Mikrocontroller und Digitale Elektronik Soft-PWM Tiny25


von Gerhard (Gast)


Lesenswert?

Ich wundere mich gerade ein wenig, den Artikel über Soft-PWM hier habe 
ich gelesen.

timer1, 1 Kanal direkt via OCR1A, top OCR1C, 3 Kanäle via Software
Sortieren, Masken, nächsten PWM-Kanal via OCR1B-Interrupt etc, alles 
gemacht, funktioniert auch - bis auf einen Fall: 2 Werte unterscheiden 
sich nur um ein LSB, dann werden die folgenden  Kanäle nicht bedient

Probeweise den Vorteiler erhöht - änderte nichts.

unsigned char OCR1B_reload[3]={10,20,30}; //klappt
unsigned char OCR1B_reload[3]={10,11,30}; //Kanal 1 ok, 2 und 3 nicht

Die Software hatte auf einem Mega88 funktioniert (denke ich zumindest, 
vielleicht ist es auch nur nicht aufgefallen, habe aber leider keinen 
hier um das mal zu testen). In der Simulation funktionert es aber mit 
dem 88er, mit dem Tiny25 aber auch nicht.

Man kann das Problem eigentlich auf folgendes herabbrechen:

// Timer1 output compare B interrupt service routine
interrupt [TIM1_COMPB] void timer1_compb_isr(void)
{
 OCR1B++;    //eigentlich kommt der neue Wert aus einem sortierten array
}

In Wirklichkeit passiert natürlich noch ein bisschen mehr, aber diese 
eine Zeile zeigt das Problem. Der Interrupt mit OCR1B+1 kommt nicht.

Den Mega88-Timer hatte ich auch nur mit 8bit laufen, top on ICR1.

von Jakob (Gast)


Lesenswert?

{ OCR1B++; }

Als ASM-Doof-ling kann ich nur beisteuern, dass OCR1B kein
Standard-Register, sondern ein I/O-Register ist.
Fürs Inkrementieren muss es in ein Standard-Register gelesen
werden, dieses inkrementieren und den neuen Inhalt wieder nach
OCR1B schreiben.

Ist ja eine tolle Erleichterung, wenn die Libraries bei Tiny
und Mega nicht vergleichbar arbeiten!

Dann lieber gleich ASM.

von Gerhard (Gast)


Lesenswert?

Daran liegt es nicht, das macht der Compiler schon richtig
                 ; 0000 0045  OCR1B++;
000059 b5eb        IN   R30,0x2B
00005a 5fef        SUBI R30,-LOW(1)
00005b bdeb        OUT  0x2B,R30

Und genau in der Art muss man es in Assembler auch machen.
Hat auch mit libraries überhaupt nichts zu tun. Ich incrementiere auch 
nicht wirklich, sondern laden den nächsten OCR-Wert aus einem array 
nach. Das mit OCR1B++ ist nur der Einfachheit halber (nächster Wert soll 
wieder einen OCR1B-Int auslösen.
Ich habe im Datenblatt keinen Hinweis gefunden, dass das nicht gehen 
sollte. Erste Vermutung, dass der Interrupt zu lange dauert und deshalb 
den nächsten nicht erwischt. Ist aber auch nicht das Problem. Vorteiler 
original ist 64, ISR dauert um die 30 Takte. Mit Vorteiler 128 dasselbe. 
Erhöhe ich OCR1B nur um eins, funktioniert das beim Tiny25-Timer1 
offensichtlich nicht. Und das ist Mist. Das Problem müsste eigentlich 
schon mal jemand gehabt haben, wenn es denn tatsächlich eins ist. 
Gefunden habe ich darüber nichts, deswegen denke ich, dass ich das 
Problem bin und nicht der arme kleine Tiny. Nur fällt mir nichts mehr 
ein, da habe ich schon ein paar Stunden draufrumgekaut :-(

von Jakob (Gast)


Lesenswert?

Falls niemand sonst helfen will - erklär mir doch mal "auf
ASM", was SW-PWM mit

> unsigned char OCR1B_reload[3]={10,20,30}; //klappt
> unsigned char OCR1B_reload[3]={10,11,30}; //Kanal 1 ok, 2 und 3 nicht

bedeuten soll.

Manchmal wird einem beim Erklärungsversuch schon klar, was
man sich vorher nicht selbst klar gemacht hat. ;-)

von Gerhard (Gast)


Lesenswert?

Auch das hat mit meinem eigentlichen Problem nichts zu tun.
Assembler kann ich aber nicht.
Grober Ablauf:
-der timer1_ov setzt alle Ausgänge, deren PWM-Wert grösser 0 ist
-ins OCR1B-Register wird der kleinste PWM-Wert aller Kanäle geschrieben
-der folgende OCR1B-Interrupt setzt den (oder die Ausgänge, falls 
gleiche Werte), die diesem PWM-Wert entsprechen zurück und lädt den 
nächstgrösseren PWM-Wert usw und irgendwann ganz von vorne

Funktioniert auch alles prächtig, solange sich 2 PWM-Werte um min. 2 
unterscheiden. Unterscheiden sie sich aber nur um 1, wird der eigentlich 
nächste auszuführende OCR1B-Int nicht ausgeführt. Damit entfällt dann 
sowohl das Abbrechen des zweiten Kanals und logischerweise damit auch 
das Nachladen des 3.Kanals.
PWM1_Sollwert 10
PWM2_Sollwert 20
PWM3_Sollwert 30 -> alles ist gut

PWM1_Sollwert 10 -> funktioniert
PWM2_SOllwert 11 -> funktioniert nicht, Kanal bleibt bei 100%
PWM3_Sollwert egal -> auch 100%

Es spucken auch keine anderen Interrupts dazwischen, ich habe alles 
andere rausgeschmissen - Problem bleibt.

Und wenn man dann sowas macht (Pseudocode)
pwm[0]=50;
pwm[1]=80;
test:
for (loop=0;loop<255;loop++)
    {pwm[2]=loop;
     set_pwm();   //sortieren, Masken setzen etc
     delay_ms(100); //nein, nicht wirklich
    }
goto test;         //auch nicht wirklich

entstehen lustige Sachen bei loop=49, 51 79 und 81

von jederZweite (Gast)


Lesenswert?

Gerhard schrieb:
> PWM3_Sollwert 30 -> alles ist gut
>
> PWM1_Sollwert 10 -> funktioniert
> PWM2_SOllwert 11 -> funktioniert nicht, Kanal bleibt bei 100%
> PWM3_Sollwert egal -> auch 100%
>
> Es spucken auch keine anderen Interrupts dazwischen, ich habe alles
> andere rausgeschmissen - Problem bleibt.
>
> Und wenn man dann sowas macht (Pseudocode)
>      delay_ms(100); //nein, nicht wirklich
> goto test;         //auch nicht wirklich
>
> entstehen lustige Sachen bei loop=49, 51 79 und 81

Das komplette Programm liegt nicht vor. Durch subjektive Kommentare, die 
aber nur für den OP bestimmt sinnvoll sind und viel Bla Bla, die den 
Programmcode anscheinend ersetzten sollen, ist es mir einfach zu mühsam 
dem OP zu folgen und dem Problem näher zu rücken.

von Peter D. (peda)


Lesenswert?

Jakob schrieb:
> Dann lieber gleich ASM.

Das hat nichts mit ASM zu tun, daß Atmel die Timerfunktionen bei jedem 
AVR-Derivat neu gemischt hat. Mich hat das auch schon aufgeregt, daß es 
keine einheitlichen Timer gibt.

von Gerhard (Gast)


Lesenswert?

Lieber jedeZweite,
danke für den äusserst wertvollen und kurzweiligen Kommentar, den rahme 
ich mir ein!

Mit der einen Zeile war eigentlich alles wichtige gesagt.

Es ist tasächlich so: der Timer1 bekommt es nicht gebacken, 2 direkt 
aufeinanderfolgende OCR1x-Werte zu  verarbeiten. Immerhin ist der 
Simulator so nett und macht es wie der reale Prozessor. Gerade mit den 
Timern habe ich mit AVR-Studio schon nette Sachen erlebt
Mit dem Timer0 klappt es wie erwartet, da müsste ich allerdings alle 4 
Kanäle via Soft-PWM bedienen, da der wiederum im fast-PWM-Mode OCR0A/B 
wohl erst bei Überlauf übernimmt, auch wenn nur einer als Hardware-PWM 
arbeitet
OCR1A dasselbe Verhalten wie OCR1B.

// Timer1 output compare A interrupt service routine
interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{
  OCR1A+=2;
  PORTB.0=!PORTB.0;
}
arbeitet völlig korrekt,
mit +1 passiert gar nichts. Jetzt werde ich noch mal tief in der Kiste 
graben, ob ich noch ein paar andere AVRs finde. Ein NET-I/O habe ich auf 
jeden Fall noch mit Mega644. Und Tiny2313 müsste auch noch da sein. 
Interessiert mich jetzt.

von Peter D. (peda)


Lesenswert?

Gerhard schrieb:
> Mit der einen Zeile war eigentlich alles wichtige gesagt.

Zeig trotzdem mal einen compilierfähigen Code, d.h. mit Init und Main.
Niemand hat Lust, erstmal rumprobieren zu müssen.

Gerhard schrieb:
> mit +1 passiert gar nichts.

Wirklich nichts oder erst nach 257 Timerzyklen?

von Gerhard (Gast)


Lesenswert?

Peter D. schrieb:
> Gerhard schrieb:
>> Mit der einen Zeile war eigentlich alles wichtige gesagt.
>
> Zeig trotzdem mal einen compilierfähigen Code, d.h. mit Init und Main.
Mach ich gleich mal, Mnimalvariante.
> Niemand hat Lust, erstmal rumprobieren zu müssen.

Es sollte ja auch niemand probieren. Ich hatte auf Erfahrungen gehofft
-Ne, geht nicht weil xyz oder
-muss gehen, bei mir hat es funktioniert

So exotisch schien mir die Aufgabe nicht

> Gerhard schrieb:
>> mit +1 passiert gar nichts.
>
> Wirklich nichts oder erst nach 257 Timerzyklen?
Nichts mehr, da beim Überlauf eh wieder der kleinste OCR-Wert geladen 
wird.

von Peter D. (peda)


Lesenswert?

Vermutlich benutzt Du den PWM-Mode, dann gilt das hier:

"Note that in PWM mode, writing to the Output Compare Registers OCR1A or 
OCR1B, the data value is first transferred to a temporary location. The 
value is latched into OCR1A or OCR1B when the Timer/Counter reaches
OCR1C."

D.h. der neue OCR1B wird erst beim Erreichen von OCR1C übernommen.

von Gerhard (Gast)


Lesenswert?

Das ist das Problemchen, dass ich im Moment mit dem Timer0 habe. Sobald 
da einer von beiden als Hardware-PWM-arbeitet, wird auch der zweite erst 
beim Überlauf übernommen.
Beim Timer1 ist es anders, es gilt nur für den, der auch an die 
PWM-Einheit gekoppelt ist. Der andere wird direkt geschrieben (sonst 
würde das folgende ja auch nicht funktionieren)
1
/*******************************************************
2
Chip type               : ATtiny25
3
AVR Core Clock frequency: 8,000000 MHz
4
Memory model            : Tiny
5
External RAM size       : 0
6
Data Stack size         : 32
7
*******************************************************/
8
9
#include <tiny25.h>
10
11
#define LED1 0 //PortB0
12
#define LED2 2 //PortB2
13
14
unsigned char pwm_on=(1<<LED1) | (1<<LED2); 
15
unsigned char OCR1B_reload[3]={10,       //ch1
16
                             20,       //ch2
17
                 0xff};    //OCR1C+1, never reached
18
19
20
21
unsigned char pwm_off[2]= {~(1<<LED1),
22
                         ~(1<<LED2)};
23
unsigned char channel_select;
24
     
25
// Timer1 overflow interrupt service routine
26
interrupt [TIM1_OVF] void timer1_ovf_isr(void)
27
{PORTB|= pwm_on;
28
 channel_select=0;
29
 OCR1B=OCR1B_reload[0];        //ch1
30
}
31
32
// Timer1 output compare B interrupt service routine
33
interrupt [TIM1_COMPB] void timer1_compb_isr(void)
34
{PORTB&=pwm_off[channel_select];
35
 channel_select++;
36
 OCR1B=OCR1B_reload[channel_select];
37
38
}
39
40
void main(void)
41
{
42
43
// Crystal Oscillator division factor: 1
44
#pragma optsize-
45
CLKPR=(1<<CLKPCE);
46
CLKPR=(0<<CLKPCE) | (0<<CLKPS3) | (0<<CLKPS2) | (0<<CLKPS1) | (0<<CLKPS0);
47
#ifdef _OPTIMIZE_SIZE_
48
#pragma optsize+
49
#endif
50
51
// Input/Output Ports initialization
52
// Port B initialization
53
// Function: Bit5=In Bit4=Out Bit3=Out Bit2=Out Bit1=Out Bit0=Out 
54
DDRB=(0<<DDB5) | (1<<DDB4) | (1<<DDB3) | (1<<DDB2) | (1<<DDB1) | (1<<DDB0);
55
// State: Bit5=T Bit4=0 Bit3=0 Bit2=0 Bit1=1 Bit0=0 
56
PORTB=(0<<PORTB5) | (0<<PORTB4) | (0<<PORTB3) | (0<<PORTB2) | (1<<PORTB1) | (0<<PORTB0);
57
58
59
// Timer/Counter 1 initialization
60
// Clock source: System Clock
61
// Clock value: 125,000 kHz
62
// Mode: PWMA top=OCR1C
63
// OC1A output: OC1A=PWM, /OC1A disc.
64
// OC1B output: Disconnected
65
// Timer Period: 2,04 ms
66
// Output Pulse(s):
67
// OC1A Period: 2,04 ms Width: 1,02 us
68
// Timer1 Overflow Interrupt: On
69
// Compare A Match Interrupt: Off
70
// Compare B Match Interrupt: On
71
PLLCSR=(0<<PCKE) | (0<<PLLE) | (0<<PLOCK);
72
73
TCCR1=(0<<CTC1) | (1<<PWM1A) | (1<<COM1A1) | (0<<COM1A0) | (0<<CS13) | (1<<CS12) | (1<<CS11) | (1<<CS10);
74
GTCCR=(0<<TSM) | (0<<PWM1B) | (0<<COM1B1) | (0<<COM1B0) | (0<<PSR1) | (0<<PSR0);
75
TCNT1=0x00;
76
OCR1A=0x80;    
77
OCR1B=0x00;
78
OCR1C=0xFE;
79
80
// Timer(s)/Counter(s) Interrupt(s) initialization
81
TIMSK=(0<<OCIE1A) | (1<<OCIE1B) | (0<<OCIE0A) | (0<<OCIE0B) | (1<<TOIE1) | (0<<TOIE0);
82
83
84
// Global enable interrupts
85
#asm("sei")
86
87
while (1)
88
      {
89
      }
90
}

Setzt man OCR1B_reload[0] auf 19, ist Kanal 2 tot (bzw. 100%).
Ganze Aufbereitung/Sortierung habe ich jetzt mal weggelassen.
Und nein, an evtl. fehlendem volatile liegt es nicht.

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.