This wiki has been archived and made read-only.
For up-to-date information about TkkrLab and it's projects please visit our main website at tkkrlab.nl.

Commodore Serial Arduino

From

Jump to: navigation, search
/*
 * Brother HR-5C thermal matrix printer driver for Arduino Diecimila
 *
 *                    Version 1.0, 11/05/2009
 *                    tapani (at) rantakokko.net
 *               (c) Copyright 2009 Tapani Rantakokko
 *
 *
 * Commodore 64 microcomputer used to be very popular in its own time.
 * Many kinds of peripheral devices were made for it, including printers.
 * Printers were connected to C-64's non-standard serial port, just like
 * more common floppy disk drives (remember the famous 1541?).
 *
 * This module provides an API for using Commodore 64's printers from
 * Arduino. It has been developed and tested with Brother HR-5C thermal
 * matrix printer, but should work (perhaps after some adjustments) with
 * other Commodore 64 compatible printers too.
 *
 * Note that you cannot simply connect Arduino's digital I/O pins to the
 * printer's serial port, you have to use a buffer chip similar to what
 * was used in the orignal devices. You can get one from a broken C-64
 * hardware (that's what I did), or buy a compatible one. The original
 * was SN7406N, but SN7407 should work fine as well (not tested, though).
 * Note that this chip also inverts all (output) signals, which is handled
 * in the code.
 *
 * Happy hacking!
 *
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
// CONSTANTS
 
// Mapping of CBM-64's serial port lines to Arduino's digital I/O pins.
// With Brother HR-5C, we need to be able to read only the data line.
int CBM_CLK_IN    = 8;      // Arduino's digital I/O pin 9.
int CBM_ATN_OUT    = 9;      // Arduino's digital I/O pin 9.
int CBM_RESET_OUT  = 10;     // Arduino's digital I/O pin 10.
int CBM_CLK_OUT    = 11;     // Arduino's digital I/O pin 11.
int CBM_DATA_IN    = 12;     // Arduino's digital I/O pin 12.
int CBM_DATA_OUT   = 13;     // Arduino's digital I/O pin 13 (+LED).
 
// For testing:
int TEST_MODE = -1;
const int DATA_MAX_LENGTH = 10;
char data[DATA_MAX_LENGTH];
int index = -1;
bool received_eoi = false;
// FUNCTIONS
 
// Resets CBM peripheral device.
void cbm_reset_device()
{
  digitalWrite(CBM_RESET_OUT, HIGH);
  delay(100); // 100 ms
  digitalWrite(CBM_RESET_OUT, LOW);
  delay(3000); // 3 seconds
}
 
// Initializes CBM peripheral device.
void cbm_init_device()
{
  // Set I/O lines to idle/inactive.
  // CBM serial lines are active low, but we have an inverter chip!
  digitalWrite(CBM_RESET_OUT, LOW);
  digitalWrite(CBM_ATN_OUT,   LOW);
  digitalWrite(CBM_CLK_OUT,   LOW);
  digitalWrite(CBM_DATA_OUT,  LOW);
 
  // Reset device.
  cbm_reset_device();
}
// Returns 1 if device is ready, and 0 if device is busy.
int cbm_clock_read()
{
  // Note: DATA in is *not* inverted by HW.
  return digitalRead(CBM_CLK_IN);
}
// Returns 1 if device is ready, and 0 if device is busy.
int cbm_device_ready()
{
  // Note: DATA in is *not* inverted by HW.
  return digitalRead(CBM_DATA_IN);
}
 
unsigned char cbm_serial_read_bit(){
  unsigned char data;
  while (cbm_clock_read())
		delayMicroseconds(10);
  data = digitalRead(CBM_DATA_IN);
  while (!cbm_clock_read())
		delayMicroseconds(10);
}
 
// Writes one bit (data byte's LSB) to CBM serial line.
void cbm_serial_write_bit(unsigned char data)
{
  digitalWrite(CBM_DATA_OUT, (~data) & 0x01);
  delayMicroseconds(20);
  digitalWrite(CBM_CLK_OUT, HIGH);
  delayMicroseconds(20);
  digitalWrite(CBM_CLK_OUT, LOW);
  delayMicroseconds(20);
}
 
unsigned char cbm_serial_read_byte()
{
  unsigned char data = 0;
  int bit = 0;
  for (bit = 0; bit < 8; bit++)
    data |= (unsigned char)((cbm_serial_read_bit() & 0x01) << bit); 
  return data;
}
 
// Writes one byte to CBM serial line (LSB first, MSB last).
void cbm_serial_write_byte(unsigned char data)
{
  int bit = 0;
  for (bit = 0; bit < 8; bit++)
    cbm_serial_write_bit((unsigned char)((data >> bit) & 0x01)); 
}
 
unsigned char cbm_serial_read_frame()
{
   bool eoi = true;
   while (cbm_clock_read())
	delayMicroseconds(10);//WAIT FOR RTS
   digitalWrite(CBM_DATA_OUT, LOW);
   for (int i = 0; i < 20;i++)
	if (!cbm_clock_read())
		delayMicroseconds(10);
	else
		eoi=false;
   if (eoi){
	digitalWrite(CBM_DATA_OUT,HIGH);
	delayMicroseconds(60);
	digitalWrite(CBM_DATA_OUT,LOW);
   }
   unsigned char data = cbm_serial_read_byte();
   while (cbm_clock_read())
	delayMicroseconds(10);//WAIT FOR RTS
   digitalWrite(CBM_DATA_OUT, HIGH);
   received_eoi = eoi;
   return data;
}
 
// Writes one data frame to CBM serial line.
void cbm_serial_write_frame(unsigned char data, int last_frame)
{
  // Begin new frame.
  digitalWrite(CBM_CLK_OUT, LOW); //RTS
 
  // Device sets DATA high when ready to receive data (not busy).
  while(!cbm_device_ready()) delayMicroseconds(10);
 
  // Last frame recognition
  if (last_frame == 1)
  {
    // TODO is this even needed?
 
    delayMicroseconds(250); // <250us, this IS the last byte
    // ... wait until printer sets data low...
    while(cbm_device_ready()) {
      delayMicroseconds(10);
    }//EOI ACK
    // ... wait until printer sets data high...
    while(!cbm_device_ready()) {
      delayMicroseconds(10);
    }
    delayMicroseconds(20);
  }
  else
  {
    delayMicroseconds(40); // <200us, this is not the last byte
  }
 
  // Write the actual data byte.
  cbm_serial_write_byte(data);
 
  // End frame.
  digitalWrite(CBM_CLK_OUT, HIGH);
  digitalWrite(CBM_DATA_OUT, LOW); // DATA high (inverted)
  delayMicroseconds(20);
 
  // Device sets DATA low when it begins processing the data (busy).
  while(cbm_device_ready()) delayMicroseconds(10);
 
  // Delay between frames.
  delayMicroseconds(100);
}
 
void cbm_serial_listen(int device, int secondary)
{
	// Header begins (device acks by setting DATA low).
  	digitalWrite(CBM_ATN_OUT, HIGH);
  	delayMicroseconds(2000); // 2 ms
  	digitalWrite(CBM_CLK_OUT, HIGH);
  	delayMicroseconds(2000); // 2 ms
	while(!cbm_device_ready())
		delayMicroseconds(10);
	cbm_serial_write_frame((unsigned char)(0x20 + device), 0);//DEVICE LISTEN
	cbm_serial_write_frame((unsigned char)(0x60 + secondary), 0);//STREAM OPEN
	// Header ends.
	delayMicroseconds(20);
	digitalWrite(CBM_ATN_OUT, LOW); // ATN inactive
}
 
void cbm_serial_unlisten()
{
	// Header begins (device acks by setting DATA low).
  	digitalWrite(CBM_ATN_OUT, HIGH);
  	delayMicroseconds(2000); // 2 ms
  	digitalWrite(CBM_CLK_OUT, HIGH);
  	delayMicroseconds(2000); // 2 ms
	while(!cbm_device_ready())
		delayMicroseconds(10);
	cbm_serial_write_frame((unsigned char)0x3F, 0);//DEVICE UNLISTEN
	// Header ends.
	delayMicroseconds(20);
	digitalWrite(CBM_ATN_OUT, LOW); // ATN inactive
}
 
void cbm_serial_untalk()
{
	// Header begins (device acks by setting DATA low).
  	digitalWrite(CBM_ATN_OUT, HIGH);
  	delayMicroseconds(2000); // 2 ms
  	digitalWrite(CBM_CLK_OUT, HIGH);
  	delayMicroseconds(2000); // 2 ms
	while(!cbm_device_ready())
		delayMicroseconds(10);
	cbm_serial_write_frame((unsigned char)0x5F, 0);
	// Header ends.
	delayMicroseconds(20);
	digitalWrite(CBM_ATN_OUT, LOW); // ATN inactive
}
 
void cbm_serial_talk(int device, int secondary)
{
	// Header begins (device acks by setting DATA low).
  	digitalWrite(CBM_ATN_OUT, HIGH);
  	delayMicroseconds(2000); // 2 ms
  	digitalWrite(CBM_CLK_OUT, HIGH);
  	delayMicroseconds(2000); // 2 ms
	while(!cbm_device_ready())
		delayMicroseconds(10);
	cbm_serial_write_frame((unsigned char)(0x20 + device), 0);//DEVICE LISTEN
	cbm_serial_write_frame((unsigned char)(0x60 + secondary), 0);//STREAM OPEN
	// Header ends.
	delayMicroseconds(20);
	digitalWrite(CBM_DATA_OUT, HIGH); //DATA SET
	digitalWrite(CBM_ATN_OUT, LOW); // ATN inactive
	digitalWrite(CBM_CLK_OUT, LOW);
	while (!cbm_clock_read())
		delayMicroseconds(10);
}
 
// Switches case for alpha values (they must be reversed).
int cbm_switch_case(char data)
{
  if (data >= 0x41 && data <= 0x5A)
  { // Convert from lower to upper (a->A)
    return data + 32;
  }
  else if (data >= 0x61 && data <= 0x7A)
  { // Convert from upper to lower (A->a)
    return data - 32;
  }
  else
  { // Not an alpha value, no conversion needed
    return data;
  }
}
 
// Prints a line of text with a CBM-64 compatible printer.
void cbm_serial_println(char* text)
{
  int i=0;
  while(text[i] != '\0')
  {
    cbm_serial_write_frame(cbm_switch_case(text[i]), 0);
    i++;
  }
  cbm_serial_write_frame(13, 1); // new line
}
// Prints a line of text with a CBM-64 compatible printer.
void cbm_serial_write_string(char* text)
{
  int i=0;
  while(text[i] != '\0')
  {
    if (text[++i] == 0)
    	cbm_serial_write_frame(cbm_switch_case(text[i]), 1);
    else
	cbm_serial_write_frame(cbm_switch_case(text[i]), 0);
  }
}
 
void cbm_serial_open(int device, int secondary)
{
	// Header begins (device acks by setting DATA low).
  	digitalWrite(CBM_ATN_OUT, HIGH);
  	delayMicroseconds(2000); // 2 ms
  	digitalWrite(CBM_CLK_OUT, HIGH);
  	delayMicroseconds(2000); // 2 ms
	while(!cbm_device_ready())
		delayMicroseconds(10);
	cbm_serial_write_frame((unsigned char)(0x20 + device), 0);//DEVICE LISTEN
	cbm_serial_write_frame((unsigned char)(0xF0 + device), 0);//STREAM OPEN
	// Header ends.
	delayMicroseconds(20);
	digitalWrite(CBM_ATN_OUT, LOW); // ATN inactive
}
 
void cbm_serial_close(int device, int secondary)
{
	// Header begins (device acks by setting DATA low).
  	digitalWrite(CBM_ATN_OUT, HIGH);
  	delayMicroseconds(2000); // 2 ms
  	digitalWrite(CBM_CLK_OUT, HIGH);
  	delayMicroseconds(2000); // 2 ms
	while(!cbm_device_ready())
		delayMicroseconds(10);
	cbm_serial_write_frame((unsigned char)(0x20 + device), 0);
	cbm_serial_write_frame((unsigned char)(0xE0 + secondary), 0);
	// Header ends.
	delayMicroseconds(20);
	digitalWrite(CBM_ATN_OUT, LOW); // ATN inactive
	cbm_serial_unlisten();
}
 
void cbm_floppy_open(int device, int secondary, char *filename)
{
	cbm_serial_open(device, secondary);
	cbm_serial_write_string(filename);
	cbm_serial_unlisten();
}
 
// For testing, print menu to computer.
void test_menu()
{
  Serial.println("Select floppy test mode:");
  Serial.println(" (1) Dump dir");
  Serial.println(" (2) Print user message");
  Serial.print("Your selection: > ");
}
 
// Arduino setup function is run once when the sketch starts.
void setup()
{
  // Set pins to either input or output.
  pinMode(CBM_RESET_OUT, OUTPUT);
  pinMode(CBM_ATN_OUT,   OUTPUT);
  pinMode(CBM_CLK_OUT,   OUTPUT);
  pinMode(CBM_DATA_OUT,  OUTPUT);
  pinMode(CBM_DATA_IN,   INPUT);
  pinMode(CBM_CLK_IN,   INPUT);
 
  // Initialize printer.
  cbm_init_device();
 
  // Begin serial communication with PC.
  Serial.begin(9600);
 
  // Print test menu.
  test_menu();
}
 
// Arduino loop function is run over and over again, forever.
void loop()
{
  char val;
 
  // Check if data has been sent from the computer.
  if (Serial.available())
  {
    // Read the most recent byte (which will be from 0 to 255).
    val = Serial.read();
    Serial.println(val);
 
    if (TEST_MODE <= 0)
      { // Set test mode
      if (val == '1') TEST_MODE = 1;
      else if (val == '2') TEST_MODE = 2;
      }
 
    if (TEST_MODE == 1)
    { // If self test mode selected, do self test now.
      Serial.println("Dumping...");
      Serial.print("Opening...");
      cbm_floppy_open(8,0,"$");     
      Serial.println("OK.");
      Serial.println("Directory:");
      cbm_serial_talk(8,0);
      for(;;){
	  Serial.print((char)cbm_switch_case((char)cbm_serial_read_frame()));
	  if (received_eoi)
		break;
      }
      cbm_serial_untalk();
      cbm_serial_close(8,0);
      TEST_MODE = 0;
      test_menu();
    }
    else if (TEST_MODE == 2)
    {
      if (index == -1)
      { // If user message test mode selected, ask it now.
        Serial.println("Type text to be printed (# ends):");
        index++;
      }
      else
      {
        if ( val == '#' )
        {
          data[index] = '\0';
          //cbm_println(data);
          Serial.println("Done.");
          index = -1;
          TEST_MODE = 0;
          test_menu();
        }
        else if (index < DATA_MAX_LENGTH)
        {
          data[index] = val;
          index++;
          if (index + 1 >= DATA_MAX_LENGTH)
          {
            data[index] = '\0';
            //cbm_print(data, DATA_MAX_LENGTH);
            index = 0;
          }
        }
      }
    }
  }
}