Sunday, November 17, 2013

Update on MKHBC-8-R1.

If I only knew this would be so much work... ;-)

OK, the point-to-point soldering isn't really fun. At least not when you have to do so many soldering points. Well, I guess nothing builds itself. If I want to see some results then I have to go through it. No way around it, I can not afford outsourcing.

Therefore over the past several, aaaah..., months, I did most of the soldering on my CPU card. So, don't loose the hope yet, we just might see the end of this part of the project before the end of the year. My goal is to have CPU card and serial port card ready this year, as I outlined in my last update.

Just take a look at the pictures, as the computer takes a shape...


Image 1 : CPU card (to the left) and mother board (to the right). I record my soldering progress the old fashioned way by checking out the connections on the list.


Image 2 : Closer look at the CPU card.
Image 3 : CPU card, the connections side. It looks like a big mess of wires, but believe me, each connection is very carefully laid out, done and checked with the multi-meter. I hope this will work :-)

Image 4 : This is how it looked like nearly 2 months ago, when I connected the Vcc and Ground points.

This article does not look like much, but it actually describes about 8-10 hours of my work (on the CPU card alone). And this brings us to the beginning of this article. ;-)

Thank you for visiting.

Marek Karcz, 11/18/2013

Saturday, November 16, 2013

Raspberry Pi, I2C LCD screen and Safe Power Down button.

The Raspberry Pi, just sitting there, asking to be played with...

As I looked over my existing projects I noticed this sad little lone R-Pi sitting on my desk. Some time ago I bought it. Played with it a bit, installed NOOB image on the SD card, connected I2C LCD text display to it and a shutdown switch and played a bit with GPIO programming in Python. I want to make a car trip computer out of this R-Pi. You know, a computer that will read car's ECU via OBD2 port and display information in real time, like the average and current gas usage, trip time and length, how many miles left on the remaining fuel and such. In the future perhaps I could add a camera and trip log. This may be a cool project. I commute to work 15 miles, so there will be plenty opportunity to test the toy.
I remembered that this LCD screen, connected to GPIO pins via some prototyping wire (female connectors at both ends) gave me a lot of trouble related to poor contact, so I decided to upgrade it and make a dedicated connector with wires soldered to it. I also need a keypad for the project, but since I did not buy or make one yet, I just connected a LCD and a shutdown switch at this time.

