Control Your Arduino With Python's Pyfirmata Library

by Fasterlearner in Circuits > Arduino

6995 Views, 5 Favorites, 0 Comments

Control Your Arduino With Python's Pyfirmata Library

PyArduinoCover.jpg

Learning to design, build, and program robots with Arduino is a really fun pastime. I've been doing it for a few years now; I've built robot arms, quadrupeds, lighting controllers, and more. But... I never have liked coding in C++ as much as I love using Python.

So I learned to use Python, with all of its power and flexibility, to control my Arduinos. Running Python on another computer can allow you to use powerful functions such as facial recognition, optical obstacle avoidance, and more with your Arduino! One of my goals is to use Blender 3D's python engine and inverse kinematics to control a robot. You can run your Python scripts on a desktop, laptop, or Raspberry Pi. In this Instructable, I will be using my laptop (Windows OS) for demonstrations.

When I was learning how to do this myself, I found a lot of useful information here on Instructables for using the pyserial library. The pyserial library is really useful, but I wanted to do most of my programming in Python. So, I will not be making yet another tutorial for using the pyserial library with Arduino. We will be using the pyfirmata and keyboard libraries instead.

I will walk you through installing the necessary Python libraries and setting up your Arduino. Then we can cover some basic functions like the basic "blink" routine, moving a servo, and reading analog inputs.

Supplies

DSCF0537.JPG

You will need:

  • Arduino and suitable USB cable
  • Computer with USB port(s) and internet connection
  • 220 Ohm Resistor
  • LED
  • Hobby servo (I'm using a 9-gram, 180 degree servo)
  • Potentiometer
  • breadboard
  • connector wires
  • And last, but not least, some motivation!

Installing Software

Logos.jpg

If you've used Python before, then it's probably installed on your machine, and you can skip this step. If you haven't, don't worry too much - it's not hard to learn. We'll start by getting Python installed on your device so that it knows what to do with your code.

Go to https://www.python.org/downloads/ to download and install the latest version of Python. Since Python is open source, it is absolutely free to install and use!

You will also need the Arduino IDE, if you don't have it already. I use version 1.6.11 for compatibility with Ardublock (an addon), but you can use whatever version you like for this project. You can get the IDE here: https://www.arduino.cc/en/Main/Software_

If you don't already have a code editor, I recommend getting one. You can edit code in a plain-old text editor, but It will be way slower and rather painful. I have tried a few, but I like VS Code the best. You can download and install the latest version of it here: https://code.visualstudio.com/download

I also like to work within Python "environments" that I create for my projects. That way, if I install a package that messes something up, I can just delete the environment instead of uninstalling/reinstalling Python. I use Anaconda for creating and managing my environments, and will include a step for setting it up here. If you like this idea too, you can get Anaconda here: https://www.anaconda.com/products/individual

Creating the Python Environment

Screenshot (25).png
Screenshot (26).png

If you haven't installed Anaconda (and don't want to), then you can skip this step.

Open Anaconda prompt.

Once it comes up with something like

(base) C:\Users\yourusername>

you're ready to get started. The word "base" in parentheses is the name of the default environment that anaconda sets up when it is installed. If you screw something up in this environment, it can give you a lot of problems. We want to mitigate that risk, so type

conda create --name Arduino --clone base

and hit enter. This will create a clone of the base environment named "Arduino" that we can use a safe playground. Once it is finished, it will display the

(base) C:\Users\yourusername>

line again. Now, type

conda activate Arduino

to activate our new environment.

If you have a shortcut to open anaconda navigator, you can open that now. Otherwise, type

anaconda-navigator

to launch the navigator app.

Once the app opens, you should see a bunch of shortcut tiles for other programming tools included with Anaconda. Above those, there is a dropdown menu that says "base (root)". Open the menu, and select your new "Arduino" environment. When it finishes loading, find the VS Code tile and select it.

VS Code may take a minute to open, especially the first time. Once it does, give it a minute to get set up. Then, go to the left side of the screen and select the "extensions" tab. When it opens, type "python" into the search bar in that tab. Select the result that has Microsoft as the author, and install it. When you're finished with that, go ahead and close/reopen VS Code. In the bottom-left corner (on the blue ribbon), it should have the version of Python you are running, as well as the name of your Arduino environment. For example, mine says:

Python 3.8.8 64-bit ('Arduino': conda)

