Senderprogramm

Abbildung 4-1: Testaufbau Sender


Funktionen:

Mit dem Senderprogramm können 4 verschiedene Empfänger angesprochen werden, es können über die Tastatur bis zu 12 Schaltbefehle und bis zu 4 Poti-Werte gesendet werden. Im nachfolgenden Test-Senderprogramm sind 10 Schaltbefehle und 4 Poti-Werte realisiert. Dabei führt das Senderprogramm folgende Funktionen aus:

  • Zyklisches Abfragen der Tastatur
  • Zyklisches Einlesen der Potentiometer
  • Vorwählen des gewünschten Empfängers 0-3 über die Tastatur
  • Ein-/Aus-Schalten der Sendefunktion über die Tastatur (Taste F = Tastencode 15)
  • Zyklische Erstellung von Sendetelegrammen für die Ansteuerung der Servos
  • Azyklische Erstellung von Sendetelegrammen wenn eine entsprechende Taste gedrückt wurde oder ein Poti zur Drehzahlverstellung von Motoren verändert wurde
  • Zyklische Erstellung von Sendetelegrammen für die Drehzahlverstellung von Motoren in einem "Hintergrundzyklus"
  • Bildung der Checksummen
  • Versenden der Telegramme


Tastaturbelegung:

         7   8   9   A

         4   5   6   B

         1   2   3   C

         0   F   E   D


  • Taste 0 - 3: Auswahl Empfänger 0 - 3
  • Taste 4: LED 1 Ein (anstelle von LED könnte auch ein Relais angesprochen werden)
  • Taste 5: LED 1 Aus
  • Taste 6: LED 1 Toggle (umschalten des aktuellen Schaltzustandes)
  • Taste 7: Motor 1 Drehzahl Vor
  • Taste 8: Motor 1 Drehzahl Rück
  • Taste 9: Motor 2 Drehzahl Vor
  • Taste A: Motor 2 Drehzahl Rück
  • Taste B: LED 2 Ein
  • Taste C: LED 2 Aus
  • Taste D: LED 2 Toggle (umschalten des aktuellen Schaltzustandes)
  • Taste E: derzeit unbenutzt
  • Taste F: Senden Ein/Aus


Verwendete Libraries:

Neben den Standard-Library Wire verwende ich folgende Libraries:

  • Metro: Zur zyklischen Abfrage der Tastatur und der Potentiometer
  • VirtualWire: Steuert die drahtlose Übertragung eines Telegramms z.B. über eine Funkübertragungsstrecke

      (Einen Link zu Metro und VirtualWire findet ihr hier: Fremd-Libraries)

Sowie meine eigenen Libraries:

  • MyKeypad_I2C: Zur Abfrage der Tastatur. Die Library kann hier heruntergeladen werden: 16er-Tastatur mit I2C
  • MySparkfun7SegI2C: Zur Ansteuerung der 7-Segmentanzeige von Sparkfun mit I2C-Schnittstelle. Die Library kann hier heruntergeladen werden: 7-Segm.anz. Sparkfun


Sonstiges:

Zykluszeiten:

Im Hauptteil des Programms laufen drei Zyklen mit verschiedenen Zykluszeiten (Angaben in Millisekunden) ab, die mit der Metro-Library realisiert wurden:

  • 100 - 150 ms zur Abfrage der Tastatur
  • 15 ms zur Bearbeitung der Servos
  • 150 - 200 ms zur Bearbeitung der Motoren

Wie man sieht, habe ich für die Bearbeitung der Servos einen wesentlich schnelleren Zyklus gewählt (15 ms), als bei den anderen Komponenten. Nur eine so rasche Abfrage der Potis und Versendung der Telegramme kann eine ruckfreie Bewegung der Servos gewährleisten. Die Abfrage der Potis für die Drehzahlsteuerung der E-Motoren kann hingegen wesentlich langsamer erfolgen (150 - 200 ms), da die Motoren selbst bei einer Drehzahländerung eine gewisse Trägheit aufweisen und somit ein schnelleres Abfragen und Senden keinen Sinn macht.

Sendeprobleme mit schnellen Zykluszeiten:

