Library for JrkG2. this bases on Arduino Library (https://github.com/pololu/jrk-g2-arduino)

JrkG2.h

Committer:
sgrsn
Date:
2018-08-26
Revision:
2:e78c0ddcf337
Parent:
1:d611aa1f9f70

File content as of revision 2:e78c0ddcf337:

#pragma once

#include "mbed.h"
#include "AsyncSerial.hpp"

/*example**********************************

#include "mbed.h"
#include "JrkG2.h"

int main()
{
    //on the other or both
    
    //on Serial
    AsyncSerial device(p9, p10);
    JrkG2Serial jrk(&device);
    
    //on I2C
    I2C device(p28, p27);
    JrkG2I2C jrk(&device);
    
    while(1)
    {
        wait_ms(1500);
        jrk.setTarget(2500);
        wait_ms(1500);
        jrk.setTarget(1500);
    }
}

***********************************************/

/// This is used to represent a null or missing value for some of the Jrk G2's
/// 16-bit input variables.
const uint16_t JrkG2InputNull = 0xFFFF;

/// This value is returned by getLastError() if the last communication with the
/// device resulted in an unsuccessful read (e.g. timeout or NACK).
const uint8_t JrkG2CommReadError = 50;

/// This enum defines the Jrk's error bits.  See the "Error handling" section of
/// the Jrk G2 user's guide for more information about what these errors mean.
///
/// See JrkG2Base::getErrorFlagsHalting() and JrkG2Base::getErrorFlagsOccurred().
enum JrkG2Error
{
    AwaitingCommand     = 0,
    NoPower             = 1,
    MotorDriver         = 2,
    InputInvalid        = 3,
    InputDisconnect     = 4,
    FeedbackDisconnect  = 5,
    SoftOvercurrent     = 6,
    SerialSignal        = 7,
    SerialOverrun       = 8,
    SerialBufferFull    = 9,
    SerialCrc           = 10,
    SerialProtocol      = 11,
    SerialTimeout       = 12,
    HardOvercurrent     = 13,
};

/// This enum defines the Jrk G2 command bytes which are used for its serial and
/// I2C interfaces.  These bytes are used by the library and you should not need
/// to use them.
enum JrkG2Command
{
    SetTarget                         = 0xC0,
    SetTargetLowResRev                = 0xE0,
    SetTargetLowResFwd                = 0xE1,
    ForceDutyCycleTarget              = 0xF2,
    ForceDutyCycle                    = 0xF4,
    MotorOff                          = 0xFF,
    GetVariable8                      = 0x80,
    GetVariable16                     = 0xA0,
    GetEEPROMSettings                 = 0xE3,
    GetVariables                      = 0xE5,
    SetRAMSettings                    = 0xE6,
    GetRAMSettings                    = 0xEA,
    GetCurrentChoppingOccurrenceCount = 0xEC,
};

/// This enum defines the modes in which the Jrk G2's duty cycle target or duty
/// cycle, normally derived from the output of its PID algorithm, can be
/// overridden with a forced value.
///
/// See JrkG2Base::getForceMode(), JrkG2Base::forceDutyCycleTarget(), and
/// JrkG2Base::forceDutyCycle().
enum JrkG2ForceMode
{
    None            = 0,
    DutyCycleTarget = 1,
    DutyCycle       = 2,
};

/// This enum defines the possible causes of a full microcontroller reset for
/// the Jrk G2.
///
/// See JrkG2Base::getDeviceReset().
enum JrkG2Reset
{
    PowerUp        = 0,
    Brownout       = 1,
    ResetLine      = 2,
    Watchdog       = 4,
    Software       = 8,
    StackOverflow  = 16,
    StackUnderflow = 32,
};

/// This enum defines the Jrk G2's control and feedback pins
enum JrkG2Pin
{
    SCL = 0,
    SDA = 1,
    TX  = 2,
    RX  = 3,
    RC  = 4,
    AUX = 5,
    FBA = 6,
    FBT = 7,
};

/// This enum defines the bits in the Jrk G2's Options Byte 3 register.  You
/// should not need to use this directly.  See JrkG2Base::setResetIntegral(),
/// JrkG2Base::getResetIntegral(), JrkG2Base::setCoastWhenOff(), and
/// JrkG2Base::getCoastWhenOff().
enum JrkG2OptionsByte3
{
    ResetIntegral = 0,
    CoastWhenOff = 1,
};

/// This is a base class used to represent a connection to a Jrk G2.  This class
/// provides high-level functions for sending commands to the Jrk and reading
/// data from it.
///
/// See the subclasses of this class, JrkG2Serial and JrkG2I2C.
class JrkG2Base
{
public:

    JrkG2Base()
    {
        _lastError = 0;
    }
  
    /// Returns 0 if the last communication with the device was successful, and
    /// non-zero if there was an error.
    uint8_t getLastError()
    {
        return _lastError;
    }
    /// Sets the target of the Jrk to a value in the range 0 to 4095.
    ///
    /// The target can represent a target duty cycle, speed, or position depending
    /// on the feedback mode.
    ///
    /// Example usage:
    /// ```
    /// jrkG2.setTarget(3000);
    /// ```
    ///
    /// This functions sends a "Set target" command to the jrk, which clears the
    /// "Awaiting command" error bit and (if the input mode is serial) will set
    /// the jrk's input and target variables.
    ///
    /// See also setTargetLowResRev(), setTargetLowResFwd(), getTarget(),
    /// forceDutyCycleTarget(), forceDutyCycle().
    
    void setTarget(uint16_t target)
    {
        // lower 5 bits in command byte
        // upper 7 bits in data byte
        if (target > 4095) { target = 4095; }
        commandW7((uint8_t)SetTarget | (target & 0x1F), target >> 5);
    }
    
    /// Sets the target of the Jrk based on a value in the range 0 to 127.
    ///
    /// If the value is zero, then this command is equivalent to the "Stop motor"
    /// command. Otherwise, the value maps to a 12-bit target less than 2048.
    ///
    /// If the feedback mode is Analog or Tachometer, then the formula is
    /// Target = 2048 - 16 * value.
    ///
    /// If the feedback mode is None (speed control mode), then the formula is
    /// Target = 2048 - (600 / 127) * value.  This means that a value of
    /// 127 will set the duty cycle target to full-speed reverse (-600).
    ///
    /// Example usage:
    /// ```
    /// jrkG2.setTargetLowResRev(100);
    /// ```
    ///
    /// This function sends a "Set target low resolution reverse" command to the
    /// Jrk G2, which clears the "Awaiting command" error bit and (if the input
    /// mode is serial) will set the jrk's input and target variables.
    ///
    /// See also setTargetLowResFwd(), setTarget(), getTarget(), and stopMotor().
    void setTargetLowResRev(uint8_t target)
    {
        if (target > 127) { target = 127; }
        commandW7(SetTargetLowResRev, target);
    }
    
    /// Sets the target of the Jrk based on a value in the range 0 to 127 that
    /// maps to a 12-bit target of 2048 or greater.
    ///
    /// If the feedback mode is Analog or Tachometer, then the formula is
    /// Target = 2048 + 16 * value.
    ///
    /// If the feedback mode is None (speed control mode), then the formula is
    /// Target = 2048 + (600 / 127) * value.  This means that a value of 127 will
    /// set the duty cycle target to full-speed reverse (-600), while a value of
    /// zero will make the motor stop.
    ///
    /// Example usage:
    /// ```
    /// jrkG2.setTargetLowResFwd(100);
    /// ```
    ///
    /// This function sends a "Set target low resolution forward" command to the
    /// Jrk G2, which clears the "Awaiting command" error bit and (if the input
    /// mode is serial) will set the jrk's input and target variables.
    ///
    /// See also setTargetLowResRev(), setTarget(), and getTarget().
    void setTargetLowResFwd(uint8_t target)
    {
        if (target > 127) { target = 127; }
        commandW7(SetTargetLowResFwd, target);
    }
    
    /// Forces the duty cycle target of the Jrk to a value in the range
    /// -600 to +600.
    ///
    /// The Jrk will ignore the results of the usual algorithm for choosing the duty
    /// cycle target, and instead set it to be equal to the target specified by this
    /// command.  The Jrk will set its 'Integral' variable to 0 while in this mode.
    ///
    /// This is useful if the Jrk is configured to use feedback but you want to take
    /// control of the motor for some time, while still respecting errors and motor
    /// limits as usual.
    ///
    /// Example usage:
    /// ```
    /// jrkG2.forceDutyCycleTarget(250);
    /// ```
    ///
    /// This function sends a "Force duty cycle target" command to the Jrk G2, which
    /// clears the "Awaiting command" error bit.
    ///
    /// To get out of this mode, use setTarget(), setTargetLowResFwd(),
    /// setTargetLowResRev(), forceDutyCycle(), or stopMotor().
    ///
    /// See also getForceMode().
    void forceDutyCycleTarget(int16_t dutyCycle)
    {
        if (dutyCycle > 600) { dutyCycle = 600; }
        if (dutyCycle < -600) { dutyCycle = -600; }
        commandWs14(ForceDutyCycleTarget, dutyCycle);
    }

    /// Forces the duty cycle of the Jrk to a value in the range -600 to +600.
    ///
    /// The jrk will ignore the results of the usual algorithm for choosing the
    /// duty cycle, and instead set it to be equal to the value specified by this
    /// command, ignoring all motor limits except the maximum duty cycle
    /// parameters, and ignoring the 'Input invalid', 'Input disconnect', and
    /// 'Feedback disconnect' errors.  This command will have an immediate effect,
    /// regardless of the PID period.  The jrk will set its 'Integral' variable to
    /// 0 while in this mode.
    ///
    /// This is useful if the jrk is configured to use feedback but you want to take
    /// control of the motor for some time, without respecting most motor limits.
    ///
    /// Example usage:
    /// ```
    /// jrkG2.forceDutyCycle(250);
    /// ```
    ///
    /// This function sends a "Force duty cycle" command to the Jrk G2, which
    /// clears the "Awaiting command" error bit.
    ///
    /// To get out of this mode, use setTarget(), setTargetLowResFwd(),
    /// setTargetLowResRev(), forceDutyCycleTarget(), or stopMotor().
    ///
    /// See also getForceMode().
    void forceDutyCycle(int16_t dutyCycle)
    {
        if (dutyCycle > 600) { dutyCycle = 600; }
        if (dutyCycle < -600) { dutyCycle = -600; }
        commandWs14(ForceDutyCycle, dutyCycle);
    }

    /// Turns the motor off.
    ///
    /// This function sends a "Stop motor" command to the Jrk, which sets the
    /// "Awaiting command" error bit.  The Jrk will respect the configured
    /// deceleration limit while decelerating to a duty cycle of 0, unless the
    /// "Awaiting command" error has been configured as a hard error.  Once the duty
    /// cycle reaches zero, the Jrk will either brake or coast.
    ///
    /// Example usage:
    /// ```
    /// jrkG2.stopMotor();
    /// ```
    void stopMotor()
    {
        commandQuick(MotorOff);
    }

    ///@}
    
    ///\name Variable reading commands
    ///@{
    
    /// Gets the input variable.
    ///
    /// The input variable is a raw, unscaled value representing a measurement
    /// taken by the Jrk of the input to the system.  In serial input mode, the
    /// input is equal to the target, which can be set to any value from 0 to 4095
    /// using serial commands.  In analog input mode, the input is a measurement
    /// of the voltage on the SDA pin, where 0 is 0 V and 4092 is a voltage equal
    /// to the Jrk's 5V pin (approximately 4.8 V).  In RC input mode, the input is
    /// the duration of the last RC pulse measured, in units of 2/3 us.
    ///
    /// See the Jrk G2 user's guide for more information about input modes.
    ///
    /// See also getTarget() and setTarget().
    uint16_t getInput()
    {
        return getVar16SingleByte(Input);
    }
    
    /// Gets the target variable.
    ///
    /// In serial input mode, the target is set directly with serial commands.  In
    /// the other input modes, the target is computed by scaling the input, using
    /// the configurable input scaling settings.
    ///
    /// See also setTarget() and getInput().
    uint16_t getTarget()
    {
        return getVar16SingleByte(Target);
    }

    /// Gets the feedback variable.
    ///
    /// The feedback variable is a raw, unscaled feedback value, representing a
    /// measurement taken by the Jrk of the output of the system.  In analog
    /// feedback mode, the feedback is a measurement of the voltage on the FBA
    /// pin, where 0 is 0 V and 4092 is a voltage equal to the Jrk's 5V pin
    /// (approximately 4.8 V).  In frequency feedback mode, the feedback is 2048
    /// plus or minus a measurement of the frequency of pulses on the FBT pin.  In
    /// feedback mode none (open-loop speed control mode), the feedback is always
    /// zero.
    ///
    /// See also getScaledFeedback().
    uint16_t getFeedback()
    {
        return getVar16SingleByte(Feedback);
    }

    /// Gets the scaled feedback variable.
    ///
    /// The scaled feedback is calculated from the feedback using the Jrk's
    /// configurable feedback scaling settings.
    ///
    /// See also getFeedback().
    uint16_t getScaledFeedback()
    {
        return getVar16SingleByte(ScaledFeedback);
    }

    /// Gets the integral variable.
    ///
    /// In general, every PID period, the error (scaled feedback minus target) is
    /// added to the integral (also known as error sum).  There are several
    /// settings to configure the behavior of this variable, and it is used in the
    /// PID calculation.
    int16_t getIntegral()
    {
        return getVar16SingleByte(Integral);
    }

    /// Gets the duty cycle target variable.
    ///
    /// In general, this is the duty cycle that the Jrk is trying to achieve.  A
    /// value of -600 or less means full speed reverse, while a value of 600 or
    /// more means full speed forward.  A value of 0 means stopped (braking or
    /// coasting).  In no feedback mode (open-loop speed control mode), the duty
    /// cycle target is normally the target minus 2048. In other feedback modes,
    /// the duty cycle target is normally the sum of the proportional, integral,
    /// and derivative terms of the PID algorithm.  In any mode, the duty cycle
    /// target can be overridden with forceDutyCycleTarget().
    ///
    /// If an error is stopping the motor, the duty cycle target variable will not
    /// be directly affected, but the duty cycle variable will change/decelerate
    /// to zero.
    ///
    /// See also getDutyCycle(), getLastDutyCycle(), and forceDutyCycleTarget().
    int16_t getDutyCycleTarget()
    {
        return getVar16SingleByte(DutyCycleTarget);
    }

    /// Gets the duty cycle variable.
    ///
    /// The duty cycle variable is the duty cycle at which the jrk is currently
    /// driving the motor.  A value of -600 means full speed reverse, while a
    /// value of 600 means full speed forward.  A value of 0 means stopped
    /// (braking or coasting).  The duty cycle could be different from the duty
    /// cycle target because it normally takes into account the Jrk's configurable
    /// motor limits and errors.  The duty cycle can be overridden with
    /// forceDutyCycle().
    ///
    /// See also getLastDutyCycle(), getDutyCycleTarget(), and forceDutyCycle().
    int16_t getDutyCycle()
    {
        return getVar16SingleByte(DutyCycle);
    }

    /// Gets the most-significant 8 bits of the "Current" variable.
    ///
    /// The Jrk G2 supports this command mainly to be compatible with older Jrk
    /// models.  In new applications, we recommend using getCurrent(), which
    /// provides a higher-resolution measurement.
    uint8_t getCurrentLowRes()
    {
        return getVar8SingleByte(CurrentLowRes);
    }
    
    /// Returns true if the Jrk's most recent PID cycle took more time than the
    /// configured PID period.  This indicates that the Jrk does not have time to
    /// perform all of its tasks at the desired rate.  Most often, this is caused
    /// by the configured number of analog samples for input, feedback, or current
    /// sensing being too high for the configured PID period.
    bool getPIDPeriodExceeded()
    {
        return getVar8SingleByte(PIDPeriodExceeded);
    }

    /// Get the "PID period count" variable, which is the number of PID periods
    /// that have elapsed.  It resets to 0 after reaching 65535.  The duration of
    /// the PID period can be configured.
    uint16_t getPIDPeriodCount()
    {
        return getVar16SingleByte(PIDPeriodCount);
    }
    
    /// Gets the errors that are currently stopping the motor and clears any
    /// latched errors that are enabled.  Calling this function is equivalent to
    /// reading the "Currently stopping motor?" column in the Errors tab of the
    /// configuration utility, and then clicking the "Clear Errors" button.
    ///
    /// Each bit in the returned register represents a different error.  The bits
    /// are defined in the ::JrkG2Error enum.
    ///
    /// Example usage:
    /// ```
    /// uint16_t errors = jrk.getErrorFlagsHalting();
    /// if (errors & (1 << (uint8_t)JrkG2Error::NoPower))
    /// {
    ///   // handle loss of power
    /// }
    /// ```
    ///
    /// It is possible to read this variable without clearing the bits in it using
    /// a getVariables().
    ///
    /// See also getErrorFlagsOccurred().
    uint16_t getErrorFlagsHalting()
    {
        return getVar16SingleByte(ErrorFlagsHalting);
    }

    /// Gets the errors that have occurred since the last time this function was
    /// called.  Unlike getErrorFlagsHalting(), calling this function has no
    /// effect on the motor.
    ///
    /// Note that the Jrk G2 Control Center constantly clears the bits in this
    /// register, so if you are running the Jrk G2 Control Center then you will
    /// not be able to reliably detect errors with this function.
    ///
    /// Each bit in the returned register represents a different error.  The bits
    /// are defined in the ::JrkG2Error enum.
    ///
    /// Example usage:
    /// ```
    /// uint32_t errors = jrk.getErrorsOccurred();
    /// if (errors & (1 << (uint8_t)JrkG2Error::MotorDriver))
    /// {
    ///   // handle a motor driver error
    /// }
    /// ```
    ///
    /// It is possible to read this variable without clearing the bits in it using
    /// getVariables().
    ///
    /// See also getErrorFlagsHalting().
    uint16_t getErrorFlagsOccurred()
    {
        return getVar16SingleByte(ErrorFlagsOccurred);
    }

    /// Returns an indication of whether the Jrk's duty cycle target or duty cycle
    /// are being overridden with a forced value.
    ///
    /// Example usage:
    /// ```
    /// if (jrk.getForceMode() == JrkG2ForceMode::DutyCycleTarget)
    /// {
    ///   // The duty cycle target is being overridden with a forced value.
    /// }
    /// ```
    ///
    /// See also forceDutyCycleTarget() and forceDutyCycle().
    JrkG2ForceMode getForceMode()
    {
        return (JrkG2ForceMode)(getVar8SingleByte(FlagByte1) & 0x03);
    }
    
    /// Gets the measurement of the VIN voltage, in millivolts.
    ///
    /// Example usage:
    /// ```
    /// uint16_t vin = jrk.getVinVoltage();
    /// ```
    uint16_t getVinVoltage()
    {
        return getVar16SingleByte(VinVoltage);
    }

    /// Gets the Jrk's measurement of the current running through the motor, in
    /// milliamps.
    uint16_t getCurrent()
    {
        return getVar16SingleByte(Current);
    }
    
    /// Gets the cause of the Jrk's last full microcontroller reset.
    ///
    /// Example usage:
    /// ```
    /// if (jrk.getDeviceReset() == JrkG2Reset::Brownout)
    /// {
    ///   // There was a brownout reset; the power supply could not keep up.
    /// }
    /// ```
    JrkG2Reset getDeviceReset()
    {
        return (JrkG2Reset)getVar8(DeviceReset);
    }

    /// Gets the time since the last full reset of the Jrk's microcontroller, in
    /// milliseconds.
    ///
    /// Example usage:
    /// ```
    /// uint32_t upTime = jrk.getUpTime();
    /// ```
    uint32_t getUpTime()
    {
        return getVar32(UpTime);
    }
    
    /// Gets the raw RC pulse width measured on the Jrk's RC input, in units of
    /// twelfths of a microsecond.
    ///
    /// Returns 0 if the RC input is missing or invalid.
    ///
    /// Example usage:
    /// ```
    /// uint16_t pulseWidth = jrk.getRCPulseWidth();
    /// if (pulseWidth != 0 && pulseWidth < 18000)
    /// {
    ///   // Input is valid and pulse width is less than 1500 microseconds.
    /// }
    /// ```
    uint16_t getRCPulseWidth()
    {
        return getVar16(RCPulseWidth);
    }

    /// Gets the raw pulse rate or pulse width measured on the Jrk's FBT
    /// (tachometer feedback) pin.
    ///
    /// In pulse counting mode, this will be the number of pulses on the FBT pin
    /// seen in the last N PID periods, where N is the "Pulse samples" setting.
    ///
    /// In pulse timing mode, this will be a measurement of the width of pulses on
    /// the FBT pin.  This measurement is affected by several configurable
    /// settings.
    ///
    /// Example usage:
    /// ```
    /// uint16_t fbtReading = jrk.getFBTReading();
    /// ```
    uint16_t getFBTReading()
    {
        return getVar16(FBTReading);
    }

    /// Gets the analog reading from the specified pin.
    ///
    /// The reading is left-justified, so 0xFFFE represents a voltage equal to the
    /// Jrk's 5V pin (approximately 4.8 V).
    ///
    /// Returns JrkG2InputNull if the analog reading is disabled or not ready or
    /// the pin is invalid.
    ///
    /// Example usage:
    /// ```
    /// uint16_t reading = getAnalogReading(JrkG2Pin::SDA);
    /// if (reading != JrkG2InputNull && reading < 32768)
    /// {
    ///   // The reading is less than about 2.4 V.
    /// }
    /// ```
    uint16_t getAnalogReading(JrkG2Pin pin)
    {
        switch (pin)
        {
            case SDA:
                return getVar16(AnalogReadingSDA);
            case FBA:
                return getVar16(AnalogReadingFBA);
            default:
                return JrkG2InputNull;
        }
    }
    
    /// Gets a digital reading from the specified pin.
    ///
    /// A return value of 0 means low while 1 means high.  In most cases, pins
    /// configured as analog inputs cannot be read as digital inputs, so their
    /// values will be 0.  See getAnalogReading() for those pins.
    ///
    /// Example usage:
    /// ```
    /// if (jrk.getDigitalReading(JrkG2Pin::RC))
    /// {
    ///   // Something is driving the RC pin high.
    /// }
    /// ```
    bool getDigitalReading(JrkG2Pin pin)
    {
        uint8_t readings = getVar8(DigitalReadings);
        return (readings >> (uint8_t)pin) & 1;
    }

    /// Gets the Jrk's raw measurement of the current running through the motor.
    ///
    /// This is an analog voltage reading from the Jrk's current sense
    /// pin.  The units of the reading depend on what hard current limit is being
    /// used (getEncodedHardCurrentLimit()).
    ///
    /// See also getCurrent().
    uint16_t getRawCurrent()
    {
        return getVar16(RawCurrent);
    }
    
    /// Gets the encoded value representing the hardware current limit the jrk is
    /// currently using.
    ///
    /// This command is only valid for the Jrk G2 18v19, 24v13, 18v27, and 24v21.
    /// The Jrk G2 21v3 does not have a configurable hard current limit.
    ///
    /// See also setEncodedHardCurrentLimit(), getCurrent().
    uint16_t getEncodedHardCurrentLimit()
    {
        return getVar16(EncodedHardCurrentLimit);
    }

    /// Gets the duty cycle the Jrk drove the motor with in the last PID period.
    ///
    /// This can be useful for converting the getRawCurrent() reading into
    /// milliamps.
    ///
    /// See also getDutyCycle(), getDutyCycleTarget(), and getCurrent().
    int16_t getLastDutyCycle()
    {
        return getVar16(LastDutyCycle);
    }
    
    /// Gets the number of consecutive PID periods during which current chopping
    /// due to the hard current limit has been active.
    ///
    /// See also getCurrentChoppingOccurrenceCount().
    uint8_t getCurrentChoppingConsecutiveCount()
    {
        return getVar8(CurrentChoppingConsecutiveCount);
    }

    /// Gets and clears the "Current chopping occurrence count" variable, which is
    /// the number of PID periods during which current chopping due to the hard
    /// current limit has been active, since the last time the variable was
    /// cleared.
    ///
    /// This command is only valid for the Jrk G2 18v19, 24v13, 18v27, and 24v21.
    /// The Jrk G2 21v3 cannot sense when current chopping occurs so this command
    /// will always return 0.
    ///
    /// See also getCurrentChoppingConsecutiveCount().
    uint8_t getCurrentChoppingOccurrenceCount()
    {
        return commandR8(GetCurrentChoppingOccurrenceCount);
    }
    ///@}
    
    ///\name RAM settings commands
    ///@{
    
    /// Sets or clears the "Reset integral" setting in the Jrk's RAM settings.
    ///
    /// If this setting is set to true, the PID algorithm will reset the integral
    /// variable (also known as error sum) when the absolute value of the
    /// proportional term exceeds 600.
    ///
    /// When enabled, this can help limit integral wind-up, or the uncontrolled
    /// growth of the integral when the feedback system is temporarily unable to
    /// keep the error small. This might happen, for example, when the target is
    /// changing quickly.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getResetIntegral().
    void setResetIntegral(bool reset)
    {
        uint8_t tmp = getRAMSetting8(OptionsByte3);
        if (getLastError()) { return; }
        if (reset)
        {
            tmp |= 1 << (uint8_t)ResetIntegral;
        }
        else
        {
            tmp &= ~(1 << (uint8_t)ResetIntegral);
        }
        setRAMSetting8(OptionsByte3, tmp);
    }
    
    /// Gets the "Reset integral" setting from the Jrk's RAM settings.
    ///
    /// See also setResetIntegral().
    bool getResetIntegral()
    {
        return getRAMSetting8(OptionsByte3) >>
        (uint8_t)ResetIntegral & 1;
    }

    /// Sets or clears the "Coast when off" setting in the Jrk's RAM settings.
    ///
    /// By default, the Jrk drives both motor outputs low when the motor is
    /// stopped (duty cycle is zero), causing it to brake.  If enabled, this
    /// setting causes it to instead tri-state both outputs, making the motor
    /// coast.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getCoastWhenOff().
    void setCoastWhenOff(bool coast)
    {
        uint8_t tmp = getRAMSetting8(OptionsByte3);
        if (getLastError()) { return; }
        if (coast)
        {
            tmp |= 1 << (uint8_t)CoastWhenOff;
        }
        else
        {
            tmp &= ~(1 << (uint8_t)CoastWhenOff);
        }
        setRAMSetting8(OptionsByte3, tmp);
    }

    /// Gets the "Coast when off" setting from the Jrk's RAM settings.
    ///
    /// See also setCoastWhenOff().
    bool getCoastWhenOff()
    {
        return getRAMSetting8(OptionsByte3) >>
        (uint8_t)CoastWhenOff & 1;
    }
    
    /// Sets the proportional coefficient in the Jrk's RAM settings.
    ///
    /// This coefficient is used in the Jrk's PID algorithm.  The coefficient
    /// takes the form:
    ///
    /// multiplier / (2 ^ exponent)
    ///
    /// The multiplier can range from 0 to 1023, and the exponent can range
    /// from 0 to 18.
    ///
    /// Example usage:
    /// ```
    /// // Set the proportional coefficient to 1.125 (9/(2^3)).
    /// jrk.setProportionalCoefficient(9, 3);
    /// ```
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getProportionalMultiplier() and getProportionalExponent(), as
    /// well as setIntegralCoefficient() and setDerivativeCoefficient().
    void setProportionalCoefficient(uint16_t multiplier, uint8_t exponent)
    {
        setPIDCoefficient(ProportionalMultiplier, multiplier, exponent);
    }

    /// Gets the multiplier part of the proportional coefficient from the Jrk's
    /// RAM settings.
    ///
    /// See also getProportionalExponent() and setProportionalCoefficient().
    uint16_t getProportionalMultiplier()
    {
        return getRAMSetting16(ProportionalMultiplier);
    }
    
    /// Gets the exponent part of the proportional coefficient from the Jrk's RAM
    /// settings.
    ///
    /// See also getProportionalMultiplier() and setProportionalCoefficient().
    uint8_t getProportionalExponent()
    {
        return getRAMSetting8(ProportionalExponent);
    }

    /// Sets the integral coefficient in the Jrk's RAM settings.
    ///
    /// This coefficient is used in the Jrk's PID algorithm.  The coefficient
    /// takes the form:
    ///
    /// multiplier / (2 ^ exponent)
    ///
    /// The multiplier can range from 0 to 1023, and the exponent can range
    /// from 0 to 18.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getIntegralMultiplier() and getIntegralExponent(), as
    /// well as setProportionalCoefficient() and setDerivativeCoefficient().
    void setIntegralCoefficient(uint16_t multiplier, uint8_t exponent)
    {
        setPIDCoefficient(IntegralMultiplier, multiplier, exponent);
    }

    /// Gets the multiplier part of the integral coefficient from the Jrk's
    /// RAM settings.
    ///
    /// See also getIntegralExponent() and setIntegralCoefficient().
    uint16_t getIntegralMultiplier()
    {
        return getRAMSetting16(IntegralMultiplier);
    }
    
    /// Gets the exponent part of the integral coefficient from the Jrk's
    /// RAM settings.
    ///
    /// See also getIntegralMultiplier() and setIntegralCoefficient().
    uint8_t getIntegralExponent()
    {
        return getRAMSetting8(IntegralExponent);
    }

    /// Sets the derivative coefficient in the Jrk's RAM settings.
    ///
    /// This coefficient is used in the Jrk's PID algorithm.  The coefficient
    /// takes the form:
    ///
    /// multiplier / (2 ^ exponent)
    ///
    /// The multiplier can range from 0 to 1023, and the exponent can range
    /// from 0 to 18.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getDerivativeMultiplier() and getDerivativeExponent(), as
    /// well as setProportionalCoefficient() and setIntegralCoefficient().
    void setDerivativeCoefficient(uint16_t multiplier, uint8_t exponent)
    {
        setPIDCoefficient(DerivativeMultiplier, multiplier, exponent);
    }

    /// Gets the multiplier part of the derivative coefficient from the
    /// Jrk's RAM settings.
    ///
    /// See also getDerivativeExponent() and setDerivativeCoefficient().
    uint16_t getDerivativeMultiplier()
    {
        return getRAMSetting16(DerivativeMultiplier);
    }
    
    /// Gets the exponent part of the derivative coefficient from the
    /// Jrk's RAM settings.
    ///
    /// See also getDerivativeMultiplier() and setDerivativeCoefficient().
    uint8_t getDerivativeExponent()
    {
        return getRAMSetting8(DerivativeExponent);
    }
    
    /// Sets the PID period in the Jrk's RAM settings.
    ///
    /// This is the rate at which the Jrk runs through all of its calculations, in
    /// milliseconds.  Note that a higher PID period will result in a more slowly
    /// changing integral and a higher derivative, so the two corresponding PID
    /// coefficients might need to be adjusted whenever the PID period is changed.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getPIDPeriod().
    void setPIDPeriod(uint16_t period)
    {
        setRAMSetting16(PIDPeriod, period);
    }
    
    /// Gets the PID period from the Jrk's RAM settings, in milliseconds.
    ///
    /// See also setPIDPeriod().
    uint16_t getPIDPeriod()
    {
        return getRAMSetting16(PIDPeriod);
    }

    /// Sets the integral limit in the Jrk's RAM settings.
    ///
    /// The PID algorithm prevents the absolute value of the integral variable
    /// (also known as error sum) from exceeding this limit.  This can help limit
    /// integral wind-up.  The limit can range from 0 to 32767.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getIntegralLimit().
    void setIntegralLimit(uint16_t limit)
    {
        setRAMSetting16(IntegralLimit, limit);
    }

    /// Gets the integral limit from the Jrk's RAM settings.
    ///
    /// See also setIntegralLimit().
    uint16_t getIntegralLimit()
    {
        return getRAMSetting16(IntegralLimit);
    }
    
    /// Sets the maximum duty cycle while feedback is out of range in the Jrk's
    /// RAM settings.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getMaxDutyCycleWhileFeedbackOutOfRange().
    void setMaxDutyCycleWhileFeedbackOutOfRange(uint16_t duty)
    {
        setRAMSetting16(MaxDutyCycleWhileFeedbackOutOfRange, duty);
    }

    /// Gets the maximum duty cycle while feedback is out of range from the Jrk's RAM
    /// settings.
    ///
    /// See also setMaxDutyCycleWhileFeedbackOutOfRange().
    uint16_t getMaxDutyCycleWhileFeedbackOutOfRange()
    {
        return getRAMSetting16(MaxDutyCycleWhileFeedbackOutOfRange);
    }

    /// Sets the maximum acceleration in the forward direction in the
    /// Jrk's RAM settings.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getMaxAccelerationForward(), setMaxAccelerationReverse(),
    /// setMaxAcceleration(), and setMaxDecelerationForward().
    void setMaxAccelerationForward(uint16_t accel)
    {
        setRAMSetting16(MaxAccelerationForward, accel);
    }

    /// Gets the maximum acceleration in the forward direction from the
    /// Jrk's RAM settings.
    ///
    /// See also setMaxAccelerationForward().
    uint16_t getMaxAccelerationForward()
    {
        return getRAMSetting16(MaxAccelerationForward);
    }
    
    /// Sets the maximum acceleration in the reverse direction in the
    /// Jrk's RAM settings.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getMaxAccelerationReverse(), setMaxAccelerationForward(),
    /// setMaxAcceleration(), and setMaxDecelerationReverse().
    void setMaxAccelerationReverse(uint16_t accel)
    {
        setRAMSetting16(MaxAccelerationReverse, accel);
    }

    /// Gets the maximum acceleration in the reverse direction from the
    /// Jrk's RAM settings.
    ///
    /// See also setMaxAccelerationReverse().
    uint16_t getMaxAccelerationReverse()
    {
        return getRAMSetting16(MaxAccelerationReverse);
    }
    
    /// Sets the maximum acceleration in both directions in the
    /// Jrk's RAM settings.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also setMaxAccelerationForward(), setMaxAccelerationReverse(),
    /// setMaxDeceleration().
    void setMaxAcceleration(uint16_t accel)
    {
        setRAMSetting16x2(MaxAccelerationForward, accel, accel);
    }

    /// Sets the maximum deceleration in the forward direction in the Jrk's RAM
    /// settings.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getMaxDecelerationForward(), setMaxDecelerationReverse(),
    /// setMaxDeceleration(), and setMaxAccelerationForward().
    void setMaxDecelerationForward(uint16_t decel)
    {
        setRAMSetting16(MaxDecelerationForward, decel);
    }
    
    /// Gets the maximum deceleration in the forward direction from the Jrk's RAM
    /// settings.
    ///
    /// See also setMaxDecelerationForward().
    uint16_t getMaxDecelerationForward()
    {
        return getRAMSetting16(MaxDecelerationForward);
    }

    /// Sets the maximum deceleration in the reverse direction in the
    /// Jrk's RAM settings.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getMaxDecelerationReverse(), setMaxDecelerationForward(),
    /// setMaxDeceleration(), and setMaxAccelerationReverse().
    void setMaxDecelerationReverse(uint16_t decel)
    {
        setRAMSetting16(MaxDecelerationReverse, decel);
    }
    
    /// Gets the maximum deceleration in the reverse direction from the Jrk's RAM
    /// settings.
    ///
    /// See also setMaxDecelerationReverse().
    uint16_t getMaxDecelerationReverse()
    {
        return getRAMSetting16(MaxDecelerationReverse);
    }

    /// Sets the maximum deceleration in both directions in the
    /// Jrk's RAM settings.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also setMaxDecelerationForward(), setMaxDecelerationReverse(),
    /// setMaxAcceleration().
    void setMaxDeceleration(uint16_t decel)
    {
        setRAMSetting16x2(MaxDecelerationForward, decel, decel);
    }
    
    /// Sets the maximum duty cycle in the forward direction in the
    /// Jrk's RAM settings.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getMaxDutyCycleForward(), setMaxDutyCycleReverse().
    void setMaxDutyCycleForward(uint16_t duty)
    {
        setRAMSetting16(MaxDutyCycleForward, duty);
    }

    /// Gets the maximum duty cycle in the forward direction from the Jrk's RAM
    /// settings.
    ///
    /// See also setMaxDutyCycleForward().
    uint16_t getMaxDutyCycleForward()
    {
        return getRAMSetting16(MaxDutyCycleForward);
    }
    
    /// Sets the maximum duty cycle in the reverse direction in the
    /// Jrk's RAM settings.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getMaxDutyCycleReverse(), setMaxDutyCycleForard().
    void setMaxDutyCycleReverse(uint16_t duty)
    {
        setRAMSetting16(MaxDutyCycleReverse, duty);
    }
    
    /// Gets the maximum duty cycle in the reverse direction from the
    /// Jrk's RAM settings.
    ///
    /// See also setMaxDutyCycleReverse().
    uint16_t getMaxDutyCycleReverse()
    {
        return getRAMSetting16(MaxDutyCycleReverse);
    }

    /// Sets the maximum duty cycle for both directions in the
    /// Jrk's RAM settings.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also setMaxDutyCycleForward(), setMaxDutyCycleReverse().
    void setMaxDutyCycle(uint16_t duty)
    {
        setRAMSetting16x2(MaxDutyCycleForward, duty, duty);
    }
    
    /// Sets the encoded hard current limit for driving in the forward direction
    /// in the Jrk's RAM settings.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// This command is only valid for the Jrk G2 18v19, 24v13, 18v27, and 24v21.
    /// The Jrk G2 21v3 does not have a configurable hard current limit.
    ///
    /// See also getEncodedHardCurrentLimitForward() and
    /// setEncodedHardCurrentLimitReverse().
    void setEncodedHardCurrentLimitForward(uint16_t encoded_limit)
    {
        setRAMSetting16(EncodedHardCurrentLimitForward,
        encoded_limit);
    }

    /// Gets the encoded hard current limit for driving in the forward direction
    /// from the Jrk's RAM settings.
    ///
    /// This command is only valid for the Jrk G2 18v19, 24v13, 18v27, and 24v21.
    /// The Jrk G2 21v3 does not have a configurable hard current limit.
    ///
    /// See also setEncodedHardCurrentLimitForward().
    uint16_t getEncodedHardCurrentLimitForward()
    {
        return getRAMSetting16(EncodedHardCurrentLimitForward);
    }
    
    /// Sets the encoded hard current limit for driving in the reverse direction
    /// in the Jrk's RAM settings
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// This command is only valid for the Jrk G2 18v19, 24v13, 18v27, and 24v21.
    /// The Jrk G2 21v3 does not have a configurable hard current limit.
    ///
    /// See also getEncodedHardCurrentLimitReverse() and
    /// setEncodedHardCurrentLimitForward().
    void setEncodedHardCurrentLimitReverse(uint16_t encoded_limit)
    {
        setRAMSetting16(EncodedHardCurrentLimitReverse, encoded_limit);
    }

    /// Gets the encoded hard current limit for driving in the reverse direction
    /// from the Jrk's RAM settings.
    ///
    /// This command is only valid for the Jrk G2 18v19, 24v13, 18v27, and 24v21.
    /// The Jrk G2 21v3 does not have a configurable hard current limit.
    ///
    /// See also setEncodedHardCurrentLimitReverse().
    uint16_t getEncodedHardCurrentLimitReverse()
    {
        return getRAMSetting16(EncodedHardCurrentLimitReverse);
    }
    
    /// Sets the encoded hard current limit for both directions in the Jrk's RAM
    /// settings.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// This command is only valid for the Jrk G2 18v19, 24v13, 18v27, and 24v21.
    /// The Jrk G2 21v3 does not have a configurable hard current limit.
    ///
    /// See also setEncodedHardCurrentLimitForward(),
    /// setEncodedHardCurrentLimitReverse(), getEncodedHardCurrentLimit(), and
    /// setSoftCurrentLimit().
    void setEncodedHardCurrentLimit(uint16_t encoded_limit)
    {
        setRAMSetting16x2(EncodedHardCurrentLimitForward,
        encoded_limit, encoded_limit);
    }

    /// Sets the brake duration when switching from forward to reverse in the
    /// Jrk's RAM settings, in units of 5 ms.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getBrakeDurationForward() and setBrakeDurationReverse().
    void setBrakeDurationForward(uint8_t duration)
    {
        setRAMSetting8(BrakeDurationForward, duration);
    }
    
    /// Gets the brake duration when switching from forward to reverse from the
    /// Jrk's RAM settings, in units of 5 ms.
    ///
    /// See also setBrakeDurationForward().
    uint8_t getBrakeDurationForward()
    {
        return getRAMSetting8(BrakeDurationForward);
    }
    
    /// Sets the brake duration when switching from reverse to forward in the
    /// Jrk's RAM settings, in units of 5 ms.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getBrakeDurationReverse() and setBrakeDurationForward().
    void setBrakeDurationReverse(uint8_t duration)
    {
        setRAMSetting8(BrakeDurationReverse, duration);
    }

    /// Gets the brake duration when switching from reverse to forward from the
    /// Jrk's RAM settings, in units of 5 ms.
    ///
    /// See also setBrakeDurationReverse().
    uint8_t getBrakeDurationReverse()
    {
        return getRAMSetting8(BrakeDurationReverse);
    }
    
    /// Sets the brake duration for both directions in the Jrk's RAM settings, in
    /// units of 5 ms.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also setBrakeDurationForward(), setBrakeDurationReverse().
    void setBrakeDuration(uint8_t duration)
    {
        setRAMSetting8x2(BrakeDurationForward, duration, duration);
    }

    /// Sets the soft current limit when driving in the forward direction in the
    /// Jrk's RAM settings, in units of mA.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getSoftCurrentLimitForward() and setSoftCurrentLimitReverse().
    void setSoftCurrentLimitForward(uint16_t current)
    {
        setRAMSetting16(SoftCurrentLimitForward, current);
    }
    
    /// Gets the soft current limit when driving in the forward direction from the
    /// Jrk's RAM settings, in units of mA.
    ///
    /// See also setSoftCurrentLimitForward().
    uint16_t getSoftCurrentLimitForward()
    {
        return getRAMSetting16(SoftCurrentLimitForward);
    }
    
    /// Sets the soft current limit when driving in the reverse direction in the
    /// Jrk's RAM settings, in units of mA.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also getSoftCurrentLimitReverse() and setSoftCurrentLimitForward().
    void setSoftCurrentLimitReverse(uint16_t current)
    {
        setRAMSetting16(SoftCurrentLimitReverse, current);
    }
    
    /// Gets the soft current limit when driving in the reverse direction from the
    /// Jrk's RAM settings, in units of mA.
    ///
    /// See also setSoftCurrentLimitReverse().
    uint16_t getSoftCurrentLimitReverse()
    {
        return getRAMSetting16(SoftCurrentLimitReverse);
    }
    
    /// Sets the soft current limit for driving in both directions in the Jrk's
    /// RAM settings, in units of mA.
    ///
    /// You would normally configure this setting ahead of time using the Jrk G2
    /// Configuration Utility, but this function allows you to change it
    /// temporarily on the fly.
    ///
    /// See also setSoftCurrentLimitForward() and setSoftCurrentLimitReverse(),
    /// setEncodedHardCurrentLimit().
    void setSoftCurrentLimit(uint16_t current)
    {
        setRAMSetting16x2(SoftCurrentLimitForward, current, current);
    }
    
    // TODO: add functions to get and set the soft current regulation level
    
    ///@}
    
    ///\name Low-level settings and variables commands
    ///@{
    
    /// Gets a contiguous block of settings from the Jrk G2's EEPROM.
    ///
    /// The maximum length that can be fetched is 15 bytes.
    ///
    /// Example usage:
    /// ```
    /// // Get the Jrk's serial device number.
    /// uint8_t deviceNumber;
    /// jrk.getEEPROMSettings(0x28, 1, &deviceNumber);
    /// ```
    ///
    /// For information on how the settings are encoded,
    /// see the Jrk G2 user's guide.
    void getEEPROMSettings(uint8_t offset, uint8_t length, uint8_t * buffer)
    {
        segmentRead(GetEEPROMSettings, offset, length, buffer);
    }
    
    /// Gets a contiguous block of settings from the Jrk G2's RAM.
    ///
    /// The maximum length that can be fetched is 15 bytes.
    ///
    /// Example usage:
    /// ```
    /// // Get the Jrk's feedback maximum setting.
    /// uint8_t buffer[2];
    /// jrk.getRAMSettings(0x1F, 2, buffer);
    /// uint16_t feedbackMaximum = buffer[0] + (buffer[1] << 8);
    /// ```
    ///
    /// Note that this library has several functions for reading and writing
    /// specific RAM settings, and they are easier to use than this function.
    ///
    /// For information on how the settings are encoded,
    /// see the Jrk G2 user's guide.
    void getRAMSettings(uint8_t offset, uint8_t length, uint8_t * buffer)
    {
        segmentRead(GetRAMSettings, offset, length, buffer);
    }

    /// Sets a contiguous block of settings in the Jrk G2's RAM.
    ///
    /// The maximum length that can be written in a single command
    /// is 7 bytes over Serial, 13 bytes over I2C.
    ///
    /// Example usage:
    /// ```
    /// // Set the Jrk's feedback maximum setting.
    /// uint16_t feedbackMaximum = 1234;
    /// uint8_t buffer[2];
    /// buffer[0] = feedbackMaximum & 0xFF;
    /// buffer[1] = feedbackMaximum >> 8 & 0xFF;
    /// jrk.setRAMSettings(0x1F, 2, buffer);
    /// ```
    ///
    /// Note that this library has several functions for reading and writing
    /// specific RAM settings, and they are easier to use than this function.
    ///
    /// For information on how the settings are encoded,
    /// see the Jrk G2 user's guide.
    void setRAMSettings(uint8_t offset, uint8_t length, uint8_t * buffer)
    {
        segmentWrite(SetRAMSettings, offset, length, buffer);
    }

    /// Gets a contiguous block of variables from the Jrk G2.
    ///
    /// Note that this library has convenient functions for reading every variable
    /// provided by the Jrk.  The main reason to use this function is if you want
    /// to read multiple variables at once for extra efficiency or to ensure that
    /// the variables are in a consistent state.
    ///
    /// The maximum length that can be fetched is 15 bytes.
    ///
    /// Example usage:
    /// ```
    /// // Get the Jrk's last device reset and its up time.
    /// uint8_t buffer[5];
    /// jrk.getVariables(0x1F, 5, buffer);
    /// ```
    ///
    /// For information on how the variables are encoded,
    /// see the Jrk G2 user's guide.
    void getVariables(uint8_t offset, uint8_t length, uint8_t * buffer)
    {
        segmentRead(GetVariables, offset, length, buffer);
    }

    ///@}

protected:
    /// Zero if the last communication with the device was successful, non-zero
    /// otherwise.
    uint8_t _lastError;

private:
    enum VarOffset
    {
        Input                           = 0x00, // uint16_t
        Target                          = 0x02, // uint16_t
        Feedback                        = 0x04, // uint16_t
        ScaledFeedback                  = 0x06, // uint16_t
        Integral                        = 0x08, // int16_t
        DutyCycleTarget                 = 0x0A, // int16_t
        DutyCycle                       = 0x0C, // int16_t
        CurrentLowRes                   = 0x0E, // uint8_t
        PIDPeriodExceeded               = 0x0F, // uint8_t
        PIDPeriodCount                  = 0x10, // uint16_t
        ErrorFlagsHalting               = 0x12, // uint16_t
        ErrorFlagsOccurred              = 0x14, // uint16_t
        
        FlagByte1                       = 0x16, // uint8_t
        VinVoltage                      = 0x17, // uint16_t
        Current                         = 0x19, // uint16_t
        
        // variables above can be read with single-byte commands (GetVariable)
        // variables below must be read with segment read (GetVariables)
        
        DeviceReset                     = 0x1F, // uint8_t
        UpTime                          = 0x20, // uint32_t
        RCPulseWidth                    = 0x24, // uint16_t
        FBTReading                      = 0x26, // uint16_t
        AnalogReadingSDA                = 0x28, // uint16_t
        AnalogReadingFBA                = 0x2A, // uint16_t
        DigitalReadings                 = 0x2C, // uint8_t
        RawCurrent                      = 0x2D, // uint16_t
        EncodedHardCurrentLimit         = 0x2F, // uint16_t
        LastDutyCycle                   = 0x31, // int16_t
        CurrentChoppingConsecutiveCount = 0x33, // uint8_t
        CurrentChoppingOccurrenceCount  = 0x34, // uint8_t; read with dedicated command
    };

    enum SettingOffset
    {
        OptionsByte1                        = 0x01,  // uint8_t
        OptionsByte2                        = 0x02,  // uint8_t
        InputMode                           = 0x03,  // uint8_t
        InputErrorMinimum                   = 0x04,  // uint16_t,
        InputErrorMaximum                   = 0x06,  // uint16_t,
        InputMinimum                        = 0x08,  // uint16_t,
        InputMaximum                        = 0x0A,  // uint16_t,
        InputNeutralMinimum                 = 0x0C,  // uint16_t,
        InputNeutralMaximum                 = 0x0E,  // uint16_t,
        OutputMinimum                       = 0x10,  // uint16_t,
        OutputNeutral                       = 0x12,  // uint16_t,
        OutputMaximum                       = 0x14,  // uint16_t,
        InputScalingDegree                  = 0x16,  // uint8_t,
        InputAnalogSamplesExponent          = 0x17,  // uint8_t,
        FeedbackMode                        = 0x18,  // uint8_t,
        FeedbackErrorMinimum                = 0x19,  // uint16_t,
        FeedbackErrorMaximum                = 0x1B,  // uint16_t,
        FeedbackMinimum                     = 0x1D,  // uint16_t,
        FeedbackMaximum                     = 0x1F,  // uint16_t,
        FeedbackDeadZone                    = 0x21,  // uint8_t,
        FeedbackAnalogSamplesExponent       = 0x22,  // uint8_t,
        SerialMode                          = 0x23,  // uint8_t,
        SerialBaudRateGenerator             = 0x24,  // uint16_t,
        SerialTimeout                       = 0x26,  // uint16_t,
        SerialDeviceNumber                  = 0x28,  // uint16_t,
        ErrorEnable                         = 0x2A,  // uint16_t
        ErrorLatch                          = 0x2C,  // uint16_t
        ErrorHard                           = 0x2E,  // uint16_t
        VinCalibration                      = 0x30,  // uint16_t
        PwmFrequency                        = 0x32,  // uint8_t
        CurrentSamplesExponent              = 0x33,  // uint8_t
        HardOvercurrentThreshold            = 0x34,  // uint8_t
        CurrentOffsetCalibration            = 0x35,  // uint16_t
        CurrentScaleCalibration             = 0x37,  // uint16_t
        FBTMethod                           = 0x39,  // uint8_t
        FBTOptions                          = 0x3A,  // uint8_t
        FBTTimingTimeout                    = 0x3B,  // uint16_t
        FBTSamples                          = 0x3D,  // uint8_t
        FBTDividerExponent                  = 0x3E,  // uint8_t
        IntegralDividerExponent             = 0x3F,  // uint8_t
        SoftCurrentRegulationLevelForward   = 0x40,  // uint16_t
        SoftCurrentRegulationLevelReverse   = 0x42,  // uint16_t
        OptionsByte3                        = 0x50,  // uint8_t
        ProportionalMultiplier              = 0x51,  // uint16_t
        ProportionalExponent                = 0x53,  // uint8_t
        IntegralMultiplier                  = 0x54,  // uint16_t
        IntegralExponent                    = 0x56,  // uint8_t
        DerivativeMultiplier                = 0x57,  // uint16_t
        DerivativeExponent                  = 0x59,  // uint8_t
        PIDPeriod                           = 0x5A,  // uint16_t
        IntegralLimit                       = 0x5C,  // uint16_t
        MaxDutyCycleWhileFeedbackOutOfRange = 0x5E,  // uint16_t
        MaxAccelerationForward              = 0x60,  // uint16_t
        MaxAccelerationReverse              = 0x62,  // uint16_t
        MaxDecelerationForward              = 0x64,  // uint16_t
        MaxDecelerationReverse              = 0x66,  // uint16_t
        MaxDutyCycleForward                 = 0x68,  // uint16_t
        MaxDutyCycleReverse                 = 0x6A,  // uint16_t
        EncodedHardCurrentLimitForward      = 0x6C,  // uint16_t
        EncodedHardCurrentLimitReverse      = 0x6E,  // uint16_t
        BrakeDurationForward                = 0x70,  // uint8_t
        BrakeDurationReverse                = 0x71,  // uint8_t
        SoftCurrentLimitForward             = 0x72,  // uint16_t
        SoftCurrentLimitReverse             = 0x74,  // uint16_t
    };

    uint8_t getVar8SingleByte(uint8_t offset)
    {
        return commandR8((uint8_t)GetVariable8 | (offset + 1));
    }
    
    uint16_t getVar16SingleByte(uint8_t offset)
    {
        return commandR16((uint8_t)GetVariable16 | (offset + 1));
    }
    
    uint8_t getVar8(uint8_t offset)
    {
        uint8_t result;
        segmentRead(GetVariables, offset, 1, &result);
        return result;
    }
    
    uint16_t getVar16(uint8_t offset)
    {
        uint8_t buffer[2];
        segmentRead(GetVariables, offset, 2, buffer);
        return ((uint16_t)buffer[0] << 0) | ((uint16_t)buffer[1] << 8);
    }

    uint32_t getVar32(uint8_t offset)
    {
        uint8_t buffer[4];
        segmentRead(GetVariables, offset, 4, buffer);
        return ((uint32_t)buffer[0] << 0) |
            ((uint32_t)buffer[1] << 8) |
            ((uint32_t)buffer[2] << 16) |
            ((uint32_t)buffer[3] << 24);
    }

    void setRAMSetting8(uint8_t offset, uint8_t val)
    {
        segmentWrite(SetRAMSettings, offset, 1, &val);
    }
    
    void setRAMSetting16(uint8_t offset, uint16_t val)
    {
        uint8_t buffer[2] = {(uint8_t)val, (uint8_t)(val >> 8)};
        segmentWrite(SetRAMSettings, offset, 2, buffer);
    }
    
    void setRAMSetting8x2(uint8_t offset, uint8_t val1, uint8_t val2)
    {
        uint8_t buffer[2] = {val1, val2};
        segmentWrite(SetRAMSettings, offset, 2, buffer);
    }
    
    void setRAMSetting16x2(uint8_t offset, uint16_t val1, uint16_t val2)
    {
        uint8_t buffer[4] = {(uint8_t)val1, (uint8_t)(val1 >> 8),
                             (uint8_t)val2, (uint8_t)(val2 >> 8)};
        segmentWrite(SetRAMSettings, offset, 4, buffer);
    }

    // set multiplier and exponent together in one segment write
    // (slightly faster than separate calls to getRAMSetting16() and getRAMSetting8())
    void setPIDCoefficient(uint8_t offset, uint16_t multiplier, uint8_t exponent)
    {
        uint8_t buffer[3] = {(uint8_t)multiplier, (uint8_t)(multiplier >> 8), exponent};
        segmentWrite(SetRAMSettings, offset, 3, buffer);
    }
    
    uint8_t getRAMSetting8(uint8_t offset)
    {
        uint8_t result;
        segmentRead(GetRAMSettings, offset, 1, &result);
        return result;
    }
    
    uint16_t getRAMSetting16(uint8_t offset)
    {
        uint8_t buffer[2];
        segmentRead(GetRAMSettings, offset, 2, buffer);
        return ((uint16_t)buffer[0] << 0) | ((uint16_t)buffer[1] << 8);
    }

    // Convenience functions that take care of casting a JrkG2Command to a uint8_t.
    
    void commandQuick(JrkG2Command cmd)
    {
        commandQuick((uint8_t)cmd);
    }
    
    void commandW7(JrkG2Command cmd, uint8_t val)
    {
        commandW7((uint8_t)cmd, val);
    }
    
    void commandWs14(JrkG2Command cmd, int16_t val)
    {
        commandWs14((uint8_t)cmd, val);
    }
    
    uint8_t commandR8(JrkG2Command cmd)
    {
        return commandR8((uint8_t)cmd);
    }
    
    uint16_t commandR16(JrkG2Command cmd)
    {
        return commandR16((uint8_t)cmd);
    }
    
    void segmentRead(JrkG2Command cmd, uint8_t offset,
                     uint8_t length, uint8_t * buffer)
    {
        segmentRead((uint8_t)cmd, offset, length, buffer);
    }

    void segmentWrite(JrkG2Command cmd, uint8_t offset,
    uint8_t length, uint8_t * buffer)
    {
        segmentWrite((uint8_t)cmd, offset, length, buffer);
    }
    
    // Low-level functions implemented by the serial/I2C subclasses.
    
    virtual void commandQuick(uint8_t cmd) = 0;
    virtual void commandW7(uint8_t cmd, uint8_t val) = 0;
    virtual void commandWs14(uint8_t cmd, int16_t val) = 0;
    virtual uint8_t commandR8(uint8_t cmd) = 0;
    virtual uint16_t commandR16(uint8_t cmd) = 0;
    virtual void segmentRead(uint8_t cmd, uint8_t offset,
    uint8_t length, uint8_t * buffer) = 0;
    virtual void segmentWrite(uint8_t cmd, uint8_t offset,
        uint8_t length, uint8_t * buffer) = 0;
};

/// Represents a serial connection to a Jrk G2.
///
/// For the high-level commands you can use on this object, see JrkG2Base.
class JrkG2Serial : public JrkG2Base
{
public:
    JrkG2Serial(AsyncSerial *stream, uint8_t deviceNumber = 255) :
    _deviceNumber(deviceNumber)
    {
        _stream = stream;
    }

    /// Gets the serial device number this object is using.
    uint8_t getDeviceNumber() { return _deviceNumber; }

private:
    //Serial *_stream;
    AsyncSerial *_stream;
    
    const uint8_t _deviceNumber;
    
    virtual void commandQuick(uint8_t cmd) { sendCommandHeader(cmd); }
    virtual void commandW7(uint8_t cmd, uint8_t val);
    virtual void commandWs14(uint8_t cmd, int16_t val);
    virtual uint8_t commandR8(uint8_t cmd);
    virtual uint16_t commandR16(uint8_t cmd);
    virtual void segmentRead(uint8_t cmd, uint8_t offset,
    uint8_t length, uint8_t * buffer);
    virtual void segmentWrite(uint8_t cmd, uint8_t offset,
    uint8_t length, uint8_t * buffer);
    
    void sendCommandHeader(uint8_t cmd);
    void serialW7(uint8_t val) { _stream->putc(val & 0x7F); }
};

/// Represents an I2C connection to a Jrk G2.
///
/// For the high-level commands you can use on this object, see JrkG2Base.
class JrkG2I2C : public JrkG2Base
{
public:
  JrkG2I2C(I2C *i2c, uint8_t address = 11) : _address((address & 0x7F) << 1)
  {
      _i2c = i2c;
  }

  /// Gets the I2C address this object is using.
  uint8_t getAddress() { return _address; }

private:
    I2C *_i2c;
    const uint8_t _address;
    
    virtual void commandQuick(uint8_t cmd);
    virtual void commandW7(uint8_t cmd, uint8_t val);
    virtual void commandWs14(uint8_t cmd, int16_t val);
    virtual uint8_t commandR8(uint8_t cmd);
    virtual uint16_t commandR16(uint8_t cmd);
    virtual void segmentRead(uint8_t cmd, uint8_t offset,
        uint8_t length, uint8_t * buffer);
    virtual void segmentWrite(uint8_t cmd, uint8_t offset,
        uint8_t length, uint8_t * buffer) ;
};