💾 Archived View for benjaminja.com › log › 2024 › 05 › 21-station_conversion captured on 2024-08-31 at 12:00:03. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2024-06-16)
-=-=-=-=-=-=-
2024-05-21
For the past few days, I have been converting my weather station firmware to use an Arduino Atmega328P chip instead of a teensy Arm Cortex M7 chip. In my last post, I found that the teensy is significantly overpowered for the job and it could be contributing to why the temperature readings are consistently so high.
I want to discuss the logic that the station does and how it has become difficult to squeeze that same logic into a much smaller device.
On the teensy, the logic was split into a few parts:
1. Interrupt when the rain sensor senses 0.01in of rain.
2. Interrupt when the anemometer turns 180Âş.
3. Keep a history of past hour of rainfall.
4. Keep a history of the past 10 minutes of wind data (this includes average and gusts).
5. Send all the information back to the base station when it needs it.
This is quite a bit of things for the station to do and is definitely way too much for the Arduino to handle. Instead of keeping a history of the data on the station, the base station will do most of the processing. I figured this would be a relatively easy process, but I have forgotten just how under powered the Arduino is.
The Arduino has 32kB of flash and 2kB of ram. 32kB is pretty small, but that isn’t really a big deal. You have to have a really big program to surpass 32kB. I don’t even think the program for the teensy was bigger than 32kB. The real problem is with the ram. 2048 bytes of ram is all you get. That’s including static variables, stack variables, and the heap.
When you compile for the arduino, it gives you a helpful graph saying how much ram and flash your program uses. For flash, it just doesn’t need to get bigger than 32kB. The ram is a different story though. The graph that is provided for ram shows only static variables that are pre-allocated. So if you used 1kB of ram in the graph, then you really only have 1kB of ram for the stack and heap. So you really need to be careful about memory at all times.
This is one of the reasons why I don’t particularly like C++ for embedded since when using classes you sometimes never really know how much memory you might end up using. Though the embedded community has made C++ feel a lot better than I think how modern C++ feels. I can’t wait for embedded Zig to mature, but it is still at the point where only like 5 boards are supported, and even then, only registers are available which make programming a nightmare. (I’ve really only tried to get into embedded Zig once, and probably missed a bunch of things. I would love to be proved wrong about embedded Zig)
With my adaption to Arduino, I have reduced the responsibilities significantly for the station.
1. Interrupt when the rain sensor senses 0.01in of rain.
2. Interrupt when the anemometer turns 180Âş.
3. Store the number of rain ticks since the last update.
4. Store the number of wind ticks and wind directions since the last update (up to 1 minute worth)
5. Send all the information back to the base station when it requests it.
While the Arduino has the same basic responsibilities, it is now performing less calculations and storing less data in ram. The biggest changes are to the wind. Before, the teensy stored 600 floats/uint8 for the wind gust/directions at a rate of 1 sample per second for 10 minutes. That alone is 3000 bytes of data and 1.5 times the total ram capacity of the Arduino. And that is not including wind averages.
I’ve since realized that I could significantly save space by storing only the last minute of data (60 samples) and instead of calculating the speed on the chip, it just counts the number of ticks during the sample. I could store each sample as a uint8, and store the wind direction also as a uint8. This makes the cache only 128 bytes long.
You might be thinking to yourself “Self, how does one get 360º to fit in 255?” I’m glad you asked! My weather vane can only read 8 cardinal directions. “Great, so the direction can fit easily in a byte!” Not exactly, 10 times a second I take a reading from the weather vane. Convert it to x/y components and average it out for the sample. I then use an atan2 LUT to convert the x/y component back into an angle. The LUT is a 21x21 table ranging from [-1.0, 1.0].
🖼 Image of the LUT—Colorized
Because the LUT is built with steps of 0.1, there are 441 possible outputs, but there are only 232 angles that are produced. So instead of outputting the angle I output the value for another LUT which converts from values [0,232) to [-174,180]. This way I can store a more accurate wind direction within a single byte. The base station will have to convert the angle index back into a real angle, but I feel like it is a pretty interesting way to get more detail out of the sensor.