September 24, 2026
Part 2 of 3. In Part 1 I covered why I forked the project and how the firmware puts protection first. This part covers the hardware change, the AC readings and the memory squeeze.
Most of this fork is firmware, but there is one place where the main board itself needed work. On the stock through-hole 2.6 main boards I built with the addon fitted, starting the C64 was unreliable. From a cold start, especially after sitting overnight, I would often get a black screen. If I turned the power off and tried again, sometimes several times, the display would eventually come up. I saw this on both main boards I built, which were the only two I ever tried the addon with. When it failed, the OLED showed 5.15 V, 0.00A and no fault, and the C64 screen stayed black. The 5 V line read 5.1 V at the connector with nothing attached, but once the C64's switch was on, only about 0.5 V reached the C64's own 5 V rail. Another power supply with no C64 Saver in line worked fine, and so did the same main C64 Saver board without the addon board installed, although possibly only marginally.
The measurements pointed at the MOSFET Q2, the IRFR5305 that switches the 5 V line. Its gate to source voltage was only about -2.7 V, and it was dropping 4.6 V from source to drain, so it was barely turned on. On the main board, Q1 (a BC547) pulls a node low to switch Q2 on, and Q2's gate follows that node. With the addon fitted, that node sat at 2.4 V. With the addon removed, it sat at 0.26 V.
My reading of the schematic is that the addon's U5 optocoupler has its LED connected to that same node, which adds roughly 7 mA of load for Q1 to sink. Q1 only gets about 0.02 mA of base current from the 33k/10k network, so the BC547A I had installed which has a gain of 176 on my tester, could not pull the node down far enough. I have not verified the circuit beyond these measurements, so treat the explanation as my best reading. The measurements themselves are solid.
Two changes fixed it on my board. The R5 change came first, on the first board I worked on, and seemed to cure the problem. Later measurements showed that Q1's gain matters too, so R5 alone may only have pushed that board into a marginal state in which it happened to start. The numbers below are with both changes made:
R5: 3.3k to 100k. This removes R5's current from Q1's load.
Q1: BC547A to BC547C. With the new higher-gain transistor fitted (with a gain of 500 hfE on my tester) and R5 at 100k, the node dropped from 2.4 V to 0.25 V and Q2's gate-to-source voltage improved to -4.89 V.
After these changes the C64 started reliably each time, including from an overnight cold start. At the BASIC prompt the input-to-output drop is 0.19 V (5.275 V in, 5.09 V on 5V_SAFE). A scope capture of the switch-on shows a single 2 microsecond blip of 5.44 V at the input, far too brief for the firmware to register as an overvoltage.
One trade-off to know about: R5 is also what pulls the gate up when the board's own hardware overvoltage circuit trips. Without the addon, R5 at 100k would make the gate rise much more slowly than at 3.3k (I estimate roughly ten times, which I did not measure). With the addon fitted, the LED path through R3 supplies most of the pull-up, so R5 barely matters, and lowering it further would gain only an estimated 35 microseconds. I left it at 100k. The firmware's own cut-off pulls the gate directly through the optocoupler and is not affected.
I measured the hardware cut-off with a scope, with the BC547C and R5 at 100k. It trips at about 5.50 V, and once the gate takes off, the output collapses within about 12 to 17 microseconds. On a slow ramp the output peaked at 5.52 V. With edges of about 3 V per millisecond it peaked at 5.54 V, and with edges of about 25 V per millisecond (no series resistance) at 5.58 V. A transient to 5.54 V lasting about 10 microseconds did not trip it, so the circuit needs at least that long to respond. I haven't tested faster edges. Extending a straight line through my three points suggests something like 5.8 to 5.9 V at about 100 V per millisecond, which is an estimate, not a measurement.
I have only tried this on a through-hole 2.6 board. The SMD 2.6 schematic uses a BC847, the surface-mount equivalent of the BC547, in the Q1 position, with the same 3k3 for R5, so it may have the same problem and respond to the same fix, but I have not tested one. (The BOM file in the SMD folder still lists a 2N7002 from an earlier version.)
While using the original C64 Saver I noticed that the amps reported on the 9VAC rail didn't make sense to me. Looking through the code I found that the raw, unscaled peak-to-peak ADC counts from the sensor were shown as a bare number (the code appends an "A", but a formatting bug cuts it off), and the scaling needed to turn those counts into real amps was missing. Once I adjusted the math, the output was seriously skewed, showing a power draw of 1.4A with nothing connected.
It turned out the readings were caused by a high AC ripple on the rail supplying the 5VDC regulator on the addon board. In the original design there is a half-wave rectifier diode (D1) on the 9VAC rail, followed by a 3 x 47uF capacitor bank, before the 5VDC regulator. In my testing the capacitor bank was struggling to smooth the large ripple from the half-wave rectification, causing a false reading for the AC sensor.
I soldered a 1000uF capacitor in parallel with the original bank, which dropped the ripple to just under 150mV and allowed better measurement of the AC current. The placement works in a pinch, but the board should be redesigned to house a bit more capacitance. All I had on hand was a 35V capacitor, which is tight for the space. A 25V part would also be fine and would likely fit better. I wouldn't go lower than that: with my transformer (11.8 V RMS at idle) the rail measured 15 V DC at idle, so a 16 V part would have almost no margin. At this point my fork has not updated the addon PCB.
One side effect worth knowing about: more capacitance on that rail means the microcontroller's supply takes longer to drain when the 9VAC is removed. That's one reason I also recommend enabling the chip's brown-out detection, covered in the programming section in Part 3.
Even with the ripple fixed, scaling the original reading into amps wasn't enough. The original method takes the highest sample minus the lowest in a short window, which is fine for a sine wave but not for the narrow current pulses a rectifier and capacitor draw. Once the counts were scaled into amps, the reading with the C64 at the BASIC prompt bounced between about 0.35 and 0.6 A while my true-RMS meter said 0.638 A. The fork replaces it with a true RMS calculation: the ADC interrupt adds up every sample and its square over a 256-sample window, and the display takes the variance, removes the sensor's noise floor, takes the square root, scales it and smooths it lightly. Idle now reads 0.00 A, and at the BASIC prompt the display settles at 0.62 to 0.64 A against the meter's 0.638 A. It takes a few seconds to settle after a change in load. The calculation isn't free: the 32-bit multiplies and the square root cost a few hundred bytes of flash, which is a large share of what was left.
There are two caveats, and the key one comes first: the scale is a fixed value in the file board.h (ACCUR_SCALE_NUM) that I set by comparing against the meter just once, so I haven't checked it at any other load. The free flash memory in the v1.00 build doesn't leave room for a settings screen to adjust this value, though that may be considered in the future. For reference, the datasheet value for the ACS712-5A would have read about 28% low on my board. The AC readings are for display only, and nothing that protects the device uses them in the current hardware design.
The AC volts reading got a smaller fix. It is still a peak-to-peak value scaled as a sine, but it is now smoothed with a running average. At idle it used to wander over about 0.3 V while a true-RMS meter held within 0.02 V; now it spans about 0.08 V (11.94 to 12.02 V against 11.89 V on the meter). I didn't switch it to true RMS, because over a window that isn't a whole number of mains cycles the RMS would wobble by a couple of percent, and it likely wouldn't fit in the flash that's left.
The ATtiny85 gives you just 8 KB of flash and a mere 512 bytes of SRAM. If you aren't careful, standard C libraries will eat your storage alive before you finish writing your primary logic, and if we want to add features we need to make things as lean as we can.
Bringing in the standard formatted-printing library just to render a few numbers on an OLED is expensive. I replaced snprintf with a small integer divide-and-modulo routine (formatNumber()). I didn't measure the saving on its own, but it made room for the new features: the original builds to 7,713 B of flash, and the fork, with all the protection logic, the settings screens and the extra features, comes to 8,147 B, including the true-RMS current reading described above. Sizes are avr-size text plus data, built with avr-gcc 15.1.
On an AVR, a plain string literal is copied into RAM at start-up. All the menu titles and screen text sat in RAM, which is a large share of a 512-byte budget. Everything fixed now sits in flash (PROGMEM) and is expanded into a small buffer only while it's being drawn. Initialized RAM dropped from 133 bytes to 13, leaving roughly 424 bytes for the stack instead of about 337.
The fault flags (overcurrent, overvoltage, sensor) share a single byte in fault_status_t. That saves a little RAM compared with the original's separate flag variables, but the bigger benefit is that the code can treat them as one value. "Is any fault active?" is a single comparison (system_faults.all != 0), which the output control, the screen wake-up, the latch timer and the recovery checks all use, and clearing every fault is a single assignment. A new fault type is just another bit (up to eight fit in the byte), and all of those checks cover it without further changes.
I replaced eeprom_write_byte with eeprom_update_byte. This stops the EEPROM being needlessly rewritten, and slowly worn out, when you save a setting that hasn't changed.
One warning for anyone building from source: the program only fits in flash with link-time optimization and dead-code removal switched on. Those flags are in the Makefile and are not optional (see the build section in Part 3).
Next, in Part 3: the new screens and settings, known limitations, how I tested it, and how to build and program it.