Monthly Archives: April 2013

CoCoFEST!

This past weekend, the Glenside Color Computer Club in Illinois hosted the 22nd annual “Last” Chicago CoCoFEST! It was held at a new location which I think was the same place the Vintage Computer Festival had used recently.

For those who couldn’t attend, I have some DVDs available from the 2009 and 2010 event. They primarily contain interviews with various vendors and attendees, and some footage of the festival — clips from the No Minimum Bid Auction and such. There is often shocking footage of our Saturday Night Music Jam, where tuning is optional and no one knows the words.

The 2009 DVD also contains my fest trip report and digital photos as bonus DVD-ROM content. The brand-new 2010 DVD contains more video (about 96 minutes) but there was no room for the bonus content.

If you want a copy, $5 each is all it takes, and if you don’t mind, throw in $1 for shipping. You can PayPal me at alsplace@pobox.com

I am being sent some video from the past years I missed, and hope to bring out some more discs in the near future. Some clips will be posted to YouTube, but I am hoping to raise money from selling DVDs so I can maintain my Glenside club membership and such.

Thanks…

The EthaWin user interface.

Years ago, I created the EthaWin user interface for the Radio Shack Color Computer 3 running OS-9. This is its story…

At the time, the main GUI for CoCO OS-9 apps was something called MultiView. MultiView was sold separately from OS-9 and no runtime version was available, so anyone who wanted to sell a MultiView app could only sell it to users who also owned MultiView. This certainly limited the potential market for MultiView apps, and probably explains why there were never as many GUI-based OS-9 apps as there could have been.

Another issue was performance. When running OS-9 on a graphics screen, it was pushing around far more data than an 80×24 hardware text screen required, and thus most of us never really used the graphical displays unless we really needed proportional fonts and other features. (Yes, back in the 1980s, there were proportional screen fonts for an operating system running on an 8-bit home computer. I guess we just took it for granted at the time, but it seems really forward thinking in retrospect.)

It also goes without saying that running a 640×225 graphic screen chewed up more of the limited system RAM than a text screen, as well, making things quite a tight fit on a 128K CoCo 3 if you had multiple windows and programs open.

One of the last obstacles MultiView faced was the learning curve. Setting it up and using it was not really any different (or any more difficult) than any other OS-9 program, but programming it was another issue. Really smart programmers didn’t seem to have any problem creating MultiView apps in BASIC09 or C or 6809 assembly. I, unfortunately, was not one of those. Reading the documentation in the MultiView developers manual made my head spin. I am not sure I even knew C programming yet, so many of the concepts were completely lost on me.

So, with all this in mind, I decided to create “the user interface for the rest of us.” I wanted something that gave the advantages of a GUI — a menu bar with pull down menus, pop-up overlay windows, and mouse control. I also wanted it te completely and easily usable from the keyboard (something MultiView, and many modern operating systems, do not provide). It also had to be fast, and use very little memory. And most importantly, it had to be super simple to program for so anyone could create programs to use it.

A GUI without Graphics
I realized that most of the GUI applications I had really didn’t need to be on graphical screens. They were just programs with mouse support. Apps like this could just as easily run on a text screen if there was some way to give them a menu and mouse system.

My solution was to build EthaWin, which would operate on a high-speed, low memory text screen. I would make use of OS-9s powerful terminal capabilities which included things like overlay windows and locking the working area so you could scroll just some parts of the display without effecting the rest.

I would create a menu bar at the top, and the user would be able to use a mouse (moving a cursor block around) to pull down any menu and make a selection. Or, they would be able to use the ALT-key to select a menu, and arrow keys to move between options (or between different menus).

I wrote EthaWin using the original OS-9 K&R C compiler, and designed it so all the developer had to do was set some variables and create some arrays which represented the menus and their options. It was super simple C code, and it worked great.

The first (and probably only) EthaWin app that was created was a disk utility I wrote called “Towel.” It quickly became on of the pieces of software I used more than anything else, since it greatly simplified the process of selectively copying files or doing other common operations. It sold very well, and I hope others found it as useful.

EthaWin was later ported to OS-9/68000 on the MM/1 computer, which always operated on a graphical screen (like a Macintosh or Atari ST or Amiga did). The code worked the same way, but graphics commands were used to decorate the menus and make things look more like a traditional GUI. The MM/1 also had an on-screen mouse at all times, so it used that instead of the cursor moving trick I did on the CoCo’s hardware text screen.

In 1995, I took a full-time job with Microware Systems, the creators of OS-9. Sub-Etha Software shut down to avoid a conflict of interest, but that didn’t stop me from trying to do more with EthaWin.

At Microware, we all had SUN Workstations, and used green screen terminals to hook to various OS-9 boxes via serial ports. After seeing some of the rather fancy curses Unix apps that did all kinds of windowing type things via a terminal session, I set out to port EthaWin to generic OS-9/68000, using nothing but the Termcap library. Termcap (terminal capabilities) was a standard way of presenting different types of screen control codes, such as VT100 or ANSI. A Termcap program would read an environment variable to learn what kind of terminal the user was using, then it would load the specific codes for that terminal from a definition text file. A Termcap program could clear the screen or move the cursor, without needing to know what escape sequence did that for the user’s terminal.

The approach I took to port EthaWin to Termcap was to write a new library, which I called TCWin (Termcap Windows). I created Termcap versions of all the standard CoCo OS-9 library calls that did things to the screen, such as turning bold on, or underline off. I figured once I had all of those, none of the rest of my source code would need to be touched. Much.

On the CoCo (and under K-Windows on the MM/1), overlay windows were managed by the system, so if you drew one, used it, then made it go away, whatever was under it would be restored. While there were some terminal protocols that offered things like this, I could not rely on that. I decided to create a buffer to represent the virtual screen, with bytes for the character and its attributes (blink, bold, underline, etc.). My routines would draw to this buffer and then the buffer would be updated to the terminal screen, and if I created an overlay window, I would create a new buffer for that, and be able to restore what was under it since the original screen buffer still existed.

I thought it was a very clever solution, and other than having to create some new wrapper functions for printing to the screen and disabling mouse functions, it worked. All functions of the normal EthaWin could be used, including scrolling. The TCWin library, when used by itself, could even do some things that the CoCo version could not do, such as having independent control of any window on the screen. (On the CoCo implementation, once an overlay window was created, all I/O went just to that window and you couldn’t use any background windows until you closed the overlay.)

Just for fun, while on a work trip to Rochester, New York, I sat around with an old CoCo friend of mine one night (who had long since moved to PCs) and ported EthaWIn yet again, this type to run on a DOS screen. Sadly, the code for this port was lost with a Toshiba hard drive crash soon afterwards. That was when I became aware of the concept of backing up hard drives…

But I digress… I could (and maybe will) write a whole series of articles on how I created this environment, but instead, I wonder… Could I port it to an Arduino? I have plans to dig out the old code and rewrite/update it for the CoCo, to use as part of my CoCo Ethernet experiment, so why not also update it for other uses?

I could not find an implementation of Termcap for the Arduino, but that’s not really important. The only capability I really need is the character sequences for certain functions (clear, cursor move, etc.) for whatever terminal types I plan to support. Initially, I might say I will only support VT100 and ANSI (very common for Unix and PCs). Then, someone could telnet in to an Ethernet Arduino, or connect using a good terminal program (not the crappy serial monitor that’s part of the Arduino IDE), and run apps more in the style of Unix — with nice text screens and menus and such.

There are some technical challenges in doing this with just 2K of RAM. Since I was previously caching a full copy of the window (and attributes) as well as overlay windows, I might have to give up some of that support. But I think some of the basics could be supported.

We shall see.

If I do decide to work on this, I will create a new category for EthaWin posts so those interested can follow my progress (or lack thereof).

Until then…

Sub-Etha Software’s Arduino Telnet server

2014-03-03: The latest source code will be found on GitHub.

I have not had time to work on this lately, but I thought I would share my current work-in-progress Telnet server code in case anyone wanted to experiment with it.

A sample setup()/loop() is provided to demonstrate how it may be used by your program.

NOTE: For the dual server code to work, you must modify your Ethernet library to fix a bug. I will clean up the code later so you can conditionally compile it without this support. This code allows the server to respond to other telnet requests while someone is using the system. It will send back a message, letting them know the system is busy and to try later.

Supported: Are You There, ECHO and maybe a few other things.

WORK IN PROGRESS!

#if 1
__asm volatile ("nop");
#endif
/*-----------------------------------------------------------------------------

Sub-Etha Software's Arduino Telnet Server
By Allen C. Huffman
www.subethasoftware.com

This is a Telnet Server. It properly (?) parses various Telnet escape
sequences, and honors a few of them. It has placed where all the others can
be trapped and handled, if needed.

It can be compiled to use RAM storage for strings, or Flash storage.

It can be compiled to include extensive Telnet debug output, showing all
the incoming and outgoing Telnet escape sequences.

For production use, it is recommended to have TELNET_DEBUG off, and USE_FLASH
on.

2013-04-12 0.0 allenh - First posted to www.subethasoftware.com.

CHECK BACK FOR UPDATES! Much more still to be done...
-----------------------------------------------------------------------------*/
#define VERSION "0.0"

