Forum: Mikrocontroller und Digitale Elektronik PPM-Signal mit ATMega8-Timer ausgeben


von Stefan T. (tommie)


Lesenswert?

Moin moin,

ich versuche mich gerade daran, mit einem ATMega8L ein PPM-Signal 
auszugeben, um das Transmittermodul einer RC-Funke anzusprechen. Bisher 
hatte ich das ganze "blind" programmiert, gestern hatte ich Zugriff auf 
ein Oszilloskop um das ganze zu testen - und hatte natürlich keinen 
Erfolg. Ich kann mir jedoch noch ganz erklären, wo der Fehler liegt.

Ich habe den Timer nun verlangsamt, um das ganze auch mit einer LED 
anzeigen zu können, komme aber dennoch der Sache nicht auf die Schliche.

Die Grundidee ist folgende: Um einen PPM-Pulszug ("frame") zu erzeugen, 
benutze ich Timer 1, der mit einer Auflösung von 1/2µS pro Tick läuft. 
Zu Beginn wird der Ausgangspegel auf HIGH gestellt, nach 300µs erreicht 
der Timer den Wert von OCR1B und löst den Interrupt aus: hier wird der 
Pegel abgesenkt, der erste Stop-Puls ist damit gesendet, und 
nachgeschaut, wie lange der erste Puls dauern soll - dieser Wert wird 
dann nach OCR1A geschrieben (Im Debugging-Beispiel soll der gesamtpuls 
zwischen den steigenden Flanken 2500µs dauern).
Erreicht der Timer diesen Wert, setzt er sich zurück und feuert den 
OC1A-IRQ: Hier wird der PPM-Ausgang wieder hochgerissen, das Spiel 
wiederholt sich mit dem nächsten Channel.

Da ein Pulszug stets 20ms dauern soll, führt eine Variable Buch über die 
durchgeführten Pulse (frame_time_remaining) und hängt die verbleibende 
Zeit als letzten Puls an.

Hier der Quelltext, den ich verwende:
1
#define N_CHANNELS 4
2
3
#define PPM_DDR  DDRB
4
#define PPM_PORT PORTB
5
#define PPM_PIN  PINB
6
#define PPM_BIT  PB0
7
8
#define FRAME_MS 20000UL
9
#define STOP_MS  300
10
11
12
static uint8_t current_channel;
13
static uint16_t frame_time_remaining;
14
15
static void set_ppm(uint8_t h) {
16
        if (h) {
17
                PPM_PORT |= 1<<PPM_BIT;
18
        } else {
19
                PPM_PORT &= ~(1<<PPM_BIT);
20
        }
21
}
22
23
static void start_ppm_frame(void) {
24
        frame_time_remaining = FRAME_MS*2;
25
        current_channel = 0;
26
}
27
28
int main(void) {
29
        /* configure PPM output port */
30
        PPM_DDR |= (1<<PPM_BIT);
31
32
        DDRD |= 1<<PD7;
33
34
        /* configure timer */
35
36
        /* enable CTC waveform generation (TOP == OCR1A) */
37
        TCCR1A = (1<<WGM12);
38
        /* enable compare interrupts */
39
        TIMSK = (1<<OCIE1B | 1<<OCIE1A | 1<<TOIE1);
40
        /* set compare value for the stop pulse to 300µs */
41
        OCR1B = STOP_MS*2;
42
        /* set pulse width to max for now */
43
        OCR1A = 0xFFFF;
44
        /* set Timer 1 to clk/8, giving us ticks of 1/2 µs */
45
        // TCCR1B = 1<<CS11;
46
        TCCR1B |= (0<<CS11 | 1<<CS12);
47
48
        /* initialize channel data */
49
        start_ppm_frame();
50
        set_ppm(1);
51
52
        /* enable interrupts */
53
        sei();
54
55
        while (1) {}
56
        return 0;
57
}
58
59
/* the timer has reached OCR1A, so the current PPM pulse has been completed */
60
ISR(TIMER1_COMPA_vect) {
61
        set_ppm(1);
62
}
63
64
/* finished sending the stop pulse */
65
ISR(TIMER1_COMPB_vect) {
66
        set_ppm(0);
67
        if (current_channel < N_CHANNELS) {
68
                /* get the pulse width for the current channel */
69
                uint16_t timeout = (1000+1500)*2;
70
                OCR1A = timeout;
71
                frame_time_remaining -= timeout;
72
        } else {
73
                /* we already transmitted the last channel, only wait for the frame to finish */
74
                OCR1A = frame_time_remaining;
75
                start_ppm_frame();
76
        }
77
}

