The Arduino Programming Language: A Complete Guide

By

Learn the Arduino programming language from setup() and loop() to pins, timing, serial communication, libraries, memory, and a complete project.

~~~

Arduino lets your code reach outside the screen.

You can read a button, measure light, move a motor, play a sound, or control a machine. This makes learning Arduino programming feel very different from building a website or a command-line program.

Your code changes something in the physical world.

In this guide, I’ll explain the Arduino programming language from the ground up. We’ll start with a tiny sketch, then move through pins, timing, serial communication, libraries, memory, and larger programs.

What is the Arduino programming language?

The short answer is: Arduino code is C++ with a beginner-friendly framework around it.

You write C++ syntax. You can use variables, functions, classes, loops, arrays, and the rest of the language.

Arduino also gives you a core library. This library provides functions such as pinMode(), digitalRead(), analogWrite(), and millis().

Finally, the Arduino build tools do some work for you. They add a few missing pieces, compile the program for your board, link the required libraries, and upload the result.

This combination is commonly called the Arduino programming language.

If Arduino itself is new to you, start with my introduction to Arduino. Then come back here to focus on the code.

An Arduino program is called a sketch

Arduino calls a program a sketch.

The main file uses the .ino extension. If your sketch is named PlantMonitor, its main file is normally:

PlantMonitor/PlantMonitor.ino

Notice that the folder and main file have the same name.

A sketch can contain more files. You can add other .ino, .h, .cpp, and .c files as the program grows.

For now, one .ino file is all we need.

The two functions every sketch needs

Every Arduino sketch starts with two functions:

void setup() {
}

void loop() {
}

setup() runs once when the board starts.

Use it to prepare the program. You can configure pins, start serial communication, and initialize libraries here.

loop() runs next. When it reaches the end, Arduino calls it again. This continues for as long as the board has power.

You can picture the lifecycle like this:

power on -> setup() -> loop() -> loop() -> loop() -> ...

The board also starts again when you press its reset button.

Your first complete Arduino sketch

Let’s blink the LED built into the board:

const int ledPin = LED_BUILTIN;

void setup() {
  pinMode(ledPin, OUTPUT);
}

void loop() {
  digitalWrite(ledPin, HIGH);
  delay(1000);

  digitalWrite(ledPin, LOW);
  delay(1000);
}

LED_BUILTIN contains the pin number connected to the onboard LED. Using it is safer than assuming the LED is always on pin 13.

In setup(), pinMode() configures that pin as an output.

In loop(), digitalWrite() turns the pin on and off. delay(1000) pauses the sketch for 1000 milliseconds, or one second.

The result is one second on, then one second off, forever.

If you want to build the circuit with a separate LED, follow my first Arduino LED project. It includes the wiring and upload steps.

What happens when you upload a sketch

The Arduino IDE shows one Upload button, but several things happen behind it.

First, the build tools prepare your .ino files. They add #include <Arduino.h> and generate function declarations when needed.

Next, a compiler turns your C++ code into machine instructions for the selected board.

The build system then links your sketch with the Arduino core and any libraries you imported. The final binary is uploaded through USB, serial, or another supported programming connection.

The board stores that program in flash memory. It can start the sketch again without your computer attached, as long as it has power.

The official sketch build process explains each step in more detail.

Where is main()?

A normal C++ program starts in a main() function. We do not write one in an Arduino sketch.

The Arduino core already provides it.

In simplified form, it works like this:

int main() {
  initializeArduino();
  setup();

  while (true) {
    loop();
  }
}

The real implementation also initializes board hardware and handles features supplied by the selected core.

This hidden main() function is one reason Arduino feels simpler than starting with raw embedded C++.

Arduino is a family of boards

Be careful with examples that say “Arduino always does this.”

Arduino is not one machine. It is a family of boards using different microcontrollers and processor architectures.

A classic Arduino Uno R3 uses an 8-bit AVR microcontroller and 5V logic. Many newer boards use 32-bit microcontrollers and 3.3V logic.

This can change:

  • the number of pins
  • which pins support PWM
  • the input and output voltage
  • the resolution of analog readings
  • the size of C++ data types
  • the amount of flash and RAM
  • the available communication interfaces

The language remains familiar, but hardware details differ.

My advice is to keep the pinout and documentation for your exact board nearby. Use names such as LED_BUILTIN and A0 when they express what you mean.

The Arduino IDE workflow

The Arduino IDE gives you the editor, board manager, library manager, compiler, uploader, and serial tools in one application.

