Sygnał PPM
Sygnał PPM
Czy za pomocą jakiegokolwiek trybu timera jest mozliwe zrealizowanie takiego sygnału jak na rysunku? (wiadomo że to koder dla serw)) z tym że jak widać cały cykl ma mieć zawsze 20 ms, "szpilki" zawsze 0,1ms i wartości 1ms oraz 2ms mozna zmieniać w zakresie od 0,5 do 2,5 ms. Czy jest to w ogóle mozliwe na jednym kanale i jakimś trybie, nigdzie nie znalazłem przykladu realizacji syg. PPM w C. JAK TO ROBIĄ PROGRAMIŚCI??
??
- Załączniki
-
- Bez tytułu.png (3.04 KiB) Przejrzano 14837 razy
Sygnał PPM można wygenerować za pomocą przerwania od porównania timera.
Zamieszczony przykład nie jest w C ale BascomAVR, Jest to program testowy, nieopracowany pod konkretne zastosowanie, był potrzebny do testowania wirtualnego kontrolera gier będącego interfejsem programowym między wejściem liniowym karty muzycznej komputera a symulatorem modelu śmigłowca, ale powinien nieco "rozjaśnić" zagadnienie. Co do sygnałów ppm używanych w modelarstwie to zależności czasowe bywają różne zależnie od aparatury (szczególnie dotyczy to impulsu synchronizacji oraz zakresów trymowania).
Zamieszczony przykład nie jest w C ale BascomAVR, Jest to program testowy, nieopracowany pod konkretne zastosowanie, był potrzebny do testowania wirtualnego kontrolera gier będącego interfejsem programowym między wejściem liniowym karty muzycznej komputera a symulatorem modelu śmigłowca, ale powinien nieco "rozjaśnić" zagadnienie. Co do sygnałów ppm używanych w modelarstwie to zależności czasowe bywają różne zależnie od aparatury (szczególnie dotyczy to impulsu synchronizacji oraz zakresów trymowania).
Kod: Zaznacz cały
'----------------------------------------------------------------------------------------
' Program Ppm
' Autor: c4v2
'----------------------------------------------------------------------------------------
'dyrektywy kompilatora
'----------------------------------------------------------------------------------------
'$sim 'Symulacja programowa
$regfile = "m168def.dat" 'Dyrektywa pliku z definicjami rejestrów mikroprocesora.
$crystal = 1000000 'Częstotliwość zegara mikroprocesora.
$baud = 4800 'Prędkość transmisji szeregowej
'----------------------------------------------------------------------------------------
'konfiguracja portów, licznika 16-bitowego
'----------------------------------------------------------------------------------------
Config Timer1 = Timer , Prescale = 1 , Compare A = Toggle , Clear Timer = 1 'Impuls co 1/8000000*8=1us
Config Adc = Single , Prescaler = Auto , Reference = Avcc
Config Portb.0 = Output 'Wyjście impulsów PPM
Config Portb.1 = Output
'----------------------------------------------------------------------------------------
'deklaracje procedur
'----------------------------------------------------------------------------------------
On Compare1a Set_ppm 'Deklaracja obsługi przerwania od porównania zawartości licznika z rejestrem.
'----------------------------------------------------------------------------------------
'deklaracje stałychych
'----------------------------------------------------------------------------------------
Const V_prog = "PPM2LineIn_0v1"
Const Hpt = 400
Const Lptmin = 600
Const Lptmid = 1100
Const Lptmax = 1740
'640 <--> 640
'460 <- 1100 -> 1740
'----------------------------------------------------------------------------------------
'deklaracje zmiennych
'----------------------------------------------------------------------------------------
Dim Clk As Byte
Dim Tmp0 As Byte
Dim Tmp1 As Byte
Dim Channel(8)as Word
Dim Lpt As Word
Dim Lpft As Word
Dim Adctmpw As Word
Dim Adctmps As Single
'----------------------------------------------------------------------------------------
'program główny
'----------------------------------------------------------------------------------------
'-[For test]-------------------
Do
Incr Tmp0
Channel(tmp0) = Lptmid
Loop Until Tmp0 > 8
'-------------------------
Start Adc
Enable Interrupts
Timer1 = 0
Enable Compare1a
Do
For Tmp0 = 0 To 5
Tmp1 = Tmp0 + 1
Adctmpw = Getadc(tmp0)
Shift Adctmpw , Right , 1
Adctmps = Adctmpw * 1.957
Channel(tmp1) = Adctmps + Lptmin
Next Tmp0
'-[For test]-----------------------
For Tmp0 = 1 To 8
Adctmpw = Channel(tmp0) + Hpt
Print "Channel[" ; Tmp0 ; "]=" ; Adctmpw ; "us"
Next Tmp0
Print "//----------"
Waitms 1000
'-------------------------
Loop
End
'----------------------------------------------------------------------------------------
'definicje procedur
'----------------------------------------------------------------------------------------
Set_ppm:
If Pinb.1 = 0 Then
Compare1a = Lpt
Set Portb.0 '???
Else
Compare1a = Hpt
Reset Portb.0 '???
Incr Clk
If Clk = 9 Then
Lpt = 19700 - Lpft '20000
Lpft = 0
Clk = 0
Else
Lpt = Channel(clk)
Lpft = Lpft + Hpt
Lpft = Lpft + Lpt
End If
End If
Return
'ENDE----------------------------------------------------------------------------------------Generacja sygnału PPM sprowadza się do konfiguracji rejestrów sterujących trybem pracy timera i obsługi przerwania. W BaskomieAVR instrukcja Config Timer, a w C można bezpośrednio lub korzystając z nagłówka ustawić bity w rejestrach (preskaler, tryb pracy, włączenie timera). Następnie obsługa przerwania (interrupts.h) od oc1a z instrukcją sprawdzającą wejście OC1(w przypadku MEGA168 PB1) (po to by wiadomo było czy podstawiać wartość dla impulsu wysokiego czy niskiego) i zależnie od stanu OC1 podstawianie pod rejestr OC1A wartości do której licznik ma zliczać. Inaczej: licznik zlicza do wartości wpisanej do rejestru OC1A dla czasu trwania impulsu wysokiego (umownie) po osiągnięciu zadanej wartości generuje przerwanie oraz zmienia sprzętowo OC1 i można w obsłudze przerwania podstawić do OC1A wartość wyznaczającą czas trwania impulsu niskiego. Dla kilku kanałów PPM należy zadbać o to aby wartości odpowiedzialne za długości trwania impulsów dla poszczególnych kanałów były interpretowane w obsłudze przerwania w odpowiedniej kolejności. W przykładowym programie realizuje to zmienna CLK, odwołując się przez indeks do tablicy z wartościami opóźnień będących przewożonymi wartościami konwersji ADC z kolejnych kanałów przetwornika AD (w przykładzie z sześciu kanałów).
Ok, dziękuję za podpowiedź, już mniej więcej wiem jak się za to zabrać, zacznę od przygotowania programu i analizowania przebiegów analizatorem stanów logicznych. Na początek włączę timer, podstawie jakieś wymyślone dane w obsłudze przerwania i zobaczymy co się będzie działo. Pewnie jeszcze będę miał pytania, "kolejny, pierwszy taki" program do napisania:) Oby się udało.
Udało mi się uzyskać odwrócony syg PPM, wiem że taki się stosuje również. Czy są jakieś róznice między zazwyczaj stosowanym syg. PPM a odwróconym, któryś jest lepszy? Zostało mi napisac kod do odbioru tego syg. i podzieleniu na osobne kanały. Sygnał jest podawany na PD7. Timer w trybie CTC. Mega16, zegar 8MHz. Jak ktos ma jakieś 'ale' do kodu proszę pisać.
Kod: Zaznacz cały
#include <avr/interrupt.h>
#include <avr/io.h>
#include <util/delay.h>
#define LED1 PD7
volatile uint16_t a; //zmienne globalne na potrzeby wpisania do nich wartosci
volatile uint16_t b; //dla szerokosci impulsu, OCR1A
int main(void)
{
DDRD |= (1<<LED1); //wyjście na LED oraz na sygnal PPM
ADCSRA |= (1<<ADEN)|(1<<ADPS2)|(1<<ADPS0); //uruchominie przetwornika, preskaler 32
ADMUX |= (1<<REFS1)|(1<<REFS0); //wewnętrzne źródło 2,56V
DDRB = 0xFF;
PORTB = 0xFF;
DDRC = 0xFF;
PORTC = 0xFF; // nieuzywane porty jako wyjscia
DDRD = 0xFF;
PORTD = 0xFF;
TCCR1B|=(1<<WGM12)|(1<<CS11); // licznik 16bit tryb CTC
TIMSK |= (1<<OCIE1A); // zezwolenie na przerwania od OCR1A
sei(); // wl.przerwan
while(1)
{
uint16_t x; //zienne lokalne
uint16_t y;
{
ADMUX &=~ (1<<MUX0); // skasuj
ADMUX |= (1<<MUX1); // ustaw kanal ADC2 PA2
ADCSRA |= (1<<ADSC); //start pomiaru
while(ADCSRA & (1<<ADSC));
x = ADC*3;
if (ADC<200)
{ a = 600;} //minimalne wychylenie orczyka serwa,
else if (ADC>925)
{ a = 2775;} //maksymalne wychylenie orczyka serwa,
else
{ a = x;} // jesli nie spełnia warunków if; a = x (ADC*3)
}
{ ;
ADMUX |= (1<< MUX1)|(1<< MUX0);
ADCSRA |= (1<<ADSC);
while(ADCSRA & (1<<ADSC));
y = ADC*3;
if (ADC<200)
{ b = 600;}
else if (ADC>925)
{ b = 2775;}
else
{ b = y;}
}
}
}
ISR (TIMER1_COMPA_vect) // przerwania
{ static uint8_t cnt; // zmienna na potrzeby instrukcji switch
cnt++;
switch(cnt)
{
case(1):
OCR1A=100; // case 1,3 i 5 to krótnie szpilki miedzy szerokosciami ipulsow dla serw
PORTD ^= (1<<PD7); // każde 'case' zmiana stanu pinu
break;
case(2):
OCR1A=a; // zmienna 'a' to wartość z ADC (ADC * 3)
PORTD ^= (1<<PD7);
break;
case(3):
OCR1A=100;
PORTD ^= (1<<PD7);
break;
case(4):
OCR1A=b; // zmienna 'b' to wartość z ADC (ADC * 3)
PORTD ^= (1<<PD7);
break;
case(5):
OCR1A=100;
PORTD ^= (1<<PD7);
break;
case(6):
OCR1A=20000-(a+b); // aby czas impulsów zawse wynosil 20ms 50Hz
PORTD ^= (1<<PD7);
break;
}
if(cnt>5)
cnt=0;
}Kawałek kodu to już coś, Brawo. Jednak wrodzony malkontent podpowiada że obsługa przerwania nie w pełni wykorzystuje dobrodziejstwa sprzętowe (zmianę na PIN.OC1A) pracy w trybie Compare i na dobrą sprawę działa jak przerwanie od przepełnienia licznika która sama modyfikuje wartość do odliczania w następnym cyklu.
Teraz kod dekodujący i są problemy, co widać na zdjęciu, wychodzi na to że Attiny2313 nie jest w stanie odczytać zbocza opadającego(INT1) wystarczająco szybko i wywołać przerwania, każde zbocze opadające mają określać czas impulsu. W datasheet jest napisane że zbocze nie może być za krótkie, ale nie napisano ile ten czas ma trwać? Attiny taktowane zegarem wew. 9,6 MHz, jak przestawiłem na 4,8MHz to Tiny w ogóle nie odczytywał stanu niskiego podawanego na PIN.. nie wiem co robić
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
Kod: Zaznacz cały
#define SER1 PD0
#define SER2 PD1
#define PPM PD3
int main(void)
{
DDRD |= (1<<SER1); //wyjście
DDRD |= (1<<SER2); //wyjście
DDRB = 0xFF;
PORTB = 0xFF;
DDRA = 0xFF;
PORTA = 0xFF; // nieuzywane porty jako wyjscia
GIMSK |= (1<<INT1);
MCUCR |= (1<<ISC11);
sei(); // wl.przerwan
while(1)
{
}
}
ISR (INT1_vect)
{ static uint8_t cnt; // zmienna na potrzeby instrukcji switch
cnt++;
switch(cnt)
{
case(1):
PORTD |= (1<<PD0);
break;
case(2):
PORTD &=~ (1<<PD0);
PORTD |= (1<<PD1);
break;
case(3):
PORTD &=~ (1<<PD1);
break;
}
if(cnt>2)
cnt=0;
}
Ostatnio zmieniony 12 cze 2013, 20:04 przez Dawid2379, łącznie zmieniany 1 raz.
Jak zwykle drobny problem:) Na płytce stykowej. Teraz prawie wszystko jest ok, zostanie poprawić i dodać kilka linijek kody aby wszystko było dopracowane, sygnały nie są do końca stabilne ponieważ serwo momentami delikatnie drga. Jutro podam ten sygnał przez moduły radiowe i zobaczę jak to będzie działało.
Tu nie chodzi o czas trwania zbocza (czas opadania lub narastania jeżeli chodzi o zbocza) a o czas między zboczem opadającym a narastającym który jest zależny od częstotliwości taktowania procesora. O ile dobrze pamiętam to procesor próbkuje wejście INT1 co takt zegara systemowego więc żeby procesor mógł zauważyć którekolwiek zbocze (zależnie od ustawień reakcji: opadające, narastające) to czas impulsu musi być co najmniej dwukrotnie dłuższy od okresu zegara systemowego.Dawid2379 pisze:...W datasheet jest napisane że zbocze nie może być za krótkie, ale nie napisano ile ten czas ma trwać?
Pojawił się spory problem, jeżeli chodzi o kod dekodujący. Kod jest prosty, ale biorąc pod uwagę tylko tę część:
Wiec tak, jeśli włączę zasilanie płytki na której są dwa procki wiadomo że startują razem.
Co oznacza że w Attiny2313 case(1) czyli pierwsze zbocze opadające (nr 1 na zdjęciu) dyktuje nam szerokość impulsu pierwszego podanego na PD0 bo w tym samym czasie też jest podawany sygnał z Atmegi16 własnie dla pierwszego serwa, ale jeśli włączę Attiny2313 w innym czasie np. w momencie kiedy drugie zbocze opadające jest podawane (nr2) z Atmegi16 to Tiny zawsze będzie brał pod uwagę pierwsze zbocze opadające jakie napotka i zmieni mi PD0 (odpowiedzialne za szer. impulsu dla pierwszego serwa) a nie PD1 odpowiedzialnego za szer. impulsu dla drugiego serwa. Generalnie aby to działało musiałbym włączać dwa procki w tym samym czasie, dekoderowi zależnie od czasu włączeni mogą i mylą się kolejności odczytu zbocz) myślę jak to rozwiązać, ma ktoś może jakiś pomysł?
Kod: Zaznacz cały
case(1):
PORTD |= (1<<PD0);
break;
case(2):
PORTD &=~ (1<<PD0);
PORTD |= (1<<PD1);
break;
case(3):
PORTD &=~ (1<<PD1);
break; Co oznacza że w Attiny2313 case(1) czyli pierwsze zbocze opadające (nr 1 na zdjęciu) dyktuje nam szerokość impulsu pierwszego podanego na PD0 bo w tym samym czasie też jest podawany sygnał z Atmegi16 własnie dla pierwszego serwa, ale jeśli włączę Attiny2313 w innym czasie np. w momencie kiedy drugie zbocze opadające jest podawane (nr2) z Atmegi16 to Tiny zawsze będzie brał pod uwagę pierwsze zbocze opadające jakie napotka i zmieni mi PD0 (odpowiedzialne za szer. impulsu dla pierwszego serwa) a nie PD1 odpowiedzialnego za szer. impulsu dla drugiego serwa. Generalnie aby to działało musiałbym włączać dwa procki w tym samym czasie, dekoderowi zależnie od czasu włączeni mogą i mylą się kolejności odczytu zbocz) myślę jak to rozwiązać, ma ktoś może jakiś pomysł?
Aby taki problem nie występował w PPM do zastosowań modelarskich stosowany jest impuls synchronizacji między paczkami impulsów przerwa równa lub trwająca dłużej niż okres przeznaczony dla dla jednego kanału (polaryzacja oczywiście umowna) np. jak na załączonym rysunku:
- Załączniki
-
- PPM.png (12.35 KiB) Przejrzano 14448 razy
Można ustawiać flagę w przerwaniu INT0 że nadszedł impuls opadający następnie odmierzać czas timerem (inkrementacja jakiejś zmiennej zegarowej) do pojawienia się zbocza narastającego i jeżeli czas jest dłuższy od 2ms ustawić flagę synchronizacji, czy zresetować licznik serw (biorąc pod uwagę poziom logiczny impulsu).
Nie miałem do czynienia z flagami. Może mi ktoś wytłumaczyć jak nimi operować. Gdzieś widziałem że flagę kasuje się tak |=, kiedy to, ta operacja zawsze ustawia nam różne rejestry w programach.
Może mi ktoś objaśnić łopatologicznie co i jak?
I jak operować tymi flagami
No i na razie wiem że ma być tak:
Nawet po Polsku nie łapię tego, zgłoszenie przerwanie - flaga ustawiona na 1, kasowanie w każdym momencie to wpisanie do niej 1? jest kasowana kiedy wykonywana jest obsługa przerwań? jakby kiedy jej się chce?INTF0 - Flaga zewnętrznego przerwania 0.
Kiedy zdarzenie na pinie INT0 wywoła przerwanie, flaga INTF0 jest ustawiana na 1. Jeśli ustawiony jest bit I w SREG oraz ustawiony bit INT0 w GICR, mikrokontroler skoczy do odpowiedniego wektora przerwania. Flaga INTF0 jest kasowana kiedy wykonywana jest procedura obsługi przerwania. Możliwe jest „ręczne” jej wykasowanie przez wpisanie do niej 1.
Może mi ktoś objaśnić łopatologicznie co i jak?
I jak operować tymi flagami
No i na razie wiem że ma być tak:
Kod: Zaznacz cały
ISR (INT0_vect)
1: start licznika
2: i tyle:(