Another Arduino Nano based obstacle avoiding /object finding Robot

I’m building another obstacle avoider/finder based on a few components controlled by an Arduino. I’m calling it QB

QB is a cube shaped (hence the name Cubee) so far fitted with:

  • a HC-SR04 sonar distance sensor (which look like eyes),
  • two chunky 66mm dia x 25mm wide wheels driven by geared DC mini motors
  • a free spinning ball bearing which acts as a sort third wheel and keeps QB level and stable)
  • two HC-020K optical switch encoders (1 for the axle of each wheel).
  • a ZK-5AD dual DC motor driver module based on the TA6586 IC (Note ZK-5AD is the brand/model name of the module) to drive the two DC motors.
  • 2 X 9V polymer ion rechargeable batteries. One for the Arduino Nano and sensors the other to power both the motors.

QB is controlled by an Arduino Nano MCU, mounted on a UNO/Nano prototyping shield that many convenient pin out options. I am aiming for “compact cuteness”. I may add other sensors in the future, but for now I want to be able to program QB to find several objects in say a 3mx3m arena, or to manage its way through a maze.

All of this has been done before and reported severally on YouTube and other forums such as this, but you may have noticed the HC-020K and the ZK-5AD are not common choices among those that have come before me…


I had a lot of trouble with the HC-020K encoder - It turns out that as supplied, they do not provide accurate counts per turn of the motor axle (read: they need to be modified to be of any use). This article explains why Optical Motor Speed Sensors (electroschematics.com). What is more, the discrepancy is different depending on whether the Arduino is powered by USB or by battery, making me think the problem was with the quality of the battery power and this set me on a very long very frustrating chase of wild geese. There are a couple of solutions to the problem described here and there on the internet. The easiest one to implement is to solder a 100nF capacitor between G and OUT on the HC-020K module. A 0.22 micro F capacitor works fine too.


Now for something I still have not solved (seeking your help)

The ZK-5AD driver requires inputs from 4 PWM capable pins on the Arduino Nano (2 for each motor). The only ones that are available to me for this purpose as Pins 5,6,9,10. The trouble is that the two motors spin at different speed even when I send them the same PWM value using the Arduino Analog-write() command the PWM vs Speed response is quite different for each motor too. The difference is large and impossible to compensate for in software. (There is no PWM setting at which the speed of the motors is the same)

I checked the resistance for each motor and both appear to be the same. When I checked the PWM signals coming out of pins 5,6,9,10 all have the same peak to peak voltage (5V), and the same RMS voltage for a given PWM setting but the frequency and width of the pulses on pins 5 and 6 are very different to the frequency and width of the pulses coming out of pins 9 and 10.

Microsoft’s AI bot Co-Pilot says that this is expected behaviour from those pins, that its likely to be causing the speed differences, and that for optimal DC motor control I need to change the Arduino’s internal timers, Timer0 and Timer1 so that the frequency of the PWM signals match and above are above 1 kHz, warning that this will also affect any code that uses functions like delay() and millis().

There are literally dozens of YouTube videos describing similar builds, and I’ve never seen any mention of needing to change the Arduino’s timers. I am hoping that someone here has do this sort of thing before and can confirm:

1- that the differences in frequency on the PWM pulses are the likely causes of for the motor speed differences (Note the PtP and RMS Voltage are the same)

2- That changing the Timers is an appropriate solution,

It looks like that part is equivalent to the more widely-known L298N.

This part is confusing me. It’s not obvious from everything else you say.

Why can’t you put a PID controller on each motor/direction channel separately? Two DC motors aren’t normally going to be exactly in sync anyway. That is, I wouldn’t expect a robot to go straight if you do something like

analogWrite(leftForwardPin, 255);
analogWrite(rightForwardPin, 255);
analogWrite(leftBackwardPin, 0);
analogWrite(rightBackwardPin, 0);

Here’s a conversation with someone who is using the standard arduino PID library that might be a valuable read:

You would of course need a separate PID controller for each motor/encoder pair.

Here’s a library that includes a PID controller and motor controller in a single file that you can read, and has an L298N reference example of using it. It might make PID control clearer? Or maybe not.

If you care here… Why not use, say, 9 and 10 for the forward direction and 5 and 6 for reverse, if you want to match them more closely? I’m guessing that you are using 9 and 10 for one motor and 5 and 6 for the other? Shouldn’t matter, because the motors won’t be perfectly matched anyway. :smiley:

Hi Michael, thank you for your great help as usual

I think you may have gathered that I am a bit above my depth here, but hey that’s the way I roll ;-).

Why can’t you put a PID controller on each motor/direction channel separately? Two DC motors aren’t normally going to be exactly in sync anyway. That is, I wouldn’t expect a robot to go straight if you do something like

My code does have PID control of sorts. That’s what I want to use the encoders for and why I spent so much time trying to get them to give me meaningful results. The full 0-255 spectrum of PWM using analogWrite is not available to me. Below 50 the driver does not pass enough power to the motors and the motor speed maxes out at about 80, so there is no point going higher. But I’ve measured both the forward and reverse speed at several PWM values using analogWrite and these are the results:

For simplicity I did not show it here, but the result is the same for FWD, REV, and when I swap which chip in the driver is used for which motor.

For the robot to move straight forward or backward the speed of the motors need to be the same. As you can see, in my set up there is virtually no PWM setting at which the motors run at the same speed, and there is no way that I would be able to get the robot to gently turn to the right as it moves forward (not important, but nice to be able to do) - All the PID control would be doing in my case is getting the right motor to run as slow as possible and the left one as fast as possible - That’s hardly control! (Isn’t that right? Am I misinterpreting the data?).

This information caused me to explore the differences between the arrangements for the two motors, and the only thing that I’ve found so far is a difference in the frequency of the PWM signal out of ports 5/6 and 9/10. (I’ve only thought of this as I write: I have NOT yet confirmed that the voltage going to each motor is different as a result… I better do that huh?).

As I said previously MS AI advice is that the frequency of the PWM signal does affect the operation of the motors in this way and that I could alter the operating frequency of Timer0 and Timer1 on the Arduino so that the PWM pulses out of Pins 5,6,9,10 are the same and above 1 kHz. But altering Timer 0 will also alter operations like delay() and millis(). Other options were even less appealing.

That’s a very creative solution! I don’t care if forward is faster than reverse! I only care that right is faster than left! and minor mismatches can be sorted by reinstating PID control. Rock your head @mcdanlj ! That’s one suggestion that the Microsoft AI did not make!

Thanks again! I’ll report back when I’ve solved the problem.

Staying one step ahead of the AI! :smiling_face:

Hi Michael, @mcdanlj

I kept everything as it was for now, and connected an oscilloscope to the motor terminals in the motor driver, which has a separate T6586 IC for each motor. When I connect either IC to Pin 5+6 on the Arduino the voltage going to the motor terminals is saw tooth shaped, 6 V RMS (confirmed with a multi-meter) and sawtooth frequency is ~ 500 Hz. When connected to pins 9+10 the frequency is ~1kHz and the RMS voltage is 7 V (also confirmed with multi-meter).

So the MS AI is correct, Pins 5&6 are controlled by Timer0 which operates at ~500 Hz and Pins 9&10 are controlled by Timer1 which operates at ~1kHz. The difference in frequency of the PWM signal out of the pins seems to significantly affect the voltage that the ZK-5AD driver sends to the motors, even at the same analogWrite() setting. Not all motor drivers have this problem - and this might explain why I haven’t seen posts describing a similar problem

I might try changing the timer settings so that both sets of pins send PWM signals with matched frequency to see what happens. As mentioned the effective range of PWM values is only 50-80. Perhaps changing the frequencies so that they are closer to the 8 kHz suggested will improve the effective range of PWM values to something closer to the expected 0-255.

Oh well, if everything worked the first time, I would not have learnt any of this!

I didn’t doubt that those timers were used for those pins; indeed, it was the source of my suggestion to pair them differently. :grin:

I had missed your point about the TA6586 IC. Oops. I finally found a datasheet for the TA6586, which is pretty sparse compared to the L298N datasheet. Besides the TA6586 being vaguely half of an L296N, the TA6586 datasheet has nothing about the internal architecture, nor any information on how fast it acts. The L298N datasheet, by contrast, has a table that includes this information:

Also, the pinouts are nothing alike, so the two may be conceptually similar to each other, but they aren’t equivalent. I see that some places market a dual-TA6586 board as an L296N board. (:rofl:)

