Problem with BLE on Nano 33 BLE when using 12 Servos

Thanks for your comments. Happy to post the code, but it's about 1000 lines in multiple files, so will have to create a link to it.

Your comment focused me on the servo driving software. <Servo.h> was the problem for sure. I measured the interrupt service times in the main loop eliminating all the other interrupts I could and this one had some long service times (over 10mS). I wrote my own interrupt-driven servo driver and everything now works (interrupt service times for the servos are now 5-7 uS occurring 24 times per 20mS servo cycle - an overhead of only 168uS per 20mS, below 1%.

The driver uses the excellent NRF52_MBED_TimerInterrupt from Khoih Hoang and two timers (6 servos on each timer). It has improved the stability of the drive signals to the servos as a bonus. I suspect that <Servo.h> just struggled to cope with 12 servos. The code is here.

/*
  Spider servo handler: uses the NRF5_MBED Timer Interrupt software written by Khoi 
  Hoang and used under the MIT license.  Excellent software BTW.
  
  Two timers are used to service the 12 servos in two groups of 6
  
  Each timer interrupt services a servo twice: once to turn on for the pulse time
  and once to turn off with a subseqent delay of 3330uS less the on time, thus each
  servo takes 3330uS to service;  x6 is the 20mS loop time.

  a servo is activated by calling the activate function with pin and servo numbers 
  the output is set to LOW and no pulse is yet generated.  when a pulse time
  is written to the servo the pulse is activated.  deactivating a servo will bring
  the line to LOW and inhibits further pulses, but the line is still set as an output.

  the ISR's take about 7uS to service an interrupt, total overhead 168uS/20mS (1%)

  if you want to use fewer than 7 servos it is possible to remove a timer by undefining 
  TWOTIMERS, however to get the timing right the ISR's still have to cycle through 6 
  servos even if some are inactive.
  
*/
//==================================================================================
//configuration defines

#define TWOTIMERS
#define SERVOTOTALTIME 3330         //6 servos per cycle x 3330 = ~20mS 
#define SERVOMIDPOINT 1500
#define TIMERBANK1     NRF_TIMER_3  //set these two to the timers to be used
#define TIMERBANK2     NRF_TIMER_4

//convenience
#define DISABLED 0
#define ENABLED -1

//==================================================================================
//includes
#include "NRF52_MBED_TimerInterrupt.h"
#include "NRF52_MBED_ISR_Timer.h"

//instantiate one/two timers - one for each bank of 6 servos
NRF52_MBED_Timer ITimer1(TIMERBANK1);
#ifdef TWOTIMERS
NRF52_MBED_Timer ITimer2(TIMERBANK2);
#endif

//==================================================================================
//the servo management arrays
int servoontimes[12];  
int servoofftimes[12]; 
int servopins[12];
int servoactive[12];      //had to use int's here; bool failed on pins 12 and above

//ISR variables (note volatile)
volatile int servoindex1 = 0;
volatile bool flag1 = false;
volatile int servoindex2 = 6;
volatile bool flag2 = false;

//----------------------------------------------------------------------------------
//interrupt service routines 
/*  
each routine handles 6 servos: ISR1 handles 0-5 and ISR2 handles 6-11
each servo requires 2 interrupts: the first will set the pin high if it is active
and set the timer for the high time, the second will set the pin low for the period
3330 less the high time.  thus, each servo requires a total of 3330uS, and 6 require
20mS. when the pin is pulled low the isr moves on to the next servo until all 6 are
serviced in a loop.  
*/

void timerISR1(){
  int pin = servopins[servoindex1];
  if (flag1){
    if (servoactive[pin]) digitalWrite(pin,LOW);
    ITimer1.setInterval(servoofftimes[servoindex1],timerISR1);
    flag1 = false;
    if (++servoindex1 == 6) servoindex1 = 0;
  }
  else{
    digitalWrite(pin,servoactive[pin]);
    ITimer1.setInterval(servoontimes[servoindex1],timerISR1);
    flag1 = true;
  }
}

#ifdef TWOTIMERS
void timerISR2(){
  int pin = servopins[servoindex2];
  if (flag2){
    if (servoactive[pin]) digitalWrite(pin,LOW);
    ITimer2.setInterval(servoofftimes[servoindex2],timerISR2);
    flag2 = false;
    if (++servoindex2 == 12) servoindex2 = 6;
  }
  else{
    digitalWrite(pin,servoactive[pin]);
    ITimer2.setInterval(servoontimes[servoindex2],timerISR2);
    flag2 = true;
  }
}
#endif
//----------------------------------------------------------------------------------
//sets up the ISR's and starts thing happening - call from Setup()
void startservohandlers(){
  for (int i=0; i<12; i++) servoactive[i] = DISABLED;
  ITimer1.attachInterruptInterval(1000, timerISR1);
#ifdef TWOTIMERS
  ITimer2.attachInterruptInterval(700, timerISR2);
#endif
}

//disables pulse production and sets the pin to LOW, pin remains an output
void deactivateservopin(int servonumber){
  digitalWrite(servopins[servonumber],LOW);
  servoactive[servonumber] = DISABLED;
  servoontimes[servonumber] = SERVOMIDPOINT;
  servoofftimes[servonumber] = SERVOTOTALTIME - servoontimes[servonumber];  
}

//activates the servopin: sets to output LOW, but does not yet permit a pulse
void activateservopin(int pin, int servonumber){
  servopins[servonumber] = pin;
  pinMode(pin,OUTPUT);
  digitalWrite(pin, LOW);  
  servoontimes[servonumber] = SERVOMIDPOINT;
  servoofftimes[servonumber] = SERVOTOTALTIME - servoontimes[servonumber];  
  servoactive[servonumber] = DISABLED;
}

//writes the pulse length to the array and enables the pulse on the pin
void writeservotime(int usec, int servonumber){
  servoontimes[servonumber] = usec;
  servoofftimes[servonumber] = SERVOTOTALTIME - usec;
  servoactive[servonumber] = ENABLED;  
}


//==================================================================================
/*
 * 
NRF52_MBED_TimerInterupt license follows:

MIT License

Copyright (c) 2019 Khoi Hoang

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
 */
//==================================================================================

On curious thing is that I tried to use bool's for the servoactive flags in the software and these would not work with the digitalWrites in the ISR's. In the end I resorted to int's that was fine.

So, all rosy. Many thanks, again.

2 Likes