Pololu Robotics & Electronics
Menu
My account Comments or questions? About Pololu Contact Ordering information Distributors

Pololu Forum

Wixel Wireless Servo Control via Potentiometer


#1

Hi all, i have been interested in wireless servo control via potentiometers recently. I would like to accomplish this with two Wixel modules. Essentially i want one Wixel with a potentiometer attached, send data to a second Wixel which has a servo attached, without some sort of computer interface.

I have looked at this topic: https://forum.pololu.com/t/wixel-wireless-servo-control/5706/6 and i know it is possible to do it without a computer interface controlling the Master Wixel. Since i am terrible at programming, would someone be able to write example codes so i can learn from it and develop my own, it would be greatly appreciated.

Thank you.


#2

If you do not feel up to the task of writing a Wixel app from scratch (which we typically consider a task more suited for advanced users), it might be easier to use a microcontroller (such as an Arduino or our A-Star 32U4 micro) connected to each Wixel. Then you can use the Wixel’s pre-made Wireless Serial App to communicate between them. You might also consider using something like a Maestro servo controller (which can accept TTL serial commands to control servos) connected to the receiving Wixel. This solution would still involve programming, but it is probably easier to get started programming an Arduino since there are a lot of references available on-line.

Please note that the Wixel operates at 3.3V, so you might also need a logic level shifter to use it with boards that operate at 5V.

If you do want to try writing your own Wixel app, I suggest downloading the Wixel Development Bundle and reading through the examples to get an idea of how to write your own program. You can find the Wixel Development Bundle under the “Resources” tab of the Wixel product page.

-Brandon


#3

I have looked into using an Arduino and an Xbee and the price Is a bit too high for me, I will certainly look into controlling a Maestro throughout Wixels as I already have two Maestro Modules.

Are there any good references for a setup like that?

Thank you.


#4

Hello.

It should be possible to write a script for a Mini Maestro (12-, 18-, or 24-channel) to process the analog input from the potentiometer and send the appropriate Set Target command through TTL serial to the transmitting Wixel. However, if you have a 6-channel Micro Maestro, you would not be able to do that since it does not have the SERIAL_SEND_BYTE command. In this case, the A-Star 32U4 Micro that I mentioned in my previous post would probably be the least expensive product we carry that would do that.

I do not know of any good references that would help you get started, but if you try and run into problems, I would be glad to see if I can help.

-Brandon


#5

I found this code for the Arduino here:http://www.quantumphysguy.com/2013/07/08/arduino-micro-maestro/

#include <SoftwareSerial.h>

const int DEFAULT_BAUD = 9600;
const int SERVO_CONTROLLER_RX_PIN = 11; // The SERVO CONTROLLER'S RX PIN.
const int SERVO_CONTROLLER_TX_PIN = 12; // The SERVO CONTROLLER'S TX PIN.

SoftwareSerial ServoController = SoftwareSerial(SERVO_CONTROLLER_RX_PIN, SERVO_CONTROLLER_TX_PIN);
void setup()
{
   ServoController.begin(DEFAULT_BAUD);
   delay(500);
}
void moveServo(int ServoChannel, int target)
{
   //656ms PWM pulse represents a servo angle of 0 degrees.
   //2000ms PWM pulse represents a servo angele of 180 degrees.
   //These values could vary based on the servo you use, check your servo's 
   //spec documentation and verify what PWM pulses are needed to move it.

   byte serialBytes[4]; //Create the byte array object that will hold the communication packet.

   target = (map(target, 0, 180, 656, 2000) * 4); //Map the target angle to the corresponding PWM pulse.

   serialBytes[0] = 0x84; // Command byte: Set Target.
   serialBytes[1] = ServoChannel; // First byte holds channel number.
   serialBytes[2] = target & 0x7F; // Second byte holds the lower 7 bits of target.
   serialBytes[3] = (target >> 7) & 0x7F; // Third byte holds the bits 7-13 of target.

   ServoController.write(serialBytes, sizeof(serialBytes)); //Write the byte array to the serial port.
}
void loop()
{
   moveServo(1, 180); //Move the servo on channel 0 to an angle of 180 degrees
   moveServo(0, 180); //Move the servo on channel 1 to an angle of 180 degrees
   delay(2000); //Wait 2000ms
}

