TL;DR: I found a pair of bugs related to gCore's Real Time Clock (implemented on the EFM8 RTC/PMIC co-processor). There's a new version of the EFM8 firmware (version 1.2) and instructions how anyone with an older board can get updated at the end of this post. The bug is fixed in all boards shipping in Sept 2023 and beyond.
I added support for Caller ID to the weeBell bluetooth code. Caller ID sends the date and time as part of the message to the phone so I also enabled use of gCore's RTC to set the system time when powered up. During testing I noticed that the gCore RTC would lose tens of seconds per day. This lead to an investigation that ultimately found two issues with my version 1.1 EFM8 firmware.
The first problem is that the RTC timer preset was wrong. The RTC timer is configured to interrupt once per second and is clocked by a 32768 Hz crystal. I set the preset to 32768. I had missed that the Silicon Labs documentation said I should set it to 32767. But it turns out even their documentation doesn't account for a hardware bug in the chip and it should be set to 32766. Thankfully someone posted this to a Silicon Labs forum. The off-by-two error lead to a timekeeping error of about -5 seconds per day. Unfortunately I didn't see this during testing.
But that still didn't explain why I was seeing much larger errors. Because the clock was always slow I immediately suspected the code was missing the RTC interrupt somehow. Turns out that assumption was correct. The RTC interrupt is not latched. It only exists for one clock cycle (1/32768 = 30.5 uSec). So if the code is servicing another interrupt with equal or higher priority and that service takes longer than 30.5 uSec then the RTC interrupt will be missed.
The EFM8SB2 has two levels of interrupt priority and the RTC interrupt has high priority. But the I2C interrupt also has high priority because it occurs at least once for every byte transferred on I2C and I want to minimize clock stretching during reads. I had done analysis and testing to characterize the I2C interrupt and thought it would always finish quickly enough. However I committed a cardinal sin. I added more code after the analysis and didn't re-analyze. And now the I2C interrupt could take, under certain circumstances, more than 30 uSec to execute.
The weeBell bluetooth code polls the EFM8 via I2C fairly often, multiple times per second, looking to see if the power button has been pressed so it can turn itself off. It's also reading the battery voltage and charge status for the GUI. It turns out that every few hundred or thousand reads one slow I2C access would line up with the RTC interrupt and the RTC interrupt would be lost. The RTC would lose a second.
The slow I2C ISR routine had to do with how I guarantee atomicity for multi-byte values. The fix was to restructure the code so the atomicity was assured by non-ISR code and the ISR simply does nothing but access an array to get data for read responses.
Lots of testing later and I'm pretty confident that the RTC is now good to go.
Updating Existing Boards
I feel badly about letting this set of bugs through and want to make it right with anyone who is impacted.
Users of boards obtained before Sept 2023 who want to upgrade their EFM8 firmware from version 1.1 to version 1.2 have the following options.
- Send the board back to me and I'll re-flash the firmware and return the board to you. You pay shipping to me and I'll pay to ship the board back to you.
- Use a Simplicity Labs USB Debug Adapter and the programming software in their Simplicity Studio to load the hex file from the repository onto the EFM8 by connecting three wires from the Debug Adapter to the board.
- Use a 3.3V 32-bit Arduino board (PJRC Teensy 3/4, ESP32 or RP2040) as a programmer with a sketch in the repository to re-flash the EFM8 by connecting three wires from the Arduino to the board.
Detailed instructions are in the repo too.
The EFM8 is programmed via a C2 interface which consists of a clock, data and common ground. These are accessible on gCore via a set of testpoints.
It's easy to use three common "Dupont" style connector wires to connect the Arduino or USB Debug Adapter to gCore without soldering.
Then after compiling and loading the sketch the firmware can be updated in just a few seconds by typing the character 'P' in the Serial Monitor and hitting return (or clicking Send).