The normal workflow is:

  1. Connect the board.
  2. Select the board model.
  3. Select its port.
  4. Write or open a sketch.
  5. Click Verify to compile it.
  6. Click Upload to compile and transfer it.
  7. Open Serial Monitor when you need to inspect data.

Selecting the correct board matters. The compiler must produce instructions for the microcontroller you are using.

The port tells the IDE which connected device should receive the program.

Arduino language basics

You do not need to master all of C++ before using Arduino.

You need a small set of ideas first: values, variables, functions, conditions, and loops. Let’s see them in the context of a board.

Statements and semicolons

Most Arduino statements end with a semicolon:

int temperature = 22;
temperature = temperature + 1;

A missing semicolon is one of the most common beginner errors.

Function definitions and control blocks use braces. They do not need a semicolon after the closing brace:

void turnLedOn() {
  digitalWrite(LED_BUILTIN, HIGH);
}

Comments

Comments let you leave notes for yourself and other people.

Use // for a single-line comment:

// Read the potentiometer.
int sensorValue = analogRead(A0);

Use /* and */ for a comment that spans several lines:

/*
  This runs once when the board starts.
*/
void setup() {
}

The compiler ignores comments. Use them to explain why the code does something, not to repeat every line in English.

Variables

A variable gives a name to a value:

int sensorValue = 0;

The type comes first, then the name, then the value.

You can change this variable later:

sensorValue = analogRead(A0);

Arduino sketches commonly use these types:

  • bool stores true or false
  • char stores one character or a small integer
  • int stores a whole number
  • long stores a larger whole number
  • float stores a number with a decimal part
  • unsigned long stores a large non-negative number

The exact size of types such as int depends on the board. An int is 16 bits on the classic Uno R3, but it is 32 bits on many modern boards.

A variable declared inside a function is local to that function. A variable declared outside every function is global and stays available for the life of the sketch.

Global variables are useful for pins and state shared by setup() and loop(). Keep temporary values local when you can.

When size matters, use fixed-width types:

uint8_t brightness = 120;
uint16_t reading = 800;
uint32_t elapsed = 0;

Arrays

An array keeps several values of the same type together:

int readings[3] = { 410, 425, 418 };

Array positions start at zero:

int firstReading = readings[0];

On a microcontroller, an array has a fixed size. Be careful not to read or write past its final position, because C++ will not stop you.

Constants

Use a constant for a value that should not change:

const int buttonPin = 2;
const int ledPin = LED_BUILTIN;

This gives the number a useful name and prevents accidental changes.

Older Arduino examples often use #define:

#define BUTTON_PIN 2

It works, but const gives the compiler more type information. I prefer const for normal values.

Arduino also provides constants including:

  • HIGH and LOW for digital pin states
  • INPUT, OUTPUT, and INPUT_PULLUP for pin modes
  • LED_BUILTIN for the onboard LED pin
  • true and false for boolean values

Conditions

Use if when the program should make a decision.

For example, turn on an LED when a sensor reading passes 600:

if (sensorValue > 600) {
  digitalWrite(ledPin, HIGH);
} else {
  digitalWrite(ledPin, LOW);
}

The condition inside the parentheses is either true or false.

You can combine conditions with && for “and” and || for “or”:

if (temperature > 20 && temperature < 30) {
  Serial.println("Temperature is comfortable");
}

Loops

Arduino already repeats loop(), but you can create smaller loops inside it.

A for loop is useful when you know how many repetitions you need:

for (int count = 0; count < 3; count++) {
  digitalWrite(LED_BUILTIN, HIGH);
  delay(100);
  digitalWrite(LED_BUILTIN, LOW);
  delay(100);
}

This flashes the LED three times.

Be careful with long loops. While a loop is running, the rest of your main program must wait.

Functions

Functions let you give a name to a piece of behavior.

Instead of repeating two digitalWrite() calls everywhere, create a function:

void setLed(bool on) {
  if (on) {
    digitalWrite(LED_BUILTIN, HIGH);
  } else {
    digitalWrite(LED_BUILTIN, LOW);
  }
}

Now the intent is clear:

setLed(true);
delay(1000);
setLed(false);

A function can also return a value:

int readLightLevel() {
  return analogRead(A0);
}

Small functions make a sketch easier to read, test, and change.

Digital input and output

A digital pin works with two logical states: HIGH and LOW.

On an output pin, these states control the voltage level. On an input pin, they describe the level the board detected.

Do not treat HIGH as a universal voltage. It depends on the board.

Configure a pin with pinMode()

Before using a digital pin, set its mode:

pinMode(7, OUTPUT);

For an input:

pinMode(2, INPUT);

