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
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
updated the code for my servo with a delay function.
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:
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:
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