Python with Arduino


By Prof. Seungchul Lee
http://iai.postech.ac.kr/
Industrial AI Lab at POSTECH

Table of Contents

0. Installation


ANACONDA Install Confirmation


Before getting started with serial communication with Python, you must make sure pip is installed. However, pip will be automatically installed if ANACONDA is installed. Thus, you will have to do this step only if you have not done it last time. Below is the link for the instructions given last time.

http://nbviewer.jupyter.org/github/i-systems/IoT/blob/master/Mechatronics%20Class/HW/Python%20Installation%20Instructions.ipynb


pip pyserial Installation

Open the cmd window (press the windows key + r and type 'cmd'), and type in the following:

python -m pip install pyserial

The download process will begin soon, and the pyserial module will be successively installed.

When you are finished installing pyserial, install bs4 using the same method. Type the following into the command window

python -m pip install bs4

1. LED with python


Starting from this section, we will be using Python along with Arduino. As mentioned in previous classes, the codes being uploaded to the Arduino may have some limitations regarding the functionality. Starting from simple tasks such as byte sending through serial communication, we will be able to plot real time graphs based on the streamed data from Arduino.


$$ \large \text{Python in PC} \quad \overset{\text{Serial}}{\longleftrightarrow} \quad \text{Arduino} \quad \overset{\text{Digital Output}}{\longleftrightarrow} \quad \text{LED}$$


Lab 1: LED ON/OFF - Transmit (numeric) data from Python

Arduino code

int ON = 0;
const int pin = 9;

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

void loop() {    
  if (Serial.available() > 0) {
    ON = Serial.read();

    if (ON == 1)
      digitalWrite(pin, HIGH);
    else if (ON == 0)
      digitalWrite(pin, LOW);

    Serial.println(ON);
  }
}

Python code

Before running this section, please make sure you are using the correct port. The sample code will be using 'COM4'.

In [ ]:
import serial
import time

ser = serial.Serial('COM4', 9600, timeout=1)
In [ ]:
ser.write(bytes([1]))
In [ ]:
ser.write(bytes([0]))

You have to close ser to disconnect. Otherwise other devices cannot use this serial communication.

In [ ]:
ser.close()

Now we are ready to combine all into python with serial communication to Arduino to control LEDs.

In [ ]:
ser = serial.Serial('COM4', 9600, timeout=1)

for i in range(10):
    ser.write(bytes([1]))
    time.sleep(0.5)
    ser.write(bytes([0]))
    time.sleep(0.5)

ser.close()    

Lab 2: LED ON/OFF - Transmit (string) data from Python

Arduino code

String LEDcmd = "";
const int pin = 9;

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

void loop() {
  if (Serial.available() > 0) {
    LEDcmd = Serial.readStringUntil('\n');

    if (LEDcmd == "ON")
      digitalWrite(pin, HIGH);
    else if (LEDcmd == "OFF")
      digitalWrite(pin, LOW);
  }
}

Python code

In [ ]:
str('ON').encode('UTF-8')
In [ ]:
ser = serial.Serial('COM4', 9600, timeout=1)
In [ ]:
ser.write(str('ON').encode('UTF-8'))
In [ ]:
ser.write(str('OFF').encode('UTF-8'))
In [ ]:
ser.close()
In [ ]:
ser = serial.Serial('COM14', 9600, timeout=1)

for i in range(10):
    ser.write(str('ON').encode('UTF-8'))
    time.sleep(0.5)
    ser.write(str('OFF').encode('UTF-8'))
    time.sleep(0.5)

ser.close()

Lab 3: LED Brightness

Arduino code

int brightness = 0;
const int pin = 9;

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

void loop() {
  if (Serial.available() > 0) {
    brightness = Serial.read();
    analogWrite(pin, brightness);
  }
}


Python code

In [ ]:
ser = serial.Serial('COM18', 9600, timeout=1)

for i in range(255):
    ser.write(bytes([i]))
    time.sleep(0.1)

for i in range(255):
    ser.write(bytes([255-i])) 
    time.sleep(0.1)    
    
ser.close() 

Lab 4: Current Time on LCD Display

Arduino code

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
String LCDRead = "";

void setup() {
  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);
  lcd.setCursor(0,0);
  // Print a message to the LCD.
  lcd.print("The current time");
  Serial.begin(9600);
}

void loop() {
  if (Serial.available()) {

    LCDRead = Serial.readStringUntil('\n');

    lcd.setCursor(0,1);
    lcd.print(LCDRead);    
  }
}

Python code

  • Displaying current time once
In [ ]:
import serial
import time
from datetime import datetime

ser = serial.Serial('COM18', 9600, timeout=1)
In [ ]:
print(datetime.now().strftime('%H:%M:%S'))
ser.write((datetime.now().strftime('%H:%M:%S') + '\n').encode('UTF-8'))
In [ ]:
ser.close()
  • Displaying the time for 15 seconds
In [ ]:
import serial
import time
from datetime import datetime

ser = serial.Serial('COM18', 9600, timeout=1)

for i in range(15):
    print(datetime.now().strftime('%H:%M:%S').encode('UTF-8'))
    ser.write((datetime.now().strftime('%H:%M:%S') + '\n').encode('UTF-8'))
    time.sleep(1)
    
ser.close()

Lab 5: Current temperature on LCD Display from the internet

Arduino code

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
String LEDRead = "";

void setup() {  
  lcd.begin(16,2);  
  Serial.begin(9600);
}

void loop() {
  if (Serial.available()) {

    LEDRead = Serial.readStringUntil('\n');
    lcd.clear();

    lcd.setCursor(0,0);
    lcd.print("Temperature in");
    lcd.setCursor(0,1);
    lcd.print("Ulsan: ");
    lcd.setCursor(7,1);
    lcd.print(LEDRead);
    lcd.setCursor(9,1);
    lcd.print("Degrees");
  }
}


Python code

In [ ]:
import serial
import time

ser = serial.Serial('COM14', 9600, timeout=1)
In [ ]:
from bs4 import BeautifulSoup
from urllib.request import Request, urlopen

url = 'https://www.google.co.kr/search?q=ulsan+temperature'

req = Request(url, headers={'User-Agent': 'Mozilla/5.0'})
html = urlopen(req).read()

soup = BeautifulSoup(html, 'html.parser')
result = soup.find_all('span', 'wob_t')

for i in range(len(result)):
    print(result[i].text)
    
ser.write(str(result[0].text[:-2]).encode('utf-8'))

2. Servo Motor with python


$$ \large \text{Python in PC} \quad \overset{\text{serial}}{\longleftrightarrow} \quad \text{arduino} \quad \overset{\text{PWM}}{\longleftrightarrow} \quad \text{servo}$$


Arduino code

#include <Servo.h>

Servo myservo;
const int pint = 9;
int pos = 0;

void setup() {
  myservo.attach(pin);
  myservo.write(0);
  Serial.begin(9600);
}

void loop() {
  if (Serial.available() > 0) {
    pos = Serial.read();
    myservo.write(pos);
    delay(50);
  }
}

Python code

In [ ]:
ser = serial.Serial('COM18', 9600, timeout=1)
In [ ]:
ser.write(bytes([90]))
In [ ]:
ser.write(bytes([180]))
In [ ]:
ser.close()
In [ ]:
ser = serial.Serial('COM18', 9600, timeout=1)

for angle in range(0,180):
    ser.write(bytes([angle]))
    time.sleep(0.1)
    
for angle in range(180,0,-1):
    ser.write(bytes([angle]))
    time.sleep(0.1)
    
ser.close()
In [ ]:
import serial
import time

def move(angle):
    if (0 <= angle <= 180):
        ser.write(bytes([angle]))
    else:
        print("Servo angle must be an integer between 30 and 180.\n")

# Start the serial port to communicate with arduino
ser = serial.Serial('COM18', 9600, timeout=1)

print("The initial servo angle is 30, type 'end' if you want to stop")

while 1:
    angle = input("Enter your angle: ")
    if angle == "end":
        print("Finished")
        ser.close()
        break
    else:
        move(int(angle))
        time.sleep(0.01)
        print("The current servo angle is " + angle)

3. Data Streaming with Python

3.1 MPU6050 and layout

Reference: http://playground.arduino.cc/Main/MPU-6050

The InvenSense MPU-6050 sensor contains a MEMS accelerometer and a MEMS gyro in a single chip. It is very accurate, as it contains 16-bits analog to digital conversion hardware for each channel. Therefor it captures the x, y, and z channel at the same time. The sensor uses the I2C-bus to interface with the Arduino.

3.2. Plotting Saved Data in Buffer from Arduino

Before plotting the streaming data from the MPU6050, we will try plotting data after saving it in a buffer. The example code below saves 50 data sets before actually plotting it.


Arduino Code

#include <Wire.h>

const int MPU = 0x68;   //Default address of I2C for MPU 6050
int16_t AcX, AcY, AcZ;