An unconnected input can float. Electrical noise may make it switch between HIGH and LOW.

Arduino can enable an internal pull-up resistor for you:

pinMode(2, INPUT_PULLUP);

Wire a button between that pin and ground. The input will read HIGH when the button is open and LOW when pressed.

This reversed logic surprises almost everyone the first time.

Write a digital output

Use digitalWrite() after setting the pin as an output:

digitalWrite(7, HIGH);

Turn it off with:

digitalWrite(7, LOW);

An I/O pin can drive a signal or a small load such as an LED with the correct resistor.

Do not power motors, pumps, relays, or high-current LED strips directly from a pin. Use a suitable transistor, driver, protection circuit, and external power supply.

Read a digital input

Use digitalRead() to get the current state:

int buttonState = digitalRead(2);

Then react to it:

if (buttonState == LOW) {
  digitalWrite(LED_BUILTIN, HIGH);
}

My digital input project shows the complete button circuit and explains INPUT_PULLUP in practice.

Analog input

The physical world is not limited to on and off.

Temperature, light, sound, distance, and position can change across a range. An analog-to-digital converter, or ADC, turns an input voltage into a number your sketch can use.

Read an analog pin with analogRead():

int value = analogRead(A0);

On an Arduino Uno R3, the default 10-bit reading ranges from 0 to 1023.

0 represents the bottom of the configured input range. 1023 represents the top.

Other boards can use different resolutions and reference voltages. Some let you change the resolution with analogReadResolution().

Never apply a voltage outside the safe input range for your board. A 5V signal that works on an Uno can damage a 3.3V-only board.

The analog input project shows how to wire and read a potentiometer.

Analog output and PWM

Despite its name, analogWrite() does not always produce a true analog voltage.

On a classic Arduino Uno R3, it produces pulse-width modulation, or PWM, on supported digital pins.

PWM switches the pin on and off very quickly. The percentage of time it stays on is called the duty cycle.

For example:

analogWrite(9, 0);

This keeps the output off.

The midpoint gives roughly a 50% duty cycle:

analogWrite(9, 127);

And the maximum keeps it on:

analogWrite(9, 255);

On the Uno R3, only pins marked with ~ support PWM. Other boards have different PWM pins, value ranges, and capabilities. Some boards also have true digital-to-analog converter outputs.

My guide to analogWrite() and PWM shows how the signal controls LED brightness.

Time: delay() and millis()

delay() is easy to understand:

delay(1000);

It pauses the main sketch for one second.

This is fine for a first blink. It becomes a problem when the board must handle several jobs.

Imagine waiting one second between sensor readings. During that pause, the sketch cannot check a button in its normal loop.

Use millis() when you need to track time without stopping everything else.

millis() returns the number of milliseconds since the sketch started:

unsigned long now = millis();

Here is a non-blocking blink:

const unsigned long interval = 1000;

unsigned long previousTime = 0;
bool ledOn = false;

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  unsigned long now = millis();

  if (now - previousTime >= interval) {
    previousTime = now;
    ledOn = !ledOn;
    digitalWrite(LED_BUILTIN, ledOn ? HIGH : LOW);
  }

  // Other work can happen here.
}

The subtraction is deliberate:

now - previousTime >= interval

On boards where millis() uses a 32-bit unsigned value, it wraps around after about 49.7 days. Unsigned subtraction keeps this timing pattern working across the wrap.

My advice is to learn this pattern early. It is the foundation for responsive Arduino programs.

Serial communication and debugging

You cannot inspect an Arduino program with a normal browser console.

The simplest debugging tool is usually the serial connection between the board and your computer.

Start it in setup():

void setup() {
  Serial.begin(9600);
}

Then print a value:

void loop() {
  int value = analogRead(A0);
  Serial.println(value);
  delay(250);
}

Open Serial Monitor in the Arduino IDE. Set it to the same baud rate used by Serial.begin().

Serial.print() keeps writing on the same line. Serial.println() adds a line ending.

You can also receive data:

if (Serial.available() > 0) {
  char command = Serial.read();

  if (command == '1') {
    digitalWrite(LED_BUILTIN, HIGH);
  }
}

Print the values that help you understand the program. Do not flood the serial connection on every fast loop unless you need that volume.

See my Arduino serial communication guide for sending and receiving data in both directions.

Useful built-in functions

The Arduino core includes many helpers. You will use a small group in most projects.

AreaFunctionsWhat they do
Digital I/OpinMode(), digitalRead(), digitalWrite()Configure, read, and write digital pins
Analog I/OanalogRead(), analogWrite()Read analog inputs and control supported outputs
Timedelay(), delayMicroseconds(), millis(), micros()Pause or measure elapsed time
SerialSerial.begin(), Serial.available(), Serial.read(), Serial.print()Communicate with a computer or another device
Mathabs(), min(), max(), constrain(), map()Transform and limit numeric values
Soundtone(), noTone()Generate and stop a square wave for a buzzer
Pulses and bitspulseIn(), shiftIn(), shiftOut(), bitRead(), bitWrite()Work with pulses, shift registers, and individual bits
InterruptsattachInterrupt(), detachInterrupt()React to supported hardware events

The official Arduino language reference is the best place to check parameters, return values, and board-specific notes.

Transform values with map() and constrain()

Sensors and outputs often use different ranges.

On an Uno R3, a potentiometer reading might range from 0 to 1023. PWM brightness ranges from 0 to 255.

Use map() to convert between them:

int brightness = map(sensorValue, 0, 1023, 0, 255);

Use constrain() when a value must remain inside a range:

brightness = constrain(brightness, 0, 255);

map() uses integer math. It does not clamp values by itself, so constrain() is useful when the input may pass its expected limits.

Libraries

A library packages code you can reuse.

For example, the Servo library handles the timing needed to control a servo motor. You include it at the top of the sketch:

#include <Servo.h>

Then create and use an object provided by the library:

Servo arm;

void setup() {
  arm.attach(9);
  arm.write(90);
}

The Arduino IDE Library Manager can install official and community libraries.

Check that a library supports your board architecture. A library written directly for AVR hardware may not work on an ARM or ESP32-based board.

Common libraries and interfaces include:

  • Wire for I2C communication
  • SPI for SPI communication
  • Servo for hobby servo motors
  • SD for memory cards
  • board-specific Wi-Fi and Bluetooth libraries

My Arduino libraries guide shows the IDE workflow.

Interrupts

The normal loop() model is enough for most beginner projects.

An interrupt asks the microcontroller to pause normal code briefly and run a small function when a hardware event happens.

For example:

volatile bool buttonPressed = false;

void handleButton() {
  buttonPressed = true;
}

void setup() {
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), handleButton, FALLING);
}

The interrupt service routine, handleButton(), should do as little work as possible.

Set a flag, store a tiny piece of data, and return. Let loop() perform the larger job.

Variables shared with an interrupt should usually be marked volatile. Avoid delay() and long serial operations inside the interrupt routine.

Interrupt support and valid pins depend on the board.

Memory matters on a microcontroller

A computer gives your program gigabytes of memory. A small microcontroller might give it only a few kilobytes of RAM.

Arduino boards normally use several kinds of memory:

  • flash stores the compiled program
  • RAM stores variables while the program runs
  • EEPROM or other non-volatile storage can keep small values after power is removed, when the board provides it

The exact sizes and storage options depend on the board.

On small boards, avoid keeping large arrays and long strings in RAM without a reason.

Repeated String concatenation can also make memory use less predictable on RAM-limited AVR boards. Fixed character buffers are often a safer choice for long-running programs.

On AVR boards, the F() macro can keep a string literal in flash when printing it:

Serial.println(F("Sensor ready"));

Do not optimize every byte before you have a problem. Start with clear code, watch the memory report after compilation, and simplify when the board is close to its limit.

Organizing a larger sketch

A one-file sketch is perfect at the start.

As the program grows, separate code by responsibility. You might keep sensor code in one pair of files and display code in another:

WeatherStation/
  WeatherStation.ino
  Sensor.cpp
  Sensor.h
  Display.cpp
  Display.h

The main .ino file can describe the program at a high level. The C++ files hold implementation details.

Arduino preprocesses .ino files, but it does not preprocess normal .cpp files in the same way. Include the headers you need and write normal C++ declarations there.

For reusable code shared across sketches, create or install a library.

A complete project: potentiometer-controlled LED

Let’s combine analog input, PWM output, serial communication, functions, and non-blocking timing.

This example targets an Arduino Uno R3.

You need:

  • a 10kΩ potentiometer
  • an LED
  • a 220Ω to 1kΩ resistor
  • a breadboard and jumper wires

Connect one outside leg of the potentiometer to 5V and the other to GND. Connect its middle leg to A0.

Connect PWM pin 9 to the LED anode through the resistor. Connect the LED cathode to GND.

Then upload this sketch:

const int sensorPin = A0;
const int ledPin = 9;
const unsigned long reportInterval = 250;

