SAMD21: Changing pin from PWM to OUTPUT and back again

Sparkfun SAMD21 DEV board: I am building a remote control sailboat which will autonomously control itself using PWM servo motors. During development, I would like to take control of this boat when she is misbehaving, and control the her with a standard RC (remote control) receiver. I have two similar PWM signals, one from the SAM21 (pin 2 PWM) and one from the RC receiver (pin 6 INPUT_PULLUP). I have both of these pins working independently. The PWM pin uses some code I found here, written by MartinL, (THANK YOU!) reproduced below with minor changes. The RC signal, I connected, using an input pin which causes an interrupt. In my interrupt handler, I simply change the state of an output pin to mirror the state of the input pin, one line of code (not shown). What I want to do is to route both signals, alternately, to the same pin using a flag I call "PWMfrom" to cause the transfer.

I tried changing the output pin (pin 2) to OUTPUT, and this worked, but I can't seem to get the pin changed back to PWM, without reinitializing 50 lines of start code again. I am sure there is an easier way to do this.

[code]
void PWMsetup() {
  REG_GCLK_GENDIV = GCLK_GENDIV_DIV(3) |          // Divide the 48MHz clock source by divisor 3: 48MHz/3=16MHz
                    GCLK_GENDIV_ID(4);            // Select Generic Clock (GCLK) 4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC |           // Set the duty cycle to 50/50 HIGH/LOW
                     GCLK_GENCTRL_GENEN |         // Enable GCLK4
                     GCLK_GENCTRL_SRC_DFLL48M |   // Set the 48MHz clock source
                     GCLK_GENCTRL_ID(4);          // Select GCLK4
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Enable the port multiplexer for the 4 PWM channels: timer TCC0 outputs
  const uint8_t CHANNELS = 4;
  const uint8_t pwmPins[] = { 2, 5, 10, 12 }; //formerly 2, 5, 6, 7  or 4,3,10,12
  for (uint8_t i = 0; i < CHANNELS; i++)
  {
    PORT->Group[g_APinDescription[pwmPins[i]].ulPort].PINCFG[g_APinDescription[pwmPins[i]].ulPin].bit.PMUXEN = 1;
  }
  // Connect the TCC0 timer to the port outputs - port pins are paired odd PMUO and even PMUXE
  // F & E specify the timers: TCC0, TCC1 and TCC2
  //formerly PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
  //formerly PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;
  PORT->Group[g_APinDescription[2].ulPort].PMUX[g_APinDescription[2].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;  //4 for pins 3&4 and 2 for pins 2&5
  PORT->Group[g_APinDescription[10].ulPort].PMUX[g_APinDescription[10].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F; //6 for pins 6&7 and 10 for pins 10&12

  // Feed GCLK4 to TCC0 and TCC1
  REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN |         // Enable GCLK4 to TCC0 and TCC1
                     GCLK_CLKCTRL_GEN_GCLK4 |     // Select GCLK4
                     GCLK_CLKCTRL_ID_TCC0_TCC1;   // Feed GCLK4 to TCC0 and TCC1
  while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization

  // Dual slope PWM operation: timers countinuously count up to PER register value then down 0
  REG_TCC0_WAVE |= TCC_WAVE_POL(0xF) |         // Reverse the output polarity on all TCC0 outputs
                   TCC_WAVE_WAVEGEN_DSBOTTOM;    // Setup dual slope PWM on TCC0
  while (TCC0->SYNCBUSY.bit.WAVE);               // Wait for synchronization

  // Each timer counts up to a maximum or TOP value set by the PER register,
  // this determines the frequency of the PWM operation:
  // 20000 = 50Hz, 10000 = 100Hz, 2500  = 400Hz
  REG_TCC0_PER = 20000;      // Set the frequency of the PWM on TCC0 to 50Hz
  while (TCC0->SYNCBUSY.bit.PER);

  // The CCBx register value corresponds to the pulsewidth in microseconds (us)
  REG_TCC0_CCB0 = 1500;       // TCC0 CCB0 - center the servo on D2
  while (TCC0->SYNCBUSY.bit.CCB0);
  REG_TCC0_CCB1 = 1500;       // TCC0 CCB1 - center the servo on D5
  while (TCC0->SYNCBUSY.bit.CCB1);
  REG_TCC0_CCB2 = 1500;       // TCC0 CCB2 - center the servo on D6
  while (TCC0->SYNCBUSY.bit.CCB2);
  REG_TCC0_CCB3 = 1500;       // TCC0 CCB3 - center the servo on D7
  while (TCC0->SYNCBUSY.bit.CCB3);

  // Divide the 16MHz signal by 8 giving 2MHz (0.5us) TCC0 timer tick and enable the outputs
  REG_TCC0_CTRLA |= TCC_CTRLA_PRESCALER_DIV8 |    // Divide GCLK4 by 8
                    TCC_CTRLA_ENABLE;             // Enable the TCC0 output
  while (TCC0->SYNCBUSY.bit.ENABLE);              // Wait for synchronization

}  //PWMsetup()

void PWMloop() {
  // called every second
  static int servocount = 1500;
  if (PWMfrom == CPU) { //let CPU be in control
    servocount += 100;
    if (servocount > 2000) {
      servocount = 1000;
    }
    //we might be able to use analogWrite() here instead of writing to register.
    REG_TCC0_CCB0 = servocount;       // TCC0 CCB0 - control the servo on D2
    while (TCC0->SYNCBUSY.bit.CCB0);
    REG_TCC0_CCB1 = servocount;       // TCC0 CCB1 - control the servo on D5
    while (TCC0->SYNCBUSY.bit.CCB1);
    REG_TCC0_CCB2 = servocount;       // TCC0 CCB2 - control the servo on D6
    while (TCC0->SYNCBUSY.bit.CCB2);
    REG_TCC0_CCB3 = servocount;       // TCC0 CCB3 - control the servo on D7
    while (TCC0->SYNCBUSY.bit.CCB3);
  } else { //let remote be in control
    //stop PWM
    REG_TCC0_CCB0 = 0;       // TCC0 CCB0 - control the servo on D2
    while (TCC0->SYNCBUSY.bit.CCB0);
    REG_TCC0_CCB1 = servocount;       // TCC0 CCB1 - control the servo on D5
    while (TCC0->SYNCBUSY.bit.CCB1);
    REG_TCC0_CCB2 = servocount;       // TCC0 CCB2 - control the servo on D6
    while (TCC0->SYNCBUSY.bit.CCB2);
    REG_TCC0_CCB3 = servocount;       // TCC0 CCB3 - control the servo on D7
    while (TCC0->SYNCBUSY.bit.CCB3);

  }//if PWMfrom
}

[/code]

You will see I have 4 PWM pins. I want to change them all simultaneously.

Thanks for your help.

Doug

Hi Doug,

I'm trying to build a mental picture of your RC system.

I can see that pin 2 is a PWM output

Is pin 6 an RC receiver channel input? Also, how are you switching control between the pilot and computer (or whatever the correct technical term for the Helmsman is?).

On my drone flight controller, I simply use a transmitter switch. The switch causes the flight controller to route stick control channels from the pilot to computer, or vice-versa.

Martin,

I am not sure what you mean by "flight controller". I am switching control between computer (PMW) and R/C receiver (pin 6), using the Samd21 logic I mentioned in my last post. This transfer is initiated by a transmitter switch, coming in on another pin. Frankly, I am having trouble visualizing how you are doing your transfer. Perhaps I am doing it the hard way.

Nevertheless, I have figured out the answer to my own question. To change pin 2 to echo the PCM input on pin 6 input from R/C receiver), I use:

    pinMode(2, OUTPUT); //change function of aux rudder pin to echo REMOTE

To change the function of pin 2 back to internally generated PWM, I use:

    PORT->Group[g_APinDescription[2].ulPort].PINCFG[g_APinDescription[2].ulPin].bit.PMUXEN = 1;

I have tested this and it does what I want.

It can be a bit intimidating to follow the a line of code (like above), through the layers, down to the 1100 page data book. Thanks for your help.

Doug

Hi Doug,

I mentioned the "flight controller" because all RC vehicles, whether fixed wing/drone, land based rover, or boat have a lot in common, and systems such as navigation normally work on very similar principles.

If your switching between user and computer control, it's usually not necessary to switch between GPIO and PWM. If at any point your require logic low or zero output on a particular motor or servo channel then it's possible to just load the timer's CCBx registers with 0. Likewise, if you require a logic high, just load the CCBx with the maximum or period (PER register) value.

The point I was making, is that on my drone flight controllers, I too use a transmitter switch to change modes. This simply causes the code to switch the control input from the pilot sticks to automated navigation system, or vice-versa. The output to the motors or servos remains set to PWM, since any interruption to their control, would see the aircraft drop from the sky.

Martin,

I came up with my design on my own, and I am not at all sure it is the best solution. I have two PWM sources, one originating from the RC receiver attached to a digital input port, and the other generated within the CPU. I switch these alternately to an output pin.

I still don't understand the topology of your system. Is your CPU in the drone or on the ground?

Fortunately for me, a boat is not so susceptible to glitches.

Doug

MartinL,

I read your post (401203.0) about your Falcon flight controller, and I answered the dumb question in my last post.

I changed my approach from PWM to PPM, and am now controlling everything with just one pin connected to my receiver. I do have a followup question to my original post:

//PWM code  
// Enable the port multiplexer for the 4 (3 in this case) PWM channels: timer TCC0 outputs
  const uint8_t CHANNELS = 3;  //formerly 4
  const uint8_t pwmPins[] = { 2, 5, 6}; //formerly 2,5,6,7
  for (uint8_t i = 0; i < CHANNELS; i++)
  {
    PORT->Group[g_APinDescription[pwmPins[i]].ulPort].PINCFG[g_APinDescription[pwmPins[i]].ulPin].bit.PMUXEN = 1;
  }
//interrupt handler code
  pinMode(7, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(7), Relay_1_ISR, FALLING);  //FALLING
}
void Relay_1_ISR() {
  return;
}

