UNIDINGS SERVO read/write SBUS PPM PWM BLHELI USW

Hallo Holger,

ich habe gestern Deinen Code auf einen 328er geladen, er funktioniert prima, meinen großen Respekt !

Ich möchte eine Schaumwaffel mit E-Motor an einer analogen 35 Mhz Anlage betreiben, die noch mit PPM läuft. An der Reichweitengrenze spinnt der BEC-Regler und lässt den Motor sogar manchmal für 2 Sek mit Vollgas laufen, bevor er einsieht, dass das PWM Signal Unsinn geworden ist. Ein Torcster als auch ein Wasbi-Regler zeigen dasselbe Verhalten. Dummerweise passiert das auch gelegentlich, wenn man den Sender vor dem Empfänger ausschaltet, weil der BEC auch einen plötzlichen einzelnen langezogenen PWM-Puls (durch Ausschalten zufällig erzeugt) als gültiges Signal erkennt, auch wenn danach nichts mehr kommt und er fatalerweise den Unsinn noch 2 Sek. lang ausführt. Ist also auch ein Sicherheitsproblem. Auch das Flattern der Drehzahl bei sehr kleiner Feldstärke nervt, man kann die Reichweite der Anlage so nicht ausnutzen.
Die BECs filtern sowas leider nicht raus.


Ich habe noch zwei Failsafe Module auftreiben können, einen von Simprop und einen aus China. Beide funktionieren grundsätzlich.
Dummerweise laufen beide synchron mit dem PWM, was aus dem Servousgang des Empfängers kommt. D.h. den Jitter des PWM geben sie an den BEC weiter. Und falls das PWM vom Empfänger ausfällt, oder wieder dazu kommt, gibt es beim Aus- bzw Einschwingen auch kurze starke Verfälschungen des PWM zum Motor.

Die Idee: Das PWM-Signal mit einem Arduino auslesen, Unsinn ausfiltern (inkl. abssurde Sprünge), puffern, glätten, ausgeben und bei zu hoher Fehlerrate ein PWM mit 1 ms ausgeben. Die Ausgabe soll dabei dauerhaft asynchron erfolgen, egal was am Eingang los ist.

Auf der Suche nach Routinen, die schnell genug sind und wenig Jitter verursachen, bin ich auf Dein Projekt gestossen. Ein par Dinge habe ich noch nicht verstanden:

In der Sektion "MODE 2 + 3 PPM-analog einlesen" steht:

PORTB |= 0b00000010;

Das setzt den Pin 1 von Port B (also D9) auf High, oder ? Wenn ja, warum ? D9 soll ja zum Lesen von PWM bzw PPM ein Eingang sein.
Es fehlt in dieser Sektion das dedizierte Setzen von D9 als Eingang (zB mit DDRB = 0b00000000).
Oder sind die Ports per Default Eingänge, und mit PORTB |= 0b00000010 wurde nur der Pull-Up-Widerstand gesetzt ?

In derselben Sektion findet man:
delay(200);
Die ISR wird durch einen Transienten auf D9 ausgelöst, und auch der Zähler wird durch einen weiteren Transienten auf D9 gestoppt, und in der Serialausgabe steht schon ein (anderes) Delay.
Daher: Wozu ist dieses delay(200); gut ?