In the loop could you please show me a way to add a potentiometer value instead of a fixed value?
(Sorry I’m still new to programming)


#6

Hello.

To read a potentiometer from an Arduino, you can use analogRead(), which will return a value between 0 and 1023 (corresponding to 0V and 5V). You can then use map() to convert the analog reading from 0-1023 into an angle of 0-180 degrees. This new value can be used in the “moveServo()” function in the code you posted. If you try adding this to your code and have problems, you can post your modified code here, and I would be happy to take a look.

By the way, right now that code will only update the servos every 2 seconds because of the “delay(2000)” command. If you want the servo to have smoother motion, you will probably need to make that much smaller, possibly 10ms or 20ms.

-Brandon


#7

I managed to get the serial connection working with a potentiometer but when i try to attach more potentiometers and servos, all of the servos end up getting controlled by the first potentiometer, what have i done wrong?

#include <SoftwareSerial.h>

int val0;
int val1;
int val2;
int val3;
int val4;
int val5;
int pot0 = 0;
int pot1 = 1;
int pot2 = 2;
int pot3 = 3;
int pot4 = 4;
int pot5 = 5;
const int DEFAULT_BAUD = 9600;
const int SERVO_CONTROLLER_RX_PIN = 0; // The SERVO CONTROLLER'S RX PIN.
const int SERVO_CONTROLLER_TX_PIN = 1; // The SERVO CONTROLLER'S TX PIN.

SoftwareSerial ServoController = SoftwareSerial(SERVO_CONTROLLER_RX_PIN, SERVO_CONTROLLER_TX_PIN);
void setup()
{
  ServoController.begin(DEFAULT_BAUD);
   delay(500);
}
void moveServo(int ServoChannel, int target)
{
   //656ms PWM pulse represents a servo angle of 0 degrees.
   //2000ms PWM pulse represents a servo angele of 180 degrees.
   //These values could vary based on the servo you use, check your servo's 
   //spec documentation and verify what PWM pulses are needed to move it.

   byte serialBytes[4]; //Create the byte array object that will hold the communication packet.

   target = (map(target, 0, 180, 992, 2000) * 4); 

   serialBytes[0] = 0x84; // Command byte: Set Target.
   serialBytes[1] = ServoChannel; // First byte holds channel number.
   serialBytes[2] = target & 0x7F; // Second byte holds the lower 7 bits of target.
   serialBytes[3] = (target >> 7) & 0x7F; // Third byte holds the bits 7-13 of target.

   ServoController.write(serialBytes, sizeof(serialBytes)); //Write the byte array to the serial port.
}
void loop()
{   
   val0 = analogRead(pot0);            
   val0 = map(val0, 0, 1023, 0, 359);     
   moveServo(0, (val0)); 

   val1 = analogRead(pot1);            
   val1 = map(val1, 0, 1023, 0, 359);    
   moveServo(1, (val1)); 

   val2 = analogRead(pot2);
   val2 = map(val2, 0, 1023, 0, 359);
   moveServo(2, (val2));

   val3 = analogRead(pot3);
   val3 = map(val3, 0, 1023, 0, 359);
   moveServo(3, (val3));

   val4 = analogRead(pot4);
   val4 = map(val4, 0, 1023, 0, 359);
   moveServo(4, (val4));

   val5 = analogRead(pot5);
   val5 = map(val5, 0, 1023, 0, 359);
   moveServo(5, (val5));
}

#8

I am not sure exactly what is causing that behavior, but I have a couple of suggestions of things you might try. I have had problems in the past with leftover voltage on the ADC that needed to be cleared before taking a different reading, which might be the same thing that is happening here. You might try clearing the leftover ADC voltage (by taking an ADC reading on the internal GND channel) before each of your potentiometer readings. An example of this can be found in our Force-Sensing Linear Potentiometer and LED Strip with Arduino Demo code, in the analogReset() function toward the bottom.

Another solution might be to take several potentiometer readings and average them to make sure the new potentiometer reading is not influenced by the previous one. The “Smoothing” tutorial on Arduino’s website shows an example of taking a running average of an analog input.

