The Direct Answer: Choosing the Right Arduino Variable Type

When working with 8-bit AVR boards like the Arduino Uno R3, the most common cause of random reboots and corrupted sensor data is improper Arduino variable typing and SRAM mismanagement. The ATmega328P microcontroller has only 2,048 bytes of SRAM. If you use the String class for text manipulation or standard int types for timing, you will inevitably cause heap fragmentation or a millis() rollover failure.

The direct rule: Always use fixed-width integers from <stdint.h> (like uint32_t for time and int16_t for sensor reads), avoid the String object entirely in favor of char arrays, and declare unchanging variables as const or in PROGMEM to keep them out of SRAM.

Spec-Sheet Table: Arduino Variable Types on 8-bit AVR (Uno/Nano/Mega)
Data Type Size (Bytes) Value Range Best Practical Use
bool 1 0 or 1 State flags (e.g., isCalibrated)
uint8_t 1 0 to 255 PWM values, raw I2C bytes
int16_t / int 2 -32,768 to 32,767 Analog reads (0-1023), standard math
uint32_t 4 0 to 4,294,967,295 Required for millis() and micros()
float 4 ±3.4028235E38 Sensor calculations (note: 6-7 digit precision limit)

Note: On 32-bit boards like the ESP32 or Arduino Zero, a standard int is 4 bytes. Using fixed-width types like int16_t ensures your code behaves identically across architectures.

Project Build: Memory-Safe Environmental Logger

This build demonstrates how to read sensor data and display it on an OLED without using a single String object, preserving the Uno R3's limited SRAM. We use snprintf to format text into pre-allocated char arrays.

Parts List & Board Variant

  • Microcontroller: Arduino Uno R3 (ATmega328P, 16MHz, 2KB SRAM, 32KB Flash)
  • Sensor: Adafruit BME280 I2C Breakout (Product ID 2652) or generic equivalent
  • Display: 0.96" SSD1306 128x64 I2C OLED
  • Passives: 2x 4.7kΩ pull-up resistors (for I2C SDA/SCL lines)
  • Wiring: 22 AWG solid core jumper wires

Pin Mapping Table

Component Pin Label Arduino Uno R3 Pin Notes
BME280 / OLED VCC / VIN 5V Ensure 5V, not 3.3V, if using generic clones
BME280 / OLED GND GND Common ground required
BME280 / OLED SDA A4 Pull-up to 5V via 4.7kΩ resistor
BME280 / OLED SCL A5 Pull-up to 5V via 4.7kΩ resistor

Complete Compilable Code

This code targets the Arduino Uno R3. It requires the Adafruit_BME280, Adafruit_SSD1306, and Adafruit_GFX libraries installed via the Library Manager.

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <stdint.h>

// Pin definitions and constants
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
#define BME_ADDRESS 0x76

// Hardware objects
Adafruit_BME280 bme;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Memory-safe variables (No String objects!)
uint32_t lastReadTime = 0;
const uint32_t READ_INTERVAL_MS = 2000;
char textBuffer[32]; // Pre-allocated buffer for string formatting

void setup() {
  Serial.begin(115200);
  
  // Initialize OLED with error handling
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    while(true); // Halt execution
  }
  
  // Initialize BME280 with error handling
  if (!bme.begin(BME_ADDRESS)) {
    Serial.println(F("Could not find BME280. Check I2C wiring."));
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0,0);
    display.print(F("BME280 ERROR"));
    display.display();
    while(true); // Halt execution
  }
  
  display.clearDisplay();
  display.display();
}

void loop() {
  // Safe millis() rollover check using uint32_t
  uint32_t currentTime = millis();
  
  if (currentTime - lastReadTime >= READ_INTERVAL_MS) {
    lastReadTime = currentTime;
    
    float tempC = bme.readTemperature();
    float hum = bme.readHumidity();
    
    // Format data into char array instead of using String concatenation
    snprintf(textBuffer, sizeof(textBuffer), "T: %.1f C  H: %.1f %%", tempC, hum);
    
    // Output to Serial
    Serial.println(textBuffer);
    
    // Output to OLED
    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0, 10);
    
    // Split display for readability using snprintf again
    snprintf(textBuffer, sizeof(textBuffer), "%.1fC", tempC);
    display.println(textBuffer);
    
    snprintf(textBuffer, sizeof(textBuffer), "%.1f%%", hum);
    display.println(textBuffer);
    
    display.display();
  }
}

Debugging Variable Corruption and Scope Failures

When an Arduino variable fails, it rarely throws a clean software exception. Instead, the microcontroller locks up, outputs garbage, or reboots. Here are the exact error strings you will encounter and how to fix them.

