generate PPM

cepics
Posts: 6
Joined: Sat Dec 08, 2018 10:13 am

generate PPM

Postby cepics » Sat Oct 19, 2019 7:24 pm

Hi all, I would like to send a PPM to this https://www.frsky-rc.com/xjt/ transmitter to control gimbal axes (PAN,TILT,ROL) with M5Stack gyro remotely.
the only working sketch I found to generate PPM is for Arduino and use timer interrupt:

Code: Select all

//https://forum.arduino.cc/index.php/topic,163199.msg1220724.html#msg1220724

// Started 18 Apr 2013 - the loco chassis is more or less ready for testing.
//
// This is a test program to drive a loco on R/C channel 1 based on the position of a potentiometer.
//
// The centre connection of the pot is connected to pin A2
// The control signal from the Arduino to the transmitter is on Pin D7.
//
// I intend that parts of this program will be used in the computer controlled version.
//
// The R/C receiver is programmed to treat the centre value (128?) as off with 0 and 255 representing
//    full forward and full reverse. Some experimentation will probably be necessary to determine the
//    correct 0 point.
// The transmitter can receive a sequence of pulses for 2 to 7 channels. For this program pulses will be
//    prepared for 7 channels with channels 2 to 7 being values that correspond to centre off.
//    The receiver would not work with only 3 channels - it needs at least 4..
//
// The Transmitter expects a sequence of pulses each of whose width (duration) conveys the
//   speed setting. The pulse lengths must vary between 1.0 and 2.0 millisecs with 1.5ms representing
//   centre off. Each pulse must be separated by a short pulse of 0.3ms and there must be a long pulse
//   to separate the groups of pulses. Ideally the group of pulses should repeat every 22ms
//   So, 2.0 + 0.3ms for 7 pulses takes 16.1ms thus requiring a separating pulse of 5.9ms.
//
// The pulses are generated by timer1 (16 bit) interrupts and to simpify matters when the computer is
//    controlling things the numbers needed to produce the sequence of pulse lengths will be stored
//    in an array. At each interrupt the next value in the array is used to set the duration for the
//    following interrupt. For this program the values in the array position corresponding to channel 3
//    will be derived from the potentiometer position. (The Rx seems to be programmed to drive the motor
//      on ch3).
//
// Timer1 is used because the longer periods afforded by the 16bits are needed. The prescaler is set
//    to divide the CPU clock by 64 giving a timer clock of 250khz or 4 microsecs (4us).
//    On that basis 0.3ms requires a delay of 75 clock steps
//                  1.0                      250
//                  1.5                      375
//                  2.0                      500
//                  5.9                     1475
//                 15.1                     3775
//
// This program only communicates with the PC for program testing.

// Data variables
unsigned int ppm[16]; // the array of values for the timer1 interrupt timings
unsigned int potVal; // value read from potemtiometer
int curPpm = 0; // the position in the array of timings
byte curPpmMax; // the actual number of timer steps

char pulsePin = 7; // the digital pin for sending pulses to the transmitter
char debugPin = 13; // something to watch to check things are working
char potPin = 2; // the analog pin for the potentiometer

byte loopCount = 0; // a crude substitute for delay()
int analogIn; // for reading potentiometer position


boolean testing = true;

void setup() {

  if (testing) {
    Serial.begin(9600);
    Serial.println("Starting TrainRadioPot");
  }

  // set the pin directions
  pinMode(pulsePin, OUTPUT);
  //  pinMode(debugPin, OUTPUT);

  // set the timing values
  ppm[0] = 1475; //3775; // long pulse - see notes above
  ppm[1] = 75; // short dividing pulse
  ppm[2] = 305; // loco1 ch1
  ppm[3] = 75; // short
  ppm[4] = 305; // loco2 ch2
  ppm[5] = 75; // short
  ppm[6] = 305; // loco3 ch3
  ppm[7] = 75; // short
  ppm[8] = 305; // loco4 ch4
  ppm[9] = 75; // short
  ppm[10] = 305; // loco5 ch5
  ppm[11] = 75; // short
  ppm[12] = 305; // loco6 ch6
  ppm[13] = 75; // short
  ppm[14] = 305; // loco7 ch7
  ppm[15] = 75; // short

  curPpmMax = 16; // the number of timer values
  curPpm = 0; // the starting position in the array of timings

  // setup and start timer interrupts
  // ppm is achieved by sending different delays to the timer interrupt
  noInterrupts();
  TCCR1A = 0;     // set entire TCCR1A register to 0
  TCCR1B = 0;     // same for TCCR1B
  // set compare match register to desired timer count:
  OCR1A = 1000; // It works by causing an interrupt when TCNT2 counts up to this number
  // This number will be set from the ppm array when the program is running
  digitalWrite(pulsePin, 0); // start with pin low
  // turn on CTC mode: Clear Timer on Compare
  bitSet(TCCR1B, WGM12);
  // Set CS10 and CS11 bits for 64 prescaler: 10, 12 = 1024
  bitSet(TCCR1B, CS10);
  bitSet(TCCR1B, CS11);
  bitClear(TCCR1B, CS12);
  // enable timer compare A interrupt:
  bitSet(TIMSK1, OCIE1A);
  interrupts();

}

