I'm trying to revive an old Puma 200 robot arm:
So far, I've hooked up 6 off-the-shelf motor controllers that each read one of the joints' encoders, and do some simple PID to drive the motors.
To talk to the 6 controllers I've had to use 6 USB-serial adapters, which has been a pain in the arse to wrangle with as they show up on my Mac with different /dev/cu.usb-serialxxxxx names every time the machine's rebooted, and if I have to abort my program without the ports being closed properly, it takes a reboot before the Mac can talk to them again... but that's another story.
I want to change the way all this works. Ideally I'd like to handle the PID stuff myself, so I can fiddle with it - maybe implement the control loop as PV instead of PD, etc.
Wondering about the feasibility of moving all the PID stuff over to my host computer (the Mac), using a Teensy to handle the communications. So the Teensy's jobs would be:
- read and keep track of the 6 encoders
- send that data to the Mac as frequently as possible
- receive torque commands from the Mac
- use torque commands to generate 6 PWM signals to feed into the motor drivers (which would now be acting like pure amplifiers).
I've had a play with a Teensy and a tiny servo motor just to see if I could get a single channel working. It tracks the encoder, updates the Mac using USB-HID-RAW, and receives new PWM commands:
Benefits:
- I can work out which way gravity is, so I can build that into my control loop
- I can measure and deal with static friction relatively easily
- Mostly, it means I can keep all my difficult coding in one place (the Mac) rather than having to track down bugs in two separate architectures
My single servo test seems to work OK, but it raises some questions:
1. First, does this seem like a crazy idea? I know typically you'd have PID implemented on the microcontroller, rather than on the host computer (which ain't really designed for realtime stuff) - but the Mac is so damn fast I'm hoping it'll succeed just through brute force
2. Can I expect a reasonably regular (ie regular enough for PID) update rate? I was overjoyed to see around 1500 updates/second appearing on the Mac, but then noticed a lot of jitter; 2 packets coming in at once, that sort of thing. I can mitigate it by timestamping the packets (well, timestamping the most recent encoder change)
3. The longest period between updates over USB has been (in my tests) 2ms - so does a 500kHz PID loop frequency sound good enough for what I'm trying to do?
and lastly:
4. My robot arm's encoders are pretty high-res (2500ppr = 10,000 counts/rev); at top speed I'd expect to see something like 400,000 counts/sec coming in. Do you think the Teensy could handle 6 channels of this?
Rather than using interrupts, I usually check quad encoders in the main program loop using this funky bit of code I found:
I'd be needing to run this code 6 times in each cycle, with at least 400,000 cycles per second... but I don't know whether the USB comms will screw this up. (I don't think they're blocking, but I'll need to try and see)
Does all this sound crazy? Comments? Thoughts?
So far, I've hooked up 6 off-the-shelf motor controllers that each read one of the joints' encoders, and do some simple PID to drive the motors.
To talk to the 6 controllers I've had to use 6 USB-serial adapters, which has been a pain in the arse to wrangle with as they show up on my Mac with different /dev/cu.usb-serialxxxxx names every time the machine's rebooted, and if I have to abort my program without the ports being closed properly, it takes a reboot before the Mac can talk to them again... but that's another story.
I want to change the way all this works. Ideally I'd like to handle the PID stuff myself, so I can fiddle with it - maybe implement the control loop as PV instead of PD, etc.
Wondering about the feasibility of moving all the PID stuff over to my host computer (the Mac), using a Teensy to handle the communications. So the Teensy's jobs would be:
- read and keep track of the 6 encoders
- send that data to the Mac as frequently as possible
- receive torque commands from the Mac
- use torque commands to generate 6 PWM signals to feed into the motor drivers (which would now be acting like pure amplifiers).
I've had a play with a Teensy and a tiny servo motor just to see if I could get a single channel working. It tracks the encoder, updates the Mac using USB-HID-RAW, and receives new PWM commands:
Benefits:
- I can work out which way gravity is, so I can build that into my control loop
- I can measure and deal with static friction relatively easily
- Mostly, it means I can keep all my difficult coding in one place (the Mac) rather than having to track down bugs in two separate architectures
My single servo test seems to work OK, but it raises some questions:
1. First, does this seem like a crazy idea? I know typically you'd have PID implemented on the microcontroller, rather than on the host computer (which ain't really designed for realtime stuff) - but the Mac is so damn fast I'm hoping it'll succeed just through brute force
2. Can I expect a reasonably regular (ie regular enough for PID) update rate? I was overjoyed to see around 1500 updates/second appearing on the Mac, but then noticed a lot of jitter; 2 packets coming in at once, that sort of thing. I can mitigate it by timestamping the packets (well, timestamping the most recent encoder change)
3. The longest period between updates over USB has been (in my tests) 2ms - so does a 500kHz PID loop frequency sound good enough for what I'm trying to do?
and lastly:
4. My robot arm's encoders are pretty high-res (2500ppr = 10,000 counts/rev); at top speed I'd expect to see something like 400,000 counts/sec coming in. Do you think the Teensy could handle 6 channels of this?
Rather than using interrupts, I usually check quad encoders in the main program loop using this funky bit of code I found:
Code:
long count=0;
void loop() {
...
count += readEncoder();
...
if (timeSinceLastUSBupdateInMicros > 1000) { sendOutPositionDataByUSB(); }
...
}
int8_t readEncoder() {
static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0 };
static uint8_t old_AB = 0;
old_AB <<= 2; //remember previous state
byte CURR_STATE = (digitalRead(0) << 1) | (digitalRead(1));
old_AB |= CURR_STATE; //add current state
return ( enc_states[( old_AB & 0x0f )]);
}
I'd be needing to run this code 6 times in each cycle, with at least 400,000 cycles per second... but I don't know whether the USB comms will screw this up. (I don't think they're blocking, but I'll need to try and see)
Does all this sound crazy? Comments? Thoughts?