void setup() {
  Wire.begin();                   // Wire library initialization
  Wire.beginTransmission(MPU);    // Begin transmission to MPU
  Wire.write(0x6B);               // PWR_MGMT_1 register
  Wire.write(0);                  // MPU-6050 to start mode
  Wire.endTransmission(true);
  Serial.begin(9600);
}

void loop() {
  Wire.beginTransmission(MPU);      // Start transfer
  Wire.write(0x3B);                 // register 0x3B (ACCEL_XOUT_H), records data in queue
  Wire.endTransmission(false);      // Maintain connection
  Wire.requestFrom(MPU, 14, true);  // Request data to MPU
  //Reads byte by byte
  AcX = Wire.read() << 8 | Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
  AcY = Wire.read() << 8 | Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  AcZ = Wire.read() << 8 | Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)

  //Prints values on Serial
  Serial.print(AcX);
  Serial.print(","); 
  Serial.print(AcY);
  Serial.print(","); 
  Serial.println(AcZ);
  delay(20);
}


Python Code

In [1]:
import serial          
import numpy as np     
import matplotlib.pyplot as plt 
%matplotlib qt

ser = serial.Serial('COM4',9600)     
In [2]:
# Define Variables
Accx = []
Accy = []
Accz = []

len = 51

for i in range (len):               # Wait until the buffer is filled up to 50 values
    while (ser.inWaiting() == 0):   
        pass                        
        
    arduinoString = ser.readline().decode("utf-8") #.strip()
    
    dataArray = (arduinoString.split(','))      # Since the values are being sent consecutively, 
                                                # the values must be split into x,y,and z
    # Data conversion from string to float
    temp1 = float(dataArray[0])             
    temp2 = float(dataArray[1]) 
    temp3 = float(dataArray[2])
    
    # Convert second element to floating number and put in P
    Accx.append(temp1)                      
    Accy.append(temp2)                      
    Accz.append(temp3)
In [3]:
print(Accx)    
[1048.0, 1068.0, 916.0, 940.0, 1052.0, 960.0, 916.0, 988.0, 960.0, 952.0, 1004.0, 952.0, 1036.0, 900.0, 1004.0, 924.0, 1132.0, 996.0, 880.0, 892.0, 912.0, 804.0, 860.0, 924.0, 944.0, 956.0, 928.0, 1016.0, 916.0, 920.0, 1028.0, 1016.0, 848.0, 904.0, 912.0, 880.0, 976.0, 1020.0, 1004.0, 916.0, 916.0, 904.0, 884.0, 944.0, 1032.0, 876.0, 948.0, 1028.0, 1028.0, 1080.0, 928.0]
In [4]:
plt.figure(1)
plt.subplot(311)                    # Set y min and max values
plt.title('Saved data')             # Plot the title
plt.grid(True)                      # Turn the grid on
plt.ylabel('Acceleration')          # Set ylabels
plt.plot(Accx, 'bo-', label='X')    # plot the temperature
plt.legend(loc='upper left')        # plot the legend
    
plt.subplot(312)                    # Set y min and max values
plt.grid(True)                      # Turn the grid on
plt.ylabel('Acceleration')          # Set ylabels
plt.plot(Accy, 'ro-', label='Y')    # plot the temperature
plt.legend(loc='upper left')          
    
plt.subplot(313)                    # Set y min and max values
plt.grid(True)                      # Turn the grid on
plt.ylabel('Acceleration')          # Set ylabels
plt.plot(Accz, 'go-', label='Z')    # plot the temperature
plt.legend(loc='upper left')          

plt.show()
In [5]:
ser.close()

3.3. Plotting Steaming Data from Arduino

Arduino code

#include <Wire.h>

const int MPU = 0x68;   //Default address of I2C for MPU 6050
int16_t AcX, AcY, AcZ;

void setup() {
  Wire.begin();                   // Wire library initialization
  Wire.beginTransmission(MPU);    // Begin transmission to MPU
  Wire.write(0x6B);               // PWR_MGMT_1 register
  Wire.write(0);                  // MPU-6050 to start mode
  Wire.endTransmission(true);
  Serial.begin(9600);
}

