New and Improved(tm) version. Uses a state machine to process user input to get rid of those pesky timeouts.
// -*- Mode:c -*-
// The above incantation tells emacs to do C style syntax highlighting
// Digital I/O sketch
// (slightly misnamed as it does analogue as well)
//
// This sketch allows the user to use the Arduino as a generic DIO module.
//
// Talk to the board at 9600n81 sending commands to read or set
// the various pins.
//
// DIO pins 0 and 1 are reserved for RS232 (as usual).
// The remaining DIO pins start as un-tied INPUT (high impedance).
//
// The command syntax is very simple. Erroneous inputs are ignored
// and result in a brief error message being printed. Case is
// ignored as are space characters. Input times out 1 second after
// the first character is received. All numbers are in HEX.
//
// DIO States are
//
// H : The pin is configured as a digital OUTPUT, set HIGH
// L : The pin is configured as a digital INPUT, set LOW
// P xx : The pin is configured as a PWM analogue output.
// xx represents the output level in hex (0-ff)
// F : The pin is configured as a digital INPUT.
// It is tied high and currently reads high
// (i.e. it may be floating).
// G : The pin is configured as a digital INPUT.
// It is tied high and currently reads low
// (i.e. it is grounded).
// U : The pin is configured as a digital INPUT.
// It is not tied and currently reads high (up).
// D : The pin is configured as a digital INPUT.
// It is not tied and currently reads high (down).
//
// Q|I|O
// Query State
// Query the state of all DIO pins (diO), ADC pins (adc Input)
// or both(Q)
//
// All other commands are preceeded by a pin number, 2-D for
// the DIO pins and 0-5 for the ADC pins.
//
// <pin>H
// <pin>L : Set the pin as digital OUTPUT, HIGH or LOW, e.g.
// "D H" will set pin 13 HIGH and light the LED
// "dl" will set pin 13 LOW and extinguish the LED
//
// <pin>T : Set the pin as digital INPUT and tie it high
//
// <pin>U : Set the pin as digital INPUT but do not tie it
//
// <pin> P xx : Set the pin as PWM output, e.g.
// "9 p 42" should set pin 9 to about 1.3V
//
// <pin>O : Query the state of the given DIO pin (see ? command)
//
// <pin>I : Query the state of the given ADC pin (see ? command)
//
//
// ToDo:
// Add an option to save the pin configuration to EEPROM
// Enable interrupt lines
// Allow user to toggle debug mode
#include <ctype.h> // toupper, isalnum
#define MASK_DIO_OUT 1
#define MASK_DIO_HIGH 2
#define MASK_DIO_PWM 4
//#define BAUD 9600
#define BAUD 115200
// state of the DIO lines
// MASK + (PWM << 8)
int dioState[14]; // 0 and 1 unused, set as serial
int verbose = 1;
#define MODE_IDLE 0
#define MODE_PIN 1
#define MODE_PWM 2
#define MODE_PWM2 3
int mode;
int pin;
int pwm;
unsigned long lastRxTime;
// for LOOP mode, set to 0 to disable loop mode
unsigned long nextTxTime;
unsigned long interval;
// Print out the status of the given DIO pin
char * DioPinStatus(unsigned pin)
{
if ((pin < 2) || (pin > 14))
{
return "invalid DIO pin";
}
// is it set to PWM?
if (dioState[pin] & MASK_DIO_PWM)
{
Serial.print("P");
Serial.print((dioState[pin] >> 8) & 0xFF, HEX);
return "";
}
// is the pin set to DIGITAL OUT?
if (dioState[pin] & MASK_DIO_OUT)
{
Serial.print((dioState[pin] & MASK_DIO_HIGH) ? "H" : "L");
return "";
}
int idx = (digitalRead(pin) == HIGH) ? 0 : 2;
if (dioState[pin] & MASK_DIO_HIGH)
{
idx++;
}
Serial.print("UFDG"[idx]);
return "";
}
// Print out the status of the given ADC pin
char * ADCPinStatus(unsigned pin)
{
if (pin > 5)
{
return "Invalid ADC pin";
}
int value = analogRead(pin);
if (value < 256)
{
Serial.print((value < 16) ? "00" : "0");
}
Serial.print(analogRead(pin), HEX);
return "";
}
// Query, print the status of all pins
void QueryDIO()
{
for (int pin = 2; pin < 14; pin++)
{
if (pin > 2)
{
Serial.print(((pin-2)%4) ? " " : " ");
}
DioPinStatus(pin);
}
Serial.println();
}
void QueryADC()
{
for (int pin = 0; pin < 6; pin++)
{
if (pin)
{
Serial.print((pin%2)?" ":" ");
}
ADCPinStatus(pin);
}
Serial.println();
}
void Query()
{
QueryDIO();
QueryADC();
}
// help message.
void RTFM()
{
// svn propset svn:keywords "Author Date Revision" DIO.pde
Serial.print(
"$Author: thomas.sprinkmeier $\r\n"
"$Date: 2008-07-04 09:49:56 +0930 (Fri, 04 Jul 2008) $\r\n"
"$Revision: 26 $\r\n"
"(Q|I|O) : Query all/ADC/DIO pins\r\n"
"L : Loop mode (press any key to abort)\r\n"
"R : Reset (DIO all untied inputs)\r\n"
"V : Toggle verbose mode\r\n"
"pin(H|L) : Set to Digital Output, HIGH or LOW\r\n"
"pin(T|U) : Set pin as Digital Input, tied or untied\r\n"
"pinI : Read ADC\r\n"
"pinO : DIO pin state\r\n"
"pinPxx : Set PWM to xx (hex)\r\n\r\n");
}
int readSerialChar()
{
// Is there anything available?
if(!Serial.available())
{
return -1;;
}
int c = Serial.read();
if (isspace(c))
{
return -1;
}
return toupper(c);
}
// convert a single char {'0'..'9','A'..'F'}
int CharToHex(char c)
{
if ((c >= '0') && (c <= '9'))
{
return c - '0';
}
if ((c >= 'A') && (c <= 'F'))
{
return 10 + (c - 'A');
}
// error value
return -1;
}
// SetX the DIO pin to mode X
// update the dioState array as needed
// HIGH or LOW (implies OUTPUT mode)
char * SetHL(unsigned pin, int H)
{
if ((pin < 2) || (pin > 13))
{
return "invalid pin for H/L";
}
if (!(dioState[pin] & MASK_DIO_OUT))
{
pinMode(pin, OUTPUT);
}
digitalWrite(pin, H ? HIGH : LOW);
dioState[pin] = (MASK_DIO_OUT | (H ? MASK_DIO_HIGH : 0));
return 0;
}
// TIED or UNTIED (implies INPUT mode)
char * SetTU(unsigned pin, int T)
{
if ((pin < 2) || (pin > 13))
{
return "invalid pin for T/U";
}
if (dioState[pin] & MASK_DIO_OUT)
{
pinMode(pin, INPUT);
}
digitalWrite(pin, T ? HIGH : LOW);
dioState[pin] = T ? MASK_DIO_HIGH : 0;
return 0;
}
// state machine called from ye olde loop function
// return NULL if OK
// return char * error message if not
char * proc()
{
// what time is it now (used to tineouts and loop mode)
unsigned long now = millis();
// is loop mode enabled?
if (nextTxTime)
{
// is it time for another output?
if (now >= nextTxTime)
{
// print query and reset time threshold
Serial.print(now, HEX);
Serial.print(" ");
Serial.print(now - nextTxTime, HEX);
Serial.println();
Query();
nextTxTime += interval;
// slowly drift to multiples of interval
nextTxTime -= ((nextTxTime % interval)+2) / 3;
}
}
// try to get a character from the serial port
int c = readSerialChar();
// if nothing was received
if (c < 0)
{
// if we're idle anyway, or we're still
// waiting for more characters...
if ((mode == MODE_IDLE) || ((lastRxTime + 1000) > now))
{
// do nothing
return 0;
}
// let the user know the last command timed out
return "timeout";
}
// OK, we received something!
lastRxTime = now;
// depending on what more we're currently in....
switch(mode)
{
case MODE_IDLE:
{
switch(c)
{
case '?':
{
RTFM();
return 0;
}
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case 'A': case 'B': case 'C': case 'D':
{
pin = CharToHex(c);
mode = MODE_PIN;
return 0;
}
case 'I':
{
QueryADC();
nextTxTime = 0;
return 0;
}
case 'L':
{
// if we're already looping ...
if ((nextTxTime) && (interval > 0x10))
{
// ... speed up
interval >>= 1;
}
else
{
// ... else start the interval at 65.536 seconds
interval = 0x10000;
}
// print the current looping interval and
// set the nextTxTime to enable looping
Serial.print(interval, HEX);
Serial.println();
nextTxTime = now;
return 0;
}
case 'O':
{
QueryDIO();
nextTxTime = 0;
return 0;
}
case 'Q':
{
Query();
nextTxTime = 0;
return 0;
}
case 'R':
{
setup();
nextTxTime = 0;
return 0;
}
case 'V':
{
verbose = !verbose;
return 0;
}
default:
{
// unknow input character
return "unknown command";
}
}
} break;
case MODE_PIN:
{
// chances are we'll be idle next
mode = MODE_IDLE;
switch(c)
{
case 'H':
case 'L':
{
return SetHL(pin, c == 'H');
}
case 'I':
{
nextTxTime = 0;
return ADCPinStatus(pin);
}
case 'O':
{
nextTxTime = 0;
return DioPinStatus(pin);
}
case 'P':
{
// make sure the user nominated a PWM pin
if ((pin == 0x3) ||
(pin == 0x5) ||
(pin == 0x6) ||
(pin == 0x9) ||
(pin == 0xa) ||
(pin == 0xb))
{
mode = MODE_PWM;
return 0;
}
return "invalid PWM pin";
}
case 'T':
case 'U':
{
return SetTU(pin, c == 'T');
}
default:
{
return "unknown pin command";
}
}
} break;
case MODE_PWM:
{
pwm = CharToHex(c);
if (pwm < 0)
{
mode = MODE_IDLE;
return "Invalid PWM1 level";
}
mode = MODE_PWM2;
return 0;
}
default: //case MODE_PWM2:
{
// whatever happens now we go back to idle mode
mode = MODE_IDLE;
int tmp = CharToHex(c);
if (tmp < 0)
{
return "Invalid PWM2 level";
}
pwm = ((pwm << 4) + tmp);
dioState[pin] = MASK_DIO_PWM + (256 * pwm);
analogWrite(pin, pwm);
return 0;
}
}
}
// Setup. Not much to do here
void setup()
{
// set up serial port
Serial.begin(BAUD);
// initialise the DIO pins (0 and 1 are reserved!
for (unsigned pin = 2; pin < 14; pin++)
{
// Set state to OUT, then set to untied.
// This will force pin being set to INPUT
// (which is the default anyway but it can't hurt)
dioState[pin] = MASK_DIO_OUT;
SetTU(pin, 0);
}
// call RTFM(); ?
// call Query(); ?
// restore state from EEPROM?
// start in IDLE mode
mode = MODE_IDLE;
}
void loop ()
{
// run the state machine...
char * errMsg = proc();
// OK?
if (!errMsg)
{
return;
}
// Not OK.... back to IDLE mode, print error message
mode = MODE_IDLE;
Serial.print(errMsg);
Serial.println();
}
#if 0
/*
IF YOU ARE USING V0011 THEN APPLY THIS PATCH!!!
Apparently 12 is fixed, but in 11 there's a nasty bug in the timer.
see
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1205791157/7#7
*/
#define junk "
begin-base64 644 TIMER_PATCH.bz2
QlpoOTFBWSZTWUwyg/0AAczfgFYQSH//+/+v36S//9+6UAMvW7jdhd3Bthom
hTNU2SeRkJ6mjRoek9T0gAaepoNqA9TRoNCJ6IMptTUaAyB6gAAaAADT1ABK
amSibKabUDIbUAAAAAAAAaHGTJoxDE0wEDAmmCMExNNNABhBJIRppNGTER6K
bTJGI0BkDQYjQMajJcDQAY1zbtVWsIs16JIgxxv5lPmf3YiGMb1/C6KZVow0
rxwbqsfQ0NyhA2JOAewDiMEOAY8+9TblwNfBdJfI45LHZxcvCOPJlJ10qmz8
AzbIl+FXpYMEIgUryjQnPzRH5+9tLlzmThzVIjSpUQ7latLJGk6VB9yTfraM
kXNe82oqSTrKc1YldvjoStnwOZ4rm1OJb2s8msFAzWkwzRyOgiAzkG/zcTub
mm4xt0LZyWxguzxyQIx790KdJ5IuYYN85Xl7e711XcPKNgGu8IhIcVhk2b4c
gwFZUSzaArJxVPHs8emOsvTyINANXG/Jmnpg+jcwXcyvkjkN7RPBT0zx6xhz
32TRY5yCfGwVzTWxOJNBiFircWpIGKyPUHsU49uhi2tMXS75VoNRt7ASioKs
b3H+ex+kdyKFtPwmPqyKV2qg2h3KoY50ZAxnOdoSCS2kV2IyLsGHIeopyO0I
DyxW9xjTjS0kTEkwOBkOLzIyRJT6bCeZES0+nEcOwwLAJI9G9mDajQijONeh
50p66A+a1/Z3J0d4dVgdLB1hqxh7FJXSWFbU0Ur5Z0FsCAM0pDpIVgSNKw8s
gFoCcl4HmE0ujuJuadTu3ow1ZdHLywOX8ErKMuMx82bExUBaYsIRoS8NGKCV
NgcWIqhRCBAQcMh6ABwwBSUAmYpkqcAZZxxNilqnDE8vLmrSrzRTVWpPC6Yq
otFnQVkgmUzA8lqLrxgYYMJ2Ny5XZywYImOhJ4D0VJWKJTWUrYUKKNTOa0cy
onZMnrcSme9hMHgMUsgy4hDjwjwYToMqIB4ghoLgmSmHBOy1qC01T4SLIJrU
rBKdgZ4N6mUFlDQRQ8s5JTeatYVgiRXOAT3BBTRjM0jOHlEU1WyqJqIQeTis
AaSXzvyMZciFciApHIH2nQdZHIQgQZ03iRUxJtb1UFEAzBXO6A2GadSBjxu/
4u5IpwoSCYZQf6A=
====
"
/**
Sorry about the encoding but the precompiler kept barfing
when I tried to include the pach 'raw'
*/
#endif