Monday, December 14, 2015

The Chronograph - Part 5

When developing the Ballistic Chronograph, I decided that I needed a way to have, effectively, a command-prompt. I also wanted to have a way to display text in an auto-scrolling view. It would be used similar to Object Pascal' Writeln, C's printf or C#'s Console.WriteLine. The Arduino libraries already have an abstract class for writing text, Print. If you've done any Arduino programming, you've already used this class indirectly via the HardwareSerial class.

HardwareSerial derives from Stream which is then derived from Print. Since I don't intend to "stream" to the display and only intend to "print", I descended from Print.

// T6963Console.h
// Copyright (c) 2013 - Allen Bauer - http://blog.thereadoracleatdelphi.com
// Under MIT License

#ifndef T6963Console_h
#define T6963Console_h

#include <inttypes.h>
#include <Arduino.h>
#include <T6963.h>
#include <Print.h>
#include <WString.h>

class LCDConsole : public Print
{
private:
 T6963 *_LCD;
 int _row;
 int _col;
 int _winLeft;
 int _winTop;
 int _winRight;
 int _winBottom;
 bool _caretVisible;
 void updateRowCol();
 void incrementRow();
 void incrementCol();
 void decrementCol();
 void decrementRow();
public:
 typedef char (*GetKey)(void);
 LCDConsole(T6963 *lcd);
 virtual size_t write(uint8_t c);
 void clearScreen(void);
 void initialize(void);
 void scrollView(void);
 bool setConsoleWindow(int winLeft, int winTop, int winRight, int winBottom);
 void restoreRowCol();
 void showCaret();
 void hideCaret();
 String readLine(GetKey getKey);

 int getConsoleLeft(void);
 int getConsoleTop(void);
 int getConsoleCols(void);
 int getConsoleRows(void);
};

#endif

With this class, I can setup a text region on the display into which I can simply use Console.print() or Console.println(). It will automatically scroll the region without affecting the rest of the display. You can enable a flashing caret, which can be used as a simple prompt. The application can call readLine() to allow the user to enter a line of text. Carriage return or Enter will exit the loop. I needed to only override the write() virtual method to get all the functionality and formatting that the Print class offers.

Since the intent was to make this class as independent as possible from whatever the input device is, readLine() takes a function pointer as a parameter. The called would implement this function in order to query whatever input system is available and return a character. Backspace (the value 0x08) is supported for simple editing. For my application, the getKey callback would query the TouchKeyboard (see previous post for information).

In the actual application, it's a little more complicated because I needed to monitor many different things. For instance, the region of the touch panel that covered the display was one region where I needed to look for button clicks, while the keyboard section was looking for key presses. There was also the need to check for state changes which happened due to interrupts. The screen timing is all interrupt driven, so you can think of the timing as a background thread.

This is where the Events and EventLoop classes come in. Since most small, dedicated, stand-alone micro-controllers don't have any kind of supervising operating system with the notion of a process, thread, file systems, etc... monitoring for input usually takes two forms; polling or interrupts. Even in the latter case, interrupts, some polling is still necessary. Interrupt handlers should be very short and do as little as possible. The code for this project does a little of both.

The Events class aggregates multiple "events" into a single class instance. Events are "registered" with the class. They can be represented by a reference to a bool variable or a function pointer which returns a bool value. Each registered event is assigned a slot number which is returned to the caller in order to identify which event was triggered.

The EventLoop methods DoEvent() and DoEvents() takes two function pointers which reference the functions to process a keyboard event and a touch event. DoEvent() will process one cycle of events, if any are detected. DoEvents() will block the caller until an event handler calls ExitLoop().

Using these two classes, I was able to create an interactive and responsive UI on the small 240x64 display. As you can tell, the amount of programming just to handle the UI and input, far and away eclipsed the code necessary to measure the amount of time between 3 electrical pulses.

I'll likely take a detour from this series and focus on another project for a while. I will likely move on to conversion of the CNC machine to a 3D printer. I may even put up another poll and enlist you, the reader's, input as well.