// Programming Tutorial
Learn to write Arduino sketches in C++ and orchestrate them with Python — from a blinking LED to real-time serial communication.
// 00 — Overview
This tutorial teaches you to write Arduino sketches (C++) and then use Python on your PC to send commands and read data over a USB serial port. By the end you will have a fully functional LED control system driven by Python.
Key insight: Arduino runs its own C++ sketch independently. Python communicates with it via the serial port — you're orchestrating hardware from a high-level language without replacing the sketch.
// 01 — Setup
STEP 01
Install Arduino IDE 2
Download and install the Arduino IDE 2.x from the official website. Connect your Arduino Uno via USB. The IDE auto-detects the port on most systems.
# On Linux/macOS — list serial ports ls /dev/tty* # Typical Arduino Uno output: /dev/ttyACM0 # Linux /dev/cu.usbmodem14101 # macOS # On Windows (PowerShell): Get-WMIObject Win32_SerialPort
On Linux, add your user to the dialout group: sudo usermod -aG dialout $USER, then log out and back in.
STEP 02
Install Python Dependencies
We use pyserial to communicate with the Arduino over USB serial, and optionally rich for prettier terminal output.
pip install pyserial rich # Verify installation python -c "import serial; print(serial.__version__)" # Expected: 3.5 or higher
STEP 03
Hardware: LED + Resistor Circuit
Wire a standard 5mm LED with a 220Ω resistor between Pin 13 and GND. Most Uno boards have a built-in LED on Pin 13 — no external wiring required for the first sketch.
| Arduino Pin | Component | Notes |
|---|---|---|
| Pin 13 | LED anode (+) via 220Ω resistor | Built-in LED also on this pin |
| GND | LED cathode (−) | Any GND pin works |
| 5V | Power rail (optional) | For external components |
| A0–A5 | Analog input (sensors) | 10-bit ADC, 0–5 V range |
| D2–D13 | Digital I/O | PWM on 3, 5, 6, 9, 10, 11 (~) |
// 02 — Arduino Sketch (C++)
Every Arduino sketch has two required functions: setup() runs once at power-on,
and loop() runs continuously. Our sketch will listen for single-character commands
sent by Python over the serial port.
Upload this to confirm your hardware works before adding serial communication.
// blink.ino — classic first sketch const int LED_PIN = 13; void setup() { pinMode(LED_PIN, OUTPUT); // set pin as output } void loop() { digitalWrite(LED_PIN, HIGH); // LED ON delay(1000); // wait 1 second digitalWrite(LED_PIN, LOW); // LED OFF delay(1000); }
This is the sketch Python will talk to. It reads a character from the serial port:
'H' → LED on, 'L' → LED off, 'S' → read analog sensor.
// serial_control.ino // Commands: H=LED on, L=LED off, S=read analog A0 const int LED_PIN = 13; const int SENSOR_PIN = A0; char cmd; void setup() { Serial.begin(9600); // open serial at 9600 baud pinMode(LED_PIN, OUTPUT); Serial.println("READY"); // handshake signal } void loop() { if (Serial.available() > 0) { cmd = (char) Serial.read(); switch (cmd) { case 'H': digitalWrite(LED_PIN, HIGH); Serial.println("LED_ON"); break; case 'L': digitalWrite(LED_PIN, LOW); Serial.println("LED_OFF"); break; case 'S': { int val = analogRead(SENSOR_PIN); // 0–1023 Serial.print("SENSOR:"); Serial.println(val); break; } default: Serial.println("ERR:UNKNOWN_CMD"); } } }
Upload the sketch via Arduino IDE → Upload (Ctrl+U) before running any Python script. The sketch stays on the board even after power-off.
// 03 — Python Control
pyserial lets Python open a virtual COM port and exchange bytes with the running Arduino sketch. The pattern is always: open port → wait for handshake → send command → read response.
import serial import time # ── configuration ────────────────────────────────── PORT = "/dev/ttyACM0" # Windows: "COM3", macOS: "/dev/cu.usbmodem..." BAUD = 9600 TIMEOUT = 2 # seconds before read() gives up # ── open serial port ─────────────────────────────── with serial.Serial(PORT, BAUD, timeout=TIMEOUT) as ard: time.sleep(2) # wait for Arduino reset after connect handshake = ard.readline().decode("utf-8").strip() print(f"Arduino says: {handshake}") # expects "READY"
import serial, time, sys def send_command(port, cmd: str) -> str: """Send a single-char command and return the response line.""" port.write(cmd.encode("utf-8")) return port.readline().decode("utf-8").strip() def main(): port_name = sys.argv[1] if len(sys.argv) > 1 else "/dev/ttyACM0" with serial.Serial(port_name, 9600, timeout=2) as ard: time.sleep(2) # allow board reset print(ard.readline().decode().strip()) # print "READY" # ── blink 5 times from Python ───────────────── for i in range(5): resp = send_command(ard, "H") # LED on print(f" [{i+1}/5] {resp}") time.sleep(0.5) resp = send_command(ard, "L") # LED off print(f" [{i+1}/5] {resp}") time.sleep(0.5) # ── read analog sensor ──────────────────────── print("\n── Sensor reading ──") resp = send_command(ard, "S") print(resp) # e.g. "SENSOR:512" if __name__ == "__main__": main()
A simple REPL loop so you can type commands in real time.
import serial, time PORT = "/dev/ttyACM0" with serial.Serial(PORT, 9600, timeout=1) as ard: time.sleep(2) print(ard.readline().decode().strip()) print("Commands: H=on L=off S=sensor Q=quit") while True: cmd = input(">> ").strip().upper() if cmd == "Q": print("Disconnecting."); break if cmd in ("H", "L", "S"): ard.write(cmd.encode()) print(" ← ", ard.readline().decode().strip()) else: print("Unknown command")
// 04 — Going Further
PROJECT A
Temperature Logger
Wire an NTC thermistor to A0. Modify the sketch to return temperature strings ("TEMP:23.4"). In Python, log the values to a CSV with datetime timestamps and plot with matplotlib.
Use threading in Python to poll the sensor every second while the main thread handles user input.
PROJECT B
PWM LED Dimmer
Extend the sketch to accept numeric brightness values ('B:128\n') and call analogWrite(LED_PIN, value). In Python, send smoothly interpolated brightness curves — sunrise simulation, breathing effect, etc.
case 'B': { int brightness = Serial.parseInt(); // reads integer after 'B' brightness = constrain(brightness, 0, 255); analogWrite(9, brightness); // PWM pin 9 Serial.print("PWM:"); Serial.println(brightness); break; }
PROJECT C
Web Dashboard with Flask
Wrap the serial logic in a Flask server exposing a REST API (/led/on, /led/off, /sensor). Build a minimal HTML front-end that controls the Arduino from a browser tab on any device on your local network.
from flask import Flask, jsonify import serial, time, threading app = Flask(__name__) ard = serial.Serial("/dev/ttyACM0", 9600, timeout=1) time.sleep(2); ard.readline() # consume READY lock = threading.Lock() def cmd(c): with lock: ard.write(c.encode()) return ard.readline().decode().strip() @app.route("/led/<state>") def led(state): r = cmd("H" if state == "on" else "L") return jsonify(response=r) @app.route("/sensor") def sensor(): return jsonify(data=cmd("S")) if __name__ == "__main__": app.run(host="0.0.0.0", port=5000)
// 05 — Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| SerialException: [Errno 13] | Permission denied | sudo usermod -aG dialout $USER |
| No response from Arduino | Wrong baud rate | Match Serial.begin() and pyserial baud |
| Garbage characters | Reset during read | Add time.sleep(2) after Serial() |
| Port busy / in use | Arduino IDE Serial Monitor open | Close the IDE Serial Monitor first |
| Empty readline() | timeout too short | Increase timeout=2 or higher |
| COM port not found (Windows) | Driver not installed | Install CH340 or FTDI driver |