Wednesday, 10 December 2014

Python Plots from Serial Input

The Arduino IDE doesn't do much with serial data returned from the board, other than display it on the serial console. Python provides a platform independent way to listen to your Arduino and draw graphs of analog data, or whatever you want. The code below provides a stripchart display based on the assumption that lines of

millis(), A0, A1, A2, A3, A4, A5

format are coming back from the Arduino on the serial port. It's hardcoded with port and speed, but easy to edit. I think the indents are OK, but this blog platform strips out the tabs on pasting...

Update: I did all of this with an arduino micro. Like the Leonardo, it doesn't reboot when you re-initialize the USB. When I switched to the UNO with the same code, I had to adjust it so it waited and read a few more lines before acting on the data. If you don't want to disrupt the UNO, you probably need to make a direct RS232 connection through an FTDI cable, rather than the onboard USB.



import matplotlib.pyplot as plt
import time
import serial

plt.ion()
ser = serial.Serial('/dev/tty.usbmodemfd1471',57600,timeout=1)
line = ser.readline() # throw away any part lines
while(ser.inWaiting() < 100): # make sure something is coming
  now = 0.0
t=[] # initialize the data lists
d1=[]
d2=[]
d3=[]
d4=[]
d5=[]
d6=[]
while (ser.isOpen()):
  line = ser.readline() # read a line of text
  mylist = line.split(",") # parse it into CSV tokens
  #print mylist
  now = float(mylist[0])/1000 # time now in seconds
  t.append(float(mylist[0])/1000) # from first element as milliseconds
  d1.append(float(mylist[1])) # six data elements added to lists
  d2.append(float(mylist[2]))
  d3.append(float(mylist[3]))
  d4.append(float(mylist[4]))
  d5.append(float(mylist[5]))
  d6.append(float(mylist[6]))
  if(ser.inWaiting() < 100): # redraw only if you are caught up
    plt.clf() # clear the figure
    plt.plot(t,d1) # plot a line for each set of data
    plt.plot(t,d2)
    plt.plot(t,d3)
    plt.plot(t,d4)
    plt.plot(t,d5)
    plt.plot(t,d6)
    plt.axis([now-60,now,min(d1)-50,max(d1)+50])
    plt.xlabel("Time Since Boot [s]")
    plt.draw()

By request, here's the arduino code I was running, which does a bunch more than just print analog values

//// AvgDAQ
// Makes the potentially dangerous assumption that the analog pins are numbered in sequence, starting at A0

//// Constants
int d = 1;
int navg = 10;
int nan = 6;
int nrpt = 500;
int ndisp = 20000;

void setup() {
  for(int i = 0; i < nan; i++) pinMode(A0 + i, INPUT);
  Serial.begin(57600);   
  analogReference(INTERNAL);  // 2.56 volts on the YUN, 1.1 on UNO
  //analogReference(EXTERNAL);  // based on the input to AREF
  Serial.print("\n\nAvgDAQ\n");
}

void loop() {
  static int linesShown = 0;
  unsigned long sums[10];    // doesn't want to work with NAN as a dimension
  getAvgDAQ(sums);
  if(linesShown < ndisp){ 
    showSums(sums);
    linesShown++;
  }
  while(millis()%nrpt);
}

void getAvgDAQ(unsigned long *sums) {
  sums[nan] = (unsigned long) millis();
  for(int i = 0;i < nan;i++) sums[i] = 0;
  for(int i = 0;i < navg;i++) for(int j = 0;j < nan;j++){ 
    sums[j] += analogRead(A0+j); 
    delayMicroseconds(d);
  }
  for(int i=0;i < nan;i++){ 
    //sums[i] *= 1404;  //convert to mv for 1.404 V AREF
    //sums[i] *= 2560;  //convert to mv for 2.56 V internal ref
    sums[i] *= 1100;    //convert to mv for 1.10 V internal ref
    sums[i] /= 1024;
    sums[i] /= navg;    // average over NAVG samples
  }
}

void showSums(unsigned long *sums) {
  char scratch[80];
  
  Serial.print(sums[nan]);
  scratch[0]=0;    // when in doubt code dangerously, writing a string over itself ;-)
  for(int i = 0;i < nan;i++) sprintf(scratch,"%s, %5d",scratch,(int) sums[i]);
  sprintf(scratch,"%s,   *",scratch);
  Serial.print(scratch);
  for(int i=0;i<80;i++) scratch[i] = '.';
  scratch[79] = 0;
  for(int i=0;i<nan;i++){
    long j = sums[i] / 20;
    if(j > 78) j = 78;
    scratch[j] = '0'+i;
  }
  Serial.println(scratch);
}

and of course to get all of this to run, you need to have pyserial and matplotlib installed. On a mac you can try

sudo easy_install pip
sudo pip install pyserial
sudo pip install matplotlib