Following the success of My First DDS VFO, complete with Arduino script programming, I found myself interested in mimicking more of the features of the digital dials in ‘real’ rigs. Like dynamic incremental speed-tuning, where the tuning rate increases or decreases dynamically depending on how fast you spin the dial. More on this later. A more achievable feature is to have the band, mode and VFO come up on the frequency where you left it at the last power-down. This involves writing these parameters into the Arduino’s EEPROM, using the EEPROM library.
The Arduino library pages tells us that there are 1024 bytes of EEPROM memory on the ATmega328. If frequency is an unsigned long (32 bits or 4 bytes) and mode and band can be represented as one byte each, we only need to store 6 bytes of data for each band in EEPROM. The Arduino library pages also state that ‘EEPROM has a limit of 100,000 write cycles per single location’ (byte), therefore, ‘avoiding rewriting the same value in any location will increase the EEPROM overall life’.
This raises a number of questions. Is that 100k writes to a specific byte? If an overused byte fails, how does it fail? Are the bytes either side of a failed byte affected? And how accurate is that 100k number of guaranteed writes? Fortunately, some great minds have worked this problem.
A handful of dedicated AVRFreaks have tested this out by writing code to thrash their poor little Arduinos all the way to premature EEPROM-demise. It sounds kind of illegal, or at least a bit immoral, until I remember that Arduinos do not have legal rights, consciousness or a soul.
The AVRFreaks’ sacrificial experiments have answered my questions. One Freak’s test failed to read back correctly for the first time at around write number 7.2 million. The damaged byte got stuck at 0x00.
Another Freak ran his tests 4 times to different EEPROM addresses on his atmega168. In all cases errors started showing up after 4M-5M writes. After failure, one byte storing 0xAA read correctly in roughly half of the cases, and in the rest it read 0x2A. He concluded that bit 7 in that byte is “leaky” towards zero. Not sure how much science there is to support the theory of ‘leaky bits’ but we get his drift.
Let’s work this out for my Arduino controlled communications receiver. Let’s say my receiver is switched on for an average of two hours a day (it isn’t, this is a generous estimate to allow for some future halcyon dreamtime when one doesn’t have to go to work). When you ‘use’ a receiver, let’s assume that for 5% of the time (10 minutes a day) you have your fingers on the dial and are turning it. The rest of the time the dial is not moving, you are listening.
You only need to refresh the stored ‘last known frequency’ when you are actively turning the dial. In fact, you only need to do so a few seconds after stopping turning the dial, if you accept that when you turn the receiver on, you are happy for it to come up reasonably near where it was when you turned it off. With a 2 second refresh, in most cases your receiver will come up exactly where it was when you turned it off, because you will have stopped tuning it (for at least 2 seconds) to reach for the off-switch.
During the 10 minutes (600 seconds) of daily tuning time, assuming a 2 second refresh period, my controller will write to the EEPROM 600/2 = 300 times a day. At this rate I will hit 100k EEPROM writes after 100,000/300 = 333 days… about one year. If my EEPROM performs like those whose tests ran to 5M write cycles my receiver will perform faultlessly for 45 years.
This seems like a good bet. In a year, worse case, if I am still tuning my receiver that much each day I will have no difficulty detecting that the startup initialisation logic is broken. If my EEPROM lasts 45 years my receiver will be electronic dust, I will be operating ethereal rigs in the Big Shack In The Sky, and VK3HN will be in the hands of some whipper snapper with augmented reality retinas and a terahertz SDR rig implanted behind his left ear.
Inserting the logic in the VFO/controller script was straightforward. First, I found the place in the code where the rotary encoder change is mapped to a frequency delta (10Hz, 100 Hz, 1 kHz etc). Each time the display is changed I get the time in milliseconds and overwrite a variable called lastDialMovement. In the main loop, outside of the changed frequency path, I periodically check lastDialMovement. If 2 seconds has elapsed since the dial was last touched, write the band, freq and mode to EEPROM.
On init(), read the last stored band, freq and mode from EEPROM and use these values to set the si5351 CLK0 (VFO) and CLK1 (heterodyne oscillator if your rig needs one, most won’t), mode and band (which should in turn switch the appropriate relays and/or FET switches). Serial.println() statements sprinkled throughout the code trace what is happening during development. If you wrap these with #ifdef DEBUG #endif preprocessor instructions you can remove them (by commenting out #define DEBUG) once you are happy that the script is debugged to reduce code size.
The feature is working just fine in my DDS VFO. Ahhh, the convenience of it, always landing where you last left off! At first I thought I would just write the attributes of the current band that can change (current VFO, frequency, mode, band) from the band parameter array into EEPROM. But when I looked at the mapping I decided it was simpler to use the library’s type-aware EEPROM.put() method to store away the entire array of 8 band parameter sets. This means 8 calls to the put() method which may be slower but insignificant in real time. In setup() the EEPROM.get() method reads the attribute set back, row by row. This eliminates tricky code at the expense of using more EEPROM bytes. The additional processing time is immaterial.
EEPROM is just like a database that has to be properly initialised. So it became necessary to separate out the declaration of the band parameter array into a second short Arduino script which does the initial write to EEPROM. The main DDS VFO script can then assume the values are already in memory. This avoids putting code into the main script to deal with the once-only write case. You have to keep the struct and values exactly aligned, of course. The proper way to do this is to put all the declarations into a single .h header file that gets included into both scripts.
Regarding EEPROM byte failure, I decided that if failure is a function of the number of writes to an individual address, then it shouldn’t matter if I write 20 or 200 bytes. Perhaps the probability of one cell failing rises when you write to a bigger block. I backed the write frequency off to every 5 seconds.
A final thought. For 8 bands the persisted array occupies about 220 bytes. On a Nano that leaves 800 bytes unused. If a cell does fail it would be easy to add a 200 byte offset to all writes and reads to move storage into a fresh area. Another thought… you could implement an offset in the code, add a counter, and every 1k sessions, cycle the block successively through the EEPROM address space, thereby spreading the write cycles, and potentially extending the life of the EEPROM by a factor of five.
Now that power-down persistence is done, what would a speed-proportional tuning algorithm look like?
In essence, you would need to detect the rotational velocity (and therefore acceleration) of the tuning dial by keeping a series of, say, 3 millisecond samples (via the millis() call). If these samples are roughly at equal intervals, and the times decrease, you can infer dial acceleration; if they decrease, deceleration.
The degree of change needs to be mapped to certain thresholds which correspond to the available frequency increments (100Hz, 1kHz, 10kHz). To avoid a runaway dial, you only want to increase the VFO tuning rate (from, say, 100Hz to 1kHz) after a short period of acceleration and sustained tuning at the higher rate. You could start with 1 second. Once this has been established, you can bump up the increment.
A symmetrical algorithm, or the same one that works with positive as well as negative deltas, would unwind the effect as the tuning rate slows or stops.
It’s an interesting Arduino programming challenge for another day.
Postscript: Writing parameters to EEPROM upon detecting an Arduino power-down has been discussed over at the Groups.io BiTx20 group. The discussion followed my sequence of thoughts almost exactly… first attempt; write every 10 seconds; second attempt; write after no dial movement and then idle; finally, Pavel CO7WT shows us how the save on power-down can be done:
1 - Put a big capacitor (> 470uF in my case) on the +5v to the Arduino 2 - Decouple the +5v of the LCD backlight LED from the +5v of the Arduino: this is the power hog 3 - Make a power divider from Vcc to an analog input and sense it aggressively for a fast dropping delta, then trigger the EEPROM save (a digital pin read can also work, but looking for a dropping delta is faster). I have done that for a friend... with a 820uF on the +5V supply to the Arduino I see: "POWER OFF" "EEPROM SAVE" On the LCD a few instants after complete power off... of course no backlight.