Bei einer Telegrammlänge von 4 Bytes (= 32 Bit) und einem Sendezyklus von 15 ms (= 66,7 Hz) ergibt das eine Anzahl von 32 Bit x 66,7 Hz = 2.133 Bit pro Sekunde nur für die Übertragung eines Wertes. Werden gleichzeitig zwei Servopotis verstellt, kommt es zum "Ruckeln" der Servos, wenn nicht ein Funkmodul mit ausreichend hoher Übertragungsrate verwendet wird. Die von mir zuerst verwendeten Superheterodyne-Funkmodule mit einer angegebenen maximalen Datenrate von 2.400 bzw. 3.000 Bit pro Sekunde waren dafür definitiv zu langsam, obwohl diese sogar bis 4.000 Bit pro Sekunde funktionierten. Ich habe jetzt neue Module eingesetzt, die mit einer Übertragungsrate bis 10.000 Bit pro Sekunde spezifiziert sind. Im Testbetrieb erreiche ich mit einer Einstellung von 8.000 Bit pro Sekunde (Vorgabe bei der Initialisierung der VirtualWire-Library) eine nahezu ruckelfreie Bewegung des Servos bei gleichzeitiger Drehzahlverstellung eines Motors. Eine weitere Erhöhung dieses Werte brachte keine Verbesserung mehr, da dann anscheinend zu viele Telegramme fehlerhaft übertragen werden.

Anpassung der PWM-Signale an die maximale bzw. minimale Pulsweite der Servos und Motoren:

Für einen Drehbereich von 0° - 180° benötigt ein Servo Impulsbreiten von ca. 1000 µs - 2000 µs (Mikrosekunden). Je nach Servotype können diese Werte allerdings variieren (meine hier eingesetzten Servos benötigt hierfür Impulsbreiten von ca. 560 µs - 2500 µs).

Die Drehzahl eines Motors wird bei Verwendung der Arduinofunktion "analogWrite" mit einem 8 Bit-PWM-Signalen (0 - 255) verstellt.

Mit den Variablen "pwmTimeXMin" und "pwmTimeXMax" (X = 1...4) müssen je nach Verwendung der Potis (für Servos oder Motoren) die jeweiligen Bereiche im Deklarationsteil für jedes Poti angegeben werden. (Siehe auch: Servosteuerung)

Debug-Modus:

Mit der Anweisung "#define debug 1" im Deklarationsteil des Programms kann das Senderprogramm in den "Debug-Modus" geschaltet werden. Dabei werden verschiedene Daten, wie eingelesene Tasten- und Potiwerte und Inhalte von Sendebuffer zur Kontrolle am Seriellen Monitor (Übertragungsrate auf 115200 Baud stellen) ausgegeben. Wenn die Anzeige am Seriellen Monitor nicht mehr benötigt wird, dann den Debug-Modus mit "#define debug 0" unbedingt ausschalten, da die serielle Ausgabe das Programm unnötig bremst.

Funk-Fernsteuerung_Sender204.ino Version 1.0

//Testprogramm Funk-Fernsteuerung Sender
//Funk-Fernsteuerung_Sender204.ino
//Code fuer Arduino
//Author Retian
//Version 1.1


#include <MySparkfun7SegI2C.h>
#include <Metro.h>
#include <Wire.h>
#include <MyKeypad_I2C.h>
#include <VirtualWire.h>


//0 ... kein Debug-Modus Ausgabe am Seriellen Monitor
//1 ... Debug-Modus Ausgabe am Seriellen Monitor
#define debug 0


bool sendeStatus = 0; //Merker, ob Sender AUS- oder Ein-geschaltet ist


//Zyklische Abfragen in Millisekunden
Metro tastZyklusMetro(100); //Zyklus für Tastatur-Bearbeitung
Metro servoZyklusMetro(15); //Zyklus für Servo-Bearbeitung
Metro motorPotiZyklusMetro(150); //Zyklus für Motor-Bearbeitung

//Hintergrundzyklus für Motoren
Metro hintergrundZyklus3(480);
Metro hintergrundZyklus4(520);


//Definition der Analog- und Digitalpins
#define potiPin1 0 //Potentiometer1-Pin ist Analogpin 0
#define potiPin2 1 //Potentiometer2-Pin ist Analogpin 1
#define potiPin3 2 //Potentiometer3-Pin ist Analogpin 2
#define potiPin4 3 //Potentiometer3-Pin ist Analogpin 3

#define txPin 2 //TX-Pin
#define ledPin 3 //LED-Pin


MySparkfun7SegI2C My7Seg(0x71); //Initalisiere 7-Segmentanzeige
MyKeypad_I2C MyTast(0x38); //Initialisiere Tastatur


//Definition des Sende-Telegramms
const byte telegrammLaenge = 4; //Telegrammlaenge in Bytes
//Byte0: IDByte
//Byte1: PWM-Impulszeit High-Byte
//Byte2: PWM-Impulszeit Low-Byte
//Byte3: CheckSumme

byte IDByte;
int pwmTime; //PWM-Impulszeit
byte checkSum;