The LCD takes just 4 wires – 5V, GND, SDA and SCL (I2C and power, Pi's GPIO pins #2, 6, 3, 5 respectively). The unfortunate thing is that the R-Pi's logic is 3.3V, however this display's power voltage requirement and logic is 5V. I tried to power it from Pi's 3.3V pin hoping that it has some built in tolerance, however the display would not work then. Therefore I powered the screen from Pi's 5V pin with a bit of scare that I might damage the Pi's GPIO port, however this seems to work fine. It looks like the device accepts 3.3V logic, just needs to be powered by 5V to operate. I guess it should be OK as long as I am not sending the input to R-Pi in the 5V level. Since LCD is an output device, this is not the case. The temporary connection I made previously suffered from poor contact and I often had voltage drops that caused information loss on the display, even with the 5V power connected to the device. With the new connector things look much better and the display is finally reliable.

Connections diagram.



The code.

I found very useful information here.
It presents the driver in Python for LCD display that is in turn driven by PCF8574 expander chip, which is basically an I2C hardware driver for the LCD display.
It is easy to implement and start programming your LCD screen in Python right away (assuming you already installed and configured your GPIO libraries).

The actual driver code listed below, lcddriver.py was taken from above web site at the time I made the project:

import smbus
from time import *

# General i2c device class so that other devices can be added easily
class i2c_device:
def __init__(self, addr, port):
   self.addr = addr
   self.bus = smbus.SMBus(port)

def write(self, byte):
   self.bus.write_byte(self.addr, byte)

def read(self):
   return self.bus.read_byte(self.addr)

def read_nbytes_data(self, data, n): # For sequential reads > 1 byte
   return self.bus.read_i2c_block_data(self.addr, data, n)


class lcd:
   #initializes objects and lcd
   '''
   Reverse Codes:
   0: lower 4 bits of expander are commands bits
   1: top 4 bits of expander are commands bits AND P0-4 P1-5 P2-6
   2: top 4 bits of expander are commands bits AND P0-6 P1-5 P2-4
   '''
   def __init__(self, addr, port, reverse=0):
      self.reverse = reverse
      self.lcd_device = i2c_device(addr, port)
      if self.reverse:
         self.lcd_device.write(0x30)
         self.lcd_strobe()
         sleep(0.0005)
         self.lcd_strobe()
         sleep(0.0005)
         self.lcd_strobe()
         sleep(0.0005)
         self.lcd_device.write(0x20)
         self.lcd_strobe()
         sleep(0.0005)
      else:
         self.lcd_device.write(0x03)
         self.lcd_strobe()
         sleep(0.0005)
         self.lcd_strobe()
         sleep(0.0005)
         self.lcd_strobe()
         sleep(0.0005)
         self.lcd_device.write(0x02)
         self.lcd_strobe()
         sleep(0.0005)

      self.lcd_write(0x28)
      self.lcd_write(0x08)
      self.lcd_write(0x01)
      self.lcd_write(0x06)
      self.lcd_write(0x0C)
      self.lcd_write(0x0F)

   # clocks EN to latch command
   def lcd_strobe(self):
      if self.reverse == 1:
         self.lcd_device.write((self.lcd_device.read() | 0x04))
         self.lcd_device.write((self.lcd_device.read() & 0xFB))
      if self.reverse == 2:
         self.lcd_device.write((self.lcd_device.read() | 0x01))
         self.lcd_device.write((self.lcd_device.read() & 0xFE))
      else:
         self.lcd_device.write((self.lcd_device.read() | 0x10))
         self.lcd_device.write((self.lcd_device.read() & 0xEF))

   # write a command to lcd
   def lcd_write(self, cmd):
      if self.reverse:
         self.lcd_device.write((cmd >> 4)<<4)
         self.lcd_strobe()
         self.lcd_device.write((cmd & 0x0F)<<4)
         self.lcd_strobe()
         self.lcd_device.write(0x0)
      else:
         self.lcd_device.write((cmd >> 4))
         self.lcd_strobe()
         self.lcd_device.write((cmd & 0x0F))
         self.lcd_strobe()
         self.lcd_device.write(0x0)

   # write a character to lcd (or character rom)
   def lcd_write_char(self, charvalue):
      if self.reverse == 1:
         self.lcd_device.write((0x01 | (charvalue >> 4)<<4))
         self.lcd_strobe()
         self.lcd_device.write((0x01 | (charvalue & 0x0F)<<4))
         self.lcd_strobe()
         self.lcd_device.write(0x0)
      if self.reverse == 2:
         self.lcd_device.write((0x04 | (charvalue >> 4)<<4))
         self.lcd_strobe()
         self.lcd_device.write((0x04 | (charvalue & 0x0F)<<4))
         self.lcd_strobe()
         self.lcd_device.write(0x0)
      else:
         self.lcd_device.write((0x40 | (charvalue >> 4)))
         self.lcd_strobe()
         self.lcd_device.write((0x40 | (charvalue & 0x0F)))
         self.lcd_strobe()
         self.lcd_device.write(0x0)

   # put char function
   def lcd_putc(self, char):
      self.lcd_write_char(ord(char))

   # put string function
   def lcd_puts(self, string, line):
      if line == 1:
         self.lcd_write(0x80)
      if line == 2:
         self.lcd_write(0xC0)
      if line == 3:
         self.lcd_write(0x94)
      if line == 4:
         self.lcd_write(0xD4)

      for char in string:
         self.lcd_putc(char)

   # clear lcd and set to home
   def lcd_clear(self):
      self.lcd_write(0x1)
      self.lcd_write(0x2)

   # add custom characters (0 - 7)
   def lcd_load_custon_chars(self, fontdata):
      self.lcd_device.bus.write(0x40);
      for char in fontdata:
         for line in char:
            self.lcd_write_char(line)



Example of use, the script that displays welcome banner, hello.py:

import lcddriver
from time import *

lcd = lcddriver.lcd()

lcd.lcd_clear()
lcd.lcd_display_string("*------------------*",1);
lcd.lcd_display_string("|System is running.|",2)
lcd.lcd_display_string("*------------------*",3);


I modified few start-up and shutdown handling scripts to run the scripts that display information on the LCD screen. This way I have neat messages that you can see on the pictures when the system is booted up and ready to use and also when I press the shutdown button, I get the information displayed about the shutdown progress. Since there is only one display and there are multiple scripts or programs that would like to write data to it, the proper way to do it would be to create some sort of a server that would take the requests from clients and relay them to the LCD screen. For now however I write them directly to the LCD screen since this was meant as a demo and proof of operation.

E.g: the script displaying welcome banner, hello.py, was added in script /etc/rc.local at the end:

cd ~pi/src/py/i2c/lcd/hello
python hello.py &
cd -

The script that polls GPIO pin #18 for low state, then shuts down the R-Pi and displays adequate messages to LCD screen, safeoff.py:

import lcddriver
from time import *
import RPi.GPIO as GPIO
import os
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.IN,pull_up_down=GPIO.PUD_UP)