// Define this to make all the strings live in Flash instead of RAM.
#define USE_FLASH

// Define this to include printing basic Telnet protocol information. This
// will include a bunch of Flash strings.
#define TELNET_DEBUG // takes about 1176 bytes of Flash + 14 bytes of RAM.

// Define this to use multiserver support,but only if you have fixed your
// Ethernet library to allow it. See:
// http://subethasoftware.com/2013/04/09/arduino-ethernet-and-multiple-socket-server-connections/
//#define TELNET_MULTISERVER

#ifdef USE_FLASH
#define FLASHMEM PROGMEM
#define FLASHSTR(x) (const __FlashStringHelper*)(x)
#define FLASHPTR(x) (const __FlashStringHelper*)pgm_read_word(&x)
#else
// If not using FLASH, no special casting or keywords.
#define FLASHMEM
#define FLASHSTR(x) (x) //(const char *)(x)
#define FLASHPTR(x) (x) //(const char *)(x)
#endif
//#define PGMT(pgm_ptr) (reinterpret_cast<const __FlashStringHelper *>(pgm_ptr))

/*---------------------------------------------------------------------------*/
// Telnet protocol stuff.
// Reference: http://www.softpanorama.net/Net/Application_layer/telnet.shtml

#include <avr/pgmspace.h>
#include <SPI.h>
#include <Ethernet.h>

// Configure MAC address and IP address.
byte mac[] FLASHMEM = { 0x42, 0x42, 0x42, 0x42, 0x42, 0x42 };
byte ip[] FLASHMEM = {10, 0, 1, 42};

#define TELNETID  "Sub-Etha Software's Arduino Telnet server."
//#define TELNETID  "Connection Established."
#define TELNETAYT "Yes. Why do you ask?"

/*---------------------------------------------------------------------------*/
// PROTOTYPES
/*---------------------------------------------------------------------------*/
byte telnetRead(EthernetClient client);
byte telnetInput(EthernetClient client, char *cmdLine, byte len);

/*---------------------------------------------------------------------------*/
// DEFINES
/*---------------------------------------------------------------------------*/

#define NUL   0  // NULL
#define BEL   7  // Bell
#define BS    8  // Backspace
#define HT    9  // Horizontal tab
#define LF    10 // Line feed
#define VT    11 // Vertical tab
#define FF    12 // Form feed
#define CR    13 // Carriage return
#define DEL   0x7f // Delete key for some terminals.

// Commands - IAC,<type of operation>,<option>
#define EOF   236 // End of file?
#define SP    237 // Suspend process?
#define ABORT 238 // Abort process?
#define EOR   239 // End of record?
#define SE    240 // End of subnegotiation parameters
#define NOP   241 // No operation
#define DM    242 // Data mark
#define BRK   243 // Break
#define IP    244 // Suspend
#define AO    245 // Abort output
#define AYT   246 // Are you there?
#define EC    247 // Erase character
#define EL    248 // Erase line
#define GA    249 // Go ahead
#define SB    250 // Subnegotiation of the indicated option follows
#define WILL  251 // Indicates the desire to being performing
#define WONT  252 // Indicates the refusal to perform
#define DO    253 // Indicates the request that the other party performs
#define DONT  254 // Indicates the demand that the other party stop performing
#define IAC   255 // Interpet as command

// Telnet Options
// http://www.iana.org/assignments/telnet-options/telnet-options.xml
// http://www.tcpipguide.com/free/t_TelnetOptionsandOptionNegotiation-2.htm
#define OPT_TRANSBIN   0  // TRANSMIT-BINARY
#define OPT_ECHO       1  // ECHO
#define OPT_RECONNECT  2  // reconnection
#define OPT_SUPGA      3  // SUPPRESS-GO-AHEAD
#define OPT_AMSN       4  // approx message size negotiation
#define OPT_STATUS     5  // STATUS
#define OPT_TIMINGMARK 6  // TIMING-MARK
#define OPT_RCTE       7  // RCTE remote controlled trans and echo
#define OPT_OUTLINEWID 8  // output line width
#define OPT_OUTPAGESIZ 9  // output page size
#define OPT_NAOCRD     10 // output carraige return disposition
#define OPT_NAOHTS     11 // output horizontal tab stops
#define OPT_NAOHTD     12 // output horizontal tab stop disposition
#define OPT_NAOFFD     13 // output formfeed disposition
#define OPT_NAOVTS     14 // output vertical tabstops
#define OPT_NAOVTD     15 // output vertical tab disposition
#define OPT_NAOLFD     16 // output linefeed disposition
#define OPT_EXTENDASC  17 // EXTEND-ASCII extended ascii
#define OPT_LOGOUT     18 // LOGOUT
#define OPT_BM         19 // BM byte macro
#define OPT_DET        20 // DET data entry terminal
#define OPT_SUPDUP     21 // SUPDUP display protocol
#define OPT_SUPDUPOUT  22 // SUPDUP-OUTPUT
#define OPT_SENDLOC    23 // SEND-LOCATION
#define OPT_TERMTYPE   24 // TERMINAL-TYPE
#define OPT_EOR        25 // END-OF-RECORD
#define OPT_TUID       26 // TUID tacacs user id
#define OPT_OUTMRK     27 // OUTMRK output marking
#define OPT_TTYLOC     28 // TTYLOC terminal location number
#define OPT_3270REGIME 29 // 3270-REGIME telnet 3270 regime
#define OPT_X3PAD      30 // X.3-PAD
#define OPT_NAWS       31 // NAWS negotiation about window size
#define OPT_TERMSPEED  32 // TERMINAL-SPEED
#define OPT_REMFLOWCTL 33 // TOGGLE-FLOW-CONTROL
#define OPT_LINEMODE   34 // LINEMODE
#define OPT_XDISPLOC   35 // X-DISPLAY-LOCATION (XDISPLOC)
#define OPT_ENVIRON    36 // ENVIRON telnet environment
#define OPT_AUTHEN     37 // AUTHENTICATION
#define OPT_ENCRYPT    38 // ENCRYPT encryption option
#define OPT_NEWENVIRON 39 // NEW-ENVIRON telnet new environment
#define OPT_TN3270E    40 // TN3270E
#define OPT_XAUTH      41 // *XAUTH
#define OPT_CHARSET    42 // CHARSET
#define OPT_RSP        43 // *telnet remote serial port (RSP)
#define OPT_COMMPORT   44 // COM-PORT-OPTION comm port control option
#define OPT_SUPPECHO   45 // *telnet suppress local echo
#define OPT_STARTTLS   46 // *telnet start TLP
#define OPT_KERMIT     47 // KERMIT
#define OPT_SENDURL    48 // *SEND-URL
#define OPT_FORWARDX   49 // *FORWARD-X
// 50-137 Unassigned
#define OPT_EXOPL      255// EXTENDED-OPTIONS-LIST (EXOPL) extended opt list

/*---------------------------------------------------------------------------*/
// GLOBALS
/*---------------------------------------------------------------------------*/

// Some globals the Ethernet stuff needs.
EthernetServer telnetServer = EthernetServer(23); // Server on this port.
#if defined(TELNET_MULTISERVER)
EthernetServer goawayServer = EthernetServer(23); // Additional listener.
#endif
EthernetClient client;                            // Client connection.
boolean        telnetConnected = false;
boolean        offlineMode = false;
uint8_t        modeFlags = 0;                     // Global option bit flats.

//#define telnetModeEnable(x)  (modeFlags = modeFlags | x)
//#define telnetModeDisable(x) (modeFlags = modeFlags & ~x)
#define telnetMode(x)        (modeFlags & x)

#define MODE_SUPGA    bit(0)
#define MODE_ECHO     bit(1)
#define MODE_LINEMODE bit(2)

enum TelnetModes
{
  MODE_LOOKING_FOR_CMD,
  MODE_LOOKING_FOR_TYPE,
  MODE_LOOKING_FOR_OPT,
  MODE_LOOKING_FOR_SB_OPT,
  MODE_LOOKING_FOR_OPT_VAL,
  MODE_LOOKING_FOR_SE,
  MODE_LOOKING_FOR_DO_OPT,
  MODE_LOOKING_FOR_DONT_OPT,
  MODE_LOOKING_FOR_WILL_OPT,
  MODE_LOOKING_FOR_WONT_OPT,
  MODE_DONE
};

#if defined(TELNET_DEBUG)
// Store these strings in Flash to save RAM.
const char SEstr[]   FLASHMEM = "SE";
const char NOPstr[]  FLASHMEM = "NO";
const char DMstr[]   FLASHMEM = "DM";
const char BRKstr[]  FLASHMEM = "BRK";
const char IPstr[]   FLASHMEM = "IP";
const char AOstr[]   FLASHMEM = "AO";
const char AYTstr[]  FLASHMEM = "AYT";
const char ECstr[]   FLASHMEM = "EC";
const char ELstr[]   FLASHMEM = "EL";
const char GAstr[]   FLASHMEM = "GA";
const char SBstr[]   FLASHMEM = "SB";
const char WILLstr[] FLASHMEM = "WILL";
const char WONTstr[] FLASHMEM = "WONT";
const char DOstr[]   FLASHMEM = "DO";
const char DONTstr[] FLASHMEM = "DONT";
const char IACstr[]  FLASHMEM = "IAC";

