Joe Paul
Supporting Member
Interesting projects. I like that you confuse some of your fellow enthusiasts. Keep on making!!
I'll never stop, God willing!!!
Interesting projects. I like that you confuse some of your fellow enthusiasts. Keep on making!!
Loved the videos. Very cool! Makes me want to get back into the model railroad stuff.
#include <Servo.h>
#define numChannels 8
byte channelValues[numChannels];
Servo servo[numChannels];
enum {header_1st_byte_get, header_2nd_byte_get, body} packet_state;
int servo_pins[] = {10,3,4,5,6,9,7,8};
void setup() {
//start serial port
Serial.begin(115200);
//init servo objects
for (uint8_t i=0;i<sizeof(servo_pins)/sizeof(int); i++)
servo[i].attach(servo_pins[i]);
packet_state = header_1st_byte_get;
}
int servo_update_index;
void loop() {
if (Serial.available()) {
switch(packet_state) {
case header_1st_byte_get:
if (0x3E == Serial.read())
packet_state=header_2nd_byte_get;
break;
case header_2nd_byte_get:
if (0x3E == Serial.read()) {
servo_update_index=0;
packet_state=body;
} else
packet_state=header_1st_byte_get;
break;
case body:
if (servo_update_index < numChannels) {
channelValues[servo_update_index++] = Serial.read();
} else {
updateServoPositions();//this function updates the servos
packet_state = header_1st_byte_get;
}
break;
default:
break;
} // end of switch statement
}; // end of 'if serial.available()' body
// Code for other functions (LED flash, sensor read, LCD output, etc).
} // end of 'loop' function
void updateServoPositions(){
for(byte i=0; i<numChannels; i++){
servo[i].write(map(channelValues[i], 0, 255, 0, 180));
/*uncomment this to print test values to IDE serial monitor
Serial.print("controller output ");
Serial.print(i);
Serial.print(" value = ");
Serial.print(channelValues[i]);
Serial.print("/255 or ");
Serial.print(map(channelValues[i], 0, 255, 0, 180));
Serial.println("/180 degrees");
*/
}
}
Just as a quick critique, I don't think that using blocking statements (such as 'for (;{}' or 'while(!Serial.available()') in the main loop is good practice, except maybe in a simple program that will never be expanded. The reasons for writing this is that nothing else can happen in the controller while the program is waiting for an entire packet to be received, and that there would a problem in certain circumstances in an ESP device (due to the watchdog timer).
I think that a better approach is to use a 'if (Serial.available() {}' with an explicit state machine in the 'if' body using a switch statement. As an example (which compiles) based on the previous code:
Code:#include <Servo.h> #define numChannels 8 byte channelValues[numChannels]; Servo servo[numChannels]; enum {header_1st_byte_get, header_2nd_byte_get, body} packet_state; int servo_pins[] = {10,3,4,5,6,9,7,8}; void setup() { //start serial port Serial.begin(115200); //init servo objects for (uint8_t i=0;i<sizeof(servo_pins)/sizeof(int); i++) servo[i].attach(servo_pins[i]); packet_state = header_1st_byte_get; } int servo_update_index; void loop() { if (Serial.available()) { switch(packet_state) { case header_1st_byte_get: if (0x3E == Serial.read()) packet_state=header_2nd_byte_get; break; case header_2nd_byte_get: if (0x3E == Serial.read()) { servo_update_index=0; packet_state=body; } else packet_state=header_1st_byte_get; break; case body: if (servo_update_index < numChannels) { channelValues[servo_update_index++] = Serial.read(); } else { updateServoPositions();//this function updates the servos packet_state = header_1st_byte_get; } break; default: break; } // end of switch statement }; // end of 'if serial.available()' body // Code for other functions (LED flash, sensor read, LCD output, etc). } // end of 'loop' function void updateServoPositions(){ for(byte i=0; i<numChannels; i++){ servo[i].write(map(channelValues[i], 0, 255, 0, 180)); /*uncomment this to print test values to IDE serial monitor Serial.print("controller output "); Serial.print(i); Serial.print(" value = "); Serial.print(channelValues[i]); Serial.print("/255 or "); Serial.print(map(channelValues[i], 0, 255, 0, 180)); Serial.println("/180 degrees"); */ } }
The big advantage of this approach is that it allows the program to execute other code immediately after the Serial.available() test fails. There isn't any additional code in this sample program, but this would be a better starting point for adding functionality there.
Hi Folks,
Here is a very simple technique with a servo with the Pi Pico and Arduino code I did last year:
if driven by Vixen, the possibilities are limitless, like for mouth movements to audio:
Take care, Joe
jeff -
I think that's a good idea to try and find a better place for this information than several pages down in a forum threads. One criteria for the destination of this information should be that it's easy to find and one of the first places that people will come across. The Vixen3 website is one good place, but I'm wondering if there might be other good locations. One that comes to mind is a sticky on the Vixen3 forum, another might be an instructables or series of instructables pages (hopefully we can find a way to make it come out better in the google searches than some of the first-effort pages there.
Not sure whether this is (still) an issue as I'm not familiar with how Vixen3 deals with servos, but one of the issues to avoid is pegging the servo's travel at either end of its travel range. This was a problem with the old Vixen, which sent zeros out to all channels at the end of a sequence to turn them off. This in effect would cause a servo to travel to its minimum position and put force on the servo gears, which could actually strip them and damage the servo. Likewise, a value of 255 would cause the servo to go to its max position and put pressure on the gears there.
Programmatically, it's always advised to have a minimum and a maximum setting for the servo's travel to prevent pegging it at either of the servo's physical min or max positions. Perhaps Vixen3 has this capability, but I don't know (i.e. if a prop is defined as a servo, then a zero is reset to the allowed minimum and a "full on" setting of 255 is converted to the allowed maximum.)
FWIW, for several years I tried to use hobby servos to run my animatronic figures but the difficulty in making them work consistently outdoors in a Minnesnowda winter caused me to abandon them in favor of windshield-washer type motors that are vastly less-susceptible to moisture and attaching their continuous, one-direction rotation to eccentric, piston-type mechanisms. I have also used actuators with an H-bridge for some of my props; actuators employ their own internal mechanisms to deal with min/max travel and are impervious to servo issues. The down-sides with actuators is their cost, they're generally not as quick, they can be noisy and they require two channels to control motion; the up-sides are that they are SUPER-POWERFUL and consistent -- a couple of mine can easily move 100#.
#define numChannels 10
#include <VarSpeedServo.h>
// https://github.com/netlabtoolkit/VarSpeedServo/releases library needs to be manually installed
VarSpeedServo servo1;
VarSpeedServo servo2;
VarSpeedServo servo3;
VarSpeedServo servo4;
VarSpeedServo servo5;
VarSpeedServo servo6;
VarSpeedServo servo7;
VarSpeedServo servo8;
VarSpeedServo servo9;
//VarSpeedServo servo10;
void setup() {
Serial.begin(115200);
pinMode (11,OUTPUT);
digitalWrite(11,HIGH); //relay set to trigger on LOW, servos are not powered until sequence starts, if necessary
servo1.attach(2);
servo2.attach(3);
servo3.attach(4);
servo4.attach(5);
servo5.attach(6);
servo6.attach(7);
servo7.attach(8);
servo8.attach(9);
servo9.attach(10);
//servo10.attach(11);
servo1.write(95,75); //as a precaution, sets all servos to position 95, approx. mid position for figures on horns
servo2.write(95),75;
servo3.write(95),75;
servo4.write(95),75;
servo5.write(95),75;
servo6.write(95),75;
servo7.write(95),75;
servo8.write(95),75;
servo9.write(95),75;
}
void loop() {
while(!Serial.available());
//set Vixen header to '~!'. Ths code is expcting that!
for( ; ; )
{if(Serial.read() == '~') {break;}}
while(!Serial.available());
for( ; ; )
{if(Serial.read() == '!') {break;}}
while(!Serial.available());
//define an array of the size of the number of channels, to store the channel values
byte channelValues[numChannels];
for(byte iChn = 0; iChn < numChannels; iChn++){
while(!Serial.available());
channelValues[iChn] = Serial.read(); //read channel value from serial data
}
//write(value, speed) - "speed" varies the speed of the movement to new position -- 0=full speed, 1-255 slower to faster
servo1.write(map(channelValues[0], 0,255, 89, 110), 75); //Schroeder 90-110, speed set to "75"
servo2.write(map(channelValues[1], 0,255, 90, 110), 75); //Patty 90-110
servo3.write(map(channelValues[2], 0,255, 90, 110), 75); //sally 90-110
servo4.write(map(channelValues[3], 0,255, 90, 110), 75); //Linus 90-110
servo5.write(map(channelValues[4], 0,255, 90, 110), 75); //PigPen 90-110
servo6.write(map(channelValues[5], 0,255, 85, 105), 75); //Frnklin 85-105
servo7.write(map(channelValues[6], 0,255, 90, 110), 75); //Lucy 90-110
servo8.write(map(channelValues[7], 0,255, 90, 110), 75); //Chuck 90-110
servo9.write(map(channelValues[8], 0,255, 75, 98), 75); //Snoopy 75-98
digitalWrite(11,channelValues[9]); // for relay for servo power supply if necessary
}
I have an alternate solution for you. ESPixelStick V4 supports an external 16 channel servo board (PCA9685) via an I2C interface. I have used it in a few projects and it is working well. You can use DDP, E1.31 or sequences stored on an SD card to sync it to your show.