Forum: Mikrocontroller und Digitale Elektronik Servosignal mit ATMega328P ausgeben


von peter (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Forum,

ich möchte mit einem ATMega328P die Pulslänge eines Servosignals messen, 
dann passend berechnen und wieder ausgegeben. Das Einlesen der Pulslänge 
des Servosignals klappt schon mal einigermaßen. Was allerdings nicht 
klappt ist die Ausgabe des Servosignals. Testweise habe ich die 
Berechnung noch weggelassen und nur mal Probeweise einen festen Wert für 
OCR0B und OCR1B definiert. Den 16-bit Timer bzw. PWM Modus habe ich als 
Mode 15 (siehe Bild) festgelegt (oder?). Mein Gedanke war, dass man mit 
0CR1A die Frequenz einstellen kann (50Hz) und am Ausgang OCR1B dann die 
Dutycycle des Servosignals festlegt. Allerdings funktioniert das nicht. 
Da ich noch blutiger Anfänger in Sachen Mikrocontrollerprogrammierung 
bin, würde ich mich freuen, wenn mir jemand weiterhelfen könnte.

Vielen Dank schon mal.
1
/*
2
 * 20201217_servo_V1.0.c
3
 *
4
 * Created: 17.12.2020 08:01:03
5
 * Author : pete
6
 */ 
7
8
#define F_CPU 8000000UL
9
#include <avr/io.h>
10
#include <avr/interrupt.h>
11
#include <util/delay.h>
12
#include <stdio.h>
13
14
volatile uint16_t T1Ovs1, T1Ovs2; //Counts overflovs
15
volatile uint16_t Capt1, Capt2;
16
volatile uint16_t Capt3; //Variables holding three timestamps
17
volatile uint16_t Flag; //capture Flag
18
uint16_t high_zeit = 0;
19
20
void InitTimer1(void) //Timer initialisieren
21
{
22
  TCCR1B|=(1<<ICES1); //steigende Flanke erfassen
23
  TCNT1=0; //Timer mit 0 initialisieren
24
  TIMSK1|=(1<<ICIE1)|(1<<TOIE1); //Input Capture und Überlauf Interrupts erlauben
25
}
26
void StartTimer1(void)
27
{
28
  TCCR1B|=(1<<CS10); //Timer starten ohne Prescaler
29
  sei(); //globale Interrupts erlauben
30
}
31
32
ISR(TIMER1_CAPT_vect) //capture ISR
33
{
34
  volatile uint16_t a=Capt1;
35
  volatile uint16_t b=Capt2;
36
  //volatile uint16_t c=Capt3;
37
  if (Flag==0)
38
  {
39
    TCCR1B&=~(1<<ICES1); //erfassen der fallenden Flanke
40
    Capt1=ICR1; //Zeitstempel speichern
41
    T1Ovs2=0; //Überlauf auf 0 setzen
42
  }
43
  
44
  if (Flag==1)
45
  {
46
    TCCR1B|=(1<<ICES1); //erfassen der steigenden Flanke
47
    Capt2=ICR1; //Zeitstempel speichern
48
    T1Ovs1=T1Ovs2; //erster Überlauf Zähler
49
  }
50
  
51
  if (Flag==2)
52
  {
53
    Capt3=ICR1; //Zeitstempel speichern
54
    TIMSK1&=~((1<<ICIE1)|(1<<TOIE1)); //Input Capture und Überlauf Interrupts stoppen
55
    high_zeit = (b + T1Ovs1*0x400L) - a;   //0x400 entspricht 10-Bit
56
  }
57
  
58
  
59
  Flag++; //Flag Zähler
60
}
61
62
ISR(TIMER1_OVF_vect) //Überlauf Service Routine
63
{
64
  T1Ovs2++; //Überlauf Zähler
65
}
66
67
68
69
int main(void)
70
{
71
  InitTimer1();
72
  StartTimer1();
73
        
74
    DDRD |= (1<<DDD5);
75
    TCCR0A = (1<<WGM00) | (1<<WGM01) | (1<<COM0B1); 
76
    TCCR0B = (1<<CS00) | (1<<WGM02);
77
    OCR0A = 79999;
78
    OCR0B = 6000;
79
    
80
    
81
    DDRB |= (1<<DDB2);
82
    TCCR1A |= (1<<WGM10) | (1<<WGM11) | (1<<COM1B1); //gesetzt sind WGM 10, WGM11 und COM1B1
83
    TCCR1B |= (1<<CS10) | (1<<WGM12) | (1<<WGM13); //gesetzt sind CS10, WGM12 und WGM13
84
    OCR1A = 79999;
85
    OCR1B = 6000; //Mittelstellung?
86
    
87
    DDRD |= (1<<PD5);
88
    DDRB |= (1<<PB2);
89
    
90
    
91
  
92
  while(1)
93
  {
94
    if (Flag==3) //alle Timestamps erfasst?
95
    {      
96
      
97
      Flag=0; //Flag löschen bzw. 0 setzen
98
      T1Ovs1=0; //Überlaufzähler löschen bzw. 0 setzen;
99
      T1Ovs2=0; //Überlaufzähler löschen bzw. 0 setzen
100
      TIFR1=(1<<ICF1)|(1<<TOV1); //clear interrupt flags to avoid any pending interrupts
101
      TIMSK1|=(1<<ICIE1)|(1<<TOIE1); //enable input capture and overflow interrupts      
102
103
      Flag=0; //Flag löschen bzw. 0 setzen
104
105
      T1Ovs1=0; //Überlaufzähler löschen bzw. 0 setzen;
106
      T1Ovs2=0; //Überlaufzähler löschen bzw. 0 setzen
107
      
108
      TIFR1=(1<<ICF1)|(1<<TOV1); //clear interrupt flags to avoid any pending interrupts
109
      
110
      TIMSK1|=(1<<ICIE1)|(1<<TOIE1); //enable input capture and overflow interrupts
111
    } 
112
  }
113
}

von re (Gast)


Lesenswert?

peter schrieb:
> Mein Gedanke war, dass man mit 0CR1A die Frequenz einstellen kann (50Hz)
> und am Ausgang OCR1B dann die Dutycycle des Servosignals festlegt

Nicht am Ausgang, sondern mit dem Register, aber der Ansatz ist OK.


peter schrieb:
> OCR0A = 79999;
> OCR1A = 79999;

Ohne das Programm näher angesehen zu haben: DAS ist zuviel.
OCR0A ist 8 Bit breit imd OCR1A ist 16 Bit breit, da sind 255 bzw. 65535 
das Maximum des zulässigen Bereichs.

Halbieren und den Vorteiler erhöhen?

peter schrieb:
> OCR1B = 6000; //Mittelstellung?

Das Verhältnis OCR1A / OCR1B passt, aber der Absolutwert 79999 eben 
nicht.

HTH
(re)

von peter (Gast)


Lesenswert?

Ah stimmt. Vielen Dank für die schnelle Hilfe, jetzt funktionierts!!!

von re (Gast)


Lesenswert?

peter schrieb:
> Ah stimmt. Vielen Dank für die schnelle Hilfe, jetzt funktionierts!!!

Glückwunsch! Und Danke für die Rückmeldung.

(re)

von peter (Gast)


Lesenswert?

Jetzt hab ich noch ne Frage. Die PWM, die mit dem 8 Bit Timer erzeugt 
wird, wird sehr ungenau bzw die Auflösung ist dann zu klein für das 
Servosignal. Gibts da eine Möglichkeit die "Auflösung" etwas zu erhöhen? 
oder muss ich mit einen µC mit zwei 16-Bit Timern besorgen?

von Veit D. (devil-elec)


Lesenswert?

Hallo,

da gibt es leider keinen Trick um die Auflösung zu erhöhen. Aber wenn du 
nur ein Signal messen und nur eins ausgeben möchtest, könntest du 
dennoch für beide Aufgaben Timer 1 verwenden und in deinem Programm 
dessen Aufgabe jeweils umschalten. Das geht allerdings nicht, wenn der 
anzusteuernde Servo ununterbrochen sein Signal bekommen muss, weil er 
zum Bsp. unbedingt seine Position halten muss wofür seine Kraft benötigt 
wird. Ansonsten kommst du um einen 2. Timer nicht herum. Wenn der µC 
sehr ähnlich sein soll, dann könntest du den ATmega328PB verwenden.

von re (Gast)


Lesenswert?

peter schrieb:
> oder muss ich mit einen µC mit zwei 16-Bit Timern besorgen?

Vorschlag:

Speise den Timer-1 mit ca. 3.276800 MHz - kommt nicht sehr auf den 
genauen Wert an.

Dann brauchst Du OCR1A nicht mehr, weil der dann sowieso alle 20ms 
überläuft.
Dann kannst Du den für eine zusätzliche PWM auf OC1A benutzen.

Diese Frequenz kannst Du mit dem nun freigewordenen Timer-0 erzeugen und 
per T1 in Timer-12 einspeisen.

HTH
(re)

von peter (Gast)


Lesenswert?

Ich werde es mit ATMega328PB mal ausprobieren, da hab ich sowieso einen 
rumliegen. Der andere Vorschlag gefällt mir auch sehr gut, vielleicht 
probiere ich das auch mal aus. Danke für die Antworten.

von Spess53 (Gast)


Lesenswert?

Hi

> oder muss ich mit einen µC mit zwei 16-Bit Timern besorgen?

Ein 16-Bit-Timer reicht.

Prescaler auf 8 und PWM auf Mode 14 oder 15 setzen und mit ICR1 bzw 
OCR1A Top auf 20ms (ca.0x4E20) einstellen. Mit OCR1A/B (Mode14) oder 
OCR1B (Mode15) im Bereich 0x400 .. 0x800 lassen sich dann die 1..2ms 
einstellen.

MfG Spess

von Stefan F. (Gast)


Lesenswert?

Für einen Servo musst du die 20 ms (50 Hz) nicht genau einhalten. Es 
darf auch gerne deutlich weniger sein.

Deswegen genügt es, den Prescaler des Timers zur Einstellung der 
Frequenz zu verwenden. Du hast dann eine höhere Auflösung weil du den 
Zähler komplett ausnutzt (0-255).

Noch besser ist es, den Timer auf 4ms (250 Hz) pro Durchlauf zu stellen 
und dann nur bei jedem 5. Durchlauf einen Ausgangsimpuls zu erzeugen. 
Für die anderen 4 Durchläufe kannst du den Ausgang abschalten (oder auf 
LOW). Dann hast du nochmal die 5-Fache Auflösung, so kannst du den Sero 
feiner ansteuern.

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.