Adding small delays for stability might also help (which is also used in the “Smoothing” tutorial), but the problem could also be caused by your setup. If you try these suggestions and are still having problems, could you post some pictures of your setup that show all of our connections?

-Brandon


#9

Introducing the analogReset() function fixed the issue, thanks! So would i just use the Wixel wireless serial app? and which RX TX pins of the wixels do i connected to the Arduino and Micro Maestro pins?


#10

Yes, since you only need to communicate between the Arduino and Maestro through TTL serial, you can use the Wireless Serial App. In this case, since you want to transmit the serial signal from one Wixel’s RX pin to the other Wixel wirelessly, you should set the serial_mode to “UART-to-Radio”, which is serial mode 2. Information about this can be found in the Wireless Serial App section of the Wixel user’s guide.

As far as your question about connections, you will need to connect the Arduino’s TX pin to the transmitting Wixel’s RX pin. Please note that the Wixel’s RX pin is not 5V tolerant, so you will need something to lower the Arduino’s signal voltage to 3.3V, like a level shifter or a voltage divider (as detailed in the “Connecting a Microcontroller via TTL Serial” section of the Wixel user’s guide). The receiving Wixel’s TX pin can be connected to the Maestro’s RX pin. Also note that the Maestro will probably be able to receive 3.3V TTL serial bytes, but it is not guaranteed to read 3.3V as high on the RX pin, so if you want to be sure it will work, we recommend boosting the signal to above 4V. More information about this can be found in the “Micro Maestro Pinout and Components” or “Mini Maestro Pinout and Components” section of the Maestro user’s guide.

You should also make sure to have ground connections between your Arduino and transmitting Wixel as well as your Maestro and receiving Wixel.

-Brandon


#11

Hello,

So i left this project for a while and i’ve come back to it, i have connected the wixels into the A-Star and the Maestro with the wireless serial app and i am no longer able to control the servo’s via the potentiometer’s, any idea what could be causing this? thanks.


#12

It is not clear to me what you have tried and what did or did not work. For example, did you have it working without the Wixels (e.g. with the A-Star sending serial command directly to the Maestro)? Did it stop working right when you added the Wixels to your system? Are you using the logic level shifters I mentioned in my previous posts? Did you put the Wireless Serial App on both Wixels? Also, please note that as the “Caveats” heading in the “Wireless Serial App” section of the Wixel’s User’s Guide mentions, data can be lost if the Wixel receives bytes on the RX line faster than the radio can convey them to the other Wixel, so you might try lowering the baud rate or adding delays to your A-Star code.

If you have tried all of those things and are still having problems, could you post your A-Star code and Maestro settings file? You can save your Maestro settings file by selecting the “Save Settings File…” option within the “File” drop-down menu of the Maestro Control Center software. Also, can you post some pictures that show all of the connections in your system?

-Brandon


#13

