Part 77: Assignment #AC5: Harvest Time!
Harvest Time!Avalon City's reaching its target size as I write this - which is a fancy way of saying that plate placement is mostly finished, and the industrial area buildings are close to being fully built. The streets seem busier and busier each day.
But you want to know what the real good news is? Avalon Fishcakes are gradually becoming a thing of the past!
Well. They will, at least, once the Ocean's Bounty project can get its kelp harvest in - and they need my help again.
I still can't believe the kelp beds are ready already. The time here has passed almost like a blur, looking back. Anyway... this would be a pretty straightforward design, except that one annoying requirement.
"If at least one unharvested location remains, move towards the oldest location... activating harvest when over any unharvested location."
That 'any' means that each time the robot moves to a new map location, we have to check the list of locations to harvest, to see if where we've moved to is one of them. It also means that the robot needs to know where it is after each move; we can't just say, "OK, hold these motors on for this long" and not keep track of the resulting position.
With that in mind, here's my plan: Divide the robot into three 'parts'.
The first part handles the memory chip that'll store the locations to harvest. If it hears that the robot's over a harvestable location, it'll remove that stored location and activate the 'harvest' output.
The second part listens to the radio and sends new locations to the memory chip.
The third part handles the motors, tracks the position, and will ask the memory handler if the current location is a harvestable location.
I'll start with the easy-looking part: the motors, and maybe the radio chip. Here I go...
[ ~ ]
Here's my first stab at the motor control units' design. The MC6000 is the main MC let's call it the 'motor management chip' to distinguish it. When cued on its x1 (not yet connected to anything), it takes a two-digit coordinate of the location to move to. Then it checks the small MCs attached to motor-x and motor-y to find out where the harvester actually is.
The reason this works is because the motor-x and motor-y MCs keep track of their current X or Y positions in their acc - and update their output lines (p0) with this information so that the motor management MC can read it too. When pinged by the motor management MC, the motor-x and motor-y MCs will compare where the motor management MC wants the harvester to be with where they actually are, and pulse their motor outputs accordingly.
So back to the motor management MC. Each time unit while it's active, the MC sends the destination location to the two small motor MCs, by way of the small MC in the top left. (The top left MC splits the location's coordinate, which is a two-digit value, into two single-digit values, then passes each digit on to the correct motor MC. We'll call it the 'splitter' from hereon.)
The management MC then gets back an updated location from the small motor MCs - it'll check that against the destination to find out if the harvester has arrived at the target position and can go to sleep.
Each time the management MC gets a location update, it'll also copy the location out on an output line I haven't decided yet. This is so that the harvester can check if it happens to be over one of the harvest locations, and can turn on harvest. As for the reason I haven't decided on an output line...
There's a design flaw with this prototype, and it is this: there's no way to turn on the harvest output! harvest is a simple I/O line, and there's no way to run a wire from a simple I/O pin to the output with the various chips placed where they are. I'm going to have to rewire for this to work.
So: That's my next task. I'll try and add more to the design as I do it, too.
[ ~ ~ ]
Behold, the rewired version! I've made a few minor improvements to this, but I'm starting to get worried about the design for reasons I'll get into in a moment.
First off, there's the rewiring itself, of course. The splitter MC is now above the motor-x and motor-y MCs, and the management MC has been moved up a space to run a wire from the harvest output along the bottom. This move also gives the management MC space to run wires on its two previously-unused XBus pins, which are used as an input line (to take note of new destination locations) and an output line (to notify where the harvester is at any point in time).
Second: most of the MCs' code is the same, except for different pin numbers to account for the rewiring. In particular, the placeholder pin numbers in the previous version of the management MC have been replaced thanks to the extra free pin - now, x0 is the management MC's input line, and x1 its output line.
Finally, there's a new MC in the design! This one's a very basic input handler for the radio, near the top left. All it does is, if it hears a new harvesting location, it'll turn that pair of values into a single two-digit value (a coordinate) and store it in the attached memory chip. I plan for it to notify some other part of the design eventually, but that part is yet to be written.
The problem I was talking about up above is that I've used over half the free space on the board already - and so far, all we have is the part of the design that controls the motors themselves! The harvesting logic, which chooses where to go next and updates the memory chip as locations are harvested, would all have to fit into the remaining free space you see there. I don't think it's possible with the design as it is - which means I have to consolidate something.
Back to the drawing board, as they say.
[ ~ ~ ~ ]
One redesign later, we're back to having the motor control section take up less than half of the board. This design has some big alterations from the last one - the only MC that hasn't changed at all is the one connected to the radio. Apart from that:
The motor-x and motor-y MCs no longer need to be pinged every time unit. Instead, they keep track of their destination in their dat, looping until they've reached the set location. They still output where the harvester is regularly over their p0 lines.
Because the motor-x and motor-y MCs don't need to be notified every time unit, the motor management MC only lets the motor-x and motor-y MCs know where to go once. This is more efficient - every time unit, the management MC still checks where the harvester is and passes that information on to the rest of the device, but the update cuts down on a lot of inter-MC communication.
Now that this is satisfactory, I need to make the core logic that will drive the harvester around. I have the first glimmer of it in the radio-handling MC - it'll write the new location to harvest to the memory chip. That leaves the parts that feed new locations to the motor management MC, erase harvested locations, and otherwise keep track of what's left for the harvester to do.
I've spent a day on this project already, but I have the feeling the dive in has just begun...
[ ~ ~ ~ ~ ]
The core logic isn't at all done, but it's getting there. A quick summary of my changes so far is below.
I've got the first stabs at control MCs done. The new small MC attached to the radio-input MC is the counter; when it receives a negative value from the radio MC, it'll add 1 to its acc, which tracks the number of locations left to harvest. If there weren't previously any locations to harvest, and now there are, it'll notify the rest of the control logic MCs (specifically the new 'finder' MC - see below) to start moving to a new location.
I'm planning to have the counter MC also be connected to another MC - maybe the motor management MC - on its input line as well. The difference is, the other MC will be there to notify the counter when a harvesting request is complete, by sending a positive value instead! In that case, the counter subtracts one from its acc instead - and will call for a 'move to new location' if there's any locations left to harvest.
There's two other new, large MC6000s in the design, both of which aren't hooked up yet and don't have their pin assignments finalized yet. I've put labels next to each one, for my benefit as much as yours.
The farther left of the new MC6000s is the finder. This MC will probably be what the counter MC notifies - when it's triggered, it looks through the memory chip until it finds any value. This will be the oldest location to harvest, because the finder MC always starts one memory cell after the newest location written, and the chip wraps around. The finder MC will pass the location it finds on to the motor management MC, and then start listening for a response that indicates the location has been correctly harvested. I'm not quite sure what it should do after that. Maybe nothing.
The other new MC6000, only slightly left of center, is the tester. This MC is the one that receives the current location of the harvester from the motor management MC. The tester MC is also connected to the memory chip, and once it gets a location, it'll check to see if that location's in the memory chip. If it finds it, it'll erase that location and pulse harvest for a time unit.
The next step in this is to wire everything up and see if it works. There'll probably be a bug or two to be polished up in the design.
I'm thinking this project is going to take another day at most at this point - but only if I'm lucky...
[ ~ ~ ~ ~ ~ ]
I have not been lucky. There are times when a design almost works - things almost line up - but they're not quite there, and so you have to fix it or find a workaround. And that's before fixing the bugs in this design, which I am sure are there, but I haven't caught yet!!
(Click the wiring diagram for a zoomed-in version.)
In this particular case, the 'almost works' is the wiring. Some of my changes since the last design required me to split the 'finder' logic across two MCs, and now I can't figure out how to wire it up and make everything fit!
It might be simpler to go into detail about how everything's changed, and where it fits into this mess.
The radio MC hasn't changed since the last version. The counter MC has, and, well... it's indirectly the cause of all this trouble.
Basically, in my last design, I'd planned to have the counter take positive values as input from a not-yet-chosen other MC. These positive values would subtract 1 from the number of locations left to harvest. The problem is, though, there's actually two types of "subtract one from the counter", which needs to be handled differently:
Target location harvested: Subtract one from the counter. If there are any other locations to be harvested (counter > 0), call up the next location to harvest.
Another location harvested on the way to the target location: Subtract one from the counter. Do not call up a new location, we're still moving to the last one.
This means that both the tester (now renamed the 'eraser') and the finder MCs have to have a way to notify the counter, but with different values. Here's how it's set up now:
New location to harvest: Negative value sent from radio MC. Counter will call up the location if there are no other pending locations (if there were, the harvester would already be running).
Done harvesting any location: Zero value sent from tester/eraser MC. Zero value doesn't trigger any checks to call up a new location.
Done harvesting target location: Positive value sent from finder MC. This will always arrive before the zero value from the eraser - so, the counter MC runs code that will wait for the eraser MC's 'zero' input and throw it out. Then, it'll call up a new location if any are left.
So far, so good - but when we last saw the finder chip, it only had a few lines of free space left. Not enough to actually wait for a response from the motor management MC that matches its acc, and then send a pulse to the counter MC.
So: I split the finder into two. The first part (the smaller MC) loops through the RAM chip (starting where indicated by the counter MC), and sends every value it finds to the second part. It stops when it runs out of array or the value it sends isn't a '0' (the second part of the finder copies its input back so the first part can see it).
The second part of the finder waits for the first non-'0' (empty) value from the finder ("xIN"), then sends it to the motor control MC ("xIN2"). It compares the locations broadcast by the motor control MC, passing them on to the eraser MC ("xOUT"). It'll also send a value to send on to the counter a -1 to subtract one from the counter (this really should be a +1, but it's a work in progress!), or a 0 to leave it where it is.
This code isn't quite set up to work with the counter yet as it's shown above: right now, I'm only sending a single value to the eraser, and nothing from the finder second part itself. I'm thinking it might be best if only the eraser and the radio MC (but not the finder) send to the counter, which'd let me revert to the simpler counter code from the last version.
The eraser MC (previously called the 'tester', as a reminder) has mostly the same code: given a value, it looks through the memory chip and removes it if it's found - which means the current location was harvested. However, there's also an extra line in the new version, which pings the counter with value (0 or -1, which is actually supposed to be a +1) sent by the finder. There's also a contingency clause that'll throw the finder's value out if the current location isn't in the RAM chip.
Importantly, the eraser is still the MC that pulses the 'harvest' output, which is going to factor into the wiring snafu later.
The motor management MC isn't that different, but I've been experimenting with a more efficient design for the motor MCs themselves; one where they loop and keep outputting values until reaching the target location, without having to be cued by the motor management MC every time unit.
It's... sort of going well. The tricky bit is that the motor-x and motor-y outputs might be on for different periods of time, but both MCs will need to output their current position each time unit until the target location is reached.
The version I'm experimenting with above tries to work around that by having the motor-x MC output as simple I/O, while having motor-y output through XBus. If motor-y runs for longer than motor-x, the lower MC will keep outputting and there's no problem. If motor-x runs for longer instead, the motor-y MC will see that motor-x's output line is not 50 (neutral), and will output as long as motor-x is busy.
It's efficient, but it makes the wiring a bit more complicated. Which reminds me...
So at the beginning of this section, I mentioned that my problem was wiring up the second 'finder' MC. Because the eraser MC is on the far left, a simple output line has to go from its lower left or upper right corner (where the simple I/O pins are) and join up with the 'harvest' output on the far right. Unfortunately, the second 'finder' MC is directly between Point A and Point B, and it has three (possibly four) separate lines - one going to the eraser, one to the motor MC, and one from the first 'finder' MC. (Possibly also one to the counter. This is TBD.) I somehow have to route the eraser's simple I/O wire around/through this, along with the eraser's connection to the counter.
I'm not sure it's possible with things in their current positions. There's nothing for it: I'm gonna have to shuffle components around until something works. Rewiring like this will take a while - maybe even a few days!
"To Be Continued", as they say.
[ ~ ~ ~ ~ ~ ~ ]
I have now been working on this project for over a week... and I finally, finally have a working prototype. Even if it looks like the underside of a fish cake!
I've annotated the wiring diagram like the last version, so you can see just how everything moved around.
(Again, click the wiring diagram for a zoomed-in version.)
Aside from moving almost every part on the board, this version isn't too different from the last prototype - just cleaned up, by a lot. I'll list what is different from the last version below.
There was a bug in my last version with regards to the counter that I hadn't fixed at the time of screenshot. The way the conditional flags shake out, the counter is supposed to take a negative input to indicate a new location being added, and a zero or positive input to indicate an old location being removed. Some of the MCs that feed the counter (the radio MC, the tester/eraser, and the finder part 2) were sending negative values when they should have been sending positive ones, or the other way around. This is fixed.
I also finalized my implementation where the finder and eraser MCs both send to the counter when a target location is being removed, including proper timing (the 'slp 1' in the middle).
The first part of the finder is mostly the same, apart from a bug fix to stop it from choosing the newest location instead of the oldest (the fix is the 'mov x2 null' to move the memory address pointer off of the cell with the most recent input).
The second part of the finder is only different in that it's been properly wired up! The 'mov 0 to the eraser MC' line has been removed, as the eraser always emits a '0' on its own.
The eraser's code hasn't been changed since the last prototype - only completed. The 'mov xIN null' line was removed, since the finder doesn't send any extra input to the eraser that might need to be thrown out.
My experiments with the self-sufficient motor MCs had to be abandoned when I rewired - in order for everything to fit, I had to go back to the earlier, smaller motor MCs from the first or second prototype shown here. Well... almost! Since the motor MCs only report their position on time units they're cued, I made the updated versions return their position over XBus instead of simple I/O - simplifying the wiring drastically.
It's worth noting that the motor management MC has an unused 'loop' label as a byproduct of the experiments, but practically speaking, it's the same code as before. Send target position, get back actual position, report the results to the finder (which passes them on to the eraser).
This version is ¥32, and uses 6148 average power per run. Phew. Heady statistics! But I'm not satisfied with the design.
For starters, splitting the 'finder' chip into two seems kind of wasteful - a lot of extra lines and board space lost to properly notify the counter. Maybe there's a way to reunite the two 'finder parts'? It'd simplify the wiring, at least.
I'll play around with different versions - though I might have to start from older experiments - before I send this version off to the Ocean's Bounty team. Though it seems impossible, there could be some better-optimized version out there...
I think. I'm not sure.
[ ~ ~ ~ ~ ~ ~ ~ ]
It took days. Again! And I kinda rebuilt the design from the ground up. But here it is: the much cleaner and less packed-in version.
(This'll be the last annotated wiring diagram. But you can still click on it to see a bigger view.)
The reason I had trouble with splitting the finder into two in the first place is because the counter would need to be notified in different ways when removing a target location, versus removing a location that the harvester just ran into on the way. But then, I got to thinking: "What happens if the harvester doesn't subtract from the counter for on-the-way locations?"
The answer is: it only presents a problem when we harvest all our target locations and the list of places to harvest is empty - if that occurs, the harvester would try to harvest the next location and get stuck reading the empty RAM chip.
Because of this, the fix is simple: if the finder completes its loop and notices that the memory chip is empty, it can just notify the counter as if a target location was successfully harvested. The counter MC will subtract 1 from its count and go about its day. Problem solved!
That's not the only change this design has, though. Here's a rundown of everything that's different:
The counter MC, now that it no longer needs to take input from the eraser MC, can be moved off to the far left. Its script is back to the simpler version from earlier prototypes, too - mostly. It now sends to the finder MC on the same line as it gets input from the radio MC, further cleaning up the wiring.
The finder MC is back in one piece! Previously, the first part of the finder received input from the counter on one XBus pin, and the second part of the finder sent pings back to the counter on a different one. These have been combined into one pin on the reunited finder - it only needed a little timing-based magic.
When the finder's cued now, it waits a tick with nop (this is so that, if the pulse was the radio MC sending something to the counter, the counter will always respond first), and then looks for the oldest value in the RAM chip. It then sends that value to the motor management MC, waits for a reply (which it gets once the target location is harvested), and tells the counter that the location is done.
The eraser MC is the same as it ever was - except, now it doesn't have to notify anyone when it's done erasing.
The motor management MC is, once again, a bit different than before. The biggest change is in who's responsible for preparing the location value that tells the motor MCs where to go. The actual motor MCs now separate their half of the target location from the two-digit target coordinate, instead of the motor management MC doing it. Because of this, they've been upgraded to MC6000s - and the motor management MC has space to notify the finder when the harvester is done moving.
There aren't really any other changes of note. In particular, if we ignore the 'coordinate separation' part, the new motor MCs are about the same as the oldest versions of the motor MCs - they compare their current and target position, update their acc with the new location they're moving to and report that, and actually pulse the motor outputs to make it all work.
This new design is a lot cleaner, but better yet - it's more power-efficient. The statistics this time are ¥33, and 5055 average power per run. I'm pretty sure this is the design I'm sending off to the team - assuming I don't find further improvements in it! I'll just take one last look before I send it off.
[ ~ ~ ~ ~ ~ ~ ~ ~ ]
Found one improvement, actually!
I had originally moved the job of splitting the coordinate from the motor management MC to the motor-x and motor-y MCs because I thought there wasn't enough room on the motor management MC to do it. It turns out: There is. There just is. I had to use a trick I've never done before to get everything to fit, but I was able to make the motor MCs tiny again.
Here's how it works:
The motor management MC needs to split the target coordinate (in its dat) into a tens digit (the X coordinate) and a ones digit (the Y coordinate) and pass those along to the correct motor MCs. (The motor MCs take the input value and directly compare it using tcp.)
If I did it the normal way, the script would look like this:
mov dat acc - copy the value to acc so we can work on it.
dgt 1 - get the tens digit.
mov acc x3 - send it to the motor-x MC.
mov dat acc - we overwrote the value when we got the tens digit, so copy it to acc again.
dgt 0 - get the ones digit.
mov acc x2 - send it to the motor-y MC.
That's one line too many to fit in the motor management MC, with everything else it's got jammed into it. But here's the trick: the dst instruction lets you set one digit of acc to a specified value... but if you specify a multi-digit value to set the digit to, the instruction will chop off all but the last digit of that value.
So, if you do dst 0 269, the target digit will be set to '9' - the rest of the value is discarded. This means I can use this to get the ones digit of dat and move it to acc at the same time!
Here's the updated script:
mov dat acc - copy the value to acc so we can work on it.
dgt 1 - get the tens digit.
mov acc x3 - send it to the motor-x MC.
dst 0 dat - set the ones digit of acc to the contents of dat. This is trimmed, so only the ones digit of dat makes the jump.
mov acc x2 - send the digit to the motor-y MC.
Only five lines instead of six, so I can fit it in the motor management MC. As a result of this, the motor-x and motor-y MCs are now MC4000s, running more efficient code. The design's both cheaper, and once again more power-efficient; it's ¥29, and 4829 average power per run.
And with this, I'm really, really sure this is the final version.
To be quite honest, this has been a mental workout like no other! The last time I did something that felt this hard was when I was a kid, playing an indie game called SpaceChem. One late-game level - "Ω-Pseudoethyne" - was almost the end of me, and was certainly the sandbagging of me for the three days it took to solve it.
This assignment? Well, it was comparable. Or maybe a little worse.
Time to hit SEND, and hope they don't want any changes.
[ ~ ~ ~ ~ ~ ~ ~ ~ ~ ]
The Ocean's Bounty Team liked the design! And, on top of that, I got to see the final harvesting robot. It looks like nothing so much as a UFO, actually - with a sort of... cage with a large number of sharp bits connected to motors, and a vacuum nozzle on the underside of the 'bot, above the 'cage'. As the harvester trails through the kelp beds, the 'cage' travels through the kelp and severs the harvestable bits; then it gets sucked up into a collection tank. (I think the idea is that the kelp beds regrow by themselves afterwards, but I'm NOT a biologist, so don't take my word for it.)
Apparently there's a mechanism that disengages my guidance chips and floats the bot to the surface once the collection tank is full. Then, someone can go in a boat and gather the harvest.
Sweeeet. We did something worthwhile! Though there is that nagging question of 'what now', I'm looking forward to eating some new and different foods. And now I can proudly claim that Avalon's made the kelp farming technology of the future, too!