Skip to content

Commit

Permalink
Commit with cleaned-up-for-release code
Browse files Browse the repository at this point in the history
  • Loading branch information
zpincus committed Oct 15, 2014
1 parent 9ac82ac commit c7af6c2
Show file tree
Hide file tree
Showing 15 changed files with 811 additions and 135 deletions.
381 changes: 381 additions & 0 deletions LICENSE.txt

Large diffs are not rendered by default.

32 changes: 22 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
# Run "make help" for target help.
######################################
# IOTool User-configurable variables #
######################################
# Set LUFA_PATH to the relative or absolute path to the LUFA source directory
LUFA_PATH = ../../lufa-LUFA-140928/LUFA

# If using an Arduino device and you want to use Arduino pin names, uncomment
# the following line:
#ARD_PINS = -DARDUINO_PIN_NAMES

# Set this to the serial port (/dev/tty/something on Mac/Linux; COMx on Windows)
# that appears when the device is attached and the reset button is pressed.
AVRDUDE_PORT = /dev/tty.usbmodemfa131

#############################
# IOTool Internal Variables #
#############################
MCU = atmega32u4
ARCH = AVR8
F_CPU = 16000000
F_USB = $(F_CPU)
OPTIMIZATION = s
OPTIMIZATION = 3
TARGET = IOTool
SRC = $(wildcard src/*.c) $(LUFA_SRC_USB_DEVICE) $(LUFA_SRC_USBCLASS_DEVICE)
LUFA_PATH = ../../lufa-LUFA-140302/LUFA
CC_FLAGS = -DUSE_LUFA_CONFIG_HEADER -Isrc
SRC = $(wildcard src/*.c) $(LUFA_SRC_USB_DEVICE) $(LUFA_PATH)/Drivers/USB/Class/Device/CDCClassDevice.c
CC_FLAGS = $(ARD_PINS) -DUSE_LUFA_CONFIG_HEADER -Isrc
LD_FLAGS =
OBJDIR = build

AVRDUDE_PROGRAMMER = avr109
AVRDUDE_PORT = /dev/tty.usbmodemfd121

# Default target
all:
AVRDUDE_FLAGS =

upload: avrdude
# Upload target
upload: all avrdude

# Include LUFA build script makefiles
include $(LUFA_PATH)/Build/lufa_core.mk
Expand Down
320 changes: 320 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,320 @@
IOTool
======
IOTool is a firmware for AVR microcontrollers that provides a simple serial
interface for scripting/sequencing input and output actions, controlled by
a host computer.

This firmware was originally developed for running laboratory experiments in
which multiple hardware devices need to signal one another via TTL, and where
the experimental parameters need to be rapidly reconfigurable. (So writing a
new firmware for each experiment was out of the questions.)

In general, this will be useful for any needs for high-speed sequencing of
inputs and outputs, controlled by a host computer.

Developed by [Zach Pincus](zplab.wustl.edu), and provided under a GPL2 license.

Basic Usage Example
-------------------
First, connect the device to a USB port and note the serial port that appears
on your system. (On Windows this will require the IOTool.inf file.)
Next, connect to that serial port in your favorite programming environment
or with a terminal program such as hyperterminal or minicom. Send the following
text to the device:
program
sh 13
dm 500
sl 13
dm 500
lo 0 9
end

This loads a program to set pin 13 high (turning on the arduino's onboard LED),
delay 500 ms, set the pin low (turning the LED off), delay 500 ms, and then
repeat that from the first step 9 more times. Sending the text `run` followed
by a newline (return key) will cause the LED to blink 10 times.

Hardware Compatibility
----------------------
IOTool is natively compatible with any board powered by an ATmega32u4, including:
- [Arduino Leonardo](http://arduino.cc/en/Main/arduinoBoardLeonardo)
- [Arduino Micro](http://arduino.cc/en/Main/ArduinoBoardMicro)
- [LilyPad Arduino USB](http://arduino.cc/en/Main/ArduinoBoardLilyPadUSB)
- [Adafruit ATmega32u4 Breakout Board](http://www.adafruit.com/products/296)
- [Adafruit Flora](http://www.adafruit.com/products/659)
- [PJRC Teensy 2.0](https://www.pjrc.com/teensy/)
- [SparkFun ATmega32u4 Breakout](https://www.sparkfun.com/products/11117)
- [SparkFun Pro Micro](https://www.sparkfun.com/products/12640)

However, it should be possible with minimal effort to port IOTool to any AVR
microcontroller supported by the LUFA USB library.

Installation
------------
0. Obtain the [LUFA USB library for AVR microcontrollers](http://www.fourwalledcubicle.com/LUFA.php)
AVR-GCC is also required: [source](http://www.nongnu.org/avr-libc/), [Mac](http://www.obdev.at/products/crosspack)
[Windows](http://sourceforge.net/projects/winavr).

1. Configure the IOTool Makefile as follows.
First, set the LUFA_PATH variable to the relative or absolute path to the
LUFA library obtained above.

Second, if you are using an Arduino device, uncomment the ARD_PINS line
in the Makefile.

Third, set AVRDUDE_PORT to the name of the serial port that appears when
the board is plugged in and the reset button is pressed. (On Windows,
an INF file supplied by the board's manufacturer is generally required for
the board to show up as a serial port.) If you will not be using avrdude
to upload the firmware (e.g. with a Teensy you need to use TeensyLoader),
skip this step.

If your microcontroller is not clocked at 16 MHz, adjust the F_CPU value.
This will change the PWM frequencies and delay times, unless additional
code modifications are made (see 'Porting' below).

2. Optional: To customize the device's serial number (used on Macs and some Linuxes
for determining the tty name), edit the #define in `src/usb_serial_base.h`.

3. Press the reset button to enable the firmware to be loaded, and run `make`
from the IOTool directory to compile and upload the code.

4. After the firmware is loaded, a new serial port will appear. On Windows,
this requires the supplied `IOTool.inf` file.

Scripting Language Specification
--------------------------------
### Command summary ###
wh p wait high: pin name
wl p wait low: pin name
wc p wait change: pin name
wt d set wait time: uint16 µs delay
rh p raw wait high: pin name
rl p raw wait low: pin name
rc p raw wait change: pin name
dm d delay ms: uint16 ms delay
du d delay µs: uint16 µs delay
pm p v set PWM: pin name, uint8 or uint16 value
sh p set high: pin name
sl p set low: pin name
st p set high-impedance "tri-state": pin name
ct b character transmit: uint8 byte
cr character receive
lo i c loop: uint8 index, uint16 count
go i goto: uint8 index

program start programming, clearing previous
end end programming, return to immediate-execution mode
run c run program: uint16 count (optional)
\x80\xFF turn serial echo off for non-interactive use
! break out of execution

### PWM-capable pins ###
8-bit @ 62.500 kHz: B7 and D0 (AVR) = 11 and 3 (Arduino)
8-bit @ 31.250 kHz: C7 and D7 (AVR) = 13 and 6 (Arduino)
10-bit @ 15.625 kHz: B5 and B6 (AVR) = 9 and 10 (Arduino)

### Detailed command description ###
*Wait for specific pin value*:`wh pin` (wait for high), `wl pin` (wait for
low), and `wc pin` (wait for change), where _pin_ is a one- or two-character
pin name (either the AVR port-letter + pin-number format, or the arduino
format; more about that below). These commands wait for a stable reading on a
given pin of a high, low, or different-from-current value (respectively). The
specified pin is set to input and the internal pull-up resistor is enabled
before readings are taken. A "stable" reading is defined as pulse lasting
longer than the currently set wait time, which defaults to 10 µs. This
provides protection against stray electromagnetic interference.

*Set waiting time for stable readings*: `wt time`, where 0 < _time_ <= 32767,
in microseconds. The `wh`, `wl`, and `wc` commands wait for a pulse to read at
the desired level for at least this many µs before returning. The default is
10 µs.

*Wait for specific pin value without delay*: `rh pin` (raw wait for high), `rl
pin` (raw wait for low), and `rc pin` (raw wait for change), where _pin_ is a
one- or two-character pin name. As soon as a pin change to the desired state
is detected, these commands return. There is no protection against
bounce/electromagnetic interference in raw wait modes. However, the response
time is faster (see timing data below). The specified pin is set to input and
the internal pull-up resistor is enabled before readings are taken.

*Delay a given interval*: `dm ms` (delay milliseconds) and `du us` (delay
micoseconds), where 0 <= _ms_ <= 65535 and 0 <= _us_ <= 32767. Note: these
delay times are for a chip clocked at 16 MHz. For other clock speeds, adjust
accordingly, or alter the timer counter values (described in the 'Porting'
section below).

*Set a pin's value*: `sh pin` (set high), `sl pin` (set low), and `st pin`
(set tristate), where _pin_ is a one- or two-character pin name. The effects
of setting high and low are obvious. Setting a pin to tri-state
(high-impedance) means that it will not drive a circuit in any direction. (The
pin is set to input and the internal pull-up resistor is disabled.)

*Enable PWM*: `pm pin value`, where _pin_ is a one- or two-character pin name,
and _value_ is a PWM duty cycle in either an 8-bit (0 <= _value_ <= 255) or
10-bit (0 <= _value_ <= 1023) range, depending on the pin specified. There are
six PWM capable pins: two 8-bit pins with a PWM frequency of 62.5 kHz (AVR
pins B7 and D0, and Arduino pins 11 and 3), two 8-bit pins at 31.25 kHz (AVR
C7 and D7 / Arduino 13 and 6), and two 10-bit pins at 15.625 kHz (AVR B5 and
B6 / Arduino 9 and 10). Note: these PWM frequencies are for a chip clocked at
16 MHz. For other clock speeds, adjust accordingly.

*Send and Receive Serial Data to/from Host* `cr` (character receive) and `ct
value` (character transmit), where 0 <= value <= 255. These commands are
useful for synchronizing script execution with the host computer. If the `cr`
command is issued, execution halts until a single byte is sent from the
computer host to the microcontroller. The contents of this byte are discarded
and not echoed back to the host. When `ct` commands are run, a single byte
with the value specified (from 0 to 255) will be transmitted to the host.

*Repeating Commands* `lo index count` (loop back), and `go index` (goto),
where 0 <= _index_ <= 255 and 0 <= _count_ <= 65535. These commands provide
for repeating script commands either a fixed number of times (`lo`), or
indefinitely (`go`). The _index_ parameter refers to the command number in the
current program (starting from 0 as the first command) to jump to. The _count_
parameter for `lo` commands refers to the number of times to execute that
loop. In the usual configuration of looping back to previous steps, this means
"repeat these steps _count_ additional times", for a total of _count_+1
executions of the looped steps. Loops can be nested within other loops.

### Control Commands ###

`program`: This command starts storing a program to run later, and clears any
previously-stored programs. Any command not placed between `program` and `end`
will be immediately executed, allowing direct control over the device pins.
(The commands `lo` and `go` are nonsensical in immediate-execution mode and
will be ignored.)

`end`: end programming, which returns the device to immediate-execution mode.

`run count`: run the program _count_ times (0 < _count_ <= 65535). If _count_
is not specified, the program is run one time. At the end of the program runs,
the string "DONE\r\n" is transmitted to the host computer.

`\x80\xFF`: These two bytes will turn off the serial echo, which is convenient
for non-interactive use. If echo is off, sending these two bytes will NOT turn
echo back on, but the bytes will be echoed back. Thus the host can
unconditionally send these bytes, read back two bytes, and be guaranteed that
echo mode is now off, regardless of what state it started out in.

`!`: Break out of executing a script. If the exclamation-point character is
sent to the device, program execution immediately halts. The only exception is
that a `du` command will not halt until the full delay has elapsed. (This is
to permit the microsecond timer to have the best possible precision by not
having to poll the USB port during the interval.) This allows the host to
indefinitely run a script using a `go` loop, and then cancel out of the script
when required.

AVR and Arduino Pin Names
-------------------------
A compile-time option chooses between the AVR and Arduino pin names listed
below. If -DARDUINO\_PIN\_NAMES is added to the CC_FLAGS line in the Makefile,
then the Arduino names will be used. Otherwise the AVR names are default.

AVR Arduino Comment on Arduino Pin
B0 SS Also Receive LED (Not connected to a pin on Leonardo)
B1 SC Marked SCK on Micro (in ICSP pin cluster on Leonardo)
B2 MO Marked MOSI on Micro (in ICSP pin cluster on Leonardo)
B3 MI Marked MISO on Micro (in ICSP pin cluster on Leonardo)
B4 8
B5 9
B6 10
B7 11
C6 5
C7 13 Also bootloader LED on Arduino
D0 3
D1 2
D2 RX
D3 TX
D4 4
D5 TL Transmit LED, not connected to any pin on Arduino
D6 12
D7 6
E6 7
F0 A5
F1 A4
F4 A3
F5 A2
F6 A1
F7 A0

A Note on Debouncing Switches
-----------------------------
Mechanical switches do not usually transition cleanly, but produce a chatter of
digital 1s and 0s as they open and close. To get a clean reading from a switch,
a "debounce" routine is necessary. The "wait time" set by the `wt` command above
could be used to wait for a steady reading before returning a value. The maximum
wait time is 32 milliseconds, but if a switch bounces longer than that, two
`wh` or `wl` commands could be used back to back to require a clean pulse of
a longer length.

Alternately, one could wait for the leading edge of the switch closure (after
a minimal wait time to exclude spikes from electromagnetic interference), and
then simply not look for switching events for the length of time the switch
might bounce for (100 ms, say). For example, here's a simple program that toggles
a LED on pin B0 in accordance with a toggled switch on B1:
wh B0
sh B1
dm 100
wl B0
sl B1
dm 100
go 0

By simply delaying 100 ms before waiting for the next switch event, the above
script excludes the possibility of reading switch bounce mistakenly.

Program Step Execution Speed
----------------------------
On a chip clocked at 16 MHz, the following commands were timed as follows:
wh: 9.0 µs + delay specified by wt + time waiting for signal
wl: 9.0 µs + delay specified by wt + time waiting for signal
rh: 6.7 µs + time waiting for signal
rl: 6.7 µs + time waiting for signal
dm: 10.6 µs + delay time
du: 4.8 µs + delay time
pm: 5.5 µs for 8-bit PWM and 5.8 µs for 10-bit
sh: 6.0 µs
sl: 6.0 µs
st: 6.0 µs
ct: ~22 µs depending, on USB bus
lo: 6.0 µs + 5 µs overhead/iteration
go: 2.9 µs

Porting to Another AVR Microcontroller
--------------------------------------
Porting this to another USB-enabled AVR microcontroller should be relatively
simple. All that is required is to edit the pin definitions in `src/pins.c` to
match the pins provided by the new microcontroller, and adjust the timer usage
in `interpreter.c` to match the capabilities and clock speed of the new chip.

On the ATmega32u4 clocked at 16 MHz, the following timers are used for internal
timing, delay timing, and PWM generation.

### Timer/Counter0 ###
Prescaler: 1 (62.5 ns/count)
Mode: Fast PWM
Frequency: 8-bit at 62.5 ns/count = 62.5 kHz
OCR0A: used to define the PWM waveform on pin OC0A (B7)
OCR0B: used to define the PWM waveform on pin OC0B (D0)

### Timer/Counter1 ###
Prescaler: 1 (62.5 ns/count)
Mode: Fast PWM, 10-bit (ICR1 = 2**10, WGM13:0 bits set to 14)
Frequency: 10-bit at 62.5 ns/count = 15.625 kHz
OCR1A: used to define the PWM waveform on pin OC1A (B5)
OCR1B: used to define the PWM waveform on pin OC1B (B6)

### Timer/Counter3 ###
Prescaler: 8 (0.5 µs/count)
Mode: Normal
OCR3A: used for ms timer ISR, which increments register by 2000 each call
OCR3B: used for µs timer: set to desired delay time and then wait on OCF3B
OCR3C: used for USB task timer ISR, must be set to 60000 (30 ms) or less

### Timer/Counter4 ###
Prescaler: 2 (125 ns/count)
Mode: Fast PWM, 8-bit (OCR4C = 2**8)
Frequency: 8-bit at 125 ns/count = 31.25 kHz
OCR4A: used to define the PWM waveform on pin OC4A (C7)
OCR4D: used to define the PWM waveform on pin OC4D (D7)
Loading

0 comments on commit c7af6c2

Please sign in to comment.