18 days ago •
Moti Firmware API Documentation The Super Brief Version
Here's synopses of the API methods provided for using Moti in the Arduino environment. Once I actually finish coding the methods I'll post more complete documentation...but this gives you the idea.
- initializes a moti with stored settings, and uses the provided Serial port for communicating over network.
- reset to default settings.
- force motor update critical processes including checking for network messages, reading encoder, and changing motion as necessary.
- shut down the motor.
- Keep on going at the given speed
rotateTo(int speed, int angle)
- Goto a specific angle at the given speed
rotateFor(int speed, int degrees)
- Turn for the given number degrees relative to the current position.
- Stop moving and hold position.
- Cut power to motor. Will rotate under external load.
- Set the speed...affects any motion in progress.
- Set the stall torque of the motor.
angleLimitsWrite(int cwLimit, int ccwLimit)
- Set the angle limits. Limits motion between the specified limits.
- reads the encoder, and returns angle as int.
- calculates and returns the rotations per minute as int.
- reads the current sensor, and returns current measurement as int.
- reads the temperature sensor, and returns temperature as int.
- reads the voltage sensor, and returns voltage as int.
- reads ADC value of the specified A2D pin, and returns the raw value as int.
- reads the binary state of specified pin, and returns the state as int.
analogWrite(int pin, int value)
- writes a PWM value to a PWM pin.
digitalWrite(int pin, int value)
- writes a binary value to a pin.
registersRead(int register, byte data, int length)
- reads a block of moti registers into the provided array.
registersRead(int register, int data)
- reads two consecutive 8-bit registers, and returns them as a single int.
- reads and returns a single register as a byte
registersWrite(int register, byte data, int length)
- writes a byte array to a block of moti registers.
registersWrite(int register, int data)
- writes each half of an int to two consecutive moti registers.
registersWrite(int register, byte data)
- writes a byte to a moti register.
- reads and returns an 8 char array used to store a user-designated label for the moti.
- writes 8 chars of the given array to a block of moti registers. Meant to store a user-designated label for the moti.
- query all available motis on the network. They will each reply with their unique ID.
- check for the specified motor on the network.
- checks the network port for new message and returns the length of the message as an int, or 0 if no message.
- checks the network port for new message and saves it to provided array.
messageParse(byte data, int length)
- decipher the message, and return a struct containing the ID of the Moti, the command, the parameters, the length of the message, and other parameters if available.
- packetize and send a message over the network containing the IDs of both sender and receiver, the command, the parameters, the length of the message, and other parameters if available.
N.B. int here means uint16_t, and byte means uint8_t.
N.B. Methods can be overloaded by providing an ID (int) as the last parameter, or a name (char), in which case this Moti will trigger the operation to occur on the designated Moti in the network if available. The only exceptions are begin(), enumerate() and ping(). Overloaded methods are not shown here for brevity.
19 days ago •
I've been mulling over how to make Moti's programming interface flexible, like Arduino, while regularly running a number of required processes, including reading the position from the encoder, checking for network messages, and affecting the motor as necessary.
Option 1: Update Moti in the main loop.
Up until now, I've been using an update() method that executes all the required processes. This is called from the main loop at regular intervals (look at the Arduino screenshot here). It works fine if you are using the default firmware, as you probably are if controlling from another device (eg. mobile app, computer), but if you want to modify the firmware, say to create an autonomous robot then the scheme's shortcomings start to show. In particular, it forces the user to deal with scheduling the updates, which is a hassle. The problem is that if the motor is rotating to a position and it is too long between updates (and thereby reading encoder steps), we risk rotating right past the destination, a potentially catastrophic failure.
How frequent does Moti need to update? It depends! The period between updates can be no longer than the time it takes to move one encoder step. Our current hardware peaks at about 80rpm, which means it takes ~1333 ms per 360º. Since the encoder ADC is 10-bit, that's 1333/1024 = ~1.3 ms per step. That's often! There's not much room for other processes to happen in the main loop when we have to update so frequently…certainly no millisecond delays. Keep in mind that this is a worst case scenario. You'd have to be using specific methods (rotateTo() or rotateFor()) at the fastest speed to require updating so often, which suggests that the frequency could be reduced when the motor is further from it's destination, and/or is moving at a slower speed.
Returning to the problem at hand, in this scenario, the user is forced to work out these timing issues and ensure that update() is called as often as necessary. But do they want to do that? I'm betting No. Imagine trying to write a program while making sure that a process executes every few milliseconds, every other line might be update()! There may be scenarios where a user needs to manually update, but as a default I think it would be better to bury it in the background.
Option 2: Update Moti in the background.
You may have already figured out that we can use interrupts to schedule updates. A timer interrupt is set to go off in time to execute the update() before the deadline. This is great, because if the update() runs in the background, a user can program the main loop normally.
But this approach has it's own issues. As a rule ISRs are supposed to be kept as short as possible, but how long is too long? Moti's update() currently takes approximately 0.5ms. Is that too long for a single ISR? I think so, as it may interfere with time sensitive operations in the main loop. A common strategy to avoid long interrupts is to set flags in the ISR and then deal with them back in the main loop. However, this puts us in the same predicament as scenario 1, where the user is forced to deal with update() in a timely manner.
Better, would be to reduce update() to a minimum so that it can be handled in the ISR. Right away, I think we can cut the routine in half through basic optimization. Also, a major chunk of that time is consumed by reading the encoder. Instead of waiting, we can monitor the conversion with another interrupt. Now we have:
1) run main loop operations until timer interrupts
Read more »
2) timer ISR: start ADC encoder reading with interrupt
3) run main loop operations until ADC interrupts
4) ADC ISR: check for new messages from network, change motor state if necessary
By peeling off the analog conversion from other processes, I think we can shave another 0.125 ms off the ISR, leaving us with an ISR of 0.125ms. I think that's worth a try, and I'll follow up in the future after giving it a go. I may also partition the network checks off into their own timer-based...