Dotstar's "show" function causing servo jitter - Page 1
Forum section: electronics / projects

author: Youkai
author_rank: Regular Contributor
timestamp: October 28, 2023, 04:49:18 am
content: I'm working on a project where I'm using a number of dotstar's in a strip (84) and 6 servo's. The MC is a Sparkfun ProMicro 5v.It seems the main cause of the jitter is strip.show() function of the dotstar's. If I comment that out the jitter almost entirely disappears. If I let the function run, the jitter happens multiple times a second. An interesting note is that the jitter only happens on the 6'th servo; i.e. the one using "servo.attach(XXX)" last in the code. For the purposes of my testing I only have a servo plugged in to servo 3 and 6 at the moment but I tested all servo wires and only 6 demonstrates the jitter.In a previous project I was having a similar issue with neopixels and servos. Someone suggested for that project that I use the following code to make sure that the neopixels weren't disabling interrupts during the servo signal time. There's more stuff in the original thread. Here's alink to the commentif you want to see that.Code:#define usToTicks(_us) (( clockCyclesPerMicrosecond()* _us) / 8)if (TCNT1 > usToTicks(8192)) {strip.show();};That doesn't seem to be working here though. Any ideas what might be causing this? Is controlling 6 servo's too many? Maybe I need to adjust the amount of time in usToTicks()? Or perhaps TCNT1 is named something different on the ProMicro than in Arduino Uno?

author: tooki
author_rank: Super Contributor
timestamp: October 28, 2023, 01:09:21 pm
content: There are many libraries for DotStar-type LEDs. Perhaps one of those gets along better with your servo interrupts than whatever library you’re using now.In the other thread I see you also discovered alternative servo libraries. It seems to me you first need to provide a current schematic, as well as the source code. Otherwise we’re all just guessing at best.