void loop() {
  Wire.beginTransmission(MPU);      // Start transfer
  Wire.write(0x3B);                 // register 0x3B (ACCEL_XOUT_H), records data in queue
  Wire.endTransmission(false);      // Maintain connection
  Wire.requestFrom(MPU, 14, true);  // Request data to MPU
  //Reads byte by byte
  AcX = Wire.read() << 8 | Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
  AcY = Wire.read() << 8 | Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  AcZ = Wire.read() << 8 | Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)

  //Prints values on Serial
  Serial.print(AcX);
  Serial.print(","); 
  Serial.print(AcY);
  Serial.print(","); 
  Serial.println(AcZ);
  delay(20);
}

Python code

In [6]:
# Plot 3 signals

import serial
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation

ser = serial.Serial('COM4', 9600)

leng = 201
fig = plt.figure(figsize=(12, 6))
ax = plt.axes(xlim=(0,leng-1), ylim=(-2, 2))

plt.title('Real-time sensor data')
plt.xlabel('Data points')
plt.ylabel('Acceleration [G]')
ax.grid(True)

graphX, = ax.plot([], [], 'b', label = 'X')
graphY, = ax.plot([], [], 'r', label = 'Y')
graphZ, = ax.plot([], [], 'g', label = 'Z')
ax.legend(loc='upper right')
ax.legend(loc='upper right')
ax.legend(loc='upper right')

t = list(range(0, leng))
accX = []
accY = []
accZ = []

for i in range(0, leng):
    accX.append(0)
    accY.append(0)
    accZ.append(0)

def init():
    graphX.set_data([], [])
    graphY.set_data([], [])
    graphZ.set_data([], [])
    return graphX, graphY, graphZ

def animate(i):
    global t, accX, accY, accZ

    while (ser.inWaiting() == 0):
        pass

    arduinoString = ser.readline().decode("utf-8")
    dataArray = arduinoString.split(',')

    accX.append(float(dataArray[0])/(32767/2))    
    accY.append(float(dataArray[1])/(32767/2))    
    accZ.append(float(dataArray[2])/(32767/2))
    accX.pop(0)
    accY.pop(0)
    accZ.pop(0)

    graphX.set_data(t, accX)
    graphY.set_data(t, accY)
    graphZ.set_data(t, accZ)

    return graphX, graphY, graphZ

delay = 20
anim = animation.FuncAnimation(fig, animate, init_func=init,
                               interval=delay, blit=True)

plt.show()     
In [7]:
ser.close()

In [8]:
# Subplot

import serial
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation

ser = serial.Serial('COM4', 9600)

leng = 201
fig = plt.figure(figsize=(12, 9))

ax1 = fig.add_subplot(3, 1, 1)
ax2 = fig.add_subplot(3, 1, 2)
ax3 = fig.add_subplot(3, 1, 3)

graphX, = ax1.plot([], [], 'b', label = 'X')
graphY, = ax2.plot([], [], 'r', label = 'Y')
graphZ, = ax3.plot([], [], 'g', label = 'Z')
axes = [ax1, ax2, ax3]

for ax in axes:
    ax.set_xlim(0, leng-1)
    ax.set_ylim(-2, 2)
    ax.set_ylabel('Acceleration [G]')
    ax.legend(loc='upper right')
    ax.grid(True)

ax1.set_title('Real-time sensor data')
ax3.set_xlabel('Data points')
    
t = list(range(0, leng))
accX = []
accY = []
accZ = []

for i in range(0, leng):
    accX.append(0)
    accY.append(0)
    accZ.append(0)

def init():
    graphX.set_data([], [])
    graphY.set_data([], [])
    graphZ.set_data([], [])
    return graphX, graphY, graphZ

def animate(i):
    global t, accX, accY, accZ

    while (ser.inWaiting() == 0):
        pass

    arduinoString = ser.readline().decode("utf-8")
    dataArray = arduinoString.split(',')

    accX.append(float(dataArray[0])/(32767/2))    
    accY.append(float(dataArray[1])/(32767/2))    
    accZ.append(float(dataArray[2])/(32767/2))
    accX.pop(0)
    accY.pop(0)
    accZ.pop(0)

    graphX.set_data(t, accX)
    graphY.set_data(t, accY)
    graphZ.set_data(t, accZ)

    return graphX, graphY, graphZ

delay = 20
anim = animation.FuncAnimation(fig, animate, init_func=init,
                               interval=delay, blit=True)

plt.show()     
In [9]:
ser.close()

4. Summary

In [25]:
%%html
<iframe src="https://www.youtube.com/embed/jemrZxfKVIY" 
width="560" height="315" frameborder="0" allowfullscreen></iframe>
In [2]:
%%javascript
$.getScript('https://kmahelona.github.io/ipython_notebook_goodies/ipython_notebook_toc.js')