Wednesday, December 12, 2012

I2C keyboard - update.


My little TI99-4A matrix keyboard testing project has evolved into a cool I2C keyboard that may find use in any micro controller hobby application. I built a prototype circuit and wrote software running on Atmel AT89S52 micro that scans a full matrix keyboard of a retro computer and sends ANSI ASCII codes via I2C bus. Why settle for a keypad with just a few keys, when you can have a full ASCII terminal connected to your micro controller project?
Any matrix keyboard can be used after modification of the keyboard scanning algorithm and key codes table. Philosophy remains the same.

Device works in master mode. I tested it with Arduino Uno board since it has I2C port and can work in slave mode.

Wiring diagram 
(8051 controller only, connecting to Arduino is trivial, I'll explain in a minute):




I2C bus consists of 2 lines, SDA (data) and SCL (clock).
SDA line goes to Arduino's input pin A4, SCL to Arduino's pin A5.
I power the keyboard controller circuit from Arduino board, so there is a total of 4 lines going from keyboard controller to Arduino: SDA, SCL, VCC and GND.


Here is the 8051 code (SDCC):


/*
 * Project: TI99-4A I2C keyboard.
 * Module:  ti994a
 * Author:  Marek Karcz
 * Purpose: TI99-4A keyboard with micro connected to I2C.
 *          I2C input device. Works in MASTER mode.
 * 
 * Hardware:
 *    Atmel 8051 compatible microcontroller AT89S52.
 *    1602 LCD display
 *    TI99-4A matrix keyboard (ports P2, P3).
 *    I2C line drivers (NPN transistors, base and pull-up resistors, NAND gates 74LS00).
 *    NAND gates - incoming SDA, SCK from I2C bus to data/clock in pins.
 *    NPN transistors - outgoing SDA, SCL signals from 8051 data/clock out pins to I2C bus.
 *
 * Keyboard to port pin connections:
 *
 * TI99-4a pin#      Pn.b
 * --------------------------
 *          11       P3.7
 *          10       P3.6
 *           3       P3.5
 *           7       P3.4
 *           2       P3.3
 *           1       P3.2
 *           4       P3.1
 *           5       P3.0
 *
 *           6       P2.6
 *           8       P2.5
 *           9       P2.4
 *          15       P2.3
 *          14       P2.2
 *          13       P2.1
 *          12       P2.0
 *
 * =================================
 *
 *  I2C bus interface:
 *
 *  P1.3 - sda (data) out inverted
 *  P1.4 - sda (data) in  inverted
 *  P1.5 - scl (clock) out inverted
 *  P1.6 - scl (clock) in  inverted
 *
 *  ================================
 *
 *  TO DO:
 *
 *  1) Auto-repeat.
 *  2) Cover all CTRL ANSI codes.
 *  3) Receive characters from I2C bus and display on LCD.
 *     I am not sure if this is practical. Perhaps separate I2C display on I2C bus, independent
 *     from this microcontroller would work better.
 *  
 */


#include <at89x52.h>
#include <stdlib.h>
#include <string.h>

// keil -> sdcc
#define sbit __sbit
#define code __code
#define using __using
#define interrupt __interrupt
#define _nop_() __asm NOP __endasm

typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef sbit BOOL ;


#define rs     P1_0 
#define rw     P1_1
#define ep     P1_2
#define KBP1   P3
#define KBP2   P2

// NOTE: I2C pins are inverted
#define SDA_OUT P1_3
#define SDA_IN  P1_4
#define SCL_OUT P1_5
#define SCL_IN  P1_6
#define I2C_HIGH 0
#define I2C_LOW  1

BYTE code dis1[] = {"I2C keyboard 1.0"};
BYTE code dis2[] = {"(C) Marek Karcz "};
BYTE code dis3[] = {"I2C line busy..."};
BYTE dispbuf[17];
BYTE keycode=0;
BYTE keysent=0;
BYTE row=0;
BYTE column=0;
BYTE code kbmatrix[8][7] =
{
{11, 43, 42, 41, 40, 22,  0},
{47, 31, 30, 29, 28, 32,  0},
{33, 20, 19, 18, 17, 21,  0},
{ 0,  9,  8,  7,  6, 10,  0},
{48,  2,  3,  4,  5,  1, 45},
{44, 24, 25, 26, 27, 23,  0},
{46, 13, 14, 15, 16, 12,  0},
{ 0, 36, 37, 38, 39, 35,  0}
};

BOOL bShiftOn = 0;
BOOL bCtrl = 0;
BOOL bFunc = 0;
BOOL bLock = 0;

void delay(BYTE ms);

void init_ports(void)
{
KBP1 = 0x00;
KBP2 = 0xFF;
P1 = 0xFF;
SDA_IN = 1;
SCL_IN = 1;
SDA_OUT = 0;
SCL_OUT = 0;
}

void init_dispbuf(void)
{
BYTE i=0;

for(i=0; i<16; i++)
{
dispbuf[i]=32;
}
dispbuf[i]=0;
}

void init_vars(void)
{
init_dispbuf();
keycode=0xFF;
row=0;
column=0;
}

void delay(BYTE ms)
{      
   BYTE i;
   while(ms--)
   {
      for(i = 0; i< 250; i++)
      {
         _nop_();
         _nop_();
         _nop_();
         _nop_();
      }
   }
}

/*
 * LCD 16x2 driver functions.
 */

BOOL lcd_bz()
{   
   BOOL result;
   rs = 0;
   rw = 1;
   ep = 1;
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   result = (BOOL)(P0 & 0x80);
   ep = 0;
   return result; 
}

void lcd_wcmd(BYTE cmd)

   while(lcd_bz());
   rs = 0;
   rw = 0;
   ep = 0;
   _nop_();
   _nop_(); 
   P0 = cmd;
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   ep = 1;
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   ep = 0;  
}

void lcd_pos(BYTE pos)
{
   lcd_wcmd(pos | 0x80);
}

void lcd_wdat(BYTE dat) 

    while(lcd_bz());
    rs = 1;
    rw = 0;
    ep = 0;
    P0 = dat;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    ep = 1;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    ep = 0; 
}

void lcd_init()
{
   lcd_wcmd(0x38); 
   delay(1);
   lcd_wcmd(0x0c); 
   delay(1);
   lcd_wcmd(0x06);
   delay(1);
   lcd_wcmd(0x01);
   delay(1);
}

void lcd_clear(BYTE pos)
{
   BYTE i=0;
   lcd_pos(pos);
   for(i=0; i<16; i++)
   {
      lcd_wdat(32);
   }
}

void lcd_text(BYTE pos, BYTE txt[])
{
   BYTE i=0;
   lcd_clear(pos);
   lcd_pos(pos);
   i=0;
   while((int)txt[i] != (int)'\0')
   {
      lcd_wdat(txt[i]); 
      i++;
   }
}

/*
 * TI99-4A keyboard driver functions.
 */

/* Scans keyboard.
 * Returns 0 if no key pressed.
 * Returns row# (1-8) if key pressed.
 * Sets global flags indicating special/control key pressed.
 * Column is calculated from the read key code.
 */
BYTE read_kb(void)
{
BYTE i=0;
BYTE j=0;
BYTE ret=0;
BYTE keyscan=0;
BYTE pattern=0x01;
BYTE col=0;

for (i=0,pattern=0x01; pattern!=0; pattern<<=1,i++)
{
  KBP2 = 0xFF;
  KBP1 = ~pattern;
  keyscan = KBP2;
  if(keyscan!=0xFF)
  {
 init_dispbuf();
 for(j=1,col=0; j!=0; j<<=1,col++)
 {
    if (~keyscan & j)
{
if (kbmatrix[i][col] == 44) bShiftOn = 1;
else if (kbmatrix[i][col] == 45) { bShiftOn = 1; bLock = 1; }
else if (kbmatrix[i][col] == 46) bCtrl = 1;
else if (kbmatrix[i][col] == 48) bFunc = 1;
else { row = i; ret = i+1; column = col; keycode = kbmatrix[row][column]; }
}
 }
  }
  delay(2);
}

// key released, reset flags
if (ret == 0)
       keysent = bShiftOn = bLock = bCtrl = bFunc = 0;

return ret;
}

/* 
 * I2C bus communication driver functions.
 *
 * NOTE: in/out data and clk signals inverted
 * 
 */