It’s not obvious to me that higher frequencies would be likely to help, but without any concept of how the two TA6586 ICs on the board work inside, it’s hard to guess.

To test, you could write a sketch which does soft PWM on only one pin (and nothing else!) using delayMicroseconds() and step the frequency from much lower to much higher and watch what you get on the scope driving a motor. (If I had this on the bench I’d just hook up my signal generator since it’s even easier than writing a sketch, but I don’t know that you have one.)

Hi Michael,

Wow, you went to a lot of trouble! Thanks again for all your help.

The L298N based units with the large transistor and heat sink seemed a bit large to me. This is a consideration because its a bit cramped inside the small 90x 90 x 90 space.

Then I saw this video

In the Video RoboJAX concludes that the ZK-5AD driver is AMAZING because it can run at 12V and deliver 4A without a heat sink. The small form factor and efficiency appealed to me so I decided to try it. In hindsight, maybe I should have stuck with the very popular well characterised L298N based drivers.

Anyhow, earlier today I took a punt and I changed the frequency of both Timer 0 and Timer 1 on the Arduino. It was not as complicated as I thought. Changing Timer 0 had the forewarned effect of changing the behaviour of the delay() function and the results of the millis() function but both in a predictable way so problems there.

I changed both timers to run 7.89 kHz now as was recommended, and that has some other consequences: With the Timers at this frequency, the PWM_value working range (in analogWrite(pin#, PWM_value) ) changed from ~50 - 80 to ~ 140 - 255. I have not been able to calibrate the motor speed within this range yet, its possible that the speed maxes out at PWM values well below 255.

At the higher PWM frequency the speed of the two motors is much more evenly matched in fact at PWM_Value=150 the; left motor is slightly faster at 250 the right motor is slightly faster and at 200 the two are more or less equal in speed. So the change in timer frequency has made a huge improvement.

Unfortunately I also found out that the code that I wrote to compensate for small differences in speed doesn’t work - and the speed of both motors seem a bit too fast now… So I think that I solved the original problem, but I am not completely out of the woods yet.

In the fullness of time, I’ll have to think about whether the next iteration of QB will use a different encoder module and a different motor driver. We’ll see.

Thanks again, Michael I hope you enjoyed the post.

Jorge

Are you using the standard Arduino PID library?

I’d expect you would use a separate PID controller for each wheel and keep setting each wheel’s target speed.

I… don’t know whether the PID controller would need to be re-tuned for the change in the base timer rate!

There should be L298N driver boards that use the flush PowerSO-20 packages. Then I also found a board on Aliexpress that is US$0.99 for four pieces with free shipping, though it is an MX1508 board. The MX1508 uses MOSFETs rather than BJTs. The L298N proper seems (if its datasheet block diagram is trustworthy) to use BJTs. The only MX1508 datasheet I find is mostly in chinese; while it shows skew in the diagram, it don’t have any measurements with time units. GitHub - Saeterncj/MX1508: Arduino library for MX1508 dual DC motor driver is a library for it. I know nothing about it, I only see that it exists, and see that it doesn’t implement PID internally, so you would wire it up with a general PID controller.

I also wonder why not use an MCU with a more capable architecture for PWM control?

Hi Michael

Thank you for your interest.

Well, not sure that I can give you a rational answer here. as it goes a lot like: “To a hammer everything looks like a nail” - More precisely, I have a handful of A. Nanos and other bits and pieces that I have used successfully for other projects (including a small silicone rubber tensile testing machine). And there are SO many videos and other resources that use A. Unos and Nanos to control robots and motors etc.

Now that I think of it, in most of those videos the author grabs a Uno, a driver of their choice and gets the motor to spin a few times clockwise and anticlockwise - the end. In some videos the author adds an encoder and after a flurry of activity shows that the encoder provides an RPM value… (such videos never show that the RPM value is correct). :laughing:

So now I am realising what I am doing is a little bit more complicated :thinking: I actually want the encoder values to be correct and to use them to get two cheap motors mounted facing away from each other to turn wheels at a precisely controlled speed for a precisely controlled number of rotations. I didn’t really think of it that way until now, but I am starting to think that I am pushing some capability boundaries, my own included… :wink:

BTW the approach that I have taken is not PID speed control. QB will travel a specific distance (number of pulses on the motor encoder) in a predetermined way (forward, back, turn left/right, spin left/right). To travel forward or reverse in a straight line, the left motor and the right motor need to turn a specific number of turns at exactly the same but opposite speed, but I don’t actually care what the exact speed is. The code that I wrote tries to compensate for a lower number of pulses in the left motor encoder by speeding up the left motor.

I am reluctant to implement full PID control - did you read all the posts in the links to PID control in your earlier reply?

Yes! I looked back at how the L298N works and its quite different! The L298N needs 6 pins to run 2 motors, but only 2 need to be PWM capable. The TA6586 needs only 4 pins to run 2 motors but all 4 pins need to be PWM capable. The Uno and the Nano have 6 pins that are PWM (2&11, 5&6, 9&10) but each pair is controlled by a different timer configured to a different frequency by default. Pins 2&11 are being used for other things in my project. I thought that your idea to use pins 5&9 for one motor and 6&10 for the other is ingenious, but I opted to operate all four pins at the same higher frequency instead… We’ll see how that works…

Finally, you’ve been very kind and shown a lot of interest. I thought you might like to see a couple of photos of what I am trying to build. It needs a LOT of refinement. A task that I have put off until I iron out the issues that we’ve been discussing. I am aiming for QB to have programmable behaviour and I intend to make the CAD files, STLs, and component list available in forums such as this or elsewhere. That’s if I ever iron out all of these issues and finish it.

Half the fun is working within constraints anyway. :smiling_face:

:+1:

I at least skimmed through them. The OP may not have stuck around, but others provided example working code that worked for them experimentally.

PID is the classic algorithm here. There are others.

I’d still suggest just using the standard Arduino PID library and at least trying it. To travel in a straight line, for a particular distance, you want both motors to turn at the same speed for a particular number of counts. You can do all sorts of tuning if you like. If you know the left motor is typically slower, you can bias the initial value before handing over to the PID controller by some experimentally-derived proportional amount. You can start setting the I and D terms to zero and see whether it “hunts” or “jitters”

I found this video helpful for gaining an intuition for the terms in a PID controller.

Thank you for the photos! That’s a cute robot. :smiley:

Background: I tried for a while, years ago, to make a rolling ball robot with a beaglebone blue with a 9-axis IMU (that includes magnetic field sensor) and ended up not completing it. It isn’t trivial, it turns out, and I ended up doing other things instead. But I do appreciate some of the difficulty. :smiley:

I learnt the principles of PID control at Uni (40 years ago), but that video makes it really clear what each of the PID parameters does! And now that I can trust the encoder values it will be really simple to play with the parameters and see their effects. (When using the graph I showed earlier, I used a video because I could not trust the encoders.)

Perhaps I will try PID control with the set point being the difference in count between the two encoders, LeftCount - RightCount is = 0. If the difference is negative then I could alter the difference between the speed of the motors in accordance with PID parameters. I think that this may work well when going straight forward, straight in reverse and spinning in either clock direction.

If I ever want for the robot to travel in a curve, I will have to think of something else because in that situation I actually want both the count and the speed to be different left from right… Anyway , I’ll persevere. But it may take a while because a number of other projects are calling. :slight_smile:

In the meantime I am really happy that I have solved the Encoder and Motor Speed mysteries and with how much I’ve learnt in the process.

Keep on keeping on - I’ll post again when I can.

PS: with both Timer0 and Timer1 at 7.8 kHz the two motor speeds are much closer to being the same. The PWM value vs RPM graphs actually cross, that is: there is a PWM setting at which the RPM of the motors is the same! That’s a much better starting position for fine motion control.

1 Like

Hi @mcdanlj Michael

I’m re-reading the whole thread and realised that I hadn’t taken in one of your suggestions, which is to have PID control independently on the speed of each wheel.

That may be more flexible if for some reason I actually want the speed of the wheels to be different.

I’ll keep thinking.

1 Like

Oh, it’s absolutely essential to have a separate PID controller for each wheel, yes! And if they act a little bit differently, you can provide separate PID parameters for each side until they go straight.

QB is pretty much finished. It works, but not as well as I had hoped.

The HC-SR04 ultrasound distance sensor seems to be picking up noise form the room (worked fine on my desktop, but not in the kitchen) - I haven’t figured out what to do about that yet - I put polyester fibre wadding around the sensor and made two tubes that extend ~1 cm in front of the sensors in an effort to make the HC-SR04 more directional. Better but not great.

@mcdanlj I mentioned earlier that the ZK-5AD controller may not have been the best choice: To control the speed of the motors 4 PWM parameters between 0 and 255 need to be passed to PWM enabled pins on the Arduino. But with this driver/motor combination the motors stop completely when the PWM signal value is < 50, and the motors stop turning faster when the PWM signal value is > 110. That means that the effective working range for the parameter is on 50 - 110, not 0 - 256.

But it seems that the motor controller was not my only sub-optimal choice.

The QB runs a bit too fast even at the lowest speed setting. This means that I should have used a DC motor gear with a lower gear ratio (in my defence I thought that those yellow gearboxes were all the same!). Also the optical encoder wheels have only 20 slots in them, which means that the number of encoder counts per revolution is too low for any meaningful speed control - I could count both the rise and the fall of the pulses to increase the pulses per revolution count to 40, but I I think that the timing between raising and falling encoder counts will not be the same.

All of this makes PID control of the wheel RPMs difficult - QB needs to travel a long distance to get a meaningful encoder count, and the RPM estimate is not very precise, by the time an adjusted signal is calculated it’s time for QB to stop and do something else.

For now I’ve generated a PWM value vs speed calibration chart for both wheels and send different parameters to the controller in accordance with those calibration charts so that both wheels travel at the same speed. This sort of works, but I fear that as the battery discharges the values will also need to change. Perhaps I can store the values in memory and update them each time QB runs, so that its ability to move in a straight line improves with each run in a session.

I have decided to add Bluetooth so that a list string of commands can be sent from a mobile phone for QB to execute - eg Move Forward/Back, Turn, Look, Flash LEDS etc. This programming QB accessible to even young children. Once that’s done, QB will probably be ready for the world. It will be fun to see where others may take the design.

I am trying to think of the best way to do that. A blog? YouTube? ThingyVerse? - I’ll post a link here once I have done that.

Any suggestions from anyone about the above mentioned issues are welcome. Here are some updated photos.



3 Likes

Yes, I also would expect jitter in the rising/falling edges.

You could experiment with other encoder wheels, I guess. Laser print on transparency film and cut wheels out of that?

I have no confident ideas on how to improve the hardware you have, though…

3 Likes

@mcdanlj You’ve already helped me a lot! I’m just reporting on progress as a way of saying thank you!

I’ve overcome the jitter on the rising and falling edges by installing capacitors across signal and ground on the opto switch. I was surprised that the module didn’t have them. I have since bought different opto switches that are not susceptible to noisy rising and falling signals but they don’t fit well in the place that I made for them in the chassis, so I kept the old ones and soldered the capacitors on.

These parts sourced from China are very cheap, but data sheets are hard to come by and unreliable. In the case of the encoder, I have no idea what the width of the infra red beam is - it may be pointless to increase the encoder slot count if the “spokes” don’t adequately block the beam.

Buying different parts would not be out of the question, but I would prefer to try to overcome the limitations of the parts I already bought, rather than throw them away.

Anyway my 7 year old neighbour played with an earlier version of QB for 30 minutes! so the design is already a success. I think that BT control will make QB more accessible and interesting and then I can say that the design is ready for “publishing” in spite of its limitations. I think that I have a little (1 cm) speaker lying around, so in addition to blinking LEDs QB may soon be able to make noises in response to commands or events.

2 Likes

Yeah, that’s the real fun of engineering. Here are some constraints, now what can I do within those constraints? :smiling_face:

The 7-year-old beta tester sounds perfect for the job! :heart:

3 Likes

Looking good.’

Regarding distance sensing; ultrasound sensors are not very directional, putting them in a tube will help but sound likes to spread out…

I wonder if you could make a better ‘beam’ by putting the sensor facing into a ‘listening vessel’ (parabolic reflector)

Another plan could be two extra sensors at, say, 45degrees to the front-facing one. And then allow for strong signals to one side when plotting paths etc.

Alternatively; and at some extra cost, you can IR laser ‘time of flight’ sensors that can give accuracy of several mm, and can even be tuned for field of view.

I built a ‘lidar’ using a $8 sensor a couple of years back.I always imagined it would work well on a robot but never took it any further.

2 Likes

@easytarget All good suggestions - some I considered before and dismissed mainly due to their impact on the appearance of the robot. Many makers put the HCSR04 on a servo. I’ve programmed QB to turn a little until it hears that something is within a specified range.

But the problem is not that it doesn’t sense stuff that is there, the problem is that its sensing and reacting to stuff that is not there. I think that its stray sound from the surroundings that’s causing the problem. My home office is carpeted, so there is not so much stray reflected sound, and so the problem is not present when I try it out at my desktop.

I made the tubes so that only the sound that comes from directly in front will get in, but they are made of 3D printed PLA (to a hammer…) and that may not have been a good choice because sound is probably bouncing of the walls and then continuing on contributing to the problem - I’ll give tubes made of foamed material, felt or something that will absorb rather than reflect sounds a try next.

Maybe your best suggestion is to use a Laser TOF sensor - either instead of or in addition to the HC-SR04 - I just looked one up on e-Bay and its not too expensive, it has a sensing range of 2m (plenty for this application) with a resolution of 1 mm and the module dimensions are small enough to make it fit somewhere in QB’s 90x90x90 mm chassis.

BTW - I got the HC06 Bluetooth Modules today - I am a happy nerd.

1 Like

I would expect that reflections on a different path (“clutter”) are more likely than environmental sounds. Those devices use an ultrasonic pulse of a specific frequency and the circuit to listen listens for a pulse at that frequency. It’s not an “impulse” (basically a click), it’s a tone. And the tone that is heard should go through a bandpass filter that “listens” only in the range of the (ultrasonic) audio frequency of the tone.

If it’s truly being confused by environmental noise, the bandpass filter is probably bad; cheap components can have poor tolerance…

But that’s not really how sound works. :slight_smile:

TL;DR — try the cheap laser TOF sensor.

There are really two primary mechanisms for directional sound reception.

  • Parabolic sound reflectors, like @easytarget mentioned
  • Shotgun microphones, which you might have had in mind when you made the tubes

Shotgun microphones use slots in the side to cancel out interference from poorly-aligned waves. They aren’t just a tube with a microphone at the bottom.

As the sound hits the front of your tube, there is a difference in impedance between free air and your tube. The pressure peak in the longitudinal sound wave hits the edge not all at the same time, and refracts around the edge, which creates a pulse of pressure in the tube of slightly distorted waveform shape. My understanding is that this is one cause of the characteristic “sounds like it’s coming through a tube” distortion you hear if you hold a pipe or tube to your ear, and the other is that the tube itself is an open resonator with a resonant frequency. An old “ear trumpet” from before hearing aids had a bell just like a trumpet and for roughly the same reason: impedance matching between the higher impedance of the tube and the lower impedance of free air. And just like a trumpet, those had a resonant frequency and I understand that they were tuned for the frequency ranges important for discriminating human voice.

Because sound is a longitudinal, not transverse wave (it’s different from light here), sound-absorbing material will attenuate all sound, regardless of incidence angle. It won’t particularly attenuate sound with an oblique angle of incidence substantially more. You might get a little bit of directionality, because the distortion will reduce the power at the original frequency, and at least if the receiving circuit has good discrimination, it will cut out the other frequencies in the filters.

One microphone manufacturer has some nice animated graphics that show a bit of how shotgun mics work. The main problem is that it shows the sound wave as if it were transverse. I understand the problem; I don’t actually know how I would represent a longitudinal wave graphically. But if you keep in mind that the wavy line here represents a measure of varying pressure along a line, and doesn’t really mean that air molecules are vibrating sideways, this should help understand how they work.

I believe that the design of the slots in the interference tubes depends on the wavelengths being rejected, but I don’t know this. And I never learned how to design them, so I wouldn’t even know how to suggest designing a shotgun mic for the ~1cm wavelength of those ultrasonic distance sensors.

I found a series of articles that you might find interesting. They are doing the signal generation and discrimination in software. The card you are using is doing all that, so it’s a black box to you, but it’s useful to understand what it is doing. I hate that they call it “ultrasonic radar” since RADAR is RAdio Detection And Ranging, but they didn’t ask me… :sob:

The intro pointing out that clutter is a problem:

A transmitter circuit:

Here’s the part that talks about false targets (clutter) and parabolic antennas:

Vacuum forming a parabolic reflector (wow, 3d printing is easier…)

The receiving circuits:

Differential reception for computed bearing: