Esempio n. 1
0
void motion_registers_reset(void)
// Reset the motion registers to zero values.
{
    // Set the default position, velocity and delta data.
    registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0);
    registers_write_word(REG_CURVE_IN_VELOCITY_HI, REG_CURVE_IN_VELOCITY_LO, 0);
    registers_write_word(REG_CURVE_OUT_VELOCITY_HI, REG_CURVE_OUT_VELOCITY_LO, 0);
    registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 0);

    // Update the buffer status.
    registers_write_byte(REG_CURVE_RESERVED, 0);
    registers_write_byte(REG_CURVE_BUFFER, motion_buffer_left());
}
Esempio n. 2
0
void regulator_registers_defaults(void)
// Initialize the motion related register values.  This is done here to
// keep the motion related code in a single file.  
{
    // Default control parameters.
    registers_write_word(REG_PID_PGAIN_HI, REG_PID_PGAIN_LO, 510);      // k1
    registers_write_word(REG_PID_DGAIN_HI, REG_PID_DGAIN_LO, 23287);    // k2   

    // Default position limits.
    registers_write_word(REG_MIN_SEEK_HI, REG_MIN_SEEK_LO, 0x0060);
    registers_write_word(REG_MAX_SEEK_HI, REG_MAX_SEEK_LO, 0x03A0);

    // Default reverse seek setting.
    registers_write_byte(REG_REVERSE_SEEK, 0x00);
}
Esempio n. 3
0
void ipd_registers_defaults(void)
// Initialize the PID algorithm related register values.  This is done 
// here to keep the PID related code in a single file.  
{
    // Default gain values.
    registers_write_word(REG_PID_PGAIN_HI, REG_PID_PGAIN_LO, 0x0400);
    registers_write_word(REG_PID_DGAIN_HI, REG_PID_DGAIN_LO, 0x0300);
    registers_write_word(REG_PID_IGAIN_HI, REG_PID_IGAIN_LO, 0x4000);

    // Default position limits.
    registers_write_word(REG_MIN_SEEK_HI, REG_MIN_SEEK_LO, 0x0060);
    registers_write_word(REG_MAX_SEEK_HI, REG_MAX_SEEK_LO, 0x03A0);

    // Default reverse seek setting.
    registers_write_byte(REG_REVERSE_SEEK, 0x00);
}
Esempio n. 4
0
void pwm_registers_defaults(void)
// Initialize the PWM algorithm related register values.  This is done
// here to keep the PWM related code in a single file.
{
    // PWM divider is a value between 1 and 1024.  This divides the fundamental
    // PWM frequency (500 kHz for 8MHz clock, 1250 kHz for 20MHz clock)(div=16) by a
    // constant value to produce a PWM frequency suitable to drive a motor.  A
    // small motor with low inductance and impedance such as those found in an
    // RC servo will my typically use a divider value between 16 and 64.  A larger
    // motor with higher inductance and impedance may require a greater divider.
    registers_write_word(REG_PWM_FREQ_DIVIDER_HI, REG_PWM_FREQ_DIVIDER_LO, DEFAULT_PWM_FREQ_DIVIDER);
}
Esempio n. 5
0
void motion_next(uint16_t delta)
// Increment the buffer counter by the indicated delta and return the position
// and velocity from the buffered curves.  If the delta is zero the current
// position and velocity is returned.
{
    float fposition;
    float fvelocity;

    // Determine if curve motion is disabled in the registers.
    if (!(registers_read_byte(REG_FLAGS_LO) & (1<<FLAGS_LO_MOTION_ENABLED))) return;

    // Are we processing an empty curve?
    if (motion_tail == motion_head)
    {
        // Yes. Keep the counter and duration at zero.
        motion_counter = 0;
        motion_duration = 0;
    }
    else
    {
        // Increment the counter.
        motion_counter += delta;

        // Have we exceeded the duration of the currently buffered curve?
        while (motion_counter > curve_get_duration())
        {
            // Reduce the buffer counter by the currently buffered curve duration.
            motion_counter -= curve_get_duration();

            // Reduce the buffer duration by the currently buffered curve duration.
            motion_duration -= curve_get_duration();

            // Increment the tail to process the next buffered curve.
            motion_tail = (motion_tail + 1) & MOTION_BUFFER_MASK;

            // Has the tail caught up with the head?
            if (motion_tail == motion_head)
            {
                // Initialize an empty hermite curve with a zero duration.  This is a degenerate case for
                // the hermite cuve that will always return the position of the curve without velocity.
                curve_init(0, 0, keys[motion_head].position, keys[motion_head].position, 0.0, 0.0);

                // Reset the buffer counter and duration to zero.
                motion_counter = 0;
                motion_duration = 0;
            }
            else
            {
                uint8_t curr_point;
                uint8_t next_point;

                // Get the current point and next point for the curve.
                curr_point = motion_tail;
                next_point = (curr_point + 1) & MOTION_BUFFER_MASK;

                // Initialize the hermite curve from the current and next point.
                curve_init(0, keys[next_point].delta,
                           keys[curr_point].position, keys[next_point].position,
                           keys[curr_point].out_velocity, keys[next_point].in_velocity);
            }

            // Update the space available in the buffer.
            registers_write_byte(REG_CURVE_BUFFER, motion_buffer_left());
        }
    }

    // Get the position and velocity from the hermite curve.
    curve_solve(motion_counter, &fposition, &fvelocity);

    // The velocity is in position units a millisecond, but we really need the
    // velocity to be measured in position units every 10 milliseconds to match
    // the sample period of the ADC.
    fvelocity *= 10.0;

    // Update the seek position register.
    registers_write_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO, float_to_int(fposition));

    // Update the seek velocity register.
    registers_write_word(REG_SEEK_VELOCITY_HI, REG_SEEK_VELOCITY_LO, float_to_int(fvelocity));
}
Esempio n. 6
0
int main (void)
{
    // Configure pins to the default states.
    config_pin_defaults();

    // Initialize the watchdog module.
    watchdog_init();

    // First, initialize registers that control servo operation.
    registers_init();

    // Initialize the PWM module.
    pwm_init();

    // Initialize the ADC module.
    adc_init();

    // Initialize the PID algorithm module.
    pid_init();

#if CURVE_MOTION_ENABLED
    // Initialize curve motion module.
    motion_init();
#endif

    // Initialize the power module.
    power_init();

#if PULSE_CONTROL_ENABLED
    pulse_control_init();
#endif

#if ENCODER_ENABLED
    // Initialize software I2C to talk with encoder.
    swi2c_init();
#endif

    // Initialize the TWI slave module.
    twi_slave_init(registers_read_byte(REG_TWI_ADDRESS));

    // Finally initialize the timer.
    timer_set(0);

    // Enable interrupts.
    sei();

    // Wait until initial position value is ready.
    {
        int16_t position;
        // Get start-up position
#if ENCODER_ENABLED
        position=(int16_t) enc_get_position_value();
#else
        while (!adc_position_value_is_ready());
        position=(int16_t) adc_get_position_value();
#endif

#if CURVE_MOTION_ENABLED
        // Reset the curve motion with the current position of the servo.
        motion_reset(position);
#endif

        // Set the initial seek position and velocity.
        registers_write_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO, position);
        registers_write_word(REG_SEEK_VELOCITY_HI, REG_SEEK_VELOCITY_LO, 0);
    }


    // XXX Enable PWM and writing.  I do this for now to make development and
    // XXX tuning a bit easier.  Constantly manually setting these values to
    // XXX turn the servo on and write the gain values get's to be a pain.
    pwm_enable();
    registers_write_enable();

    // This is the main processing loop for the servo.  It basically looks
    // for new position, power or TWI commands to be processed.
    for (;;)
    {
        uint8_t tick;
        int16_t pwm;
        int16_t position;
        // Is ADC position value ready?
        // NOTE: Even when the encoder is enabled, we still need the ADC potmeasurement as the
        //       clock because that is how the original firmware was written.
        tick=adc_position_value_is_ready();
        if(tick)
        {
#if PULSE_CONTROL_ENABLED
            // Give pulse control a chance to update the seek position.
            pulse_control_update();
#endif

#if CURVE_MOTION_ENABLED
            // Give the motion curve a chance to update the seek position and velocity.
            motion_next(10);
#endif
        }

        // Get the new position value.
        if(tick)
        {
            position = (int16_t) adc_get_position_value(); // NOTE: For encoder builds, this is the clock: clear the flag
#if ENCODER_ENABLED
        } // Always run the encoder (faster PID to PWM loop = better?)
        position = (int16_t) enc_get_position_value();
        if (position >= 0)
        {
#endif
            // Call the PID algorithm module to get a new PWM value.
            pwm = pid_position_to_pwm(position, tick);

            // Update the servo movement as indicated by the PWM value.
            // Sanity checks are performed against the position value.
            pwm_update(position, pwm);
        }

        // Is a power value ready?
        if (adc_power_value_is_ready())
        {
            // Get the new power value.
            uint16_t power = adc_get_power_value();

            // Update the power value for reporting.
            power_update(power);
        }

        // Was a command recieved?
        if (twi_data_in_receive_buffer())
        {
            // Handle any TWI command.
            handle_twi_command();
        }

#if MAIN_MOTION_TEST_ENABLED
        // This code is in place for having the servo drive itself between
        // two positions to aid in the servo tuning process.  This code
        // should normally be disabled in config.h.
#if CURVE_MOTION_ENABLED
        if (motion_time_left() == 0)
        {
            registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 2000);
            registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0x0100);
            motion_append();
            registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 1000);
            registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0x0300);
            motion_append();
            registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 2000);
            registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0x0300);
            motion_append();
            registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 1000);
            registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0x0100);
            motion_append();
        }
