The next phase of the build was to write the control code for the module. That can be seen in this video here, and this document is the first half of that (the arch and programming):
For those not wanting to scroll through the same thing a second time, here are the main link: https://github.com/Invalid-Entry/ProjectScribble
However for those wanting the breakdown and the pictures from the video, and for those wanting to work through the code in a written perspective, here it it is:
As mentioned in the video, that little 'stick man' is the UML official icon for an 'Actor' where that basically means human interacting person. As a complete aside, I find sometimes engineer / academics terms to be bizarre and purposely designed to confuse lay-people. Like the use of the word Agent in some areas means AI thing, or it could be a person or it could be a computer service of some kind.
Breaking this down into the next diagram shows the beginning of a tool chain. The blue boxes indicate functions that are currently developed and are working well at the time of writing.
The idea behind the tool chain is a sequence of purpose built tools which each work well and do a good function, being tied together into a sequence that produces the desired output. This architecture is distinct from trying to make a single program that does all the individual functions (either by incorporating other things as a library). The key thing here is that it's a chain, i.e. inputs of earlier stages can be saved to disk and re used, optimising the processing workload of further down the chain. It also means that later we can put a control mechanism in place about whether individual steps are needed.
We'll be looking at the tool chain of this later on, and part of it is the optimising of the pathing (both removing minor invisible features, and optimising pen-up movements), which I've started to discuss here.
The Arduino code takes the format of a loop, looking to see if there has been data on the serial, and if there has, process that instruction. It basically is storing four numbers: the current length of the cables (with 0 being the start rest), and the target length of the cables, with those lengths being the 'number of steps away from rest'. When it has a Go command, it then basically sits in a loop sending pulses to the drivers, until it thinks it has reached the number of steps needed to be in the right place.
Open Loop vs Closed Loop
This is an open loop system. The difference is that in an Open Loop system, the controlling unit has no idea where the thing is, just where it should be. The difference is that a closed loop system has some kind of feedback mechanism which allows the controller to detect where it is - i.e. being able to handle things such as pesky human interaction (moving it), slippage of the controls.
Closed loop systems are better™. They can't be told to do things they' shouldn't do, but they're able to self-correct from errors. Unfortunately they're generally more expensive to build as you need to add sensors and then processing of that data. Perhaps in the future we will make this more closed loop... But for the moment, it requires the gondola to be placed in an exact known place, and the R (or it started up) command sent.
The only interesting block of code
In the Arduino code, the only really interesting bit is the serialisation and deserialisation of the target. Originally I wanted to make the entire thing human-friendly, by having someone be able to type into the Serial (either using Arduinos serial monitor, or screen attached to the serial or hyperterm or something similar) something like
A2405 and the machine would know what I meant. Unfortunately this is actually annoyingly difficult.
Firstly, this is something called Binary Coded Decimal (BCD), where each byte is a number between 0 and 9 in ascii, and then theres an unknown number of numbers. So because I'm in prototype mode, I decided not to - instead this is a binary interface which is impossible to type in (possibly copy-paste though?)
// now read 4 Bytes temp_value = Serial.read(); temp_value = temp_value << 8; temp_value += Serial.read(); temp_value = temp_value << 8; temp_value += Serial.read(); temp_value = temp_value << 8; temp_value += Serial.read();
Could this have been optimised as a loop? Probably. Its doing a trick which is how the number is stored. Each byte is read in, and then shifted in the target value along, meaning when the next byte is added (think of the
+ as an OR operation), the previous bytes has already been shifted along. Because shift operations are efficient, this means this is easier to write in code. Other ways of doing (such as having a byte buffer and writing the bytes to an increasing offset is possibly more efficient but given the speed of serial compared the speed of this code, probably not worth it). However, why not do the BCD....
Just out of interest, to do the BCD option, the code would have to do one of three things:
- The command would be fixed length, i.e.
A0002450so that the number of bytes is known in advance
- Use an end marker such as
;so the command would be
A2450;(also new line or space could be used as the marker?
- Use a length argument of known length, i.e. something like
A42450where the 4 indicates there are 4 bytes following.
All three options have their pros and cons. The second one is probably the best for humans, especially if it could be coupled with space/newline being the end markers.
So the challenge is with decimal numbers (assuming we don't like the first option) is an unknown number of bytes. At that point it would look something like: (pseudo code)
number = 0 while end_not_found temp = serial.read(); if temp=';': // or other end bytes end_not_found = False else: number = number * 10 bcd_decode = number - 0x30; // convert the offset of 0 == 0x30 1=0x31 etc number += bcd_decode
And then ignoring all the edge cases here (like what if the byte is not in the range 0x30-0x31, what if there is no end byte and it overflows etc. Theres a lot more that could go wrong here, as opposed to the process above which reads 4 bytes into a 4 byte number and :. can never overflow or do weird things (even if its harder to type a number in).
Remembering this project is for fun and not for making a professional system, doing it the simple way, outside of a loop, is easy to debug and see what it's doing.
Python Code - Interface
The python code at this point becomes a little less interesting, possibly even mundane. It's probable that the next version of this will see me move all these little functions into a class (and :. remove the serial argument, and probably the debug output argument into class parameters). But the idea is that each of the functions does one thing, i.e. maps the protocol commands to a nice easy to understand function. Again the only really interesting thing is the serialisation of the number, which Python, being a great Higher Level Language, (and especially python 3) requires a bit of poking to decode.
target_bytes = location.to_bytes(4, byteorder='big') ser.write(target_bytes)
Oh. Ok maybe it doesn't. The other thing to note is a lot of little
sleep() calls after serial, just to make sure theres time for the hardware to send, the Arduino to process (as its on a loop), and start to reply. This (imo) is a good thing. The 'computer' will run much faster then the Arduino.
This basically completes our lowest level of interaction - being able to instruct the machine to go to a particular set of lengths, and block until it gets there.