Läsa gamla remsor med Digital PC04 och ett Arduino kort
Postat: 2 augusti 2013, 23:55:27
Här är ett cross-over projekt: Vintage datorperiferienhet med Arduinokort.
Varför vill man läsa gamla pappersremsor? Det kom en fråga från Rhode Island Computer Museum http://www.ricomputermuseum.org (via Pontus) om vi (dvs min far och jag) på Dalby Datormuseum (http://www.datormuseum.se) råkade ha några pappersremsor till PDP-9 eftersom man höll på med att restaurera sin gamla PDP-9.
Faderen letade och kom upp med en hög remsor. Hur gör man då på säkrast möjliga sätt för att flytta dessa ett kvarts varv runt jorden, med tanke på att det kanske är de enda kvarvarande remsorna som finns? Naturligtvis kopierar man dem och mailar en fil till jänkarna.
Fast hur kopierar man dem till en fil på ett bra sätt? Remsorna är av veckad typ så den FACIT standalone läsare som fanns att tillgå var inte så lämplig. Annars är dessa FACIT läsare från Åtvidaberg nog det bästa i pappersremseläsarväg. HP använde dessa.
Det blev till att plocka fram en gammal Digital Equipment PC04 läsare ur förråden.

Denna sak från tidigt 70-tal läser veckade remsor med 300 tecken per sekund. Det är bara en hake. Man måste ha något som styr den. Den är stendum. Det fanns ett antal alternativ att tillgå. T ex att försöka få igång vår PDP-9 maskin. Inte så lockande. Den har aldrig fungerat särskilt bra. En PDP8/a skulle nog vara betydligt enklare att koppla den till och på så sätt läsa ut remsorna. Fast frakta den till Stockholm dessutom kändes krångligt och då var det dessutom mycket intressantare att prova att läsa med ett Arduino kort.
Sagt och gjort Arduino blev det!

Till saken hör alltså att PC04 (läsardelen) som sagt är stendum. Den består en stegmotor, en rad forotransistorer förstärkare för foto transistor (G918) och drivare för stegmotor (M040)

Kondensatorerna är ganska stora i denna enhet så jag använde en varia för att sakta ta mig upp till 115 VAC som var märkspänningen. Inga problem uppstod. Det gamla linjära kragget i den levererade spänningar väl inom toleranserna 40 år efter födelsedagen. Observera att i bilden ovan så är ett M040 och G918 kortet urtaget för fotografering. Alla drivkort för stansen är också urplockade. Stansen är för övrigt grunkan till vänster. Den blå stegmotorn är den som matar pappersremsan vid läsning.

G918. Jag var tvungen att trimma den stora trimpoten som styr tröskel referensen till förstärkarna eftersom en bit betedde sig lite illa.

M040. Rejäla drivtransistorer till stegmotorn.
All logik för att styra stegmotor etc sitter på kortet som sitter i datorn (i PDP8/a eller /e), på M840 som består av ett fyrtiotal TTL kretsar på ett quad kort. För PDP-8/I och PDP-8/L sitter ett kort som gör motsvarande i läsaren. Jag är osäker på hur PDP-11 interfacade till denna läsare. Fast det finns det säkert någon annan som vet.
Mer info om PC04:
https://dl.dropboxusercontent.com/u/969 ... 4-PC05.pdf
Nåväl. Jag knackade i hop denna lilla snutt Arduino-c-kod:
Och denna snutt C-kod på värddatorn:
Efter lite debuggande fungerade koden fint och resultatet blev en trevlig binärfil som gick att skicka utan risk.
Filen är ett diagnostikprogram för att testa en bandstation av typen TC02 / TU55. Den är i BIN-format, därav att högsta biten nästan alltid är satt. PDP-9 är en 18 bitars maskin. Som jag förstår det läste man ut tre tecken från pappersremsan och slog ihop dem till ett 18 bitars ord. Vissa bitar använda bara för att markera blockindelningen.
Galet.. Javisst!
Varför vill man läsa gamla pappersremsor? Det kom en fråga från Rhode Island Computer Museum http://www.ricomputermuseum.org (via Pontus) om vi (dvs min far och jag) på Dalby Datormuseum (http://www.datormuseum.se) råkade ha några pappersremsor till PDP-9 eftersom man höll på med att restaurera sin gamla PDP-9.
Faderen letade och kom upp med en hög remsor. Hur gör man då på säkrast möjliga sätt för att flytta dessa ett kvarts varv runt jorden, med tanke på att det kanske är de enda kvarvarande remsorna som finns? Naturligtvis kopierar man dem och mailar en fil till jänkarna.
Fast hur kopierar man dem till en fil på ett bra sätt? Remsorna är av veckad typ så den FACIT standalone läsare som fanns att tillgå var inte så lämplig. Annars är dessa FACIT läsare från Åtvidaberg nog det bästa i pappersremseläsarväg. HP använde dessa.
Det blev till att plocka fram en gammal Digital Equipment PC04 läsare ur förråden.

Denna sak från tidigt 70-tal läser veckade remsor med 300 tecken per sekund. Det är bara en hake. Man måste ha något som styr den. Den är stendum. Det fanns ett antal alternativ att tillgå. T ex att försöka få igång vår PDP-9 maskin. Inte så lockande. Den har aldrig fungerat särskilt bra. En PDP8/a skulle nog vara betydligt enklare att koppla den till och på så sätt läsa ut remsorna. Fast frakta den till Stockholm dessutom kändes krångligt och då var det dessutom mycket intressantare att prova att läsa med ett Arduino kort.
Sagt och gjort Arduino blev det!

Till saken hör alltså att PC04 (läsardelen) som sagt är stendum. Den består en stegmotor, en rad forotransistorer förstärkare för foto transistor (G918) och drivare för stegmotor (M040)

Kondensatorerna är ganska stora i denna enhet så jag använde en varia för att sakta ta mig upp till 115 VAC som var märkspänningen. Inga problem uppstod. Det gamla linjära kragget i den levererade spänningar väl inom toleranserna 40 år efter födelsedagen. Observera att i bilden ovan så är ett M040 och G918 kortet urtaget för fotografering. Alla drivkort för stansen är också urplockade. Stansen är för övrigt grunkan till vänster. Den blå stegmotorn är den som matar pappersremsan vid läsning.

G918. Jag var tvungen att trimma den stora trimpoten som styr tröskel referensen till förstärkarna eftersom en bit betedde sig lite illa.

