Controlling Pins via Serial

I’m teaching myself some Python and I wrote this little program that allows me to control most all of the pin functions both analog and digital on my arduino from my computer. I know this has been done a million times, but I still thought I might share.

I’d love to hear any comments. I’m sure I could have done the Arduino side a little cleaner with strtok, but it was really too easy writing this code and I probably would have had to think about strtok.

Arduino code

#define MAX_REQ_SIZE 20
#define MAX_COMMAND_SIZE 20

char req_buffer[MAX_REQ_SIZE];
int buffer_index = 0;

boolean packet_started = false;

void setup()
{
  Serial.begin(9600);
  pinMode(17, OUTPUT);
  pinMode(18, OUTPUT);
  pinMode(19, OUTPUT);
  digitalWrite(17, HIGH);
}

void loop()
{
  serialHandler();
}

void serialHandler()
{
  if (Serial.available())
  {
    char c = Serial.read();
    
    if (packet_started)
    {
      if ((c == '>') || (buffer_index == MAX_REQ_SIZE))
      {
        packet_started = false;
        req_buffer[buffer_index] = 0;
        reqHandler();
        buffer_index = 0;
      }      
      else
      {
        req_buffer[buffer_index] = c;
        buffer_index++;
      }        
    }
    
    if (c == '<') packet_started = true;
  }
}

void reqHandler()
{
  char* command;   // pointer to the command
  char* arg[4];    // pointers to arguments
  //char command[MAX_COMMAND_SIZE];
  
  command = req_buffer;  // commmand should be the first thing
  arg[0] = req_buffer;   // stert looking from beginning for the (
  while(*(arg[0]) != '(')
  {
    (arg[0])++;
  }
  *(arg[0]) = 0;
  (arg[0])++;   // Now arg1 points to the start of the first argument and the ( is a NULL
  
  boolean args_end = false;  
  
  for(int icount = 1; icount < 4; icount++)  
  {
    arg[icount] = arg[icount - 1];  // Start scanning from the beginning of the last arg
    if(args_end) continue;     //  All the left over pointers will point to the NULL at the end.
    
    while(*(arg[icount])!=',')  // scan through for a comma
    {
      if(*(arg[icount]) == ')')  // means we've hit the end
      {
        args_end = true;
        break;
      }
      else
      {
        (arg[icount])++;  // keep moving the pointer
      }
    }
    
    *(arg[icount]) = 0;  //  out of while so this is the comma, make it NULL
    if(!args_end)  //  If it's not the end, then make the pointer point to the beginning of the next argument.  
    {
      (arg[icount])++;
    }
  }
  //  <get(pin)>
  if(strcmp(command, "get") == 0) getCommand(arg[0]);
  //  <set(pin,state)>
  if(strcmp(command, "set") == 0) setCommand(arg[0], arg[1]);
  //  <mod(pin,mode)>
  if(strcmp(command, "mod") == 0) modCommand(arg[0], arg[1]);
  //  <all()>
  if(strcmp(command, "all") == 0) allCommand();
  //  <ana(pin)>
  if(strcmp(command, "ana") == 0) anaCommand(arg[0]);
  //  <pwm(pin,value)>
  if(strcmp(command, "pwm") == 0) pwmCommand(arg[0], arg[1]);   
}


void anaCommand(char* pin_arg)
{
  int pin_number;
  int pin_value;
  
  pin_number = atoi(pin_arg);
  pin_value = analogRead(pin_number - 14);
  
  char response[30];
  
  sprintf(response, "<a_pin:%d:%d>", pin_number, pin_value);
  Serial.println(response);
}
  
void pwmCommand(char* pin_arg, char* val_arg)
{
  int pin_number;
  int pin_value;
    
  pin_number = atoi(pin_arg);
  pin_value = atoi(val_arg);
  constrain(pin_value, 0, 255);
  analogWrite(pin_number, pin_value);
}  


void getCommand(char* pin_arg)
{
  int pin_number;
  int pin_state;
    
  pin_number = atoi(pin_arg);
  
  pin_state = digitalRead(pin_number);
  
  char response[30];
  
  sprintf(response, "<Pin %d:%d>", pin_number, pin_state);
  Serial.println(response);
}

void setCommand(char* pin_arg, char* state_arg)
{
  char state_str;
  int pin_number;
    
  pin_number = atoi(pin_arg);
  if(*state_arg == 'h' || *state_arg =='H')
  {
    digitalWrite(pin_number, HIGH);
  }
  else
  {
    digitalWrite(pin_number, LOW);
  }
}