lcd = lcddriver.lcd()
while True:
   if(GPIO.input(18) == 0):
      lcd.lcd_clear()
      lcd.lcd_display_string("Shutting down...",1)
      os.system("sudo shutdown -h now")
      break
   time.sleep(2)

progress = "/"
while True:
   lcd.lcd_display_string(progress,2)
   progress=progress+"/"
   time.sleep(1);

was added to /etc/rc.local script as well:

cd ~pi/src/py/i2c/lcd/safeoff
python safeoff.py &
cd -

The script that displays the final message, systemoffmsg.py:

import lcddriver

lcd = lcddriver.lcd()
lcd.lcd_clear()
lcd.lcd_display_string("System is halted.",1)

And the script that displays the system restart message, sysrestmsg.py:

import lcddriver

lcd = lcddriver.lcd()
lcd.lcd_clear()
lcd.lcd_display_string("System will restart.",1)
lcd.lcd_display_string("Wait for welcome",2)
lcd.lcd_display_string("screen...",3)

were added to /etc/init.d/halt and /etc/init.d/reboot respectively:

   log_action_msg "Will now halt"
   python ~pi/src/py/i2c/lcd/safeoff/sysoffmsg.py >/dev/null 2>&1
   halt -d -f $netdown $poweroff $hddown
}

and

do_stop () {
   # Message should end with a newline since kFreeBSD may
   # print more stuff (see #323749)
   log_action_msg "Will now restart"
   python ~pi/src/py/i2c/lcd/safeoff/sysrestmsg.py
   reboot -d -f -i
}

Pictures.

Image 1: R-Pi with I2C LCD and shutdown switch connected before it was put in the case.

Image 2: Detailed view of GPIO connector.

Image 3: Detailed view of I2C LCD connector.

Image 4: R-Pi in case with LCD attached on top with the rubber bands.

Image 5: R-Pi, side view.

Image 6: R-Pi, back side view, a bit of creativity with a Lego (C) block supporting the LCD screen.

Image 7: ...and the SD card side view.

Image 8: Welcome banner displayed on LCD after R-Pi boot-up.

Image 9: Testing shutdown button.

Image 10: Shutdown progress displayed.

Image 11: It is now safe to power off your R-Pi.

Well, this is it. Nothing much, but perhaps this article will help somebody doing first steps in R-Pi exploration or looking for the information related to the topic of connecting LCD or making a safe power-off switch for R-Pi's embedded use (with no keyboard and HDMI display).

Thanks for reading.

Marek Karcz, 11/16/2013

Monday, August 5, 2013

Getting acquainted with Raspberry Pi

The whole Raspberry Pi craze lasted long enough for me to go and buy one of these boards to try it  on. Got one on e-bay, model B, just a board. I purchased SD card separately, an 8GB SDHC SanDisk Ultra. Few days later, just 2 days ago I received my order in mail.
I must say the setup was a breeze. With the NOOB image, all you need to do is format your SD card, copy files from ZIP archive to your card and stick the SD card in the RPi's slot. Connect keyboard, mouse and display and power it up.
In 2 days that I have been playing with this board I managed to:
  • Setup US keyboard layout (default is British). 
  • Configure WiFi network (SMC WiFi dongle, based on ZD1211 chip).
  • Install and configure VNC server so I can access GUI desktop of my RPi from Windows or Linux based computer with TightVNC client.
  • Check USB to RS-232 converter that works with RPi (based on PL2302 chip). 
  • Install and configure Samba for server file sharing.
  • Install GPIO and I2C Python libraries.
  • Connect and successfully program (with the help of wonderfully rich internet resources already available for RPi) I2C LCD screen that I originally purchased for my Arduino.
  • Make a safe shutdown switch connected to GPIO pin #18 which is polled by a simple Python script that shuts down RPi when the switch is pressed for more than 1 second while displaying shutdown progress on LCD screen.