If yours looks the same (except for maybe the Python version), you have a green flag to start coding! If not, click on the area of the ribbon that has the version and environment. This should create a dropdown menu somewhere at the top of your screen showing all the interpreters that VS Code can find. Select your 'Arduino' environment.

Getting Your Environment and Arduino Ready!

Screenshot (29).png
Screenshot (30).png
Screenshot (31).png

Now you need create/select a file folder to store your scripts in. For example, I created a folder in my Documents folder named "PYTHON". Inside of that folder, I created one called "Arduino". When you're finished, go to the "Explorer" tab on the left side of VS Code, and navigate to your new folder.

Select the folder in the explorer tab, and then right-click it and select "New File". This will create a new file inside your scripts folder, and give you the opportunity to name it. I suggest naming it something like "Arduino_Experiments.py". If you don't include the ".py" on the end, it will default to a .txt file type, which is not what you want.

The file should automatically open in your editor window. When it does, you can type up the following:

#%% import libraries and see if they work
import os
import time

os.system("pip3 install keyboard")
import keyboard
while True:
    if keyboard.is_pressed('space'): 
        print('Keyboard library works... Awesome!')
        break

os.system("pip install pyfirmata")
import pyfirmata
board = pyfirmata.Arduino('COM3')
while True:
    if keyboard.is_pressed('space'):
        board.digital[13].write(1)
        time.sleep(0.1)
    else:
        board.digital[13].write(0)
        time.sleep(0.1)

Press ctrl+s to save your changes. It's always important to do this very frequently when making changes - losing your progress is easier than it may sound.

Note that the "import keyboard" and "import pyfirmata" lines are probably underlined with yellow, like in my screenshot above. This is because they haven't been installed yet, which is what the lines immediately above them are meant to do. Also note that I am preparing to run this script in my "gamedev" environment, since I already installed them in my "Arduino" environment.

Now, before we can run this, we need to get the ID of the COM port our Arduino is plugged into, and tell our Arduino that it should be expecting instructions. Plug your Arduino into one of your computer's USB ports, open the Arduino IDE, and select Tools>Board: from the upper left. In the menu that appears, select the model that you have plugged into your computer. Now select Tools>Port: and select the option that is highlighted. Replace the 'COM_PORT_HERE' in your Python script with the name of the port you selected. In my case, that is 'COM3'.

Back in the Arduino IDE, select File>Examples>Firmata>StandardFirmata. This will load a script in the IDE that is built to handle commands from software on your computer using the appropriate firmata library (pyfirmata, in our case). Click the arrow pointing to the right in the top-left corner of your screen to upload this script to your Arduino. When it is finished, head back to VS Code!

Click somewhere on your script, and hit shift+enter to run the script in an interactive window. Wait a minute or two for VS Code to install the keyboard library, and then click in the interactive window and press the space bar. If you get the success message, pat yourself on the back. The tricky bit is almost over! Wait another minute or two, and then press the spacebar a few more times. If you see the built-in LED on pin 13 of your Arduino blinking in response, Congratulations! You just did in 10 minutes what took me several days to figure out!

Those two import lines are probably still underlined in yellow, but a quick(ish) restart of VS Code should make that go away. You can now comment out or delete the os.system lines.

By the way, putting a "#" character at the beginning of a line will comment that line so that it is not run like code. To comment out an entire block of code, select it and press ctrl+k+c. To uncomment, press ctrl+k+u.

Putting a "#%%" before and after a block of code will make a "cell" of code that you can run independently of the rest of your script by pressing shift+enter.

Blink Sketch

Screenshot (38).png
Screenshot (40).png

While testing our library imports, we already proved that we can make the built-in LED on pin 13 blink when we press the spacebar. But in case you want another example, here's a script that will make it blink automatically:

#%% import libraries
import time
import keyboard
import pyfirmata

# set up arduino board
board = pyfirmata.Arduino('COM3')

# start while loop to keep blinking indefinitely
while True: 

    if keyboard.is_pressed('esc'): # stop making the arduino blink if the escape key is pressed
        break

    board.digital[13].write(1) # turn pin 13 ON
    time.sleep(0.5)            # wait 1/2 second
    board.digital[13].write(0) # turn pin 13 OFF
    time.sleep(0.5)            # wait 1/2 second