#else
        {
            // Get the timer.
            uint16_t timer = timer_get();

            // Reset the timer if greater than 800.
            if (timer > 800) timer_set(0);

            // Look for specific events.
            if (timer == 0)
            {
                registers_write_word(REG_SEEK_HI, REG_SEEK_LO, 0x0100);
            }
            else if (timer == 400)
            {
                registers_write_word(REG_SEEK_HI, REG_SEEK_LO, 0x0300);
            }
        }
#endif
#endif
    }

    return 0;
}
Esempio n. 7
0
int16_t regulator_position_to_pwm(int16_t current_position)
// This function takes the current servo position as input and outputs a pwm
// value for the servo motors.  The current position value must be within the
// range 0 and 1023. The output will be within the range of -255 and 255 with
// values less than zero indicating clockwise rotation and values more than
// zero indicating counter-clockwise rotation.
{
    int16_t k1;
    int16_t k2;
    int16_t output;
    int16_t command_position;
    int16_t current_velocity;
    int16_t current_error;

    // Get the command position to where the servo is moving to from the registers.
    command_position = (int16_t) registers_read_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO);
    
    // Get estimated velocity
    current_velocity = (int16_t) registers_read_word(REG_VELOCITY_HI, REG_VELOCITY_LO);

    // Are we reversing the seek sense?
    if (registers_read_byte(REG_REVERSE_SEEK) != 0)
    {
        // Yes. Update the system registers with an adjusted reverse sense
        // position. With reverse sense, the position value to grows from
        // a low value to high value in the clockwise direction.
        registers_write_word(REG_POSITION_HI, REG_POSITION_LO, (uint16_t) (MAX_POSITION - current_position));

        // Adjust command position for the reverse sense.
        command_position = MAX_POSITION - command_position;
    }
    else
    {
        // No. Update the system registers with a non-reverse sense position.
        // Normal position value grows from a low value to high value in the
        // counter-clockwise direction.
        registers_write_word(REG_POSITION_HI, REG_POSITION_LO, (uint16_t) current_position);
    }
    
    // Get the control parameters.
    k1 = (int16_t) registers_read_word(REG_PID_PGAIN_HI, REG_PID_PGAIN_LO);
    k2 = (int16_t) registers_read_word(REG_PID_DGAIN_HI, REG_PID_DGAIN_LO);

    // Determine the current error.
    current_error = command_position - current_position;

	// The following operations are fixed point operations. To add/substract
	// two fixed point values they must have the same fractional precision
    // (the same number of bits behind the decimal).  When two fixed point
    // values are multiplied the fractional precision of the result is the sum
    // of the fractional precision of the the the factors (the sum of the bits
    // behind the decimal of each factor).  To reach the best possible precision
    // the fixed point bit is chosen for each variable separately according to 
    // its maximum and dimension.  A shift factor is then applied after
    // multiplication in the fixed_multiply() function to adjust the fractional
    // precision of the product for addition or subtraction.

    // Used fixed point bits, counted from the lowest bit:
    // Control Param. k1:  fp_k1     =  5
    // Control Param. k2:  fp_k2     =  5
    // Position state z1:  fp_z1     =  5 
    // Velocity state z2:  fp_z2     = 11
	// Real Position  x1:  fp_x1     =  0
	// PWM output:         fp_output =  0

	// output = k1 * x1 + k2 * x2
    output  = fixed_multiply(k1, current_error, 5);         // fp: 5 + 0  -> 0 : rshift = 5
    output += fixed_multiply(k2, -current_velocity, 16);    // fp: 5 + 11 -> 0 : rshift = 16

    // Check for output saturation.
    if (output > MAX_OUTPUT) output = MAX_OUTPUT;
    if (output < MIN_OUTPUT) output = MIN_OUTPUT;

    return output;
}
Esempio n. 8
0
File: main.c Progetto: mokis/MServo
int main (void)
{
	// Configure pins to the default states.
	config_pin_defaults();

    // Initialize the watchdog module.
    watchdog_init();

    // First, initialize registers that control servo operation.
    registers_init();

    // Initialize the PWM module.
    pwm_init();

    // Initialize the ADC module.
    adc_init();

#if ESTIMATOR_ENABLED
    // Initialize the state estimator module.
    estimator_init();
#endif

#if REGULATOR_MOTION_ENABLED
    // Initialize the regulator algorithm module.
    regulator_init();
#endif

#if PID_MOTION_ENABLED
    // Initialize the PID algorithm module.
    pid_init();
#endif

#if IPD_MOTION_ENABLED
    // Initialize the IPD algorithm module.
    ipd_init();
#endif

#if CURVE_MOTION_ENABLED
    // Initialize curve motion module.
    motion_init();
#endif

    // Initialize the power module.
    power_init();

#if PULSE_CONTROL_ENABLED
    pulse_control_init();
#endif

    // Initialize the TWI slave module.
    twi_slave_init(registers_read_byte(REG_TWI_ADDRESS));

    // Finally initialize the timer.
    timer_set(0);

    // Enable interrupts.
    sei();

    // Wait until initial position value is ready.
    while (!adc_position_value_is_ready());

#if CURVE_MOTION_ENABLED
    // Reset the curve motion with the current position of the servo.
    motion_reset(adc_get_position_value());
#endif

    // Set the initial seek position and velocity.
    registers_write_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO, adc_get_position_value());
    registers_write_word(REG_SEEK_VELOCITY_HI, REG_SEEK_VELOCITY_LO, 0);

    // XXX Enable PWM and writing.  I do this for now to make development and
    // XXX tuning a bit easier.  Constantly manually setting these values to 
    // XXX turn the servo on and write the gain values get's to be a pain.
    pwm_enable();
    registers_write_enable();

    // This is the main processing loop for the servo.  It basically looks
    // for new position, power or TWI commands to be processed.
    for (;;)
    {
        // Is position value ready?
        if (adc_position_value_is_ready())
        {
            int16_t pwm;
            int16_t position;

#if PULSE_CONTROL_ENABLED
            // Give pulse control a chance to update the seek position.
            pulse_control_update();
#endif

#if CURVE_MOTION_ENABLED
            // Give the motion curve a chance to update the seek position and velocity.
            motion_next(10);
#endif

            // Get the new position value.
            position = (int16_t) adc_get_position_value();

#if ESTIMATOR_ENABLED
            // Estimate velocity.
            estimate_velocity(position);
#endif

#if PID_MOTION_ENABLED
            // Call the PID algorithm module to get a new PWM value.
            pwm = pid_position_to_pwm(position);
#endif

#if IPD_MOTION_ENABLED
            // Call the IPD algorithm module to get a new PWM value.
            pwm = ipd_position_to_pwm(position);
#endif

#if REGULATOR_MOTION_ENABLED
            // Call the state regulator algorithm module to get a new PWM value.
            pwm = regulator_position_to_pwm(position);
#endif

            // Update the servo movement as indicated by the PWM value.
            // Sanity checks are performed against the position value.
            pwm_update(position, pwm);
        }

        // Is a power value ready?
        if (adc_power_value_is_ready())
        {
            // Get the new power value.
            uint16_t power = adc_get_power_value();

            // Update the power value for reporting.
            power_update(power);
        }

        // Was a command recieved?
        if (twi_data_in_receive_buffer())
        {
            // Handle any TWI command.
            handle_twi_command();
        }

#if MAIN_MOTION_TEST_ENABLED
        // This code is in place for having the servo drive itself between 
        // two positions to aid in the servo tuning process.  This code 
        // should normally be disabled in config.h.
#if CURVE_MOTION_ENABLED
        if (motion_time_left() == 0)
        {
            registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 2000);
            registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0x0100);
            motion_append();
            registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 1000);
            registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0x0300);
            motion_append();
            registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 2000);
            registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0x0300);
            motion_append();
            registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 1000);
            registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0x0100);
            motion_append();
        }