I was going to publish some of the URL-s and instructions/code that I used while achieving above goals, however anybody that can Google stuff can find out quick enough how to do these things. No need to create more noise I guess. Perhaps when I do something original with the touch of my own original thought, I will publish detailed documentation of the process.

These are wonderful times for electronics/computer hobbyists. Sometimes I think this stuff is just too easy!
My plan is to make a trip/car computer out of this RPi with the aid of OBD2 to USB codes scanner/converter I got on eBay, which is based on ELM 327 chip and with aforementioned I2C LCD display as a data presentation device. There is a piece of free software that works with that ODB2 scanner device on RPi and it can be integrated into Python's code.
Considering that this board is almost a full blown PC computer, and considering its low price and the amount of software/code base already available on the internet for tinkering and numerous examples of use, this is a great deal. The device that is ready for standard PC compatible peripherals (via USB) and with its GPIO expansion slot, it is the best of 2 worlds - a standard PC platform running familiar Linux OS with more than sufficient processing power for hobby projects and beyond and with the means to interface with outside world it is a great board to be added to any computer/electronics hobbyist's  toolbox.

Friday, June 7, 2013

MKHBC-8-R1 homebrew 6502 computer - assembling CPU card.

Very slowly however consequently I move forward with the project. I finished (well, almost) my back plane and now I am ready to assemble my first two cards - CPU card (which is really a combination of CPU, address and I/O decoders, ROM and RAM) and the serial port/uart card.
Every engineering project should be well documented. I created circuit diagrams in Express SCH and component layout for CPU card in Express PCB.
I also created a design and specifications document which I update regularly.

(NOTE: You may want to download this document to your computer and view it locally with Open Office instead of the Word viewer provided by SkyDrive - for some reason it can not display pictures embedded in the document when viewed in the web viewer. Probably Open Office/Word format incompatibility issue.)

CPU card circuit diagram.
CPU card - EPROM.

CPU card - base RAM.

Some of the circuit elements are missing on the board, I will update the blog shortly as the work progresses.
It is worth the note that despite the fact that I use a multiple board back plane design, I decided to make the CPU card able to function in a single board mode (sort of). Additionally to the euro 64-pin male connector that goes into the back plane's slot, the CPU card will also have the IDE type 40-pin male connector on the opposite side (or on the top) from euro connector (J4 on the diagram). This 40-pin connector, or rather slot will be identical that I used for the I/O expansion bus slots. The connector's pins will be connected to the same signals on CPU card that go into the I/O expansion bus (will be compatible with I/O expansion bus pin-out and IOSEL signal will go directly to one of the /IO0../IO7 pins on CPU card, perhaps via some jumper selector to allow configuration choices) thus creating ability to connect the I/O card (e.g: serial port card, general purpose I/O card etc.) directly to the CPU board. The euro connector will be used in the SBC mode to attach the CPU card to the control panel board with power supply, control buttons, LCD, generic I/O etc. - we can call it a simplified back plane, with one 64-pin slot only (luckily I have one spare). The details are included in the design and specifications document mentioned earlier. When inserted to back plane, the J4 slot on the CPU card will not be used. This design may require some creative programming so the firmware would not require to be updated if I move between back plane and SBC modes (auto detection of some sort).
I made some layout work already on the CPU card and soldered the IC sockets and J4 connector into the board.


CPU card - PCB layout.
The actual CPU card board in its current building phase.

The back of the CPU card - there will be a lot more soldered points and wires here soon.

CPU card sitting in the back plane's CPU bus slot.
Since the back-plane is almost finished, I include the circuit diagram of CPU and I/O expansion buses below as well.


Back plane - CPU bus.
Back plane - I/O expansion bus.
Thank you for visiting my blog.

Marek

6/8/2013.