Sorry for the vague response before, i have finally come back to this project, properly this time and i am still encountering some minor problems. I introduced the smoothing code, although i feel as though i’ve over-complicated it, and i am running into problems where Servo 3 is completely unresponsive when controlled by potentiometer 3 buy when pot 2 is being used it has a residual effect on servo 3 .I have tested the servo, it isn’t the problem, my wiring isn’t interfering anywhere and I have also tested the potentiometer with a multimeter. And servo 2 is very unreliable and has very lagged jolting movement.
Here is my code:

    #include <SoftwareSerial.h>
    const int pot0Readings =10;
    const int pot1Readings =10;
    const int pot2Readings =10;
    const int pot3Readings =10;
    const int pot4Readings =10;
    const int pot5Readings =10;
    const int DEFAULT_BAUD = 9600;
    const int SERVO_CONTROLLER_RX_PIN = 0; // The SERVO CONTROLLER'S RX PIN.
    const int SERVO_CONTROLLER_TX_PIN = 1; // The SERVO CONTROLLER'S TX PIN.
    int pot0 = A0; //input pins
    int pot1 = A1;
    int pot2 = A4;
    int pot3 = A6;
    int pot4 = A8;
    int pot5 = A9;
    int led = 13;
    int readings0[pot0Readings];    // the readings from the analog input
    int readings1[pot1Readings];
    int readings2[pot2Readings];
    int readings3[pot3Readings];
    int readings4[pot4Readings];
    int readings5[pot5Readings];
    int readIndex0 =0;    //Current position in Array
    int readIndex1 =0;
    int readIndex2 =0;
    int readIndex3 =0;
    int readIndex4 =0;
    int readIndex5 =0;
    int total0 =0;
    int total1 =0;    //the running total
    int total2 =0;
    int total3 =0;
    int total4 =0;
    int total5 =0;
    int average0 =0;    //the running average
    int average1 =0;
    int average2 =0;
    int average3 =0;
    int average4 =0;
    int average5 =0;

    SoftwareSerial ServoController = SoftwareSerial(SERVO_CONTROLLER_RX_PIN, SERVO_CONTROLLER_TX_PIN);

    void setup(){
      ServoController.begin(DEFAULT_BAUD);
       delay(500);   
       Serial.begin(9600);
      memset(readings0, 0, sizeof(readings0));
      memset(readings1, 0, sizeof(readings1));
      memset(readings2, 0, sizeof(readings2));
      memset(readings3, 0, sizeof(readings3));
      memset(readings4, 0, sizeof(readings4));
      memset(readings5, 0, sizeof(readings5)); 
      }

    void moveServo(int ServoChannel, int target){
       //656ms PWM pulse represents a servo angle of 0 degrees.
       //2000ms PWM pulse represents a servo angele of 180 degrees.
       byte serialBytes[4]; //Create the byte array object that will hold the communication packet.
       target = (map(target, 0, 180, 656, 2000) * 4); //Map the target angle to the corresponding PWM pulse.
       serialBytes[0] = 0x84; // Command byte: Set Target.
       serialBytes[1] = ServoChannel; // First byte holds channel number.
       serialBytes[2] = target & 0x7F; // Second byte holds the lower 7 bits of target.
       serialBytes[3] = (target >> 7) & 0x7F; // Third byte holds the bits 7-13 of target.
       ServoController.write(serialBytes, sizeof(serialBytes)); //Write the byte array to the serial port.
    }

    void loop(){   
    //smoothing for servo 0
    //subtract the last reading:
      total0 = total0 - readings0[readIndex0];
      // read from the sensor:
       analogReset();    //function to clear residual analogue readings
      readings0[readIndex0] = analogRead(pot0);
      // add the reading to the total:
     total0 = total0 + readings0[readIndex0];
      // advance to the next position in the array:
      readIndex0 = readIndex0 + 1;
      // if we're at the end of the array...
      if (readIndex0 >= pot0Readings) {
        // ...wrap around to the beginning:
        readIndex0 = 0;
      }
      // calculate the average:
      average0 = total0 / pot0Readings;
      Serial.println(average0);         
       average0 = map(average0, 0, 1023, 0, 179);     
       moveServo(0, (average0)); 
      
      //smoothing for servo 1  
      total1 = total1 - readings1[readIndex1]  ;
       analogReset();
      readings1[readIndex1] = analogRead(pot1);
     total1 = total1+ readings1[readIndex1];
      readIndex1 = readIndex1 + 1;
      if (readIndex1 >= pot1Readings) {
        readIndex1 = 0;
      }
      average1 = total1 / pot1Readings;
       Serial.println(average1);     
       average1 = map(average1, 0, 1023, 0, 179);     
       moveServo(1, (average1)); 
      
      //smoothing for servo 2   
      total2 = total2- readings2[readIndex2];
     analogReset();
     readings2[readIndex2] = analogRead(pot2);
      total2 = total2 + readings2[readIndex2];
      readIndex2 = readIndex2 + 1;
      if (readIndex2 >= pot2Readings) {
        readIndex2 = 0;
      }
      average2 = total2 / pot2Readings; 
       Serial.println(average2);         
       average2 = map(average2, 0, 1023, 0, 179);     
       moveServo(2, (average2)); 
      
       //smoothing for servo 3  
      total3 = total3 - readings3[readIndex3];
     analogReset(); 
     readings3[readIndex3] = analogRead(pot3);
      total3= total3 + readings3[readIndex3];
      readIndex3 = readIndex3 + 1;
      if (readIndex3 >= pot3Readings) {
        readIndex3 = 0;
      }
      average3 = total3/ pot3Readings;  
       Serial.println(average3);       
       average3 = map(average3, 0, 1023, 0, 179);     
       moveServo(3, (average3)); 
       
       //smoothing for servo 4
      total4 = total4 - readings4[readIndex4];
     analogReset(); 
     readings4[readIndex4] = analogRead(pot4);
      total4 = total4 + readings4[readIndex4];
      readIndex4 = readIndex4 + 1;
      if (readIndex4 >= pot4Readings) {
        readIndex4 = 0;
      }
      // calculate the average:
      average4 = total4 / pot4Readings;    
       Serial.println(average4);          
       average4 = map(average4, 0, 1023, 0, 179);     
       moveServo(4, (average4)); 
      
       //smoothing for servo 5
      total5 = total5 - readings5[readIndex5];
     analogReset(); 
     readings5[readIndex5] = analogRead(pot5);
      total5 = total5 + readings5[readIndex5];
      readIndex5 = readIndex5 + 1;
      if (readIndex5 >= pot5Readings) {
        readIndex5 = 0;
      }
      // calculate the average:
      average5 = total5 / pot5Readings;
       Serial.println(average5);        
       average5 = map(average5, 0, 1023, 0, 179);     
       moveServo(5, (average5)); 
        
      digitalWrite(led, HIGH);
    }

    void analogReset(){
    #if defined(ADMUX)
    #if defined(ADCSRB) && defined(MUX5)
        // Code for the ATmega2560 and ATmega32U4
        ADCSRB |= (1 << MUX5);
    #endif
        ADMUX = 0x1F;
        // Start the conversion and wait for it to finish.
        ADCSRA |= (1 << ADSC);
        loop_until_bit_is_clear(ADCSRA, ADSC);
    #endif

    }

