Designed on implemented by crack embedded team:
Dan Hoke
Todd Hendry
David Friedl
In order to keep the project simple, we made some limiting design decisions.
First, since we were working with limited memory, we set the maximum amount of time that the motor can be “trained” to 15 seconds.
Second, we had planned on having a stepper motor with 200 steps per revolution (1.8 deg per step). This meant that an encoder with 100 steps per revolution would give us an integer-scaling factor of two: easy to implement. We could not get a driver for the 200-step motor so we had to settle for one with 128 steps per revolution (non integer scaling). Since floating-point math is not feasible on the Atmel with our tight deadlines, we decided to ignore this limitation (a real implementation would use a different encoder or motor).
Also, the “training” encoder and “replaying” motor are mechanically decoupled. A real implementation would have these shafts coupled and probably mounted within the same housing.
The XPJ5000 is placed into training mode by pressing and holding the “train” button. The training mode active light will come on. When training, encoder motion in either direction will be recorded. The XPJ5000 can record up to 15 seconds of motion. When you are done training, release the training mode button. If the training mode button is held for 15 seconds, the training mode light will extinguish to indicate that training has stopped.
To replay recorded motion, press and hold the replay button. The XPJ5000 will repeat the recorded motion and then reverse it to return to the start position. You can stop motion at any time by releasing the replay button.
For data compression purposes, the XPJ5000 has a motion gradient of about ¼ second. It is not intended to be a rapid response device. It is intended for methodical, controlled motion. If you need to guarantee motion to a particular point, you will need to hold the encoder there for at least ¼ second.
Our most critical deadline (in training mode) is keeping track of how many ticks have come in from the encoder. We could not have used the internal counter because of the ¼ second time gradient. If the encoder starts to spin in the other direction in the same ¼ second, we need to decrement the tick count to keep the intended motion accurate.
When in replay mode, our deadline is writing the motor pulses, but this is less time critical since a delay only means the motor will pulse slightly later.
In either mode, we have activities that need to occur every ¼ second. When training this is to write an instruction and when replaying, this is to decode the next instruction. Since this only needs to happen 4 times a second, it is handled in the bottom half.
Main()
The main() function drives the program and controls the system’s state. First, main enables the necessary interrupts (timer and external) and sets the interrupt priorities. Then it loops infinitely in a “while” loop. Inside the loop, main reads in the values of the two buttons, “Training” and “Replaying”, and sets the mode to the corresponding state. It also calls GetNextSpeed() if the calculateNext flag is set, which is set when the in “REPLAY” mode.
GetnextSpeed()
GetnextSpeed() is called from main() only when the mode = “REPLAY”. It retrieves the next instruction held in the instruction array and parses it into its corresponding direction and tick count. It then looks up the tick count value in the speed vector to get the timer value needed to reach that tick count. This will be used to preload the timer to control when the interrupt is called.
Once the last programmed byte has been read, the direction of the counter is reversed. This forces the program to run in reverse, returning the motor to its original position.
timer1_isr()
This interrupt controls the output to the stepper motor. It uses timer1 in 16-bit mode. It writes a complete period every eighth interrupt. This is required to allow pulses to be as slow as 4 per second(since the 16-bit timer interrupts at approximately 32 times a second. The actual interrupt frequency is controlled by preloading the 16-bit timer.
extTimer1_isr()
Counts the number of pulses that have been received, in a certain direction, from the encoder. This is only used in Training mode. Since the encoder outputs two signals, 45 degrees out of phase, direction can be sensed by sampling the other input when this interrupt is called.
timer0_isr()
This interrupt is controlled by a 16-bit timer, timer0. Since the timer interrupts 32 times per second, we only enter the control structure every eighth interrupt to lower the frequency to 4 times per second. In “REPLAY” mode we set the speed to be the value previously calculated in GetNextSpeed(). We then set the calculateNext flag, so main() knows to call GetNextSpeed() when system is idle. If the speed is zero, we turn off the Motor interrupt to ensure that no pulses will be written.
In “Training” mode we make the instruction (direction bit + speed), write it to the instruction array, increment the instruction counter, and reset the tick counter.
Speed Vector:
The speed vector is calculated by setting an appropriate timer delay to give us the number of interrupts needed in a quarter second. In order to calculate the 16-bit timer preload value we use the following formula: 2562 – (2562 / (x +1)) where x is the number of pulses in a quarter of a second.
For example:
The instruction sets speed to 15. This means we need 15 pulses sent to the motor in the next quarter second. Plugging in 15 for x into the formula we get the value: 61440. This value is then preloaded into timer1. So an interrupt will be generated every 4096 instructions or 128 times every quarter second, which generates 16 actual motor pulses per quarter second. (The last motor pulse will not be given a chance to fire in this quarter second.)