Monday, May 27, 2013

I2C keyboard controller - update.

Months of testing with various Arduino projects has proven that the I2C keyboard prototype is working therefore I decided to assemble it on the permanent board. I used a RadioShack's pre-drilled stripboard number 276-168B and point to point soldering technique with 30-gauge solderable magnet wire (insulation coating dissolves under heat from soldering iron). I have made modifications to the original circuit diagram. I added programming connector so the firmware can be updated without taking out the AT89S52 chip out of the board. All you need is an ISP programmer with 10 pin connector and you need to open the J4 jumper pins 1, 2 for programming mode. A fact worth noting is that I connected AT89S52's pin #31 (/EA) to VCC - a must for an internal program execution mode. It took me an extra hour to figure this out when my board wasn't working. I apparently had this connection done on the breadboard but I forgot to update it on the diagram. Months passed since and I forgot. I also replaced ugly keyboard and LCD symbols on the diagram with connector symbols. The board is built in such a way that it can also serve as a basic development board for AT89S52. It has standard 16x2 16-pin LCD connector for HDD44780 compatible display which can be used as multi purpose I/O connector in case LCD display is not used and 15-pin connector for the matrix keyboard (TI-99/4A) can be used as a multi purpose I/O ports connector as well. I may need to add some more I/O connectors and a serial port in case I use this board for any other purpose than I2C keyboard driver, but this is not likely. I will rather build another one, but I digress.

I did changes to the firmware since last update:

/*
 * Project: TI99-4A I2C keyboard.
 * Module:  ti994a
 * Author:  Marek Karcz
 * Updated: 2012/12/30
 * Purpose: TI99-4A keyboard and LCD disp. with micro connected to I2C.
 *          I2C input device. Works in MASTER mode.
 *          I2C output device (LCD). 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. - DONE
 *  1a) Keyboard scan must run in interrupt and queue the keys in a buffer. - DONE
 *  2) Cover all CTRL ANSI codes.
 *  3) Receive characters from I2C bus and display on LCD.
 *     In progress...
 *     Current protocol: 
 *     * Device sends a single key code to I2C bus.
 *     * Device requests single key code from I2C bus (echo).
 *     Planned expansion to protocol:
 *     * Device sends a single key code to I2C bus.
 *     * Device requests number of available bytes for LCD display from I2C bus.
 *     * Device reads all available bytes from I2C bus.
 *  4) Fully implement arbitration and clock stretching. - DONE
 *  5) Figure out why Arduino does not ACK when addressed as slave-receiver. - DONE
 *     (NOTE: implementation of #4 and polling data until ACK returns OK)
 *  6) Use extra port line to generate interrupt for external receiver/CPU.
 *  
 */


#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
//#define _nop4_() __asm NOP NOP NOP NOP NOP NOP __endasm
#define _nop4_() __asm NOP NOP NOP 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
#define ACK        FALSE
#define NACK       TRUE
#define TRUE       1
#define FALSE      0
#define WAIT4ACK   100
#define KEYBUFLEN  16
#define INTDIVCT   0
#define KBRPTDEL   18          // keyboard repeat delay

BYTE code g_ucaDispMsgPrdNameVer[] = {"I2C kbrd 2.4    "};
BYTE code g_ucaDispMsg2[]          = {"(C) Marek Karcz "};
BYTE code g_ucaDispMsgLineBusy[]   = {"I2C line busy..."};
BYTE g_ucaDispBuf[17];
BYTE g_ucKeyCode=0;
BYTE g_ucPrevKey=0;
BYTE g_ucRow=0;
BYTE g_ucColumn=0;
BYTE code g_ucaKbMatrix[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 g_bShiftOn = FALSE;
BOOL g_bCtrl = FALSE;
BOOL g_bFunc = FALSE;
BOOL g_bLock = FALSE;
BOOL g_bStarted = FALSE;

BYTE g_ucaKeyBuf[KEYBUFLEN];
BYTE g_ucKeyBufStartIndex = 0;// at the beginning buffer is empty
BYTE g_ucKeyBufEndIndex = 0;  // StartIndex == EndIndex -> buffer empty
BYTE g_ucDivIntCt = INTDIVCT; // kb scan interrupt divider/counter
BYTE g_ucKbRptDel = KBRPTDEL; // kb repeat delay counter

void init_SFR(void)
{
   TMOD |= 0x01; // timer 0, mode 1, 16 bit
TH0  =  0x70; // for 40 ms timer (on XTAL=11.0592 MHz)
TL0  =  0x00; // keyboard scan frequency is 25/INTDIVCT/s
IE   =  0xA2; // 0x82; // enable IE.1 (timer 0) and global interrupt. (IE.7)
   TR0  =  1;    // timer 0 runs
}

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

void init_g_ucaDispBuf(void)
{
BYTE i = 0;

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

void init_vars(void)
{
BYTE i = 0;
init_g_ucaDispBuf();
g_ucKeyCode=0xFF;
g_ucPrevKey=0;
g_ucRow=0;
g_ucColumn=0;
g_ucKeyBufStartIndex = g_ucKeyBufEndIndex = 0;
for (i=0; i<KEYBUFLEN; i++)
{
g_ucaKeyBuf[i] = 0;
}
g_ucDivIntCt = INTDIVCT;
g_ucKbRptDel = KBRPTDEL;
}

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

/*
 * LCD 16x2 driver functions.
 */

BOOL lcd_bz()
{   
   BOOL result;
   rs = 0;
   rw = 1;
   ep = 1;
   _nop4_();
   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;
   _nop4_();
   ep = 1;
   _nop4_();
   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;
    _nop4_();
    ep = 1;
    _nop4_();
    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++;
   }
}

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, g_ucaDispMsgPrdNameVer);
}

/*
 * 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;

g_ucKeyCode = 0;
for (i=0,pattern=0x01; pattern!=0; pattern<<=1,i++)
{
  KBP2 = 0xFF;
  KBP1 = ~pattern;
  keyscan = KBP2;
  if(keyscan!=0xFF)
  {
 init_g_ucaDispBuf();
 for(j=1,col=0; j!=0; j<<=1,col++)
 {
    if (~keyscan & j)
{
if (g_ucaKbMatrix[i][col] == 44) g_bShiftOn = TRUE;
else if (g_ucaKbMatrix[i][col] == 45) { g_bShiftOn = TRUE; g_bLock = TRUE; }
else if (g_ucaKbMatrix[i][col] == 46) g_bCtrl = TRUE;
else if (g_ucaKbMatrix[i][col] == 48) g_bFunc = TRUE;
else { g_ucRow = i; ret = i+1; g_ucColumn = col; g_ucKeyCode = g_ucaKbMatrix[g_ucRow][g_ucColumn]; }
}
 }
  }
}

// key released, reset flags
if (ret == 0)
       g_bShiftOn = g_bLock = g_bCtrl = g_bFunc = FALSE;

return ret;
}

/*
 * Keyboard buffer is a ring/round-about FIFO register.
 */
static void add2KeyBuf(BYTE kc) using 1
{
   g_ucaKeyBuf[g_ucKeyBufEndIndex++] = kc;
if (g_ucKeyBufEndIndex >= KEYBUFLEN)
{
g_ucKeyBufEndIndex = 0;
}
}

static BYTE getKeyFromBuf(void) using 1
{
   BYTE kbk = 0;

if (g_ucKeyBufStartIndex != g_ucKeyBufEndIndex)
{
  kbk = g_ucaKeyBuf[g_ucKeyBufStartIndex++];
if (g_ucKeyBufStartIndex >= KEYBUFLEN)
{
g_ucKeyBufStartIndex = 0;
}
}

return kbk;
}

/*
 * Interrupt routine - timer 0.
 * Scan keyboard, queue pressed keys in the buffer;
 */
void tim0(void) interrupt 1 using 1
{
TH0=0x70; // reload counter
TL0=0x00;

if (g_ucDivIntCt) g_ucDivIntCt--;

   if (g_ucDivIntCt == 0)
{
if (read_kb())
{
  if (g_ucKeyCode != g_ucPrevKey)
{
        add2KeyBuf(g_ucKeyCode);
g_ucKbRptDel = KBRPTDEL;
}
else
{
if (g_ucKbRptDel) g_ucKbRptDel--;
if (g_ucKbRptDel == 0)
{
           add2KeyBuf(g_ucKeyCode);
g_ucKbRptDel = KBRPTDEL/4;
}
}
}
g_ucPrevKey = g_ucKeyCode;
g_ucDivIntCt = INTDIVCT;
}

}

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

BOOL I2C_SclBusy(void)
{
return (SCL_IN == I2C_LOW);
}

BOOL I2C_SdaBusy(void)
{
return (SDA_IN == I2C_LOW);
}

BOOL I2C_LineBusy(void)
{
return (I2C_SclBusy() || I2C_SdaBusy());
}

/*
 * Arbitration of the I2C line.
 * Returns TRUE if won, FALSE if lost.
 */
BOOL I2C_Arbitration(void)
{
return ((I2C_SdaBusy()) ? FALSE : TRUE);
}

void I2C_StretchScl(void)
{
   int nTimeOut = 2000;

while (I2C_SclBusy() && nTimeOut > 0) // clock stretching
{
  nTimeOut--; // prevent hang up
}
}

/* Send start condition with clock stretching
 * and arbitration of I2C line.
 *
 */
void I2C_Start(void)
{
   SDA_OUT = I2C_HIGH;
   SCL_OUT = I2C_HIGH;

   if (g_bStarted) // already started - do restart
   {
      _nop4_();
I2C_StretchScl();
 _nop4_();
   }
   if (I2C_Arbitration())
   {
      _nop4_();
      SDA_OUT = I2C_LOW;
      _nop4_();
      SCL_OUT = I2C_LOW;
      g_bStarted = TRUE;
}
}

void I2C_Stop(void) /*Stop condition*/
{
I2C_StretchScl();
   if (I2C_Arbitration())
   {
      SDA_OUT = I2C_LOW;
      SCL_OUT = I2C_LOW;
      _nop4_();
      SCL_OUT = I2C_HIGH;
      _nop4_();
      SDA_OUT = I2C_HIGH;
      _nop4_();
      g_bStarted = FALSE;
}
}

void I2C_WriteBit(BOOL bitval)
{
   SDA_OUT = ((bitval) ? I2C_HIGH : I2C_LOW);
   _nop4_();
SCL_OUT = I2C_HIGH;
I2C_StretchScl();
if (!bitval || I2C_Arbitration())
{
  _nop4_();
SCL_OUT = I2C_LOW;
}
}

BOOL I2C_ReadBit(void)
{
   BOOL br = FALSE;

SCL_OUT = I2C_HIGH;
_nop4_();
I2C_StretchScl();
br = ((SDA_IN == I2C_HIGH) ? TRUE : FALSE);
   _nop4_();
SCL_OUT = I2C_LOW;

return br;
}

BOOL I2C_WriteByte(BOOL sendstart,
   BOOL sendstop,
BYTE byteval)
{
   BYTE bitc = 0;
BOOL nack = FALSE;

if (sendstart)
{
  I2C_Start();
}

for (bitc=0; bitc<8; bitc++)
{
  I2C_WriteBit((byteval & 0x80) != 0);
byteval <<= 1;
}

nack = I2C_ReadBit();

if (sendstop)
{
  I2C_Stop();
}

return nack;
}

BYTE I2C_ReadByte(BOOL nack,
  BOOL sendstop)
{
   BYTE rbyte = 0;
BYTE bitc = 0;

for (bitc=0; bitc<8; bitc++)
{
  rbyte = (rbyte << 1) | I2C_ReadBit();
}

I2C_WriteBit(nack);

if (sendstop)
{
  I2C_Stop();
}

return rbyte;
}

void I2C_SendKey(BYTE kc)
{
BOOL nack = TRUE;
int nTimeOut = WAIT4ACK;

   lcd_pos(0x4b); lcd_wdat('S');
SDA_OUT = I2C_HIGH;
SCL_OUT = I2C_HIGH;
    _nop4_();
// slave receiver address = 4 
// shift left for 7 bit addr. and set LSB (R/W bit)
// LSB = 0 - WRITE
while (nack && nTimeOut > 0)
{
  nack = I2C_WriteByte(TRUE,FALSE,(4<<1)&0xfe); 
  lcd_pos(0x4f); lcd_wdat((nack) ? 'N' : 'A');
  nTimeOut--;
delay(5);
}
nTimeOut = WAIT4ACK;
nack = TRUE;
while (nack && nTimeOut > 0)
{
  nack = I2C_WriteByte(FALSE,TRUE,kc);
  lcd_pos(0x4a); lcd_wdat((nack) ? 'N' : 'A');
nTimeOut--;
delay(5);
}
}

BYTE I2C_GetKey(void)
{
   BYTE brd = 0;
BOOL nack = TRUE;
int  nTimeOut = WAIT4ACK;

   lcd_pos(0x4b); lcd_wdat('G');
SDA_OUT = I2C_HIGH;
SCL_OUT = I2C_HIGH;
   _nop4_();
// slave transmitter address  = 4
// shift left for 7 bit addr. and set LSB (R/W bit)
// LSB = 1 - READ
while (nack && nTimeOut > 0)
{
      nack = I2C_WriteByte(TRUE,FALSE,(4<<1)|0x01); 
  lcd_pos(0x4e); lcd_wdat((nack) ? 'N' : 'A');
nTimeOut--;
delay(5);
}
   brd = I2C_ReadByte(NACK, TRUE);

return brd;
}

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

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

   g_bShiftOn = g_bLock = g_bFunc =  g_bCtrl = FALSE;

   return ch;
}

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

   init_vars();
   init_ports();
   lcd_init();
init_SFR();
   delay(10);
   // wait for I2C bus to become available
   while (I2C_LineBusy())
   {
 lcdmsg(g_ucaDispMsgLineBusy, 4, 255); // Line busy message to LCD
   }
   lcd_text(0,g_ucaDispMsgPrdNameVer);
   lcd_text(0x40,g_ucaDispMsg2);
   delay(255);
   delay(255);
   while(1)
   {
 if ((key = getKeyFromBuf()) != 0)
 {
_uitoa(g_ucKeyCode,kcodehex,16);
_uitoa(g_ucKeyCode,kcodedec,10);
strcpy(g_ucaDispBuf,kcodehex);
strcat(g_ucaDispBuf,":");
strcat(g_ucaDispBuf,kcodedec);
   lcd_text(0x40,g_ucaDispBuf);
ch = convKeyCode2Char(key);
if (ch != 0)
{
   I2C_SendKey(ch);
delay(1);
   ch = I2C_GetKey();
   if (ch != 0)
   {
  rowbuf[0] = ch;
  rowbuf[1] = 0;
     lcd_text(0x0f, rowbuf);
   }
}
 }
 else
 {
    lcd_clear(0x40);
 }
   }
}

LCD display is not really needed. I used it for debugging purposes. It can be safely taken out without any changes to the firmware, the controller will still work.

Arduino test sketch code:

// Wire Slave Receiver/Sender
// by Marek Karcz 2012
// based on examples
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Receives data as an I2C/TWI slave device
// Sends 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>

unsigned char nLastRcvCh = 0;

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

void loop()
{
   delay(100);
}

unsigned char getCharFromKeyCode(unsigned char c)
{
   unsigned 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;
   unsigned char c;

   //Serial.print("receiveEvent:");
   while(Wire.available()) // loop through all
   {
      c = Wire.read(); // receive byte as an integer
      ch = getCharFromKeyCode(c);
      if (ch)
         Serial.print(ch); // print the character
   }
   nLastRcvCh = c;
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent()
{
   //Serial.println(":requestEvent");
   Wire.write(nLastRcvCh);
   nLastRcvCh = 0;
}


For the practical applications, I recommend to create 2 separate character buffers (one for I2C keyboard protocol and one for application use) for received via I2C bus characters and add them to the buffers as they come in receiveEvent method. Do not perform any more actions in the receiveEvent/requestEvent methods other than adding the characters to the buffer and sending back an echo in requestEvent as required by the keyboard driver protocol. In the main application loop, retrieve characters from the buffer designed for the application use. This will ensure lag free operation of the keyboard and will prevent locking up.

Here is an updated circuit diagram and some pictures of the populated board and pictures of the board with LCD display and keyboard attached and connected to the Arduino board for testing. To date it is the cleanest point to point soldering board I have ever made. I guess I am getting better at this, which is good. I need all the training I can get before I start assembling my MKHBC-8-R1 computer boards.







Thanks for looking at my blog. I hope you find it helpful.

Marek Karcz
05/27/2013