It’s been a while since the last post of this series. As so often, the task turned out to be more demanding than I first thought. And then I was also entirely new to assembly language, got distracted by my Inductance Meter Project (https://soldernerd.com/2014/12/14/arduino-based-inductance-meter/) and went on a skiing holiday. But finally, the promised library is ready.
If you’re new to my Arduino-based ultrasonic wind meter project, you might want to click here for an overview: https://soldernerd.com/arduino-ultrasonic-anemometer/. This is also where you find all the various downloads, including the new library.
Using the new library is easy:
extern volatile anemometerResults_t *anemometerCalculationSet;
extern volatile uint8_t anemometerStatus;
You can then access the results as follows (replace NORTH_TO_SOUTH with SOUTH_TO_NORTH, EAST_TO_WEST or WEST_TO_EAST as needed):
I’ll go through the meaning and usage of these one by one:
This function has to be called once before the anemometer can be used:
anemometerStatus notifies your when a new set of measurements has been completed and is ready to use. Every time a new set is ready, anemometerStatus will be set to 1. You have to set it back to 0 once you’re done with your calculations.
/* use the results */
anemometerStatus = 0;
A new set of results is made available exactly every 250ms or 4 times a second.
anemometerCalculationSet is a pointer to the ready-to-use results.
Internally, the library uses a ping-pong buffer. Every time a new set of results becomes available, anemometerCalculationSet is updated to point to the last completed set of results.
The result set pointed to by anemometerCalculationSet is a
where anemometerResults_t is a struct defined as
The following #defines may be used as array subscripts:
#define NORTH_TO_SOUTH 0
#define EAST_TO_WEST 1
#define SOUTH_TO_NORTH 2
#define WEST_TO_EAST 3
This is easy to understand. It is the time it takes for the envelope detector to trigger. It is averaged over 32 measurements. Reference point is the rising edge of the first pulse sent. The unit is nanoseconds (ns).
sine and cosine indicate the phase shift between the transmitted and the received signal.
This was by far the most challenging part of library. For each of the 32 measurements, 4 rising and 4 falling edged of the zero-crossing detector are evaluated. So the phase shift indicated by sine and cosine is an average over 256 measurements. But phase shifts wrap around, casually speaking. A phase shift of 359 degrees is almost the same as phase shift of 1 degree. The average should be zero degrees, not 180. Now you could try to solve the problem by constraining your phase shift to the range of -180 to +180 degrees. But that only moves the problem to the other side of the circle: -179 and +179 degrees are almost the same and their average should be 180 degrees, not zero.
I spent quite some time thinking about this and came up with increasingly complex alogrithms that still failed when some unlucky combination of angles was encountered. Remember, we are trying to average n (currently n=256) angles, not only 2.
But of course, many people smarter than me have encountered the problem before and have come up with a perfectly elegant solution: If phi is your phase shift, then calculate sine(phi) and cosine(phi). Sum up all the sines and all the cosines. Then use the arctangent function to convert the summed sines and cosines back to an (averaged) angle. [If wordpress had something like LATEX support, one could state this as a nice-looking formula]
So there is an elegant solution. But there’s also a tight time budget: The zero-crossing detector triggeres every 12.5 microseconds (us). In order to not miss the next zero crossing, we need to calculate both sine and cosine and add them to their respective sum in less time than that. And then there is some overhead like context switching. Plus we also have to do some housekeeping during that time.
sine and cosine are expensive functions, even more so on a 8bit microcontroller. So the only way was using a lookup table. With this approach I managed to stay within budget (around 10.8us). Besides, missing the next edge is not the end of the world – the edge after that is almost as good.
So we now have summed sine and cosine values – how do we use them?
atan2(cosine, .sine) gives you the phase shift in radians. Multiply this by (180/PI) if you prefer degrees. My preferred approach is:
(12500.0/PI) * atan2(cosine, sine)
This also gives you the phase shift but with nanoseconds (ns) as unit which makes it directly comparable to the timeOfFlight which is also in ns.
There’s also another function returning the temperature. It returns a int16_t containing the temperature in 0.01 degrees centigrade. So if the temperature is 23.45 degrees, it will return 2345.
It’s currently implemented using floating-point math and does not account for the sensor’s (slightly) non-linear nature. It’s there mainly as a placeholder. I’m planning to implement it properly using a lookup-table with interpolation which will make it much faster and will allow it to include the non-linear effects.
The .zip file with the library also includes a very basic arduino sketch using that library. I’m still evaluating what is the best way to calculate the actual wind speed and direction taking into account issues such as calibration and the like.
But my preliminary results look quite promising and I’m confident that most if not all the heavy lifting has been done.
As always, I highly appreciate any feedback and suggestions. Click here for the next post on this project: https://soldernerd.com/2015/02/17/arduino-ultrasionic-anemometer-part-14-wind-tunnel-testing/