#else
        {
            // Get the timer.
            uint16_t timer = timer_get();

            // Reset the timer if greater than 800.
            if (timer > 800) timer_set(0);

            // Look for specific events.
            if (timer == 0)
            {
                registers_write_word(REG_SEEK_HI, REG_SEEK_LO, 0x0100);
            }
            else if (timer == 400)
            {
                registers_write_word(REG_SEEK_HI, REG_SEEK_LO, 0x0300);
            }
        }
#endif
#endif
    }

    return 0;
}
Esempio n. 9
0
int16_t ipd_position_to_pwm(int16_t current_position)
// This function takes the current servo position as input and outputs a pwm
// value for the servo motors.  The current position value must be within the
// range 0 and 1023. The output will be within the range of -255 and 255 with
// values less than zero indicating clockwise rotation and values more than
// zero indicating counter-clockwise rotation.
//
// The feedback approach implemented here was first published in Richard Phelan's
// Automatic Control Systems, Cornell University Press, 1977 (ISBN 0-8014-1033-9)
//
// The theory of operation of this function will be filled in later, but the
// diagram below should gives a general picture of how it is intended to work.
//
//
//                           +<------- bounds checking -------+
//                           |                                |
//             |¯¯¯¯¯|   |¯¯¯¯¯¯¯¯|   |¯¯¯¯¯|   |¯¯¯¯¯¯¯¯¯|   |
//  command -->|  -  |-->|integral|-->|  -  |-->|  motor  |-->+-> actuator
//             |_____|   |________|   |_____|   |_________|   |
//                |                      |                    |
//                |                      +<-- Kv * velocity --+
//                |                      |                    |
//                |                      +<-- Kp * position --+
//                |                                           |
//                +<-------------Ki * position ---------------+
//
//
{
    int16_t deadband;
    int16_t command_position;
    int16_t maximum_position;
    int16_t minimum_position;
    int16_t current_velocity;
    int16_t command_error;
    int16_t output;
    int16_t position_output;
    int16_t velocity_output;
    int16_t integral_output;
    uint16_t position_gain;
    uint16_t velocity_gain;
    uint16_t integral_gain;

    // Determine the velocity as the difference between the current and previous position.
    current_velocity = current_position - previous_position;

    // Update the previous position.
    previous_position = current_position;

    // Get the command position to where the servo is moving to from the registers.
    command_position = (int16_t) registers_read_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO);
    minimum_position = (int16_t) registers_read_word(REG_MIN_SEEK_HI, REG_MIN_SEEK_LO);
    maximum_position = (int16_t) registers_read_word(REG_MAX_SEEK_HI, REG_MAX_SEEK_LO);

    // Set the deadband value and divide by two for calculations below.
    deadband = 2;

    // Are we reversing the seek sense?
    if (registers_read_byte(REG_REVERSE_SEEK) != 0)
    {
        // Yes. Update the system registers with an adjusted reverse sense
        // position. With reverse sense, the position value to grows from
        // a low value to high value in the clockwise direction.
        registers_write_word(REG_POSITION_HI, REG_POSITION_LO, (uint16_t) (MAX_POSITION - current_position));

        // Adjust command position for the reverse sense.
        command_position = MAX_POSITION - command_position;
        minimum_position = MAX_POSITION - minimum_position;
        maximum_position = MAX_POSITION - maximum_position;
    }
    else
    {
        // No. Update the system registers with a non-reverse sense position.
        // Normal position value grows from a low value to high value in the
        // counter-clockwise direction.
        registers_write_word(REG_POSITION_HI, REG_POSITION_LO, (uint16_t) current_position);
    }

    // Sanity check the command position. We do this because this value is
    // passed from the outside to the servo and it could be an invalid value.
    if (command_position < minimum_position) command_position = minimum_position;
    if (command_position > maximum_position) command_position = maximum_position;

    // The command error is the difference between the command position and current position.
    command_error = command_position - current_position;

    // Adjust proportional error due to deadband.  The potentiometer readings are a 
    // bit noisy and there is typically one or two units of difference from reading 
    // to reading when the servo is holding position.  Adding deadband decreases some 
    // of the twitchiness in the servo caused by this noise.
    if (command_error > deadband)
    {
        // Factor out deadband from the command error.
        command_error -= deadband;
    }
    else if (command_error < -deadband)
    {
        // Factor out deadband from the command error.
        command_error += deadband;
    }
    else
    {
        // Adjust to command error to zero within deadband.
        command_error = 0;
    }

    // Get the positional, velocity and integral gains from the registers.
    position_gain = registers_read_word(REG_PID_PGAIN_HI, REG_PID_PGAIN_LO);
    velocity_gain = registers_read_word(REG_PID_DGAIN_HI, REG_PID_DGAIN_LO);
    integral_gain = registers_read_word(REG_PID_IGAIN_HI, REG_PID_IGAIN_LO);

    // Add the command error scaled by the position gain to the integral accumulator.
    // The integral accumulator maintains a sum of total error over each iteration.
    integral_accumulator_update(command_error, integral_gain);

    // Get the integral output. There is no gain applied to the integral output.
    integral_output = integral_accumulator_get();

    // Determine the position output component.  We multiply the position gain
    // by the current position of the servo to create the position output.
    position_output = fixed_multiply(current_position, position_gain);

    // Determine the velocity output component.  We multiply the velocity gain
    // by the current velocity of the servo to create the velocity output.
    velocity_output = fixed_multiply(current_velocity, velocity_gain);

    // registers_write_word(REG_RESERVED_18, REG_RESERVED_19, (uint16_t) position_output);
    // registers_write_word(REG_RESERVED_1A, REG_RESERVED_1B, (uint16_t) velocity_output);
    // registers_write_word(REG_RESERVED_1C, REG_RESERVED_1D, (uint16_t) integral_output);

    // The integral output drives the output and the position and velocity outputs
    // function as a frictional component to counter the integral output.
    output = integral_output - position_output - velocity_output;

    // Is the output saturated? If so we need limit the output and clip
    // the integral accumulator just at the saturation level.
    if (output < MIN_OUTPUT)
    {
        // Calculate a new integral accumulator based on the output range.  This value
        // is calculated to keep the integral output just on the verge of saturation.
        integral_accumulator_reset(position_output + velocity_output + MIN_OUTPUT);

        // Limit the output.
        output = MIN_OUTPUT;
    }
    else if (output > MAX_OUTPUT)
    {
        // Calculate a new integral accumulator based on the output range.  This value
        // is calculated to keep the integral output just on the verge of saturation.
        integral_accumulator_reset(position_output + velocity_output + MAX_OUTPUT);

        // Limit the output.
        output = MAX_OUTPUT;
    }

    // Return the output.
    return output;
}
Esempio n. 10
0
void pulse_control_update(void)
// Update the servo seek position and velocity based on any received servo pulses.
{
    int16_t pulse_position;

    // Did we get a pulse?
    if (pulse_flag)
    {
        // Ignore unusual pulse durations as a sanity check.
        if ((pulse_duration > 500) && (pulse_duration < 2500))
        {
            // Convert the pulse duration to a pulse time.
            pulse_position = pulse_duration - 998;

            // Limit the pulse position.
            if (pulse_position < MIN_POSITION) pulse_position = MIN_POSITION;
            if (pulse_position > MAX_POSITION) pulse_position = MAX_POSITION;

            // Apply a low pass filter to the pulse position.
            pulse_position = filter_update(pulse_position, &filter_reg_pulse);

            // Are we reversing the seek sense?
            if (registers_read_byte(REG_REVERSE_SEEK) != 0)
            {
                // Yes. Update the seek position using reverse sense.
                registers_write_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO, (MAX_POSITION - pulse_position));
            }
            else
            {
                // No. Update the seek position using normal sense.
                registers_write_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO, pulse_position);
            }

            // The seek velocity will always be zero.
            registers_write_word(REG_SEEK_VELOCITY_HI, REG_SEEK_VELOCITY_LO, 0);

            // Make sure pwm is enabled.
            pwm_enable();

            // Update the pulse time used as a time stamp.
            pulse_time = timer_get();
        }

        // Reset the pulse time flag.
        pulse_flag = 0;
    }
    else
    {
        // If we haven't seen a pulse in .5 seconds disable pwm.
        if (timer_delta(pulse_time) > 50)
        {
            // Disable pwm.
            pwm_disable();

            // Update the pulse time used as a time stamp.
            pulse_time = timer_get();
        }
    }

    return;
}
Esempio n. 11
0
int main (void)
{
    // Configure pins to the default states.
    config_pin_defaults();

    // Initialize the watchdog module.
    watchdog_init();

    // First, initialize registers that control servo operation.
    registers_init();

#if PWM_STD_ENABLED || PWM_ENH_ENABLED
    // Initialize the PWM module.
    pwm_init();
#endif

#if STEP_ENABLED
    // Initialise the stepper motor
    step_init();
#endif
    
    // Initialize the ADC module.
    adc_init();

    // Initialise the Heartbeart
    heartbeat_init();

    // Initialize the PID algorithm module.
    pid_init();

#if CURVE_MOTION_ENABLED
    // Initialize curve motion module.
    motion_init();
#endif

    // Initialize the power module.
    power_init();

#if PULSE_CONTROL_ENABLED
    pulse_control_init();
#endif

#if BACKEMF_ENABLED
    // Initialise the back emf module
    backemf_init();
#endif

#if ALERT_ENABLED
    //initialise the alert registers
    alert_init();
#endif

    // Initialize the TWI slave module.
    twi_slave_init(banks_read_byte(POS_PID_BANK, REG_TWI_ADDRESS));

    // Finally initialize the timer.
    timer_set(0);

    // Enable interrupts.
    sei();

    // Trigger the adc sampling hardware
    adc_start(ADC_CHANNEL_POSITION);

    // Wait until initial position value is ready.
    while (!adc_position_value_is_ready());

#if CURVE_MOTION_ENABLED
    // Reset the curve motion with the current position of the servo.
    motion_reset(adc_get_position_value());
#endif

    // Set the initial seek position and velocity.
    registers_write_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO, adc_get_position_value());
    registers_write_word(REG_SEEK_VELOCITY_HI, REG_SEEK_VELOCITY_LO, 0);

    // XXX Enable PWM and writing.  I do this for now to make development and
    // XXX tuning a bit easier.  Constantly manually setting these values to
    // XXX turn the servo on and write the gain values get's to be a pain.