Compile-Time Error: region 'data' overflowed by X bytes
Meaning: Your global variables and statically allocated arrays exceed the 2,048 bytes of SRAM available on the ATmega328P. The linker is refusing to build because the heap and stack will collide immediately upon boot.
Fix: Move static text (like "Could not find BME280") into Flash memory using the F() macro in Serial prints, or use PROGMEM for large lookup tables.
Runtime Error (Serial Monitor): ⸮⸮⸮ (Garbage characters) or ovf on OLED
Meaning: Heap fragmentation. You are likely using the String class inside the loop() function. Every time you concatenate a String, it requests a new block of SRAM. Over hours of runtime, the SRAM becomes Swiss-cheesed with tiny unusable gaps, causing a memory allocation failure that corrupts adjacent variables or crashes the I2C bus.
Fix: Replace all String objects with fixed-size char arrays and use snprintf() as shown in the code above.

The First Three Things to Check When It Fails

If your sensor data suddenly reads NaN, zeros out, or the board resets after exactly 32 seconds or 49 days, run through this checklist:

  1. Check millis() Data Types: If your timing variable is declared as int (max 32,767), it will overflow and turn negative after just 32.7 seconds. If it's declared as unsigned int, it overflows at 65 seconds. Always use uint32_t or unsigned long for time tracking.
  2. Audit for the String Class: Use your IDE's "Find" tool (Ctrl+F) and search for the capital letter S followed by tring. If you find it inside loop() or any frequently called function, rip it out and replace it with char buffers.
  3. Verify I2C Pull-ups and Scope Shadowing: If variables read correctly once but fail later, check for variable shadowing (declaring a local variable with the same name as a global one). Also, ensure your I2C bus has 4.7kΩ pull-up resistors; floating I2C lines can induce electrical noise that the ATmega328P misinterprets as data, corrupting the variables holding the sensor readings.

Extending and Simplifying the Build

Depending on your project goals, you may need to scale this memory-safe architecture up or down.

How to Extend: Adding SD Card Logging

To log these variables to a microSD card, you will need to add an SPI-based SD card module. Warning: The Arduino Uno R3 uses pins 10 (SS), 11 (MOSI), 12 (MISO), and 13 (SCK) for hardware SPI. Even if you use a different pin for the SD card's Chip Select, Pin 10 must be configured as an OUTPUT in your setup() function, or the ATmega328P's SPI hardware will default to slave mode and lock up the bus. Use the SdFat library rather than the default SD library, as SdFat allows you to strictly control buffer sizes and avoid hidden SRAM bloat.

How to Simplify: Dropping the OLED

If you only need Serial output, remove the SSD1306 code. To further optimize for ultra-low power or minimal memory footprints, use the PROGMEM attribute for any large constant arrays (like calibration curves). According to the AVR Libc PROGMEM documentation, this forces the compiler to store the data in the 32KB Flash memory rather than copying it into the 2KB SRAM at boot.

Frequently Asked Questions

Why does my Arduino variable reset to zero randomly?

If a global variable randomly resets to zero while the rest of the program keeps running, you are likely experiencing a Brownout Detector (BOD) reset or a Watchdog Timer (WDT) reset. When the ATmega328P experiences a voltage dip below 2.7V (often caused by a high-current component like a relay or motor kicking on), it triggers a hardware reset. All SRAM is wiped, and global variables revert to their initialized state (usually zero). Fix this by separating motor power supplies from the logic supply and adding a 100µF decoupling capacitor across the 5V and GND rails.

What is the difference between an Arduino variable and a constant?

A standard variable (e.g., int sensorVal;) is stored in SRAM and can be altered at runtime. A constant (e.g., const int MAX_LIMIT = 100;) is a promise to the compiler that the value will never change. On AVR boards, the compiler is smart enough to store const variables in Flash memory (program space) rather than SRAM, provided they are global or static. Using const for pin definitions and thresholds is a critical habit for saving SRAM. For more details on memory management, refer to the official Arduino Memory Guide.

How do I store an Arduino variable in EEPROM so it survives a power loss?

To save a variable like a calibration offset or a total run-time counter, use the built-in <EEPROM.h> library. The ATmega328P has 1,024 bytes of EEPROM. Use EEPROM.put(address, variable) and EEPROM.get(address, variable) instead of read() and write(), as put/get handle multi-byte variables (like float or uint32_t) automatically. Crucial edge case: EEPROM has a write limit of roughly 100,000 cycles. Never put an EEPROM.write() command directly inside the loop() without a time-delay or state-change condition, or you will destroy that memory sector in a matter of hours.

Can I use floating-point Arduino variables for precise calculations?

You can, but you must understand the hardware limits. On 8-bit AVR boards, both float and double are exactly the same: 32-bit IEEE 754 floating-point numbers. They offer only 6 to 7 significant decimal digits of precision. If you try to store 123456.789 in a float, it will truncate to 123456.8. Furthermore, floating-point math on an 8-bit chip is emulated in software, making it slow. If you need high precision or fast execution (e.g., for PID control loops), multiply your values by 100 or 1000 and use int32_t fixed-point math instead.