unsigned long previousReport = 0;

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  int sensorValue = analogRead(sensorPin);
  int brightness = map(sensorValue, 0, 1023, 0, 255);

  analogWrite(ledPin, brightness);

  unsigned long now = millis();

  if (now - previousReport >= reportInterval) {
    previousReport = now;

    Serial.print("Sensor: ");
    Serial.print(sensorValue);
    Serial.print(" Brightness: ");
    Serial.println(brightness);
  }
}

Turn the potentiometer. The LED brightness follows its position.

At the same time, Serial Monitor receives a new report four times per second. The sketch does not use delay(), so it remains free to handle more work.

If you use another board, check its voltage, ADC range, and PWM pins first. Replace 1023, 255, and pin 9 when its specifications differ.

Common Arduino programming mistakes

Most early Arduino problems come from a small set of causes.

The wrong board or port is selected

If compilation or upload fails, confirm both selections in the IDE.

Some boards also need a board package installed through Boards Manager.

The wiring does not share ground

Two powered parts of a circuit need a common reference. Connect their grounds when the circuit design requires it.

An input is floating

Use a pull-up or pull-down resistor. INPUT_PULLUP is the easiest option for many buttons and switches.

analogWrite() is treated as analog input

analogRead() reads an analog input. analogWrite() controls an output, commonly using PWM.

The names are similar, but the operations are different.

delay() blocks important work

Replace long delays with elapsed-time checks using millis().

The serial baud rates do not match

Use the same rate in Serial.begin() and Serial Monitor.

A pin has two jobs

Pins used by serial, I2C, SPI, timers, or a shield may conflict with your own I/O code. Check the board and library documentation.

The circuit asks too much from a pin

Microcontroller pins are for signals and small loads. Use drivers for motors, relays, solenoids, pumps, and powerful lights.

Board-specific assumptions slip into the code

Pin 13 is not the built-in LED everywhere. analogRead() is not always 10-bit. Logic is not always 5V.

Prefer named constants and verify the exact board.

Can you program Arduino with another language?

Yes, but the answer depends on the board and what you mean by “program.”

Some supported boards can run MicroPython directly. Other tools run Python, JavaScript, or Go on a computer and control a board over serial, often using Firmata firmware.

In that second setup, the higher-level program does not run on the small Arduino microcontroller. It runs on the connected computer and sends commands to the board.

The Arduino C++ toolchain remains the most portable path across the ecosystem. It also gives you direct access to the core APIs, libraries, timing, and hardware.

How I would use Arduino

I would use Arduino for a small device with one clear job.

A plant watering controller is a good example. It can read soil moisture, decide when watering is needed, switch a pump through a proper driver, and report its state.

I would also use it for a temperature logger, a custom control panel, a light installation, or a simple robot.

The tight connection between code and hardware is Arduino’s strength.

I would not choose a small classic Arduino for a rich graphical interface, heavy image processing, a large local database, or software that needs many operating-system services. A Raspberry Pi or another Linux computer is a better fit there.

Modern Arduino boards cover a much wider range than the Uno, so always judge the specific board. Still, the best Arduino projects usually have a focused purpose and predictable behavior.

Arduino programming FAQ

Is the Arduino language C or C++?

Arduino sketches are compiled as C++. The framework keeps the first steps simple, but you can use classes, function overloading, templates, and other C++ features supported by the selected toolchain.

You can also add .c files to a sketch when you need C code.

Do I need to learn C++ first?

No. You can start with setup(), loop(), variables, conditions, and pin functions.

Learn more C++ as your projects need functions, classes, data structures, and better organization.

How fast does loop() run?

It has no fixed rate.

Arduino calls it again as soon as the previous call ends. The speed depends on your code, the processor, library work, interrupts, and blocking functions such as delay().

Can an Arduino run two sketches at once?

A traditional Arduino microcontroller runs one compiled program. That program can manage many tasks by checking each one quickly inside loop().

Some advanced boards and third-party cores support threads or multiple processor cores, but that is board-specific.

Does an Arduino keep the sketch after power is removed?

Yes. The compiled program is stored in non-volatile flash memory.

When power returns, the board starts it again.

Can I use normal C++ classes?

Yes. Arduino libraries are commonly built with C++ classes.

On small boards, remember that every abstraction still uses program memory, RAM, and processor time.

What to learn next

The best next step is a physical project.

Start by blinking an LED. Then read a button and read a potentiometer.

Those three projects teach the central Arduino loop: read the world, make a decision, and control an output.

Once that feels natural, add serial debugging, replace delays with millis(), and bring in one library at a time.

Tagged: Arduino · All topics

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about electronics: