Using Servos with Vixen and Arduino

Loved the videos. Very cool! Makes me want to get back into the model railroad stuff.

Nice vids in your signature! Some model railroaders think my projects are an abomination! "What? Snoopy on a servo!" Not fine scale modeling! LOL
 
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.
 
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.

Thanks, Phil, for the code and tutorial!!! I will play around with it. I will be using 8 or 9 servos. Most of my effort will be consumed with building the sequence to music. Eventually I might add a "pan and tilt" type of movement with a second servo for each figure, so up to 18 servos. I tested, only in as much as getting the code to compile, using an old variable speed servo VarSpeedServo.h library where after the position there is a speed parameter:

servo1.write(map(channelValues[0], 0,255, 0, 180),75); // "75" is for a slower speed, Speed=0: full speed, Speed=1: Slowest
Speed=255: Fastest.

Library here as a zip: https://github.com/netlabtoolkit/VarSpeedServo/releases

Take care, Joe.
 
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
 
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

Very nice. I like that.
 
Thoroughly approve of all those code mods, much more scalable :) Every day is an education! Between us hopefully we've got some decent code out there. I like the use of enums to show the different states.

Sort of on topic - it's odd that the new version of the Arduino IDE doesn't highlight byte as a known data type (I tend to use these as variables when i know the value is going to be 255 or less). Looks like uint8_t can be used interchangeably and is also an 8 bit var.
 
Anyone have any interest in partnering with me to compile some of these basic examples into some pages on the Vixen website. We seem to have a good crowd of folks interested in this kind of thing. I know there are random articles out there that lead people on this journey, and if we can provide them with some more current documentation tailored to the Vixen side it might be helpful.

By the way I appreciate all of you that play in this space pitching in to help with the support. This is what community is all about.


Sent from my iPhone using Tapatalk
 
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.
 
Additional code suggestion - the header characters should be different from each other. This might not matter too much for servo controller programs, but might be a good idea for the possible future use of this code in controlling (SSR) relays or maybe even smaller pixel strings. It's quite possible, for example, that someone might be ramping a whole bunch of channels up or down in synchrony, which would slow down the process of re-discovering packet boundaries.

Another thing that I'd like to see would be including a byte count field at the start of the packet, as well as a packet trailer. The former is just aspirational, the latter may make recovering packet boundaries easier. Placing a timer around the packet reception would also help recovery, as a long interval between bytes within an incoming packet would likely indicate that something went wrong. It's debatable, I suppose, whether the additional complexity is a good idea, and it would certainly make it
more difficult for newbies to understand.
 
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.

I really want to centralize as much as we can toward the Vixen doc site since there is a lot of other relevant information there. There are many times folks ask questions that has information on the Vixen site. The old site was pretty rough to find things, but the new one should be more organized and can be added to much easier. It can be cross linked to other topic areas to further enhance the learning. If anyone does something in instructables, it should make clear references back to the Vixen site pages that apply. The Vixen site pages reference this forum as well for this type of support. For what it is worth, the Vixen site is all open source with the content on github so it should be easier for others to contribute. I?ll take this to a top level thread as to not further pollute this one with a sidebar.


Sent from my iPhone using Tapatalk
 
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#.
 
There are filters that can be used in the patching to help shape the output. If you want zeros to be a 1 as the minimum value, this is possible using a curve to translate the input value to a desired output.

There was some recent work to make moving fixtures much easier to manage, and I think much of that can align to this as well since servos are in essence moving fixtures.


Sent from my iPhone using Tapatalk
 
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#.

Hi Dirk,

I am not an expert by any means, but one thing I have done is to cut power (with a relay) to the servo before the loop ends and engage power to the servo a few seconds into the loop. In my case, especially with the servos that rock a figure (as opposed to just turning it) the servo will strip it's gears (as you know) if it's travel s impeded by what's attached to its horn. The Arduino code takes the output from Vixen and maps it to a range you decide.

The guys that do the talking pumpkins and skulls don't seem to an issue, some of them even used Vixen 2.

I hope to dive into this on the 26th. I'll share what I know, but the guys here know their stuff and work through any problem with you.

Take care, Joe.
 
Last edited:
Configuring lower and upper bounds on the servo values can also be handled in the Arduino sketch, either as fixed values or passed in either the packet header or the packet body.
 
Hi Folks,

So this is just an update (my efforts) that the variable speed library works well (for me) with the servo code below, but the VarSpeedServo.h library needs to be installed manually in the Arduino IDE. Because servos sometimes behave in an unintentional manner, I have added a relay to only apply power when needed, using last pin. The code handles the variable speed library and drives more than just 8 servos, 9 servos in my case. My code is not efficient or tight, but it works for me. I am sure it can be greatly improved upon!

Code:
#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
}

It's not easy doing the sequencing when you have chronic tendonitis; very mouse intensive, LOL.

Take care, Joe.
 
Last edited:
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.
 
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.

Impressive displays!!!!! A tremendous effort!!!!!
Thanks for the suggestions, Martin! Right now I am concentrating on the servos with Vixen. My main hobby is electric trains at Christmas. Vixen offers an easier way of automation with on-the-fly adjustments. I want to try my hand at audioanimatronics. I am learning all the ins and outs of Vixen. Writing a sequence is tedious, but a lot of fun!

Take care, Joe.
 
I definitely recommend adding upper and lower limits in the Arduino code. They can save the servos and are pretty easy to add.

Another possibility to handle the end of a sequence (assuming that the incoming data sets the servo to 0), is to manually map in the Arduino code to replace a 0 input to a value that will center the servo. I would assume this value would be a 127 or 128.
 
Back
Top