//Index-Nummern des Sendetelegrammms (Variable "sendeBuffer")
#define indexIDByte 0
#define indexHighByte 1
#define indexLowByte 2
#define indexCheckSumByte telegrammLaenge - 1
//Über diese Bytes soll die Checksumme ermittelt werden
#define checkSumByteAnf 0
#define checkSumByteEnd (telegrammLaenge - 2)


//Zusammensetzung des ID-Bytes
//Bit7-6: EmpfängerID
//Bit5-2: KomponentenID
//Bit1-0: BefehlsID
byte empfaengerID = 0;
byte komponentenID;
byte befehlsID;

byte taste; //Wert der gedrückten Taste
int potiWert1; //Wert des Potentiometer1
int potiWert2; //Wert des Potentiometer2
int potiWert3; //Wert des Potentiometer3
int potiWert4; //Wert des Potentiometer4

//Wird nur bei Potis für Motoren verwendet
//int altPotiWert1; //Altwert des Potentiometer1
//int altPotiWert2; //Altwert des Potentiometer2
int altPotiWert3; //Altwert des Potentiometer3
int altPotiWert4; //Altwert des Potentiometer4


byte sendeBuffer[telegrammLaenge]; //Der Sendebuffer wird der VirtualWire-Library übergeben


int pwmTime1Max = 2400; //maximale PWM-Impulszeit für Servo/Motor1
int pwmTime1Min = 560; //minimale PWM-Impulszeit für Servo/Motor1
int pwmTime2Max = 2460; //maximale PWM-Impulszeit für Servo/Motor2
int pwmTime2Min = 560; //minimale PWM-Impulszeit für Servo/Motor2
int pwmTime3Max = 255; //maximale PWM-Impulszeit für Servo/Motor3
int pwmTime3Min = 0; //minimale PWM-Impulszeit für Servo/Motor3
int pwmTime4Max = 255; //maximale PWM-Impulszeit für Servo/Motor4
int pwmTime4Min = 0; //minimale PWM-Impulszeit für Servo/Motor4


//Merker für Tastenbefehl, der gesendet werden soll. Nicht alle Tasten bewirken einen Sendebefehl!
bool sendTastBefehl = false;


//Definition vom maximal 16 Komponenten (0 bis 15)
#define LED1 1
#define LED2 2
#define Servo1 12
#define Servo2 13
#define Motor1 14
#define Motor2 15


void setup() {
  if (debug)
  {
    Serial.begin(115200);
    if (MyTast.isReady()) Serial.println("Tastatur gefunden");
    else Serial.println("Tastatur-Fehler");
    if (My7Seg.isReady()) Serial.println("7-Segmentanzeige gefunden");
    else Serial.println("7-Segmentanzeige-Fehler");
  }

  pinMode(ledPin, OUTPUT);

  //altPotiWert1 = analogRead(potiPin1);
  //altPotiWert2 = analogRead(potiPin2);
  altPotiWert3 = analogRead(potiPin3);
  altPotiWert4 = analogRead(potiPin4);

  //Initialisierung von VirtualWire
  vw_set_tx_pin(txPin); //Setze TX-Pin
  vw_setup(8000); //Übertragungsrate 8000 Bit/Sekunde

  //sendeBuffer[0] = syncindexHighByte;
  My7Seg.setBrightness(50);
  My7Seg.sendChar('E', empfaengerID, ' ', ' ');
}