// Create an array of pointers to Flash strings, in Flash.
const char *telnetCmd[] FLASHMEM = // 240-255
{
  SEstr, NOPstr, DMstr, BRKstr, IPstr, AOstr, AYTstr, ECstr,
  ELstr, GAstr, SBstr, WILLstr, WONTstr, DOstr, DONTstr, IACstr
};

// Store these strings in Flash to save RAM.
// "lowercase" are items where I couldn't find the official string
// name (not part of an RFC). More research is needed.
// 0-9
const char opt_transbin[]   FLASHMEM = "TRANSMIT-BINARY";
const char opt_echo[]       FLASHMEM = "ECHO";
const char opt_reconnect[]  FLASHMEM = "reconnection";
const char opt_supga[]      FLASHMEM = "SUPPRESS-GO-AHEAD";
const char opt_status[]     FLASHMEM = "STATUS";
const char opt_amsn[]       FLASHMEM = "amsn";
const char opt_timingmark[] FLASHMEM = "TIMING-MARK";
const char opt_rcte[]       FLASHMEM = "RCTE";
const char opt_outlinewid[] FLASHMEM = "output-line-width";
const char opt_outpagesiz[] FLASHMEM = "output-page-size";
// 10-19
const char opt_naocrd[]     FLASHMEM = "NAOCRD";
const char opt_naohts[]     FLASHMEM = "NAOHTS";
const char opt_naohtd[]     FLASHMEM = "NAOHTD";
const char opt_naoffd[]     FLASHMEM = "NAOFFD";
const char opt_naovts[]     FLASHMEM = "NAOVTS";
const char opt_naovtd[]     FLASHMEM = "NAOVTD";
const char opt_naolfd[]     FLASHMEM = "NAOLFD";
const char opt_extendasc[]  FLASHMEM = "EXTEND-ASCII";
const char opt_logout[]     FLASHMEM = "LOGOUT";
const char opt_bm[]         FLASHMEM = "BM";
// 20-29
const char opt_det[]        FLASHMEM = "DET";
const char opt_supdup[]     FLASHMEM = "SUPDUP";
const char opt_supdupout[]  FLASHMEM = "SUPDUP-OUTPUT";
const char opt_sendloc[]    FLASHMEM = "SEND-LOCATION";
const char opt_termtype[]   FLASHMEM = "TERMINAL-TYPE";
const char opt_eor[]        FLASHMEM = "END-OF-RECORD";
const char opt_tuid[]       FLASHMEM = "TUID";
const char opt_outmrk[]     FLASHMEM = "OUTMRK";
const char opt_ttyloc[]     FLASHMEM = "TTYLOC";
const char opt_3270regime[] FLASHMEM = "3270-REGIME";
// 30-39
const char opt_x3pad[]      FLASHMEM = "X.3-PAD";
const char opt_naws[]       FLASHMEM = "NAWS";
const char opt_termspeed[]  FLASHMEM = "TERMINAL-SPEED";
const char opt_remflowctl[] FLASHMEM = "TOGGLE-FLOW-CONTROL";
const char opt_linemode[]   FLASHMEM = "LINEMODE";
const char opt_xdisploc[]   FLASHMEM = "X-DISPLAY-LOCATION";
const char opt_environ[]    FLASHMEM = "ENVIRON";
const char opt_authen[]     FLASHMEM = "AUTHENTICATION";
const char opt_encrypt[]    FLASHMEM = "ENCRYPT";
const char opt_newenviron[] FLASHMEM = "NEW-ENVIRON";
// 40-49
const char opt_tn3270e[]    FLASHMEM = "TN3270E";
const char opt_xauth[]      FLASHMEM = "xauth";
const char opt_charset[]    FLASHMEM = "CHARSET";
const char opt_rsp[]        FLASHMEM = "rsp";
const char opt_commport[]   FLASHMEM = "COM-PORT-OPTION";
const char opt_suppecho[]   FLASHMEM = "suppress-echo";
const char opt_starttls[]   FLASHMEM = "start-tls";
const char opt_kermit[]     FLASHMEM = "KERMIT";
const char opt_sendurl[]    FLASHMEM = "send-url";
const char opt_forwardx[]   FLASHMEM = "forward-x";
// 255
const char opt_exopl[]      FLASHMEM = "EXTENDED-OPTIONS-LIST";

// Create an array of option codes and pointers to Flash strings, in Flash.
typedef struct
{
  const byte code;
  const char *name;
}
TelnetOptStruct;

TelnetOptStruct telnetOpt[] FLASHMEM =
{
  {
    OPT_TRANSBIN,   opt_transbin   }
  ,
  {
    OPT_ECHO,       opt_echo   }
  ,
  {
    OPT_RECONNECT,  opt_reconnect   }
  ,
  {
    OPT_SUPGA,      opt_supga   }
  ,
  {
    OPT_AMSN,       opt_amsn   }
  ,
  {
    OPT_STATUS,     opt_status   }
  ,
  {
    OPT_TIMINGMARK, opt_timingmark   }
  ,
  {
    OPT_RCTE,       opt_rcte   }
  ,
  {
    OPT_OUTLINEWID, opt_outlinewid   }
  ,
  {
    OPT_OUTPAGESIZ, opt_outpagesiz   }
  ,
  // 10-19
  {
    OPT_NAOCRD,     opt_naocrd   }
  ,
  {
    OPT_NAOHTS,     opt_naohts   }
  ,
  {
    OPT_NAOHTD,     opt_naohtd   }
  ,
  {
    OPT_NAOFFD,     opt_naoffd   }
  ,
  {
    OPT_NAOVTS,     opt_naovts   }
  ,
  {
    OPT_NAOVTD,     opt_naovtd   }
  ,
  {
    OPT_NAOLFD,     opt_naolfd   }
  ,
  {
    OPT_EXTENDASC,  opt_extendasc   }
  ,
  {
    OPT_LOGOUT,     opt_logout   }
  ,
  {
    OPT_BM,         opt_bm   }
  ,
  // 20-29
  {
    OPT_DET,        opt_det   }
  ,
  {
    OPT_SUPDUP,     opt_supdup   }
  ,
  {
    OPT_SUPDUPOUT,  opt_supdupout   }
  ,
  {
    OPT_SENDLOC,    opt_sendloc   }
  ,
  {
    OPT_TERMTYPE,   opt_termtype   }
  ,
  {
    OPT_EOR,        opt_eor   }
  ,
  {
    OPT_TUID,       opt_tuid   }
  ,
  {
    OPT_OUTMRK,     opt_outmrk   }
  ,
  {
    OPT_TTYLOC,     opt_ttyloc   }
  ,
  {
    OPT_3270REGIME, opt_3270regime   }
  ,
  // 30
  {
    OPT_X3PAD,      opt_x3pad   }
  ,
  {
    OPT_NAWS,       opt_naws   }
  ,
  {
    OPT_TERMSPEED,  opt_termspeed   }
  ,
  {
    OPT_REMFLOWCTL, opt_remflowctl   }
  ,
  {
    OPT_LINEMODE,   opt_linemode   }
  ,
  {
    OPT_XDISPLOC,   opt_xdisploc   }
  ,
  {
    OPT_ENVIRON,    opt_environ   }
  ,
  {
    OPT_AUTHEN,     opt_authen   }
  ,
  {
    OPT_ENCRYPT,    opt_encrypt   }
  ,
  {
    OPT_NEWENVIRON, opt_newenviron   }
  ,
  // 40-49
  {
    OPT_TN3270E,    opt_tn3270e   }
  ,
  {
    OPT_XAUTH,      opt_xauth   }
  ,
  {
    OPT_CHARSET,    opt_charset   }
  ,
  {
    OPT_RSP,        opt_rsp   }
  ,
  {
    OPT_COMMPORT,   opt_commport   }
  ,
  {
    OPT_SUPPECHO,   opt_suppecho   }
  ,
  {
    OPT_STARTTLS,   opt_starttls   }
  ,
  {
    OPT_KERMIT,     opt_kermit   }
  ,
  {
    OPT_SENDURL,    opt_sendurl   }
  ,
  {
    OPT_FORWARDX,   opt_forwardx   }
  ,
  // 255
  {
    OPT_EXOPL,      opt_exopl   }
};
#endif // #if defined(TELNET_DEBUG)

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

const char telnetID[]  FLASHMEM = TELNETID;
const char telnetAYT[] FLASHMEM = TELNETAYT;

void setup()
{
  Serial.begin(9600);
  while(!Serial);

  Serial.println();
  Serial.println(FLASHSTR(telnetID));

  showFreeRam();

  telnetInit();
}