Zuletzt noch die Schleife:
while(1){
if (MODE==2)SerialViewCHannels(1); //read single
if (MODE==3)SerialViewCHannels(8); //read stream

Was bedeutet die 1 im Argument von while(1) ?

Läuft die Schleife nur genau einmal ?

Viele Gruesse

Andreas
 
Hallo Andreas


Hab das eben erst gesehen.
Ich war mal wieder zu sparsam mit Kommentaren im Code, habs mal hier ein bissl nachgeholt:
Code:
////////////////  MODE 2 + 3 PPM-analog einlesen  /////////


>>>>Das ab hier läuft nur einmal, wenn PPM-einlesen aufgerufen wird, das (void)Setup für PPM-einlesen sozusagen.
void read_PPM(){         // use: ISR PCINT0

  PCICR  = 0b00000001;
  PCMSK0 = 0b00000010;
  TCCR1A = 0b00000000;
  TCCR1B = 0b00000010;
  PORTB |= 0b00000010;   <<<< Pullup Widerstand setzen
  sei();
  delay(200);   <<< Kurz warten, bis die ISRs Daten haben, bevor etwas angezeigt wird, da sonst die erste Zeile Unsinn gezeigt werden würde.  (Man könnte das delay in der SerialViewCHannels(x) auch am Anfang stellen, und das delay hier dafür wegsparen. )


                                                  
while(1){      <<<< Hier beginnt eine Endlosschleife while(1){  } läuft endlos, mit void setup ginge das hier nicht, da es mehrere Enlosschleifen im Code gibt.
        
if (MODE==2)SerialViewCHannels(1); //read single
if (MODE==3)SerialViewCHannels(8); //read stream

}     <<< Ende der Endlosschleife, wieder zum "while(1){  " zurück
}

Zu der Eingangsfrage - du willst mit dem Arduino nur ein Kanal PPM einlesen, und dann korrigiert wieder ausgeben ?
Wenn der Arduino sonst nix zu tun hat, würde ich das fix mit pulseIn erledigen. Wenn was zeitkritisches dazukäme, dann per ISR.

Einfacher PPM-Passthrough
D5 RC Signal
D9 Reglerausgang
Code:
Servo ESC_OUT;

void setup() {
 pinMode (5,INPUT_PULLUP);
 ESC_OUT.attach(9);
}

void loop() {
int ESC_IN = pulseIn(5, HIGH);
//Hier ESC_IN durch den Kakao ziehen ...
ESC_OUT.writeMicroseconds(ESC_IN);
}

Lese grad "jitterfrei"
Evtl könntest du auch einfach meinen alten Solarflieger-MPP dafür umodeln, ist ja fast dasselbe was Du vorhast, nur in der Schleife anders:
D2 + D3 sind gebrückt als RC-In um die High-Low Abfrage zu sparen
D9 ist RC-Ausgabe

Code:
/* Simply Solar by Holle for Arduino Nano 16MHz and ProMini 5V/16MHz
   D2+D3(both!) RC from Receiver
   D9 ESC (500HZ)
   One Resistor 10K from A5 to GND
   One Resistor from A0 to Solarvoltage:
   10 Solar-Cells 47K (Umpp 5,20V)
   12 Solar-Cells 56K (Umpp 6,24V)
   14 Solar-Cells 68K (Umpp 7,28V)
   16 Solar-Cells 82K (Umpp 8,32V)
 */


#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>

int main(void){
DDRB  |= 0b00100010;   // PPB5/D13 ist LED, PB1/D9 is ESC
PORTD |= 0b00001100;   // PD2+3/D2+3 is RC Input
TCCR1A = 0b10000010;   // Top:ICR1, PWM an OC1A
TCCR1B = 0b00010010;   // Prescale 1/8
TCCR2A = 0b00000000;   // nix da
TCCR2B = 0b00000010;   // Prescale 1/8
TIMSK2 = 0b00000001;   // INT Overflow
EIMSK  = 0b00000011;   // INT0+INT1 aktiv
EICRA  = 0b00001011;   // INT0 rising INT1 falling      
ADMUX  = 0b11000101;   // ADC5, Aref 1,1V,
ADCSRA = 0b10000111;   // prescale 1/128, 125KHz
ICR1   = 2000;         // SimonK-Mode 2ms/500Hz
OCR1A  = 1100;            
sei();
_delay_ms(2500);   //init ESC

while(1){
ADCSRA |= (1<<ADSC); while(ADCSRA &(1<<ADSC));  // read ADC0 without free running           
if (OCR1B < ADC)  {if (OCR1A < 1900) {OCR1A++;}}   // Usolar to high >> inc ESC-PWM
else          {if (OCR1A > 1100) {OCR1A--;}}   // Usolar to low  >> dec ESC-PWM
}}

ISR(INT0_vect){ OCR2B = 0b11111101;          // rising edge  // 3xOVFs to OCR2=0   
        TCNT2 = 0b00000000;
        TCCR2B  = 0b00000010;
}                          
ISR(INT1_vect){ TCCR2B  = 0b00000000;
        OCR1BH  = OCR2B;         // falling edge
        OCR1BL  = TCNT2;
}
ISR(TIMER2_OVF_vect){OCR2B++;}              // Int Overflow
 
Zuletzt bearbeitet:
Und da war er mal wieder, der Timeout 🤪

Habs mal freigeräumt, ungetestet

OCR1A = OCR1B / 2; wäre ein PPM-Passthrough.

Bin mir nur zu 99% sicher ob Du die Werte aus OCR1B noch durch / 2 teilen musst, da kommt vermutlich 2200-3800 statt 1100-1900µS, probieren oder vorab mal serial ausgeben. Den /2 Teiler könnte man auch in der ISR erledigen (wie beim Unidings), aber bei einem Live-System, besser die ISRs so kurz wie möglich (darum die Holzhammer-methode mit der D2-D3 Brücke:D)


Code:
//   D2+D3(both!) RC from Receiver
//   D9 ESC (500HZ)

#define F_CPU 16000000UL
#include <util/delay.h>
#include <avr/io.h>
#include <avr/interrupt.h>

int main(void){
DDRB  |= 0b00100010;   // PPB5/D13 ist LED, PB1/D9 is ESC
PORTD |= 0b00001100;   // PD2+3/D2+3 is RC Input
TCCR1A = 0b10000010;   // Top:ICR1, PWM an OC1A
TCCR1B = 0b00010010;   // Prescale 1/8
TCCR2A = 0b00000000;   // nix da
TCCR2B = 0b00000010;   // Prescale 1/8
TIMSK2 = 0b00000001;   // INT Overflow
EIMSK  = 0b00000011;   // INT0+INT1 aktiv
EICRA  = 0b00001011;   // INT0 rising INT1 falling   
ICR1   = 2000;         // SimonK-Mode 2ms/500Hz
OCR1A  = 1100;         // Leerlauf      
sei();
_delay_ms(2500);       // init ESC



while(1){  //Endlosscheleife

//OCR1B Enhällt den eingelesenen Rc-Wert
//OCR1A darin wird die RC-Ausgabe reingeschrieben

}} //Ende Endlosschleife



ISR(INT0_vect){ OCR2B = 0b11111101;          // rising edge  // 3xOVFs to OCR2=0
        TCNT2 = 0b00000000;
        TCCR2B  = 0b00000010;
}                       
ISR(INT1_vect){ TCCR2B  = 0b00000000;
        OCR1BH  = OCR2B;         // falling edge
        OCR1BL  = TCNT2;
}
ISR(TIMER2_OVF_vect){OCR2B++;}              // Int Overflow
Was mir aber gerade noch einfällt, evtl. reicht auch ein Elko 1000-2200µF am Empfänger, (sind jede Menge in 6,3V auf alten Motherboards drauf), den einfach mit einem Servokabel an einem freiem Empfängerkanal stecken, kann bei sowas manchmal Wunder bewirken.
 
Zuletzt bearbeitet:
Moin
Grad nen Fehler im letztem Beitrag gesehen, wenn man das Servosignal 1zu1 durchreichen will, muss die Zeile:

ICR1 = 2000; // SimonK-Mode 2ms/500Hz

in:

ICR1 = 20000; // RC-PPM 20ms/50Hz

geändert werden, sonst könnte es sein, das ältere Regler das nicht erkennen, und für Servos sowieso.
 
Hallo Holger,

schonmal herzlichen Dank für Deinen Support!
Pull-Up und Delay habe ich jetzt verstanden. Den Rest muss ich sorgfältig lesen und brauche auch Zeit, um es zu kapieren. Ich habe zwar vor 30 Jahren Nachrichtentechnik studiert, aber bisher nie was mit Mikropozessoren zu tun gehabt. Sowas wie:


ist neben Deinen sehr nützlichen Hinweisen für einen Anfänger wie mich sehr hilfreich, wenn es um ISRs geht.

Der pulseln ist leider auch nicht genau genug, so meine Erinnerung.
Die Ausgabe muss auf alle Fälle mit nem ISR erfolgen, sonst ist mir der Jitter zu gross. Der Eingang sollte eine untergeordneten ISR auslösen (sofern das möglich ist). Dazwischen muss gepuffert, dann gefiltert und danach noch mit einer mathem. Funktion geglättet werden, also ja, der Prozessor bekommt auch sonst noch was zu tun, aber das ist nicht zeitkritisch.

Eine analoge Schaltung wäre hier auch denkbar (z.B. ein Konstrukt aus OP-Amps), aber das wird sehr aufwändig und langsam, wenn man auf einen Jitter von unter 1 us kommen will (entspricht weniger als 1 Promille eines PWM Pulses).

Leider habe ich wegen beruflicher Fortbildung (abends), demnächst 2 Wochen Urlaubsreise und einem Ad-hoc Zwischenprojekt (Notstrom für den Winter mit Solar, Batterie und Generator) gerade keine Zeit, das Hobby kommt nun mal zuletzt.
Ich melde mich aber in ca 6 Wochen auf jeden Fall wieder.

Nochmals Danke und bis dann

Andreas
 
Ansicht hell / dunkel umschalten
Oben Unten