Software Sketch
I needed something to control my dust collector automation system and this is what happened.
It draws very little current so it runs OK on a 3M long cat-5 cable.
As Neal Okerblocm commented when he saw the system, the terminal looks like the terminal we built at ti back in the 1970s.
The hardware is smple, it's great for controlling a large system or even bread boarding.
As an example, take a look at the Dust Automation Sketch.
I was thinking about the old VT100 monitor when I did this.
It scrolls like a VT100 (with only 4 rows of chars), and you can use the VT100 escape sequences to clear screen and postion the cursor.
The first row and collumn is zero.
Scrolling happens when printing on row 3 and print a 21st character, or issue a NL, the bottom 3 rows are scrolled up and printing starts on row 3.
When you position the cursor, the terminal does an Erase To End Of Line from the new position.
The Set Cursor Position escape sequence always has 1 character for row and 2 for column (ESC[r;ccf).
If you want to erase a line, position the cursor to the 1st column on the line.
Set Cursor Position | "Esc[r;ccf" (Move cursor to row/col)
|
Clear Screen | "Esc[2J"
|
/***********************************************************************
SCROLLING DATA TERMINAL (SPEAKS AT 9600b 8N1)
Nano, PCF8574 LCD DRIVER CHIP, 4002 LCD Display , PCF8574 I2C I/O EXPANDER
, 4x4 MCU BOAR MATRIX ARRAY SWITCH TACTILE KEYPAD
KEYPAD 16 KEY 4 BOTTOM KEYS (ROW 3) READ 8 BIITS FROM KEYPAD
1 2 3 A -R0 | **** 0000 #### DDDD | COLS IN LO NIBBLE
4 5 6 B -R1 | **** 0000 #### DDDD | COLS: 01 02 04 08
7 8 9 C -R2 | | ROWS: 10 20 40 80
* 0 # D -R3 |_ 1 2 3 4 5 6 7 8 9 10 _| ROWS IN HI NIBBLE
C0 C1 C2 C3 Conn | nc C0 C1 C2 C3 R0 R1 R2 R3 nc |
Nano I2C: Scl: A5 (24), Sda: A4 (23)
WHEN PRINTING TO DATA TERM USE: Serial.print,NOT println, EXTRA CR IS PROBLEM
***********************************************************************/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
/************************** DEFINES ************************************/
#define EXP 0X20 // EXPANDER ADDR
#define LCD 0X27 // LCD ADDR
#define CLRDLY 200 // CLEAR SCREEN DELAY 200MS
#define CURDLY 10 // CURSOR POSITION DELAY 10MS
#define COLS_HI 0X0F // COLS HI, ROWS LO
#define ROWS_HI 0XF0 // ROWS HI, COLS LO
#define IRQ 2 // IRQ 0 on DIGITAL PIN 2
#define DBNC_DLY 200 // DEBOUNCE DELAY MS
#define ESC '\\' // BACKSLASH 27
#define NL 10 // NEWLINE
#define SPC ' '
#define SPLAT '*'
#define BS 8
#define ROWMAX 4
#define COLMAX 20
#define INSTRMAX 100
#define ESCMAX 32
#define BLTMOT 1200000 // BACK LITE TIME OUT: 20 MINS (MS) (20 x 60 x 1000)
#define NORM 0
#define ESCSEQ 1
// #define DEBUG 1 // TURN ON WHEN DEBUGGGING FROM IDE
/************************** VARIABLES ************************************/
LiquidCrystal_I2C Lcd( LCD, 20, 4 ); //INSTANTIATE LCD, SET ADDRS, COLS, ROWS
char RCChar[9][9] = { // CONVERT ROW/COL BITS (1,2,4,8) TO ASCII CHR
0,0,0,0,0,0,0,0,0,
0,'1','2',0,'3',0,0,0,'A',
0,'4','5',0,'6',0,0,0,'B',
0,0,0,0,0,0,0,0,0,
0,'7','8',0,'9',0,0,0,'C',
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,
0,'*','0',0,'#',0,0,0,'D'};
bool KeyAvail = false; // SET BY IRQ, READ BY LOOP
bool LcdBkOn = true; // LCD BACKLIGHT ON
bool Rs232Avail= false; // SET BY serialEvent CLEARED BY LOOP
int32_t KeyTim =0, LstActn =0; // TIME OF LAST KEY OPER, OR LAST BACKLIGHT
short KbRow, KbCol; // KBRD ROW, COL
byte Mode = NORM, InNdx =0, EscNdx =0;
char LcdDat[4][21]; // TERMINAL DATA STORAGE ROW,COL
int LcdCol =0, LcdRow = 0; // TERMINAL ARRY INDEXES
char InStr[INSTRMAX], EscSeq[ESCMAX], *Err; // ASCII INPUT BUFS
/************************** PROTOTYPES ************************************/
void applyEscSeq(); // APPLY ESC SEQ
byte blOff(); // LCD BACKLIGHT OFF
byte chkBakLt(); // CHECK LCD BACKLIGHT
void chkSeq( char chr ); // CHECK ESC SEQUENCE
void escRst();
void expWt( char dat ); // WRITE TO I/O EXPANDER
byte expRd(); // READ FROM I/O EXPANDER
void isr0(); // KEYPAD INTERRUPT SERVICE ROUTING
char rdKbd(); // READ KEYPAD
void lcdScroll(); // SCROLL LCD
void lcdPnt( char inChr ); // PRINT TO LCD
void serialEvent(); // RUNS EACH TIME (AFTER) LOOP RUNS
/*F******************************************************************
*
**********************************************************************/
void
setup()
{
Wire.begin(); // START I2C
Serial.begin( 9600 );
pinMode( IRQ, INPUT_PULLUP); // INIT KEYPAD INT PIN AS IRQ INPUT
attachInterrupt( digitalPinToInterrupt( IRQ ), isr0, FALLING); // KP IRQ
memset( InStr, 0, sizeof( InStr )); // CLEAR ASCII BUF
memset( LcdDat, ' ', sizeof( LcdDat )); // FILL LCD MEM W SPC
KeyTim = millis() + DBNC_DLY;
expWt( COLS_HI ); // WRITE COLS HI, ROWS LO TO EXPANDER
Lcd.init(); // INITIALIZE LCD, CLEARS SCREEN
Lcd.backlight(); // OR COULD Lcd.setBacklight( HIGH ); // BACKLIGHT ON
Lcd.blink(); // MAKE CURSOR Blink
LcdRow = LcdCol =0;
Lcd.setCursor( LcdCol, LcdRow); // GO HOME COL 0, ROW 0
LstActn = millis(); // CAPTURE TIME
}
/*F******************************************************************
*
**********************************************************************/
void
loop()
{
char key, ndx, chr;
if( Err )
{ // CLEAN UP & DISPLAY ERROR MSG
Mode = NORM;
Lcd.clear();
lcdPnt( Err );
Err = NULL;
}
if( KeyAvail == true ) // ============= KBD =================
{ // RECEIVED CHAR FROM KEYPAD
if( (key = rdKbd()) != 0 ) // READ KEYPAD
Serial.print( key ); // TX (SEND) KEY TO HOST
KeyAvail = false;
if( chkBakLt() == 1 )
return; // BACK LIGHT WAS OFF, DISCARD CHAR
} // END OF KEY AVAIL
if( digitalRead( IRQ ) == LOW )
expRd(); // INCASE BOUNCE LEFT IRQ LO
if( Rs232Avail == true ) // ============= RS-232 =================
{ // RECEIVED CHAR FROM RS-232 link
chkBakLt();
for( ndx =0; (ndx < INSTRMAX); ndx++ )
{ // PRINT Instr, ONE CHAR AT A TIME
chr = InStr[ndx];
InStr[ndx] = 0; // REPLACE CHR WITH NULL
if( (chr < 32) && (chr != NL) && (chr != BS))
continue;
lcdPnt( chr ); // PRINT TO LCD
} // END OF FOR
InNdx = InStr[0] = 0; // CLEAR INSTR
Rs232Avail= false;
} // END OF Rs232
delay( 100 );
blOff();
}
/*F******************************************************************
* PRINT 1 CHAR TO LCD
**********************************************************************/
void
lcdPnt( char inChr ) // PRINT TO LCD
{
#ifdef DEBUG
Serial.print("lcdPnt: inChr ");
Serial.println( inChr );
#endif
if( Mode == ESCSEQ )
{ // ACCUMULATING AN ESCAPE SEQUENCE
if( EscNdx >= ESCMAX )
{ // ESCSEQ TOO LONG, MUST HAVE MISSED THE END
escRst();
return;
}
EscSeq[EscNdx++] = inChr; // ADD NEW CHAR TO ESCAPE SEQ BUF
if( (EscSeq[3] == 'j') || (EscSeq[5] == 'f') || (EscSeq[6] == 'f'))
applyEscSeq(); // RECOGNIZED ESCAPE SEQ
return;
}
if( (inChr == ESC) || (inChr == '\\'))
{ // NEW CHAR IS AN ESCAPE
Mode = ESCSEQ;
EscNdx =1;
memset( EscSeq, 0, sizeof( EscSeq ));
EscSeq[0] = ESC;
#ifdef DEBUG
Serial.println("lcdPnt: got esc seq ");
#endif
if( EscNdx > 6 )
Mode = NORM;
return;
}
if( (LcdRow >= ROWMAX) && (LcdCol >= COLMAX) )
lcdScroll(); // AT END OF DISPLAY, SCROLL UP
if( (inChr == NL) ) // NEWLINE
{
if( LcdCol >= (COLMAX -1) )
return; // ALREADY AT END OF LINE DISCARD NEWLINE
LcdRow++; // NEWLINE NOT AT END OF ROW
LcdCol = 0;
return;
}
if( inChr == BS )
{ // EXECUTE A BACK SPACE
if( --LcdCol < 0 )
{
LcdCol = 0;
LcdRow--;
}
Lcd.setCursor( LcdCol, LcdRow );
}
if( (inChr < 32) && (inChr != NL) && (inChr != BS))
inChr = SPLAT; // AN UNKNOWN CHAR, SPLAT IT
if( (LcdCol >= COLMAX) && (LcdRow < ROWMAX) )
{ // END OF ROW, NOT AT BOT OF DISPLAY GO TO NEXT LOWER ROW AND 1st COL
LcdRow++;
LcdCol = 0;
}
if( LcdRow >= ROWMAX )
lcdScroll();
Lcd.setCursor( LcdCol, LcdRow );
Lcd.print( inChr ); // PRINT TO LCD
LcdDat[LcdRow][LcdCol++] = inChr; // RECORD WHATS ON LCD ROW,COL
}
/*F******************************************************************
* SCROLL LCD SCREEN
**********************************************************************/
void // LCD ROW 0 AT TOP, COL 0 ON LEFT
lcdScroll() // SCROLL LCD
{
byte ndxR, ndxC;
char chr;
Lcd.clear(); // CLEAR SCREEN
delay( CLRDLY );
for( ndxR = 0; ndxR < ROWMAX; )
{ // SCAN COLS
for( ndxC = 0; ndxC < COLMAX; ndxC++ )
{ // COPY 1 ROW OF CHARS TO NEXT ROW UP
chr = LcdDat[ndxR+1][ndxC]; // GET CHR FROM NEXT ROW
if( (chr < 32) || (ndxR == 3) )
chr = SPC;
LcdDat[ndxR][ndxC] = chr; // PLACE IN SAME COL NEXT ROW UP
Lcd.print( chr ); // PRINT TO LCD
} // END OF COL NDX CPY
Lcd.setCursor( 0, ++ndxR ); // COL: 0, Row +1 DOWN 1 ROW
} // END OF ROW UP CPY
if( LcdRow >= ROWMAX )
LcdRow = 3;
LcdCol = 0;
Lcd.setCursor( LcdCol, LcdRow ); // SET CURSOR SCREEN BOTTOM LEFT
}
/*F******************************************************************
* APPLY ESCAPE SEQ
**********************************************************************/
void
applyEscSeq() // APPLY ESC SEQ
{
byte row, col, ndx;
if( EscSeq[3] == 'j') // ESC[2j
{ // CLEAR SCREEN
Lcd.clear(); // CLR SCRN
LcdCol = LcdRow =0;
escRst();
}
else
if((EscSeq[5] == 'f') || (EscSeq[6] == 'f')) // ESC[r;ccf
{ // POSITION CURSOR
row = (EscSeq[2] & 15);
col = ((EscSeq[4] & 15) *10) +(EscSeq[5] & 15);
if( (row > ROWMAX) || (col > COLMAX) )
{
Lcd.print( "ESC SEQ ERROR");
return;
}
LcdCol = col;
LcdRow = row;
Lcd.setCursor( LcdCol, LcdRow ); // COL:, ROW:
for( ndx = LcdCol; ndx < COLMAX; ndx++ )
{ // CLEAR TO END OF LINE
LcdDat[LcdRow][ndx] = SPC; // CLEAR SCROLLING BUFF
Lcd.print( SPC );
}
Lcd.setCursor( LcdCol, LcdRow ); // COL:, ROW:
escRst();
}
}
/*F******************************************************************
* WRITE TO I/O EXPANDER (PCF8574 KEYPAD)
**********************************************************************/
void
expWt( char dat )
{
Wire.beginTransmission( EXP );
Wire.write( dat );
Wire.endTransmission();
}
/*F******************************************************************
* READ FROM I/O EXPANDER (PCF8574 KEYPAD)
**********************************************************************/
byte
expRd()
{
byte dat = 0;
Wire.requestFrom( EXP, 1); // REQUEST 1 BYTE FROM EXP
if( Wire.available()) // IF BYTE AVAILABLE
dat = Wire.read(); // READ A BYTE
return( dat );
}
/*F******************************************************************
* Check IF LCD BACK LIGHT IS ON
**********************************************************************/
byte
chkBakLt()
{
byte rtn = 0;
if( !LcdBkOn )
{
Lcd.backlight(); // TURN LCD BACKLIGHT ON
LcdBkOn = true; // SET BACKLIGHT MODE ON
rtn = 1;
}
LstActn = millis(); // NOTE TIME
return( rtn );
}
/*F******************************************************************
* CHECK TIME & TURN LCD BACK LIGHT OFF
**********************************************************************/
byte
blOff()
{
int32_t now;
now = millis(); // GRAB CURRENT TIME MS
if( (now - LstActn) > BLTMOT )
{
Lcd.noBacklight(); // TURN OFF LCD BACKLIGHT
LstActn = millis(); // NOTE TIME
LcdBkOn = false;
}
}
/*F******************************************************************
* END ESC SEQ, CLEAR ESC SEQ[], RESET MODE
*********************************************************************/
void
escRst()
{
EscNdx = 0;
memset( EscSeq, 0, sizeof( EscSeq ));
Lcd.setCursor( LcdCol, LcdRow );
Mode = NORM;
}
/*F******************************************************************
KEYPAD: 4X4 MATRIX R8 R4 R2 R1 C8 C4 C2 C1, COL BITS IN LOW NIBBLE WEAK PULLUP
WHEN COLS HI, ROWS LO (0X0F), KEY PRESS PULLS DOWN A COL AND INTERRUPTS
READ COL BITS THEN SET ALL 4 COLS HI & ROWS LO, READ FROM ROW
READ VALUE, ISOLATE NIBBLE, INVERT DATA, CONVERT BIN BIT POSITION TO VALUE
**********************************************************************/
char
rdKbd()
{
int cNdx, rNdx, cnt;
byte rdCol, rdRow, tmp;
char kbChr;
// DFLT: COLS_HI (OXOF)
tmp = ~expRd() & 15; // 1'S COMPLEMENT OF COL IN LO NIBBLE DAT & MSK
delayMicroseconds( 2000 ); // WAIT FOR SWITCHING NOISE TO SUBSIDE
rdCol = ~expRd() & 15; // 1'S COMPLEMENT OF COL IN LO NIBBLE DAT & MSK
if( !tmp || (tmp != rdCol) )
return( 0 ); // DATA NOT Stable FOR 2MS
expWt( ROWS_HI ); // WRITE ROWS HI, COLS LO
delayMicroseconds( 10 ); // WAIT 10US FOR EXP TO WORK
tmp = ~expRd(); // 1'S COMPLEMENT OF ROWS (HI NIBBLE)
rdRow = (tmp >> 4) & 15; // MOV HI NIBBLE TO LOW NIBBLE AND MASK
kbChr = RCChar[rdRow][rdCol]; // CONVERT ROW/COL BITS TO ASCII CHAR
expWt( COLS_HI ); // BACK TO DEFAULT STATE, WAITING FOR BUTTON PUSH
delay( 100 );
if( kbChr != 0)
{ // GOOD READ
KeyAvail = false; // TURN OFF KB DATA AVAIL FLAG
KeyTim = millis() + DBNC_DLY; // RECORD LAST KEY PUSH TIME
}
return( kbChr );
}
/*F******************************************************************
* KeyPad INTERRUPT SERVICE ROUTINE
**********************************************************************/
void
isr0()
{
if( millis() > KeyTim ) // THROW AWAY IRQs UNTIL DBNC_DLY
KeyAvail = true; // NEW INTERRRUPT OCCURRED
}
/*F******************************************************************
* RS-232 INTERRUPT SERVICE ROUTINE
**********************************************************************/
void
serialEvent()
{ // RUNS EACH TIME (AFTER) LOOP RUNS
char chr;
while( Serial.available()) // INTERRUPT SERVICE ROUTIND, DON'T TARRY HERE
{
chr = (char)Serial.read(); // GET NEW CHAR
if( (chr != NL) && (chr != BS) && (chr < 32) )
continue; // DISCARD NON PRINtable OR PRINT CONTROL CHAR
if( InNdx >= INSTRMAX )
{
Err = "InStr Oflo";
InNdx = 0;
memset( InStr, SPC, sizeof( InStr));
return;
}
InStr[InNdx++] = chr;
Rs232Avail= true; // SET FLAG SO MAIN LOOP CAN DO SOMETHING ABOUT IT
if( InNdx >= INSTRMAX )
{ // IN STR NDX TOO LARGE, RESET IT
InNdx =0;
break;
}
}
}
|
|