OpenServo.com Forum Index OpenServo.com
Discussion of the OpenServo project
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

Porting software to OpenServo Version 3 boards
Goto page 1, 2, 3, 4, 5  Next
 
Post new topic   Reply to topic    OpenServo.com Forum Index -> Software
View previous topic :: View next topic  
Author Message
mpthompson



Joined: 02 Jan 2006
Posts: 650
Location: San Carlos, CA

PostPosted: Tue May 01, 2007 11:05 pm    Post subject: Porting software to OpenServo Version 3 boards Reply with quote

I finally was able to clear my plate of other projects to begin work of porting the OpenServo code base to Cliff's OpenServo Version 3 design. I wanted to start this thread to facilitate discussion as the porting process takes place.

My first priority will be to get the bootloader going. As long as the hardware is working, this shouldn't be a big deal. However, I do want to take this opportunity to change some aspects of the bootloader as described below.

The primary change I want to make is to eliminate the 3 second delay the bootloader imposes when the servo starts up or encounters a reset condition. This delay was originally implemented so a programmer can interrupt the starting of the OpenServo application code and start reprogramming of the flash. It works, but is very clunky. Instead, the OpenServo should immediately start the application code so the servo is responsive immediately from power-up. However, I still need a way of forcing the servo into the bootloader for reprogramming if for some reason the application portion of flash gets corrupted.

In my balancing 'bot I implemented a bootloader that works a little differently. Under normal circumstances, the bootloader will immediately invoke the application code. However, the bootloader will remain active if the PB3/MOSI line is held low at time of power up/reset or if the MCUSR register indicates a reset for a condition other than power-on reset. For the OpenServo, I will probably modify this scenario slightly so the OpenServo can be forced into bootloader if the PB3/MOSI line is held low (it's on the external connector) or if only the watchdog system reset flag is high in the MCUSR.

The other change is to have the bootloader and application code share the same address on the I2C bus. Currently, the bootloader always comes up at the 0x7f address. Instead, I plan to reserve the lower seven bits of the first EEPROM byte as the intended OpenServo I2C address by both the bootloader and OpenServo application code.

That's it for right now. I'll have a lot more to discuss about changes I would like to make to the OpenServo application code over the coming weeks. I think we know a lot more about how the OpenServo might be used in real applications and I think a number of improvements can be made for better and easier operation of the OpenServo.

The other issue I'm considering right now is whether to branch the OpenServo Version 3 software within CVS or simply create an independent tree for the Version 3 software. I'm not a whiz with CVS and I could easily screw things up during the process of creating a branch. Simply creating an independent tree for Version 3 is easier for me. Also, the OpenServo code base isn't very large and moving features between two indpendent trees if fairly easy.

Comments on any of this?

-Mike
Back to top
View user's profile Send private message Send e-mail Visit poster's website Yahoo Messenger
poor-robot



Joined: 09 Mar 2007
Posts: 45
Location: Portland, OR

PostPosted: Wed May 02, 2007 12:35 am    Post subject: Reply with quote

I like CRCs. It worries me to potentially bootload a device with corrupted data.

Could the I2C booloader sequence include CRC'd packets over the I2C lines? Could the bootloader receive a CRC of the full application code as part of the bootload process? You could then CRC the application flash and compare vs. the known CRC (in EEPROM) before booting into it. The application code could probably also benefit from CRC use in commincation.

Checking packet validity could alleviate some headaches caused by unreliable I2C communication. With V3 there is also the new risk of smoking MOSFETs with corrupted code since both top and bottom FETs are under SW control.

CCITT-16 (0x1021) is a decent 16 bit CRC that is commonly used. There are better ones. The C2 polynomial (0x97) is the best 8 bit one I have found. Neither routine takes up much code space.
Back to top
View user's profile Send private message Visit poster's website
mpthompson



Joined: 02 Jan 2006
Posts: 650
Location: San Carlos, CA