/*Send start condition*/
void I2C_Start(void)        /*Initial conditions*/
{
        SDA_OUT = I2C_HIGH;
        SCL_OUT = I2C_HIGH;
        _nop_ ();
        _nop_ ();
        _nop_ ();
        _nop_ ();
        SDA_OUT = I2C_LOW;
        _nop_ ();
        _nop_ ();
        _nop_ ();
        _nop_ ();
SCL_OUT = I2C_LOW;
}

void I2C_Stop(void) /*Stop condition*/
{
SCL_OUT = I2C_LOW;
        SDA_OUT = I2C_LOW;
        _nop_ ();
        _nop_ ();
        _nop_ ();
        _nop_ ();
        SCL_OUT = I2C_HIGH;
        _nop_ ();
        _nop_ ();
        _nop_ ();
        _nop_ ();
        SDA_OUT = I2C_HIGH;
        _nop_ ();
        _nop_ ();
        _nop_ ();
        _nop_ ();
}

BOOL I2C_Ack(void)  /* Receive acknowledge bit */
{
        SDA_IN = 1;
        _nop_ ();
        _nop_ ();
        _nop_ ();
        _nop_ ();
        SCL_OUT = I2C_HIGH;
        _nop_ ();
        _nop_ ();
        _nop_ ();
        _nop_ ();
        SCL_OUT = I2C_LOW;

return (SDA_IN == I2C_LOW);
}

void  I2C_NoAck(void)       /* Send acknowledge bit */
{
        SDA_OUT = I2C_LOW;
        _nop_ ();
        _nop_ ();
        _nop_ ();
        _nop_ ();
        SCL_OUT = I2C_HIGH;
        _nop_ ();
        _nop_ ();
        _nop_ ();
        _nop_ ();
        SCL_OUT = I2C_LOW;
}

void I2C_Send(BYTE d)   /*Subroutine to send data, d is required to send data*/
{
        BYTE BitCounter=8;       /*Median control*/
        BYTE temp;     /*Intermediate variable control*/
        do
        {
           temp=d;
           SCL_OUT = I2C_LOW;
           _nop_ ();
           _nop_ ();
           _nop_ ();
           _nop_ ();
           if((temp&0x80)==0x80)/* If the highest bit is 1*/
                SDA_OUT = I2C_HIGH;
           else
                SDA_OUT = I2C_LOW;
           _nop_ ();
           _nop_ ();
           _nop_ ();
           _nop_ ();
           SCL_OUT = I2C_HIGH;
           temp=d<<1;        /*RLC*/
           d=temp;
           BitCounter--;
         }while(BitCounter);
         SCL_OUT = I2C_LOW;
}

BYTE I2C_Read(void) /*Read one byte of data, and returns the byte value*/
{
        BYTE temp=0;
        BYTE temp1=0;
        BYTE BitCounter=8;
        SDA_IN = 1;
        do{
          SCL_OUT = I2C_LOW;
          _nop_ ();
          _nop_ ();
          _nop_ ();
          _nop_ ();
          SCL_OUT = I2C_HIGH;
          _nop_ ();
          _nop_ ();
          _nop_ ();
          _nop_ ();
          if(SDA_IN == I2C_HIGH)       /*If Sda=1;*/
                temp=temp|0x01;  /*The lowest temp 1*/
          else
                temp=temp&0xfe;  /*Otherwise, the lowest temp clear 0*/
          if(BitCounter-1)
          {   temp1=temp<<1;
              temp=temp1;
          }
          BitCounter--;
        }while(BitCounter);
        return(temp);
}

BOOL I2C_LineBusy(void)
{
return (SDA_IN != I2C_HIGH || SCL_IN != I2C_HIGH);
}

void lcdmsg(BYTE msg[], BYTE iter, BYTE dl)
{
BYTE n;

lcd_text(0, msg);
for(n=0; n<iter; n++)
{
       delay(dl);
}
    lcd_text(0, dis1);
}

void I2C_SendKey(BYTE kc)
{
if (!I2C_LineBusy())
{
  if (kc != keysent) // debounce
  {
     I2C_Start();
     I2C_Send(4<<1); // slave receiver address 
                     // shift left for 7 bit addr. + LSB as a R/W bit
     // LSB = 0 - WRITE
     I2C_Ack();
     I2C_Send(kc);
     I2C_Ack();
     I2C_Stop();
          keysent = kc;
  }
}
else
{
  lcdmsg(dis3, 8, 255); // Line busy message to LCD
  lcd_text(0, dis1);
}
}

