-
Interfacing old Commodore 64 printers with Arduino
Posted on June 2nd, 2009 4 comments
This article explains how to use an old Commodore 64 printer in Arduino projects. A simple schematic for an adapter between the printer and an Arduino is presented, as well as a software driver for easy printing from Arduino code. Arduino is a popular open-source electronics prototyping platform based on Atmel Atmega 168 microcontroller. Commodore 64 is a classic microcomputer from the 80’s that still has many fans. The printer that is here used with Arduino is Brother HR-5C thermal matrix printer, but other CBM-64 compatible printers should work as well.
Motivation
I still remember that long-awaited day when my dad brought home a Commodore 64 microcomputer! That device became to be my first computer, and I spent countless hours playing with it in my youth – mostly gaming, but also a little bit of programming with BASIC. I still have it, although it doesn’t work anymore… We used to have many peripheral devices, too: the famous 1541 floppy disk drive, 1530 tape player, joysticks, paddles, cartridges, and even a printer!
In fact, that printer (Brother HR-5C) was a wonderful little thing: battery or AC powered, printing on thermal paper and other paper types too with a ribbon cassette, supporting full CBM-64 character set and custom characters. The printer is still working just fine, and I happen to have a lot of thermal transfer paper in my storage (unused fax machine paper). However, since my CBM-64 is dead, I cannot actually use the printer
I’ve been thinking about building an adapter so that I could connect the printer to modern PCs. I just couldn’t find any proper reason why would I want to print with that device instead of my much better quality and faster laser printer…
Until, I got an idea…
When playing with the Arduino prototyping platform, I though that it would be easy to use Arduino for building such an adapter between the printer and a PC. Then I suddenly realized that in fact it would be much more useful and fun to leave the PC out of the equation, and print directly from Arduino! Arduino does not have any kind of user interface, so debug printing on paper would be nice, and many data logging projects would benefit from printing capabilities as well.
This finally gave me the motivation to actually do something about it. I decided to build an interface between the Commodore 64 printer and Arduino, plus write a simple software printer driver so that the device can be easily used from Arduino code.
Since many more or less weird Arduino and Commodore 64 projects are popular in the Internet, I thought I should share my project as well – although, I realize that there are hardly many working Commodore 64 printers around anymore. If you do have one, consider yourself lucky.
Goal
The goal of the project is to be able to easily attach a Commodore 64 compatible printer to an existing or new Arduino project, both in terms of hardware and software. This can be achieved with a rather simple adapter device that is used for connecting the devices physically together, and a piece of software with initialization and printing functions.
When finished, the setup can be used for many kinds of printing tasks from Arduino with minimal programming. Here are some use case ideas:
- Debug printing during Arduino project development
- Continuous data logging for example from electricity consumption or temperature measurements
- Usage reports from home made devices
- Graphical representations of measured sensor data
- Long print-outs up to several meters (“Happy Birthday!”) with thermal transfer paper
- Simple customer feedback system in concert with a mobile phone (i.e. print all incoming SMS on paper)
You can probably figure out many more.
Things you’ll need
- Commodore 64 compatible printer and its power source
- Arduino Diecimila or other similar Arduino platform
- Small piece of prototyping breadboard or stripboard
- SN7406 or compatible hex inverter buffer/driver chip (e.g. from a broken Commodore 64, or a new SN7407 from shop)
- 6 pin DIN male – male cable (e.g. a Commodore 64 serial cable)
- Female 6 pin DIN connector at 90 degree angle for the breadboard/stripboard (e.g. from a broken Commodore 64)
- Some short wires for internal connections of the adapter device, and longer ones for attaching the adapter to Arduino
- A small plastic case (OPTIONAL but recommended)
- A small capacitor for filtering VCC for SN7406 (e.g. 100 nF) (OPTIONAL)
- A 5mm red/green LED and a 250 Ohm resistor for making a power-on indicator (OPTIONAL)
As the project requires building a simple electronics device, you also need tools for soldering etc.
DISCLAIMER: In case you decide to build the adapter, do it at your own risk! I will not take any responsibility of possible damages or losses due to incorrect construction, errors in the instructions, or in the design. Don’t do it unless you know what you’re doing.
With that said, we’re working here with low voltages and small currents, so probably the worst you can do is to fry your Arduino or the printer.
Design
The basic idea is to connect the printer to Arduino, which then pretends to be a Commodore 64 microcomputer and commands the printer to print something.
The printer communicates with the host via the CBM-64 serial line. It is bi-directional, but uses only a single data line that is read and written by host and terminal in turns, controlled by attention (ATN) and clock (CLK) lines. CBM-64 serial port also uses reset (RST) and serial request (SRQ) lines, which are not compulsory in printing.
Lines are zero active, and both the host and the terminal use hex inverter/driver chips. The printer already has such a chip, but Arduino doesn’t – therefore you need to either take one from a real Commdore 64 (I did this), or buy a compatible one. Note that I am not 100% sure that you cannot make it work without the chip, but at least one 1541 to PC adapter developer reported that communication from microcontroller to CBM-64 serial line really does require it. Naturally this inverter/driver IC chip also needs power (VCC) and ground (GND) signals, which you can get directly form Arduino’s VCC and GND pins.
The printer does not control any other line than the data line (DAT), therefore you need to use in total 5 general purpose I/O pins from Arduino: data in, data out, attention out, clock out, and reset out. If you don’t have 5 I/O lines available, you might try to survive without the reset line, or to connect that to Arduino’s reset line/switch. Add VCC and GND, and you have 6-7 wires to be connected to Arduino, and on the other end a 6 pin DIN connector to be connected to the printer. The interverter/driver chip does its job in between. This means that the adapter can be very small, and it can be easily built e.g. on a prototyping breadboard or a strip board. I used the former during development, then moved to latter when I got it working.
The difficult part of the project is to get the timings of the serial lines controlled correctly in Arduino code. Fortunately I have the manual for the printer, and unlike many today’s user’s manuals, this one has got some real information in it: serial interface schematics and timing charts! In addition, 1541 to PC adapter projects documented in the Internet are helpful. Yet I must admit that without an oscilloscope I wouldn’t have easily figured out how to really get it working… You shouldn’t need it though, as the printer software driver for Arduino is now written and tested to work.
Adapter device
The image below represents a simple schematics for building the adapter. Remember to connect VCC and GND to the interverter/buffer chip (not shown in the image). I have also used a capacitor for filtering VCC of the inverter/driver chip (not shown in the image), as well as a power ON indicator with a 5mm LED and a 250 Ohm resistor, but it will work without these.
Here are some images of the adapter construction:
Software
In case you are familiar with C programming language, developing for Arduino is easy. Basically there are two important functions, one for initial setup after power on, and another that will be looped as long as the device gets power. The printer driver software for Arduino adds more functions that can be called from the initialization and loop functions. To enable easy testing, the driver can be controlled from PC via USB serial port connection. When connected, it prints a simple menu to the serial port, including tests for printing the basic character set and custom text.
The source code for the printer driver is listed below. It is GPL licensed, so feel free to use it in your own (free) projects!
/* * 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_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). // Brother HR-5C printer modes (graphic|business, different char set). int CMB_PRN_MODE_GRAPHIC = 0; int CBM_PRN_MODE_BUSINESS = 7; // CBM printer device addresses. int CBM_PRN_ADDR = 4; // Typically CBM printer is device 4 or 5. int CBM_PRN_ADDR_2 = CBM_PRN_MODE_BUSINESS; // 2nd addr = printer mode. // For testing: int TEST_MODE = -1; const int DATA_MAX_LENGTH = 10; char data[DATA_MAX_LENGTH]; int index = -1; // 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_device_ready() { // Note: DATA in is *not* inverted by HW. return digitalRead(CBM_DATA_IN); } // 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); } // Writes one byte to CBM serial line (LSB first, MSB last). void cbm_serial_write_byte(unsigned char data) { cbm_serial_write_bit( data & 0x01); // 1st bit (LSB) cbm_serial_write_bit((data >> 1) & 0x01); // 2nd bit cbm_serial_write_bit((data >> 2) & 0x01); // 3rd bit cbm_serial_write_bit((data >> 3) & 0x01); // 4th bit cbm_serial_write_bit((data >> 4) & 0x01); // 5th bit cbm_serial_write_bit((data >> 5) & 0x01); // 6th bit cbm_serial_write_bit((data >> 6) & 0x01); // 7th bit cbm_serial_write_bit((data >> 7) & 0x01); // 8th bit (MSB) } // 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); // 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); } // ... 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); } // Begins communication sequence on CBM serial line. void cbm_serial_begin() { // 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 // Write listener address. cbm_serial_write_frame((unsigned char)(0x20 + CBM_PRN_ADDR), 0); // Write secondary address. cbm_serial_write_frame((unsigned char)(0x60 + CBM_PRN_ADDR_2), 0); // Header ends. delayMicroseconds(20); digitalWrite(CBM_ATN_OUT, LOW); // ATN inactive } /* // Ends communication sequence on CBM serial line. void cbm_serial_end() { //TODO is this even needed? // End communication digitalWrite(CBM_ATN_OUT, HIGH); delayMicroseconds(20); // Write unlisten command. cbm_serial_write_frame((char)(0x3F), 0); digitalWrite(CBM_ATN_OUT, LOW); delayMicroseconds(100); digitalWrite(CBM_CLK_OUT, LOW); } */ // 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 text with a CBM-64 compatible printer. void cbm_print(char* text, int length) { for (int i=0; i<length; i++) { cbm_serial_write_frame(cbm_switch_case(text[i]), 0); } } // Prints a line of text with a CBM-64 compatible printer. void cbm_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, 0); // new line } // Prints a self test text. // TODO: When your printer is working, comment this out to save memory. void cbm_print_self_test() { char buf[64] = "Hello World, here are the chars supported by the printer:\0"; cbm_println(buf); for (int i=0; i<16; i++) { cbm_serial_write_frame(i+32, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(i+48, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(i+64, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(i+80, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(i+96, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(i+112, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(i+160, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(i+176, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(i+192, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(i+200, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(i+224, 0); cbm_serial_write_frame(32, 0); cbm_serial_write_frame(i+240, 0); cbm_serial_write_frame(13, 0); } } // For testing, print menu to computer. void test_menu() { Serial.println("Select printer test mode:"); Serial.println(" (1) Print self test (charset)"); 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); // Initialize printer. cbm_init_device(); // Prepare for printing. cbm_serial_begin(); // 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("Now printing..."); cbm_print_self_test(); Serial.println("Done."); 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; } } } } } }Links
- Arduino: http://www.arduino.cc/
- Commodore 64 in Wikipedia: http://en.wikipedia.org/wiki/Commodore_64
- Basic information about Commodore peripheral ports: http://www.devili.iki.fi/Computers/Commodore/articles/Peripheral_Ports/
- Commodore serial port timing charts: http://www.classic-games.com/commodore64/serial.html
- SN7406N datasheet: http://www.alldatasheet.com/datasheet-pdf/pdf/27354/TI/SN7406N.html
- Commodore 1541 – PC adapter: http://www.bitcity.de/theory.htm
- Commodore 64 printer – PC adapter and driver: http://www.textfiles.com/computers/c64topc.txt
- GPL license: http://www.gnu.org/licenses/
4 responses to “Interfacing old Commodore 64 printers with Arduino”

-
Hi, Congratulations to the site owner for this marvelous work you’ve done. It has lots of useful and interesting data.
-
Martijn Mellema December 2nd, 2009 at 22:42
Hi, i’m using your system to get it working on a Star NL-10 printer.. Thank you for all your information!
With regards,
Martijn (The Netherlands)
-
Martijn Mellema December 4th, 2009 at 13:45
Hi,
i have a question about the graphics and business charcter set. In my Star NL-10 printer the standaard control codes are: CHR$(145) and business CHR$(17). And it says something about The operating mode
” your printer has twho basic modes of operation: the Commodore mode and the ASCII mode. Normally you will use the Commodore mode, wich is set automatically whn you turn on the pinter by setting the DIP switch 1-5 on. The ASCII operating mode — wich has no functional relation to ASCII codes — provides certain other functions. Operating mode commands:
function
Select ASCII operating mode: CHR$(27);CHR$(93);CHR$(49)
Select Commodore operating mode: CHR$(27);CHR$(93);CHR$(48)”
Do i have to change anything in the script? and to i have to change the DIP switches?
mail me: i_kill_bombs (AT) hotm ail . co m
Leave a reply














Grearjumdemia June 5th, 2009 at 20:03