Based on contributions by ViktorWenemoser1.
This shows how to send text commands from a Python script on your PC to an Arduino over USB serial, so a computer can trigger actions like moving a servo or lighting LEDs. It’s a common foundation for automation projects where the Arduino handles real-time hardware and Python handles logic, a GUI, or a vision pipeline.
What you need
- An Arduino board and a USB cable
- Arduino IDE
- Python 3.x
- The
pyseriallibrary:pip install pyserial - LEDs, a servo, or other peripherals to control (depending on your project)
Steps
1. Design a simple command protocol
Pick a simple text format both sides agree on. This example uses "{command} {param}\n", e.g. servo_on \n or set_all_leds Red\n. The Arduino replies with done\n when it has finished executing a command, so Python knows when it’s safe to send the next one.
2. Write the Arduino sketch
On the Arduino side, implement the functions your commands map to, and a loop() that reads incoming serial text, matches it against your command list, calls the matching function, and finally prints done. The example below shows the LED-control part of such a sketch (using the FastLED library, addressable LED strips on pins 9/10/11):
#include "Led_controller.h"
CRGB leds[NUM_STRIPS][MAX_NUM_LEDS]; // Array to hold LED data for each strip
int dataPins[NUM_STRIPS] = {9, 10, 11}; // Adjust the data pins according to your setup
int numLeds[NUM_STRIPS] = {30, 30, 30}; // Example: 30 LEDs on each strip
/* Initialization (default settings) */
void initialize_leds() {
for (int i = 0; i < NUM_STRIPS; i++) {
// Initialize each LED strip separately
if (i == 0) {
FastLED.addLeds<WS2812B, DATA_PIN_1, RGB>(leds[i], numLeds[i]);
} else if (i == 1) {
FastLED.addLeds<WS2812B, DATA_PIN_2, RGB>(leds[i], numLeds[i]);
} else if (i == 2) {
FastLED.addLeds<WS2812B, DATA_PIN_3, RGB>(leds[i], numLeds[i]);
}
}
set_all_leds(CRGB::Black); // Initialize LEDs as off
FastLED.setBrightness(50); // Set initial brightness
FastLED.show();
delay(500);
}
/* All LED control */
void set_all_leds(CRGB color) {
for (int i = 0; i < NUM_STRIPS; i++) {
for (int j = 0; j < numLeds[i]; j++) {
leds[i][j] = color;
}
}
FastLED.show();
}
/* LED group control */
void set_led_range(int stripIndex, int startLed, int endLed, CRGB color) {
if (stripIndex < 0 || stripIndex >= NUM_STRIPS) {
Serial.println("Invalid strip index!");
return;
}
if (startLed < 0 || endLed >= numLeds[stripIndex] || startLed > endLed) {
Serial.println("Invalid LED range!");
return;
}
for (int i = startLed; i <= endLed; i++) {
leds[stripIndex][i] = color;
}
FastLED.show();
}
/* Load bar control */
void load_bar_range(CRGB color, unsigned long duration, int stripIndex, int startIndex, int endIndex) {
if (stripIndex < 0 || stripIndex >= NUM_STRIPS) {
Serial.println("Invalid strip index!");
return;
}
int totalLeds = endIndex - startIndex + 1;
unsigned long interval = duration / totalLeds;
for (int i = startIndex; i <= endIndex && i < numLeds[stripIndex]; i++) {
leds[stripIndex][i] = color;
FastLED.show();
delay(interval);
}
for (int i = startIndex; i <= endIndex && i < numLeds[stripIndex]; i++) {
leds[stripIndex][i] = CRGB::Black;
}
FastLED.show();
}
Check: this is the LED-control part of the original sketch only. It does not include the
void loop()command dispatcher that reads incoming serial text (e.g.initialize_servo,servo_on,set_all_leds ...) and calls the matching function — that part was not included in the source. You need to write your ownloop()that reads a line withSerial.readStringUntil('\n'), splits it into a command word and parameters, calls the matching function (also add your own servo-control functions if you use a servo), and ends by printingSerial.println("done").
3. Install pyserial and find your COM port
pip install pyserial
On Windows, find the COM port in Device Manager. On macOS/Linux, list ports with ls /dev/tty.*.
4. Write the Python script
import serial
import time
# Connect to the Arduino (adjust the port)
arduino = serial.Serial(port='COM4', baudrate=9600, timeout=.1) # e.g. 'COM4' on Windows, '/dev/ttyUSB0' on Linux
time.sleep(2) # Wait for the Arduino to reset after opening the port
arduino.reset_input_buffer() # Clear the serial buffer
def send_command(command, param=""):
full_command = f"{command} {param}\n"
arduino.write(full_command.encode())
print(f"Sent: {full_command.strip()}")
time.sleep(0.1) # Give the Arduino time to start responding
while True:
response = arduino.readline().decode().strip()
if response == "done":
print("Arduino: done")
break
elif response:
print(f"Arduino: {response}")
def execute_sequence():
"""Execute a sequence of commands."""
commands = [
("initialize_servo", ""), # Initialize the servo
("servo_on", ""), # Move servo to 90 degrees
("initialize_leds", ""), # Initialize LEDs
("set_all_leds", "Red"), # Set all LEDs to red
("set_strip_leds", "0 Blue"), # Set LEDs of strip 0 to blue
("set_led_range", "0 0 9 Red"), # Set LEDs 0-9 on strip 0 to red
("load_bar_range", "Yellow 5000 0 10 20"), # 5-second yellow load bar on LEDs 10-20 of strip 0
("servo_off", ""), # Move the servo back to 0 degrees
]
for command, param in commands:
send_command(command, param)
def execute_sequence2():
"""Execute a second sequence of commands."""
commands = [
("servo_on", ""),
("servo_off", ""),
("initialize_leds", ""),
("load_bar_range", "Green 5000 0 0 30"), # 5-second green load bar on LEDs 0-30 of strip 0
("set_all_leds", "Black"),
]
for command, param in commands:
send_command(command, param)
if __name__ == "__main__":
print("Starting sequence 1...")
execute_sequence()
time.sleep(5)
print("Starting sequence 2...")
execute_sequence2()
print("Program finished.")
Supported commands used above: initialize_servo, servo_on, servo_off, initialize_leds, set_all_leds {color}, set_strip_leds {stripIndex} {color}, set_led_range {stripIndex} {startIndex} {endIndex} {color}, load_bar_range {color} {duration} {stripIndex} {startIndex} {endIndex}.
5. Run it
Save the script (e.g. arduino_communication.py) and run:
python arduino_communication.py
Troubleshooting
- Nothing happens / script hangs: the COM port or baud rate on the Python side doesn’t match the Arduino sketch. Both must use the same value (9600 in this example).
- Script blocks forever waiting for
done: make sure every branch of your Arduinoloop()dispatcher ends by printingdone, otherwise Python’ssend_command()will wait indefinitely. - Use the Arduino IDE’s Serial Monitor to see exactly what bytes are being sent and received while debugging.
- Adjust the
time.sleep()delays in the Python script to match how long your hardware actually needs to move/react.
Related
- How to communicate between a Raspberry Pi and an Arduino over serial — communicating between a Raspberry Pi and an Arduino over serial
- How to control the 5-finger Robot Hand from Arduino and Python — controlling the Robot Hand over serial
Rewritten and consolidated (Sept 2026) from the original student how-to’s: How to Establish Communication Between Python and Arduino.