ValveControl Arduino Sketch¶
This sketch can operate a set of pneumatic axes using various valve configurations. This version only provides open-loop or closed-loop control and is suitable as a driver program to be controlled over USB from a separate performance host program.
The sketch files can be found in the ValveControl folder, and are also available in a single zip file.
Contents
Top-Level Functions¶
-
void
setup
(void)¶ Standard Arduino initialization function to configure the system. This code will need to be modified to initialize the channel[] array with Pneumatic objects matching the specific valve and manifold configuration, and the controller[] array with the physical axis specifications.
-
void
loop
()¶ Standard Arduino polling function to handle all I/O and periodic processing. This function is called repeatedly as fast as possible from within the built-in library to poll program events. This loop should never be allowed to stall or block so that all tasks can be constantly serviced.
Global Values¶
-
Pneumatic
channel
[NUM_CHANNELS]¶ Control object for each pneumatic channel. The declaration statically initializes the global state objects for the channels. Note that this does not initialize the hardware; that is performed in setup().
-
Joint
controller
[NUM_JOINTS]¶ Control object for each closed-loop joint controller. These objects manage the sensor input for each joint and can generate control signals to one or more Pneumatic objects. The declaration statically initializes the global state objects for the axes. Note that this does not initialize the hardware; that is performed in setup().
ASCII Messaging Protocol¶
-
static void
vc_parse_input_message
(int argc, char *argv[])¶ Process an input message received from the USB port. Unrecognized commands are silently ignored.
- Parameters
argc
: number of argument tokensargv
: array of pointers to strings, one per token
-
static void
vc_hardware_poll
(long interval)¶ Polling function to update all background control tasks.
-
static void
vc_serial_input_poll
(long interval)¶ Polling function to process messages arriving over the serial port. Each iteration through this polling function processes at most one character. It records the input message line into a buffer while simultaneously dividing it into ‘tokens’ delimited by whitespace. Each token is a string of non-whitespace characters, and might represent either a symbol or an integer. Once a message is complete, parse_input_message() is called.
Pneumatic Channel Controller Class¶
-
class
Pneumatic
¶ An instance of this class manages generation of fill and empty signals for one pneumatic channel.
Public Types
Public Functions
-
Pneumatic
()¶ Default constructor called during array initialization. Note that this is a no-op, since each object needs to be properly initalized with unique pin numbers later.
-
Pneumatic
(int8_t _fill_pin, int8_t _empty_pin, enum pneumatic_config_t _config)¶ Main constructor. The arguments are the pin numbers for the step and direction outputs. Note: this does not initialize the underlying hardware. Unused outputs should supply a pin number of -1.
-
void
begin
(void)¶ Hardware initialization. This class follows the Arduino convention of initializing instance data in the constructor but not hardware.
-
void
setDutyCycle
(float _duty)¶ Set the valve duty cycle and enable open-loop output mode. The duty cycle is specified from -1.0 to 1.0; positive values fill, negative values empty.
-
void
update
(long interval)¶ Main polling function to be called as often as possible. The interval argument is the duration in microseconds since the last call.
Private Types
Private Functions
-
void
set_valves
(bool _fill, bool _empty)¶ Utility function to set valve state and flags.
Private Members
-
int8_t
fill_pin
¶ The I/O pins for this channel designated using the Arduino convention.
-
int8_t
empty_pin
¶
-
pneumatic_config_t
config
¶ Configuration of the valve set for this axis.
-
bool
fill
¶ Current actuator state.
-
bool
empty
¶
-
long
timer
¶ Countdown for the current actuator signal phase, in microseconds.
-
long
period
¶ Duration of the full cycle, in microseconds.
-
long
active
¶ Duration of the active period. Positive values fill, negative values empty.
-
float
duty
¶ Current duty cycle fraction (-1 to 1).
-
Pneumatic::pneumatic_state_t
mode
¶
-
Joint Axis Controller Class¶
-
class
Joint
¶ An instance of this class manages generation of fill and empty signals for one joint axis.
Public Types
Public Functions
-
Joint
()¶ Default constructor called during array initialization. Note that this is a no-op, since each object needs to be properly initalized with unique pin numbers later.
-
Joint
(enum joint_config_t _config, Pneumatic *_push, Pneumatic *_pull = NULL, int8_t _position_pin = -1, float _scale = 0.32, float _offset = 512)¶ Main constructor. Note: this does not initialize the underlying hardware.
-
void
begin
(void)¶ Hardware initialization. This class follows the Arduino convention of initializing instance data in the constructor but not hardware.
-
void
update
(long interval)¶ Main polling function to be called as often as possible. The interval argument is the duration in microseconds since the last call.
-
void
send_status
(void)¶ Send a status output message to the serial port. This should not be called too often to avoid stalling.
-
void
setTargetPosition
(float _degrees)¶ Set the position target.
-
void
setMode
(Joint::joint_state_t _mode)¶ Set the control mode.
-
void
setScale
(float _scale)¶ Update the position sensor gain.
-
void
setOffset
(int _offset)¶ Update the position sensor offset.
Private Members
-
int8_t
position_pin
¶ The I/O pins for this channel designated using the Arduino convention.
pin designator for the analog position input (e.g. A0)
-
joint_config_t
config
¶ Configuration of the valve set for this axis.
-
long
sensor_timer
¶ Countdown for the sensor measurement period, in microseconds.
-
long
sensor_period
¶ Duration of the sensor measurement cycle, in microseconds.
-
long
control_timer
¶ Countdown for the control period, in microseconds.
-
long
control_period
¶ Duration of the control cycle, in microseconds.
-
int
raw_position
¶ Raw position signal, in ADC units.
-
float
position
¶ Filtered position signal, in degrees.
-
float
scale
¶ Scaling between ADC units and degrees, units are degree/ADC unit.
-
int
offset
¶ Offset subtracted from raw ADC signal prior to scaling, in ADC units.
-
float
desired_position
¶ Current position target.
-
float
k_position
¶ Proportional position gain, units are 1/degree.
-
float
output_pwm
¶ Current output PWM rate.
-
joint_state_t
mode
¶ Current control mode for single axis.
-
ValveControl.ino¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 | /// \file ValveControl.ino
///
/// \brief Hardware I/O driver for controlling pneumatic valves using a simple message protocol.
///
/// \copyright Copyright (c) 2014-2016, Garth Zeglin. All rights
/// reserved. Licensed under the terms of the BSD 3-clause license.
///
/// \details This example is intended as a starting point for creating custom
/// firmware for driving one or more pneumatic actuators. It includes a
/// simple ASCII protocol for sending motion commands to ease
/// connecting to dynamic code (e.g. Max or Python) running on a laptop
/// or Raspberry Pi.
#include "Pneumatic.h"
#include "Joint.h"
/****************************************************************/
/**** ASCII messaging scheme ************************************/
/****************************************************************/
// The message protocol is based on commands encoded as a sequence of string
// tokens and integers in a line of text. One line is one message. All the
// input message formats begin with a string naming a specific command or
// destination followed by one or two argument integers. The output formats are
// similar but include more general debugging output with a variable number of
// tokens.
// The following message formats are recognized by this program.
// Command Arguments Meaning
// ping query whether the controller is running
// stop set all valve channels to a neutral state
// led <value> controls the built-in LED, value is 0 or non-zero
// speed <axis> <value> sets the PWM rate and direction; axis is an integer from 1 to N, value ranges over -100 to 100
// pos <axis> <value> sets the joint position target; axis is an integer from 1 to N, value is in degrees
// This program generates the following messages:
// Command Arguments Meaning
// awake initialization has completed or ping was received
// led <value> reply with current LED state
/****************************************************************/
/**** Global variables and constants ****************************/
/****************************************************************/
// The baud rate is the number of bits per second transmitted over the serial port.
#define BAUD_RATE 115200
// Some versions of the Arduino IDE don't correctly define this symbol for an
// Arduino Uno. Note that this pin definition is potentially shared with
// SPINDLE_DIR_PIN.
#ifndef LED_BUILTIN
#define LED_BUILTIN 13
#endif
/****************************************************************/
/// Property specification for each pneumatic axis. This is used to define an
/// initialization table for the specific connected hardware.
struct axis_config_t {
int8_t fill, empty;
enum Pneumatic::pneumatic_config_t config;
};
#if 1
// Declare the hardware configuration for a valve card in the valve stack rack.
static struct axis_config_t config_table[] = {
{ 2, 3, Pneumatic::FILL_EMPTY },
{ 4, 5, Pneumatic::FILL_EMPTY },
{ 6, 7, Pneumatic::FILL_EMPTY },
{ 8, 9, Pneumatic::FILL_EMPTY }
};
#endif
#if 0
// Declare the hardware configuration for the 'instructor station'.
static struct axis_config_t config_table[] = {
// { 3, 5, Pneumatic::PROP_FILL_PROP_EMPTY },
// { 6, 9, Pneumatic::PROP_FILL_PROP_EMPTY },
{ 3, 5, Pneumatic::FILL_EMPTY },
{ 6, 9, Pneumatic::FILL_EMPTY },
{ 2, 4, Pneumatic::FILL_EMPTY },
{ 7, -1, Pneumatic::FILL_THREEWAY },
{ 8, -1, Pneumatic::FILL_THREEWAY }
};
#endif
/// Compute the number of entries in the axis table in use.
#define NUM_CHANNELS (sizeof(config_table) / sizeof(struct axis_config_t))
/// Control object for each pneumatic channel. The declaration
/// statically initializes the global state objects for the
/// channels. Note that this does not initialize the hardware;
/// that is performed in setup().
static Pneumatic channel[NUM_CHANNELS];
/// Define the number of physical axes to control.
#define NUM_JOINTS 2
/// Control object for each closed-loop joint controller. These objects manage
/// the sensor input for each joint and can generate control signals to one or
/// more Pneumatic objects. The declaration statically initializes the global
/// state objects for the axes. Note that this does not initialize the
/// hardware; that is performed in setup().
static Joint controller[NUM_JOINTS];
/****************************************************************/
/****************************************************************/
/// Process an input message received from the USB port.
/// Unrecognized commands are silently ignored.
/// \param argc number of argument tokens
/// \param argv array of pointers to strings, one per token
static void vc_parse_input_message(int argc, char *argv[])
{
// Interpret the first token as a command symbol.
char *command = argv[0];
/* -- process zero-argument commands --------------------------- */
if (argc == 1) {
if ( string_equal( command, "ping" )) {
send_message("awake");
} else if (string_equal(command, "stop")) {
// stop all feedback control and close off all manifolds
for (int i = 0; i < NUM_JOINTS; i++) controller[i].setMode(Joint::IDLE);
for (int i = 0; i < NUM_CHANNELS; i++) channel[i].setDutyCycle(0.0);
} else if (string_equal(command, "empty")) {
// stop all feedback control and empty all manifolds
for (int i = 0; i < NUM_JOINTS; i++) controller[i].setMode(Joint::IDLE);
for (int i = 0; i < NUM_CHANNELS; i++) channel[i].setDutyCycle(-1.0);
}
}
/* -- process one-argument commands --------------------------- */
else if (argc == 2) {
long value = atol(argv[1] );
// Process the 'led' command.
if ( string_equal( command, "led" )) {
#ifdef LED_BUILTIN
// turn on the LED if that value is true, then echo it back as a handshake
digitalWrite(LED_BUILTIN, (value != 0) ? HIGH : LOW);
#endif
send_message( "led", value );
}
}
/* -- process two-argument commands --------------------------- */
else if (argc == 3) {
if ( string_equal( command, "speed" )) {
int axis = atoi(argv[1]);
int value = atoi(argv[2]);
// takes an integer value between -100 and 100
if (axis > 0 && axis <= NUM_CHANNELS) channel[axis-1].setDutyCycle( 0.01 * value );
}
else if ( string_equal( command, "pos" )) {
int axis = atoi(argv[1]); // joint number starting with 1
int value = atoi(argv[2]); // in degrees
if (axis > 0 && axis <= NUM_JOINTS) {
controller[axis-1].setMode(Joint::POSITION);
controller[axis-1].setTargetPosition((float) value);
}
}
else if ( string_equal( command, "offset" )) {
int axis = atoi(argv[1]); // joint number starting with 1
int value = atoi(argv[2]); // in raw ADC units
if (axis > 0 && axis <= NUM_JOINTS) controller[axis-1].setOffset(value);
}
else if ( string_equal( command, "scale" )) {
int axis = atoi(argv[1]); // joint number starting with 1
float value = atof(argv[2]); // in degree/raw ADC units
if (axis > 0 && axis <= NUM_JOINTS) controller[axis-1].setScale(value);
}
}
}
/****************************************************************/
/// Polling function to update all background control tasks.
static void vc_hardware_poll(long interval)
{
for (int i = 0; i < NUM_JOINTS; i++) {
controller[i].update(interval);
}
for (int i = 0; i < NUM_CHANNELS; i++) {
channel[i].update(interval);
}
}
/****************************************************************/
/**** Standard entry points for Arduino system ******************/
/****************************************************************/
/// Standard Arduino initialization function to configure the system. This code
/// will need to be modified to initialize the channel[] array with Pneumatic
/// objects matching the specific valve and manifold configuration, and the
/// controller[] array with the physical axis specifications.
void setup(void)
{
// Initialize the array of axis control objects and valve I/O as soon as possible.
for (int i = 0; i < NUM_CHANNELS; i++) {
channel[i] = Pneumatic(config_table[i].fill, config_table[i].empty, config_table[i].config);
channel[i].begin();
}
// Initialize a closed loop controller. The scale and offset were calculated empirically and
// will need to be adjusted for every individual joint.
// single-ended test:
// controller[0] = Joint(Joint::SINGLE_FILL_EMPTY, &channel[0], NULL, A0, -0.338, 608);
// Our default joints are double-ended:
controller[0] = Joint(Joint::DUAL_FILL_EMPTY, &channel[0], &channel[1], A0, 0.338, 512);
controller[1] = Joint(Joint::DUAL_FILL_EMPTY, &channel[2], &channel[3], A1, 0.338, 512);
#ifdef LED_BUILTIN
pinMode( LED_BUILTIN, OUTPUT );
#endif
// initialize the Serial port
Serial.begin( BAUD_RATE );
// additional hardware configuration can go here
// send a wakeup message
send_message("awake");
}
/****************************************************************/
/// Standard Arduino polling function to handle all I/O and periodic processing.
/// This function is called repeatedly as fast as possible from within the
/// built-in library to poll program events. This loop should never be allowed
/// to stall or block so that all tasks can be constantly serviced.
void loop()
{
// The timestamp in microseconds for the last polling cycle, used to compute
// the exact interval between output updates.
static unsigned long last_update_clock = 0;
// Read the microsecond clock.
unsigned long now = micros();
// Compute the time elapsed since the last poll. This will correctly handle wrapround of
// the 32-bit long time value given the properties of twos-complement arithmetic.
unsigned long interval = now - last_update_clock;
last_update_clock = now;
// Begin the polling cycle.
vc_hardware_poll(interval);
vc_serial_input_poll(interval);
vc_status_output_poll(interval);
// other polled tasks can go here
}
/****************************************************************/
void vc_status_output_poll(long interval)
{
static long timer = 0;
const long period = 100000; // 10 Hz
// Wait for the next status output event time point.
timer -= interval;
if (timer <= 0) {
timer += period;
controller[0].send_status();
Serial.print(" ");
controller[1].send_status();
Serial.println();
}
}
/****************************************************************/
|
Joint.h¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | /// \file Joint.h
/// \brief Pneumatic axis controller for one joint.
/// \copyright Copyright (c) 2015-2016, Garth Zeglin. All rights reserved. Licensed under the terms of the BSD 3-clause license.
/// \details Pneumatic axis controller for one joint, using separate fill and empty valves.
#ifndef __JOINT_H_INCLUDED__
#define __JOINT_H_INCLUDED__
#include <Arduino.h>
#include <stdint.h>
#include "Pneumatic.h"
/// An instance of this class manages generation of fill and empty signals for
/// one joint axis.
class Joint {
public:
/// Valve configuration for the axis.
enum joint_config_t { SINGLE_FILL_EMPTY, ///< single set of fill/empty valves, with gravity or spring return
DUAL_FILL_EMPTY, ///< double set of fill/empty valves to actively push and pull
NUM_JOINT_CONFIGURATIONS ///< the total number of defined valve configuration modes
};
/// Control modes for the axis.
enum joint_state_t { IDLE, ///< no control computed, no outputs updated
POSITION, ///< proportional position control
NUM_JOINT_MODES ///< the total number of axis control modes
};
private:
/****************************************************************/
// The following instance variables may only be modified from a non-interrupt
// context, i.e., not within poll().
/// The underlying Pneumatic objects manage generate valve switching signals.
Pneumatic *push, *pull;
/// The I/O pins for this channel designated using the Arduino convention.
int8_t position_pin; ///< pin designator for the analog position input (e.g. A0)
/// Configuration of the valve set for this axis.
enum joint_config_t config;
/// Countdown for the sensor measurement period, in microseconds.
long sensor_timer;
/// Duration of the sensor measurement cycle, in microseconds.
long sensor_period;
/// Countdown for the control period, in microseconds.
long control_timer;
/// Duration of the control cycle, in microseconds.
long control_period;
/// Raw position signal, in ADC units.
int raw_position;
/// Filtered position signal, in degrees.
float position;
/// Scaling between ADC units and degrees, units are degree/ADC unit.
float scale;
/// Offset subtracted from raw ADC signal prior to scaling, in ADC units.
int offset;
/// Current position target.
float desired_position;
/// Proportional position gain, units are 1/degree.
float k_position;
/// Current output PWM rate.
float output_pwm;
/// Current control mode for single axis.
enum joint_state_t mode;
/****************************************************************/
public:
/// Default constructor called during array initialization. Note that this is
/// a no-op, since each object needs to be properly initalized with unique pin
/// numbers later.
Joint() { }
/// Main constructor. Note: this does not initialize the underlying hardware.
Joint( enum joint_config_t _config, Pneumatic *_push, Pneumatic *_pull = NULL, int8_t _position_pin = -1, float _scale = 0.32, float _offset = 512 ) {
push = _push;
pull = _pull;
position_pin = _position_pin;
config = _config;
control_timer = 0;
sensor_timer = 0;
sensor_period = 5000; // 200Hz
control_period = 50000; // 20Hz
raw_position = 0;
position = 0.0;
scale = _scale;
offset = _offset;
mode = IDLE;
desired_position = 0.0;
k_position = 1.0 / 30.0; // full-on PWM at 30 degrees error
output_pwm = 0.0;
}
/// Hardware initialization. This class follows the Arduino convention of
/// initializing instance data in the constructor but not hardware.
void begin(void) {
// Read the analog input once to guarantee the ADC mode and initialize the filter.
if (position_pin >= 0) {
raw_position = analogRead(position_pin);
position = (raw_position - offset) * scale;
}
}
/// Main polling function to be called as often as possible. The interval
/// argument is the duration in microseconds since the last call.
void update(long interval);
/// Send a status output message to the serial port. This should not be called too often to avoid stalling.
void send_status(void);
/// Set the position target.
void setTargetPosition(float _degrees) {
desired_position = _degrees;
}
/// Set the control mode.
void setMode(Joint::joint_state_t _mode) {
mode = _mode;
}
/// Update the position sensor gain.
void setScale(float _scale) {
scale = _scale;
}
/// Update the position sensor offset.
void setOffset(int _offset) {
offset = _offset;
}
};
#endif //__JOINT_H_INCLUDED__
|
Joint.cpp¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | /// \file Joint.cpp
/// \copyright Copyright (c) 2016, Garth Zeglin. All rights reserved. Licensed under the terms of the BSD 3-clause license.
#include "Joint.h"
void Joint::update(long interval)
{
// Wait for the next sensor measurement time point.
sensor_timer -= interval;
if (sensor_timer <= 0) {
sensor_timer += sensor_period;
// Always process sensor input if available.
if (position_pin >= 0) {
raw_position = analogRead(position_pin);
// Apply a linear calibration to convert from ADC units to degrees.
float calibrated_raw_position = (raw_position - offset) * scale;
// Apply a first-order low-pass filter to reduce the sampling noise.
position += 0.5 * (calibrated_raw_position - position);
}
}
// Wait for the next control cycle time point.
control_timer -= interval;
if (control_timer <= 0) {
control_timer += control_period;
// Compute
switch (mode) {
case IDLE:
break;
case POSITION:
float position_error = desired_position - position;
output_pwm = k_position * position_error;
if (push) {
push->setDutyCycle(output_pwm);
}
if (pull) {
pull->setDutyCycle(-output_pwm);
}
break;
}
}
}
/****************************************************************/
void Joint::send_status(void)
{
Serial.print(raw_position);
Serial.print(" ");
Serial.print(position, 2); // limit the output to two decimals
Serial.print(" ");
Serial.print(output_pwm, 2); // limit the output to two decimals
}
/****************************************************************/
|
Pneumatic.h¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | /// \file Pneumatic.h
/// \brief Pneumatic channel controller for one valve set.
/// \copyright Copyright (c) 2015-2016, Garth Zeglin. All rights reserved. Licensed under the terms of the BSD 3-clause license.
/// \details Pneumatic axis controller for one valve set. Different modes are available, including using separate fill and empty valves.
#ifndef __PNEUMATIC_H_INCLUDED__
#define __PNEUMATIC_H_INCLUDED__
#include <Arduino.h>
#include <stdint.h>
/// An instance of this class manages generation of fill and empty signals for
/// one pneumatic channel.
class Pneumatic {
public:
/// Valve configuration for the channel.
enum pneumatic_config_t { FILL_EMPTY, ///< pair of two-way valves: one fill, one empty
PROP_FILL_PROP_EMPTY, ///< pair of proportional two-way valves: one fill one empty
FILL_THREEWAY ///< single three-way valve: fills when active, empties otherwise
};
private:
/****************************************************************/
// The following instance variables may only be modified from a non-interrupt
// context, i.e., not within poll().
/// The I/O pins for this channel designated using the Arduino convention.
int8_t fill_pin, empty_pin;
/// Configuration of the valve set for this axis.
enum pneumatic_config_t config;
/// Current actuator state.
bool fill, empty;
/// Countdown for the current actuator signal phase, in microseconds.
long timer;
/// Duration of the full cycle, in microseconds.
long period;
/// Duration of the active period. Positive values fill, negative values empty.
long active;
/// Current duty cycle fraction (-1 to 1).
float duty;
/// Current control mode for single axis.
enum pneumatic_state_t { IDLE, ///< no air flow, commands ignored
OPEN_LOOP, ///< open-loop control with valve speed control via PWM
NUM_PNEUMATIC_MODES ///< the total number of axis modes
} mode;
/// Utility function to set valve state and flags.
void set_valves( bool _fill, bool _empty) {
fill = _fill;
empty = _empty;
switch (config) {
case FILL_EMPTY:
digitalWrite( fill_pin, (fill) ? (HIGH) : (LOW));
digitalWrite( empty_pin, (empty) ? (HIGH) : (LOW));
break;
case PROP_FILL_PROP_EMPTY:
// bypass the soft PWM if the valves are proportional
if (duty > 0.0) analogWrite(fill_pin, (int) (duty * 255));
else analogWrite(fill_pin, 0);
if (duty < 0.0) analogWrite(empty_pin, (int) (-duty * 255));
else analogWrite(empty_pin, 0);
break;
case FILL_THREEWAY:
digitalWrite( fill_pin, (fill) ? (HIGH) : (LOW));
break;
}
}
/****************************************************************/
public:
/// Default constructor called during array initialization. Note that this is
/// a no-op, since each object needs to be properly initalized with unique pin
/// numbers later.
Pneumatic() { }
/// Main constructor. The arguments are the pin numbers for the step and
/// direction outputs. Note: this does not initialize the underlying hardware.
/// Unused outputs should supply a pin number of -1.
Pneumatic( int8_t _fill_pin, int8_t _empty_pin, enum pneumatic_config_t _config) {
fill_pin = _fill_pin;
empty_pin = _empty_pin;
config = _config;
fill = false;
empty = false;
timer = 0;
period = 50000;
active = 0;
duty = 0.0;
mode = IDLE;
}
/// Hardware initialization. This class follows the Arduino convention of
/// initializing instance data in the constructor but not hardware.
void begin(void) {
if (fill_pin >= 0){
pinMode( fill_pin, OUTPUT );
digitalWrite( fill_pin, LOW );
}
if (empty_pin >= 0) {
pinMode( empty_pin, OUTPUT );
digitalWrite( empty_pin, LOW );
}
}
/// Set the valve duty cycle and enable open-loop output mode.
/// The duty cycle is specified from -1.0 to 1.0; positive values fill, negative values empty.
void setDutyCycle(float _duty) {
duty = constrain(_duty, -1.0, 1.0);
mode = OPEN_LOOP;
active = duty * period;
}
/// Main polling function to be called as often as possible. The interval
/// argument is the duration in microseconds since the last call.
void update(long interval);
};
#endif //__PNEUMATIC_H_INCLUDED__
|
Pneumatic.cpp¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | /// \file Pneumatic.cpp
/// \copyright Copyright (c) 2016, Garth Zeglin. All rights reserved. Licensed under the terms of the BSD 3-clause license.
#include "Pneumatic.h"
void Pneumatic::update(long interval)
{
switch (mode) {
case IDLE:
break;
case OPEN_LOOP:
// Wait for the next event time point.
timer -= interval;
if (timer <= 0) {
if (active == 0) { // no air flow
set_valves( false, false ); // fill, empty
timer += period;
} else if (active > 0) { // filling
if (fill) {
set_valves( false, false ); // fill, empty
timer += (period - active);
} else {
set_valves( true, false); // fill, empty
timer += active;
}
} else { // emptying
if (empty) {
set_valves( false, false ); // fill, empty
timer += (period - (-active));
} else {
set_valves( false, true); // fill, empty
timer += (-active);
}
}
}
break;
}
}
|