M040. Rejäla drivtransistorer till stegmotorn.
All logik för att styra stegmotor etc sitter på kortet som sitter i datorn (i PDP8/a eller /e), på M840 som består av ett fyrtiotal TTL kretsar på ett quad kort. För PDP-8/I och PDP-8/L sitter ett kort som gör motsvarande i läsaren. Jag är osäker på hur PDP-11 interfacade till denna läsare. Fast det finns det säkert någon annan som vet.
Mer info om PC04:
https://dl.dropboxusercontent.com/u/969 ... 4-PC05.pdf
Nåväl. Jag knackade i hop denna lilla snutt Arduino-c-kod:
Kod: Markera allt
/*
PC04 paper tape reader program for Arduino
Mattis Lind
Ardruino Uno PINs.
Pins 10,11,12,13 is reserved for the Ethernet shield
Pin 4 is used to control the SD card
To use serial we need to use pin 4 and 10 for stepper rather than 0 and 1.
Arduino pin Use Direction PC04 Reader Connector
------------------------------------------------------------------------------
4 Stepper Motor Coil A(0) Out P
10 Stepper Motor Coil A(1) Out R
2 Stepper Motor Coil B(0) Out S
3 Feed hole detector In N
5 Stepper power enable Out U
6 Feed switch In V
7 Stepper Motor Coil B(1) Out T
8 Hole 1 detector In D
9 Hole 2 detector In E
A0 Hole 3 detector In F
A1 Hole 4 detector In H
A2 Hole 5 detector In J
A3 Hole 6 detector In K
A4 Hole 7 detector In L
A5 Hole 8 detector In M
Ground GND C
Reader 300 cps. I.e 300 steps per second. Timer driven, one interrupt each 1.667 milisecond
Use timer 1 to control the stepper motor.
There need to be a slow turn on / turn off logic as well. The M840 module start
at a 5 ms clock time and then ramps down to a 1.67 ms clock time. We will do
similar when starting and stopping.
The Power line is just to decrease the power to the motor when it is in a stopped
state. Thus to let the motor become completely lose we need to switch all stepper
signals to off.
Feed hole input generate an edge triggered interrupt. The ISR will the initiate a timer to
expire within 200 microseconds.
The timer 2 200 microseconds timeout ISR will sample the eight holes data and put them into
a buffer and signal a semafor to the mainloop that data is available.
Mainloop waits for the semaphore to be active and then reformats the data into hexadecimal
and transmits it over serial line.
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#define STEPPER_A0 4
#define STEPPER_A1 10
#define STEPPER_B0 2
#define STEPPER_B1 7
#define STEPPER_POWER 5
#define FEEDSWITCH 6
#define FEEDHOLE 3
#define HOLE_1 8
#define HOLE_2 9
#define HOLE_3 A0
#define HOLE_4 A1
#define HOLE_5 A2
#define HOLE_6 A3
#define HOLE_7 A4
#define HOLE_8 A5
#define TEST_OUT 11
#define HOLE_1_SHIFT 0
#define HOLE_2_SHIFT 1
#define HOLE_3_SHIFT 2
#define HOLE_4_SHIFT 3
#define HOLE_5_SHIFT 4
#define HOLE_6_SHIFT 5
#define HOLE_7_SHIFT 6
#define HOLE_8_SHIFT 7
#define MAX_RAMP 100
#define RAMP_FACTOR 360
#define TIMER1_VALUE 38869 // preload timer1 65536-16MHz/1/600Hz
//#define TIMER1_VALUE 2
#define TIMER2_VALUE 206 // 256 - 16000000/64*200E-6
#define STEPPER_ON 1
void setup ()
{
noInterrupts(); // disable all interrupts
Serial.begin (115200);
// Setup Stepper pins as output
pinMode(STEPPER_A0, OUTPUT);
pinMode(STEPPER_A1, OUTPUT);
pinMode(STEPPER_B0, OUTPUT);
pinMode(STEPPER_B1, OUTPUT);
pinMode(STEPPER_POWER, OUTPUT);
pinMode(TEST_OUT, OUTPUT);
// initialize timer1
pinMode(FEEDHOLE, INPUT);
digitalWrite(FEEDHOLE, HIGH); // Enable pullup resistor
/*
EIMSK |= (1 << INT1); // Enable external interrupt INT1
EICRA |= (1 << ISC11); // Trigger INT1 on rising edge
EICRA |= (1 << ISC10);
*/
attachInterrupt(1,extInt,RISING);
TCCR1A = 0;
TCCR1B = 0;
TCCR2A = 0;
TCCR2B = 0;
TCNT1 = TIMER1_VALUE;
TCCR1B |= (1 << CS10); // no prescaler
//TCCR1B |= (1 << CS11);
//TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1); // enable timer overflow interrupt
//TCCR2B |= (1 << CS10); // clk / 64 prescaler
//TCCR2B |= (1 << CS11);
TCCR2B |= (1 << CS12);
digitalWrite(STEPPER_POWER, ~STEPPER_ON);
interrupts(); // enable all interrupts
}
volatile int data;
volatile int data_flag;
volatile int overrun;
// 200 micro second timeout interrupt
ISR(TIMER2_OVF_vect) {
digitalWrite(TEST_OUT,0);
TIMSK2 &= ~(1 << TOIE2); // Single shot disable interrupt from timer 2
EIMSK |= (1 << INT1); // Re-enable external interrupt INT1
data = digitalRead(HOLE_1);
data |= (digitalRead(HOLE_2) << HOLE_2_SHIFT);
data |= (digitalRead(HOLE_3) << HOLE_8_SHIFT);
data |= (digitalRead(HOLE_4) << HOLE_7_SHIFT);
data |= (digitalRead(HOLE_5) << HOLE_6_SHIFT);
data |= (digitalRead(HOLE_6) << HOLE_5_SHIFT);
data |= (digitalRead(HOLE_7) << HOLE_4_SHIFT);
data |= (digitalRead(HOLE_8) << HOLE_3_SHIFT);
if (data_flag) {
// data_flag was not cleared by main loop. Overrun detected set overrun flag!
overrun = 1;
}
else {
data_flag = 1;
}
}
volatile int reader_run = 0;
volatile int rampup=MAX_RAMP;
// Edge triggered feed hole interrupt routine
void extInt () {
data_flag = 0;
digitalWrite(TEST_OUT,1);
if (reader_run) {
TCNT2 = TIMER2_VALUE; // Set Timer 2 to the 200 micro second timeout
TIMSK2 |= (1 << TOIE2); // now enable timer 2 interrupt
EIMSK &= ~(1 << INT1); // Disable external interrupt INT1 untill timeout has occured
// filtering out spurious interrupts
}
}
int state;
// Stepper motor interrupt routine. 300 Hz
ISR (TIMER1_OVF_vect) {
if (!digitalRead(FEEDSWITCH)||reader_run) {
digitalWrite(STEPPER_POWER, STEPPER_ON);
if (rampup>0) rampup--;
TCNT1 = TIMER1_VALUE-rampup*RAMP_FACTOR; // preload timer
//TCNT1 = TIMER1_VALUE;
switch (state) {
case 0:
digitalWrite(STEPPER_A1, 0);
digitalWrite(STEPPER_B0, 0);
digitalWrite(STEPPER_B1, 1);
digitalWrite(STEPPER_A0, 1);
state = 1;
break;
case 1:
digitalWrite(STEPPER_B1, 0);
digitalWrite(STEPPER_A1, 0);
digitalWrite(STEPPER_A0, 1);
digitalWrite(STEPPER_B0, 1);
state = 2;
break;
case 2:
digitalWrite(STEPPER_B1, 0);
digitalWrite(STEPPER_A0, 0);
digitalWrite(STEPPER_A1, 1);
digitalWrite(STEPPER_B0, 1);
state = 3;
break;
case 3:
digitalWrite(STEPPER_A0, 0);
digitalWrite(STEPPER_B0, 0);
digitalWrite(STEPPER_B1, 1);
digitalWrite(STEPPER_A1, 1);
state = 0;
break;
}
}
else {
digitalWrite(STEPPER_POWER, !STEPPER_ON);
rampup=MAX_RAMP;
digitalWrite(STEPPER_A0, 1);
digitalWrite(STEPPER_B0, 1);
digitalWrite(STEPPER_B1, 1);
digitalWrite(STEPPER_A1, 1);
state=0;
}
}
void loop () {
int ch;
if (Serial.available()) {
ch = Serial.read();
if (ch == 'R') { // If pressing R the reader will start
reader_run = 1;
}
if (ch == 'S') { // Pressing S will cause it to stop.
reader_run = 0;
}
}
if(data_flag) {
Serial.write(data);
data_flag = 0;
}
if (overrun) { // If overrun occurs it will stop reading and print error message.
overrun=0;
reader_run = 0;
Serial.println("ERROR: OVERRUN OCCURED");
}
delay(1);
}
Kod: Markera allt
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
int main (int argc, char *argv[])
{
int serfd, filefd, ret, reading=50;
unsigned char data;
char * cmd;
char b[1]; // read expects an array, so we give it a 1-byte array
int i=0;
int timeout;
int preamble=1;
struct termios toptions;
if (argc!=3) {
fprintf(stderr, "wrong number of arguments");
exit(1);
}
fprintf (stderr, "serieport: %s fil: %s\n", argv[1], argv[2]);
serfd = open(argv[1], O_RDWR | O_NONBLOCK);
if (serfd == -1) {
perror("Failed to open serial port ");
exit(1);
}
if (tcgetattr(serfd, &toptions) < 0) {
perror("Couldn't get terminal attributes");
exit(1);
}
cfsetispeed(&toptions, B115200);
cfsetospeed(&toptions, B115200);
// 8N1
toptions.c_cflag &= ~CSIZE;
toptions.c_cflag |= CS8;
toptions.c_cflag &= ~PARENB;
toptions.c_cflag &= ~CSTOPB;
// no flow control
toptions.c_cflag &= ~CRTSCTS;
toptions.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
toptions.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl
toptions.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // raw mode
toptions.c_oflag &= ~OPOST; // raw mode
toptions.c_cc[VMIN] = 0;
toptions.c_cc[VTIME] = 0;
tcsetattr(serfd, TCSANOW, &toptions);
if( tcsetattr(serfd, TCSAFLUSH, &toptions) < 0) {
perror("Couldn't set terminal attributes");
exit(1);
}
sleep(4);
tcflush(serfd, TCIOFLUSH);
sleep(4);
fprintf (stderr, "Opened serial port OK\n");
filefd = open (argv[2], O_RDWR | O_CREAT | O_TRUNC, 0666);
if (filefd==-1) {
fprintf (stderr, "Failed to open destination file: %s\n", argv[2] );
exit(0);
}
fprintf (stderr, "Opened file OK\n");
cmd = "R";
ret = write(serfd,cmd , 1);
if (ret!=1) {
fprintf(stderr, "Failed to write start reader command\n");
exit(1);
}
timeout=50;
fprintf (stderr, "Wrote start reader command command\n");
do {
int n = read(serfd, b, 1); // read a char at a time
if( n==-1) {
fprintf(stderr, "Failed to read one byte\n");
exit(1);
}
if( n==0 ) {
usleep( 1 * 1000 ); // wait 1 msec try again
timeout--;
continue;
}
timeout=50;
ret = write (filefd, b, 1);
if (ret !=1) {
fprintf (stderr, "Failed to write one byte to file\n");
exit(0);
}
if (b[0] == 0) {
reading--;
}
else {
preamble=0;
reading=50;
}
} while( (reading>0||preamble) && timeout>0 );
cmd = "S";
ret = write(serfd,cmd , 1);
if (ret!=1) {
fprintf(stderr, "Failed to write stop reader command\n");
exit(1);
}
fprintf (stderr, "Wrote stop reader command\n");
close(serfd);
close(filefd);
}
Kod: Markera allt
mattiss-mac-pro:~ mattis_lind$ hexdump test.bin
0000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
*
0000160 00 00 00 00 00 b8 9b 82 b8 80 84 b8 80 82 bd 80
0000170 80 b8 ad 84 b8 8f 82 b8 bf 82 b8 81 a4 8d bf bd
0000180 89 bf b4 85 bf be bc 89 80 b1 bf b0 9d bf bd 85
0000190 bf bd 89 bf b4 85 bf bf 9d bf bd 85 bf bd 89 bf
00001a0 b4 9d bf bd 85 bf bd 89 bf b4 87 bf be a5 bf be
00001b0 9d bf bd 85 bf bd a5 bf bf b1 bf a6 bc 82 80 bc
00001c0 80 a0 b1 bf 98 85 bf bf a5 bf bf b1 bf ba bc 80
00001d0 a0 80 80 80 b8 81 81 b1 bf b5 b8 81 8a b8 81 a4
00001e0 b3 bf b4 b8 81 81 b1 bf ba b3 bf be b1 bf d0 00
00001f0 80 80 80 bf bf bd aa 80 83 80 80 80 a4 80 80 b2
0000200 80 80 00 80 80 90 bf bf b8 b5 94 ad 80 80 90 b8
0000210 83 84 b8 83 81 bc 88 80 bc 80 a0 b8 80 a2 b8 9b
0000220 a4 b2 80 90 00 80 82 80 bf bf a7 a3 bd a1 bd 80
Galet.. Javisst!