The Netduino forums have been replaced by new forums at community.wildernesslabs.co.
This site has been preserved for archival purposes only
and the ability to make new accounts or posts has been turned off.
Continuous Rotation Servo on the Shield Base controlled with Potentiometer
Thanks to the latest PWM Shield Base update I now have a basic continuous rotation servo example to share.
For this example I am using a Parallax Continuous Rotation Servo and a Potentiometer module to control the speed and direction of the servo. To connect it to the Shield Base, attach the red wire to the 5V header, and the black wire to the GND on the Shield Base. The signal wire, in this case yellow, will connect to digital pin D5. Connect the Shield Base to the socket 4 of the Netduino Go! and the Potentiometer module to socket 6. Please note that while the Shield Base is still in beta, it will use an entire channel of sockets. Therefore if it's plugged into socket 4, you can only plug other modules into sockets 5-8.
After creating a new Netduino Go Application you will need to add references to the Potentiometer, Shield Base, and Microsoft.SPOT.Hardware.PWM. Since the Shield Base is still in beta, you will need browse to the location of the beta assembly files (download here), or if you have already used the Shield Base you can find it in the 'Recent' tab of the 'Add References' dialog screen. The Potentiometer and Microsoft.SPOT.Hardware.PWM (ver. 4.2.0.1) assemblies can be found in the '.Net' tab. Once you have added these as references we will add the following to the top of our project's template:
using NetduinoGo;
Our next step is to add to the Main method of our project the following code:
ShieldBase shieldbase = new ShieldBase(GoSockets.Socket4);Potentiometer pot = new Potentiometer(GoSockets.Socket6);
This will create two new objects for both the Shield Base and Potentiometer and tell the application to which sockets the modules will be connected.
Next we will create a new PWM object called servo. The new 4.2 .NetMF PWM class has two constructors; one that is mainly use for actuators such as a Piezo or LED, and the other used for the control of things like a servo. In our example we will initialize a new instance of the PWM class with the PWMChannel that the signal line is connected to, the period, duration, a scale factor, and a value that indicates whether the output is inverted, in our case it is false.
PWM servo = new PWM(shieldbase.PWMChannels.PWM_0, 20000, 1500, PWM.ScaleFactor.Microseconds, false);
Our first argument being passed is the PWMChannel.PWM_0 which is found on D3. The next two arguments set the Period and Duration properties of the servo object in microseconds, because we are using the Microseconds as a scale factor.
In the case of the Parallax servo, it requires a 1.5 ms pulse (1.5 ms is 1500 [color=rgb(68,68,68);font-family:arial, sans-serif;font-size:small;]?[/color]s), continually refreshed every 20 ms to centre it as shown in the following diagram. Servo-centre.gif5.9KB86 downloads
The servo will begin to turn clockwise with pulses of any value below 1.5 ms and will gradually increase speed until reaching 1.3 ms, while pausing in between pulses for 20 ms for the smoothest movement. Servo-clockwise.gif9.67KB87 downloads
In order to turn the servo counter-clockwise, we will pulse the servo any value above 1.5 ms, and up to the maximum speed at pulses of 1.7 ms. Servo-counterclockwise.gif9.14KB57 downloads
WARNING: Not all servos will work under the same conditions, however most do. You should find the appropriate pulse widths for your specific servo to prevent any potential damage.
So how does all this translate into code? Well let's jump into it and see. Our next step will be to add two values that will represent the maximum clockwise speed, and the maximum counter-clockwise speed. Then we'll start the PWM port for an indefinite amount of time with the Start method.
int maxClockwise = 1300; // Full speed clockwise direction int maxCounterClockwise = 1700; // Full speed counter-clockwise directionservo.Start(); // Start the PWM port.
At this point if we ran our application the servo should stay in its centre position. If you notice that it doesn't you may need to calibrate the servo. To do this gently twist the potentiometer adjustment screw, found just above the wires, with a small Phillips screwdriver until the servo does not turn or vibrate.
The next portion of our code will be to control the servo with the input we receive from the Potentiometer module. The GetValue method of the Potentiometer class will return a float anywhere from 0.0 to 1.0. For our purposes we will want to convert that value to something in the range of 1300 and 1700. To do so we will use a Mapping method that re-maps a number from one range to another.
In our example the input will be the value we receive from GetValue(). The inMin and inMax values are the range of values we would get from the potentiometer, 0 and 1 respectively. The outMin, and outMax are the values that we want to see in our new range, so we will use 1300 and 1700. To see how we implement this in our code, let's setup up an infinite loop to repeatedly poll the potentiometer and set the servo speed and direction accordingly. We will also pause for 20ms before we change the duration of the pulse to the servo.
while (true){ float ptValue = Map(pot.GetValue(), 0, 1, maxClockwise, maxCounterClockwise); servo.Duration = (uint)ptValue; // A value from 1300-1700 Thread.Sleep(20); // Sleep for 20 ms}
Now deploy the application and you should see your servo rotate in cordination with the twisting of your Potentiometer module. If you want to see the actual pulse value that your servo is receiving, you can add the following just before you set the Duration property of your servo.
Great job Steve and a very good lesson on servos/programming. It would be perfect if the file contained a free servo in it.
I will order one tonight.
By the way, I was working on building a pinout card for the shield base but only identified 4 pwm channels on it so far. Do you know the pins for all 6 channels?
Chuck
We should celebrate. If you PM me your address, we'll send you one of the new Piezo Buzzer modules
Now, for more important things...
By the way, I was working on building a pinout card for the shield base but only identified 4 pwm channels on it so far. Do you know the pins for all 6 channels?
The two new PWM channels on Shield Base are on D3 and D11. These are consistent with the Arduino pinout...so this should give you enhanced compatibility with Arduino motor/servo shields.
Hi Steve,
After upgrading the firmware of the shield base I noticed my duration for the full left and right action has changed.
Before I upgraded the values where as follows, full left duration 1020, full right 4720
However now the duration full left starts at around 720 and ends full right at around 2300.
This is the 180 degrees servo, so basically the resolution between the two has changes.
In the old firmware the resolution was 4720 - 1020 = 3700
New firmware 2300 - 720 = 1580 so basically halve the resolution!
Any ideas?
Cheers,
Olaf
Great write up! This inspired me to pull out some old servos and start playing with the shield and PWM. (I'm so new to all this microcontroller stuff, but having far too much fun.) I ended up creating some classes to make it easier to play with the servos. I thought I'd post it to get feedback. (Who knows, others may even find it useful.)
First, I defined a class "Servo" that extends the Microsoft.SPOT.Hardware.PWM class. The class adds methods to center the servo, to rotate it given a specified angle, and to position it given a bounded value (e.g. POT value).
The servo class is designed to work with the ServoFactory static class. ServoFactory lets you configure settings for different servos and offers a single static method to construct a servo instance. I use this rather than instantiating the servo's directly. The servo type, defined in an enum, determines how the servo behaves in terms of allowed range of movement, pulse widths, etc.
/// <summary>
/// This class encapsulates the operation of a servo, allowing for easy operations to set angle and return-to-center.
/// </summary>
public class Servo : PWM
{
/// <summary>
/// Constructs an instance of the servo. Using the ServoFactory to create an instance of a specific servo type
/// is the recommended method of servo instantiation
/// </summary>
/// <param name="channel">The PWM channel to which the servo is connected</param>
/// <param name="info">Struct containing servo-specific functional parameters</param>
/// <param name="reverse">Indicates whether or not the default servo rotation should be reversed</param>
/// <param name="maxAngle">Sets the maximum angle the servo can travel.</param>
public Servo(Cpu.PWMChannel channel, ServoFactory.ServoConfig info, bool reverse, double maxAngle)
: base(channel, 20000, info.Neutral, ScaleFactor.Microseconds, info.Invert)
{
ReverseMult = reverse ? -1 : 1;
MaxAngle = maxAngle;
Settings = info;
}
/// <summary>
/// A multiplier used to negate values if reverse mode is desired.
/// </summary>
private float ReverseMult;
/// <summary>
/// Holds the maximum rotation angle allowed
/// </summary>
private double MaxAngle;
/// <summary>
/// Holds a reference to the servo's parameter values
/// </summary>
private ServoFactory.ServoConfig Settings;
/// <summary>
/// Returns the servo to it's neutral position
/// </summary>
public void Center()
{
base.Duration = Settings.Neutral;
}
/// <summary>
/// Moves the servo to the specified angle, or the max angle if outside those bounds
/// </summary>
/// <param name="angle">The position angle in degrees</param>
public void TurnTo(double angle)
{
double a = System.Math.Min(System.Math.Abs(angle), Settings.MaxRotationAngle);
if (angle < 0)
a *= -1;
base.Duration = (uint)(Settings.Neutral + (a / Settings.AngleFromNeutral * Settings.DegPulse * ReverseMult));
}
public void ReverseRotation()
{
ReverseMult *= -1;
}
/// <summary>
/// Moves a servo to a position based on the input relative to the min and max bounds. inMin corresponds to the minimum
/// servo angle and inMax the maximum angle.
/// </summary>
/// <param name="input">desired value</param>
/// <param name="inMin">lower bound</param>
/// <param name="inMax">upper bound</param>
public void MoveTo(double input, double inMin, double inMax)
{
double outMin = Settings.Neutral - ReverseMult * (MaxAngle / Settings.AngleFromNeutral * Settings.DegPulse);
double outMax = Settings.Neutral + ReverseMult * (MaxAngle / Settings.AngleFromNeutral * Settings.DegPulse);
base.Duration = (uint)((input - inMin) * (outMax - outMin) / (inMax - inMin) + outMin);
}
}
/// <summary>
/// This is a static helper class that provides a factory method to construct a known set of servo types
/// based on their configuration data.
/// </summary>
public static class ServoFactory
{
/// <summary>
/// Defines parameters identifying operational performance of a servo
/// </summary>
public struct ServoConfig
{
/// <summary>
/// Enumeration identifying the servo type
/// </summary>
public ServoType Type;
/// <summary>
/// Holds the pulse width in # of microseconds to set server neutral position
/// </summary>
public uint Neutral;
/// <summary>
/// Holds the pulse adjustment to rotate the servo the specified pulse angle
/// </summary>
public uint DegPulse;
/// <summary>
/// Holds the pulse angle set when the DegPulse value is applied to the neutral pulse width
/// For example, 400 microseconds with a DegPulse of 45 Deg indicates that the servo will rotate
/// 45 deg when the pulse is added to neutral
/// </summary>
public double AngleFromNeutral;
/// <summary>
/// Sets the maximum rotation angle for the servo - ensures that users don't try to over-rotate the servo
/// </summary>
public double MaxRotationAngle;
/// <summary>
/// Sets a boolean indicating whether or not to invert the PWM signal
/// </summary>
public bool Invert;
}
/// <summary>
/// Array holding information for each registered servo type
/// </summary>
private static ServoConfig[] registeredServos;
// To Add additional servo definitions, add an enum value above ServoCount and add a record
// to the ServoInfo array in the initialize method
/// <summary>
/// Identifies the servos available.
/// </summary>
public enum ServoType
{
Airtronics_94102,
Futuba_S3004,
ServoCount
}
/// <summary>
/// Initializes the parameters for the known registered servo types
/// </summary>
private static void Initialize()
{
registeredServos = new ServoConfig[(int)ServoType.ServoCount];
registeredServos[(int)ServoType.Futuba_S3004] = new ServoConfig { Type = ServoType.Futuba_S3004, Neutral = 1520, DegPulse = 400, AngleFromNeutral = 45, MaxRotationAngle = 90, Invert = false };
registeredServos[(int)ServoType.Airtronics_94102] = new ServoConfig { Type = ServoType.Airtronics_94102, Neutral = 1500, DegPulse = 390, AngleFromNeutral = 45, MaxRotationAngle = 90, Invert = false };
}
/// <summary>
/// Factory method that creates an instance of the Servo class, initialized from the available servo information
/// </summary>
/// <param name="channel">The PWM channel the servo is connected to</param>
/// <param name="type">The enumeration value for a known servo type</param>
/// <param name="reverse">If true, the requests to move the servo are reversed from their default behavior</param>
/// <param name="maxAngle">Sets the maximum allowed rotation angle - defaults to the maximum specified for the servo, but can be made to be less</param>
/// <returns>An instance of the servo</returns>
/// <exception cref="UnknownTypeException">Throws this if the servo type is not defined.</exception>
public static Servo CreateServo(Cpu.PWMChannel channel, ServoType type, bool reverse = false, double maxAngle = double.NaN)
{
if (registeredServos == null)
Initialize();
foreach (var servoInfo in registeredServos)
{
if (servoInfo.Type == type)
return new Servo(channel,
servoInfo,
reverse,
(maxAngle == double.NaN) ? servoInfo.MaxRotationAngle : System.Math.Min(servoInfo.MaxRotationAngle, maxAngle));
}
// If you got here, the servo type was not initialized.
throw new UnknownTypeException();
}
}
An example of how I used this code is shown below.
namespace ServoFun
{
public class Program
{
private static ShieldBase shieldBase;
private static NetduinoGo.Button btn;
private static Servo servo;
static bool started;
public static void Main()
{
btn = new NetduinoGo.Button(GoSockets.Socket1);
btn.ButtonReleased += new NetduinoGo.Button.ButtonEventHandler(btn_ButtonReleased);
shieldBase = new ShieldBase(GoSockets.Socket5);
// Create our type-specific servo
servo = ServoFactory.CreateServo(shieldBase.PWMChannels.PWM_PIN_D5, ServoFactory.ServoType.Futuba_S3004);
servo.Start();
servo.Center();
started = true;
while (true)
{
Thread.Sleep(100);
}
}
static void btn_ButtonReleased(object sender, bool isPressed)
{
if (started)
{
if (ndx >= testAngles.Length)
{
// Once we get to last test data angle, stop the servo
servo.Stop();
started = false;
}
else
{
// Test servo rotation.
servo.TurnTo(testAngles[ndx++]);
}
}
else
{
// Pressing after stop restarts the servo and re-centers it.
servo.Start();
servo.Center();
ndx = 0;
started = true;
}
}
static float[] testAngles = new float[] { 18, 45,-45,-90}; // Specify a set of rotational angles
static int ndx = 0; // Index into my test data.
}
}