void modCommand(char* pin_arg, char* mode_arg)
{
  int pin_number;
    
  pin_number = atoi(pin_arg);
  if(*mode_arg == 'o' || *mode_arg =='O' || *mode_arg == '0')  // middle one is an oh, last one is a zero
  {
    pinMode(pin_number, OUTPUT);
  }
  else
  {
    pinMode(pin_number, INPUT);
  }
}
  
void allCommand()
{
  char response[40];
  
  sprintf(response, "<%d:%d:%d:%d:%d:%d:%d:%d:%d>" , DDRD,PIND, PORTD, DDRB, PINB, PORTB, DDRC, PINC, PORTC);
  Serial.println(response);
}

The Python side:

# File:  Pins_7.py

import time

import threading

from Tkinter import *

import serial
ser = serial.Serial("COM4", 9600, timeout = 1)


class PinWin:
    def __init__(self, master, pin_no):

        self.myParent = master
        self.myPinNumber = pin_no
        self.analogAble = 0
        self.pwmAble = 0

        if self.myPinNumber in range(0,8):
            self.RegisterSet = 0
            self.pinOffset = 0
        elif self.myPinNumber in range(8,14):
            self.RegisterSet = 1
            self.pinOffset = 8
        elif self.myPinNumber in range(14,20):
            self.RegisterSet = 2
            self.pinOffset = 14
            self.analogAble = 1
        else:
            exit(1)

        if self.myPinNumber in [3,5,6,9,10,11]:
            self.pwmAble = 1
            
        
    ###main_frame
        self.main_frame = Frame(master, bd=1, relief = SUNKEN)
        self.main_frame.pack(side = TOP, anchor = W)

    ###widget_frames
        self.label_frame = Frame(self.main_frame, bd = 1, relief = GROOVE)
        self.in_out_frame = Frame(self.main_frame, bd = 1, relief = GROOVE)
        self.high_low_frame = Frame(self.main_frame, bd = 1, relief = GROOVE)

        self.kill_button = Button(self.main_frame, text = "X",command = self.dieGracefully)
        
        self.kill_button.pack(side = LEFT)
        self.label_frame.pack(side = LEFT)
        self.high_low_frame.pack(side = LEFT)
        self.in_out_frame.pack(side = LEFT)

    
    ###pin_label
        self.label_str = StringVar()
        self.label_str.set("Pin :  " + str(pin_no))
        self.pin_label = Label(self.label_frame, textvariable = self.label_str, relief = GROOVE)
        self.pin_label.pack(side = LEFT)
        
    ###state_label
        self.state_label_str = StringVar()
        self.state_label_str.set("U")
        self.mode_label_str = StringVar()
        self.mode_label_str.set("U")
        self.state_label = Label(self.label_frame, textvariable = self.state_label_str,
                                 width = 5, relief = GROOVE)
        self.state_label.pack(side = LEFT)

        self.mode_label = Label(self.label_frame, textvariable = self.mode_label_str,
                                 width = 5, relief = GROOVE)
        self.mode_label.pack(side = LEFT)
        

    ###HIGH / LOW Radio Buttons
        self.pin_val = IntVar()
        self.pin_val.set(0)

        self.radio_High = Radiobutton(self.high_low_frame, text="High", variable = self.pin_val,
                                      value = 1, command = self.setPin)
        self.radio_Low = Radiobutton(self.high_low_frame, text="Low", variable = self.pin_val,
                                      value = 0, command = self.setPin)
        self.radio_Low.pack(side = LEFT)
        self.radio_High.pack(side = LEFT)

    ###MODE Radio Buttons
        self.pin_mode = IntVar()
        self.pin_mode.set(0)

        self.radio_Output = Radiobutton(self.in_out_frame, text="Output", variable = self.pin_mode,
                                      value = 1, command = self.modPin)
        self.radio_Input = Radiobutton(self.in_out_frame, text="Input", variable = self.pin_mode,
                                      value = 0, command = self.modPin)
        self.radio_Input.pack(side = LEFT)
        self.radio_Output.pack(side = LEFT)

    ###analog_frame
        if self.analogAble == 1:
            self.analog_frame = Frame(self.main_frame, bd = 1, relief = GROOVE)
            self.analog_frame.pack(side = LEFT)

            self.analog_enable_var = IntVar()

            self.analog_enable_check = Checkbutton(self.analog_frame,
                                        text = "Analog", variable = self.analog_enable_var,
                                        command = self.analogEnable)
            self.analog_value = IntVar()
            self.analog_value.set(-1)
            self.analog_value_label = Label(self.analog_frame, textvariable = self.analog_value,
                                 width = 5, relief = GROOVE)



            self.analog_enable_check.pack(side = LEFT)
            self.analog_value_label.pack(side = LEFT)


    ###pwm_frame
        if self.pwmAble == 1:
            self.pwm_frame = Frame(self.main_frame, bd = 1, relief = GROOVE)
            self.pwm_frame.pack(side = LEFT)

            self.pwm_enable_var = IntVar()

            self.pwm_enable_check = Checkbutton(self.pwm_frame,
                                        text = "PWM", variable = self.pwm_enable_var,
                                        command = self.pwmEnable)
            self.pwm_value = IntVar()
            self.pwm_value.set(0)
            self.pwm_value_entry = Entry(self.pwm_frame, textvariable = self.pwm_value,
                                width = 5, relief = GROOVE, state = DISABLED)
            self.pwm_value_entry.bind("<Return>", self.pwmSet)

            self.pwm_enable_check.pack(side = LEFT)
            self.pwm_value_entry.pack(side = LEFT)
            

    def setPin(self):
        self.state_str = ""
        if(self.pin_val.get() == 0):
            self.state_str = "LOW"
        else:
            self.state_str = "HIGH"
            
        self.com_str = "<set({0!s},{1})>".format(self.myPinNumber, self.state_str)
        ser.write(self.com_str)
        if (not pins.monitor_flag) : pins.get_all()

    def modPin(self):
        self.mod_str = ""
        if(self.pin_mode.get() == 0):
            self.mod_str = "INPUT"
            self.radio_High.config(state = DISABLED)
            self.radio_Low.config(state = DISABLED)
            if self.pwmAble:
                self.pwm_enable_check.config(state = DISABLED)
        else:
            self.mod_str = "OUTPUT"
            self.radio_High.config(state = NORMAL)
            self.radio_Low.config(state = NORMAL)
            if self.pwmAble:
                self.pwm_enable_check.config(state = NORMAL)

        self.com_str = "<mod({0!s},{1})>".format(self.myPinNumber, self.mod_str)
        ser.write(self.com_str)
        if (not pins.monitor_flag) : pins.get_all()


    def analogEnable(self):
        if self.analog_enable_var.get() == 1:
            self.pin_mode.set(0)
            self.modPin()
            self.mod_str = "ANALOG"
            self.radio_Output.config(state = DISABLED)
            self.radio_Input.config(state = DISABLED)
            self.state_label.config(state = DISABLED)
            self.mode_label.config(state = DISABLED)
            
        else:
            self.radio_Output.config(state = NORMAL)
            self.radio_Input.config(state = NORMAL)
            self.state_label.config(state = NORMAL)
            self.mode_label.config(state = NORMAL)
            self.analog_value.set(-1)
            
    def pwmEnable(self):
        if self.pwm_enable_var.get() == 1:
            self.pin_mode.set(1)
            self.modPin()
            self.mod_str = "PWM"
            
            self.radio_Output.config(state = DISABLED)
            self.radio_Input.config(state = DISABLED)
            self.state_label.config(state = DISABLED)
            self.mode_label.config(state = DISABLED)
            self.radio_High.config(state = DISABLED)
            self.radio_Low.config(state = DISABLED)
            self.pwm_value_entry.config(state = NORMAL)

        else:
            self.pin_val.set(0)
            self.setPin()
            self.radio_Output.config(state = NORMAL)
            self.radio_Input.config(state = NORMAL)
            self.state_label.config(state = NORMAL)
            self.mode_label.config(state = NORMAL)
            self.radio_High.config(state = NORMAL)
            self.radio_Low.config(state = NORMAL)
            self.pwm_value_entry.config(state = DISABLED)
            self.pwm_value.set(0)

    def pwmSet(self, event):
        if self.pwm_enable_var.get() == 1:
            self.com_str = "<pwm({0!s},{1!s})>".format(self.myPinNumber, self.pwm_value_entry.get())
            ser.write(self.com_str)