author: Youkai
author_rank: Regular Contributor
timestamp: October 28, 2023, 04:42:30 pm
content: Sure Tooki. Here you go. Schematic attached and the source code I'm using is:Code:#include <Arduino.h>#include <Servo.h>#include <Adafruit_DotStar.h>#include <SPI.h>#include <DFPlayerMini_Fast.h>#include <YaTimer.h>#include <SoftwareSerial.h>SoftwareSerial mySerial(5, 9); // RX, TX - ProMicro - only the following can be used for RX: 8, 9, 10, 11, 14 (MISO), 15 (SCK), 16 (MOSI).//servo stuff#define ICE_PIN 0#define WAVE_1_PIN 2#define WAVE_2_PIN 3#define PLASMA_1_PIN 4#define PLASMA_2_PIN 7#define MISSILE_PIN 8Servo iceServo;Servo wave1Servo;Servo wave2Servo;Servo plasma1Servo;Servo plasma2Servo;Servo missileServo;int iceClosedPos = 130;int iceOpenPos = 80;int waveClosedPos = 130;int waveOpenPos = 40;int plasmaClosedPos = 50;int plasmaOpenPos = 160;int missileClosedPos = 120;int missileOpenPos = 30;//button stuff#define BUTTON_POWER_PIN 6#define BUTTON_WAVE_PIN 10#define BUTTON_ICE_PIN 14#define BUTTON_PLASMA_PIN 18#define BUTTON_PHAZON_PIN 19#define BUTTON_TRIGGER_PIN 20#define BUTTON_MISSILE_PIN 21int activeBeam = 1;boolean stateNeedsToBeUpdated = false;boolean triggerActive = false;boolean triggerWasActive = false;boolean chargeStarted = false;boolean missileActive = false;boolean missileWasActive = false;//LED stuff#define NUMPIXELS 83#define DATAPIN 16 // MOSI - Green#define CLOCKPIN 15 // SCK - YellowAdafruit_DotStar strip(NUMPIXELS, DATAPIN, CLOCKPIN, DOTSTAR_BGR);//NOTE: use a HSV color wheel that starts at 0 = red and goes through yellow first//pick your color (value from 0 - 360 and multiply that by 182 to get the corresponding color value for DotStaruint32_t currentLedColor = 0x000000;uint32_t colorOff = 0x000000;int colorIdPower = 10788;int colorIdIce = 33100;int colorIdWave = 52080;int colorIdPlasma = 0;int colorIdPhazon = 41850;int colorIdleValue = 50;int colorFireValue = 100;int colorCurrentValue = 50;int colorVariationMax = 50;int colorVariationStep = 5;boolean colorValueDescending = true;int colorInitialValue = 50;boolean colorInitialValueDescending = true;int colorStepDelayIdle = 30;int colorStepDelayCharging = 1;int fireLedIntensityValue = 0;DFPlayerMini_Fast speaker;YaTimer triggerTimer = YaTimer(200);YaTimer speakerTimer = YaTimer(50);YaTimer ledTimer = YaTimer(30);YaTimer missileTimer = YaTimer(1500);void setup(){Serial.begin(115200);mySerial.begin(9600);speaker.begin(mySerial, false);iceServo.attach(ICE_PIN);iceServo.write(iceClosedPos);wave1Servo.attach(WAVE_1_PIN);wave1Servo.write(waveClosedPos);wave2Servo.attach(WAVE_2_PIN);wave2Servo.write(waveClosedPos);plasma1Servo.attach(PLASMA_1_PIN);plasma1Servo.write(plasmaClosedPos);plasma2Servo.attach(PLASMA_2_PIN);plasma2Servo.write(plasmaClosedPos);missileServo.attach(MISSILE_PIN);missileServo.write(missileClosedPos);pinMode(BUTTON_POWER_PIN, INPUT_PULLUP);pinMode(BUTTON_WAVE_PIN, INPUT_PULLUP);pinMode(BUTTON_ICE_PIN, INPUT_PULLUP);pinMode(BUTTON_PLASMA_PIN, INPUT_PULLUP);pinMode(BUTTON_PHAZON_PIN, INPUT_PULLUP);pinMode(BUTTON_TRIGGER_PIN, INPUT_PULLUP);pinMode(BUTTON_MISSILE_PIN, INPUT_PULLUP);strip.begin(); // Initialize pins for outputstrip.show(); // Turn all LEDs off ASAPtriggerTimer.start();speakerTimer.start();ledTimer.start();missileTimer.start();speaker.volume(30);stateNeedsToBeUpdated = true;updateState();}void loop(){checkForNewBeamButtonPress();updateState();triggerActions();missileActions();}void checkForNewBeamButtonPress() {//NOTE: inputs are pullup so the are pressed when they read low/false.if (!digitalRead(BUTTON_POWER_PIN) && activeBeam != 1) {activeBeam = 1;stateNeedsToBeUpdated = true;return;}if (!digitalRead(BUTTON_WAVE_PIN) && activeBeam != 2) {activeBeam = 2;stateNeedsToBeUpdated = true;return;}if (!digitalRead(BUTTON_ICE_PIN) && activeBeam != 3) {activeBeam = 3;stateNeedsToBeUpdated = true;return;}if (!digitalRead(BUTTON_PLASMA_PIN) && activeBeam != 4) {activeBeam = 4;stateNeedsToBeUpdated = true;return;}if (!digitalRead(BUTTON_PHAZON_PIN) && activeBeam != 5) {activeBeam = 5;stateNeedsToBeUpdated = true;return;}}void updateState() {setLeds();if (stateNeedsToBeUpdated == false) {return;}setBeams();stateNeedsToBeUpdated = false;}void setBeams() {iceServo.write(iceClosedPos);wave1Servo.write(waveClosedPos);wave2Servo.write(waveClosedPos);plasma1Servo.write(plasmaClosedPos);plasma2Servo.write(plasmaClosedPos);if (activeBeam == 2) {wave1Servo.write(waveOpenPos);wave2Servo.write(waveOpenPos);}if (activeBeam == 3) {iceServo.write(iceOpenPos);}if (activeBeam == 4) {plasma1Servo.write(plasmaOpenPos);plasma2Servo.write(plasmaOpenPos);}if (activeBeam == 5) {iceServo.write(iceOpenPos);wave1Servo.write(waveOpenPos);wave2Servo.write(waveOpenPos);plasma1Servo.write(plasmaOpenPos);plasma2Servo.write(plasmaOpenPos);}}void setLeds() {stepInitialValue();strip.fill(colorOff, 0, 53);if (activeBeam == 1) {for (int i = 0; i < 13; i++) {stepColorValue();currentLedColor = strip.ColorHSV(colorIdPower, 255, colorCurrentValue);strip.setPixelColor(16 + 12 - i, currentLedColor);strip.setPixelColor(40 + 12 - i, currentLedColor);}}if (activeBeam == 2) {for (int i = 0; i < 8; i++) {stepColorValue();currentLedColor = strip.ColorHSV(colorIdWave, 255, colorCurrentValue);strip.setPixelColor(0 + i, currentLedColor);strip.setPixelColor(8 + 7 - i, currentLedColor);}}if (activeBeam == 3) {for (int i = 0; i < 11; i++) {stepColorValue();currentLedColor = strip.ColorHSV(colorIdIce, 70, colorCurrentValue);strip.setPixelColor(29 + i, currentLedColor);}}if (activeBeam == 4) {for (int i = 0; i < 13; i++) {stepColorValue();currentLedColor = strip.ColorHSV(colorIdPlasma, 255, colorCurrentValue);strip.setPixelColor(16 + 12 - i, currentLedColor);strip.setPixelColor(40 + 12 - i, currentLedColor);}}if (activeBeam == 5) {for (int i = 0; i < 53; i++) {stepColorValue();currentLedColor = strip.ColorHSV(colorIdPhazon, 255, colorCurrentValue);strip.setPixelColor(0 + i, currentLedColor);}}if (fireLedIntensityValue > 0) {fireLedIntensityValue -= colorVariationStep;if (fireLedIntensityValue < 0) {fireLedIntensityValue = 0;}if (activeBeam == 1) {currentLedColor = strip.ColorHSV(colorIdPower, 255, fireLedIntensityValue);}if (activeBeam == 2) {currentLedColor = strip.ColorHSV(colorIdWave, 255, fireLedIntensityValue);}if (activeBeam == 3) {currentLedColor = strip.ColorHSV(colorIdIce, 70, fireLedIntensityValue);}if (activeBeam == 4) {currentLedColor = strip.ColorHSV(colorIdPlasma, 255, fireLedIntensityValue);}if (activeBeam == 5) {currentLedColor = strip.ColorHSV(colorIdPhazon, 255, fireLedIntensityValue);}strip.fill(currentLedColor, 54, 83);}strip.show();}void stepInitialValue() {boolean stepInitialValue = true;if (!triggerTimer.timeOver()) {stepInitialValue = false;}if (!chargeStarted && !ledTimer.elapsedCheck(colorStepDelayIdle)) {stepInitialValue = false;}if (chargeStarted && !ledTimer.elapsedCheck(colorStepDelayCharging)) {stepInitialValue = false;}if (stepInitialValue) {ledTimer.start();if (colorInitialValue <= colorIdleValue - colorVariationMax) {colorInitialValueDescending = false;}if (colorInitialValue >= colorIdleValue) {colorInitialValueDescending = true;}if (colorInitialValueDescending) {colorInitialValue -= colorVariationStep;} else {colorInitialValue += colorVariationStep;}}colorCurrentValue = colorInitialValue;colorValueDescending = colorInitialValueDescending;}void stepColorValue() {if (colorCurrentValue <= colorIdleValue - colorVariationMax) {colorValueDescending = false;}if (colorCurrentValue >= colorIdleValue) {colorValueDescending = true;}if (colorValueDescending) {colorCurrentValue -= colorVariationStep;} else {colorCurrentValue += colorVariationStep;}}void triggerActions() {triggerActive = !digitalRead(BUTTON_TRIGGER_PIN);if (triggerActive && !triggerWasActive) {colorInitialValue = colorFireValue;colorInitialValueDescending = true;fireLedIntensityValue = colorFireValue;playFireSound();speakerTimer.start();chargeStarted = false;triggerWasActive = true;triggerTimer.start();}if (triggerActive && !chargeStarted) {if (speakerTimer.elapsedCheck(1000)) {playChargeSound();chargeStarted = true;triggerTimer.start();}}if (chargeStarted && !triggerActive && triggerWasActive) {colorInitialValue = colorFireValue;colorInitialValueDescending = true;playChargedFireSound();chargeStarted = false;triggerTimer.start();}if (!triggerActive && triggerTimer.elapsedCheck(220)) {triggerWasActive = false;}}void playFireSound() {switch (activeBeam) {case 1:speaker.play(4);break;case 2:speaker.play(10);break;case 3:speaker.play(7);break;case 4:speaker.play(13);break;case 5:speaker.play(16);break;}}void playChargeSound() {switch (activeBeam) {case 1:speaker.play(5);break;case 2:speaker.play(11);break;case 3:speaker.play(8);break;case 4:speaker.play(14);break;case 5:speaker.play(17);break;}}void playChargedFireSound() {switch (activeBeam) {case 1:if (speakerTimer.elapsedCheck(2500)) {speaker.play(6);} else {speaker.play(4);}break;case 2:if (speakerTimer.elapsedCheck(2200)) {speaker.play(12);} else {speaker.play(10);}break;case 3:if (speakerTimer.elapsedCheck(2200)) {speaker.play(9);} else {speaker.play(7);}break;case 4:if (speakerTimer.elapsedCheck(2200)) {speaker.play(15);} else {speaker.play(13);}break;case 5:speaker.play(16);break;}}void missileActions() {missileActive = !digitalRead(BUTTON_MISSILE_PIN);if (missileActive && !missileWasActive) {//speaker.play(16);missileWasActive = true;missileTimer.start();missileServo.write(missileOpenPos);}if (!missileActive && missileTimer.elapsedCheck(220)) {missileWasActive = false;}if (missileTimer.timeOver()) {missileServo.write(missileClosedPos);}}There's a lot there. If I have time later I'll do some testing and strip it down to the minimum that still generates the jitter. For now if you comment out line 268 that will stop the jitter.