#if PWM_STD_ENABLED || PWM_ENH_ENABLED
    pwm_enable();
#endif
#if STEP_ENABLED
    step_enable();
#endif

    registers_write_enable();

    // This is the main processing loop for the servo.  It basically looks
    // for new position, power or TWI commands to be processed.
    for (;;)
    {
        static uint8_t emf_motor_is_coasting = 0;

        // Is the system heartbeat ready?
        if (heartbeat_is_ready())
        {
            static int16_t last_seek_position;
            static int16_t wait_seek_position;
            static int16_t new_seek_position;

            // Clear the heartbeat flag
            heartbeat_value_clear_ready();

#if PULSE_CONTROL_ENABLED
            // Give pulse control a chance to update the seek position.
            pulse_control_update();
#endif

#if CURVE_MOTION_ENABLED
            // Give the motion curve a chance to update the seek position and velocity.
            motion_next(10);
#endif

            // General call support
            // Check to see if we have the wait flag enabled. If so save the new position, and write in the
            // old position until we get the move command
            if (general_call_enabled()) 
            {
                //we need to wait for the go command before moving
                if (general_call_wait())
                {
                    // store the new position, but let the servo lock to the last seek position
                    wait_seek_position = (int16_t) registers_read_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO);
                    if (wait_seek_position != last_seek_position) // do we have a new position?
                    {
                        new_seek_position = wait_seek_position;
                        registers_write_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO, last_seek_position);
                    }
                }
                last_seek_position = registers_read_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO);

                //check to make sure that we can start the move.
                if (general_call_start() || 
                    ( registers_read_byte(REG_GENERAL_CALL_GROUP_START) == banks_read_byte(CONFIG_BANK, REG_GENERAL_CALL_GROUP)))
                {
                    // write the new position with the previously saved position
                    registers_write_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO, new_seek_position);  
                    general_call_start_wait_reset();  // reset the wait flag
                    general_call_start_reset();  // reset the start flag
                }
            }