#define INPUT_SIZE 40
void loop()
{
  char buffer[INPUT_SIZE];
  byte count;

  showFreeRam();

  if (offlineMode)
  {
    Serial.print(F("[Offline]Command or 'BYE': "));
  }
  else if (telnetConnected)
  {
    client.print(F("[Telnet]Command or 'BYE': "));
    Serial.print(F("[Telnet]Command or 'BYE': "));
  }
  count = telnetInput(client, buffer, INPUT_SIZE);
  if (count==255)
  {
    Serial.println(F("[Connection Lost]"));
  }
  else
  {
    Serial.print(count);
    Serial.println(F(" bytes received from client."));
  }
  if (strcmp_P(buffer, PSTR("BYE"))==0)
  {
    if (offlineMode)
    {
      Serial.println(F("[Online Mode]"));
      offlineMode = false;
    }
    if (telnetConnected==true) telnetDisconnect();
  }
}

unsigned int freeRam()
{
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

void showFreeRam()
{
  Serial.print(F("Free RAM: "));
  Serial.println(freeRam());
}

/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/
/*---------------------------------------------------------------------------*/

/*---------------------------------------------------------------------------*/
// FUNCTIONS
/*---------------------------------------------------------------------------*/

// Initialize the Ethernet Shield.
void telnetInit()
{
  // Temporary arrays so we don't waste RAM storing them.
  byte tempMac[6];
  byte tempIp[4];

  memcpy_P(tempMac, mac, 6);
  memcpy_P(tempIp, ip, 4);

  Ethernet.begin(tempMac, tempIp);

  Serial.print(F("Server address: "));
  Serial.println(Ethernet.localIP());

  telnetServer.begin();
#if defined(TELNET_MULTISERVER)
  goawayServer.begin();
#endif
}

// Block and wait for an incoming Ethernet TCP connection,
// or local keyboard connection.
// true = Telnet connection
// false = local connection
boolean telnetWaitForConnection()
{
  Serial.println(F("[Waiting on Connection]"));
  modeFlags = 0;
  while(1)
  {
    //ledBlink();

    // Check for local connection
    if (Serial.available())
    {
      // Go offline if logging on locally.
      Serial.println(F("[Offline Connection]"));
      Serial.println();
      Serial.println(FLASHSTR(telnetID));

      offlineMode = true;
      return false;
    }

    if (!offlineMode)
    {
      client = telnetServer.available();
      if (client)
      {
        //telnetConnected = true;
        Serial.println(F("[New Connection]"));

        client.println();
        client.println(FLASHSTR(telnetID));

        // Wait to see what the client has to say, if anything.
        delay(100); // half second pause
        while(telnetRead(client));

        /*
         // So... What is your terminal type?
         telnetSendSb(OPT_TERMTYPE, 1);
         delay(100); // half second pause
         while(telnetRead(client));
         */
        /*
        // We will control the echo, too.
         if (telnetMode(MODE_ECHO)==false)
         {
         telnetSendEscCmd(DO, OPT_ECHO);
         }
         delay(100);
         while(telnetRead(client));
         */
        return true;
      }
    }
  }
}

void telnetDisconnect()
{
  Serial.println(F("[Closing Connection]"));
  if (offlineMode)
  {
    offlineMode = false;
  }
  else
  {
    delay(1);
    client.stop();
    telnetConnected = false;
  }
}

// Read data from Telnet connection.
byte telnetRead(EthernetClient client)
{
  static byte  mode = MODE_LOOKING_FOR_CMD;
  byte         ch;
  boolean      done = false;

  ch = 0; // Return 0 if we don't find any data.
  done = false;
  // While not done and there is data available...
  while (!done && client.available())
  {
    // Read character from telnet connection.
    ch = client.read();

    // What are we doing, currently?
    switch(mode)
    {
      // Normal mode. Look for commands.
    case MODE_LOOKING_FOR_CMD:
      if (ch==IAC)
      {
#if defined(TELNET_DEBUG)
        telnetPrintCmd(IAC);
#endif
        mode = MODE_LOOKING_FOR_TYPE;
      }
      else
      {
        // Not a command, just return it.
        done = true;
        continue; // Go back to while.
      }
      break;

      // IAC,<type of operation>,<option>
      // Looking for command type.
    case MODE_LOOKING_FOR_TYPE:
      if (ch==IAC)
      {
        // Two in a row is escaped, per PFudd on RFC comments.
        // http://www.faqs.org/rfcs/rfc854.html
#if defined(TELNET_DEBUG)
        telnetPrintHex(ch);
#endif
        done = true;
        continue;
      }
      // Print type.
#if defined(TELNET_DEBUG)
      telnetPrintCmd(ch);
#endif
      switch(ch)
      {
      case SE: // Subnegotiation ends.
        mode = MODE_DONE;
        break;

      case SB: // Subnegotiation follows.
        mode = MODE_LOOKING_FOR_SB_OPT;
        break;

      case DO:
        mode = MODE_LOOKING_FOR_DO_OPT;
        break;

      case DONT:
        mode = MODE_LOOKING_FOR_DONT_OPT;
        break;

      case WILL:
        mode = MODE_LOOKING_FOR_WILL_OPT;
        break;

      case WONT:
        mode = MODE_LOOKING_FOR_WONT_OPT;
        break;

      case AYT:
        mode = MODE_DONE;
        client.println(FLASHSTR(telnetAYT));
        break;

        // Commands with no options.
      case EC:
        ch = BS;
        mode = MODE_LOOKING_FOR_CMD;
        break;

      case EOF:
      case SP:
      case ABORT:
      case EOR:
      case NOP:
      case DM:
      case BRK:
      case IP:
      case AO:
      case EL:
      case GA:
        mode = MODE_LOOKING_FOR_CMD;
        break;

        // Anything we don't understand, we'll assume has no option...?
      default:
        mode = MODE_LOOKING_FOR_CMD;
        break;
      } // end of switch(ch)
      break;

      // Looking for option.
    case MODE_LOOKING_FOR_OPT:
    case MODE_LOOKING_FOR_SB_OPT:
#if defined(TELNET_DEBUG)
      telnetPrintOpt(ch);
#endif
      if (mode==MODE_LOOKING_FOR_SB_OPT)
      {
        //test: mode = MODE_LOOKING_FOR_SE;
        mode = MODE_LOOKING_FOR_OPT_VAL;
      }
      else
      {
        //telnetHandleDo(ch);
        mode = MODE_DONE;
      }
      break;

    case MODE_LOOKING_FOR_DO_OPT:
#if defined(TELNET_DEBUG)
      telnetPrintOpt(ch);
#endif
      telnetHandleDo(ch);
      mode = MODE_DONE;
      break;

    case MODE_LOOKING_FOR_DONT_OPT:
#if defined(TELNET_DEBUG)
      telnetPrintOpt(ch);
#endif
      telnetHandleDont(ch);
      mode = MODE_DONE;
      break;

    case MODE_LOOKING_FOR_WILL_OPT:
#if defined(TELNET_DEBUG)
      telnetPrintOpt(ch);
#endif
      telnetHandleWill(ch);
      mode = MODE_DONE;
      break;

    case MODE_LOOKING_FOR_WONT_OPT:
#if defined(TELNET_DEBUG)
      telnetPrintOpt(ch);
#endif
      telnetHandleWont(ch);
      mode = MODE_DONE;
      break;

    case MODE_LOOKING_FOR_OPT_VAL:
#if defined(TELNET_DEBUG)
      telnetPrintHex(ch);
#endif
      mode = MODE_LOOKING_FOR_SE;
      break;

      // Subnegotiation stream in progress.
    case MODE_LOOKING_FOR_SE:
      if (ch==IAC)
      {
#if defined(TELNET_DEBUG)
        telnetPrintCmd(IAC);
#endif
        mode = MODE_LOOKING_FOR_TYPE;
      }
      else
      {
        //if (isprint(ch)) {
        //  Serial.print((char)ch);
        //} else {
#if defined(TELNET_DEBUG)
        telnetPrintHex(ch);
#endif
        //}
      }
      break;

      // Unknown mode.
    default:
      // Serial.println("*** Unknown mode... ***");
      break;
    }

    // If we are not in normal mode, nothing to return.
    if (mode!=MODE_LOOKING_FOR_CMD) ch = 0;

    // If done, toggle to normal mode.
    if (mode==MODE_DONE)
    {
      Serial.println();
      mode = MODE_LOOKING_FOR_CMD;
    }
  } // end of while(client.avaialable())
  /*
  if (ch<32 || ch>127) {
   //Serial.print("Returning: ");
   Serial.print("(");
   Serial.print(ch, DEC);
   Serial.print(")");
   }
   */
  return ch;
}

void telnetSendEsc()
{
  client.write(IAC);
#if defined(TELNET_DEBUG)
  Serial.print(F(">"));
  telnetPrintCmd(IAC);
#endif
}
void telnetSendEscCmd(byte cmd)
{
  telnetSendEsc();
  client.write(cmd);
#if defined(TELNET_DEBUG)
  telnetPrintCmd(cmd);
#endif
}
void telnetSendEscCmd(byte cmd, byte option)
{
  telnetSendEscCmd(cmd);
  client.write(option);
#if defined(TELNET_DEBUG)
  telnetPrintOpt(option);
#endif
}
void telnetSendSb(byte option, byte val)
{
  telnetSendEscCmd(SB, option);
  client.write(val);
  client.write(IAC);
  client.write(SE);
#if defined(TELNET_DEBUG)
  telnetPrintHex(val);
  telnetPrintCmd(IAC);
  telnetPrintCmd(SE);
#endif
}

/*---------------------------------------------------------------------------*/

// If the server asks us if we WILL use an option, if we will, we should
// respond and tell them we DO, or DONT.
boolean telnetHandleWill(byte opt)
{
  if (telnetHandleOptEnable(opt)==true)
  {
    telnetSendEscCmd(DO, opt);
    return true;
  }
  telnetSendEscCmd(DONT, opt);
  return false;
}

boolean telnetHandleDo(byte opt)
{
  if (telnetHandleOptEnable(opt)==true)
  {
    telnetSendEscCmd(WILL, opt);
    return true;
  }
  telnetSendEscCmd(WONT, opt);
  return false;
}

boolean telnetHandleDont(byte opt)
{
  if (telnetHandleOptDisable(opt)==true)
  {
    // Tell them we WONT use it.
    telnetSendEscCmd(WONT, opt);
    return true;
  }
  // If we can't not do the option, what should we do? Ignore for now.
  return false;
}

boolean telnetHandleWont(byte opt)
{
  if (telnetHandleOptDisable(opt)==true)
  {
    // Tell them we DONT use it.
    telnetSendEscCmd(DONT, opt);
    return true;
  }
  // If we can't not do the option, what should we do? Ignore for now.
  return false;
}

// Enable the option, if we can.
// true = we can, and we did.
// false = we cannot, and we did not.
boolean telnetHandleOptEnable(byte opt)
{
  switch(opt)
  {
  case OPT_ECHO:
    // Turn on echo mode bit.
    telnetModeEnable(MODE_ECHO);
    Serial.print(F("(echo mode enabled)"));
    break;

  case OPT_SUPGA:
    telnetModeEnable(MODE_SUPGA);
    Serial.print(F("(suppress go ahead enabled)"));
    break;

    /*
    case OPT_LINEMODE:
     telnetModeEnable(MODE_LINEMODE);
     Serial.print(F("(line mode enabled)"));
     break;
     */
    /*
    case OPT_TERMTYPE:
     // Just here so the client can send it to us.
     break;
     */
    // Else, we do not do this. Let them know.
  default:
    return false;
  }
  // If here, tell sender we will do as they requested.
  return true;
}

// Disable the option, if we can.
// true = we did
// false = we did not
boolean telnetHandleOptDisable(byte opt)
{
  boolean wasDisabled = false;
  switch(opt)
  {
  case OPT_ECHO:
    wasDisabled = telnetModeDisable(MODE_ECHO);
    if (wasDisabled) Serial.println(F("(echo mode disabled)"));
    break;

  case OPT_SUPGA:
    wasDisabled = telnetModeDisable(MODE_SUPGA);
    if (wasDisabled) Serial.println(F("(suppress go ahead disabled)"));
    break;
    /*
    case OPT_LINEMODE:
     wasDisabled = telnetModeDisable(MODE_LINEMODE);
     if (wasDisabled) Serial.println(F("(line mode disabled)"));
     break;
     */
    // Else, we have been told not to do something we don't know how to not
    // do... What do we do? "WONT" is the only valid response. So if we
    // don't know what it is, we probably won't be doing it. Right?
  default:
    // I guess this is fine. Should we respond?
    //telnetSendEscCmd(WONT, opt);
    return false; // Let called know we had a request we didn't handle.
  }
  // If here, tell the sended we wont do what they asked us not to do.
  //telnetSendEscCmd(WONT, opt);
  return wasDisabled;
}

boolean telnetModeEnable(byte mode)
{
  modeFlags = modeFlags | mode;
  return true;
}
boolean telnetModeDisable(byte mode)
{
  if (telnetMode(mode)==false) return false;
  modeFlags = modeFlags & ~mode;
  return true;
}

/*---------------------------------------------------------------------------*/

// LINE INPUT str
// Read string up to len bytes. This code comes from my Hayes AT Command
// parser, so the variables are named differently.
#define CR           13
#define BEL          7
#define BS           8
#define CAN          24
byte telnetInput(EthernetClient client, char *cmdLine, byte len)
{
  int     ch;
  byte    cmdLen = 0;
  boolean done;
  boolean echoMode;

  // We cannot read zero bytes, so we won't even try.
  if (len==0) return 0;

  if (!offlineMode)
  {
    // Do we need to let them know they can send us stuff?
    if (client.connected() && !telnetMode(MODE_SUPGA)) telnetSendEscCmd(GA);
  }

  done = false;
  while(!done)
  {
    //ledBlink();

    // We use this multiple places, so do it once.
    echoMode = telnetMode(MODE_ECHO);

#if defined(TELNET_MULTISERVER)
    // Check for secondary connection
    EthernetClient client2 = goawayServer.available();
    if (client2)
    {
      if (client2.connected())
      {
        Serial.println(F("[Secondary client connected.]"));
        client2.println();
        client2.println(FLASHSTR(telnetID));
        client2.println(F("The system is busy right now. Please try again later."));
        delay(1);
        client2.stop();
        Serial.println(F("[Secondary client disconnected.]"));
      }
    }
#endif

    if (!offlineMode)
    {
      if (telnetConnected==false)
      {
        telnetConnected = telnetWaitForConnection();
        // If serial (false), start inputting?
        //if (telnetConnected==false) continue;
        // Otherwise...
        // On fresh connection, simulate CR from client.
        cmdLine[0] = '\0';
        return 0;
      }
      else if (client.connected()==false)
      {
        Serial.println(F("\n[Connection Lost]"));
        telnetDisconnect();
        return 255;
      }
    }

    ch = -1; // -1 is no data available
    if (Serial.available()>0)
    {
      ch = Serial.read();
      if (cmdModeCheck(ch)==true) cmdMode();
      // Make sure we echo local typing to the remote client.
      echoMode = true;
    }
    else if (client.available()>0)
    {
      //ch = client.read();
      if (!offlineMode) ch = telnetRead(client);
    }
    else
    {
      if (cmdModeCheck(0)==true) cmdMode();
      continue; // No data. Go back to the while()...
    }
    switch(ch)
    {
    case -1: // No data available.
      break;

    case CR:
      //if (echoMode)
      if (!offlineMode && telnetConnected)
      {
        client.write((char)CR);
        client.write((char)LF);
      }
      Serial.println();
      cmdLine[cmdLen] = '\0';
      done = true;
      break;

    case CAN: // ^X
      //print(F("[CAN]"));
      while(cmdLen>0)
      {
        if (!offlineMode && telnetConnected && echoMode)
        {
          client.write((char)BS);
          client.print(F(" "));
          client.write((char)BS);
        }
        Serial.write((char)BS);
        Serial.print(F(" "));
        Serial.write((char)BS);
        cmdLen--;
      }
      cmdLen = 0;
      break;

    case BS:
    case DEL:
      if (cmdLen>0)
      {
        if (!offlineMode && telnetConnected && echoMode)
        {
          client.write((char)BS);
          client.print(F(" "));
          client.write((char)BS);
        }
        Serial.write((char)BS);
        Serial.print(F(" "));
        Serial.write((char)BS);
        cmdLen--;
      }
      break;

    default:
      // If there is room, store any printable characters in the cmdline.
      if (cmdLen<len-1)
      {
        if ((ch>=32) && (ch<=128)) // isprint(ch) does not work.
        {
          if (!offlineMode && telnetConnected && echoMode) client.write((char)ch);
          Serial.write((char)ch);
          cmdLine[cmdLen] = ch; //toupper(ch);
          cmdLen++;
        }
        // Ignore other nonprintable characters.
      }
      else
      {
        //if (echoMode) ???
        if (!offlineMode && telnetConnected) client.write((char)BEL); // Overflow. Ring 'dat bell.
        Serial.write((char)BEL);
      }
      break;
    } // end of switch(ch)
  } // end of while(!done)

  return cmdLen;
}

/*---------------------------------------------------------------------------*/
// Telnet Protocol debug stuff. These routines will print out text versions
// of the Telnet protocol messages sent and recieved.
//
#if defined(TELNET_DEBUG)
void telnetPrintCmd(byte type)
{
  Serial.print(F("["));
  if (type>=SE && type<=IAC)
  {
    Serial.print(FLASHPTR(telnetCmd[type-SE]));
  }
  else
  {
    Serial.print(type);
  }
  Serial.print(F("]"));
}

void telnetPrintOpt(byte opt)
{
  int i;
  boolean found;

  found = false;
  Serial.print(F("["));
  for (i=0; i<(sizeof(telnetOpt)/sizeof(*telnetOpt)); i++)
  {
    if (pgm_read_byte(&telnetOpt[i].code) == opt)
    {
      Serial.print(FLASHPTR(telnetOpt[i].name));
      found = true;
      break;
    }
  }
  if (!found) Serial.print(opt, HEX);
  Serial.print(F("]"));
}

void telnetPrintHex(byte val)
{
  Serial.print(F("["));
  Serial.print(val, HEX);
  Serial.print(F("]"));
}
#endif // #if defined(TELNET_DEBUG)
/*---------------------------------------------------------------------------*/
// End of TelnetServer

Cleanup in aisle four…

I have just spent far too much time going back and editing all the posts to this site which include source code. WordPress has done a wonderful job mangling them, but I think I finally have a solution in place, moving forward, that should prevent it. The ALLRAM BBS conversion project has also been updated to the latest version.

Also, you will notice the theme has been changed. I was having some issues with the other terminal-looking theme, and it didn’t support all of the things I liked having (like the horizontal menu bar at the top of each page). When I find time, I will learn how to customize WordPress and try to make this theme look more retro and less generic.

Greetings from the Sub-Etha!

Don’t Panic!

You have been diverted.

Welcome to the new home of…

---------|---------|---------|---------|---------|---------|---------|---------|
       _______              __             _______             __
      / _____/\            / /\           / _____/\           / /\
     / /\____\/           / / /          / /\____\/  __      / / /     ______
    / /_/__    __  __    / /_/_   ___   / /_/     __/ /_    / /_/_    /___  /\
   /____  /\  / /\/ /\  / ___ /\ /__/\ / ___/\   /_  __/\  / __  /\  _\__/ / /
   \___/ / / / / / / / / /\/ / / \__\// /\__\/   \/ /\_\/ / /\/ / / / __  / /
 _____/ / / / /_/ / / / /_/ / /      / /_/__     / /_/   / / / / / / /_/ / /
/______/ / /_____/ / /_____/ /      /______/\   /___/\  /_/ /_/ / /_____/ /
\______\/  \_____\/  \_____\/       \______\/   \___\/  \_\/\_\/  \_____\/
 =======    ======    ======   ===   =======   ======    ======    ======
                                S O F T W A R E
                               Established 1990.

      "In Support of the CoCo and OS-9" ... and Perl, and Arduino, and...

---------|---------|---------|---------|---------|---------|---------|---------|

In 1990, Sub-Etha Software was formed in Lufkin, Texas. You can read all about it, if you are curious. Since then, much has changed. It’s hard to believe that was almost 25 years ago!

Although Sub-Etha hasn’t done much for years, for the past few months, I have been posting on my Apple site, www.appleause.com, about my exploits with the Arduino platform. I haven’t done this much recreational programming since my days using the Radio Shack Color Computer. This has made me decide to dust off my old software company, Sub-Etha Software, and set up this website. Although I have had the domain for many years, it was just a subsection of one of my personal sites… until tonight.

I have now migrated all my programming-related posts from the Appleause site over to this one. I think they will fit in better here, and it finally gave me an excuse to do something with this domain.

So here it is – the all-new Sub-Etha Software website. Here you will find historical information about the original Sub-Etha, which started back in 1990 to offer products for the Radio Shack Color Computer and Microware OS-9 operating system. There is even a page which will eventually document the items we made for the Interactive Media Systems MM/1 computer, which ran OS-9/68000 (“OSK”). I may even make a page covering some of the items we sold after I took a job with Microware and had to shut down Sub-Etha. (Hint: Sub-Etha SoftWEAR didn’t sell software…)

Around 2001, I learned a bit of Perl programing and created a few simple scripts which I made available free under the Sub-Etha Software name, so Perl gets a page here, too. And lastly, some of my recent development efforts on the Arduino and related devices like the Teensy 2.0. I may even through in some BASIC Stamp talk at some point if I ever dig out my old hardware.

But since Sub-Etha started with the CoCo, it remains with the CoCo. There will be discussions about new CoCo related projects, and a listing of some of CoCo websites I think all CoCoists should check out.

Stay tuned…

Part 3: Debouncing digital inputs.

It seems I completely forgot the story I started telling awhile ago. If you did, too, you can go back and read about how I got started with Arduino, along with a few more notes, and finally, the one where I started dissecting my first sketch which read inputs from a haunted house pressure mat and turned them in to serial signals some software could read.

Back in February, I was discussing a problem I noticed with my first Arduino project. When switches would connect, and generate the serial strings for on/off like I wanted, I would often see the data stutter, toggling back and forth quickly before resting at on or off. I recognized this problem from learning about how the TRS-80 Color Computer’s keyboard matrix worked, and I knew the solution would be to add some debounce code to my sketch.

There actually is a Debounce library available to the Arduino, but I chose to write my own since it was a good opportunity to learn. Plus, the Bounce library seemed to provide much more capability than I needed. You can find this library here:

http://playground.arduino.cc//Code/Bounce

It seems very easy to use, but instead, I chose to apply some of the information I read on another section of the Arduino site. My debounce code would look like this:

/* For I/O pin status and debounce. */
unsigned int pinStatus[DI_PIN_COUNT];    // Last set PIN mode.
unsigned long pinDebounce[DI_PIN_COUNT]; // Debounce time.
unsigned int debounceRate = DEBOUNCE_MS; // Debounce rate.

I would create arrays to hold the last known pin status, and a time value for that pin. The time value would be used to mark debounce time. The debounce rate variable would be used for how long the debounce should be.

First, notice I was using “unsigned int” for the pin status. On the Arduino, and int is a 16-bit value, and takes up 2-bytes of RAM. The pin count could easily be represented with byte variable instead, saving a byte for each instance. It’s not a big deal wasting a few bytes for a small sketch, but for big projects, every last byte can matter. SO, don’t code like that. Use a byte. Or if you are really in a crunch, remember that a byte is made up of eight bits, so one byte could actually be used to track the status of eight different pins. So, my original version would take 16 bytes of RAM to track eight pins, but I could have done that using only one byte. That’s quite a savings. I can discuss using bits in a future article.

Okay, so those were my variables. The actual code that did all the work looks like this. First, I defined the pins I would be using for the project:

/* TX/RX pins are needed to USB/serial. */
#define DI_PIN_START  2
#define DI_PIN_END    12
#define DI_PIN_COUNT  (DI_PIN_END-DI_PIN_START+1)

The different Arduinos and Teensys and such have different I/O pin numbers. I decided I would just use a range of pins, so I specify the starting pin and ending pin. In this example, I skipped pin 0 and 1 since those are used by the serail port. Pin 13 is connected to an LED I wanted to blink (letting me know the board was alive and processing), so I could use pins 2-12 and get eleven pins to wire up to the pressure mats in the haunted house.

Inside setup(), I did a loop through these pins to put them in INPUT mode (using the pull-up resistor so I didn’t need extra wiring), and to initialize my arrays. You will notice I have something commented out. Originally, I read the current state of all the pins so I could mark them as changing later. So, if a pressure mat was “on” when it started, it would do nothing until the mat was released. This sounded like a good idea, but it meant there would be no way for the software to know things needed to go “now” on power up, so instead I set the status to HIGH (meaning off), thus if a mat were depressed when the program started, it would immediately see that as a change to LOW and send out the serial data.

  // Initialize the pins and pinStatus array.
  for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ )
  {
    // Set pin to be digital input using pullup resistor.
    pinMode(thisPin+DI_PIN_START, INPUT_PULLUP);
    // Read and save the current pin status.
    pinStatus&#91;thisPin&#93; = HIGH; //digitalRead(thisPin+DI_PIN_START);
    // Clear debounce time.
    pinDebounce&#91;thisPin&#93; = 0;
  }&#91;/code&#93;


