Arduino Playground is read-only starting December 31st, 2018. For more info please look at this Forum Post

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 preceded 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 timeouts 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:
	    {
	      // unknown 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
https://forum.arduino.cc/index.php/topic,38381.html
*/

#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