#if BACKEMF_ENABLED
            // Quick and dirty check to see if pwm is active. This is done to make sure the motor doesn't
            // whine in the audible range while idling.
            uint8_t pwm_a = registers_read_byte(REG_PWM_DIRA);
            uint8_t pwm_b = registers_read_byte(REG_PWM_DIRB);
            if (pwm_a || pwm_b)
            {
                // Disable PWM
                backemf_coast_motor();
                emf_motor_is_coasting = 1;
            }
            else
            {
                // reset the back EMF value to 0
                banks_write_word(INFORMATION_BANK, REG_BACKEMF_HI, REG_BACKEMF_LO, 0);
                emf_motor_is_coasting = 0;
            }
#endif

#if ADC_ENABLED
            // Trigger the adc sampling hardware. This triggers the position and temperature sample
            adc_start(ADC_FIRST_CHANNEL);
#endif

        }
    
    
        // Wait for the samples to complete
#if TEMPERATURE_ENABLED
        if (adc_temperature_value_is_ready())
        {
            // Save temperature value to registers
            registers_write_word(REG_TEMPERATURE_HI, REG_TEMPERATURE_LO, (uint16_t)adc_get_temperature_value());
        }
#endif
#if CURRENT_ENABLED
        if (adc_power_value_is_ready())
        {

            // Get the new power value.
            uint16_t power = adc_get_power_value();

            // Update the power value for reporting.
            power_update(power);
        }
