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
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 interrupt as well, with a different priority than the motor handling routine for more efficiency. There are some remaining concerns with this approach with respect to potential conflicts between main loop code and the ISRs (e.g. overlapping analogReads), but I think I can handle this in the background, either in the ISR, or with a custom Moti.analogRead() method. So it looks like I'm moving forward with handling updates in the background with interrupts.
As usual, I'd love to hear any feedback you have.