#14

I glanced at your code and did not see anything obvious that would cause the odd behavior you described with servo 2 and 3. I recommend simplifying it down to getting one servo working with one potentiometer, then slowly introducing additional servos and potentiometers one at a time. You might consider using the Arduino IDE’s serial monitor for checking the target position that is being sent to the Maestro from the A-Star. Which A-Star 32U4 board are you using? Are you sure potentiometer 3 is connected to the correct pin (A6 in your code) and results in valid target positions? Also, if you connect the Maestro to your computer via USB and open the Maestro Control Center while your code is running, are the indicators for servo 2 and 3 under the “Status” tab moving correctly or do they match the behavior of the servos?

Brandon


#15

I am using the A-star micro, i have completely rewired everything and i changed the pins that some of the potentiometers were operating on:

int pot0 = A0; //input pins
int pot1 = A1;
int pot2 = A11;
int pot3 = A10;
int pot4 = A8;
int pot5 = A9;

and now potentiometers 2 and 3 are constantly giving me a reading of 1023 and not varying at all, i have checked all my connections and none of them seem to be incorrect, The maestro control center readings are also verifying that servo 2 and 3 are at the maximum position. Strangely if i connect the 5v pin of the potentiometer to the ouput pin and then remove that connection the values of both potentiometer 2 and 3 drop to 0 and then quickly move back.


#16

Can you clarify what output pin are you referring to here?

Can you post pictures of your setup that show all of your connections? A wiring diagram might help as well. Do you still have 6 servos and potentiometers in your system? Are all of the other servos and potentiometers working correctly (other than numbers 2 and 3)? If so, you might have a few damaged potentiometers. Can you try measuring the signals of your potentiometers with a multimeter to see if they are changing as expected?

Brandon


#17