PostPosted: Wed May 02, 2007 6:09 am    Post subject: Reply with quote

The inside of a servo is a fairly intense environment for an MCU because of the close proximity of the power FETs and motor. I see how such a CRC check could add some protection by verifying what the flash is indeed intact before attempting to run it. The main tradeoff is code space for the bootloader. Currently it just fits within 1024 bytes, but a CRC verification feature would likely force the bootloader to the next increment at 2048 bytes. Is such a tradeoff worth it?

The OpenServo does currently support an optional checksum feature on all I2C packets. If the computation overhead isn't too drastic it should be fairly easy to substitute the checksum with a more robust CRC based validation. Also, the EEPROM is also protected via checksum which can also benefit from a CRC upgrade. Do you have a lead on C code for the C2 polynomial?

-Mike
Back to top
View user's profile Send private message Send e-mail Visit poster's website Yahoo Messenger
poor-robot



Joined: 09 Mar 2007
Posts: 45
Location: Portland, OR

PostPosted: Wed May 02, 2007 9:18 pm    Post subject: Reply with quote

Code:

#define POLYNOMIAL 0x97
static inline unsigned char get_crc(unsigned char *data, unsigned char bytes)
{
  unsigned char crc = 0xFF;
  unsigned char i;
  while (--bytes > 0) {
  crc ^= data[bytes];
  for (i = 0; i < 8; i++) {
    if (crc & (1<<7))
      crc = (crc << 1) ^ POLYNOMIAL;
    else
      crc = (crc << 1);
    }
  }
  return crc;
}

50 bytes or so. You may not want it static inline.
Back to top
View user's profile Send private message Visit poster's website
poor-robot



Joined: 09 Mar 2007
Posts: 45
Location: Portland, OR

PostPosted: Wed May 02, 2007 9:24 pm    Post subject: Reply with quote

And here's the CCITT-16. Exact same concept, 16 bits, different coding style. I think I pulled this down from some example somewhere. It provides better protection than the 8-bit crc. I only use the 8 bit CRC for low power over-the-air transmission.
Code:

#define CCITT_POLY 0x1021
UINT16 crc_ccitt(unsigned int crc, unsigned char* buffer, int len)
{
  BYTE i;
  while (--len >= 0)
  {
    crc = crc^(unsigned int)*buffer++<<8;
    i = 8;
    do
    {
      if (crc & 0x8000) { crc = crc<<1 ^ CCITT_POLY; }
      else { crc <<= 1; }
    } while(--i);
  }
  return (crc);
}
Back to top
View user's profile Send private message Visit poster's website
mpthompson



Joined: 02 Jan 2006
Posts: 650
Location: San Carlos, CA

PostPosted: Fri May 11, 2007 5:00 am    Post subject: Reply with quote

For those interested, below is how the revamped PWM module is shaping up. The comments in the code use the same symbols as Cliff's schematic so it should be fairly easy to follow.

Code:

/*
    Copyright (c) 2006 Michael P. Thompson <mpthompson@gmail.com>

    Permission is hereby granted, free of charge, to any person
    obtaining a copy of this software and associated documentation
    files (the "Software"), to deal in the Software without
    restriction, including without limitation the rights to use, copy,
    modify, merge, publish, distribute, sublicense, and/or sell copies
    of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be
    included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

    $Id$
*/

#include <inttypes.h>
#include <avr/interrupt.h>
#include <avr/io.h>

#include "openservo.h"
#include "config.h"
#include "pwm.h"
#include "registers.h"

//
// ATmega168
// =========
//
// PWM output to the servo motor utilizes Timer/Counter1 in 8-bit mode. 
// Output to the motor is assigned as follows:
//
//  PB1/OC1A - PWM_A    - PWM pulse output direction A
//  PB2/OC1B - PWM_B    - PWM pulse output direction B
//  PD2      - EN_A     - PWM enable direction A
//  PD3      - EN_B     - PWM enable direction B
//  PD4      - SMPLn_B  - BEMF sample enable B
//  PD7      - SMPLn_A  - BEMF sample enable A
//