/*
 * Convert keyboard scan code to ANSI ASCII code.
 * (more or less :-) )
 */
BYTE convKeyCode2Char(BYTE kc)
{
   BYTE ch = 0;

   switch (kc)
   {
      case  1: ch = ((bShiftOn) ? '!' : '1'); break; 
      case  2: ch = ((bShiftOn) ? '@' : '2'); break; 
      case  3: ch = ((bShiftOn) ? '#' : '3'); break; 
      case  4: ch = ((bShiftOn) ? '$' : '4'); break; 
      case  5: ch = ((bShiftOn) ? '%' : '5'); break; 
      case  6: ch = ((bShiftOn) ? '^' : '6'); break; 
      case  7: ch = ((bShiftOn) ? '&' : '7'); break; 
      case  8: ch = ((bShiftOn) ? '*' : '8'); break; 
      case  9: ch = ((bShiftOn) ? '(' : '9'); break; 
      case 10: ch = ((bShiftOn) ? ')' : '0'); break; 
      case 11: ch = ((bShiftOn) ? '+' : '='); break; 
      case 12: if (bCtrl) ch = 17; /* CTRL-Q */ else ch = ((bShiftOn) ? 'Q' : 'q'); break; 
      case 13: if (bFunc) ch = '~'; else ch = ((bShiftOn) ? 'W' : 'w'); break; 
      case 14: if (bFunc) ch = 128; /* up arrow */ else ch = ((bShiftOn) ? 'E' : 'e'); break; 
      case 15: if (bFunc) ch = '['; else ch = ((bShiftOn) ? 'R' : 'r'); break; 
      case 16: if (bFunc) ch = ']'; else ch = ((bShiftOn) ? 'T' : 't'); break; 
      case 17: ch = ((bShiftOn) ? 'Y' : 'y'); break; 
      case 18: if (bFunc) ch = '_'; else ch = ((bShiftOn) ? 'U' : 'u'); break; 
      case 19: if (bFunc) ch = '?'; else ch = ((bShiftOn) ? 'I' : 'i'); break; 
      case 20: if (bFunc) ch = '\''; else ch = ((bShiftOn) ? 'O' : 'o'); break; 
      case 21: if (bFunc) ch = '"'; else ch = ((bShiftOn) ? 'P' : 'p'); break; 
      case 22: ch = ((bShiftOn) ? '-' : '/'); break; 
      case 23: if (bFunc) ch = '|'; else ch = ((bShiftOn) ? 'A' : 'a'); break; 
      case 24: if (bCtrl) ch = 19; /* CTRL-S */
  else {if (bFunc) ch = 129; /* left arrow */ else ch = ((bShiftOn) ? 'S' : 's');}
  break; 
      case 25: if (bFunc) ch = 130; /* right arrow */ else ch = ((bShiftOn) ? 'D' : 'd'); break; 
      case 26: if (bFunc) ch = '{'; else ch = ((bShiftOn) ? 'F' : 'f'); break; 
      case 27: if (bFunc) ch = '}'; else ch = ((bShiftOn) ? 'G' : 'g'); break; 
      case 28: if (bCtrl) ch = 8; /* CTRL-H or BACKSPACE */ else ch = ((bShiftOn) ? 'H' : 'h'); break; 
      case 29: ch = ((bShiftOn) ? 'J' : 'j'); break; 
      case 30: ch = ((bShiftOn) ? 'K' : 'k'); break; 
      case 31: ch = ((bShiftOn) ? 'L' : 'l'); break; 
      case 32: ch = ((bShiftOn) ? ':' : ';'); break; 
      case 33: ch = '\n'; break;
      case 35: if (bCtrl) ch = 26; /* CTRL-Z */
  else {if (bFunc) ch = '\\'; else ch = ((bShiftOn) ? 'Z' : 'z');}
  break; 
      case 36: if (bFunc) ch = 131; /* down arrow */ else ch = ((bShiftOn) ? 'X' : 'x'); break; 
      case 37: if (bCtrl) ch = 3; /* CTRL-C */
  else { if (bFunc) ch = '`'; else ch = ((bShiftOn) ? 'C' : 'c');} 
  break; 
      case 38: ch = ((bShiftOn) ? 'V' : 'v'); break; 
      case 39: ch = ((bShiftOn) ? 'B' : 'b'); break; 
      case 40: ch = ((bShiftOn) ? 'N' : 'n'); break; 
      case 41: ch = ((bShiftOn) ? 'M' : 'm'); break; 
      case 42: ch = ((bShiftOn) ? '<' : ','); break; 
      case 43: ch = ((bShiftOn) ? '>' : '.'); break; 
      case 44: ch = 0; break;
 case 45: ch = 0; break;
 case 46: ch = 0; break;
 case 47: ch = ' '; break;
 case 48: ch = 0; break;

      default: ch = 0; break;
   }

   bShiftOn = 0; bLock = 0; bFunc = 0; bCtrl = 0;

   return ch;
}

