Skip to content

Arduino - Variables & Constants

A variable is a named storage location that our program can manipulate. In Arduino (and C++), each variable has a specific type, which determines the size and layout of the variable’s memory, the range of values that can be stored, and the set of operations that can be applied to it.

Choosing the right data type is crucial for writing efficient and bug-free code. Using a type that’s too small will cause errors, while using one that’s too large wastes precious memory.

TypeSize (on Uno/AVR)RangeTypical Use
bool1 bytetrue or falseStoring state (e.g., button pressed, LED on)
byte1 byte0 to 255Working with raw byte data, PWM values
int2 bytes-32,768 to 32,767General purpose integer math, loop counters
unsigned int2 bytes0 to 65,535When negative numbers are not needed
long4 bytes-2,147,483,648 to 2,147,483,647Large integer values
unsigned long4 bytes0 to 4,294,967,295Storing the value from millis()
float4 bytes-3.4028235E+38 to 3.4028235E+38Numbers with a decimal point (6-7 digits precision)
char1 byte-128 to 127Storing a single character (e.g., ‘A’)

Note: The size of int and long can vary on different Arduino boards. On 32-bit boards like the ESP32 or Arduino Due, an int is typically 4 bytes. Always check the documentation for your specific board.

A variable’s scope defines where in the program it can be accessed. Understanding scope is key to organizing your code and avoiding bugs.

Local variables are declared inside a function (like setup() or loop()) or a code block (like a for loop). They are only accessible within that function or block. They are created when the function is called and destroyed when it exits. This is the preferred scope for most variables.

Global variables are declared outside of any function, usually at the top of the sketch. They can be accessed and modified from any function in your program. While useful, they should be used sparingly, as they can make your code harder to debug (a bug in one function could unexpectedly change a global variable used by another).

A static variable is a special type of local variable. It is only accessible within its function, but its value is preserved between function calls. It’s a great way to maintain state inside a function without using a global variable.

// --- Global Variable ---
// Accessible from anywhere in this file.
int globalCounter = 0;
void setup() {
Serial.begin(9600);
}
void loop() {
// --- Local Variable ---
// 'loopMessage' is created and destroyed on each run of loop().
String loopMessage = "Inside loop().";
Serial.println(loopMessage);
// We can access and modify the global variable here.
globalCounter++;
Serial.print("Global counter is now: ");
Serial.println(globalCounter);
// Call our custom function
myFunction();
delay(1000);
Serial.println("----------");
}
void myFunction() {
// This function CANNOT access 'loopMessage' from loop().
// Trying to do so would cause a compile error.
// Serial.println(loopMessage); // <-- THIS WOULD NOT COMPILE
// --- Static Local Variable ---
// 'staticCounter' is initialized only ONCE (the first time this function runs).
// Its value is remembered across calls to myFunction().
static int staticCounter = 0;
staticCounter++;
Serial.print("myFunction() has been called ");
Serial.print(staticCounter);
Serial.println(" times.");
}

A constant is a variable whose value cannot be changed after it is defined. Using constants for pin numbers, sensor thresholds, or configuration settings makes your code much more readable and easier to maintain.

The modern, preferred way to create a constant is with the const keyword. It tells the compiler the variable is read-only and allows it to perform optimizations.

const int ledPin = 13;
const float pi = 3.14159;
const long baudRate = 9600;
void setup() {
// Now use the constant instead of a "magic number"
pinMode(ledPin, OUTPUT);
Serial.begin(baudRate);
}
void loop() {
// ledPin = 12; // This would cause a compile error, which is good!
}

The ‘#define’ Preprocessor Directive (Legacy)

Section titled “The ‘#define’ Preprocessor Directive (Legacy)”

You may see older code use #define to create constants. This is a preprocessor directive that performs a simple text-replacement before the code is compiled. While it works, const is generally safer because it is type-aware and respects scope.

// Legacy method
#define LED_PIN 13