void loop() {

  if (loopCount == 1) {
    analogIn = analogRead(potPin);
    // the reading must be converted from the range 0 - 1023 to the range 250 - 500
    //   which means dividing by 4 and adding 180 to give 305 at the centre
    potVal = (analogIn >> 2) + 180;
    //		if (potVal > 302 && potVal < 308) {
    if (potVal > 280 && potVal < 320) {
      potVal = 305; // to create an off space without jitter
    }
    //	ppm[2] = potVal;
    //  ppm[8] = potVal;// } CH4 ROL STABE
    // ppm[4] = potVal; //   CH2 PAN STABE YAW
    ppm[6] = potVal; //    CH3 TILT STABE PICH

  }


  //	loopCount = 0; // for testing - so analog read is ignored
  loopCount = loopCount + 1; // it will roll over to zero automatically
  // the purpose of loopCount is so that the pot isn't read every loop
}


// interrupt routine that is triggered when the timer counts up to the preset value
ISR(TIMER1_COMPA_vect) {
  noInterrupts();
  digitalWrite(pulsePin, !digitalRead(pulsePin)); // change the state of the pin
  OCR1A = ppm[curPpm]; // set the value for the next time interval
  curPpm = ((curPpm + 1) % curPpmMax); // move the index on
  // the modulus operator makes the index roll over to the start
  interrupts();
}
I also found this function (not yet tested with the transmitter)

Code: Select all

//this programm will put out a PPM signal

//////////////////////CONFIGURATION///////////////////////////////
#define chanel_number 8  //set the number of chanels
#define default_servo_value 1500  //set the default servo value
#define PPM_FrLen 22500  //set the PPM frame length in microseconds (1ms = 1000µs)
#define PPM_PulseLen 300  //set the pulse length
#define onState 1  //set polarity: 1 is positive, 0 is negative
#define sigPin 10  //set PPM signal pin on the arduino
//////////////////////////////////////////////////////////////////


/*this array holds the servo values for the ppm signal
 change theese values in your code (usually servo values are between 1000 and 2000)*/
int ppm[chanel_number];

void setup(){  

  //initiallize default ppm values
  for(int i=0; i<chanel_number; i++){
    ppm[i]= default_servo_value;
  }

  pinMode(sigPin, OUTPUT);
  digitalWrite(sigPin, !onState);  //set the PPM signal pin to the default state (off)
}

void loop(){
  //put main code here
  ppmWrite();
}

void ppmWrite(){  //generate PPM signal
  static unsigned long lastFrLen;
  static unsigned long lastServo;
  static unsigned long lastPulse;
  static boolean PPM_run;
  static boolean pulse;
  static boolean pulseStart = true;
  static byte counter;
  static byte part = true;

  if(micros() - lastFrLen >= PPM_FrLen){  //start PPM signal after PPM_FrLen has passed
    lastFrLen = micros();
    PPM_run = true;
  }

  if(counter >= chanel_number){
    PPM_run = false;
    counter = 0;
    pulse = true;  //put out the last pulse
  }

  if(PPM_run){
    if (part){  //put out the pulse
      pulse = true;
      part = false;
      lastServo = micros();
    }
    else{  //wait till servo signal time (values from the ppm array) has passed
      if(micros() - lastServo >= ppm[counter]){
        counter++;  //do the next channel
        part = true;
      }
    }
  }

  if(pulse){
    if(pulseStart == true){  //start the pulse
      digitalWrite(sigPin, onState);
      pulseStart = false;
      lastPulse = micros();
    }
    else{  //will wait till PPM_PulseLen has passed and finish the pulse
      if(micros() - lastPulse >= PPM_PulseLen){
        digitalWrite(sigPin, !onState);
        pulse = false;
        pulseStart = true;
      }
    }
  }
}
I really don't know how to port all of this to ESP32.
I'm trying to merge the first sketch with a timer interrupt sketch for esp32

Code: Select all

volatile int interruptCounter;
int totalInterruptCounter;

hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  interruptCounter++;
  portEXIT_CRITICAL_ISR(&timerMux);

}

void setup() {

  Serial.begin(115200);

  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  //  timerAlarmWrite(timer, 1000000, true);
  timerAlarmWrite(timer,   22000, true); PER IL PPM ?? ??

  timerAlarmEnable(timer);

}

void loop() {

  if (interruptCounter > 0) {

    portENTER_CRITICAL(&timerMux);
    interruptCounter--;
    portEXIT_CRITICAL(&timerMux);

    totalInterruptCounter++;

    Serial.print("An interrupt as occurred. Total number: ");
    Serial.println(totalInterruptCounter);

  }
}
I'm very confused....

idahowalker
Posts: 131
Joined: Wed Aug 01, 2018 12:06 pm

Re: generate PPM

Postby idahowalker » Sun Oct 20, 2019 1:45 am

ESP32 has a hardware timer that you can use. Here is a link for the hardware timer that may interest you.
https://docs.espressif.com/projects/esp ... troduction

Who is online

Users browsing this forum: No registered users and 15 guests