So, I spent the last few days tweaking my feedback loop to incorporate phase matching as well as tempo matching. See, before, my code would simply match the motor speed to a designated speed, but it could be completely off beat otherwise. I tried a few different methods for achieving phase matching, and I think I found one that works.
Earlier, I measured speed by simply starting a timer at the beginning of every cycle and reading it at the end of every cycle. Similarly, for beat measurement, I start a phase timer as soon as a certain command is sent to the unit and reset it when it runs down to zero. This timer continuously runs down and resets until I tell it to stop. The idea is that the circuit keeps a local copy of the song's timing so that it doesn't need constant input from an outside source.
To get it started, I simply let it measure the time elapsed between certain commands that are triggered by my laptop. If I trigger those commands every song beat, it will have an accurate measurement of the song's BPM. In the future, the beat-detection circuit will send those signals.
The minimum length of time that the timer can measure (as currently implemented) is around .4 milliseconds. With this level of granularity, you'd expect that it could measure the BPM of a song accurately enough such that once synced up, it won't drift during the length of the song. What I found is that it would rapidly drift away such that it was noticeably off beat within 10-20 seconds.
I believe this is due to unpredictable delays that occur somewhere between my laptop and the circuit. Remember that this signal is being sent through a USB-RS-232 dongle. There's a lot going on. RS-232 commands are not considered to be time-critical, so the dongle adding delay is likely not outside of its operation spec.
My future BPM detection circuit will be able to send commands with much higher precision, but in the meantime, I simply sent the timing command every single beat so that I effectively manually reset any drift that the circuit might be experiencing at the start of every beat. A little bit of a pain, but tapping a button on my keyboard isn't too difficult.
The goal of my feedback control system is to get the period of each cycle as close to the target time as possible and to get the phase timer as synchronized to the cycle timer as possible. At the beginning of every cycle, my code looks at the phase timer. If the phase timer is very close to zero, that means that the beat is leading the wipers by a small margin. If the phase timer is very close to its maximum value, that means that the beat is trailing the wipers by a small margin.
Simple Proportional Feedback
If you remember from my last post, I had a feedback loop that looked something like this:
The idea is to calculate how far you are off your target and adjust proportionally to bring you closer to your target. Adding phase control makes it look something like this:
There's an important point to be made about this system. Because you only care to be on some beat, you always have the option to speed up or slow down to get back on beat. Because of this, the subtraction of measured phase to desired phase is considered to be subtraction from the closest beat.
Looking at these two loops, you'd expect this problem to be no harder than the previous problem as there are two isolated systems. The issue is that the systems aren't quite so isolated.
For example, let's say the system's timing is correct, but the phase is trailing by a small amount. I could compensate by speeding up slightly, but then the system would see that the speed is too high and try to slow it down. This would throw the phase off again, so the same adjustments will be made again and again. The result is an oscillatory system.
Here's a graph of the process. The x axis is cycles and the y axis is period (measured in units of .4-ish milliseconds).
In this plot, the "phase" measurement is actually the absolute value of the phase error and is trying to approach zero.
The speed error gain was a lot higher than the phase error gain, so you can see that the system favors speed to phase especially when it's first starting out and appears to be ignoring phase altogether. This quickly sets it into oscillation, and the system never really settles down as it continues to oscillate upwards of 30 cycles later.
So, what happens if we lower the gain of the phase by a lot? I re-wrote the firmware so that it restricts the maximum change the phase gain can effect to either +1 or -1 on the motor's speed setting (a scale from 1 to 255; the first 150 or so are reasonably fast). This should prevent oscillations as even at its worst, the speed can only be altered a small amount to account for the phase. Here's a plot:
As you can see, there are a lot fewer oscillations this time around. You can also see that the phase is being matched very gradually taking tens of cycles. Keep in mind that each of these cycles takes upwards of a second. It could take even longer if the phase was further off to begin with.
The plus side of this method is how stable the system is once it finally settles down.
To speed up the settling, I decided to take the advice of a friend who suggested a slightly smarter feedback system.
Match Speed then Phase
The entire point of doing this feedback system is that I don't know exactly how fast the wipers will move when given a certain speed setting command. There are a lot of variables that can interfere with the wiper speed such as window wetness, battery voltage, wind, dirt, etc. Because of these, I need to guess a certain speed and then evaluate how good the guess is before guessing again.
If I knew exactly how fast the wipers would move at a certain speed setting before doing any tests, I could create what's called a "feed forward" system where I could basically predict the future.
The thing is, once my code has found the speed setting that brings about the desired wiper speed, it's safe to assume that the same speed setting will produce close to the same speed in the future so long as there aren't any major changes in atmospheric conditions that can interfere.
This lets me do what is essentially a feed-forward system. I designed my code to take advantage of two facts:
- The wiper setting that produced a speed will likely produce the same speed if used again.
- Wipers take negligible time to accelerate.
Basically, once my wipers are satisfied that their speed is correct enough, they can stop entirely and just wait for the beat to catch up. As soon as the beat catches up, they can start immediately at the previously determined speed setting and not be too far off in speed or phase.
So my code now has three parts: First, it completely ignores phase error and tries to match speed. Second, it pauses and allows the beat to catch up. Third, once the beat is caught up, it starts again and uses the feedback method described above. Remember that that method was very stable, it just took too long to get there. With all of the hard work done, it's perfectly adequate to maintain the already near-perfect beat.
Here's a plot:
As you can see, the phase drifts wildly for the first 13 cycles or so and then rapidly drops to zero and stays at zero.
It was warm out today, so I had a chance to test my new algorithm on the real thing:
I was excited to find that the far end of the wiper cycle appears to be directly in the center of the ends time-wise. This came as good news because otherwise, I would need to adjust motor speed mid-cycle and try to guess the location of the far end of the cycle as the wiper motor only provides feedback for the parked position.
I was less excited to see that the parking switch appears to be slightly out of place. Humans are incredibly perceptive to mismatches in beat, so try as I might to ignore it, I couldn't help but notice that the beat of the song was coming slightly before the beat of the wipers going down.
While it would be convenient if the parking switch were to be moved by a few degrees, I'll probably have to make some considerations in the future to add an appropriate lead or lag to counteract the misalignment of the parking switch. These considerations could be made on either the motor driver itself or on the device sending the signals to the motor driver. Either way, I'll deal with it later.
I am very satisfied with how well this all turned out. Though I'm only part of the way there, getting the motor control down was a major stepping stone and honestly the one I was most worried about. All I need to do to properly finish up this step is to find a way to securely mount the motor driver in the engine bay and test how well it stands up to hot environmental conditions.
Now that the hardware part is more or less completed, what comes next is a bunch of audio processing and coding. Not exactly my favorite thing in the world, but at least it can be done from the comfort of my chair rather than in out in the parking lot.
Project files can be found here: Beat Tracking Wipers package v1.0