Forum: Mikrocontroller und Digitale Elektronik Attiny 85 CTC PWM zum ansteuern eines Schrittmotors


von Peter (Gast)


Angehängte Dateien:

Lesenswert?

Moin zusammen,

ich will einen Schrittmotor einfach so drehen lassen. Habe aktuell zum 
testen einen TB6600 Treiber mit einem 3Nm 4,2A Schrittmotor.

Jetzt will ich mit einem Attiny 85 dem Timer 0 als CTC verwenden um das 
Pulssignal für den Schrittmotortreiber zu erzeugen.

Mein Problem ist, das die PWM absolut "unstabil" läuft.
Rein theoretisch müsste ich ja von ca. 15-3900Hz "einstellen".
Aber es läuft wie im Bild zu sehen. Kann mir jemand sagen wo mein Fehler 
ist?
1
#define    F_CPU      8000000L
2
#include  <avr\io.h>
3
#include  <stdlib.h>
4
#include   <avr/interrupt.h>
5
6
#define   PWM_PIN         0
7
#define   PWM_DDR        DDRB
8
#define   PWM_PORT      PORTB
9
10
volatile   uint8_t temp=0;
11
12
uint8_t adc_8bit(uint8_t channel)
13
{
14
  ADMUX = channel;                                    // Analog Kanal einstellen
15
  ADMUX |= (1<<ADLAR);        // 
16
  ADCSRA|=(1<<ADEN)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);   // ADC Register einstellen und Messung starten
17
  loop_until_bit_is_clear (ADCSRA,ADSC);             // Warten bis die Messung abgeschlossen ist
18
  return ADCH;                                      // Mittelwert zurück in Ergebnis schreiben
19
}
20
21
void timer0_init(uint16_t prescaler)
22
{
23
  //Prescaler Timer 0 Attiny85
24
  TCCR0A|= (1<<WGM01)|(1<<COM0A0);          //WGM für CTC Modus    COM0A0 für Toggle on Compare Match
25
  TCCR0B=0;
26
  if(prescaler==1){    TCCR0B |= (1<<CS00);}                  // Prescaler=1
27
  if(prescaler==8){    TCCR0B |= (1<<CS01);}                  // Prescaler=8
28
  if(prescaler==64){    TCCR0B |= (1<<CS01)|(1<<CS00);}              // Prescaler=64
29
  if(prescaler==256){    TCCR0B |= (1<<CS02);}                  // Prescaler=256
30
  if(prescaler==1024){  TCCR0B |= (1<<CS02)|(1<<CS00);}              // Prescaler=1024
31
}
32
33
int main(void)
34
{
35
  uint8_t temp_alt=0;
36
  timer0_init(1024);
37
  PWM_DDR|=(1<<PWM_PIN);
38
  
39
  while(1)
40
  {
41
    temp=255-adc_8bit(3);
42
    if(temp!=temp_alt)
43
    {
44
      OCR0A=temp;
45
      temp_alt=temp;
46
    }
47
  }
48
}

von Peter (Gast)


Angehängte Dateien:

Lesenswert?

Bzw. So...

Beitrag #6605886 wurde vom Autor gelöscht.
von Lukas W. (lukas_we)


Lesenswert?

Hallo Peter,

Bevor du dich mit dem Thema Programm auseinandersetzt solltest du dich 
doch erst einmal mit dem Thema PWM auseinandersetzen und dir ansehen wie 
man die Hardware überhaupt benutzt bzw. ansteuert.

Hier ein link zum Thema PWM:
www.elektronik-kompendium.de/sites/kom/0401111.htm

Zur Ansteuerung benötigst du kein PWM sondern nur einen kurzen Puls am 
am Pulse-eingang des Treibers. In deinem Programm veränderst du aber 
nicht die Frequenz sondern den Tastgrad (Verhältnis der Impulsdauer zur 
Pausendauer).
Den Impuls kannst du auch mit Timer0 erzeugen jedoch würde ich einfach 
einen Timer Overflow Interrupt nehmen und in der Funktion eine Variable 
hochzählen. mit der Variable und if-Abfragen kannst du dann einen 
Ausgang schalten und somit deinen Puls generieren, sowie auch die 
Frequenz deines Pulses verändern.

von Peter (Gast)


Lesenswert?

Hallo Lukas,
Ich weiß worauf du hinaus willst. Das habe ich übrigens auch schon 
probiert.
Aber deine Aussage ist so nicht korrekt.
Ich bin mit dem Timer durch setzen des WGM01 Bit im CTC Modus. Clear on 
Compare Match. Heißt mein Tcnt0 zählt nur bis der Wert den im Ocr0a 
eingestellten Wert erreicht hat.
Mit dem Com0A Bit wird der Ausgang beim erreichen des Compare Match 
getoggelt.
Dadurch ist mein Tastgrad wenn sich mein OCr0a nicht ändert immer 50%.
Das funktioniert auch Einwand frei wenn ich den Ocr0a einmalig beim 
initialisieren Setzte. Dann kann ich meine Frequenz von etwa 15 Hz 
(ocr0a=255) bis ca 3900 Hz (Ocr0a =1) verändern.
Die Pwm läuft dann auch sauber durch wenn der Ocr0a nur beim 
initialisieren gesetzt wird und nicht in der While Schleife.
Das es mit dem Tastgrad von 50% klappt weiß ich daher, das es hiermit 
funktioniert(Natürlich mit einem Arduino Nano nicht dem attiny85).
https://github.com/hausen8/cold-end

Der ADC gibt auch die richtigen Werte raus. Die habe ich mir mit nem 
S-Uart an den PC gesendet und überprüft.
Nur die Kombi sobald ich mit dem ADC den OCR0A setze funktioniert nicht.

von MWS (Gast)


Lesenswert?

Dürfte daran hängen, dabla:
> must be done with care since the CTC mode does not have the double buffering 
feature.

von Thorsten O. (Firma: mechapro GmbH) (ostermann) Benutzerseite


Lesenswert?

Laufen noch weitere Interrupts, oder ist das oben das vollständige 
Programm? Wenn mehrere IR-Quellen aktiv sind, ist es essentiell, die 
nicht-unterbrechbaren Routinen so kurz wie möglich zu machen. Den Rest 
kann man durch Setzen von Flags und bedingtes Abarbeiten in der Mainloop 
erledigen.

Mit freundlichen Grüßen
Thorsten Ostermann

von (Gast)


Lesenswert?

Das Setzen vom OCR0A setzt offenbar den Timer zurück, d.h. der beginnt 
neu zu zählen. Das muss man in der while-Schleife mit dem Timer 
synchronisieren. Datenblatt lesen.

von MWS (Gast)


Lesenswert?

rµ schrieb:
> Das Setzen vom OCR0A setzt offenbar den Timer zurück, d.h. der beginnt
> neu zu zählen.

Da zählt nichts neu, aber der Timer verpasst sein durch OCR0A 
vorgegebenes TOP und läuft bis 255 durch.

Der ADC-Wert wackelt sicher ein wenig und wenn OCR0A dann in der 
Hauptschleife massiv zugehauen wird, dann ergeben sich in Kombination 
mit der fehlenden Doppelpufferung interessante Schwebungseffekte.

> Das muss man in der while-Schleife mit dem Timer
> synchronisieren.

Einsynchronisieren ist richtig, in der Hauptschleife könnte man es 
blockierend machen, ist aber selbst dort bei Prescaler 1 und TOP von 1 
aufgrund der Latenz der Sync-Warteschleife schwierig bis unmöglich ohne 
Glitch.

Wenn der neue Wert kleiner als der alte Wert ist, würde OCR0A im 
Interrupt setzen, indem man versucht den Doppelpuffer nachzubilden. Aber 
selbst im Interrupt gilt bei kleinem Prescaler und kleinem TOP obiges 
aufgrund der Interrupt-Latenz.

Bei Werten von Prescaler 1 TOP 1 -> TOP 0 wird's so und so schwierig :D
Da hilft dann nur Timer anhalten, auf 0 setzen, neuen OCR0A-Wert setzen, 
Timer wieder starten.

von MWS (Gast)


Lesenswert?

Probier's so, bei Schrittmotor-üblichen Frequenzen dürfte das reichen.
1
    if(temp > temp_alt)
