The Direct Answer: Which Arduino Variable Type Should You Pick?
The biggest mistake hobbyists make is relying on generic C++ types like int or long. On 8-bit AVR boards (like the Uno or Nano), an int is 16 bits, but on 32-bit ARM/ESP32 boards, an int is 32 bits. This cross-platform inconsistency causes silent overflows and memory bugs when you port code.
The professional standard is to stop using generic types and explicitly declare bit-widths using the <stdint.h> library. Use the decision tree below to pick the exact type for your next build.
| What are you storing? | Value Range Needed | Do NOT Use | Concrete Pick (Use This) |
|---|---|---|---|
| Pin numbers, boolean states, small counters | 0 to 255 | int, byte |
uint8_t |
| ADC readings (10-bit/12-bit), PWM duty cycles | 0 to 4,095 | int |
uint16_t |
| Encoder positions, timestamps (millis), large sums | -2.14B to +2.14B | int, long |
int32_t |
| Fractional math, PID control, sensor scaling | Decimals | double (on AVR) |
float (32-bit IEEE 754) |
#include <stdint.h> at the top of your sketch. Default to int32_t for any counter or math variable unless you are strictly optimizing for SRAM on an 8-bit board, in which case use uint16_t or uint8_t.
Project Build: High-Resolution Encoder Position Tracker
To demonstrate why explicit variable types matter, we are building a rotary encoder tracker. Encoders generate thousands of pulses; if you use a standard 16-bit int on an AVR board, your counter will overflow and wrap from 32,767 to -32,768 in seconds. We will use an interrupt-driven approach, which also requires the volatile qualifier.
Parts List
- Microcontroller: Arduino Nano V3 (ATmega328P, 16MHz, 5V logic)
- Sensor: KY-040 Rotary Encoder Module (with breakout board and pull-up resistors)
- Display: 0.96-inch SSD1306 I2C OLED (128x64, 4-pin)
- Wiring: 22 AWG solid core jumper wires, half-size solderless breadboard
Pin Mapping Table
| Component | Module Pin | Arduino Nano V3 Pin | Notes |
|---|---|---|---|
| KY-040 Encoder | CLK | D2 (INT0) | Must be a hardware interrupt pin |
| KY-040 Encoder | DT | D4 | Standard digital read |
| KY-040 Encoder | SW | D5 | Pushbutton (optional) |
| KY-040 Encoder | VCC / GND | 5V / GND | Module has onboard pull-ups |
| SSD1306 OLED | SDA | A4 | I2C Data (add 4.7k pull-up if missing) |
| SSD1306 OLED | SCL | A5 | I2C Clock |
Difficulty Rating: 2/5 (Beginner-Intermediate) | Time: 20 minutes
Complete Compilable Code with Type-Safe Counters
This code targets the Arduino Nano V3 (ATmega328P). It requires the Adafruit_SSD1306 and Adafruit_GFX libraries installed via the Library Manager. Notice the strict use of stdint types and the volatile keyword for the interrupt service routine (ISR) variable.
#include <stdint.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// --- Pin Definitions (uint8_t saves RAM compared to int) ---
const uint8_t PIN_ENC_CLK = 2; // Hardware interrupt 0
const uint8_t PIN_ENC_DT = 4;
const uint8_t PIN_ENC_SW = 5;
const uint8_t PIN_LED_ERR = 13; // Onboard LED for error signaling
// --- Display Definitions ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// --- State Variables ---
// volatile is REQUIRED for variables modified inside an ISR
// int32_t prevents the 16-bit overflow wrap-around on AVR boards
volatile int32_t encoder_position = 0;
volatile bool encoder_updated = false;
void setup() {
pinMode(PIN_ENC_DT, INPUT);
pinMode(PIN_ENC_SW, INPUT_PULLUP);
pinMode(PIN_LED_ERR, OUTPUT);
// Initialize I2C Display with Error Handling
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
// Halt and blink LED if OLED fails to initialize
while(true) {
digitalWrite(PIN_LED_ERR, HIGH);
delay(250);
digitalWrite(PIN_LED_ERR, LOW);
delay(250);
}
}
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 20);
display.print('Ready');
display.display();
delay(500);
// Attach Interrupt (Trigger on falling edge of CLK)
attachInterrupt(digitalPinToInterrupt(PIN_ENC_CLK), readEncoder, FALLING);
}
void loop() {
// Only update display when the ISR flags a change (saves I2C bus time)
if (encoder_updated) {
encoder_updated = false;
// Create a local copy to prevent tearing if ISR fires during print
int32_t local_pos = encoder_position;
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(1);
display.print('Position:');
display.setTextSize(2);
display.setCursor(0, 25);
display.print(local_pos);
display.display();
}
}
// --- Interrupt Service Routine ---
void readEncoder() {
// Read DT pin to determine direction
uint8_t dt_state = digitalRead(PIN_ENC_DT);
if (dt_state == HIGH) {
encoder_position++;
} else {
encoder_position--;
}
encoder_updated = true;
}
Debugging Variable Overflows: Exact Errors and Ranked Causes
When you use the wrong variable type, the compiler might catch it, or worse, it might fail silently at runtime. Here is how to debug the most common variable-related failures.
The Compiler Warning
If you try to assign a large constant to a 16-bit integer (e.g., int timeout = 60000; on an Uno), the compiler will throw this exact error string:
warning: overflow in implicit constant conversion [-Woverflow]
The Fix: Change the type to uint16_t (max 65,535) or int32_t. Alternatively, append the UL suffix to the constant (60000UL) if the variable is already correctly typed but the compiler is misinterpreting the literal.
The Runtime Wrap-Around (Silent Failure)
If your serial monitor or display suddenly jumps from 32767 to -32768, you have hit the 16-bit signed integer ceiling. The binary representation of 32767 is 01111111 11111111. Adding one flips the sign bit, resulting in 10000000 00000000 (-32768 in two's complement).
The First 3 Things to Check When Math Fails
- Check your board architecture: Are you compiling for AVR (Uno/Nano/Mega) where
intis 16-bit, or ARM/ESP32 whereintis 32-bit? Never assumeintsize. Switch toint32_t. - Check for signed/unsigned mismatch: If you compare a signed
int32_twith an unsigneduint32_t(like the return value ofmillis()), the compiler promotes the signed variable to unsigned. A negative number becomes a massive positive number, breaking your logic. Useunsigned longoruint32_tfor all time-tracking variables. - Check ISR volatility: If a variable is updated inside an
attachInterruptfunction but read inloop(), it must be declaredvolatile. Without it, the GCC compiler optimizes the read out of the loop, caching the initial value in a CPU register and ignoring hardware updates.
SRAM Profiling: Why 8-Bit AVR Boards Run Out of Memory
The ATmega328P on the Arduino Nano V3 has exactly 2,048 bytes of SRAM. This must hold your global variables, the heap (dynamic allocations like String objects), and the stack (local variables inside functions). Using the wrong variable types bloats your globals and causes stack collisions, leading to random reboots.
| Data Type | AVR (Uno/Nano) Size | ARM/ESP32 Size | Max Value (Signed) |
|---|---|---|---|
bool / uint8_t |
1 byte | 1 byte | 255 (unsigned) |
int |
2 bytes | 4 bytes | 32,767 (AVR) / 2.14B (ARM) |
int32_t / long |
4 bytes | 4 bytes | 2,147,483,647 |
float |
4 bytes | 4 bytes | ~3.4 x 10^38 (7 digits precision) |
double |
4 bytes (Same as float!) | 8 bytes | ~1.7 x 10^308 (15 digits precision) |
float calculation is emulated in software, consuming significant Flash memory and CPU cycles. If you are doing PID control or sensor averaging on a Nano, multiply your values by 100 or 1000 and use int32_t fixed-point math instead. It is exponentially faster and saves SRAM.
How to Extend or Simplify the Build
Depending on your project constraints, you can scale this encoder tracker up or down.
Simplify: Drop the I2C Display
If you are building a hidden mechanism where visual feedback isn't needed, remove the SSD1306 OLED and the Adafruit libraries. This immediately frees up roughly 1,500 bytes of Flash and 150 bytes of SRAM used by the display buffer. Replace the display logic in loop() with a simple Serial.println(local_pos);.
Extend: Port to the ESP32-C3 for High-Speed Tracking
If you are tracking a high-RPM motor, the 16MHz ATmega328P might miss interrupts. Port the code to an ESP32-C3 SuperMini.
- Variable shifts: On the ESP32, a standard
intis 32 bits. However, stick toint32_tto maintain code portability. - Interrupt handling: The ESP32 handles interrupts differently. You will need to use
portENTER_CRITICAL_ISRif you are scaling to multi-core ESP32 variants to prevent race conditions when reading the encoder state. - Memory: With 400KB+ of SRAM, you can safely add WiFi logging via MQTT without worrying about the 2KB AVR limit.
By enforcing strict, explicit variable typing via <stdint.h>, you eliminate the most common class of embedded bugs: the silent overflow. Your code will compile cleanly, run predictably, and port effortlessly between 8-bit and 32-bit architectures.
References: Arduino Language Reference: Data Types, AVR Libc: Integer Types (stdint.h), GCC Warning Options Documentation.