Dadurch, dass der Timer jetzt wesentlich langsamer läuft, sollte man das 
ja auf der LED verfolgen können; diese lecuhtet aber eigentlich 
permanent, nur kurz unterbrochen. Erwartet hätte ich eigentlich, dass 
die LED nur für einen kurzen Moment aktiv ist und ansonsten dunkel.

Sieht jemand den Fehler?

von Michael (Gast)


Lesenswert?

Stefan Tomanek schrieb:
> Da ein Pulszug stets 20ms dauern soll, führt eine Variable Buch über die
> durchgeführten Pulse (frame_time_remaining) und hängt die verbleibende
> Zeit als letzten Puls an.

Die Zeit ist absolut unkritisch. Irgendetwas im Bereich 10..100 ms 
tut's. Großartige Restzeitberechnungen sind da überflüssig.

von Stefan T. (tommie)


Lesenswert?

Michael schrieb:

> Die Zeit ist absolut unkritisch. Irgendetwas im Bereich 10..100 ms
> tut's. Großartige Restzeitberechnungen sind da überflüssig.

Ja, das habe ich schon gelesen, ich wollte aber erstmal nah am Original 
bleiben :-)

Hast du sonst eine Idee, warum das Signal nicht korrekt generiert wird?

von Falk B. (falk)


Lesenswert?

@  Stefan Tomanek (tommie)

>permanent, nur kurz unterbrochen. Erwartet hätte ich eigentlich, dass
>die LED nur für einen kurzen Moment aktiv ist und ansonsten dunkel.

Jo.

>Sieht jemand den Fehler?

Nein.

Naja, ich würde es etwas anders machen. Nämlich den Timer1 durchlaufen 
lassen und nur OCR1A jeweils erhöhen. Dann braucht man auch keine zwei 
OCR Interrupts. Prinzipiell so.

Beitrag "Re: Soft-PWM mit Atmega16"

Ausserdem kann und sollte man die Bitmuster als auch die OCR Zahlen der 
einzelnen Kanäle vorher berechnen, das spart einiges an Zeit in der ISR. 
Und möglichst keine Funktinsaufrufe in der ISR, das erzeugt unnötig 
viele push/pops und kostet Zeit. Vor allem für so eine einfache, kleine 
Funktion.

MFG
Falk

von Michael (Gast)


Lesenswert?

Stefan Tomanek schrieb:
> Dadurch, dass der Timer jetzt wesentlich langsamer läuft, sollte man das
> ja auf der LED verfolgen können; diese lecuhtet aber eigentlich
> permanent, nur kurz unterbrochen. Erwartet hätte ich eigentlich, dass
> die LED nur für einen kurzen Moment aktiv ist und ansonsten dunkel.

Du könntest dein Programm auch mit Originaltakteinstellungen im 
Simulator laufen lassen und in den Registern gucken, was anders läuft, 
als du es dir das gedacht hast. Dafür gibt es den Debugger.

von Stefan T. (tommie)


Lesenswert?

Falk Brunner schrieb:

> Und möglichst keine Funktinsaufrufe in der ISR, das erzeugt unnötig
> viele push/pops und kostet Zeit. Vor allem für so eine einfache, kleine
> Funktion.

die set_ppm-Funktion ist auch dem Debugging entsprungen, allerdings wird 
der gcc das wohl inlinen (müsste ich mal nachprüfen).

Ich habe nochmal experimentiert: Wenn ich das set_ppm(1) in der 
TIMER1_COMPA_vect auskommentiere, blitzt die LED nur kurz auf - ich kann 
mir aber gerade nicht erklären, wo der Port PB0 wieder eingeschaltet 
wird, nachdem der OCR1A-IRQ ihn deaktiviert hat; habe ich evtl. 
irgendein Bit gesetzt, das den Timer1 in Hardware an PB0 (ist ja auch 
ICR0) schalten und walten lässt?

von Stefan T. (tommie)


Lesenswert?

Michael schrieb:

> Du könntest dein Programm auch mit Originaltakteinstellungen im
> Simulator laufen lassen und in den Registern gucken, was anders läuft,
> als du es dir das gedacht hast. Dafür gibt es den Debugger.

simulavr unterstützt leider die Timer-Register (noch) nicht :-/