This imports the libraries you need, makes Python start talking to the Arduino, and starts a loop that will make the LED on pin 13 blink indefinitely. The "if" statement is there so that you can stop the script whenever you want by pressing the escape key.

If you want, you can also plug a 220-Ohm resistor into pin 13, and connect it to the positive side (long leg) of an LED. Connect the negative side of the LED (short leg) to one of the ground (GND) pins on the Arduino. This will be a bit more visible than the built-in LED.

Keyboard Servo Control Sketch

Screenshot (33).png
Screenshot (33).png

As a mechanical engineer, I love building robots. Especially ones that move. I've used my Arduino with hobby servos to build robot arms and quadrupeds in the past; those were some of my favorite projects. But changing, compiling, and uploading the code for the robot always wore on my patience. With Python's pyfirmata library, this process can take a little less time. You can just change the code in the editor and run it. So, here's how to control a servo with your keyboard:

Important note: I am using a 180 degree servo (these are usually black, not blue like the one in the diagram). The code we are about to write will make it rotate to whatever position (in degrees, between 0 and 180) we want. However, if you are using a continuous servo (usually blue), the number you pass to the servo will make it rotate at a certain speed in a certain direction. The code should still work, but the results will be different.

Go ahead and wire up your servo+Arduino, and plug your Arduino into your computer. I like to be consistent with which USB port I use to avoid headaches. If you're not sure which wires go where, see the diagram above. The negative wire goes to a GND pin and is usually black or blue. The positive wire is usually red, and goes to the 5V pin. The COM wire is usually orange or brown, and goes to a digital output pin (I'll be using digital pin 2, like my diagram above).

Make a new file in your "Arduino" folder for controlling your servo. I called mine Servo_Control.py, but you can be as creative as you want (when I get bored while programming, I start making puns and jokes with my variable names... you can do whatever you want when coding!). Go ahead and copy/paste the first few lines from the blink sketch into your new file and update the COM port if needed:

#%% import libraries
import time
import keyboard
import pyfirmata

# set up arduino board
board = pyfirmata.Arduino('COM3')

Now we're going to add a block of code that will set the initial servo angle and rotation speed:

#%% setup servo on pin 2
angle  = 90                     # initial angle
da = 5                          # initial speed (degrees per keypress)
servo1 = board.get_pin('d:2:s') # pin to communicate to the servo with
servo1.write(angle)             # set servo to initial angle

Now we need an easy way of telling the servo to move to any position on demand. One way of doing this is to set up a simple function that tells the Arduino to relay instructions to the servo:

# set up a function that will tell the servo to move to a specific position when called
def move_servo(angle):  # define function
    servo1.write(angle) # move servo to specified angle

Now all we need is a way to call this function when we press a button on our keyboard.

A quick tip here is to type

#%%

before starting the next bit. That way, you can run all of the above code just once (by pressing shift+enter), and then continue writing/editing everything after it without running the imports all over again.

A while loop will tell Python to continuously check whether or not we are pressing a key, and relay information to the Arduino accordingly.

#%% while loop 
while True:

    if keyboard.is_pressed('a'): # check if a is pressed
        if angle + da < 180:     # check to make sure new angle will not exceed 180 degrees
            angle = angle + da   # if new angle is OK, change to it
            move_servo(angle)    # set servo position to new angle by calling the function we made earlier
            time.sleep(0.1)      # wait a little bit (0.1 seconds) before checking again

Now, go ahead and run the first section of code (everything up to the while loop) by clicking somewhere inside it and pressing shift+enter. When it is finished, you should have a new "interactive window" on the right side. Press shift+enter again to run the while loop, and click somewhere inside the interactive window. The servo should now turn clockwise when you press the 'a' key (or whatever key you used). When you're done, click the red square button that says 'interrupt' near the top to stop running the script. It'll come up with a bunch of ugly warning messages, but don't worry - everything is fine.

Let's add a few more features to this while loop!