/*
 * -------------- MAIN LOOP ---------------------
 */
main()
{
   BYTE kcodehex[5];
   BYTE kcodedec[10];
   BYTE rowbuf[3];
   BYTE row=0;
   BYTE ch=0;

   init_vars();
   init_ports();
   lcd_init();
   delay(10);
   // wait for I2C bus to become available
   while (I2C_LineBusy())
   {
 lcdmsg(dis3, 4, 255); // Line busy message to LCD
   }
   lcd_text(0,dis1);
   lcd_text(0x40,dis2);
   delay(255);
   delay(255);
   while(1)
   {
      if((row=read_kb())!=0)
 {
_uitoa(keycode,kcodehex,16);
_uitoa(keycode,kcodedec,10);
_uitoa(row,rowbuf,10);
strcpy(dispbuf,kcodehex);
strcat(dispbuf,":");
strcat(dispbuf,kcodedec);
strcat(dispbuf,":");
strcat(dispbuf,rowbuf);
    lcd_text(0x40,dispbuf);
ch = convKeyCode2Char(keycode);
if (ch != 0)
   I2C_SendKey(ch);
 }
 else
 {
    lcd_clear(0x40);
 }
   }
}

and here is the Arduino code:


// Wire Slave Receiver
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Receives data as an I2C/TWI slave device
// Refer to the "Wire Master Writer" example for use with this

// Created 29 March 2006

// This example code is in the public domain.


#include <Wire.h>


boolean bShiftPressed = false;

void setup()
{
  Wire.begin(4);                // join i2c bus with address #4
  Wire.onReceive(receiveEvent); // register event
  Serial.begin(9600);           // start serial for output
  Serial.println("I2C Slave Receiver");
}

void loop()
{
  delay(100);
}

char getCharFromKeyCode(int c)
{
   char ch = 0;
   
   switch (c)
   {
      case 128: break;
      case 129: break;
      case 130: break;
      case 131: break;
      
      default: ch = c; break;
   }
   
   return ch;
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany)
{
  char ch;
  int c;

  while(1 < Wire.available()) // loop through all but the last
  {
    c = Wire.read(); // receive byte as an integer
    ch = getCharFromKeyCode(c);
    if (ch)
       Serial.print(ch);         // print the character
  }
  c = Wire.read();    // receive byte as an integer
  ch = getCharFromKeyCode(c);
  if (ch)
     Serial.print(ch);         // print the character
}


Some pictures of the prototype and a screenshot of the Arduino's serial console on the receiving end:






Thank you for looking.

Marek Karcz
12/13/2012

Saturday, December 8, 2012

Arduino - starting my own playground.

Arduino - starting my own playground.


I have to admit, I have been skeptical about the Arduino hype for a while. I thought it to be a hobby platform for complete amateurs or technically unsophisticated audience. It sure is, but now I just started to see that it is not a bad thing. It also does not exclude that platform from being useful to a little bit more electronics/programming/technically oriented audience, a category I'd like to include myself in.

It all started the day when my local Radio Shack store had some sale event. I bought an Arduino Uno R3 board and some "getting started" book on Amazon. After reading through few chapters and testing the example sketches I realized how awesome this idea was to create an open micro controller platform, easy to use and create community around it. There are tons of code examples, cheap hardware extensions/shields and there is this free IDE, which is so easy to use. If you hate soldering or you are not into creating highly custom electronic circuits, the Arduino is like the Lego blocks equivalent in the hobby electronics world. You buy the components, connect them together, use one or more of the freely available programming examples as a template for the programming part of your project and you are ready to go!

