A compact ATtiny85-based circuit that adds intelligent power management — push-on/hold-off control, graceful shutdown, USB detection, battery charging and zero-idle-current sleep — to any Li-Po/Li-Ion powered project.
From Idea To Realization
Many popular development boards — ESP32, ESP8266, Arduino-compatible modules and the like — are excellent for rapid prototyping and finished products alike. However, they typically offer little or no built-in battery management beyond a simple on/off switch. The result is a familiar set of frustrations: the board cannot tell the difference between “connected to a USB programmer” and “running on battery”, there is no polite way to ask it to save its state before the power disappears, and the quiescent current of a plain MOSFET switch still drains the cell overnight.
This project addresses all of those issues with a small add-on board built around a single ATtiny85 microcontroller. The ATtiny85 consumes only a few microamps in deep sleep, costs next to nothing, and yet is powerful enough to implement a complete power-management state machine in a few hundred lines of Arduino-compatible C++.
The design is deliberately generic: it works as a development aid (e.g. protecting the battery when you plug in the USB cable) and as a permanent fixture in finished products (providing a clean user interface and graceful shutdown).
Features At Glance
Automatic battery disconnection when USB VBUS is detected — no current drawn from the cell while programming or charging.
Push-on / hold-off button interface — a short press turns the load on; holding the button for two seconds initiates a graceful shutdown.
Configurable shutdown grace period (default five seconds) with a blinking warning signal so the load firmware can save or send data, close files or display a goodbye screen before power is cut.
Load isolation during battery charging — the charger module feeds the battery directly without the load interfering.
Deep-sleep mode with zero load current — in the off state the ATtiny85 enters PWR_DOWN sleep; measured system current is in the sub-microamps range.
The Circuit
As can be seen in the schematic in Figure 1, the circuit is built around the microcontroller U1, a Microchip ATtiny85 [1] which manages the user interface, the load switch and the USB VBUS detection. C1 and C2 are the usual decoupling capacitors for the microcontroller.
Figure 1: The schematic diagram
A standard USB-C Li-Po/Li-Ion Protected Charger Module (M1) accepts power from a USB-C connector and charges the 3.7 V cell connected at JP1. The battery positive rail is labeled VBAT throughout the schematic.
The P-channel MOSFET Q1 (FDN306P) is a purely hardware protection device requiring no firmware intervention. Its source is connected to VBAT. Under normal battery-only operation, the gate is pulled to GND through R3 (10 kΩ), making Vgs sufficiently negative to keep Q1 fully on. When the charger is active, its input voltage (approximately 5 V) is applied to the gate through R1 (1 kΩ), making the gate potential higher and positive compared to the source potential. Q1 turns off, isolating the load from the charging path. The transition is entirely automatic, governed solely by the charger input voltage.
The actual user-controlled load switch is the solid-state relay K1 (G3VM-41AY1) [2]. The ATtiny85 drives the internal LED of K1 through MOSFET_RELAY_PIN (PB2): When the MCU asserts this pin, K1's internal MOSFET pair conducts and connects VBAT to the VBUS output rail that feeds the load via JP2. R6 (1.5 kΩ) limits the LED current.
USB VBUS presence is detected by the ATtiny85's built-in analog comparator. The VBUS rail (5 V when USB is connected) is applied through R4 (3.3 kΩ) to the positive comparator input AIN0+ (PB0, pin 5). R9 (330 kΩ) keeps this input low in the absence of VBAT and VBUS. Typically, USB VBUS is always greater than VBAT, so D1, a Schottky diode, protects PB0 from excessive overvoltage (Microchip specifies a maximum permissible voltage of VDD +0.5V on the inputs in the datasheet) [3].
The battery voltage VBAT is applied to the negative input AIN1− (PB1, pin 6) through the internal pull-up resistor, enabled via firmware. When USB is plugged in, VBUS exceeds the VBAT, the comparator output bit ACO goes high, and the firmware immediately opens K1 to disconnect the load.
It is important to note that due to K1's very low Ron and the limited current involved, the value of VBAT will tend to equal VBUS at the moment of USB connection. This will effectively prevent the comparator from activating and the relay from opening. It will also cause an unwanted current to flow to the battery. The addition of C4 achieves the effect of delaying the increase in voltage on PB1, giving the comparator time to trigger.
SW1 is a momentary push-button connected between PB4 (pin 3 of the ATtiny85) and GND, with a 100 nF capacitor C3 for hardware debouncing. The ATtiny85 enables the internal pull-up on PB4, so the pin reads HIGH at rest and LOW when the button is pressed. A pin-change interrupt wakes the MCU from deep sleep.
JP2 is a three-pin header that exposes VCC, the Power-Off Signal, and GND to the target board. The Power-Off Signal is an open-drain output driven by Q2 (2N7002 N-channel MOSFET). R8 and R2 (330 Ω) are overcurrent protection resistors.
The Power-Off Signal is active-low and the shutdown delay can be adjusted by changing POWER_OFF_DELAY_MS in the firmware. D2 is an indicator LED that remains lit when the load is powered by VBAT and flashes during the power-off sequence.
JP3 is a standard 6-pin ICSP header, allowing the ATtiny85 to be programmed in-circuit with any ISP-compatible programmer.
Prototype Construction
The prototype was built on a small rectangle of perf-board, as shown in Figure 2. Through-hole components were used predominantly. The FDN306P MOSFET, available only in SMD packages, was chosen for its particularly low RDS(on) (on-state drain-source resistance) of 50 mΩ at VGS = –2.5 V, thereby minimizing voltage drop. Its current handling capability exceeds 2 A, which is more than adequate for the intended purpose.
The 2N7002 MOSFET, the BAT46W diode, and the MOSFET relay are also SMD devices, although the latter is available in a through-hole version as well. The microcontroller is mounted in a socket, while the battery is connected via a standard JST-PH connector. The charging module has been soldered directly onto the board.
Figure 2: The prototype board, component side
It is important that the traces carrying the supply current to the connected board be kept short and of sufficient cross-sectional area (as can be seen in Figure 3), in order to minimize series resistance, voltage drop, and therefore power dissipation — a critical factor in battery-powered applications. The MOSFET relay used is rated for a typical Ron of 90 mΩ at a continuous load current of 2000 mA. On the prototype, a voltage drop of 50 mV was measured at VBAT = 3.7 V and 300 mA of current, from the battery connector to the GND and VCC OUT terminal of JP2.
Figure 3: The prototype board, solder side The Firmware
I wrote the firmware using Arduino IDE 1.8.19 with ATTinyCore 1.5.2 by SpenceKonde [4], available from the Board Manager. No additional libraries are required. The framework targets the ATtiny85 at 1 MHz (internal oscillator). The low clock speed is intentional: it reduces active current consumption and is more than sufficient for a power-management task that spends almost all of its time asleep.
The sketch file Smart_Battery_Power_Controller.ino is available for download, and contains Arduino IDE configuration instructions, code comments, and further useful information and links. As mentioned, the firmware is flashed using the ICSP interface via a common USBasp programmer. I have also made the relative .HEX file available for completeness (Smart_Battery_Power_Controller.hex).
The code follows the classic Arduino sketch structure. It begins with including necessary libraries, pin and constant definitions, state machine enumerations, and declaring global variables. This is followed by the setup() routine, where pins are configured, Pin-Change Interrupt enabled and ADC entirely disabled.
The firmware implements a two-state machine: POWER_OFF and RUNNING. In POWER_OFF the solid-state relay K1 is open (load disconnected) and the ATtiny85 is in PWR_DOWN sleep, drawing only a few hundred nanoamps. A pin-change interrupt on the button pin wakes the MCU. In RUNNING K1 is closed and the main loop continuously polls the analog comparator and the button state.
Rather than using the ADC (which would require a voltage reference and additional code), the firmware uses the ATtiny85's built-in analog comparator directly [5]. The ADC peripheral is disabled entirely in setup() to save power. The comparator result is available at any time by reading the ACO bit in the ACSR register — a single instruction with no conversion delay — through the readCompStatus( ) function that polls the analog comparator for USB VBUS presence.
The function checkStatus() evaluates the button state and manages power sequencing. When the button is held for BTN_HOLD_MS (2000 ms by default), the firmware sets the startPowerOff flag and enters a blocking countdown loop of POWER_OFF_DELAY_MS (5000 ms by default). During this period the OFF_SIGNAL pin blinks at approximately 1 Hz (200 ms off, 800 ms on) to alert the load firmware. If USB is inserted during the countdown, the loop exits immediately and load power is cut without waiting for the timer to expire. If the countdown completes normally, the solid-state relay is turned off and the ATtiny85 enters deep sleep.
The power_off() function disables the ADC, configures PWR_DOWN sleep mode, and calls sleep_cpu(). In this state the ATtiny85 consumes less than 1 µA at room temperature. Because K1 is also open, the only current path from the battery is through the ATtiny85 itself and the quiescent current of the charger module — typically 1 µA total.
The most interesting function is probably checkStatus(), which implements the push-on/old-off button logic: A single press (any duration) while OFF turns the system ON immediately. Holding the button for BTN_HOLD_MS while ON triggers the power-off sequence:
Sets status to POWER_OFF and raises the startPowerOff flag.
OFF_SIGNAL_PIN starts blinking (in readCompStatus()) as a pre-warning.
After POWER_OFF_DELAY_MS the MOSFET relay is opened and the MCU sleeps.
Let's then see the fundamental operations performed within this routine by examining the commented code:
The complete code consists of several hundred lines, and I advise programming enthusiasts or those interested in making modifications to examine it by opening it in the Arduino Editor. I tried to use self-explanatory names for functions and variables as much as possible.
Customization and Adaptation
Two #define constants at the top of the sketch control the timing behavior and are the most likely parameters to adjust for a specific application:
BTN_HOLD_MS — How long the button must be held to initiate shutdown (default 2000 ms). Increase this to reduce the chance of accidental power-off.
POWER_OFF_DELAY_MS — The grace period given to the load (default 5000 ms). Set this to match the worst-case save/shutdown time of your application firmware.
The load firmware should monitor the Power-Off Signal pin (JP2 pin 2). When this pin goes low and starts blinking, the load has POWER_OFF_DELAY_MS milliseconds to complete its housekeeping before power is cut unconditionally.
If longer grace periods or more complex signalling protocols are needed, the firmware can be extended without hardware changes.
Semiconductors U1 = ATtiny85 Q1= FDN306P P-channel MOSFET Q2 = 2N7002 N-Channel MOSFET K1 = G3VM-41AY1 MOSFET Relay D1 = BAT46W Skottky diode D2= LED 3mm blue Miscellaneous SW1 = Normally open button M1 = Charger module TP4057 with protection Final Thought
This small add-on board transforms any Li-Po or Li-Ion powered development board into a well-behaved battery-operated device. With just over a dozen components and a single eight-pin MCU, you get automatic USB detection, a professional push-on/hold-off interface, a configurable shutdown grace period, and a true zero-current off state — all without modifying the target board's firmware beyond adding a few lines to monitor the Power-Off Signal pin. The design is a practical example of how even the smallest AVR microcontrollers can handle real-world power-management tasks that would otherwise require a complex discrete circuitry.
Möchten Sie einen Kommentar mit Ihrer Bewertung hinterlassen? Bitte melden Sie sich unten an. Nicht gewünscht? Dann schließen Sie einfach dieses Fenster.
Diskussion (0 Kommentare)