In my code example (written mostly by you) in my first post, I have these lines ( have changed the ports). I only need 3 PWM outputs, and in the interest of saving pins, I tried to remove pin 7, so I could reuse it. I tried using it for a pin activated interrupt handler, and voila! the PWM pulse came back. I suppose I could try using it for something else, but I am now afraid of what it might do. Your comments?

Doug

Hi Doug,

I think I know what's happening. Are you running the interrupt code before the PWM?

I believe the line that's causing the trouble, is actually the one below the ones shown:

PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;

Just replace it with this line:

PORT->Group[g_APinDescription[6].ulPort].PMUX[g_APinDescription[6].ulPin >> 1].reg |= PORT_PMUX_PMUXE_F;

What's going on, is that the attachInterrupt() function correctly sets up digital pin D7. However, on the SAMD21 D6 (port pin PA20) and D7 (PA21) both share the same PMUX (peripheral multiplexer) register. The call to this register by the PWM portion of the code is resetting the peripheral multiplexer switch for D7 from the interrupt input (at position A), back to the timer output (at position F).

The replacement line of code only switches D6 to a timer output, leaving D7 unchanged as an interrupt input.

MartinL

Martin,

Yes I am running the interrupt code first. I tried your modification and it worked.

Don't know why you have time to lurk on this forum as well as doing whatever else you do, but I appreciate it. Thanks!

Doug

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.