void loop() {

  //Zyklisches Abfragen der Tastatur
  if (tastZyklusMetro.check())
  {
    taste = MyTast.receiveKey(-1);
    if (debug && taste != 255)
    {
      Serial.print("Taste: ");
      Serial.println(taste);
    }
    if (taste >= 0 && taste <= 15)
    {
      //Mit den Tasten 0 - 4 wird der Empfaenger ausgwählt
      if (taste == 0)
      {
        empfaengerID = 0;
        //Schreibe "E0" in die 7-Segmentanzeige
        My7Seg.sendChar('E', empfaengerID, ' ', ' ');
      }

      if (taste == 1)
      {
        empfaengerID = 1;
        //Schreibe "E1" in die 7-Segmentanzeige
        My7Seg.sendChar('E', empfaengerID, ' ', ' ');
      }

      if (taste == 2)
      {
        empfaengerID = 2;
        //Schreibe "E2" in die 7-Segmentanzeige
        My7Seg.sendChar('E', empfaengerID, ' ', ' ');
      }

      if (taste == 3)
      {
        empfaengerID = 3;
        //Schreibe "E3" in die 7-Segmentanzeige
        My7Seg.sendChar('E', empfaengerID, ' ', ' ');
      }

      //Mit den Tasten 4 - 15 können Befehle gesendet werden
      if (taste == 4) //Befehl LED 1 Ein
      {
        komponentenID = LED1;
        befehlsID = 1;
        sendTastBefehl = true; //Merker wenn Befehl gesendet werden soll
      }

      if (taste == 5)//Befehl LED 1 Aus
      {
        komponentenID = LED1;
        befehlsID = 0;
        sendTastBefehl = true; //Merker wenn Befehl gesendet werden soll
      }

      if (taste == 6) //Befehl LED 1 umschalten (toggle)
      {
        komponentenID = LED1;
        befehlsID = 2;
        sendTastBefehl = true; //Merker wenn Befehl gesendet werden soll
      }

      if (taste == 7) //Befehl Motor 1 Drehrichtung vorwaerts
      {
        komponentenID = Motor1;
        befehlsID = 0;
        sendTastBefehl = true; //Merker wenn Befehl gesendet werden soll
      }

      if (taste == 8) //Befehl Motor 1 Drehrichtung rueckwärts
      {
        komponentenID = Motor1;
        befehlsID = 1;
        sendTastBefehl = true; //Merker wenn Befehl gesendet werden soll
      }

      if (taste == 9) //Befehl Motor 2 Drehrichtung vorwaerts
      {
        komponentenID = Motor2;
        befehlsID = 0;
        sendTastBefehl = true; //Merker wenn Befehl gesendet werden soll
      }

      if (taste == 10) //Befehl Motor 2 Drehrichtung rueckwärts
      {
        komponentenID = Motor2;
        befehlsID = 1;
        sendTastBefehl = true; //Merker wenn Befehl gesendet werden soll
      }

      if (taste == 11) //Befehl LED 2 Ein
      {
        komponentenID = LED2;
        befehlsID = 1;
        sendTastBefehl = true; //Merker wenn Befehl gesendet werden soll
      }

      if (taste == 12)//Befehl LED 2 Aus
      {
        komponentenID = LED2;
        befehlsID = 0;
        sendTastBefehl = true; //Merker wenn Befehl gesendet werden soll
      }

      if (taste == 13) //Befehl LED 1 umschalten (toggle)
      {
        komponentenID = LED2;
        befehlsID = 2;
        sendTastBefehl = true; //Merker wenn Befehl gesendet werden soll
      }

      if (taste == 14) //
      {
        //Platzhalter für Befehle Taste14
      }

       if (taste == 15) //Senden EIN/AUS
      {
        sendeStatus = !sendeStatus;
      }

      IDByte = (empfaengerID << 6) | (komponentenID << 2) | befehlsID;

      //Werte-Bytes im Telegramm loeschen
      for (byte i = 2; i < telegrammLaenge - 1; i++) sendeBuffer[i] = 0;

      sendeBuffer[indexIDByte] = IDByte;

      //Wenn eine Taste mit Sendebefehl gedrueckt wurde, dann gehe zur Sende-Routine
      if (sendTastBefehl == true) Sende();
    }
  }


  //Zyklus Servo-Bearbeitung
  if (servoZyklusMetro.check())
  {


    //Bearbeitung Servo1
    potiWert1 = analogRead(potiPin1); //Einlesen Poti Servo1

    if (debug)
    {
      Serial.print("Potiwert1: ");
      Serial.println(potiWert1);
    }

    //Ermittlung PWM-Impulszeit Servo1
    pwmTime = map(potiWert1, 0, 1023, pwmTime1Min, pwmTime1Max);

    if (debug)
    {
      Serial.print("pwmTime: ");
      Serial.println(pwmTime);
    }

    //IDByte und Sendebuffer definieren
    komponentenID = Servo1;
    befehlsID = B11;
    IDByte = (empfaengerID << 6) | (komponentenID << 2) | befehlsID;
    //altPotiWert1 = potiWert1; //aktueller Potiwert wird in "altPotiWert" gespeichert
    sendeBuffer[indexIDByte] = IDByte;
    sendeBuffer[indexHighByte] = highByte(pwmTime);
    sendeBuffer[indexLowByte] = lowByte(pwmTime);

    Sende(); //Gehe zur Sende-Routine


    //Bearbeitung Servo2
    potiWert2 = analogRead(potiPin2);  //Einlesen Poti Servo2

    if (debug)
    {
      Serial.print("Potiwert2: ");
      Serial.println(potiWert2);
    }

    //Ermittlung PWM-Impulszeit Servo2
    pwmTime = map(potiWert2, 0, 1023, pwmTime2Min, pwmTime2Max);

    if (debug)
    {
      Serial.print("pwmTime: ");
      Serial.println(pwmTime);
    }

    //IDByte und Sendebuffer definieren
    komponentenID = Servo2;
    befehlsID = B11;
    IDByte = (empfaengerID << 6) | (komponentenID << 2) | befehlsID;
    //altPotiWert2 = potiWert2; //aktueller Potiwert wird in "altPotiWert" gespeichert
    sendeBuffer[indexIDByte] = IDByte;
    sendeBuffer[indexHighByte] = highByte(pwmTime);
    sendeBuffer[indexLowByte] = lowByte(pwmTime);

    Sende(); //Gehe zur Sende-Routine
  }


  //Zyklus Motor-Bearbeitung
  if (motorPotiZyklusMetro.check())
  {


    //Bearbeitung Motor1
    potiWert3 = analogRead(potiPin3);  //Einlesen Poti Motor1

    //Bei Verstellung von Poti3 oder wenn Hintergrundzyklus aktiv, dann sende PWM-Zeit
    if (abs(potiWert3 - altPotiWert3) || hintergrundZyklus3.check())
    {
      if (debug)
      {
        Serial.print("Potiwert3: ");
        Serial.println(potiWert3);
      }
      //Ermittlung der PWM-Impulszeit Motor 1
      pwmTime = map(potiWert3, 0, 1023, pwmTime3Min, pwmTime3Max);

      if (debug)
      {
        Serial.print("pwmTime: ");
        Serial.println(pwmTime);
      }

      //IDByte und Sendebuffer definieren
      komponentenID = Motor1;
      befehlsID = B11;
      IDByte = (empfaengerID << 6) | (komponentenID << 2) | befehlsID;
      altPotiWert3 = potiWert3; //aktueller Potiwert wird in "altPotiWert" gespeichert
      sendeBuffer[indexIDByte] = IDByte;
      sendeBuffer[indexHighByte] = highByte(pwmTime);
      sendeBuffer[indexLowByte] = lowByte(pwmTime);

      Sende(); //Gehe zur Sende-Routine
    }


    //Bearbeitung Motor2
    potiWert4 = analogRead(potiPin4); //Einlesen Poti Motor2

    //Bei Verstellung von Poti4 oder wenn Hintergrundzyklus aktiv, dann sende PWM-Zeit
    if (abs(potiWert4 - altPotiWert4) || hintergrundZyklus4.check())
    {
      if (debug)
      {
        Serial.print("Potiwert4: ");
        Serial.println(potiWert4);
      }

      //IDByte und Sendebuffer definieren
      pwmTime = map(potiWert4, 0, 1023, pwmTime4Min, pwmTime4Max);

      if (debug)
      {
        Serial.print("pwmTime: ");
        Serial.println(pwmTime);
      }

      komponentenID = Motor2;
      befehlsID = B11;
      IDByte = (empfaengerID << 6) | (komponentenID << 2) | befehlsID;
      altPotiWert4 = potiWert4; //aktueller Potiwert wird in "altPotiWert" gespeichert
      sendeBuffer[indexIDByte] = IDByte;
      sendeBuffer[indexHighByte] = highByte(pwmTime);
      sendeBuffer[indexLowByte] = lowByte(pwmTime);

      Sende(); //Gehe zur Sende-Routine
    }
  }
}