Python code continued:

def getFromReg(self):
        self.reg_set = pins.port_maps[self.RegisterSet]

        self._ddr = self.reg_set[0]
        self._pin = self.reg_set[1]
        self._port = self.reg_set[2]
        
        if((self._ddr >> (self.myPinNumber - self.pinOffset)) & 1):
            self.mode_label_str.set("OUT")
            self.pin_mode.set(1)
            self.radio_High.config(state = NORMAL)
            self.radio_Low.config(state = NORMAL)
            if self.pwmAble :
                self.pwm_enable_check.config(state = NORMAL)

            if((self._port >> (self.myPinNumber - self.pinOffset)) & 1):
                self.state_label_str.set("HIGH")
                self.pin_val.set(1)
            else:
                self.state_label_str.set("LOW")
                self.pin_val.set(0)
                
        else:
            self.mode_label_str.set("IN")
            self.pin_mode.set(0)
            self.radio_High.config(state = DISABLED)
            self.radio_Low.config(state = DISABLED)
            if self.pwmAble:
                self.pwm_enable_check.config(state = DISABLED)

            if((self._pin >> (self.myPinNumber - self.pinOffset)) & 1):
                self.state_label_str.set("HIGH")
                self.pin_val.set(1)
            else:
                self.state_label_str.set("LOW")
                self.pin_val.set(0)


    def getAnalog(self):
        self.com_str = "<ana({0!s})>".format(self.myPinNumber)
        ser.write(self.com_str)

        self.packet_finished = 0
        self.packet_started = 0

        while self.packet_finished == 0:
            ch = ser.read()

            if self.packet_started == 0:
                if ch == '<' :
                    self.packet_started = 1
                    self.packet_finished = 0
                    self.resp = ""

            elif ch == '>' :
                self.packet_started = 0
                self.packet_finished = 1
   
            else:
                self.resp = self.resp + ch

        self.resp_list = self.resp.split(':')

        if (self.resp_list[0] == "a_pin" and int(self.resp_list[1]) == self.myPinNumber):
            self.analog_value.set(int(self.resp_list[2]))
        else:
            self.analog_value.set(-1)


    def doPin(self):
        if self.analogAble == 1:
            if self.analog_enable_var.get() == 1:
                self.getAnalog()
            else:
                self.getFromReg()
        elif self.pwmAble == 1:
            if self.pwm_enable_var.get() == 1:
                pass
            else:
                self.getFromReg()
        else:
            self.getFromReg()



    def dieGracefully(self):
        self.main_frame.destroy()
        i = 0
        for pin in pins.pin_containers:
            if pin.myPinNumber == self.myPinNumber:
                pins.pin_containers.pop(i)
                break
            i=i+1
        
        
        

