Skip to content

Arduino - Data Types

When you program an Arduino, you’re primarily writing in C++. Data types are a fundamental concept in C++ for declaring variables and functions. The type of a variable determines its size in memory and how the stored bits are interpreted. Choosing the right data type is crucial on memory-constrained microcontrollers like an Arduino.

This guide covers the essential data types for Arduino development, updated with modern best practices.

Here is a summary of the fundamental data types available in the Arduino environment:

TypeSize (on Uno/AVR)Range / Description
boolean1 byteStores true or false.
byte1 byteStores an unsigned number from 0 to 255.
char1 byteStores a character or a small integer from -128 to 127.
unsigned char1 byteStores an unsigned number from 0 to 255 (same as byte).
int2 bytesStores an integer from -32,768 to 32,767.
unsigned int2 bytesStores an unsigned integer from 0 to 65,535.
long4 bytesStores a large integer from -2,147,483,648 to 2,147,483,647.
unsigned long4 bytesStores a large unsigned integer from 0 to 4,294,967,295.
float4 bytesStores a single-precision floating-point number.
double4 bytesSame as float on AVR-based boards (Uno, Mega). 8 bytes on modern ARM boards (Due, Portenta).
voidN/AUsed to indicate that a function returns no value.

The void keyword is used exclusively in function declarations to specify that the function does not return any value. The built-in setup() and loop() functions are classic examples.

void setup() {
// This function runs once and returns nothing.
}

A boolean holds one of two values: true or false. It’s perfect for tracking states, like whether an LED is on or off. It occupies one byte of memory.

const int ledPin = 13;
boolean isLedOn = false;
void setup() {
pinMode(ledPin, OUTPUT);
}
void loop() {
if (isLedOn) {
digitalWrite(ledPin, LOW);
isLedOn = false;
} else {
digitalWrite(ledPin, HIGH);
isLedOn = true;
}
delay(1000);
}

A char stores a character value using one byte of memory. Character literals are enclosed in single quotes (e.g., 'A'). Internally, characters are stored as numbers according to the ASCII table. This allows for arithmetic operations on them.

// 'A' has an ASCII value of 65.
char myChar = 'A';
// This will result in the character 'C', which has an ASCII value of 67.
char nextChar = myChar + 2;
Serial.print(nextChar); // Prints "C"

A common issue in embedded systems is that the size of int can vary between architectures (e.g., 2 bytes on an Arduino Uno, 4 bytes on an Arduino Due). This can lead to unexpected behavior when porting code.

To avoid this, modern C++ practice encourages using fixed-width integers from the <stdint.h> header. These types declare their size explicitly.

  • int8_t / uint8_t: 8-bit signed/unsigned integer (equivalent to char and byte).
  • int16_t / uint16_t: 16-bit signed/unsigned integer (equivalent to int on Uno).
  • int32_t / uint32_t: 32-bit signed/unsigned integer (equivalent to long).
  • int64_t / uint64_t: 64-bit signed/unsigned integer.
#include <stdint.h>
// This variable will be 16 bits on ANY Arduino board.
uint16_t sensorValue = 1023;

Strings: The String Object vs. C-style char Arrays

Section titled “Strings: The String Object vs. C-style char Arrays”

Handling text in Arduino can be done in two ways, each with significant trade-offs.

This is the traditional, memory-efficient way. A string is an array of characters ending with a null terminator ('\0'). They are fast and don’t cause memory fragmentation, but can be more complex to manipulate.

char message[16] = "Hello, world!"; // Array of 16 chars, 15 for text, 1 for null terminator.
Serial.println(message);

The String object provides easy-to-use methods for text manipulation (concatenation, searching, etc.). However, it uses dynamic memory allocation. On small microcontrollers, frequent creation and destruction of String objects can lead to heap fragmentation, eventually causing your program to crash unpredictably.

String message = "Hello, ";
String name = "Arduino";
// Easy to concatenate, but uses dynamic memory behind the scenes.
String greeting = message + name;
Serial.println(greeting);

For small, stable sketches, the String object can be convenient. For any large, long-running, or mission-critical application, it is strongly recommended to use C-style char arrays to avoid memory issues.

An array is a collection of variables of the same type.

int sensorReadings[5]; // An array to hold 5 integer readings.
sensorReadings[0] = analogRead(A0);
sensorReadings[1] = analogRead(A1);

A struct allows you to group different data types into a single, user-defined type. This is incredibly useful for organizing related data.

struct SensorData {
int id;
float temperature;
float humidity;
boolean isActive;
};
void setup() {
Serial.begin(9600);
SensorData livingRoomSensor;
livingRoomSensor.id = 1;
livingRoomSensor.temperature = 22.5;
livingRoomSensor.humidity = 45.8;
livingRoomSensor.isActive = true;
Serial.print("Sensor ID: ");
Serial.println(livingRoomSensor.id);
Serial.print("Temperature: ");
Serial.println(livingRoomSensor.temperature);
}
  • Integer Overflow: Assigning a value to a numeric variable that is outside its range. For example, int count = 40000; will overflow and result in a negative number. Use a larger type like unsigned int or long.
  • Signed/Unsigned Mismatch: Performing arithmetic with both signed and unsigned numbers can lead to unexpected results, especially during comparisons.
  • Floating Point Imprecision: float and double are approximations. Don’t use == to compare them directly. Instead, check if the absolute difference is within a small tolerance: if (abs(a - b) < 0.001) { ... }.