XOD subtlety I don't understand

OK. I’m trying to take this a step at a time and I am having trouble. I think there are subtleties of XOD that I do not understand.

The goal: make my Elegoo robot car move forward when there is no obstacle in front; move backward when there is an obstacle in front. (I have not tried to implement a loop yet. I’m just trying to get it to work if it starts with the “road clear” or with an obstacle in front.)

I want to take ultrasonic sensor readings and, depending upon the reading, make the car go forward or backward. I first approached this by determining if I could “interrupt” the forward motion of the car. I was able to do this with a delay node, like this:

stop-delay

The timer is set to 10 seconds. It’s “Set” pin is set to trigger “on boot.” The motor’s “On” pin is set to trigger “on boot.” On boot, the delay and motor are both “booted”. When the delay finishes, it pulses the “Stop” pin of the motor and the motor stops. This works. So far so good.

So, all I have to do is trigger the “Stop” pin of the motor with the ultrasonic sensor to stop the motor, correct? Not so fast.

Here is my node to stop the motor by triggering the “Stop” pin with the ultrasonic sensor. If the distance in inches read by the sensor is greater than 0 and less than 20, then the “Stop” pin is triggered via a “pulse on true” node. Otherwise, the motor “On” pin is triggered via a “pulse on false” node. But, instead of this expected behavior, it just runs continuously regardless of whether there is an object in front or not. I don’t know why.

stop-us-sensor

For anyone interested, my motor patch is just a custom gweimer/h-bridge with default pinouts for my Elegoo car (and some pin label changes and remapping of “speed” inputs). Here is the patch for my motor:

Elegoo-motor

And here is the patch for my ultrasonic sensor:

I am finding that often I will construct a patch that works in isolation, but when I combine it with another, or others, it stops working. I think there is some subtlety in the signaling that I do not yet understand. (By the way, I have read the documentation on “Concepts” [including the execution model and linking rules, among other things].)

Perhaps not obvious from the existing documentation, there are basically 2 ways to use XOD. In a constant feedback configuration (push button, LED lights; release button and LED goes off) or as a state machine (using pulses to control program flow). When you try to combine the two options as you have here, you will generally run into problems. You have a pulse to do a range reading (which only happens once… You need continuous readings as you move), but input to motor control is independent of pulses (it depends only on output of “and” node, and pulse-on-X nodes only fire when there is a change). Part of the reason this causes problems is that all nodes get initialized before program actually starts. Code that is not dependent on pulses actually executes at this time and can produce unexpected results. Code that is dependent on pulse has nothing to do during initialization.

One option would be to replace the pulse-on-true/false nodes with a gate to direct the pulse from the ultrasonic DONE pin to the appropriate start/stop pin. You will also need to loop back to check range again. (You will also need to handle ultrasonic error pulse, or your program will stop processing if there is an error.)

This should help you get your start/stop code working, and may be enough to get you farther. Let us know what you come up with or if you are still stuck.

gweiner, I really appreciate your help, but I think there is just too much going on behind the scenes for me to figure out how XOD works. I tried replacing the pulse-on-x nodes as you suggested with pass-if nodes to pulse the motor, but now, instead of running regardless of whether an obstacle is in front, it does not move, regardless of the presence, vel non, of an obstacle.

I have to admit, I’m getting a little frustrated. The attraction for “visual languages” for me is the promise of getting away from the tedium of coding, but I could (and, in fact, did) have accomplished this with code in about 30 minutes rather than the HOURS I have spent trying to get this to work in XOD. The problem is not with XOD - it seems really fantastic. I just can’t seem to “get it.” :frowning_face:

Here is the current version:

Update: This version appears to work, more or less. I added some defer nodes to give the logic gates time to “catch up” with the motor and I added a reset if there is an error from the ultrasonic sensor.

For some reason, occasionally, but not always, after I place an object in front, and then remove it, the ultrasonic sensor stops reading the distance. At that point I’m not getting an error pulse (so far as I can see) but that could be because it is getting reset too quickly for it to register on my watch node. But, even if I am getting an error pulse, it should reset the ultrasonic sensor via the “defer-any-pan” reset path - so, I’m still puzzled over that. But at least it is “sort-of” working.

Sorry. I suggested using “gate”, but I should have said “branch”.

The “defer” node between ultrasonic & “branch” is to make sure conditional for branch-GATE has settled before the pulse hits “branch”. In your code, you put the defer after the “pass-if” node, so the pass-if-COND pins may not be set to new value before pulse gets to them.

In your code, you have a continuous-pulse node. This can start a new pulse before the previous pulse has worked its way through the patch, giving you multiple pulses trying to process at the same time, which can be a problem, especially since the ultrasonic sensor needs time to settle down before doing another ping. With my code, the done pulse (or error pulse) loops back to start another cycle, so there is only one pulse traveling through the system at a time. This is generally very important in a state system unless it is really possible to have multiple states at the same time (in your case, you cannot have a “go” and “stop” state at the same time). This also gets you closer to being able to add code to check left/right for room to turn & continue forward after reaching an object (assuming that will be your next step; instead of grabbing the ultrasonic-DONE pulse to loop back, you would only want the pulse to the motor-ON pin to loop back; the pulse sent to motor-OFF would instead branch off to the code to look left/right).

Another problem I have run into in the past (maybe it is fixed in newer XOD versions), trying to feed “boot” pulse directly into the program doesn’t always work since it seems to send the pulse before program is ready for it. Adding a “defer” node after “boot” fixes that issue.

1 Like

Yours is a better, cleaner approach.

The defer node has me a bit puzzled. It seems to be simply a DELAY node that lasts only as long as the “transaction”. The key is, what is a “transaction”? I was thinking that a “transaction” was a “single pass” through the patch, but I am beginning to believe that it is more “atomic” than that. I have not been able to find any documentation on the DEFER node and transactions, so I’m still getting used to them.

Because of my view of a transaction as a “single pass” through the patch, I thought that a continuous pulse would actually only send a single pulse until the transaction was complete, i.e., until that pulse had made its way through the patch. I thought only then would a new pulse be triggered. Obviously this is incorrect.

Yes, I’ve seen several of your patches and I am starting to adopt the approach of placing a defer after the boot pulse.