Since I bought the Arduino Uno board, I bought several shields and electronic kits/modules for arduino that were interesting to me. I got motor shield, Ethernet shield, serial port shield, RTC/EEPROM I2C module, SD card module (SPI), LCD/keypad shield, I2C 20x4 LCD module, another Arduino board (a clone that was included in some bundle) and I am still waiting for Arduino Mega board. This stuff is amazingly cheap and easy to put together and getting to work. Using widely available code examples from internet, arduino web site, arduino IDE or included with the shields/modules that I bought, I was able to test all the modules above in 2 short evening sessions. Just a little bit of soldering was required with some of the shields and modules since some of them arrived in the form of partially assembled kits or with soldering holes for optional pins or sockets.

As an example, I present you with this nice sandwiched circuit, consisting of Arduino Uno R3 board, serial port shield sitting on top of it, the LCD/keypad shield sitting on top of the serial port shield and the I2C RTC/EEPROM module sitting in the socket I soldered into the LCD/keypad module. It took me just few hours to learn this stuff, collect and write the code, solder extra pins and sockets and put it all together:




The program it runs is just a test of RTC module. The date/time is displayed on the LCD and also sent over the serial port:



// Date and time functions using a DS1307 RTC connected via I2C and Wire lib

/*
  The LCD circuit:
 * LCD RS pin to digital pin 8
 * LCD Enable pin to digital pin 9
 * LCD D4 pin to digital pin 4
 * LCD D5 pin to digital pin 5
 * LCD D6 pin to digital pin 6
 * LCD D7 pin to digital pin 7
 * LCD BL pin to digital pin 10
 * KEY pin to analogl pin 0
 */


#include <Wire.h>
#include "RTClib.h"
#include <LiquidCrystal.h>

LiquidCrystal lcd(8, 13, 9, 4, 5, 6, 7);
RTC_DS1307 RTC;
boolean bBlink = true;

void setup () {
  // initialize LCD keypad module
    lcd.clear(); 
    lcd.begin(16, 2);
    lcd.setCursor(0,0); 
    lcd.print("RTC test");       
  // power to i2c_ds1307_at24c32 module provided via A2, A3 pins
    pinMode(A3, OUTPUT); 
    digitalWrite(A3, HIGH);
    pinMode(A2, OUTPUT);
    digitalWrite(A2, LOW);
  // start communication, I2C and RTC
    Serial.begin(9600);
    Wire.begin();
    RTC.begin();

  if (! RTC.isrunning()) {
    Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled
    //RTC.adjust(DateTime(__DATE__, __TIME__));
  }

}

void loop () {
    DateTime now = RTC.now();

    lcd.setCursor(0,1);
    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(' ');
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();
    
    lcd.print(now.year(), DEC);
    lcd.print('/');
    lcd.print(now.month(), DEC);
    lcd.print('/');
    lcd.print(now.day(), DEC);
    lcd.print(' ');
    lcd.print(now.hour(), DEC);
    lcd.print(':');
    lcd.print(now.minute(), DEC);
    if (bBlink)
    {
       lcd.print(':');
       bBlink = false;
    }
    else
    {
       lcd.print(' ');
       bBlink = true;
    }

    Serial.print(" since 1970 = ");
    Serial.print(now.unixtime());
    Serial.print("s = ");
    Serial.print(now.unixtime() / 86400L);
    Serial.println("d");

    // calculate a date which is 7 days and 30 seconds into the future
    DateTime future (now.unixtime() + 7 * 86400L + 30);

    Serial.print(" now + 7d + 30s: ");
    Serial.print(future.year(), DEC);
    Serial.print('/');
    Serial.print(future.month(), DEC);
    Serial.print('/');
    Serial.print(future.day(), DEC);
    Serial.print(' ');
    Serial.print(future.hour(), DEC);
    Serial.print(':');
    Serial.print(future.minute(), DEC);
    Serial.print(':');
    Serial.print(future.second(), DEC);
    Serial.println();

    Serial.println();
    delay(1000);
}


