Sygnał PPM

To forum jest dla wszystkich pasjonatów mikrokontrolerów AVR Atmela. Wymiana doświadczeń i pomoc dla początkujących w pisaniu programów zarówno w C, Asemblerze jak i BASCOM. Zapraszam znawców tematu, aby pomogli wszystkim początkującym!
Awatar użytkownika
Dawid2379
Użytkownik
Posty: 318
Rejestracja: 17 lis 2010, 19:42
Lokalizacja: Kluczbork
Kontakt:

Sygnał PPM

Post autor: Dawid2379 » 05 cze 2013, 17:58

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
Bez tytułu.png (3.04 KiB) Przejrzano 14851 razy

Awatar użytkownika
c4v2
Użytkownik
Posty: 426
Rejestracja: 22 lis 2005, 15:14
Lokalizacja: z przed monitora

Post autor: c4v2 » 05 cze 2013, 18:45

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).

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----------------------------------------------------------------------------------------

Awatar użytkownika
Dawid2379
Użytkownik
Posty: 318
Rejestracja: 17 lis 2010, 19:42
Lokalizacja: Kluczbork
Kontakt:

Post autor: Dawid2379 » 06 cze 2013, 16:57

Nic nie łapie z BASCOM niestety, jakiś opis skrócony jak to się robi? Może ma ktoś przykład w C?

Awatar użytkownika
c4v2
Użytkownik
Posty: 426
Rejestracja: 22 lis 2005, 15:14
Lokalizacja: z przed monitora

Post autor: c4v2 » 07 cze 2013, 7:45

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).

Awatar użytkownika
Dawid2379
Użytkownik
Posty: 318
Rejestracja: 17 lis 2010, 19:42
Lokalizacja: Kluczbork
Kontakt:

Post autor: Dawid2379 » 07 cze 2013, 18:34

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.

Awatar użytkownika
Dawid2379
Użytkownik
Posty: 318
Rejestracja: 17 lis 2010, 19:42
Lokalizacja: Kluczbork
Kontakt:

Post autor: Dawid2379 » 11 cze 2013, 17:13

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;
}
Załączniki
Bez tytułu.png
Bez tytułu1.png
Bez tytułu3.png

Awatar użytkownika
c4v2
Użytkownik
Posty: 426
Rejestracja: 22 lis 2005, 15:14
Lokalizacja: z przed monitora

Post autor: c4v2 » 12 cze 2013, 5:07

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.

Awatar użytkownika
Dawid2379
Użytkownik
Posty: 318
Rejestracja: 17 lis 2010, 19:42
Lokalizacja: Kluczbork
Kontakt:

Post autor: Dawid2379 » 12 cze 2013, 19:36

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>

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; 
}
Załączniki
Bez tytułu.png
Ostatnio zmieniony 12 cze 2013, 20:04 przez Dawid2379, łącznie zmieniany 1 raz.

Awatar użytkownika
Dawid2379
Użytkownik
Posty: 318
Rejestracja: 17 lis 2010, 19:42
Lokalizacja: Kluczbork
Kontakt:

Post autor: Dawid2379 » 12 cze 2013, 20:02

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.
Załączniki
Bez tytułu1.png

Awatar użytkownika
c4v2
Użytkownik
Posty: 426
Rejestracja: 22 lis 2005, 15:14
Lokalizacja: z przed monitora

Post autor: c4v2 » 13 cze 2013, 9:21

Dawid2379 pisze:...W datasheet jest napisane że zbocze nie może być za krótkie, ale nie napisano ile ten czas ma trwać?
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.

Awatar użytkownika
Dawid2379
Użytkownik
Posty: 318
Rejestracja: 17 lis 2010, 19:42
Lokalizacja: Kluczbork
Kontakt:

Post autor: Dawid2379 » 13 cze 2013, 19:24

Pojawił się spory problem, jeżeli chodzi o kod dekodujący. Kod jest prosty, ale biorąc pod uwagę tylko tę część:

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; 
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ł?
Załączniki
Bez tytułu.png

Awatar użytkownika
c4v2
Użytkownik
Posty: 426
Rejestracja: 22 lis 2005, 15:14
Lokalizacja: z przed monitora

Post autor: c4v2 » 14 cze 2013, 8:24

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
PPM.png (12.35 KiB) Przejrzano 14462 razy

Awatar użytkownika
Dawid2379
Użytkownik
Posty: 318
Rejestracja: 17 lis 2010, 19:42
Lokalizacja: Kluczbork
Kontakt:

Post autor: Dawid2379 » 14 cze 2013, 16:05

Dzięki za zainteresowanie c4v2. Ale jak to zrealizować programowo? Wcześniej właśnie myślałem o tym bo zależnie od szerokości dwóch impulsów dla serw zostaje mi jeden długi impuls od ok. 18,8ms do ok 14ms tylko nie wiem jak go programowo zdekodować i ustalić resztę

Awatar użytkownika
c4v2
Użytkownik
Posty: 426
Rejestracja: 22 lis 2005, 15:14
Lokalizacja: z przed monitora

Post autor: c4v2 » 14 cze 2013, 18:57

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).

Awatar użytkownika
Dawid2379
Użytkownik
Posty: 318
Rejestracja: 17 lis 2010, 19:42
Lokalizacja: Kluczbork
Kontakt:

Post autor: Dawid2379 » 18 cze 2013, 16:37

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.
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.
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?
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:(

ODPOWIEDZ