The direct answer to choosing data types in Arduino is dictated by your microcontroller's architecture and the physical limits of the data you are measuring. On a standard 8-bit AVR board (like the Arduino Uno R3), an int is strictly 2 bytes (holding -32,768 to 32,767). If your sensor math, timing variables, or counters exceed this, you must use long (4 bytes) or unsigned long. Using the wrong data type doesn't just waste precious SRAM; it causes silent integer overflows that corrupt calculations and crash state machines.
This guide breaks down the exact memory footprints, architectural differences between 8-bit and 32-bit boards, and provides a real-world RPM logging project to demonstrate proper type casting, overflow prevention, and debugging techniques.
The Data Types in Arduino Cheat Sheet
Before writing a single line of code, you must understand how the compiler allocates memory. The most common mistake makers make is assuming C++ standard sizes apply universally. They do not. An int on an Arduino Uno (ATmega328P) is 16 bits, but on an ESP32 or Arduino Due, an int is 32 bits.
int16_t, uint32_t, or int8_t (from <stdint.h>) if you plan to port your code between 8-bit AVRs and 32-bit ARM/Xtensa chips. This guarantees the byte size regardless of the board.
| Data Type | Size (AVR / Uno) | Size (ARM / ESP32) | Value Range (AVR) | Primary Use Case |
|---|---|---|---|---|
boolean / bool |
1 byte | 1 byte | 0 or 1 (false/true) | Flags, button states, simple logic gates. |
byte / uint8_t |
1 byte | 1 byte | 0 to 255 | Raw I2C/SPI buffers, PWM values (0-255), RGB colors. |
int / int16_t |
2 bytes | 4 bytes | -32,768 to 32,767 | Analog reads (0-1023), pin numbers, small counters. |
unsigned int |
2 bytes | 4 bytes | 0 to 65,535 | Positive-only sensor readings, larger PWM frequencies. |
long / int32_t |
4 bytes | 4 bytes | -2,147,483,648 to 2,147,483,647 | Large calculations, GPS coordinates, total pulse counts. |
unsigned long |
4 bytes | 4 bytes | 0 to 4,294,967,295 | Mandatory for millis() and micros() timing. |
float |
4 bytes | 4 bytes | -3.4e38 to 3.4e38 (6-7 digits) | Temperature, voltage, PID control loops. (Slow on AVR). |
char |
1 byte | 1 byte | -128 to 127 (ASCII) | Single characters, serial parsing, building strings. |
Source reference: Arduino Language Reference and AVR Libc stdint documentation.
Project Build: High-Speed RPM & Temperature Logger
To see data types in action, we will build an optical RPM and temperature logger. This project specifically highlights the danger of integer overflow when calculating RPM from microsecond pulses, and the necessity of unsigned long for non-blocking timing.
Parts List & Board Variant
- Microcontroller: Arduino Uno R3 (ATmega328P, 8-bit AVR, 16MHz, 2KB SRAM)
- Sensor 1: LM393 IR Obstacle/Speed Sensor Module (Digital Output)
- Sensor 2: DS18B20 Waterproof Temperature Sensor + 4.7kΩ Pull-up Resistor
- Display: 128x64 I2C OLED (SSD1306 Driver)
- Wiring: 22 AWG solid core hook-up wire
Pin Mapping Table
| Component | Module Pin | Arduino Uno Pin | Notes |
|---|---|---|---|
| IR Speed Sensor | DO (Digital Out) | D2 | Must use hardware interrupt pin (INT0) |
| DS18B20 | Data | D4 | Requires 4.7kΩ pull-up to 5V |
| SSD1306 OLED | SDA | A4 | I2C Data |
| SSD1306 OLED | SCL | A5 | I2C Clock |
Complete Code with Error Handling
The following code targets the Arduino Uno R3. Notice the explicit use of unsigned long for timing, the UL suffix on constants to force 32-bit math, and the runtime checks for sensor failures.
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <OneWire.h>
#include <DallasTemperature.h>
// --- Pin Definitions ---
const byte IR_SENSOR_PIN = 2; // Hardware interrupt 0
const byte ONE_WIRE_BUS = 4; // DS18B20 data pin
// --- Display Setup ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
// --- Sensor Setup ---
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
// --- Timing & State Variables ---
// CRITICAL: millis() and micros() MUST be unsigned long
unsigned long previousMillis = 0;
const unsigned long updateInterval = 500;
volatile unsigned long pulseCount = 0;
volatile unsigned long lastPulseMicros = 0;
unsigned long elapsedTimeMicros = 0;
void IRAM_ATTR countPulse() {
unsigned long currentMicros = micros();
elapsedTimeMicros = currentMicros - lastPulseMicros;
lastPulseMicros = currentMicros;
pulseCount++;
}
void setup() {
Serial.begin(115200);
// Initialize I2C Display with error handling
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Halt if display fails
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.display();
sensors.begin();
pinMode(IR_SENSOR_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(IR_SENSOR_PIN), countPulse, FALLING);
}
void loop() {
unsigned long currentMillis = millis();
// Non-blocking timing check
if (currentMillis - previousMillis >= updateInterval) {
previousMillis = currentMillis;
// Disable interrupts briefly to read volatile variables safely
noInterrupts();
unsigned long safePulseCount = pulseCount;
unsigned long safeElapsed = elapsedTimeMicros;
interrupts();
// Calculate RPM: (Pulses / ElapsedSec) * 60
// To avoid float math on AVR, we use integer scaling.
// 60,000,000UL forces the compiler to treat the constant as Unsigned Long.
// Without 'UL', 60000000 overflows a 16-bit int and becomes garbage.
unsigned long rpm = 0;
if (safeElapsed > 0) {
rpm = (60000000UL / safeElapsed);
}
// Read Temperature
sensors.requestTemperatures();
float tempC = sensors.getTempCByIndex(0);
// Error handling for disconnected sensor (returns -127.0)
if (tempC < -126.0) {
Serial.println("Error: DS18B20 disconnected or shorted.");
tempC = 0.0;
}
// Output to Serial and OLED
Serial.print("RPM: "); Serial.print(rpm);
Serial.print(" | Temp: "); Serial.println(tempC);
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print("RPM: "); display.println(rpm);
display.print("Temp: "); display.print(tempC); display.println(" C");
display.display();
}
}
Debugging: "overflow in implicit constant conversion"
When working with math in Arduino, you will eventually encounter compiler warnings or silent runtime failures. The most common explicit compiler error related to data types is:
warning: overflow in implicit constant conversion [-Woverflow]
This happens when you try to assign a number that exceeds the maximum capacity of the declared variable type, or when the compiler evaluates a constant expression that exceeds 16-bit limits before assigning it.
Ranked Causes and Fixes
- Missing the
ULSuffix in Math: In the code above,60000000ULis used. If you write60000000without theUL, the AVR compiler treats it as a standard 16-bitint. Since 60,000,000 is far larger than 32,767, it overflows during compilation. Fix: Always appendUL(Unsigned Long) orL(Long) to large constants. - Assigning Large Literals to
int: Writingint myTimer = 40000;triggers this warning because 40,000 exceeds the 32,767 limit of a signed 16-bit integer. Fix: Change the declaration tounsigned intorlong. - Intermediate Math Overflow: If you write
long result = 1000 * 1000;, the compiler multiplies two 16-bitints. The result (1,000,000) overflows the 16-bit boundary before it is assigned to thelongvariable. Fix: Cast one of the operands:long result = 1000L * 1000;.
The First Three Things to Check When Timing or Math Fails
If your project behaves erratically after running for a few minutes, or your math outputs negative numbers, check these three items immediately:
- Are you using
intformillis()?millis()returns anunsigned long. If you store it in anint, it will overflow and turn negative after just 32.7 seconds. Always useunsigned longfor time variables. - Is your rollover logic subtraction-based? Never use
if (currentMillis > previousMillis + interval). This breaks whenmillis()rolls over at ~49 days. Always useif (currentMillis - previousMillis >= interval). Unsigned math handles the rollover wrap-around perfectly. - Are you mixing signed and unsigned types? Comparing a signed
intto anunsigned longforces the compiler to cast the signed integer to unsigned. A negativeintbecomes a massive positive number, ruining conditional logic.
Memory Optimization Strategies for 8-Bit Boards
The ATmega328P on the Arduino Uno has only 2,048 bytes of SRAM. Data types directly dictate whether your project will run out of memory and crash (often manifesting as random reboots or garbled serial output).
- Avoid the
StringClass: The capital-SStringobject dynamically allocates memory on the heap, leading to severe fragmentation. Use null-terminated character arrays (char[]) or theSafeStringlibrary instead. - Use
PROGMEMfor Constants: If you have large lookup tables, error messages, or static strings, store them in flash memory using theF()macro orPROGMEM.
Example:Serial.println(F("Motor controller initialized"));keeps the text in the 32KB flash rather than eating up precious SRAM. - Beware of
floatOverhead: On 8-bit AVRs, the hardware has no Floating Point Unit (FPU). Everyfloatcalculation is emulated in software, consuming hundreds of clock cycles and pulling in large math libraries that bloat your flash usage. Where possible, use integer math with implicit scaling (e.g., store 25.5°C as255and divide by 10 only when printing).
Extending and Simplifying the Build
To Simplify: If you do not need the OLED display, remove the I2C initialization and Adafruit_SSD1306 library. This frees up roughly 15% of the Uno's flash memory and eliminates I2C bus capacitance issues. Rely entirely on the Serial Monitor for debugging.
To Extend: To log data to an SD card, add an SPI MicroSD module (CS to D10, MOSI to D11, MISO to D12, SCK to D13). When writing to the SD card, buffer your sensor readings in a byte array and write in 512-byte chunks. Writing single float variables directly to the SD card in the main loop will cause severe latency and missed RPM pulses due to the SPI bus speed and SD card controller wear-leveling delays.