#endif
#if ADC_POSITION_ENABLED
        if (adc_position_value_is_ready())
        {
            int16_t position;
            // Get the new position value from the ADC module.
            position = (int16_t) adc_get_position_value();
#else
        if (position_value_is_ready())
        {
            int16_t position;
            // Get the position value from an external module.
            position = (int16_t) get_position_value();
#endif
            int16_t pwm;
#if BACKEMF_ENABLED
            if (emf_motor_is_coasting == 1)
            {
                uint8_t pwm_a = registers_read_byte(REG_PWM_DIRA);
                uint8_t pwm_b = registers_read_byte(REG_PWM_DIRB);

                // Quick and dirty check to see if pwm is active
                if (pwm_a || pwm_b)
                {
                    // Get the backemf sample.
                    backemf_get_sample();

                    // Turn the motor back on
                    backemf_restore_motor();
		    emf_motor_is_coasting = 0;
                }
            }
#endif

            // Call the PID algorithm module to get a new PWM value.
            pwm = pid_position_to_pwm(position);

#if ALERT_ENABLED
            // Update the alert status registers and do any throttling
            alert_check();
#endif

            // Allow any alerts to modify the PWM value.
            pwm = alert_pwm_throttle(pwm);

#if PWM_STD_ENABLED || PWM_ENH_ENABLED
            // Update the servo movement as indicated by the PWM value.
            // Sanity checks are performed against the position value.
            pwm_update(position, pwm);
#endif

#if STEP_ENABLED
            // Update the stepper motor as indicated by the PWM value.
            // Sanity checks are performed against the position value.
            step_update(position, pwm);
#endif
        }
    
        // Was a command recieved?
        if (twi_data_in_receive_buffer())
        {
            // Handle any TWI command.
            handle_twi_command();
        }

        // Update the bank register operations
        banks_update_registers();

#if MAIN_MOTION_TEST_ENABLED
        // This code is in place for having the servo drive itself between
        // two positions to aid in the servo tuning process.  This code
        // should normally be disabled in config.h.
#if CURVE_MOTION_ENABLED
        if (motion_time_left() == 0)
        {
            registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 2000);
            registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0x0100);
            motion_append();
            registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 1000);
            registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0x0300);
            motion_append();
            registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 2000);
            registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0x0300);
            motion_append();
            registers_write_word(REG_CURVE_DELTA_HI, REG_CURVE_DELTA_LO, 1000);
            registers_write_word(REG_CURVE_POSITION_HI, REG_CURVE_POSITION_LO, 0x0100);
            motion_append();
        }