Inside the main loop(), I would once again loop through all the pins and check their status:

&#91;code lang="cpp"&#93;  // Loop through each Digital Input pin.
  for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ )
  {
    // Read the pin's current status.
    int status = digitalRead(thisPin+DI_PIN_START);&#91;/code&#93;


Next, I would compare the current status to the one stored in the array and see if it had changed. No need to do anything for a pin that reads HIGH if it previously read HIGH. We only care about changes in status, so someone standing on a pressure mat wouldn't trigger it dozens of times before they stepped off.

&#91;code lang="cpp"&#93;    // In pin status has changed from our last toggle...
    if (status != pinStatus&#91;thisPin&#93;)
    {&#91;/code&#93;


And now it gets fun. The pinDebounce&#91;&#93; array is used to hold a time value, or 0 if there is nothing going on with that pin. If it has a number in it, that means we are in the process of counting down a debounce value. If it is 0, we need to start a debounce counter. The way we do that is by looking at the current time using millis(), and adding the debounce time to it, and storing that. So if millis() is 1200 when a pressure mat is triggered, and we are using a debounce rate of 1 second (1000 millis), we would store 1200+1000=2200 in that variable:

&#91;code lang="cpp"&#93;      // Remember when it changed, starting debounce mode.
      // If not currently in debounce mode,
      if (pinDebounce&#91;thisPin&#93;==0)
      {
        // Set when we can accept this as valid (debounce is considered
        // done if the time gets to this point with the status still the same).
        pinDebounce&#91;thisPin&#93; = millis()+debounceRate;
      }&#91;/code&#93;


Next, we check the pin to see if it's non-zero. If it is, it indicates this pin is in debounce mode, and we need to see if the current time in millis() is greater than the pin's pinDebounce&#91;&#93; time that was set earlier. If it is, we have seen the status hold long enough to believe it is a real trigger, and emit either "on" or "off" messages depending on which way the pin just toggled:

&#91;code lang="cpp"&#93;      // Check to see if we are in debounce detect mode.
      if (pinDebounce&#91;thisPin&#93;>0)
      {
        // Yes we are. Have we delayed long enough yet?
        if ( (long)(millis()-pinDebounce[thisPin]) >= 0 )
        {
            // Yes, so consider it switched.
            // If pin is Active LOW,
            if (status==LOW)
            {
              // Emit UPPERCASE "On" character.
              Serial.println(char(65+thisPin));
            } else {
              // Emit lowercase "Off" character.
              Serial.println(char(97+thisPin));
              if (pinsOn>0) pinsOn--;
              if (pinsOn==0) digitalWrite(LED_PIN, LOW);
            }

Then we remember this new status, and reset the debounce counter for that pin back to zero.

            // Remember current (last set) status for this pin.
            pinStatus[thisPin] = status;
            // Reset debounce time (disable, not looking any more).
            pinDebounce[thisPin] = 0;
        } // End of if ( (long)(millis()-pinDebounce[thisPin]) >= 0 )

      } // End of if (pinDebounce[thisPin]>0)
    }
    else // No change? Flag no change.
    {
      // If we were debouncing, we are no longer debouncing.
      pinDebounce[thisPin] = 0;
    }
  } // End of for()

At the very end, we have a condition that is met only if the pin’s status has not changed since the last time.

Does this look correct to you? If the pin hasn’t changed, I am resetting the debounce back to 0. But what if the pin were in a debounce mode? Wouldn’t we want to keep counting? It looked weird to me as I put the code here, but it is correct. If the pin has not changed, we are not interested in debouncing. If the pin is HIGH, it just resets to zero over and over (useless), but if it changes from HIGH to LOW, we enter the code and start the debounce. Note that the pinStatus[] variable has not been updated yet, so it remains HIGH.

The next loop through, the status of the pin is read again. If it is still LOW, we enter the code again because it is still different than the last pinStatus[], which is still HIGH. We do more debounce checks. And this continues.

The reason the reset to 0 is correct is because of this. We are not comparing the status to the last time we read, but to the last time we considered it switched after a debounce. As we continue the example, at some point, the pin has held its status long enough to be considered debounced, and we toggle it and update pinStatus[] to now be LOW, and the debounce counter is reset.

Clear as mud? Because we have not really change the status, we keep setting it to 0. But as long as the current pin status is different than the previously saved pinStatus[], we will keep entering that loop and checking, never getting to the reset 0. In fact, the only time we get to the reset 0 if current pinStatus is the same as the last pinStatus[]. So, a switch not switching will just reset 0 all the time, but once the switch has changed, it will fall in to the code to check debounce and never touch the reset 0 code. This is what resets the debounce timer if the switch goes from ON to OFF (which should trigger, but not yet), then pops back to ON quickly… resetting the counter… Then if it pops to OFF again, it starts the counter fresh.

I hope that makes sense.

Here’s the full script. Remember, it was the first thing I ever wrote for the Arduino, and I have done more to it since then. You will also see I have a part that checks the serial input for “?” to be typed, then prints out the current pin status. A nice thing for debugging.

Hope this helps… The next version will add Analog inputs (for reading laser tag light sensors), and then this code morphs in to an Atari joystick converter.

Until then…

/*—————————————————————————–

Arduino Digital Input

Monitor digital inputs, then emit a serial character depending on the pin
status. The character will be uppercase for pin connected (N.O. button push)
and lowercase for pin disconnected (N.O. button released). It will begin with
“A” for the first I/O pin, “B” for the next, and so on. Currently, with pins
0 and 1 used for serial TX/RF, this leaves pins 2-12 available (10), with pin
13 reserved for blinking the onboard LED as a heartbeat “we are alive”
indicator.

This software was written to allow an Arduino act as a cheap input/trigger
interface to software such as VenueMagic. As I do not own a copy of this
software, I could only test it under the 15 day trial. There may be other
issues…

2012-10-09 0.0 allenh – Initial version.
2012-10-11 0.1 allenh – Updated debounce to work with timing rollover, via:
http://www.arduino.cc/playground/Code/TimingRollover
Fixed bug where last DI pin was not being used.

—————————————————————————–*/
//#include
#include

/* TX/RX pins are needed to USB/serial. */
#define DI_PIN_START 2
#define DI_PIN_END 12
#define DI_PIN_COUNT (DI_PIN_END-DI_PIN_START+1)

#define LED_PIN 13
#define LEDBLINK_MS 1000
#define DEBOUNCE_MS 100 // 100ms (1/10th second)

/*—————————————————————————*/
/*
* Some sanity checks to make sure the #defines are reasonable.
*/
#if (DI_PIN_END >= LED_PIN)
#error PIN CONFLICT: PIN END goes past LED pin.
#endif

#if (DI_PIN_START < 2) #error PIN CONFLICT: PIN START covers 0-TX and 1-RX pins. #endif #if (DI_PIN_START > DI_PIN_END)
#error PIN CONFLICT: PIN START and END should be a range.
#endif
/*—————————————————————————*/

/* For I/O pin status and debounce. */
unsigned int pinStatus[DI_PIN_COUNT]; // Last set PIN mode.
unsigned long pinDebounce[DI_PIN_COUNT]; // Debounce time.
unsigned int debounceRate = DEBOUNCE_MS; // Debounce rate.
unsigned long pinCounter[DI_PIN_COUNT];

/* For the blinking LED (heartbeat). */
unsigned int ledStatus = LOW; // Last set LED mode.
unsigned long ledBlinkTime = 0; // LED blink time.
unsigned int ledBlinkRate = LEDBLINK_MS; // LED blink rate.

unsigned int pinsOn = 0;

/*—————————————————————————*/

void setup()
{
// Just in case it was left on…
wdt_disable();
// Initialize watchdog timer for 2 seconds.
wdt_enable(WDTO_4S);

// Initialize the pins and pinStatus array.
for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ ) { // Set pin to be digital input using pullup resistor. pinMode(thisPin+DI_PIN_START, INPUT_PULLUP); // Read and save the current pin status. pinStatus[thisPin] = HIGH; //digitalRead(thisPin+DI_PIN_START); // Clear debounce time. pinDebounce[thisPin] = 0; pinCounter[thisPin] = 0; } // Set pin 13 to output, since it has an LED we can use. pinMode(LED_PIN, OUTPUT); // Initialize the serial port. Serial.begin(9600); // Docs say this isn't necessary for Uno. while(!Serial) { ; } // Emit some startup stuff to the serial port. Serial.println("ArduinoDI by Allen C. Huffman (alsplace@pobox.com)"); Serial.print("Configured for: "); Serial.print(debounceRate); Serial.print("ms Debounce, "); Serial.print(DI_PIN_COUNT); Serial.print(" DI Pins ("); Serial.print(DI_PIN_START); Serial.print("-"); Serial.print(DI_PIN_END); Serial.println(")."); Serial.println("(Nathaniel is a jerk.)"); } /*---------------------------------------------------------------------------*/ void loop() { // Tell the watchdog timer we are still alive. wdt_reset(); // LED blinking heartbeat. Yes, we are alive. if ( (long)(millis()-ledBlinkTime) >= 0 )
{
// Toggle LED.
if (ledStatus==LOW) // If LED is LOW…
{
ledStatus = HIGH; // …make it HIGH.
} else {
ledStatus = LOW; // …else, make it LOW.
}
// Set LED pin status.
if (pinsOn==0) digitalWrite(LED_PIN, ledStatus);
// Reset “next time to toggle” time.
ledBlinkTime = millis()+ledBlinkRate;
}

// Check for serial data.
if (Serial.available() > 0) {
// If data ready, read a byte.
int incomingByte = Serial.read();
// Parse the byte we read.
switch(incomingByte)
{
case ‘?’:
showStatus();
break;
default:
break;
}
}

// Loop through each Digital Input pin.
for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ ) { // Read the pin's current status. int status = digitalRead(thisPin+DI_PIN_START); // In pin status has changed from our last toggle... if (status != pinStatus[thisPin]) { // Remember when it changed, starting debounce mode. // If not currently in debounce mode, if (pinDebounce[thisPin]==0) { // Set when we can accept this as valid (debounce is considered // done if the time gets to this point with the status still the same). pinDebounce[thisPin] = millis()+debounceRate; } // Check to see if we are in debounce detect mode. if (pinDebounce[thisPin]>0)
{
// Yes we are. Have we delayed long enough yet?
if ( (long)(millis()-pinDebounce[thisPin]) >= 0 )
{
// Yes, so consider it switched.
// If pin is Active LOW,
if (status==LOW)
{
// Emit UPPERCASE “On” character.
Serial.println(char(65+thisPin));
pinCounter[thisPin]++;
pinsOn++;
digitalWrite(LED_PIN, HIGH);
} else {
// Emit lowercase “Off” character.
Serial.println(char(97+thisPin));
if (pinsOn>0) pinsOn–;
if (pinsOn==0) digitalWrite(LED_PIN, LOW);
}
// Remember current (last set) status for this pin.
pinStatus[thisPin] = status;
// Reset debounce time (disable, not looking any more).
pinDebounce[thisPin] = 0;
} // End of if ( (long)(millis()-pinDebounce[thisPin]) >= 0 )

} // End of if (pinDebounce[thisPin]>0)
}
else // No change? Flag no change. if (status != pinStatus[thisPin])
{
// If we were debouncing, we are no longer debouncing.
pinDebounce[thisPin] = 0;
}
} // End of for()
}

void showStatus()
{
int status = 0;

Serial.print(“DI: “);

for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ ) { // Read the pin's current status. int status = digitalRead(thisPin+DI_PIN_START); Serial.print(thisPin+DI_PIN_START); Serial.print("="); Serial.print(digitalRead(thisPin+DI_PIN_START)); Serial.print(" "); } Serial.println(""); for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ ) { Serial.print(thisPin+DI_PIN_START); Serial.print(":"); Serial.print(pinCounter[thisPin]); Serial.print(" "); } Serial.println(""); //Serial.print("millis() = "); //Serial.println(millis()); } /*---------------------------------------------------------------------------*/ // End of file.[/code] P.S. – Don’t mind my rude comment to Nathaniel in the status display. He’s the effects designer out at the Sleepy Hollow Haunted Scream Park I was doing this project for, and I only kid him because he’s such a great guy. He’s really not a jerk. Honest.

CoCo appears on TWiT

Curtis Boyle, a long time TRS-80 Color Computer developer, donated some Radio Shack CoCo stuff to Leo Laporte’s TWiT TV studio museum. (If you don’t know what that is, don’t worry. I would have no idea myself if it weren’t for working at a place that sold satellite TV systems back around 2002, where I watched him on a show at the defunct cable channel Tech TV.) Anyway, Curtis wrote a demo program that drew the TWiT logo and flashed colors, and it was shown on the live stream between recording some of their shows.

Screen shots follow. Good job, Curtis!

TRS-80 Color Computer items on Leo's desk.

TRS-80 Color Computer items on Leo’s desk.

Curtis Boyle's demo program, shown on TWiT between shows

Curtis Boyle’s demo program, shown on TWiT between shows

4/19/2013 Update: Curtis shared his source code on the CoCo mailing list today.

10 PALETTE 8,63:PALETTE 0,0:WIDTH40:CLS1
20 ON BRK GOTO 410
30 'TWIT logo CC3 program by L. Curtis Boyle
40 POKE65497,0:INPUT "<R>GB OR <C>OMPOSITE MONITOR (TV):";MN$
50 PALETTE 0,0:PALETTE 3,63
60 IF MN$<>"R" AND MN$<>"r" THEN 80
70 PALETTE 1,36:PALETTE 2,25:BC=0:GOTO 90
75 'Depending on your TV/Composite monitor settings, you may need to adjust these
80 PALETTE 1,8:PALETTE 2,31:BC=0
90 PALETTE 4,BC:HSCREEN2:HCLS4:POKE 65434,BC
100 HCIRCLE(70,96),70,2
110 HPAINT(70,96),2,2
130 HCIRCLE(70,96),56,0,1,.5,1
140 HCIRCLE(70,96),44,0,1,.51,.99
150 HCOLOR0:HLINE(15,92)-(125,104),PSET,BF
160 HPAINT(18,87),0,0
170 HLINE(50,70)-(62,134),PSET,BF
180 HLINE(78,96)-(90,134),PSET,BF
190 HCIRCLE(84,76),7,1:HPAINT(84,76),1,1
200 HCOLOR3:HPRINT(18,19),"Netcasts you love from"
210 HPRINT(21,20),"people you trust"
215 'Start of "TWiT"
220 HCOLOR3:HLINE(149,65)-(191,75),PSET,BF
230 HLINE(165,75)-(175,117),PSET,BF
240 HLINE(191,75)-(200,117),PSET:HLINE-(210,117),PSET
250 HLINE-(217,91),PSET:HLINE-(224,117),PSET:HLINE-(234,117),PSET
260 HLINE-(246,65),PSET:HLINE-(236,65),PSET:HLINE-(229,91),PSET:HLINE-(222,65),PSET
270 HLINE-(212,65),PSET:HLINE-(205,91),PSET:HLINE-(198,65),PSET:HLINE-(191,65),PSET
280 HPAINT(194,66),3,3
290 HLINE(254,75)-(264,117),PSET,BF:HCIRCLE(259,65),7,1:HPAINT(259,65),1,1
300 HLINE(272,65)-(314,75),PSET,BF:HLINE(288,75)-(298,117),PSET,BF
310 FORT=1 TO 4000:NEXT T
320 ST=1:EN=15
330 FOR TC=ST TO EN
340 IF INKEY$="" THEN 380
350 IF ST<>1 THEN 370
360 ST=BC:EN=BC:GOTO 330
370 ST=1:EN=15:GOTO330
380 PALETTE4,TC:POKE65434,TC:FORT=1TO100:NEXTT
390 NEXTTC:GOTO330
400 GOTO 400
410 POKE65496,0:END

A cheap way to get an Arduino on WiFi

Update 3/8/2014: There is an Arduino WiFi shield for around $40 now sold by Adafruit Industries (but you have to solder on the connectors). That shield costs about as much as the TP-Link router I mention in this article, and a $20 Ethernet shield. However, with clone Ethernet shields now around $9 from China, it’s still cheaper to hack something together. (Another advantage of the TP-Link device is you can have it set up to connect and publish the connection via Ethernet, and plug it in to anything that needs it — an Arduino, an OUYA, TiVo, etc.)

To test my Arduino Ethernet code, I needed a way to get my Arduino on my home network. Unfortunately, my router is somewhere else, and I was not able to run a long ethernet cable to it. I initially experimented with making my old MacBook act as a gateway using Apple’s Internet Sharing. I was going to have it share my home WiFi internet out the ethernet port, and hook that port up to the Arduino. Unfortunately, again, it seems there is some problem with the current version of Mac OS X and Internet Sharing and I was unable to get it to work. So, I turned to a free bit of software called Ice Floor which is a GUI front end for the Unix firewall running on Mac OS X. With a bit of Googling I was able to configure Ice Floor to let my Arduino hook to the MacBook, then reach out to the Internet.

I won’t be writing about that. It was easy, once I figured it out, but I spent all night trying.

What I really wanted was a way to get my Arduino on my home WiFi.

With Arduino ethernet interfaces being real cheap if your order from China, or slightly less cheap if your order from the USA, it puzzled me that WiFi shields were so much more. Well, if you don’t need everything to fit inside a small Arduino case, you can just get one of the cheap Ethernet shields and then use one of these things:

http://www.amazon.com/TP-LINK-TL-WR702N-Wireless-Repeater-150Mpbs/dp/B007PTCFFW/

This $23 TP-LINK WR702N WiFi router is really teeny tiny (about 2″x2″ and .5″ tall), and comes with a short ethernet cable, USB cable, and USB power supply. It has several modes of operation, including the one you would expect — plugging up to an Ethernet jack and broadcasting it as a WiFi signal. But, it also has a Client mode, so you can plug it up via Ethernet to the Arduino and then use it as a WiFi card.

Configuration was a bit tricky because I didn’t know what I was doing, but basically, you plug it up to power (USB or power adapter), and configure it via your computer and an Ethernet cable. If your computer is already on a network that is 192.186.0.x (like mine was, from my home DSL router), you will need to disable that from your computer (turn off WiFi, or unplug the Ethernet cable). The instructions (on the website) tell you to change your computer’s IP address to 192.168.0.10, and then in your web browser you go to 192.168.0.254 (which is the router’s default IP address). Up loads an admin web page.

Type in the password (admin/admin), then click the easy setup button and select Client. It will then give you a screen where you can browse to the WiFi network you wish to join, and enter the password (if it’s a protected network) and encryption method used (again, if it’s a protected network).

Once you do that, the little box will reboot and then try to connect to that WiFi hotspot, and then get an IP address from it and link the Ethernet port to the WiFi… So, configure it, then unplug it from the computer and plug it to the Arduino and… your Arduino’s Ethernet code now talks out WiFi.

So, if you ever try to telnet in to my home Arduino, that is how the connection will be getting there.

Just passing information along. Hope it helps someone.