I managed to get all of the servos functioning and working properly so then i started on cleaning up the program, now the problem i am having is servo 1 is turning the full 180 degrees and using the whole range of the potentiometer, while the rest of the servos are only moving about 90 degress and only engaging when the potentiometer reaches half way. For reference, the power rails of all the potentiometers are connected in parallel, and the output of each potentiometer connect to the a-star micro analog pins. I have checked all of the connections for the potentiometers, and their outputs in the serial monitor, they are all functioning correctly.

    #include <SoftwareSerial.h>
    const int DEFAULT_BAUD = 9600;
    const int SERVO_CONTROLLER_RX_PIN = 0; // The SERVO CONTROLLER'S RX PIN.
    const int SERVO_CONTROLLER_TX_PIN = 1; // The SERVO CONTROLLER'S TX PIN.
    const int pot0 = A0; //input pins
    const int pot1 = A1;
    const int pot2 = A11;
    const int pot3 = A10;
    const int pot4 = A8;
    const int pot5 = A9;
    const int led = 13;
    const int led2 = 11;
    int readings[6][10];    // the readings from the analog input
    int intArray[3][6];  //array that holds 0=readIndex, 1=Total and 2=Average, Respectively

    SoftwareSerial ServoController = SoftwareSerial(SERVO_CONTROLLER_RX_PIN, SERVO_CONTROLLER_TX_PIN);

    void setup(){
      ServoController.begin(DEFAULT_BAUD);
       delay(500);   
       Serial.begin(9600);
      memset(readings, 0, sizeof(readings));
      memset(intArray, 0, sizeof(intArray));
       pinMode(led2, OUTPUT);
      }

    void moveServo(int ServoChannel, int target){
       //656ms PWM pulse represents a servo angle of 0 degrees.
       //2000ms PWM pulse represents a servo angele of 180 degrees.
       byte serialBytes[4]; //Create the byte array object that will hold the communication packet.
       target = (map(target, 0, 180, 656, 2000)*5); //Map the target angle to the corresponding PWM pulse.
       serialBytes[0] = 0x84; // Command byte: Set Target.
       serialBytes[1] = ServoChannel; // First byte holds channel number.
       serialBytes[2] = target & 0x7F; // Second byte holds the lower 7 bits of target.
       serialBytes[3] = (target >> 7) & 0x7F; // Third byte holds the bits 7-13 of target.
       ServoController.write(serialBytes, sizeof(serialBytes)); //Write the byte array to the serial port.
    }

    void smoothing(int channel, int pin){
      intArray[1][channel] = intArray[1][channel] - readings[channel][intArray[0][channel]];  //subtract the last reading:
      analogReset();    //function to clear residual analogue readings
      readings[channel][intArray[0][channel]] = analogRead(pin);  // read from the sensor
      intArray[1][channel] = intArray[1][channel] + readings[channel][intArray[0][channel]];    // add the reading to the total
      intArray[0][channel] = intArray[0][channel]+1;    // advance to the next position in the array
      if (intArray[0][channel] >= 10){    // if we're at the end of the array...
        intArray[0][channel] = 0;      // ...wrap around to the beginning
      }
      intArray[2][channel] = intArray[1][channel] / 10;    // calculate the average
      Serial.println(intArray[2][channel]);  //print average in console 
      intArray[2][channel] = map(intArray[2][channel], 0, 1023, 0, 180);     
      moveServo(channel, (intArray[2][channel]));
    }

    void loop(){   
      smoothing(0, pot0);
      smoothing(1, pot1);
      smoothing(2, pot2);
      smoothing(3, pot3);
      smoothing(4, pot4);
      smoothing(5, pot5);
      digitalWrite(led, HIGH);
      digitalWrite(led2, HIGH);
    }

    void analogReset(){
    #if defined(ADMUX)
    #if defined(ADCSRB) && defined(MUX5)
        // Code for the ATmega2560 and ATmega32U4
        ADCSRB |= (1 << MUX5);
    #endif
        ADMUX = 0x1F;
        // Start the conversion and wait for it to finish.
        ADCSRA |= (1 << ADSC);
        loop_until_bit_is_clear(ADCSRA, ADSC);
    #endif
    }

#18

It looks like you are mapping the target positions to be between 820 and 2500 microseconds. As a reference, pulse widths from standard RC hobby signals are typically between 1000 and 2000 microseconds.

Each channel on the Maestro has a setting for the minimum and maximum pulse widths it will send, and it sounds like you might be sending commands that set the target to some values outside of this limit. The default minimum and maximum values for each servo channel are 992 and 2000 microseconds respectively, but they can be configured in the “Channel Settings” tab of the Maestro Configuration Utility. Please note that sending target positions that drive your servos beyond their end stops could damage the servos, so you should make sure that your servos can accept the pulse widths you are sending. If you are not sure what your servos are capable of, you can find information for how to use the Maestro to find the range of your servo under the “FAQs” tab of the Maestro’s product page.

Brandon