von MWS (Gast)


Lesenswert?

Stefan Tomanek schrieb:
> warum das Signal nicht korrekt generiert wird?

Es gibt kein WGM12 in TCCR1A.

> TCCR1A = (1<<WGM12);

Außerdem ist's schlecht Interrupts zu erlauben, für die man keine ISR 
definiert.

> TIMSK = (1<<OCIE1B | 1<<OCIE1A | 1<<TOIE1);

von Stefan T. (tommie)


Lesenswert?

MWS schrieb:
> Stefan Tomanek schrieb:
>> warum das Signal nicht korrekt generiert wird?
>
> Es gibt kein WGM12 in TCCR1A.
>
>> TCCR1A = (1<<WGM12);

Argh, Danke. Das erklärt auch obiges Verhalten, damit habe ich halt den 
Output-Compare-Modus eingeschaltet. Manchmal sieht man den Wald vor 
Bäumen nicht, vor allem, wenn die WGM-Bits sich ber zwei Register 
erstrecken.

> Außerdem ist's schlecht Interrupts zu erlauben, für die man keine ISR
> definiert.
>
>> TIMSK = (1<<OCIE1B | 1<<OCIE1A | 1<<TOIE1);

Der Overflow-IRQ ist auch nur ein Debugging-Relikt, ich hatte 
zwischendurch so einiges ausprobiert... :-)

von MWS (Gast)


Lesenswert?

Stefan Tomanek schrieb:
> damit habe ich halt den Output-Compare-Modus eingeschaltet.

Nein, das war nicht der Fall, Du hast nur einmal einen Compare Match A 
erzwungen, der sich aufgrund unbesetzter COM1A1..0 nicht ausgewirkt hat.

Das tatsächliche Problem entstand dadurch, dass mangels gesetztem WGM12 
der CTC-Mode nicht eingeschaltet war, damit lief der Timer im normalen 
Modus und beim Overflow hast Du dann auch noch wegen fehlender ISR 'nen 
Reset bekommen.

von Stefan T. (tommie)


Lesenswert?

Stimmt, danke für den Hinweis. Jetzt scheint das Teil zu laufen, ich 
muss jetzt doch nochmal ein Oszilloskop organisieren, um das genau zu 
untersuchen.

von Peter ⛄ W. (Firma: Huddel und Brassel Ltd.) (jaffel) Benutzerseite


Lesenswert?

Fuer den lahmen Kram reicht doch die Soundkarte.

von Michael (Gast)


Lesenswert?

Stefan Tomanek schrieb:
> simulavr unterstützt leider die Timer-Register (noch) nicht :-/

Und was ist mit AVR Studio?

von Stefan T. (tommie)


Lesenswert?

Michael schrieb:
> Stefan Tomanek schrieb:
>> simulavr unterstützt leider die Timer-Register (noch) nicht :-/
>
> Und was ist mit AVR Studio?
1
stefan@exa:~$ sudo apt-get install avr-studio
2
Paketlisten werden gelesen... Fertig
3
Abhängigkeitsbaum wird aufgebaut       
4
Statusinformationen werden eingelesen... Fertig
5
E: Paket avr-studio kann nicht gefunden werden
6
stefan@exa:~$

von Peter ⛄ W. (Firma: Huddel und Brassel Ltd.) (jaffel) Benutzerseite


Lesenswert?

Stefan Tomanek schrieb:
> stefan@exa:~$ sudo apt-get install avr-studio
> Paketlisten werden gelesen... Fertig
> Abhängigkeitsbaum wird aufgebaut
> Statusinformationen werden eingelesen... Fertig
> E: Paket avr-studio kann nicht gefunden werden
> stefan@exa:~$

Ich schmeiss mich weg lol...

von Chris (Gast)


Lesenswert?

ähm AVR-Studio gibts nur für Windows :-)

von Stefan T. (tommie)


Lesenswert?

Chris schrieb:
> ähm AVR-Studio gibts nur für Windows :-)