author: Ian.M
author_rank: Super Contributor
timestamp: October 28, 2023, 06:33:27 pm
content: You are bit-banging the 'Dotstar' LEDs (line 57):Code:Adafruit_DotStar strip(NUMPIXELS, DATAPIN, CLOCKPIN, DOTSTAR_BGR);See this page:https://learn.adafruit.com/adafruit-dotstar-leds/arduino-library-useand search it forSPI-capable. Try:Code:Adafruit_DotStar strip(NUMPIXELS, DOTSTAR_BGR);which should use hardware SPI (Yes, I've checked you have them connected to the SCLK and MOSI pins), which (unless the library is pants-on-head stupid) is unlikely to block interrupts for long, if at all.If you were waiting for the gap in the servo frame, as you now have six servos, it takes about 14400 us worst case to send all their servo pulses, so you'd need to increase the usToTicks parameter in the TCNT1 check conditional accordingly. Try 15000.Another potential source of trouble is using softwareSerial, as that's also trying to do interrupt driven pin-bashing. It may be better to move the ice servo to a different pin and use the hardware serial port on Pro Micro pins 0 and 1 for the DFplayer mini control interface. On Atmega32U4 based Arduinos that's theSerial1class (Serialis the USB virtual UART link to the host).

author: Youkai
author_rank: Regular Contributor
timestamp: October 28, 2023, 08:53:46 pm
content: Quote from: Ian.M on October 28, 2023, 06:33:27 pmI'll try the other stuff. When I was setting up my tests for the DFPlayer I couldn't for the life of me get them to work on the hardware serial. Though that may have been user error. Either way I've already soldered the circuit together so if it's possible to solve without pulling bits of it apart that would be preferred.

author: Youkai
author_rank: Regular Contributor
timestamp: October 28, 2023, 09:12:24 pm
content: Thank you Ian.M. The SPI change fixed it entirely. I remember when I was making the diagram seeing something about the MOSI and SCK pins for the dotstar so I set them up specifically on those pins. I guess I missed the part about initializing the strip so it uses that. One tiny change fixed the whole thing. You're my hero!

author: tooki
author_rank: Super Contributor
timestamp: October 30, 2023, 08:01:52 am
content: Nice! It’s a bit of a pity the Adafruit Dotstar library neither auto-detects if hardware SPI can be used on the selected pins, nor uses an explicit flag to make it clear whether you’re bit-banging or not.Regardless, glad it’s working now!