Modern C++ Framework for Arduino AVR.
Beginner-friendly on the surface. Register-level underneath.
ArduLite is a lightweight, header-driven C++ framework for bare-metal AVR programming on the Arduino Uno, Nano, and Mega 2560. No pinMode, no digitalWrite, no hidden runtime — every call is designed to fold away at compile time into plain register access. New to microcontrollers? Start with Quick Start — you don't need to understand registers to use it.
01 · Design Philosophy
Why ArduLite exists
The Arduino Core is friendly, but its abstractions — digitalWrite's pin lookup table, String's heap churn, delay()'s Timer0 dependency — cost cycles and bytes you don't always get to see. ArduLite is what you get if you rebuild the beginner-friendly vocabulary on nothing but avr-libc, and refuse anything that isn't zero-cost.
Goals
| Goal | How ArduLite delivers it |
|---|---|
| Zero heap | new, malloc, std::vector, std::string are all off the table. Every object lives on the stack or in static storage with a size known at compile time. |
| Zero collision | ArduLite never defines pinMode, digitalWrite, delay, millis, HIGH/LOW, or byte. It can be mixed into a project that still uses the Arduino Core without a single symbol clash. |
| Zero overhead | Pin-facing methods are marked always_inline. With a literal pin number, the whole lookup folds away at compile time into one sbi/cbi/sbic instruction — see the strip above. |
| Opt-in everything | The master header only pulls in Digital, Analog, PWM and Utils. Timers, UART, SPI, I2C, interrupts and every component are separate headers you include only when you use them, so an unused peripheral is never initialized and never costs flash. |
Explicit non-goals
ArduLite 1.x/2.x is scoped deliberately narrow. It does not attempt to support ESP8266, ESP32, STM32 or RP2040, and it does not ship WiFi, Bluetooth, Ethernet, MQTT, HTTP, OTA, or an RTOS. Those belong to separate projects built for those chips — bolting them onto an AVR-only, no-heap framework would break every guarantee above.
avr/io.h by hand every time.
02 · Setup
Installation
Arduino IDE — via Library Manager (easiest)
- Open the Arduino IDE, then go to Sketch → Include Library → Manage Libraries… (or press
Ctrl+Shift+I). - Search for ArduLite in the Library Manager search box.
- Click Install on the ArduLite entry by Ajang Rahmat.
- Select Arduino Uno, Arduino Nano, or Arduino Mega 2560 under Tools → Board — ArduLite only targets these three.
- Include the master header in your sketch:
#include <ArduLite.h>.
Arduino IDE — manual install
- Download the latest release archive from github.com/ArduLite/ArduLite.
- Extract it and rename the folder to
ArduLiteif needed, then place it inside your Arduinolibrariesdirectory (Documents/Arduino/librarieson most systems). - Restart the Arduino IDE so it re-scans the libraries folder.
- Select your board and include
ArduLite.h, same as above.
PlatformIO
Add it as a library dependency from the command line — PlatformIO fetches it straight from the GitHub repository, no manual copying needed.
# from inside your PlatformIO project folder
pio pkg install --library "https://github.com/ArduLite/ArduLite.git"
# or, for an older PlatformIO CLI version:
pio lib install "https://github.com/ArduLite/ArduLite.git"
Or add it directly to platformio.ini and let PlatformIO install it on the next build:
[env:uno]
platform = atmelavr
board = uno
framework = arduino
lib_deps =
https://github.com/ArduLite/ArduLite.git
; ArduLite does not need the Arduino framework at runtime, but PlatformIO's
; "arduino" framework is the simplest way to get an avr-gcc toolchain wired up.
Then run pio run to build, or pio run -t upload to build and flash.
Arduino.h required. ArduLite sketches use a plain int main() entry point instead of setup()/loop(). If you're on the Arduino IDE, this still compiles fine — the IDE just needs one .ino file with a main() in it, which every ArduLite example provides.
03 · First Sketch
Quick Start
Three progressively richer examples: a blink, a PWM fade, and a sensor-driven brightness loop — the same three examples from the project README, shown here with a line-by-line breakdown.
1. Blink
#include <ArduLite.h>
int main() {
Digital led(13, OUT);
forever() {
led.on();
wait(1000);
led.off();
wait(1000);
}
}
Digital led(13, OUT) configures pin 13 as an output once, at construction. forever() expands to while(1). wait(1000) blocks for 1000 ms using a calibrated _delay_loop_2 — no Timer0, no millis() dependency.
2. PWM fade
#include <ArduLite.h>
int main() {
PWM led(9);
forever() {
for (uint8_t i = 0; i < 255; i++) { led.write(i); wait(5); }
for (uint8_t i = 255; i > 0; i--) { led.write(i); wait(5); }
}
}
3. Sensor-driven brightness
#include <ArduLite.h>
int main() {
PWM led(9);
Analog sensor(A0);
forever() {
int sensorValue = sensor.read();
float brightness = sensorValue * (255.0 / 1023.0);
led.write(brightness);
}
}
All three sketches are in examples/, importable directly from File → Examples → ArduLite in the Arduino IDE.
04 · Core Concepts
The zero-overhead model
Two techniques make this possible almost everywhere in ArduLite:
1. always_inline + compile-time constant pins
Every pin-facing helper (digitalWriteHigh, digitalTogglePin, ...) is marked __attribute__((always_inline)). Without it, GCC would leave the call as a real function at -Os, and the pin number would stop being a compile-time constant — forcing a runtime bit-shift to compute (1 << pin). With the attribute, and a literal pin like Digital led(13, OUT), the entire register lookup folds away and one method call becomes one AVR instruction.
2. Hardware toggle instead of read-modify-write
toggle() exploits a real AVR feature: writing a logic 1 to a bit in the PINx register flips that bit of PORTx, leaving every other bit untouched — 2 cycles, no read-modify-write race. Older chips without this feature (ATmega8/16/32) automatically fall back to PORTx ^= mask.
| ArduLite call | Compiles to (ATmega328P, literal pin) |
|---|---|
| Digital led(13, OUT) | sbi 0x04, 5 |
| led.on() | sbi 0x05, 5 |
| led.off() | cbi 0x05, 5 |
| led.read() | sbic 0x03, 5 |
| led.toggle() | out 0x03, r24 (PINB write-1-to-toggle) |
constexpr. Passing a runtime variable as the pin still works correctly, but the compiler can no longer fold the branch chain, so you pay for the port lookup at runtime — the same trade-off you'd hit writing the register logic by hand.
Timer & resource map
ArduLite is careful never to let two modules fight over the same hardware timer. Know this map before combining modules in one sketch.
| Resource | Owner | Notes |
|---|---|---|
| Timer0 | Untouched | Reserved for the Arduino Core's own millis()/delay() if you ever mix them in — ArduLite never touches it. |
| Timer1 | PWM (pins 9/10 Uno), Servo, Pulse | Do not use Servo, Pulse, and PWM on pin 9/10 at the same time — they all reconfigure Timer1. |
| Timer2 — COMPA | Time / Timer / Stopwatch / Button / Interrupt debounce | The single 1 ms tick source (initTimer()), shared safely by every module that needs a clock. |
| Timer2 — COMPB | createTimer() (TimerInterrupt) | Only enabled when initTimerInterrupt()/createTimer() is actually called — a sketch that never uses it pays nothing. |
| ADC | Analog | Initialized lazily on first read() call, AVcc reference, default prescaler 64. |
| TWI/I2C hardware | i2c global (ArduLiteI2C) | Bit-rate register fixed at construction time (TWBR=72) — roughly 100 kHz at 16 MHz. |
| SPI hardware | spi global (ArduLiteSPI) | Master mode only. The SS/CS pin is not managed automatically — pick it yourself via Digital. |
| UART hardware | uart / uart1..3 globals | Mega 2560 exposes all four hardware UARTs; Uno/Nano only uart (UART0). |
| EEPROM | eeprom global | 1024 bytes on ATmega328P, 4096 bytes on ATmega2560. |
Core header — ArduLite.h
The master header is intentionally small. It defines short integer aliases, the forever() macro, and the two blocking-wait functions, then pulls in only Digital, Analog, PWM and Utils. Everything else is opt-in.
| Symbol | What it is |
|---|---|
| int8 / int16 / int32 uint8 / uint16 / uint32 | Aliases for int8_t … uint32_t, in ArduLite's own naming style. |
| forever() { } | Macro for while(1) { } — the main program loop, used instead of Arduino's loop() callback. |
| wait(uint32_t ms) | Blocking millisecond delay. A real inline function, deliberately not a macro — a macro would rewrite every wait(...) substring across the whole program, including unrelated method names. |
| waitMicros(uint16_t us) | Blocking microsecond delay, built on _delay_loop_2 (4 CPU cycles/iteration). One call covers up to ~16383 µs at 16 MHz. |
wait()/waitMicros() never touch a timer — they're busy-loops calibrated purely from F_CPU. That means they can be called safely even before initTimer() has run, and they never collide with ArduLiteTime's Timer2-based clock.
05 · GPIO Reference
Digital
src/ArduLiteDigital.h
Digital pin control with true zero-cost abstraction. Holds a single byte of RAM (the pin number) and nothing else — every method is always_inline.
| Constructor / Method | Description |
|---|---|
| Digital(pin, dir) | Configures pin as IN or OUT. Direction is applied once, immediately. |
| on() | Drives the pin HIGH. |
| off() | Drives the pin LOW. |
| write(bool value) | HIGH when true, LOW when false. |
| read() const | Returns the current logic level as bool. |
| toggle() | Flips the pin — uses the hardware PINx-toggle trick where available. |
| pullUp() | Enables the internal pull-up resistor. Only meaningful on an IN pin. |
| number() const | Returns the stored pin number. |
#include <ArduLite.h>
int main() {
Digital button(2, IN);
Digital led(13, OUT);
button.pullUp(); // button wired to GND, no external resistor needed
forever() {
if (!button.read()) { // active-low: pressed == LOW
led.toggle();
wait(300); // crude debounce
}
}
}
Direction { IN, OUT } — ArduLite's own vocabulary, never Arduino's INPUT/OUTPUT, so both can coexist in one project.
Analog
src/ArduLiteAnalog.h / .cpp
10-bit ADC reads through the AVR's built-in analog-to-digital converter. Arduino-style A0–A15 pin constants are defined automatically if not already present.
| Constructor / Method | Description |
|---|---|
| Analog(pin) | Binds to an analog pin (e.g. A0). The ADC hardware itself is initialized lazily, on the first read() call across the whole program. |
| read() | Blocking single conversion. Returns a 10-bit value, 0–1023. |
| setPrescaler(uint8_t factor) | Sets the ADC clock prescaler: 2, 4, 8, 16, 32, 64, or 128. Default is 64 (a safe, accurate speed at 16 MHz). Lower prescalers read faster but lose precision. |
Analog sensor(A0);
sensor.setPrescaler(16); // faster reads, slightly less accurate
int raw = sensor.read(); // 0 - 1023
PWM
src/ArduLitePWM.h / .cpp
Hardware PWM output using each chip's fast-PWM timer channels directly (Timer0/1/2/3/4 depending on pin and board) — no analogWrite(), no software bit-banging.
| Constructor / Method | Description |
|---|---|
| PWM(pin) | Attaches to a PWM-capable pin and configures the matching timer in fast-PWM mode, non-inverted. Pins that aren't PWM-capable on the target board are silently ignored (no-op). |
| write(uint8_t value) | Duty cycle 0-255. 0 disables PWM output entirely and forces the pin LOW (rather than leaving a 0%-duty PWM signal running). |
PWM led(9);
led.write(128); // ~50% brightness
led.write(0); // off, pin forced LOW
Pulse
src/ArduLitePulse.h
Measures pulse width in microseconds via Timer1, equivalent to Arduino's pulseIn() but implemented directly on the register.
| Constructor / Method | Description |
|---|---|
| Pulse(pin, mode=IN, timeoutUs=30000) | Binds to a pin (D0-D19 range) with a busy-wait timeout in microseconds. |
| read() | Waits for a full HIGH pulse and returns its duration in microseconds. Returns 0 on timeout. |
| readLow() | Same, but measures a LOW pulse instead. |
| setTimeout(uint32_t timeoutUs) | Changes the timeout without recreating the object. |
Pulse input(7);
forever() {
unsigned long width = input.read(); // microseconds, 0 = timeout
}
PWM (pins 9/10) and Servo — don't combine them in the same sketch.06 · Timing Reference
Time, Timer & Stopwatch
src/ArduLiteTime.h
ArduLite never defines millis(), micros() or delay() as free functions — that vocabulary lives inside objects (time.millis()) so it can never collide with the Arduino Core. Timer2 is the single 1 ms tick source shared by everything in this file, plus Button and Interrupt debounce.
Time — reads the running clock
Every method is static and the class holds no data — time costs no RAM and adds no call overhead beyond the read itself.
| Method | Description |
|---|---|
| time.millis() | Milliseconds since the timer started. Wraps to 0 after ~49 days. |
| time.micros() | Microseconds since start, 4 µs resolution @16 MHz. Wraps after ~71 minutes. |
| time.seconds() | Whole seconds since start. |
| time.now() | Alias for millis(). |
| Time::wait(uint32_t ms) | Blocking wait based on the Timer2 clock rather than a busy-loop — stays accurate even when ms comes from a variable. |
| Time::waitMicros(uint16_t us) | Blocking microsecond wait, same underlying primitive as the core header's waitMicros(). |
Timer — non-blocking periodic task
9 bytes of RAM per instance. No interrupt, no callback — just a polled check you call from inside forever().
| Constructor / Method | Description |
|---|---|
| Timer(uint32_t intervalMs) | Starts counting immediately from construction. |
| ready() | Returns true exactly once each time the interval elapses. Advances by one interval rather than snapping to "now" — the rhythm doesn't drift, but resynchronizes automatically if it falls far behind. |
| reset() | Restarts counting from the current time. |
| setInterval(ms) | Changes the interval and restarts counting. |
| start() / stop() | Pauses/resumes. ready() always returns false while stopped. |
| isRunning() const | Current run state. |
| remaining() const | Milliseconds left before the next ready() fires true. |
#include <ArduLite.h>
#include <ArduLiteTime.h>
#include <ArduLiteUART.h>
int main() {
Digital led(13, OUT);
Timer blink(250); // fast task
Timer report(1000); // slow task, runs independently
uart.begin(9600);
uint16_t blinkCount = 0;
forever() {
if (blink.ready()) { led.toggle(); blinkCount++; }
if (report.ready()) {
uart.send("Uptime "); uart.send(time.seconds());
uart.send(" seconds, blinks: "); uart.sendLine(blinkCount);
}
}
}
Stopwatch — measures a duration
| Method | Description |
|---|---|
| start() | Starts, or resumes after stop(). |
| stop() | Pauses. The accumulated duration is kept. |
| reset() | Back to zero, stopped. |
| restart() | reset() then start() in one call. |
| elapsed() const | Total duration in milliseconds. |
| elapsedSeconds() const | Total duration in whole seconds. |
| isRunning() const | Current run state. |
Stopwatch watch;
watch.start();
// ... work ...
watch.stop();
uart.sendLine(watch.elapsed()); // duration in ms
07 · Components Reference
LED
src/ArduLiteLED.h
A semantic wrapper around Digital for LEDs, with common-anode (active-low) support.
| Constructor / Method | Description |
|---|---|
| LED(pin, activeLow=false) | Set activeLow=true for common-anode LEDs that light up when the pin is driven LOW. |
| on() / off() / toggle() | Standard state control, already accounting for activeLow. |
| write(bool on) | Explicit on/off. |
| isOn() const | Current logical state (not raw pin level). |
| blink(uint16_t delayMs, uint16_t count=1) | Blocking blink, count times, delayMs on and off each cycle. |
LED led(13);
led.blink(500, 5); // blink 5 times, 500 ms on/off
Relay
src/ArduLiteRelay.h
Controls a relay module, defaulting to active-low (the common wiring on cheap relay boards, where a LOW signal energizes the coil).
| Constructor / Method | Description |
|---|---|
| Relay(pin, activeLow=true) | Set activeLow=false for boards where HIGH energizes the relay. |
| on() / off() / toggle() | State control, accounting for activeLow. |
| write(bool on) | Explicit on/off. |
| isOn() const | Current logical state. |
Relay lamp(7); // active-low module, the common case
lamp.on();
wait(1000);
lamp.off();
Buzzer
src/ArduLiteBuzzer.h
Drives an active buzzer — one that sounds continuously when given HIGH, as opposed to a passive buzzer that needs a driven frequency.
| Constructor / Method | Description |
|---|---|
| Buzzer(pin) | Starts silent. |
| on() / off() / toggle() | Direct state control. |
| beep(uint16_t ms) | Blocking beep for ms milliseconds, then turns off. |
Buzzer buz(8);
buz.beep(200); // sounds for 200 ms
Servo
src/ArduLiteServo.h
Generates a standard 50 Hz hobby-servo signal (1.0–2.0 ms pulse) via Timer1's Fast PWM mode 14, with zero extra interrupts and zero heap.
| Method | Description |
|---|---|
| Servo() | Constructs unattached — no timer activity yet. |
| attach(pin) | Binds to a supported OC1 pin and starts the 50 Hz frame. Silently does nothing on an unsupported pin. Moves to the middle position automatically. |
| detach() | Releases the OC1 output, freeing Timer1 for other modules. |
| isAttached() const | Current attach state. |
| write(uint8_t angle) | Position, 0-180 degrees. |
| writeMicroseconds(uint16_t us) | Direct pulse-width control, clamped to [usMin, usMax]. |
| setPulseRange(minUs, maxUs) | Recalibrates the 0°/180° endpoints (default 1000/2000 µs) for servos that don't follow the standard range. |
Servo servo;
servo.attach(9);
forever() {
for (uint8_t ang = 0; ang <= 180; ang++) { servo.write(ang); wait(15); }
for (uint8_t ang = 180; ang > 0; ang--) { servo.write(ang); wait(15); }
}
PWM (pins 9/10) and Pulse — don't run all three together.Ultrasonic
src/ArduLiteUltrasonic.h
Driver for the HC-SR04 distance sensor: emits a 10 µs trigger pulse, then measures how long the echo line stays HIGH. Distance follows cm = time(µs) / 58.
| Constructor / Method | Description |
|---|---|
| Ultrasonic(trigPin, echoPin, timeout=30000) | timeout is the maximum wait for the echo, in microseconds. |
| distanceCm() | Blocking measurement. Returns 0 when there is no reflection within the timeout. |
| distanceInch() | distanceCm() / 2.54. |
Ultrasonic ultra(7, 6); // trig pin 7, echo pin 6
forever() {
float cm = ultra.distanceCm();
}
08 · Communication Reference
UART
src/ArduLiteUART.h
Interrupt-driven serial with type-safe overloads for every AVR integer and float type, plus non-blocking line receive via callback. Global instances are ready to use out of the box.
uartMega 2560: + uart1, uart2, uart3| Method | Description |
|---|---|
| begin(baud, callback=nullptr) | Starts the UART at baud. If callback is given, it fires from the RX interrupt every time a full line (terminated by \r or \n) arrives. |
| send(...) | Overloaded for char, const char*, every signed/unsigned integer width, float, and bool (sent as "true"/"false" from PROGMEM). |
| send(uint32_t data, uint8_t base) | Sends a number in an arbitrary base, e.g. send(addr, 16) for hex. |
| sendLine(...) | Same overload set as send(), followed by \r\n. |
Blocking-style scan output
uart.begin(9600);
uart.sendLine("I2C Scanner ArduLite");
if (i2c.probe(addr)) {
uart.send("Device found at 0x");
uart.send(addr, 16); // address in hex
uart.sendLine("");
}
Interrupt-driven command receiver
#include <ArduLite.h>
#include <ArduLiteLED.h>
#include <ArduLiteUART.h>
LED led(13);
bool strEqual(const char *a, const char *b) {
while (*a && *a == *b) { a++; b++; }
return *a == *b;
}
void onData(const char *line) { // called from the RX ISR on every full line
if (strEqual(line, "on")) led.on();
else if (strEqual(line, "off")) led.off();
else if (strEqual(line, "toggle")) led.toggle();
}
int main() {
uart.begin(9600, onData);
forever() { } // main loop can stay empty — RX is fully interrupt-driven
}
volatile, and never call wait() or send long messages from inside it. The internal line buffer is 16 bytes.I2C
src/ArduLiteI2C.h
Direct TWI (Two-Wire Interface) hardware driver — no software bit-banging. A ready-to-use global instance i2c is provided.
| Method | Description |
|---|---|
| begin() | Configures TWI hardware at roughly 100 kHz (16 MHz clock, TWBR=72). |
| probe(addr) | Sends SLA+W and checks for an ACK. Returns true if a device answers at that address — the basis of an I2C bus scanner. |
| read(addr, reg, data*, len) | Reads len bytes starting at register reg on device addr. |
| write(addr, reg, data*, len) | Writes len bytes to register reg on device addr. |
Two free helper functions are also declared alongside i2c: bcdToDec(uint8_t) and decToBcd(uint8_t), handy when talking to RTC modules (e.g. DS1307/DS3231) that store time fields in BCD.
i2c.begin();
for (uint8_t addr = 1; addr < 128; addr++) {
if (i2c.probe(addr)) {
uart.send("Device found at 0x");
uart.send(addr, 16);
uart.sendLine("");
}
}
SPI
src/ArduLiteSPI.h
Master-mode driver on the AVR's hardware SPI registers (SPCR/SPSR/SPDR). MOSI, SCK and MISO are handled automatically; the chip-select pin is deliberately not managed by this module, so you choose it freely through Digital.
| Method | Description |
|---|---|
| begin() | Enables SPI hardware as master, Mode 0, MSB-first, clock F_CPU/16. Safe to call repeatedly. |
| end() | Disables the SPI hardware. |
| transfer(uint8_t data) | Full-duplex: sends one byte, returns the byte received at the same time. |
| transfer16(uint16_t data) | Sends/receives two bytes, high byte first. |
| setBitOrder(SPI_MSBFIRST / SPI_LSBFIRST) | Bit transmission order. |
| setDataMode(SPI_MODE0..3) | Selects clock polarity/phase. |
| setClockDivider(2..128) | Clock speed = F_CPU / divider. |
Digital cs(10, OUT); // our chosen SS/CS pin, held HIGH
cs.on();
spi.begin();
cs.off(); // select slave
uint8_t reply = spi.transfer(0x42); // send and receive at once
cs.on(); // release slave
begin() does this for you internally, in addition to whatever CS pin you drive yourself.09 · Interrupts Reference
Interrupt (external)
src/ArduLiteInterrupt.h
Wraps AVR's two true external-interrupt lines (INT0/INT1), with optional built-in debounce timed off the Timer2 tick.
| Constructor | Description |
|---|---|
| Interrupt(pin, callback, mode) | No debounce. pin must be an INT0/INT1-capable pin. |
| Interrupt(pin, callback, mode, debounceDelayMs) | Same, with a debounce window applied in the ISR itself. |
mode maps to the AVR ISCn bits: 0 = LOW level, 1 = any logical CHANGE, 2 = FALLING edge, 3 = RISING edge.
volatile uint16_t pressCount = 0;
void onPress() { pressCount++; }
int main() {
uart.begin(9600);
Interrupt button(2, onPress, 3, 50); // pin 2, RISING, 50 ms debounce
forever() {
// pressCount keeps incrementing in the background even while busy
}
}
PinChange
src/ArduLitePinChange.h
Pin Change Interrupts fire on almost any digital pin — unlike the two dedicated external-interrupt lines above. Ideal for encoders, flow sensors, and revolution counters.
| Constructor / Enum | Description |
|---|---|
| PinChange(pin, callback, mode=CHANGE) | Registers one pin and enables the matching PCICR/PCMSK bits automatically. |
| PinChangeMode::CHANGE | Fires on every level change, both directions. |
| PinChangeMode::RISING | Fires only on LOW→HIGH. |
| PinChangeMode::FALLING | Fires only on HIGH→LOW. |
volatile uint32_t pulses = 0;
void countPulse() { pulses++; }
int main() {
PinChange counter(6, countPulse, PinChangeMode::RISING);
forever() { /* pulses keeps growing via ISR */ }
}
TimerInterrupt
src/ArduLiteTimerInterrupt.h
A scheduler for periodic callbacks that keep their rhythm even while the main loop is busy — the interrupt-driven counterpart to the polling Timer class. Runs on Timer2's COMPB unit, a separate compare channel from the 1 ms tick in ArduLiteTime.h, so the two never fight over one vector.
| Function | Description |
|---|---|
| createTimer(intervalMs, callback) | Registers a periodic task, auto-calling initTimerInterrupt() on first use. Returns a task ID, or TIMER_INVALID if all slots are full. |
| setTimerEnabled(id, bool) | Enables/disables a task without removing it. |
| setTimerInterval(id, newInterval) | Changes a task's period and restarts its count. |
Slot count defaults to MAX_TIMERS = 4. Define #define MAX_TIMERS 8 before including the header to trade RAM for more slots.
Digital led(13, OUT);
void blink() { led.toggle(); }
int main() {
createTimer(500, blink); // calls blink() every 500 ms, automatically
forever() { } // free to do other work — blink keeps its rhythm
}
volatile, and never call wait() or send long UART messages from it.10 · Storage & Utilities
EEPROM
src/ArduLiteEEPROM.h
A concise wrapper around avr/eeprom.h for data that survives power loss. A ready-to-use global instance eeprom is provided.
| Method | Description |
|---|---|
| length() const | EEPROM size in bytes for the current chip. |
| readByte / writeByte(addr, value) | 1-byte read/write. writeByte always writes, even if the value is unchanged. |
| updateByte(addr, value) | Writes only when the stored value differs — the endurance-friendly choice for values that rarely change. |
| read/write/updateUInt16(addr, value) | 2-byte access, little-endian. |
| read/write/updateUInt32(addr, value) | 4-byte access, little-endian. |
const uint16_t ADDR_COUNT = 0;
int main() {
uart.begin(9600);
uint32_t boots = eeprom.readUInt32(ADDR_COUNT) + 1;
eeprom.updateUInt32(ADDR_COUNT, boots); // endurance-friendly write
uart.send("Powered on "); uart.send(boots); uart.sendLine(" times.");
forever() { }
}
Utils
src/ArduLiteUtils.h / .cpp
Bit manipulation, math, pseudo-random numbers, and low-level board-aware pin register helpers — all zero-cost, mostly constexpr.
Bit utilities
| Function | Description |
|---|---|
| bitReadFast(value, bit) | constexpr. Returns 1 or 0. |
| bitSetFast / bitClearFast / bitToggleFast(value&, bit) | In-place bit set/clear/flip. |
| bitReverse(uint8_t v) | Reverses the order of all 8 bits. |
| bitCount(uint8_t v) | Population count — number of bits set to 1. |
Math helpers (templated, constexpr)
| Function | Description |
|---|---|
| constrainFast(value, low, high) | Clamps into [low, high]. |
| scaleMap(value, inMin, inMax, outMin, outMax) | Re-scales a value between two ranges — ArduLite's equivalent of Arduino's map(). |
| minOf(a, b) / maxOf(a, b) | Generic min/max. |
Pseudo-random
| Function | Description |
|---|---|
| randomSeed(uint32_t seed) | Seeds the internal LCG state (falls back to 1 if seed==0). |
| randomInt(uint16_t max) | Pseudo-random integer in [0, max). |
| randomRange(min, max) | Pseudo-random integer in [min, max] inclusive. |
Low-level pin helpers
Free functions used internally by Digital, exposed for anyone who wants direct register access without instantiating a class.
| Function | Description |
|---|---|
| pinOutput(pin) / pinInput(pin) | Sets direction directly. |
| pinPullup(pin, enable) | Enables/disables the internal pull-up. |
| pinWrite(pin, value) / pinRead(pin) | Direct level write/read. |
| pinToggle(pin) | Hardware toggle via the PINx trick. |
| arduino_lite::pinRegs(pin) | Returns the raw PinRegs{ ddr, port, pin, mask } struct for a pin — the same lookup Digital uses internally. |
int raw = sensor.read(); // 0-1023
long angle = scaleMap(raw, 0, 1023, 0, 180); // re-scale like Arduino's map()
angle = constrainFast(angle, 0L, 180L);
randomSeed(time.micros());
uint16_t dice = randomRange(1, 6);
11 · Appendix
Supported boards & pin map
Version 1.x/2.x scope is intentionally three boards. Everything in this document — Digital, PWM, Servo, PinChange, SPI, I2C — auto-selects the correct register set for whichever of these is targeted at compile time.
| Board | MCU | PWM pins | Hardware serial | I2C (SDA/SCL) | SPI (MOSI/MISO/SCK) |
|---|---|---|---|---|---|
| Uno | ATmega328P | 3, 5, 6, 9, 10, 11 | 1 (uart) | A4 / A5 | 11 / 12 / 13 |
| Nano | ATmega328P | 3, 5, 6, 9, 10, 11 | 1 (uart) | A4 / A5 | 11 / 12 / 13 |
| Mega 2560 | ATmega2560 | 2–13, 44–46 | 4 (uart, uart1-3) | 20 / 21 | 51 / 50 / 52 |
ArduLiteDigital.h and ArduLiteUtils.h at the register-mapping level (they lack the PINx hardware-toggle feature, so toggle() falls back to read-modify-write) — but they are not part of the officially supported board list in the PRD.ArduLite vs. the Arduino Core
| Task | Arduino Core | ArduLite |
|---|---|---|
| Configure & write a pin | pinMode(13, OUTPUT); digitalWrite(13, HIGH); | Digital led(13, OUT); led.on(); |
| Read a button (active-low) | digitalRead(2) == LOW | button.pressed() |
| Blocking delay | delay(500); | wait(500); |
| Running clock | millis() | time.millis() |
| Non-blocking periodic task | if (millis() - last >= interval) {...} | if (myTimer.ready()) {...} |
| Re-scale a range | map(v, 0, 1023, 0, 180) | scaleMap(v, 0, 1023, 0, 180) |
| PWM output | analogWrite(9, 128); | PWM led(9); led.write(128); |
| Dynamic strings | String s = "x" + String(v); // heap | Not supported by design — use uart.send() overloads |
Arduino.h.Examples index
All 21 example sketches ship in examples/ and are importable from File → Examples → ArduLite.
Led_Blink
Minimal Digital output blink.
Led_Toggle_With_Push_Button
Digital input with pull-up + toggle.
Led_PWM
PWM fade up and down.
Analog_Read_Control_Led_Brightness
Analog + PWM brightness control.
Analog_Coding_Test_Speed
ADC prescaler speed comparison.
Simple_Pulse_Read
Basic Pulse width capture.
Simple_Timer_or_Millis
Timer class vs. manual millis math.
Periodic_Task
Two independent Timer-driven tasks.
Periodic_Task_Interrupt
createTimer() ISR-driven scheduling.
Stopwatch
Start/stop/elapsed duration measurement.
Component_LED
LED wrapper with blink().
Component_Button
Debounced press/release edges.
Component_Relay
Active-low relay control.
Component_Buzzer
Active buzzer beep().
Component_Servo
0–180° sweep over Timer1.
Component_Ultrasonic
HC-SR04 distance in cm.
UART_Interrupt
Serial command line receiver.
I2C_Scanner
Bus scan with probe().
SPI_Demo
MOSI/MISO loopback self-test.
Button_Interrupt
Debounced INT0 button.
Pulse_Counter
PinChange rising-edge counter.
Persistent_Counter
Boot counter surviving power loss.
Save_Configuration
Storing settings in EEPROM.
Design rules (project conventions)
ArduLite's internal docs/rules.md spells out the constraints every module in this reference follows — useful context if you're extending the library yourself.
| Rule | Detail |
|---|---|
| C++ standard | Target C++17. Required: constexpr, inline, enum class, explicit constructors, const-correctness. |
| Forbidden | Exceptions, RTTI, virtual inheritance, std::thread, std::filesystem, and any heap allocation (new/delete/malloc/calloc/realloc/free). |
| Avoid | std::string, std::vector — anything that hides a dynamic allocation. |
| API readability | Digital led(13); led.on(); is the goal, not digitalWrite(13, HIGH);. Every API must read more clearly than the Arduino API it replaces. |
| Opt-in modules | No optional module is auto-included in the master header. No peripheral is initialized unless the sketch actually uses it. |
| Naming | Classes: PascalCase. Variables and methods: camelCase. |
License & authorship
ArduLite is released under the MIT License, © 2025 Ajang Rahmat.
Created by Ajang Rahmat — Robot Class, PT AViSha Inovasi Indonesia.
Source: github.com/ArduLite/ArduLite
12 · For Beginners
Frequently asked questions
New to Arduino or embedded C++? Start here.
What is ArduLite, in plain terms?
It's a free library that lets you control an Arduino board with short, readable code like led.on() instead of the longer digitalWrite(13, HIGH) style — while producing code that is just as fast (often faster) than writing the registers by hand yourself.
Do I need to know what a "register" is to use it?
No. You can build complete projects — blink an LED, read a button, sweep a servo — using only the object methods shown in this reference, exactly like the Quick Start examples above. The register-level explanations in Zero-Overhead Model are optional background reading for when you're curious how it works under the hood.
Which Arduino board should I use with ArduLite?
Any Arduino Uno or Nano is the easiest starting point — they're the cheapest, most common boards and every example in this documentation works on them unmodified. Use a Mega 2560 only if your project needs more pins or more than one hardware serial port.
How is this different from just using the normal Arduino language?
You don't have to choose — see the ArduLite vs. Arduino Core table. ArduLite never redefines Arduino's own functions, so you can use both in the same sketch if a library you need still expects the standard Arduino API.
I'm stuck — where do I start?
Read Installation, copy the first example from Quick Start exactly as written, upload it, and confirm the LED blinks before changing anything. Then open Examples Index and pick the next one closest to what you want to build.