Servo motor gpiod guide

I was struggling to control a servo motor (TS90M) with gpiod. But I finally got it working.
Now time to help others with the same struggle even tho it is straightforward to some people. I was not one of those...

Let me carry you through my errors...
It started with the correct node
I used

instead of

To get the node:

npm install node-red-node-pi-gpiod

The pwm signal from the first link was horrible for the servo.
The second link GPIOD was necessary.

From this point I used a few commands. I am not sure which are abundant -but it works the way I did it- (maybe someone else could tell me which commands are and are not useful)

sudo apt install pigpio

Now start running deamon when the pi boots:

sudo systemctl enable pigpiod
sudo systemctl start pigpiod

To check if it is running:

sudo systemctl status pigpiod

Then open the GPIOD node and select GPIO17 which is pin 11

AND VOILA!

ANOTHER ERROR
image
some "econnrefused" nonsense. Had no idea what it meant. (I still have no idea..)

I solved this with raspi-config settings

sudo raspi-config

interface options

Remote gpio

"Yes"

sudo reboot

The green box and the OK.....Best feeling ever
image


updated the code for my servo with a delay function.
image

flow with the delay function:

[{"id":"e7d5405eb7a85912","type":"inject","z":"6d52a149a8bfc7fa","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"","payload":"50","payloadType":"num","x":3490,"y":220,"wires":[["df0aef8119bd78aa"]]},{"id":"ef69bbfccb9893bc","type":"inject","z":"6d52a149a8bfc7fa","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"2","topic":"","payload":"100","payloadType":"num","x":3490,"y":260,"wires":[["df0aef8119bd78aa"]]},{"id":"a669c728c5e2a578","type":"pi-gpiod out","z":"6d52a149a8bfc7fa","name":"","host":"","port":"8888","pin":"23","set":"","level":"0","out":"ser","sermin":"500","sermax":"2500","freq":"800","x":3780,"y":240,"wires":[]},{"id":"15645002d9b760e2","type":"inject","z":"6d52a149a8bfc7fa","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"0.1","topic":"","payload":"0","payloadType":"num","x":3490,"y":180,"wires":[["df0aef8119bd78aa"]]},{"id":"df0aef8119bd78aa","type":"function","z":"6d52a149a8bfc7fa","name":"","func":"// Limits the slew rate incoming payload values\n// optionally sending intermediate values at specified rate\nlet maxRate = 1000;         // max slew rate units/minute\nlet sendIntermediates = true;  // whether to send intermediate values\nlet period = 0.1;              // period in millisecs to send new values (if sendIntermediates) smooth or cluncky low smooth high value cluncky\nlet jumpThreshold = 101;       // if the step asked for is more that this then goes immediately to that value\n\nvar newValue = Number(msg.payload);\nvar timer = context.get('timer') || 0;\n// check the value is  a number\nif (!isNaN(newValue) && isFinite(newValue)) {\n  var target = msg.payload;\n  context.set('target', target);\n  // set last value to new one if first time through\n  var lastValue = context.get('lastValue');\n  if (typeof lastValue == \"undefined\" || lastValue === null) {\n    lastValue = newValue;\n    context.set('lastValue', newValue);\n  }\n  // calc new value\n  msg.payload = calcOutput();\n  // stop the timer\n  if (timer) {\n    clearTimeout(timer);\n    context.set('timer', null);\n  }\n  // restart it if required to send intermediate values\n  if (sendIntermediates) {\n    timer = setInterval(\n      function() {\n        // the timer has run down calculate next value and send it\n        var newValue = calcOutput();\n        if (newValue != context.get('lastValueSent')) {\n          context.set('lastValueSent', newValue);\n          node.send({ payload: newValue });\n        }\n      },\n      period);\n    context.set('timer', timer);\n  }\n  context.set('lastValueSent', msg.payload);\n} else {\n  // payload is not a number so ignore it\n  // also stop the timer as we don't know what to send any more\n  if (timer) {\n    clearTimeout(timer);\n    context.set('timer', null);\n  }\n  msg = null;\n}\nreturn msg;\n\n// determines the required output value\nfunction calcOutput() {\n  var lastValue = context.get('lastValue');\n  var target = context.get('target');\n  // set to current value if first time through or step > threshold\n  if (typeof lastValue == \"undefined\" || lastValue === null) lastValue = target;\n  var now = new Date();\n  var lastTime = context.get('lastTime') || now;\n  // limit value to last value +- rate * time\n  var maxDelta = (now.getTime() - lastTime.getTime()) * maxRate / (60 * 1000);\n  if (Math.abs(target - lastValue) > jumpThreshold) {\n    // step > threshold so go there imediately\n    newValue = target;\n  } else if (target > lastValue) {\n    newValue = Math.min(lastValue + maxDelta, target);\n  } else {\n    newValue = Math.max(lastValue - maxDelta, target);\n  }\n  context.set('lastValue', newValue);\n  context.set('lastTime', now);\n  return newValue;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3640,"y":220,"wires":[["a669c728c5e2a578","44a193237c2be80c"]]},{"id":"44a193237c2be80c","type":"debug","z":"6d52a149a8bfc7fa","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":3790,"y":200,"wires":[]}]

The code in the function node for creating the delay:

// Limits the slew rate incoming payload values
// optionally sending intermediate values at specified rate
let maxRate = 1000;         // max slew rate units/minute
let sendIntermediates = true;  // whether to send intermediate values
let period = 0.1;              // period in millisecs to send new values (if sendIntermediates) smooth or cluncky low smooth high value cluncky
let jumpThreshold = 101;       // if the step asked for is more that this then goes immediately to that value

var newValue = Number(msg.payload);
var timer = context.get('timer') || 0;
// check the value is  a number
if (!isNaN(newValue) && isFinite(newValue)) {
  var target = msg.payload;
  context.set('target', target);
  // set last value to new one if first time through
  var lastValue = context.get('lastValue');
  if (typeof lastValue == "undefined" || lastValue === null) {
    lastValue = newValue;
    context.set('lastValue', newValue);
  }
  // calc new value
  msg.payload = calcOutput();
  // stop the timer
  if (timer) {
    clearTimeout(timer);
    context.set('timer', null);
  }
  // restart it if required to send intermediate values
  if (sendIntermediates) {
    timer = setInterval(
      function() {
        // the timer has run down calculate next value and send it
        var newValue = calcOutput();
        if (newValue != context.get('lastValueSent')) {
          context.set('lastValueSent', newValue);
          node.send({ payload: newValue });
        }
      },
      period);
    context.set('timer', timer);
  }
  context.set('lastValueSent', msg.payload);
} else {
  // payload is not a number so ignore it
  // also stop the timer as we don't know what to send any more
  if (timer) {
    clearTimeout(timer);
    context.set('timer', null);
  }
  msg = null;
}
return msg;

// determines the required output value
function calcOutput() {
  var lastValue = context.get('lastValue');
  var target = context.get('target');
  // set to current value if first time through or step > threshold
  if (typeof lastValue == "undefined" || lastValue === null) lastValue = target;
  var now = new Date();
  var lastTime = context.get('lastTime') || now;
  // limit value to last value +- rate * time
  var maxDelta = (now.getTime() - lastTime.getTime()) * maxRate / (60 * 1000);
  if (Math.abs(target - lastValue) > jumpThreshold) {
    // step > threshold so go there imediately
    newValue = target;
  } else if (target > lastValue) {
    newValue = Math.min(lastValue + maxDelta, target);
  } else {
    newValue = Math.max(lastValue - maxDelta, target);
  }
  context.set('lastValue', newValue);
  context.set('lastTime', now);
  return newValue;
}

This section is a guide to control the servo using a microcontroller. I used an Arduino Nano for this.
motor pin: D9
5V from arduino
gnd from arduino
And ofc Micro USB cable for serial communication between pi and nano

The serial node is required:

What the flow, used to send commands from pi to Arduino Nano, looks like:
image
The flow used to send commands from pi to Arduino Nano:

[{"id":"f79fee50547a731b","type":"template","z":"4d2383b657776608","name":"go to 90 degrees","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"move {{payload}}","output":"str","x":490,"y":920,"wires":[["2bbb64d3c1645aa4"]]},{"id":"5607580915280b22","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":310,"y":880,"wires":[["b5620e65f485fdf4"]]},{"id":"b6328353f3b5fa70","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"90","payloadType":"num","x":310,"y":920,"wires":[["f79fee50547a731b"]]},{"id":"b5620e65f485fdf4","type":"template","z":"4d2383b657776608","name":"go to 10 degrees","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"move {{payload}}","output":"str","x":490,"y":880,"wires":[["2bbb64d3c1645aa4"]]},{"id":"6433e6f0dd1a31d0","type":"template","z":"4d2383b657776608","name":"go to 180 degrees","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"move {{payload}}","output":"str","x":490,"y":960,"wires":[["2bbb64d3c1645aa4"]]},{"id":"0217be77d4309c50","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"180","payloadType":"num","x":310,"y":960,"wires":[["6433e6f0dd1a31d0"]]},{"id":"2bbb64d3c1645aa4","type":"serial out","z":"4d2383b657776608","name":"","serial":"08b1c6602c6bf53c","x":710,"y":920,"wires":[]},{"id":"08b1c6602c6bf53c","type":"serial-port","serialport":"/dev/ttyUSB0","serialbaud":"115200","databits":"8","parity":"none","stopbits":"1","waitfor":"","newline":"\\n","bin":"false","out":"char","addchar":"","responsetimeout":"10000"}]

The code that was used to flash the Arduino Nano:

#include <Servo.h>

Servo servoMotor;  // Create a Servo object

long last_update = 0;
long last_measured = 0;
long time_now;
float temp = 0;
String str;
int iCount, i;
String sParams[6];


void setup() {
  Serial.begin(115200);
  servoMotor.attach(9);  // Attach the servo to pin 9 (D9)
}


void loop() {
  if (Serial.available()) {
    String command = Serial.readStringUntil('\n');  // Read the command from the serial input

    // Split the command into parts
    int numParams = StringSplit(command, ' ', sParams, 6);

    if (sParams[0] == "move" && numParams == 2) {
      int angle = sParams[1].toInt();  // Convert the parameter to an integer
      moveServo(angle);                // Call a function to move the servo to the specified angle
    }
  }
}


void moveServo(int angle) {
  if (angle >= 0 && angle <= 180) {
    servoMotor.write(angle);  // Set the servo position to the specified angle with a command like "move 10" for 10 degrees "move 90" for 90 degrees or "move 180" for 180 degrees. keep in mind the space between move and the number
  }
}


int StringSplit(String sInput, char cDelim, String sParams[], int iMaxParams) {
  int iParamCount = 0;
  int iPosDelim, iPosStart = 0;

  do {
    iPosDelim = sInput.indexOf(cDelim, iPosStart);
    if (iPosDelim > (iPosStart + 1)) {
      sParams[iParamCount] = sInput.substring(iPosStart, iPosDelim);
      iParamCount++;
      if (iParamCount >= iMaxParams) {
        return (iParamCount);
      }
      iPosStart = iPosDelim + 1;
    }
  } while (iPosDelim >= 0);

  if (iParamCount < iMaxParams) {
    sParams[iParamCount] = sInput.substring(iPosStart);
    iParamCount++;
  }

  return (iParamCount);
}



This section is a guide to control the speed of the servo using a microcontroller via serial commands. I used an Arduino Nano for this.

What the flow looks like:
image

The flow used to send speed and position commands from pi to Arduino Nano:

[{"id":"2bbb64d3c1645aa4","type":"serial out","z":"4d2383b657776608","name":"","serial":"08b1c6602c6bf53c","x":710,"y":1140,"wires":[]},{"id":"a46fbad28d0b817f","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":290,"y":1160,"wires":[["2eee4b6e69555140"]]},{"id":"a1de5bd6b52185d3","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"90","payloadType":"num","x":290,"y":1200,"wires":[["2eee4b6e69555140"]]},{"id":"2eee4b6e69555140","type":"template","z":"4d2383b657776608","name":"Position","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"move {{payload}}","output":"str","x":480,"y":1200,"wires":[["2bbb64d3c1645aa4"]]},{"id":"0b61cdc577a1d4e5","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"180","payloadType":"num","x":290,"y":1240,"wires":[["2eee4b6e69555140"]]},{"id":"574e5a2d65f5584a","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0.1","payloadType":"num","x":290,"y":980,"wires":[["ae88d8d1e5851281"]]},{"id":"9b37bfb797952501","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":290,"y":1020,"wires":[["ae88d8d1e5851281"]]},{"id":"8dd0a1f103cc1b78","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":290,"y":1060,"wires":[["ae88d8d1e5851281"]]},{"id":"688c01b56d27e637","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"100","payloadType":"num","x":290,"y":1100,"wires":[["ae88d8d1e5851281"]]},{"id":"ae88d8d1e5851281","type":"template","z":"4d2383b657776608","name":"period in msec","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"speed {{payload}}","output":"str","x":500,"y":1040,"wires":[["2bbb64d3c1645aa4"]]},{"id":"8fc53fed077d9a06","type":"comment","z":"4d2383b657776608","name":"Higher value = slower servo","info":"","x":300,"y":940,"wires":[]},{"id":"08b1c6602c6bf53c","type":"serial-port","serialport":"/dev/ttyUSB0","serialbaud":"115200","databits":"8","parity":"none","stopbits":"1","waitfor":"","newline":"\\n","bin":"false","out":"char","addchar":"","responsetimeout":"10000"}]

The arduino code:

#include <Servo.h>

Servo servoMotor;

String sParams[6];

int angle = 0;
int targetAngle = 0;

unsigned long msecPeriod = 10;
unsigned long msecLst;

// -----------------------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  servoMotor.attach(9);
  servoMotor.write(angle);
}

// -----------------------------------------------------------------------------
void loop() {
  unsigned long msec = millis();

  if (targetAngle != angle) {
    if (msec - msecLst >= msecPeriod) {
      msecLst = msec;

      if (targetAngle > angle)
        angle++;
      else
        angle--;
      servoMotor.write(angle);
    }
  }

  if (Serial.available()) {
    String command = Serial.readStringUntil('\n');
    int numParams = StringSplit(command, ' ', sParams, 6);

    if (sParams[0] == "move" && numParams == 2) {
      targetAngle = sParams[1].toInt();
    } else if (sParams[0] == "speed" && numParams == 2) {
      msecPeriod = sParams[1].toInt();
    }
  }
}

// -----------------------------------------------------------------------------
int StringSplit(
  String sInput,
  char cDelim,
  String sParams[],
  int iMaxParams) {
  int iParamCount = 0;
  int iPosDelim, iPosStart = 0;

  do {
    iPosDelim = sInput.indexOf(cDelim, iPosStart);
    if (iPosDelim > (iPosStart + 1)) {
      sParams[iParamCount] = sInput.substring(iPosStart, iPosDelim);
      iParamCount++;
      if (iParamCount >= iMaxParams) {
        return (iParamCount);
      }
      iPosStart = iPosDelim + 1;
    }
  } while (iPosDelim >= 0);

  if (iParamCount < iMaxParams) {
    sParams[iParamCount] = sInput.substring(iPosStart);
    iParamCount++;
  }

  return (iParamCount);
}

Special thanks to Zenofmud dceejay and Google for helping me :smiley:

1 Like

Now a way to slow done the servo movements.... Does anyone know?

This Function node code should get you going. You should be able to adjust the settings at the start, and enable sendIntermediates to match your requirement.

// Limits the slew rate incoming payload values
// optionally sending intermediate values at specified rate
let maxRate = 1.5/60;         // max slew rate units/minute
let sendIntermediates = false;   // whether to send intermediate values
let period = 1000;          // period in millisecs to send new values (if sendIntermediates)
let jumpThreshold = 0.25;   // if the step asked for is more that this then goes immediately to that value

var newValue = Number(msg.payload);
var timer = context.get('timer') || 0;
// check the value is  a number
if (!isNaN(newValue) && isFinite(newValue)) {
    var target = msg.payload;
    context.set('target', target);
    // set last value to new one if first time through
    var lastValue = context.get('lastValue');
    if (typeof lastValue == "undefined" || lastValue === null) {
        lastValue = newValue;
        context.set('lastValue', newValue);
    }
    // calc new value
    msg.payload = calcOutput();
    // stop the timer
    if (timer) {
        clearTimeout(timer);
        context.set('timer', null);
    }
    // restart it if required to send intermediate values
    if (sendIntermediates) {
        timer = setInterval(function(){
            // the timer has run down calculate next value and send it
            var newValue = calcOutput();
            if (newValue != context.get('lastValueSent')) {
                context.set('lastValueSent', newValue);
                node.send({payload: newValue});
            }
        },period);
        context.set('timer', timer);
    }
    context.set('lastValueSent', msg.payload);
} else {
    // payload is not a number so ignore it
    // also stop the timer as we don't know what to send any more
    if (timer) {
        clearTimeout(timer);
        context.set('timer', null);
    }
    msg = null;
}
return msg;

// determines the required output value
function calcOutput() {
    var lastValue = context.get('lastValue');
    var target = context.get('target');
    // set to current value if first time through or step > threshold
    if (typeof lastValue == "undefined" || lastValue === null) lastValue = target;
    var now = new Date();
    var lastTime = context.get('lastTime') || now;
    // limit value to last value +- rate * time
    var maxDelta = (now.getTime() - lastTime.getTime()) * maxRate / (60 * 1000);
    if (Math.abs(target - lastValue) > jumpThreshold) {
        // step > threshold so go there imediately
        newValue = target;
    } else if (target > lastValue) {
        newValue = Math.min( lastValue + maxDelta, target);
    } else {
        newValue = Math.max( lastValue - maxDelta, target);
    }
    context.set('lastValue', newValue);
    context.set('lastTime', now);   
    return newValue;
}
1 Like

No need to do that, you have already achieved that with
sudo systemctl enable pigpiod
enable means to start it automatically at boot. It is years since rc.local was used for that. I don't know whether it does any harm to have it in.

1 Like

Oh! then why is that in the description of the node! (in the link)

Where did you see the instructions to use systemctl to control it?

I can't remember... why? :smiley:

[{"id":"e7d5405eb7a85912","type":"inject","z":"6d52a149a8bfc7fa","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"","payload":"50","payloadType":"num","x":3490,"y":220,"wires":[["df0aef8119bd78aa"]]},{"id":"ef69bbfccb9893bc","type":"inject","z":"6d52a149a8bfc7fa","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"2","topic":"","payload":"100","payloadType":"num","x":3490,"y":260,"wires":[["df0aef8119bd78aa"]]},{"id":"a669c728c5e2a578","type":"pi-gpiod out","z":"6d52a149a8bfc7fa","name":"","host":"","port":"8888","pin":"23","set":"","level":"0","out":"ser","sermin":"500","sermax":"2500","freq":"800","x":3780,"y":240,"wires":[]},{"id":"15645002d9b760e2","type":"inject","z":"6d52a149a8bfc7fa","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"0.1","topic":"","payload":"0","payloadType":"num","x":3490,"y":180,"wires":[["df0aef8119bd78aa"]]},{"id":"df0aef8119bd78aa","type":"function","z":"6d52a149a8bfc7fa","name":"","func":"// Limits the slew rate incoming payload values\n// optionally sending intermediate values at specified rate\nlet maxRate = 1000;         // max slew rate units/minute\nlet sendIntermediates = true;  // whether to send intermediate values\nlet period = 0.1;              // period in millisecs to send new values (if sendIntermediates) smooth or cluncky low smooth high value cluncky\nlet jumpThreshold = 101;       // if the step asked for is more that this then goes immediately to that value\n\nvar newValue = Number(msg.payload);\nvar timer = context.get('timer') || 0;\n// check the value is  a number\nif (!isNaN(newValue) && isFinite(newValue)) {\n  var target = msg.payload;\n  context.set('target', target);\n  // set last value to new one if first time through\n  var lastValue = context.get('lastValue');\n  if (typeof lastValue == \"undefined\" || lastValue === null) {\n    lastValue = newValue;\n    context.set('lastValue', newValue);\n  }\n  // calc new value\n  msg.payload = calcOutput();\n  // stop the timer\n  if (timer) {\n    clearTimeout(timer);\n    context.set('timer', null);\n  }\n  // restart it if required to send intermediate values\n  if (sendIntermediates) {\n    timer = setInterval(\n      function() {\n        // the timer has run down calculate next value and send it\n        var newValue = calcOutput();\n        if (newValue != context.get('lastValueSent')) {\n          context.set('lastValueSent', newValue);\n          node.send({ payload: newValue });\n        }\n      },\n      period);\n    context.set('timer', timer);\n  }\n  context.set('lastValueSent', msg.payload);\n} else {\n  // payload is not a number so ignore it\n  // also stop the timer as we don't know what to send any more\n  if (timer) {\n    clearTimeout(timer);\n    context.set('timer', null);\n  }\n  msg = null;\n}\nreturn msg;\n\n// determines the required output value\nfunction calcOutput() {\n  var lastValue = context.get('lastValue');\n  var target = context.get('target');\n  // set to current value if first time through or step > threshold\n  if (typeof lastValue == \"undefined\" || lastValue === null) lastValue = target;\n  var now = new Date();\n  var lastTime = context.get('lastTime') || now;\n  // limit value to last value +- rate * time\n  var maxDelta = (now.getTime() - lastTime.getTime()) * maxRate / (60 * 1000);\n  if (Math.abs(target - lastValue) > jumpThreshold) {\n    // step > threshold so go there imediately\n    newValue = target;\n  } else if (target > lastValue) {\n    newValue = Math.min(lastValue + maxDelta, target);\n  } else {\n    newValue = Math.max(lastValue - maxDelta, target);\n  }\n  context.set('lastValue', newValue);\n  context.set('lastTime', now);\n  return newValue;\n}","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":3640,"y":220,"wires":[["a669c728c5e2a578","44a193237c2be80c"]]},{"id":"44a193237c2be80c","type":"debug","z":"6d52a149a8bfc7fa","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":3790,"y":200,"wires":[]}]

image

updated the code for my servo

// Limits the slew rate incoming payload values
// optionally sending intermediate values at specified rate
let maxRate = 1000;         // max slew rate units/minute
let sendIntermediates = true;  // whether to send intermediate values
let period = 0.1;              // period in millisecs to send new values (if sendIntermediates) smooth or cluncky low smooth high value cluncky
let jumpThreshold = 101;       // if the step asked for is more that this then goes immediately to that value

var newValue = Number(msg.payload);
var timer = context.get('timer') || 0;
// check the value is  a number
if (!isNaN(newValue) && isFinite(newValue)) {
  var target = msg.payload;
  context.set('target', target);
  // set last value to new one if first time through
  var lastValue = context.get('lastValue');
  if (typeof lastValue == "undefined" || lastValue === null) {
    lastValue = newValue;
    context.set('lastValue', newValue);
  }
  // calc new value
  msg.payload = calcOutput();
  // stop the timer
  if (timer) {
    clearTimeout(timer);
    context.set('timer', null);
  }
  // restart it if required to send intermediate values
  if (sendIntermediates) {
    timer = setInterval(
      function() {
        // the timer has run down calculate next value and send it
        var newValue = calcOutput();
        if (newValue != context.get('lastValueSent')) {
          context.set('lastValueSent', newValue);
          node.send({ payload: newValue });
        }
      },
      period);
    context.set('timer', timer);
  }
  context.set('lastValueSent', msg.payload);
} else {
  // payload is not a number so ignore it
  // also stop the timer as we don't know what to send any more
  if (timer) {
    clearTimeout(timer);
    context.set('timer', null);
  }
  msg = null;
}
return msg;

// determines the required output value
function calcOutput() {
  var lastValue = context.get('lastValue');
  var target = context.get('target');
  // set to current value if first time through or step > threshold
  if (typeof lastValue == "undefined" || lastValue === null) lastValue = target;
  var now = new Date();
  var lastTime = context.get('lastTime') || now;
  // limit value to last value +- rate * time
  var maxDelta = (now.getTime() - lastTime.getTime()) * maxRate / (60 * 1000);
  if (Math.abs(target - lastValue) > jumpThreshold) {
    // step > threshold so go there imediately
    newValue = target;
  } else if (target > lastValue) {
    newValue = Math.min(lastValue + maxDelta, target);
  } else {
    newValue = Math.max(lastValue - maxDelta, target);
  }
  context.set('lastValue', newValue);
  context.set('lastTime', now);
  return newValue;
}

Now control the servo motor using serial communication.
I used an arduino nano.
motor pin D9
5V from arduino
gnd from arduino
And ofc Micro USB cable for serial communication between pi and nano

The flow:
image

[{"id":"f79fee50547a731b","type":"template","z":"4d2383b657776608","name":"go to 90 degrees","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"move {{payload}}","output":"str","x":490,"y":920,"wires":[["2bbb64d3c1645aa4"]]},{"id":"5607580915280b22","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"10","payloadType":"num","x":310,"y":880,"wires":[["b5620e65f485fdf4"]]},{"id":"b6328353f3b5fa70","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"90","payloadType":"num","x":310,"y":920,"wires":[["f79fee50547a731b"]]},{"id":"b5620e65f485fdf4","type":"template","z":"4d2383b657776608","name":"go to 10 degrees","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"move {{payload}}","output":"str","x":490,"y":880,"wires":[["2bbb64d3c1645aa4"]]},{"id":"6433e6f0dd1a31d0","type":"template","z":"4d2383b657776608","name":"go to 180 degrees","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"move {{payload}}","output":"str","x":490,"y":960,"wires":[["2bbb64d3c1645aa4"]]},{"id":"0217be77d4309c50","type":"inject","z":"4d2383b657776608","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"180","payloadType":"num","x":310,"y":960,"wires":[["6433e6f0dd1a31d0"]]},{"id":"2bbb64d3c1645aa4","type":"serial out","z":"4d2383b657776608","name":"","serial":"08b1c6602c6bf53c","x":710,"y":920,"wires":[]},{"id":"08b1c6602c6bf53c","type":"serial-port","serialport":"/dev/ttyUSB0","serialbaud":"115200","databits":"8","parity":"none","stopbits":"1","waitfor":"","newline":"\\n","bin":"false","out":"char","addchar":"","responsetimeout":"10000"}]

The arduino code:

#include <Servo.h>

Servo servoMotor;  // Create a Servo object

long last_update = 0;
long last_measured = 0;
long time_now;
float temp = 0;
String str;
int iCount, i;
String sParams[6];


void setup() {
  Serial.begin(115200);
  servoMotor.attach(9);  // Attach the servo to pin 9 (D9)
}


void loop() {
  if (Serial.available()) {
    String command = Serial.readStringUntil('\n');  // Read the command from the serial input

    // Split the command into parts
    int numParams = StringSplit(command, ' ', sParams, 6);

    if (sParams[0] == "move" && numParams == 2) {
      int angle = sParams[1].toInt();  // Convert the parameter to an integer
      moveServo(angle);                // Call a function to move the servo to the specified angle
    }
  }
}


void moveServo(int angle) {
  if (angle >= 0 && angle <= 180) {
    servoMotor.write(angle);  // Set the servo position to the specified angle with a command like "move 10" for 10 degrees "move 90" for 90 degrees or "move 180" for 180 degrees. keep in mind the space between move and the number
  }
}


int StringSplit(String sInput, char cDelim, String sParams[], int iMaxParams) {
  int iParamCount = 0;
  int iPosDelim, iPosStart = 0;

  do {
    iPosDelim = sInput.indexOf(cDelim, iPosStart);
    if (iPosDelim > (iPosStart + 1)) {
      sParams[iParamCount] = sInput.substring(iPosStart, iPosDelim);
      iParamCount++;
      if (iParamCount >= iMaxParams) {
        return (iParamCount);
      }
      iPosStart = iPosDelim + 1;
    }
  } while (iPosDelim >= 0);

  if (iParamCount < iMaxParams) {
    sParams[iParamCount] = sInput.substring(iPosStart);
    iParamCount++;
  }

  return (iParamCount);
}


The serial node:

That is too high a rate, that is 10000 updates per second. Set to 100 (msec) initially.

it seems to work tho?

How else can i make it smooth -and- slow

Try 1 millisecond or 10.

Servos usually work on a pulse width of 1-2ms. (1.5mS being centre position) so I would say 10mS would be fast enough.

Good point.

@WBr changing the period will not affect how fast the servo moves, just how jerky it is.

The small servos I used to control with a PIC microcontroller had a pulse width (as @dceejay indicated) of 1mS to 2mS with a repetition frequency of 50Hz or 20mS between pulses. The 1mS to 2mS is like a pulse width modulation waveform that is used to define the angular position. 1mS is zero degrees while 2mS is 180 degrees. 1.5mS will be approx 90 degrees. Fine tuning will be needed.

Just in case there is confusion, the node is not controlling the pulses directly, it is just setting the PWM control value.

For a heavier servo, I used an external 5V supply (it needs 2.7A max). (both servos have the same pulse width of 500ms - 2500ms)
I used GPIO17 on the pi

The servo did not work. But the TS90M did work
Do I need a common ground as I am not using gnd from the pi anymore because of the external 5V power?

Yes, you need a common ground. However, I don't know whether what you describe will work. The gpio output will only pull up to 3.3V so whether that will be ok when the servo is supplied with 5V I don't know. Also there is a possibility that the servo input will float up to the 5V level when the pi is not pulling it down, in which case there is a possibility it will damage the pi. Have you seen examples of others doing what you describe without problems?

Personally I have not seen a problem driving most servos - as long as the high is above 2.1-2.4 V and the low below 0.7 then for me at least they have been ok (even when driving servos with a 6V separate battery supply.) - but yes MUST have a common ground.

You are right of course if the servos have a pull up - but I've not seen any like that (yet).

1 Like

Ok, that's good to know.

Updated the post with optional Arduino integration!