Achso! War mit gar nicht bewusst! Ich habe mich schon gewundert, warum 
ich das Programm mit meiner Paketverwaltung nicht installieren konnte!
(https://de.wikipedia.org/wiki/Ironie)

von Lakshmikanthan (Gast)


Lesenswert?

The below mentioned code is for ATMEGA328 but i want to modify the code 
to work with ATMEGA8a.

 i am bit familiarised with arduino but not good enough for the below 
code please guide...

          Thanks in advanced...

feel free to contact me (mcitizen203@gmail.com)


/*
A basic receiver using the nRF24L01 module to receive channels and 
convert them to PPM.
 */

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
#include "printf.h"

////////////////////// PPM CONFIGURATION//////////////////////////
#define channel_number 6  //set the number of channels
#define sigPin 2  //set PPM signal output pin on the arduino
#define PPM_FrLen 27000  //set the PPM frame length in microseconds (1ms 
= 1000µs)
#define PPM_PulseLen 400  //set the pulse length
//////////////////////////////////////////////////////////////////

int ppm[channel_number];

const uint64_t pipeIn =  0xE8E8F0F0E1LL;

RF24 radio(9, 10);

// The sizeof this struct should not exceed 32 bytes
struct MyData {
  byte throttle;
  byte yaw;
  byte pitch;
  byte roll;
  byte dial1;
  byte dial2;
  byte switches; // bitflag
};

MyData data;

void resetData()
{
  data.throttle = 0;
  data.yaw = 127;
  data.pitch = 127;
  data.roll = 127;
  data.dial1 = 0;
  data.dial2 = 0;
  data.switches = 0;

  setPPMValuesFromData();
}

void setPPMValuesFromData()
{
  ppm[0] = map(data.throttle, 0, 255, 1000, 2000);
  ppm[1] = map(data.yaw,      0, 255, 1000, 2000);
  ppm[2] = map(data.pitch,    0, 255, 1000, 2000);
  ppm[3] = map(data.roll,     0, 255, 1000, 2000);
  ppm[4] = map(data.dial1,    0, 255, 1000, 2000);
  ppm[5] = map(data.dial2,    0, 255, 1000, 2000);
  ppm[6] = data.switches & 0x1 ? 2000 : 1000;
  ppm[7] = data.switches & 0x2 ? 2000 : 1000;
}

/**************************************************/

void setupPPM() {
  pinMode(sigPin, OUTPUT);
  digitalWrite(sigPin, 0);  //set the PPM signal pin to the default 
state (off)

  cli();
  TCCR1A = 0; // set entire TCCR1 register to 0
  TCCR1B = 0;

  OCR1A = 100;  // compare match register (not very important, sets the 
timeout for the first interrupt)
  TCCR1B |= (1 << WGM12);  // turn on CTC mode
  TCCR1B |= (1 << CS11);  // 8 prescaler: 0,5 microseconds at 16mhz
  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
  sei();
}

void setup()
{
  Serial.begin(9600);
  printf_begin();

  resetData();
  setupPPM();

  // Set up radio module
  radio.begin();
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);

  radio.openReadingPipe(1,pipeIn);
  radio.startListening();

  radio.printDetails();
}

/**************************************************/

unsigned long lastRecvTime = 0;

void recvData()
{
  while ( radio.available() ) {
    radio.read(&data, sizeof(MyData));
    lastRecvTime = millis();
  }
}

/**************************************************/

void loop()
{
  recvData();

  unsigned long now = millis();
  if ( now - lastRecvTime > 1000 ) {
    // signal lost?
    resetData();
  }

  setPPMValuesFromData();
}

/**************************************************/

ISR(TIMER1_COMPA_vect){
  static boolean state = true;

  TCNT1 = 0;

  if ( state ) {
    //end pulse
    PORTD = PORTD & ~B00000100; // turn pin 2 off. Could also use: 
digitalWrite(sigPin,0)
    OCR1A = PPM_PulseLen;
    state = false;
  }
  else {
    //start pulse
    static byte cur_chan_numb;
    static unsigned int calc_rest;

    PORTD = PORTD | B00000100; // turn pin 2 on. Could also use: 
digitalWrite(sigPin,1)
    state = true;

    if(cur_chan_numb >= channel_number) {
      cur_chan_numb = 0;
      calc_rest += PPM_PulseLen;
      OCR1A = (PPM_FrLen - calc_rest);
      calc_rest = 0;
    }
    else {
      OCR1A = (ppm[cur_chan_numb] - PPM_PulseLen);
      calc_rest += ppm[cur_chan_numb];
      cur_chan_numb++;
    }
  }
}

von Timo N. (tnn85)


Lesenswert?

Try to change this line
1
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
to
1
TIMSK |= (1 << OCIE1A); // enable timer compare interrupt

However, there is Arduino Code in your code. I guess you need to find an 
Arduino Bootloader for ATmega8A.

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.