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