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