#else
        {
            // Get the timer.
            uint16_t timer = timer_get();

            // Reset the timer if greater than 800.
            if (timer > 800) timer_set(0);

            // Look for specific events.
            if (timer == 0)
            {
                registers_write_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO, 0x0100);
            }
            else if (timer == 400)
            {
                registers_write_word(REG_SEEK_POSITION_HI, REG_SEEK_POSITION_LO, 0x0300);
            }
        }
#endif
#endif
    }

    return 0;
}
Esempio n. 12
0
int16_t speed_position_to_pwm(int16_t current_position)
// This is a modified pi algorithm with feed forward by which the seek
// veolcity is assumed to be a moving target. The algorithm attempts to
// output a pwm value that will achieve a predicted velocity.
{
    // We declare these static to keep them off the stack.
    static int16_t p_component;
    static int16_t i_component;
    static int16_t seek_velocity;
    static int16_t current_velocity;
    static int32_t pwm_output;
    static uint16_t i_gain;
    static uint16_t p_gain;
    static int16_t anti_windup;
    static uint16_t feed_forward_gain;
    static int16_t max_speed;

       
    // Get the seek velocity.
    seek_velocity = (int16_t) registers_read_word(REG_SEEK_VELOCITY_HI, REG_SEEK_VELOCITY_LO);

    max_speed = (int16_t)banks_read_word(POS_PID_BANK, REG_MAX_SPEED_HI, REG_MAX_SPEED_LO);

    if (seek_velocity> max_speed) seek_velocity =  max_speed;
    if (seek_velocity<-max_speed) seek_velocity = -max_speed;

    // Get current velocity from measurement of back-emf
    current_velocity = banks_read_word(INFORMATION_BANK, REG_BACKEMF_HI, REG_BACKEMF_LO);
    // Determine rotation sense by last PWM value
    if (registers_read_byte(REG_PWM_DIRA)) current_velocity = - current_velocity;


    // Are we reversing the seek sense?
    if (banks_read_byte(POS_PID_BANK, REG_REVERSE_SEEK) != 0)
    {
        registers_write_word(REG_VELOCITY_HI, REG_VELOCITY_LO, (uint16_t) -current_velocity);
    
    }
    else
    {
        // No. Update the position and velocity registers without change.
        registers_write_word(REG_VELOCITY_HI, REG_VELOCITY_LO, (uint16_t) current_velocity);
    }

    
    // Get the proportional and integral gains.
    p_gain = banks_read_word(POS_PID_BANK, REG_PID_PGAIN_HI, REG_PID_PGAIN_LO);
    i_gain = banks_read_word(POS_PID_BANK, REG_PID_IGAIN_HI, REG_PID_IGAIN_LO);
    feed_forward_gain = banks_read_word(POS_PID_BANK, REG_FEED_FORWARD_HI, REG_FEED_FORWARD_LO);
    anti_windup = (int16_t)banks_read_word(POS_PID_BANK, REG_ANTI_WINDUP_HI, REG_ANTI_WINDUP_LO);


    // The proportional component to the PID is the position error.
    p_component  = seek_velocity - current_velocity;
   
    // Increment the integral component of the PID
    i_component += (int32_t) p_component * (int32_t) i_gain;

    // Saturate the integrator for anti wind-up
    if (i_component >  anti_windup) i_component =  anti_windup;
    if (i_component < -anti_windup) i_component = -anti_windup;

    // Start with zero PWM output.
    pwm_output = 0;
    
    // Apply the feed forward component of the PWM output.
    pwm_output += (int32_t) seek_velocity * (int32_t) feed_forward_gain;

    // Apply the proportional component of the PWM output.
    pwm_output += (int32_t) p_component * (int32_t) p_gain;

    // Apply the integral component of the PWM output.
    pwm_output += i_component;

    // Shift by 8 to account for the multiply by the 8:8 fixed point gain values.
    pwm_output >>= 8;

    // Check for output saturation.
    if (pwm_output > MAX_OUTPUT)
    {
        // Can't go higher than the maximum output value.
        pwm_output = MAX_OUTPUT;
    }
    else if (pwm_output < MIN_OUTPUT)
    {
        // Can't go lower than the minimum output value.
        pwm_output = MIN_OUTPUT;
    }

    // Return the PID output.
    return (int16_t) pwm_output;
    //return (int16_t) registers_read_word(REG_SEEK_VELOCITY_HI, REG_SEEK_VELOCITY_LO);

}