Problem with BLE on Nano 33 BLE when using 12 Servos

I hope someone can shed some light on a problem I am having with my quadruped robot. It is controlled by a Nano 33 BLE which drives 12 MG995 servos. Power is from a large LiPo battery nicely regulated, and separated servo power (in two banks of 6) and power to the Nano 33.

I wrote the quadruped code using <Servo.h> as the servo driver software, and it all works pretty well. The robot stands and moves etc.

Then I wrote the software for the BLE to create a communications interface so I could send control commands and also read the status of the robot by setting and reading various characteristics. The BLE software was written separately using <ArduinoBLE.h >and tested with the Lightblue app on my smartphone, and it was all fine. However, when I integrated the BLE software with the rest of the quadruped software, the BLE never showed up on the app. The BLE part had somehow stopped working.

I thought that there might be a few reasons:

1 Power: I eliminated this problem by cutting the power to the servos to eliminate any power surges. It wasn't that. Still no BLE.

2 RF interference: all those wires radiating square waves at 50hZ. I removed the wiring to the servos, and still no luck.

3 Memory etc., but the total usage is 32% and 27% of flash and ram, so that's doubtful as a problem.

Finally, I fixed on the idea that the servo software was somehow messing around with the BLE software, possibly through various interrupts. So, I ran all the software with servos and BLE combined but inhibited the Sevo.attach(PIN) function - I simply didn't attach any pins to the servo software. The BLE worked, but the quadruped was a bit inert! So, I then started to gradually include some Servo.attach(PIN) functions. I managed to attach 8 out of 12 and had those 8 servos and BLE functioning with all the cables attached and the power on and legs moving (apart from the 4 unattached servos). When I increased it to 9 and beyond, it failed.

So, it would appear that somehow, the servo interrupt handler becomes a bit too loaded for the BLE to function. Can anyone shed any light on what might be happening? I confess I don't really know how the BLE module functions, nor its interrupt handling requirements. I do know that a quadruped 4 servos short doesn't function too well!

Many thanks, and this is my first post so many thanks for a great forum.

Welcome to the forum.

Can you share your code? Please use code tags. Click the </> icon.

BLE works at 2.4GHz and sends very small packets.

Everything is running on the same processor. So, there is a good chance to interfere with the radio. In general you should write your code so loop runs as often as possible, no delay or waiting for stuff to happen ... then you should be fine.

What are you doing in that handler and how often does it run? Short interrupts should be fine but spending a lot of time in there is generally not a good idea.

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

Glad you got it working. Well done.

Just remember writing to I/Os in interrupt service routines can be a dangerous game. Writes to the same port in the main code can overwrite your writes in the ISR. With the Arduino pin naming you may not realize that this is the case when it happens.