Week 12 - Apr 4th 2012 - Interface and Application Programming

Weekly Assignment - write an application that interfaces with an input &/or output device

This week I wanted to experiment with directly interfacing through USB, sidestepping the need for the serial FTDI interface. To impliment this I worked with the V-USB software USB implementation for AVR microcontrollers.

The first stage was to build one of the examples included with the V-USB package. The LEDcontrol module attaches an LED to an output pin and allows it to be switched on or off using USB control messages, and also includes a message that requests the module send back the status of the LED. I was using the attiny45 microcontroller with 8 pins, so some had to double between ISP and USB (two lines are required, D+ and D-) duties, as seen in the schematic.

I used the makefile provided with the example to program the board. The setup required several files were edited:

  • the makefile to correct the microcontroller, speed, programmer and fuses
  • usbconfig.h to indicate the pins connected to D+ and D- from the USB socket
  • main.c to indicate the pin the LED is connected to

To interface with the board, I wrote a python script that sends the various control commands to switch the LED on and off, and to request the LED status. Note that the idVendor and idProduct variables are set to those shown in the hareware profile above.

#! /usr/bin/env python import usb import sys import Tkinter dev = usb.core.find(idVendor=0x16c0, idProduct=0x05dc) dev.set_configuration() class ledguiapp_tk(Tkinter.Tk): def __init__(self,parent): Tkinter.Tk.__init__(self,parent) self.parent = parent self.initialize() def initialize(self): self.grid() buttonOn = Tkinter.Button(self,text='Switch LED on',command=self.buttonOnClick) buttonOn.grid(column=0,row=0) buttonOff = Tkinter.Button(self,text='Switch LED off',command=self.buttonOffClick) buttonOff.grid(column=1,row=0) buttonStatus = Tkinter.Button(self,text='Check LED status',command=self.buttonStatusClick) buttonStatus.grid(column=0,row=1,columnspan=2) self.labelStatusVar = Tkinter.StringVar() labelStatus = Tkinter.Label(self,textvariable=self.labelStatusVar) labelStatus.grid(column=0,row=2,columnspan=2) self.labelInstructionVar = Tkinter.StringVar() self.labelInstructionVar.set('<n> on, <f> off, <s> status, <x> exit') labelInstruction = Tkinter.Label(self,textvariable=self.labelInstructionVar) labelInstruction.grid(column=0,row=3,columnspan=2) self.bind('x','exit') self.bind('n',self.buttonOnClick) self.bind('f',self.buttonOffClick) self.bind('s',self.buttonStatusClick) self.resizable(False,False) def buttonOnClick(self,*event): self.labelStatusVar.set('') dev.ctrl_transfer(0x40,1,1,0) def buttonOffClick(self,*event): self.labelStatusVar.set('') dev.ctrl_transfer(0x40,1,0,0) def buttonStatusClick(self,*event): ret = dev.ctrl_transfer(0xc0,2,0,0,1) if ret[0]&1 == 1: self.labelStatusVar.set('LED is on') elif ret[0]&1 == 0: self.labelStatusVar.set('LED is off') if __name__ == "__main__": app = ledguiapp_tk(None) app.title('USB LED') app.mainloop()

The script uses the pyusb module to allow USB communication. The GUI setup is based largely on the tutorial here. Most of the code sets up the buttons and labels; the dev.ctrl_transfer instructions within the functions are what interact with the USB device.

Next I wanted to adapt the LEDcontrol example to reaad an input. I adapted the board to have a push button instead of an LED, as seen in the schematic. NB. the schematic includes a 10k pull-up resistor pulling the input pin (PB1) high. This is also the pin used as the ISP MISO connection so I intended to program the board then add this resistor. While writing the program, however, it occurred to me I could activate the internal pull-up resistor so in the end didn't need to solder on the resistor at all.

For the hardware programming I adapted the LEDcontrol example code by editing the following files:

  • makefile for the programming settings as above
  • usbconfig.h to change the USB pins and device and vendor names. I could also have changed the identifier codes that the client-side software uses to ensure it is connected to the correct device in this file.
  • resquests.h to define one custom command, CUSTOM_RQ_POLL_STATUS, instruction index zero, that will be used to poll the button status.
  • main.c to set PB1 as an input and activate the internal pull-up resistor, and to define the action for CUSTOM_RQ_POLL_STATUS - return the state of the button as shown in this snippet:
    if(rq->bRequest == CUSTOM_RQ_POLL_STATUS){ /*request status of button */ dataBuffer[0] = ((BUTTON_PORT_INPUT & _BV(BUTTON_BIT)) != 0); /* determine status of button */ usbMsgPtr = dataBuffer; /* prepare data to return */ return 1; /* send one byte */

I wrote another python script to continually poll the button by asking the USB device to send back the status, and then to display a label indicating the result. The frequency at which the script is polling the button is once per 100ms as determined by the self.after(100,app.buttonPoll) instruction.

#! /usr/bin/env python import usb import sys import Tkinter dev = usb.core.find(idVendor=0x16c0, idProduct=0x05dc) dev.set_configuration() class buttonguiapp_tk(Tkinter.Tk): def __init__(self,parent): Tkinter.Tk.__init__(self,parent) self.parent = parent self.initialize() def initialize(self): self.grid() self.labelDescriptionVar = Tkinter.StringVar() labelDescription = Tkinter.Label(self,textvariable=self.labelDescriptionVar) self.labelDescriptionVar.set('Button being checked every 100ms') labelDescription.grid(column=0,row=0) self.labelStatusVar = Tkinter.StringVar() labelStatus = Tkinter.Label(self,textvariable=self.labelStatusVar) labelStatus.grid(column=0,row=1) self.resizable(False,False) self.bind('x','exit') def buttonPoll(self): ret = dev.ctrl_transfer(0xc0,0,0,0,1) if ret[0]&1 == 1: self.labelStatusVar.set('button : not pressed') elif ret[0]&1 == 0: self.labelStatusVar.set('button : pressed') self.after(100,app.buttonPoll) if __name__ == "__main__": app = buttonguiapp_tk(None) app.title('USB BUTTON') app.buttonPoll() app.mainloop()
Ideally I will update this device and script to be event-driven (so nothing happens until you press or release the button, as opposed to continually polling the device which is very inefficient) but this will require me to learn a bit more c, python, and usb implementation. An easier way to achieve this may be to program the board to emulate a class-compliant keyboard, and use the button to represent a key.

<<< Week 11

Week 13 >>>