For the rapid project development or ad-hoc circuit testing, Arduino is a great platform that also more technically sophisticated audience can use. Definitely not only for artists and kids learning electronic, as it is categorized by some.
Arduino will stay in my toolbox for the time to come.

Credits:

I used code samples from following sources:

DS1307 RTC tutorial.

Code sample from eBay listing of Keypad Shield 1602 LCD For Arduino MEGA 2560 1280 UNO R3 A005.

Marek Karcz
2012/12/08

Monday, December 3, 2012

I2C Keyboard and LCD panel - a little side project.


I obtained this nice unused TI99-4A keyboard on eBay. Because the price was right, I decided this was going to be a keyboard for my MKHBC-8-R1 system. But then I thought - why shouldn't it be a universal keyboard for all my hobby computer/microcontroller projects? 

TI99-4A keyboard is a matrix keyboard. It has 15 output pins, which form 8x7 matrix. To connect it to the computer system in a "classic" way, you need two 8-bit ports (less 1 pin). I'd have to add to my computer system a dedicated I/O chip that would serve only as a keyboard scanning circuit (with appropriate firmware in ROM). This seems to be a little bit wasteful. Perhaps instead I could equip my computer system with implementation of I2C bus, which is a widely used standard in chip to chip communication applications. I save 13 pins, since data only travels on 2 lines and I can use I2C port for other standard compatible devices, so it is not "wasted" to serve only one purpose.

Therefore I started this side project to develop an I2C "console", which will consist of TI99-4A keyboard and a basic 16x2 text LCD panel. My choice of hardware is 8051 compatible Atmel chip AT89S52.

Two reasons:

1) I know it.
2) I have a drawer full of them.

There are other practical reasons of course, like:


  • Widely known architecture with tons of code examples available all over the internet.
  • Four 8-bit 2-way digital ports.
  • 8 kB of flash program memory programmable in circuit via SPI interface with cheap flash programmer from eBay.
  • Built in timers, UART etc.
  • Really cheap.


 For starters I built this test circuit to test the keyboard. It has not I2C implemented yet.



NOTE: The internet sources say that pin# 15 of the TI99-4A keyboard is colored in red. However the one that I got has a pin# 1 marked with red wire. This gave me a bit of a trouble before I figured it out (my program would not work).

Here is thesource  code for SDCC. Program will display the numeric (not ASCII, just the matrix code) code of each pressed key on the LCD panel:


/*
 * Project: TI99-4A keyboard.
 * Module:  ti994a
 * Author:  Marek Karcz
 * Purpose: Testing TI99-4A keyboard.
 * 
 * Hardware:
 *    AT89S52
 *    1602 LCD display
 *    TI99-4A matrix keyboard (ports P2, P3).
 *
 * Keyboard to port pin connections:
 *
 * TI99-4a pin#      Pn.b
 * --------------------------
 *          11       P3.7
 *          10       P3.6
 *           3       P3.5
 *           7       P3.4
 *           2       P3.3
 *           1       P3.2
 *           4       P3.1
 *           5       P3.0
 *
 *           6       P2.6
 *           8       P2.5
 *           9       P2.4
 *          15       P2.3
 *          14       P2.2
 *          13       P2.1
 *          12       P2.0
 */


#include <at89x52.h>
#include <stdlib.h>
#include <string.h>

// keil -> sdcc
#define sbit __sbit
#define code __code
#define using __using
#define interrupt __interrupt
#define _nop_() __asm NOP __endasm

typedef unsigned char BYTE;
typedef unsigned int WORD;
typedef sbit BOOL ;


#define rs     P1_0 
#define rw     P1_1
#define ep     P1_2
#define KBP1   P3
#define KBP2   P2

BYTE code dis1[] = {"TI99-4A"};
BYTE code dis2[] = {"Keyboard test"};
//BYTE code msg1[] = {"Key pressed"};
BYTE dispbuf[17];
BYTE keycode=0;
BYTE row=0;
BYTE column=0;
BYTE code kbmatrix[8][7] =
{
{11, 43, 42, 41, 40, 22,  0},
{47, 31, 30, 29, 28, 32,  0},
{33, 20, 19, 18, 17, 21,  0},
{ 0,  9,  8,  7,  6, 10,  0},
{48,  2,  3,  4,  5,  1, 45},
{44, 24, 25, 26, 27, 23,  0},
{46, 13, 14, 15, 16, 12,  0},
{ 0, 36, 37, 38, 39, 35,  0}
};