class App:

    def __init__(self, master):
    ###top_frame
        top_frame = Frame(master)
        top_frame.pack(anchor = NW, fill = BOTH, expand = YES)
    ###button_frame
        button_frame = Frame(master)
        button_frame.pack(anchor = S, fill = BOTH, expand = YES)
        
    ###pin_labels
        self.pin_containers = []

        for i in range(2,20):
            self.pin_containers.append(PinWin(top_frame, i))            

    ###buttons
        self.button = Button(button_frame, text="QUIT", command=top_frame.quit)
        self.button.pack(side = RIGHT)

        self.hi_there = Button(button_frame, text="Say Pins", command=self.say_pins)
        self.hi_there.pack(side = RIGHT)

        self.run_button = Button(button_frame, text="Run", command=self.monitor_start)
        self.run_button.pack(side=RIGHT)

        self.monitor_flag = 0


    def reset_monitor(self):
        self.run_button.config(text = "Run", command = self.monitor_start)
        self.monitor_flag = 0   

    def monitor_start(self):
        self.run_button.config(text = "Stop", command = self.reset_monitor)
        self.monitor_flag = 1
        self.monitor_pins()

    def monitor_pins(self):
        self.get_all()       

        if self.monitor_flag == 1:
            threading.Timer(0.5, self.monitor_pins).start()     


    def say_hi(self):
        print "hi there, everyone!"

    def say_pins(self):
        self.get_all()

    def get_all(self):
        ser.write("<all()>")

        self.packet_finished = 0
        self.packet_started = 0

        while self.packet_finished == 0:
            ch = ser.read()

            if self.packet_started == 0:
                if ch == '<' :
                    self.packet_started = 1
                    self.packet_finished = 0
                    self.resp = ""

            elif ch == '>' :
                self.packet_started = 0
                self.packet_finished = 1
   
            else:
                self.resp = self.resp + ch                        
            
        self.port_list = self.resp.split(':')
        ###print self.port_list

        self.port_maps = [
            [int(self.port_list[0]),int(self.port_list[1]),int(self.port_list[2])],
            [int(self.port_list[3]),int(self.port_list[4]),int(self.port_list[5])],
            [int(self.port_list[6]),int(self.port_list[7]),int(self.port_list[8])]
            ]

        for pin in self.pin_containers:
            pin.doPin()            


root = Tk()

pins = App(root)

root.mainloop()

Here’s a screen capture!