// 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) 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.
#define PWM_FREQ_DIVIDER      64

// Defines the top value for timer 1.
#define PWM_TOP_VALUE          ((uint16_t) PWM_FREQ_DIVIDER * 16) - 1;

// Defines the duty cycle for timer 1.
#define PWM_OCRN_VALUE(pwm)    (uint16_t) ((pwm * (((uint32_t) PWM_FREQ_DIVIDER * 16) - 1)) / 255)

// Flags that indicate PWM output in A and B direction.
static uint8_t pwm_a;
static uint8_t pwm_b;

static void pwm_dir_a(uint8_t pwm_duty)
// Send PWM signal for rotation with the indicated pwm ratio (0 - 255).
// This function is meant to be called only by pwm_update.
{
    // Determine the duty cycle value for the timer.
    uint16_t duty_cycle = PWM_OCRN_VALUE(pwm_duty);

    // Disable interrupts.
    cli();

    // Do we need to reconfigure PWM output for direction A?
    if (!pwm_a)
    {
        // Yes. Make sure PWM_A (PB1/OC1A) and PWM_B (PB2/OC1B) are low.
        PORTB &= ~((1<<PB1) | (1<<PB2));

        // Set SMPLn_B (PD4) and SMPLn_A (PD7) to high.
        PORTD |= ((1<<PD4) | (1<<PD7));

        // Set EN_B (PD3) to low.
        PORTD &= ~(1<<PD3);

        // Enable PWM_A (PB1/OC1A) and disable PWM_B (PB2/OC1B) output.
        TCCR1A = (1<<COM1A1) | (0<<COM1A0) |
                 (0<<COM1B1) | (0<<COM1B0) |
                 (0<<WGM11) | (0<<WGM10);

        // Set EN_A (PD2) to high.
        PORTD |= (1<<PD2);

        // Reset the B direction flag.
        pwm_b = 0;
    }

    // Update the A direction flag.  A non-zero value keeps us from
    // recofiguring the PWM output A when it is already configured.
    pwm_a = pwm_duty;

    // Update the PWM duty cycle.
    OCR1A = duty_cycle;
    OCR1B = 0;

    // Restore interrupts.
    sei();

    // Save the pwm A and B duty values.
    registers_write_byte(REG_PWM_DIRA, pwm_a);
    registers_write_byte(REG_PWM_DIRB, pwm_b);
}


static void pwm_dir_b(uint8_t pwm_duty)
// Send PWM signal for rotation with the indicated pwm ratio (0 - 255).
// This function is meant to be called only by pwm_update.
{
    // Determine the duty cycle value for the timer.
    uint16_t duty_cycle = PWM_OCRN_VALUE(pwm_duty);

    // Disable interrupts.
    cli();

    // Do we need to reconfigure PWM output for direction B?
    if (!pwm_b)
    {
        // Yes. Make sure PWM_A (PB1/OC1A) and PWM_B (PB2/OC1B) are low.
        PORTB &= ~((1<<PB1) | (1<<PB2));

        // Set SMPLn_B (PD4) and SMPLn_A (PD7) to high.
        PORTD |= ((1<<PD4) | (1<<PD7));

        // Set EN_A (PD2) to low.
        PORTD &= ~(1<<PD2);

        // Disable PWM_A (PB1/OC1A) and enable PWM_B (PB2/OC1B) output.
        TCCR1A = (0<<COM1A1) | (0<<COM1A0) |
                 (1<<COM1B1) | (0<<COM1B0) |
                 (0<<WGM11) | (0<<WGM10);

        // Set EN_B (PD3) to high.
        PORTD |= (1<<PD3);

        // Reset the A direction flag.
        pwm_a = 0;
    }

    // Update the B direction flag.  A non-zero value keeps us from
    // recofiguring the PWM output B when it is already configured.
    pwm_b = pwm_duty;

    // Update the PWM duty cycle.
    OCR1A = 0;
    OCR1B = duty_cycle;

    // Restore interrupts.
    sei();

    // Save the pwm A and B duty values.
    registers_write_byte(REG_PWM_DIRA, pwm_a);
    registers_write_byte(REG_PWM_DIRB, pwm_b);
}