2
    {
3
      OCR0A=temp;
4
      temp_alt=temp;
5
  }
6
    if(temp < temp_alt)
7
    {
8
      TIFR |= (1<<OCF0A);
9
      while (!(TIFR & 1<<OCF0A));
10
        OCR0A=temp; 
11
        temp_alt=temp;
12
  }
Die Blockierung der Hauptschleife bei niedrigen Frequenzen dürfte für 
manuelle Bedienung nicht störend sein.

von m.n. (Gast)


Lesenswert?

Es ist zwar der "Holzhammmer" aber als funktionierendes Beispiel für den 
Anfang auch nützlich: 
http://www.mino-elektronik.de/Generator/takte_impulse.htm#bsp5

von Peter (Gast)


Lesenswert?

Thorsten O. schrieb:
> Laufen noch weitere Interrupts, oder ist das oben das vollständige
> Programm?

Nein laufen keine, und ja, war das ganze Programm.

MWS schrieb:
> Wenn der neue Wert kleiner als der alte Wert ist, würde OCR0A im
> Interrupt setzen, indem man versucht den Doppelpuffer nachzubilden.

Doppelpuffer nachbilden?

Würde das nicht reichen?
1
ISR(TIM0_COMPA_ISR)
2
{
3
    OCR0A=temp;
4
}

m.n. schrieb:
> Es ist zwar der "Holzhammmer" aber als funktionierendes Beispiel für den
> Anfang auch nützlich:
> http://www.mino-elektronik.de/Generator/takte_impulse.htm#bsp5

So ähnlich wollte ich es basteln.
Ich will es nicht vorgekaut bekommen. Schonmal danke für die vielen 
Hilfreichen Tips. Bin meiner Lösung schon um einiges näher

MWS schrieb:
> Probier's so, bei Schrittmotor-üblichen Frequenzen dürfte das
> reichen.

Super vielen lieben dank. Das funktioniert tip top. Wie oben schon 
gefragt, ob man es nicht einfach direkt im Interrupt so übertragen kann 
oder ob da was schiefgehen kann?

von MWS (Gast)


Lesenswert?

Peter schrieb:
> Wie oben schon
> gefragt, ob man es nicht einfach direkt im Interrupt so übertragen kann
> oder ob da was schiefgehen kann?

Nö, was soll groß schiefgehen? Schlimmer als zu Beginn kann's nicht 
werden.

Die ISR vom Aufruf über den Interruptvektor bis zum Übertragen von temp 
nach OCR0A wird 20-30 Takte dauern, die ISR insgesamt geschätzte 40-50.

Die Aufrufrate der ISR mal benötigte Takte darf (@8MHz) 8.000.000 
Takte/s nicht überschreiten, geteilt durch 50 für die ISR wären 160.000 
Aufrufe/s. Da der Portpin toggelt, wird's die Hälfte, also OC0A 80kHz 
max. Der Tiny85 ist dann bei 100% Auslastung, die Hauptschleife wird 
nicht mehr abgearbeitet, weil nach dem vorigen Interrupt gleich der 
nächste kommt.

Wenn die angenommenen 50 Takte unterschritten würden, also Prescaler 1 
und OCR0A 30, dann kommt Verhau raus, weil die ISR stets "nachhinkt" und 
einen neuen OCR0A-Wert nur mehr zufällig zum richtigen Zeitpunkt setzt.

Daraus ergibt sich:
1) Die ISR-Methode belastet mehr als die Hauptschleifen-Methode

2) Die ISR-Methode sollte bei einem Prescaler >= 64 mit Werten >= 0 für 
TOP arbeiten. Bei TOP = 0 müsste man u.U. aufpassen, ob laufendes 
Schreiben von 0 auf OCR0A in der ISR zu einem Problem führt.

3) Bei der Hauptschleifen-Methode kommt's nur auf die Latenz der paar 
Zeilen Code an, optimal übersetzt würde das zur Opcode-Sequenz: SBI, IN, 
ANDI, BRNE & OUT. Ergibt unter Annahme des ungünstigsten Zeitpunktes in 
dem Timer0 das OCF0A setzt, ein TOP von 9 bei Prescaler 1 = 400kHz.

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.