#%% while loop 
while True:

    if keyboard.is_pressed('a'): # check if a is pressed
        if angle + da < 180:     # check to make sure new angle will not exceed 180 degrees
            angle = angle + da   # if new angle is OK, change to it
            move_servo(angle)    # set servo position to new angle by calling the function we made earlier
            time.sleep(0.1)      # wait a little bit (0.1 seconds) before checking again
    
    elif keyboard.is_pressed('d'): 
        if angle - da > 0:         # check to make sure new angle will not exceed 0 degrees
            angle = angle - da     
            move_servo(angle)      
            time.sleep(0.1)        
    
    elif keyboard.is_pressed('w'): # if w is pressed, increase da by 1
        if da + 1 < 180:
            da = da + 1
            time.sleep(0.1)
   
    elif keyboard.is_pressed('s'): # if s is pressed, decrease da by 1
        if da - 1 > 0:
            da = da - 1
            time.sleep(0.1)
    
    elif keyboard.is_pressed('r'): # if r is pressed reset a and da
        angle = 90
        da = 5
        move_servo(angle)
        time.sleep(0.1) 

    elif keyboard.is_pressed('esc'): # if esc is pressed, quit script
        break

Your script should now let you control the direction of your servo by pressing 'a' and 'd', and the speed with 'w' and 's'. If you press 'r', it should reset to its original position and speed, and pressing 'esc' should quit the script. Bigger step sizes make the servo a little 'jerky', but if you're as impatient as I am, you probably won't care too much.

Play around with these if/elif statements to figure out exactly what's going on!

Analog Read With a Potentiometer

Screenshot (35).png
Screenshot (36).png

A useful feature of Arduinos is their ability to read a variable (analog) input and use that value to make decisions and/or control a device such as an LED. Arduinos typically measure a range of 0-5 V, and map those readings to values of 0-255. However, pyfirmata reports those values with a smaller range (0-1). If it's important to use values between 0 and 255 for your application, you can simply multiply the value from pyfirmata by 255 (they can be used as percentages of 255).

Make a new .py file, and name it whatever you want. I called mine "Potentiometer.py". Then go ahead and paste the same first few lines we've used in all of our files so far:

#%% import libraries
import time
import keyboard
import pyfirmata

# set up board
board = pyfirmata.Arduino('COM3')

Now, here's the thing about analog read. If you've been looking at other tutorials for the pyfirmata library, you've probably seen lines like this:

it = pyfirmata.util.Iterator(board)
it.start()

Until now, we haven't needed it. As far as I can tell, it starts a "thread" that is required for using some functions. Go ahead and put it in your script now.

Previously, we have only accessed digital pins and written outputs from them. To initialize an analog pin for input, type the following:

board.analog[0].mode = pyfirmata.INPUT

This tells your script that you have plugged something into the A0 pin on your Arduino. In our case, that something is a wire connected to the middle pin of your potentiometer. If you haven't already done so, refer to the image above and construct your circuit.

Finally, write the following while loop. The "RawValue" variable is the value pyfirmata gets from the Arduino, ranging from 0-1. We can multiply that value by 255 to get a more standard voltage reading. This is what the "MappedValue" variable is doing. To avoid flooding the interactive window with values instantaneously, I added a time.sleep() function to make the script wait for 0.5 seconds before repeating. I also added an escape hotkey like we did in the servo script.

#%%
while True:
    RawValue = board.analog[0].read()
    MappedValue = RawValue*255
    print(MappedValue)
    time.sleep(0.5)

    if keyboard.is_pressed('esc'): # if esc is pressed, quit script
        break

And that's it! go ahead and plug in your Arduino, and run your script!

After the imports are finished, hit shift+enter a second time (if you included #%% before the while loop like I did) to start collecting and printing values. If all goes well, it should start printing the mapped voltages to the interactive window.

Try switching out MappedValue with RawValue in the print() line to see the difference. Once you've got the hang of that, try using MappedValue as an output for an LED on an analog pin to control the brightness.

Further Learning

There you have it!

You just learned to control your Arduino with one of the most powerful and flexible coding languages out there. You can use this to do practically anything you can imagine with your Arduino. The only drawback is that the Arduino itself cannot run your Python script - that has to be done on something like a computer or Raspberry Pi.

If you liked this Instructable and want to learn more about using Pyfirmata, here are some all-around good resources I found while I was learning this myself:

Official pyfirmata documentation:

https://pyfirmata.readthedocs.io/en/latest/

pyfirmata description, installation, and board layout:

https://pypi.org/project/pyFirmata/

Arduino firmata documentation:

https://www.arduino.cc/en/reference/firmata

Youtube Tutorial by APMonitor.com:

https://www.youtube.com/watch?v=RS35q6ksU6w

I really hope you enjoyed learning this skill as much as I did, and I can't wait to see what you make with it!

Have fun!