void pwm_init(void)
// Initialize the PWM module for controlling a DC motor.
{
    // Set EN_A (PD2) and EN_B (PD3) to low.
    PORTD &= ~((1<<PD2) | (1<<PD3));

    // Set SMPLn_B (PD4) and SMPLn_A (PD7) to high.
    PORTD |= ((1<<PD4) | (1<<PD7));

    // Enable PD2, PD3, PD4 and PD7 as outputs.
    DDRD |= ((1<<DDD2) | (1<<DDD3) | (1<<DDD4) | (1<<DDD7));

    // Set PWM_A (PB1/OC1A) and PWM_B (PB2/OC1B) are low.
    PORTB &= ~((1<<PB1) | (1<<PB2));

    // Enable PB1/OC1A and PB2/OC1B as outputs.
    DDRB |= ((1<<DDB1) | (1<<DDB2));

    // Reset the timer1 configuration.
    TCNT1 = 0;
    TCCR1A = 0;
    TCCR1B = 0;
    TCCR1C = 0;
    TIMSK1 = 0;

    // Set timer top value.
    ICR1 = PWM_TOP_VALUE;

    // Set the PWM duty cycle to zero.
    OCR1A = PWM_OCRN_VALUE(0);
    OCR1B = PWM_OCRN_VALUE(0);

    // Configure timer 1 for PWM, Phase and Frequency Correct operation, but leave outputs disabled.
    TCCR1A = (0<<COM1A1) | (0<<COM1A0) |                    // Disable OC1A output.
             (0<<COM1B1) | (0<<COM1B0) |                    // Disable OC1B output.
             (0<<WGM11) | (0<<WGM10);                       // PWM, Phase and Frequency Correct, TOP = ICR1
    TCCR1B = (0<<ICNC1) | (0<<ICES1) |                      // Input on ICP1 disabled.
             (1<<WGM13) | (0<<WGM12) |                      // PWM, Phase and Frequency Correct, TOP = ICR1
             (0<<CS12) | (0<<CS11) | (1<<CS10);             // No prescaling.

    // Update the pwm values.
    registers_write_byte(REG_PWM_DIRA, 0);
    registers_write_byte(REG_PWM_DIRB, 0);
}


void pwm_update(uint16_t position, int16_t pwm)
// Update the PWM signal being sent to the motor.  The PWM value should be
// a signed integer in the range of -255 to -1 for clockwise movement,
// 1 to 255 for counter-clockwise movement or zero to stop all movement.
// This function provides a sanity check against the servo position and
// will prevent the servo from being driven past a minimum and maximum
// position.
{
    uint8_t pwm_width;
    uint16_t min_position;
    uint16_t max_position;

    // Are we reversing the seek sense?
    if (registers_read_byte(REG_REVERSE_SEEK) != 0)
    {
        // Yes. Swap the minimum and maximum position.

        // Get the minimum and maximum seek position.
        min_position = registers_read_word(REG_MAX_SEEK_HI, REG_MAX_SEEK_LO);
        max_position = registers_read_word(REG_MIN_SEEK_HI, REG_MIN_SEEK_LO);

        // Make sure these values are sane 10-bit values.
        if (min_position > 0x3ff) min_position = 0x3ff;
        if (max_position > 0x3ff) max_position = 0x3ff;

        // Adjust the values because of the reverse sense.
        min_position = 0x3ff - min_position;
        max_position = 0x3ff - max_position;
    }
    else
    {
        // No. Use the minimum and maximum position as is.

        // Get the minimum and maximum seek position.
        min_position = registers_read_word(REG_MIN_SEEK_HI, REG_MIN_SEEK_LO);
        max_position = registers_read_word(REG_MAX_SEEK_HI, REG_MAX_SEEK_LO);

        // Make sure these values are sane 10-bit values.
        if (min_position > 0x3ff) min_position = 0x3ff;
        if (max_position > 0x3ff) max_position = 0x3ff;
    }

    // Disable clockwise movements when position is below the minimum position.
    if ((position < min_position) && (pwm < 0)) pwm = 0;

    // Disable counter-clockwise movements when position is above the maximum position.
    if ((position > max_position) && (pwm > 0)) pwm = 0;

    // Determine if PWM is disabled in the registers.
    if (!(registers_read_byte(REG_FLAGS_LO) & (1<<FLAGS_LO_PWM_ENABLED))) pwm = 0;

    // Determine direction of servo movement or stop.
    if (pwm < 0)
    {
        // Less than zero. Turn clockwise.

        // Get the PWM width from the PWM value.
        pwm_width = (uint8_t) -pwm;

        // Turn clockwise.
#if SWAP_PWM_DIRECTION_ENABLED
        pwm_dir_b(pwm_width);
#else
        pwm_dir_a(pwm_width);
#endif
    }
    else if (pwm > 0)
    {
        // More than zero. Turn counter-clockwise.

        // Get the PWM width from the PWM value.
        pwm_width = (uint8_t) pwm;

        // Turn counter-clockwise.
#if SWAP_PWM_DIRECTION_ENABLED
        pwm_dir_a(pwm_width);
#else
        pwm_dir_b(pwm_width);
#endif

    }
    else
    {
        // Stop all PWM activity to the motor.
        pwm_stop();
    }
}


void pwm_stop(void)
// Stop all PWM signals to the motor.
{
    // Disable interrupts.
    cli();

    // Are we moving in the A or B direction?
    if (pwm_a || pwm_b)
    {
        // Set EN_A (PD2) and EN_B (PD3) to low.
        PORTD &= ~((1<<PD2) | (1<<PD3));

        // Set SMPLn_B (PD4) and SMPLn_A (PD7) to high.
        PORTD |= ((1<<PD4) | (1<<PD7));

        // Set PWM_A (PB1/OC1A) and PWM_B (PB2/OC1B) are low.
        PORTB &= ~((1<<PB1) | (1<<PB2));

        // Disable OC1A and OC1B outputs.
        TCCR1A &= ~((1<<COM1A1) | (1<<COM1A0));
        TCCR1A &= ~((1<<COM1B1) | (1<<COM1B0));

        // Reset the A and B direction flags.
        pwm_a = 0;
        pwm_b = 0;
    }

    // Set the PWM duty cycle to zero.
    OCR1A = PWM_OCRN_VALUE(0);
    OCR1B = PWM_OCRN_VALUE(0);

    // Restore interrupts.
    sei();

    // Save the pwm A and B duty values.
    registers_write_byte(REG_PWM_DIRA, pwm_a);
    registers_write_byte(REG_PWM_DIRB, pwm_b);
}



Continued...
Back to top
View user's profile Send private message Send e-mail Visit poster's website Yahoo Messenger
mpthompson



Joined: 02 Jan 2006
Posts: 650
Location: San Carlos, CA

PostPosted: Fri May 11, 2007 5:16 am    Post subject: Reply with quote

The code above does not currently implement disabling PWM output for BEMF yet. That work will be coming...

There are three major aspects to consider regarding this new code.

First, the PWM frequency can now be controlled with the PWM_FREQ_DIVIDER. This will likely be moved to a register so it can be dynamically changed, but I put it as a compilation option for now. By setting PWM_FREQ_DIVIDER divider to various values I can get the motor in my servo to respond better to various PID gain values. This feature alone is a substantial improvement over the 2.1 code which used a fixed frequency value that was not easy to adjust.

Second, in the old code I actually stopped the Timer1 while changing the output values which could cause glitches on the pulse outputs. The timer is now running at all times even as I change the duty cycle values which are double buffered and loaded when the timer reaches its BOTTOM value. This probably doesn't have much of an impact, but is a bit more elegant.

Finally, I switched to using the Phase and Frequency Correct mode of operation for Timer1 to generate PWM pulses to the H-Bridge. I believe this is the best Timer1 output mode to use for generating PWM pulses.

All this code will backport easily to the 2.1 hardware so it will benefit from these changes in how PWM pulses are sent to the H-Bridge.

Any comments are welcome.

-Mike
Back to top
View user's profile Send private message Send e-mail Visit poster's website Yahoo Messenger
Cliff



Joined: 23 Jan 2007
Posts: 150
Location: Saratoga, CA

PostPosted: Sat May 12, 2007 8:04 am    Post subject: Reply with quote

Mike,

I haven't tried to go through your code in detail yet, but on the first pass it looks fine to me. I the one thing I wondered, when reading the code, is if the new braking belongs in this mix. Seems to me that there should be a flag that tells the stop routine whether or not to turn on both enables to brake. If both enables are on and the next move is in the same direction as the last move, the move routine will need to turn the extra enable off before turning on the PWM (on a direction change it looks to me that this would happen anyway). You may want to get fancy with braking later, but for basic brake holding I think that this is all that would be needed.

Cliff
Back to top
View user's profile Send private message
mpthompson



Joined: 02 Jan 2006
Posts: 650
Location: San Carlos, CA

PostPosted: Sun May 13, 2007 10:03 am    Post subject: Reply with quote

Cliff,

I haven't yet considered exactly how to add breaking to the mix. As you describe, basic braking to be applied when the h-bridge is not driving the motor in either direction seems easy enough. Set PWM_A and PWM_B to low and set EN_A and EN_A to high so the motor is braked by current looping around the lower half of the h-bridge.

However, in reality the servo is rarely controlling the h-bridge to deliver zero power to the motors for simple braking to be applied. Sensor noise and any unbalanced loading on the servo will mean that a some power to the h-bridge is always being applied. Implementation a deadband could help, but isn't a complete solution.

Something that would be worth experimenting with is controlling the h-bridge so that a duty cycle consists of power applied to the motor and then motor braking (power/braking cycle) instead of power applied to the motor and then coasting (power/coasting cycle) as is done now. It seems to accomplish a power/breaking cycle in the A direction we would hold EN_A high and then alternately toggle between PWM_A and EN_B. Unfortunately, it doesn't look like the alternate toggling of PWM_A and EN_B can be accomplished purely in the AVR timer hardware -- software intervention with each PWM cycle would be needed which would not be easy.

Any thoughts on this? I believe the way I'm currently driving the H-bridge is sign magnitude control where only one side of the h-bridge is pulsed. It seems that it would be useful to see how to best control the circuit for locked antiphase control where both sides of the bridge are pulsed alternately and the motor is continuously driven. Locked antiphase has more losses due to all four FETs being switched, but it may have some desireable control characteristics for the servo.

-Mike
Back to top
View user's profile Send private message Send e-mail Visit poster's website Yahoo Messenger
Cliff



Joined: 23 Jan 2007
Posts: 150
Location: Saratoga, CA

PostPosted: Sun May 13, 2007 1:02 pm    Post subject: Reply with quote

Mike,

Mike wrote:
However, in reality the servo is rarely controlling the h-bridge to deliver zero power to the motors for simple braking to be applied. Sensor noise and any unbalanced loading on the servo will mean that a some power to the h-bridge is always being applied.


It seems to me that one of the major servo complaints is servo noise, when trying to hold a load in a fixed position. It also seems to me that braking is under appreciated as a way to solve that problem.

Here is a quote, from a description of the vX2 circuit operation that I wrote, but have not finished, that I think applies here:
Quote:
When OS v2 wants to stop the motor, it simply stops sending PWM pulses and the motor coasts to a stop. The problem with this is that the stopped motor doesn't provide any holding torque, so that if the motor has lifted an arm, for instance, the only thing resisting the pull of gravity is the friction of the motor gear train. If the gear train resistance is not enough to keep the arm up, the motor must be powered with a low duty cycle PWM signal to keep it in place; or as in most small servos, once in the desired position, the power is cut off, the gear train resistance is not high enough to hold against gravity, the position changes, causing the servo to power up and seek the proper position, and so on - this oscillation costs power and can be noisy.

One of the interesting things about permanent magnet DC motors is that they make great DC generators. As a generator, its behavior is the mirror image of a motor. To get a motor to turn faster, you apply a higher voltage. When you turn a generator faster, it produces a higher voltage. If need more torque from a motor, you have to give it more current. If you need more current from a generator, it gets harder to turn.

Now then, back to braking the motor. When I'm finished moving a motor and remove power to stop it, I can make the motor stop faster by recognizing that it is also a generator and asking it for current. If I shorted the generator, I would be asking for all the current it can produce, which would make it stop quickly and be very hard to turn. The new bridge circuit can do this with those extra enable signals. Once the PWM signal is removed (PWM_ A and PWM_B switches open), closing both EN_A and EN_B switches will short the motor and provide stopping and holding torque. In fact, with the motor shorted, the servo can hold anything it could lift by running, because it has as much holding torque as the running motor has stall torque. Further, when running the motor the torque is reduced by the resistance of the gear train, when braking it has the holding torque plus the resistance of the gear train. And best of all, braking with holding torque doesn't use any power and it doesn't make any noise.

A small dead band would take care of any potentiometer noise (which I'm expecting to be less in OSvX2 than in OSv2.1) and any unbalanced load is going to be much less than the motor stall torque plus the gear train resistance (which is the holding torque we have with the motor shorted). Using braking, there should be no requirement to drive the servo in order to hold the same position. Shocked [Mike, if you are a skeptic (it's not only allowed, it's encouraged Smile ), take one of your servos, short the motor and try to turn it.]

I can see that when doing a movement curve list, you would want the servo to coast between positions - but when at an end location, IMHO that the brake should be used.

Mixing braking with power cycles is what I was referring to when I said that you could get fancy latter. I expect there is a learning curve to go through to make that useful. Also, with the Atmel resources available, I don't think we are going to be able to get any sort of locked anti-phase mode working well enough to use (I could be wrong, its been known to happen, but in any case this issue should be moved to the later bucket).

Cliff
Back to top
View user's profile Send private message
mpthompson



Joined: 02 Jan 2006
Posts: 650
Location: San Carlos, CA

PostPosted: Sun May 13, 2007 7:24 pm    Post subject: Reply with quote

Quote:
[Mike, if you are a skeptic (it's not only allowed, it's encouraged), take one of your servos, short the motor and try to turn it.]

No problem Cliff. I'll rig up the software to implement braking when power is to be removed from the h-bridge. If I've learned anything on the OpenServo project is to not put a lot of faith in my own intuition on how something should or shouldn't work.

I'll leave control over braking as an optional feature that can be dynamically changed by twiddling a bit in the registers.

Barry was also surprised that I removed a deadband from the PID routines, something I anticipated putting back in, but didn't get around to yet. I'll be looking at doing this as well as some other tweaks to the PID that I've learned from working with my robot which uses PID routines in various places.

-Mike
Back to top
View user's profile Send private message Send e-mail Visit poster's website Yahoo Messenger
mpthompson



Joined: 02 Jan 2006
Posts: 650
Location: San Carlos, CA

PostPosted: Mon May 21, 2007 10:15 pm    Post subject: Reply with quote

I have checked into CVS the initial version of the firmware for Cliff's OpenServo Version 3 hardware design. The new code is in the AVR_OpenServo_V3 directory.

I didn't make nearly as much progress as I hoped over the last week, but all the basics should be working. I wanted to make it available for others to look at and use. No new features have yet been implemented and the register layout is still compatible with the Version 2 firmware.

-Mike
Back to top
View user's profile Send private message Send e-mail Visit poster's website Yahoo Messenger
ginge
Site Admin


Joined: 14 Jan 2006
Posts: 1028
Location: Manchester, UK

PostPosted: Wed Sep 19, 2007 11:16 pm    Post subject: Reply with quote

Hi All,

I am picking up from where Mike left off on the V3 port.

Here is a picture from the scope of the back emf working on the new v3 code.

At the moment it is only reading the back emf into a register value, and not applying this to any of the movement algorithms. This will come next.

With this in mind, we should be able to use the back emf velocity information to make the speed element of the motion more accurate and consistent. I will post up a graph of the position versus the back emf values soon, it certainly looks good to integrate into the pid algo.


Click for bigger.

This is a scope of PWM channel A and PWM channel B. The width of the scope seen is a little over 20ms. Each of the spikes is the motor coasting in the 10ms "heartbeat" window.


Here is a closeup and breakdown of one Back EMF sample



The code has been rejigged so that the old asynchronous adc conversion system is now run synchronously with the system heartbeat (10ms)

Between section 1. and 2. the adc is set to sample position and current.
2. to 3. is a wait period determined as 1 cycle of the back emf which shows as a sine wave as the fields cross the magnets. This gives enough time to sample one half sine as not to skew the back emf adc reading.
At 3. we sample the backemf and then re-enable the PWM.

Barry
_________________
http://www.headfuzz.co.uk/
http://www.robotfuzz.co.uk/
Back to top
View user's profile Send private message Send e-mail Visit poster's website Yahoo Messenger MSN Messenger
ginge
Site Admin


Joined: 14 Jan 2006
Posts: 1028
Location: Manchester, UK

PostPosted: Thu Sep 20, 2007 6:04 pm    Post subject: Reply with quote

Preliminary readings from the adc channel:-



Running at 9v moving between 2 positions



Running at 60% duty cycle for 5v operation

It is still not as smooth as I would have thought for an unloaded servo... I guess I will have to play with the params some more

EDIT:
Just noticed the legends on the axis are the wrong way around. Oops.

Barry
_________________
http://www.headfuzz.co.uk/
http://www.robotfuzz.co.uk/
Back to top
View user's profile Send private message Send e-mail Visit poster's website Yahoo Messenger MSN Messenger
ginge
Site Admin


Joined: 14 Jan 2006
Posts: 1028
Location: Manchester, UK

PostPosted: Thu Sep 20, 2007 8:22 pm    Post subject: Reply with quote

Here is another executing the motion profile test, which moves from 0x0100 to 0x0300 and back in a simple curve.



Again, I am concerned with the data. It doesn't look overly clean. Hopefully we can filter this to get a better result.

Cliff, any ideas how what I can do with firmware to clear this up a little other than digital filtering?

At the moment the capture delay is set to 1.5ms but anything over that seems to make no difference, at least up to 4ms where things start getting even worse.

Barry
_________________
http://www.headfuzz.co.uk/
http://www.robotfuzz.co.uk/
Back to top
View user's profile Send private message Send e-mail Visit poster's website Yahoo Messenger MSN Messenger
Display posts from previous:   
Post new topic   Reply to topic    OpenServo.com Forum Index -> Software All times are GMT
Goto page 1, 2, 3, 4, 5  Next
Page 1 of 5

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group