void Sende()
{

  if(sendeStatus == true)

  {
    //Ermittlung der Checksumme
    checkSum = 0;
    for (byte i = checkSumByteAnf; i <= checkSumByteEnd; i++)
    {
      CheckByte(sendeBuffer[i], i);
    }

     //Schreibe ermittelte Checksumme in den Sendebuffer
    sendeBuffer[indexCheckSumByte] = checkSum;

    if (debug)
    {
      Serial.print("IDByte: ");
      Serial.println(IDByte, BIN);
      Serial.print("Sendebuffer: ");
      for (byte i = 0; i < telegrammLaenge; i++)
      {
        Serial.print(sendeBuffer[i]);
        Serial.print(" ");
      }
      Serial.println("\n");
    }

    //Senden des Telegramms
    vw_send((byte*)sendeBuffer, telegrammLaenge);
    vw_wait_tx();

    //Bei jedem gesendetem Telegramm blinkt die LED für 0,05 ms auf
    digitalWrite(ledPin, 1);
    delayMicroseconds(50);
    digitalWrite(ledPin, 0);

    sendTastBefehl = false; //Rücksetzen des Merkers für Tastenbefehl

  }
}


//Checksummenbildung
void CheckByte(byte buf, byte i)
{
  for (int j = 0; j < 8; j++)
  {
    if (bitRead(buf, j) == 1) checkSum += (i + j + 1);
  }
}


Weiter zum Empfängerprogramm