Esp8266 servo software control

I need my ESP8266 to control a servo while using a DS18B20. The 1-Wire protocol used by DS18B20 occasionally disables interrupts, which causes servo jitter.

The Playground SoftwareServo library works with AVRs but doesn't work with the ESP8266.

I wrote my own "softwareServo" program, below, using the ESP8266's "ccount" register for timing. That register is incremented with every clock tick.

The code works (servo goes to the correct angles), but it has some jitter, too.

Any ideas on how to improve this code, or things to test? (I don't have a way of measuring the pulse timing.)

/*
  esp8266 & servo, using software control
  It works, but there is some jitter.  
*/

uint32_t currentCount, startPulseCount, angleCount, previousTime;
const uint32_t twentyCount = 1600000;   // 20x10^-3 x 80x10^6 counts   20.00 mS = 20000 uS
const uint32_t minCount = 68000;        // 0.85x10^-3 x 80x10^6 counts  0.85 mS =   850 uS
const uint32_t maxCount = 188000;       // 2.35x10^-3 x 80x10^6 counts  2.35 mS =  2350 uS

uint8_t myAng[4] = {18, 31, 135, 150}; // test servo angles
uint8_t myAngle, i;

bool pinIsHigh = false;

const byte pinForServo = 0; // was 16, but can't use GPOS or GPOC with 16.  FWIW, setting 0 low turns on red LED.

static inline int32_t asm_ccount(void) { // gets contents of reg that is updated w/ every clock tick...80x10^6 ticks/s (if crystal is accurate)
  int32_t r;                             // this asm function takes one count, vs ESP.getCycleCount() which takes 4 or 5
  asm volatile ("rsr %0, ccount" : "=r"(r));
  return r;
}

void setup() {
  pinMode(pinForServo, OUTPUT);
}

void loop() {
  //currentTime = millis();  // takes about 7 uS

  if (millis() - previousTime >= 5000) { // change servo angle, for demo purposes
    previousTime += 5000;
    myAngle = myAng[i % 4]; // degrees
    i++;
    angleCount = minCount + myAngle * (maxCount - minCount) / 180;
  }

  currentCount = asm_ccount(); // takes one tick to execute, so should subtract one tick, or 1/80 uS... neglect
  static uint32_t previous20count = currentCount + twentyCount; // just for 1st time thru
  
  // set servo pin high every 20 ms (50 Hz)    
  if (currentCount - previous20count  >= twentyCount) { 
    GPOS = (1 << pinForServo); // set pin high; takes 0.06 uS vs 1.5 uS for digitalWrite (fun, but hardly worth doing...)
    previous20count += twentyCount;
    startPulseCount = currentCount;
    pinIsHigh = true;
  }

 // check to see if pulse time is over; if so, set pin low
  if (pinIsHigh and currentCount - startPulseCount >= angleCount) { 
    GPOC = (1 << pinForServo); // clear pin (set low) after appropriate pulse width
    pinIsHigh = false;
  }

} // end of loop

Another solution would be to use a stand-alone servo controller like Adafruit 16-Channel 12-bit PWM/Servo Driver - I2C interface [PCA9685] : ID 815 : $14.95 : Adafruit Industries, Unique & fun DIY electronics and kits

Overkill for one servo. Oh well.

Or just put up with the jitter.

The simplest solution is to simple send the pulse in a blocking way, holding the processor for the length of it, and ditch the whole timer solution.

but that doesn't include any of the other code.

Ok, thanks, will give that a shot.

Yep. One step at a time. If I can't get this simple approach to work, there's no point in adding code for the DS18B20, fan motor control, OTA, web server, and Thingspeak.

FWIW, even the non-blocking code for the DS18B20 (in the poorly named WaitForConversion2 example), reading one sensor takes 14 ms, a large chunk of the available time between 20 ms pulses.

That 20ms pulse frequency is really quite relaxed, The minimum is 20ms, but i have had no issues with pulses up to 50ms interval, and even 100ms was still ok. Try it out, make sure you send at least a pulse between sensor readings, and maybe also between webserver handles and sensor readings.

No but it does mean that any advice i may give initially could be of lesser quality because i lack information.
If i do not know how the sensors are implemented, i have to guess. I assumed the webserver, but i had not thought about a fan motor.
I think in this case the advice is still correct, really the pulse interval is quite relaxed, and i would assume the webserver and wifi connection to see if that would cause any interference, as i am sure it probably does on an interrupt based solution, It shouldn't really. Connecting to an STA is the ultimate test.

You could give this library a try

Or this library

best regards Stefan

Thank you. I had no idea the pulse frequency could be so low. I changed the guts to this, and it works fine, with no jitter, for pulse durations from 20 ms to 100 ms, like you said.

  if (currentTime - prevPulseTime >= pulseDuration) {
    GPOS = (1 << pinForServo); //set pin high; takes 0.06 us or so, vs 1.5 us for digitalWrite (hardly worth doing...but fun!)
    prevPulseTime += pulseDuration;
    delayMicroseconds(angleUS); // not rec for delays > 20 ms, since doesn't yield; no problem here; max delay is 2.5 ms
    GPOC = (1 << pinForServo);  // clear pin (set low) after appropriate pulse width   
  }

True, but most people here are first interested in an MRE. Nonetheless, I appreciate your enthusiasm for starting with the whole enchilada. I may be back... :slight_smile:

I just discovered that I only need to send a single pulse to set or change the servo angle, and then I can wait as long as desired before sending another pulse to change the angle.

My highly calibrated fingers :slight_smile: detect that with the control line held LOW, the servo will not rotate even when subject to "significant" torque (I suppose due to friction in the gearing).

My Hitec D85MG (a "digital" type) is subject to very little torque while in operation, so merely calling the following when an angle change is needed (which is rather infrequently) will be sufficient. No need to call at regular intervals.

    digitalWrite(pinForServo, HIGH);
    delayMicroseconds(angleInMicroSecs);
    digitalWrite(pinForServo, LOW);

Servo gurus would tell you to call it regularly, and there isn't any harm in doing that.

This is wrong.
What you only do a single time in the code

when using a servo-library

is setting a servo-position.

The library itself will repeat to send the pulses with a constant pulse-length

If you stop feeding in a pulse-train into the servo it might be that it stays at momentary position but just from the friction inside the gear.

Well the technical specs of the Hitec D85MG-Servo say 4,3 kgcm at 6V
which is pretty strong for a 13mm RC-servo. (this is reached through a higher gear-ratio.

Of course for much larger servos the torque can go up to 20 kgcm 50 kgcm or even 150 kgcm.

So I ask what did this "operation" look like?
If you feed-in a valid RC-Servo-Signal into a 4,3 kgcm you will need a lot of force in your hand to turn a 3cm servo-horn away from the commaned position.

Of course if you have 2 kg on a lever 20 cm long this means a torque of 2kg x 20 cm = 40 kg which will make the servo turn but will overheat the motor and damage the gear pretty quick.

But this would be a totally inappropriate application for a 4,3 kgcm servo

best regards Stefan

Not "might be"...in fact it does stay at the last set position (due to friction). Which is what I said. And the friction is more than sufficient for my purpose.

Not sure what you are asking.

Not feeding in a valid servo-signal is an undefined state for the servo.
Very good if it stays at the momentary position but it might be that with electromagnetic noise the servo will turn to a random position.

I quoted your text

and I would like to know what exactly do you mean if you write

can you please replace the word "operation" with a multi-sentence detailed description what your servo is really doing .

  • Is your servo waving air with a standard-servohorn ?
  • Does your servo just make some noise with nothing mechanically connected to it?
  • does your servo move a little pin mounted on the servohorn in a gauge?

best regards Stefan

This assertion is not always true, and it indicates that you are unaware of a salient fact (which I recently learned): some digital servos, including mine, and perhaps many if not all, are designed to hold their last position when the control signal is lost. [*]

In another test, I verified that with the power off, I can turn the servo, with difficulty (and, BTW, that resistance would be perfectly adequate for my purposes). But with the power on, after sending just a single pulse, the servo fights me, strongly, to hold position. I can feel it and hear it doing so.

Of course, just because you can do something, it doesn't mean you should, and you have a possibly valid point about noise: in an electrically noisy environment, with a long unshielded cable between the servo and controller, some random pulse may trigger the servo, even though the controller is holding the control line low.

[*] and, FWIW, some digital servos, including mine, can be programmed to go to a particular position upon loss of signal (aka as the "fail-safe" position), or to go "limp," i.e., act as if they've lost power, and not hold position, except by friction.