void delay(BYTE ms);

void init_ports(void)
{
KBP1 = 0x00;
KBP2 = 0xFF;
P1 = 0xFF;
}

void init_dispbuf(void)
{
BYTE i=0;

for(i=0; i<16; i++)
{
dispbuf[i]=32;
}
dispbuf[i]=0;
}

void init_vars(void)
{
init_dispbuf();
keycode=0xFF;
row=0;
column=0;
}

void delay(BYTE ms)
{      
   BYTE i;
   while(ms--)
   {
      for(i = 0; i< 250; i++)
      {
         _nop_();
         _nop_();
         _nop_();
         _nop_();
      }
   }
}

BOOL lcd_bz()
{   
   BOOL result;
   rs = 0;
   rw = 1;
   ep = 1;
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   result = (BOOL)(P0 & 0x80);
   ep = 0;
   return result; 
}

void lcd_wcmd(BYTE cmd)

   while(lcd_bz());
   rs = 0;
   rw = 0;
   ep = 0;
   _nop_();
   _nop_(); 
   P0 = cmd;
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   ep = 1;
   _nop_();
   _nop_();
   _nop_();
   _nop_();
   ep = 0;  
}

void lcd_pos(BYTE pos)
{
   lcd_wcmd(pos | 0x80);
}

void lcd_wdat(BYTE dat) 

    while(lcd_bz());
    rs = 1;
    rw = 0;
    ep = 0;
    P0 = dat;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    ep = 1;
    _nop_();
    _nop_();
    _nop_();
    _nop_();
    ep = 0; 
}

void lcd_init()
{
   lcd_wcmd(0x38); 
   delay(1);
   lcd_wcmd(0x0c); 
   delay(1);
   lcd_wcmd(0x06);
   delay(1);
   lcd_wcmd(0x01);
   delay(1);
}

void lcd_clear(BYTE pos)
{
   BYTE i=0;
   lcd_pos(pos);
   for(i=0; i<16; i++)
   {
      lcd_wdat(32);
   }
}

void lcd_text(BYTE pos, BYTE txt[])
{
   BYTE i=0;
   lcd_clear(pos);
   lcd_pos(pos);
   i=0;
   while((int)txt[i] != (int)'\0')
   {
      lcd_wdat(txt[i]); 
      i++;
   }
}

/* Scans keyboard.
 * Returns 0 if no key pressed.
 * Returns row# (1-8) if key pressed.
 * Column is calculated from the read key code.
 */
BYTE read_kb(void)
{
BYTE i=0;
BYTE j=0;
BYTE ret=0;
BYTE keyscan=0;
BYTE pattern=0x01;

for (i=0,pattern=0x01; pattern!=0; pattern<<=1,i++)
{
  KBP2 = 0xFF;
  KBP1 = ~pattern;
  keyscan = KBP2;
  if(keyscan!=0xFF)
  {
 init_dispbuf();
 for(j=1,column=0; j!=0; j<<=1,column++)
 {
    if (~keyscan & j)
     {
        break;
     }
 }
 row = i;
          ret = i+1;
 keycode = kbmatrix[row][column];
 break;
  }
  delay(4);
}

return ret;
}

main()
{
   BYTE kcodehex[5];
   BYTE kcodedec[10];
   BYTE rowbuf[3];
   BYTE row=0;

   init_vars();
   init_ports();
   lcd_init();
   delay(10);
   lcd_text(4,dis1);
   lcd_text(0x41,dis2);
   delay(255);
   delay(255);
   while(1)
   {
      if((row=read_kb())!=0)
 {
     _uitoa(keycode,kcodehex,16);
     _uitoa(keycode,kcodedec,10);
     _uitoa(row,rowbuf,10);
     strcpy(dispbuf,kcodehex);
     strcat(dispbuf,":");
     strcat(dispbuf,kcodedec);
     strcat(dispbuf,":");
     strcat(dispbuf,rowbuf);
     lcd_text(0x41,dispbuf);
 }
 else
 {
    lcd_clear(0x41);
 }
   }
}



Hopefully this project will become something practical.
I built the prototype on a breadboard.




Thank you for reading.

Marek Karcz
12/3/2012