Saturday, 25 July 2020

6522 Shift Register - Shift in under PH02 clock

Introduction

6522 Shift Register - Shift in under PH02 clock

In this article I share my findings in the configuration and use of the Shift Register in Mode 2. The approach to this, the code and methods used may appear convoluted, but I have learned so much in the process.  [6522 configuration and usage, 74165 and 7474 usage, Arduino interrupt programming].

Hopefully you will find some of this article interesting and maybe even useful.


SR Mode 2 - Shift Serial Data in under control of PH02

The shift rate is a direct function of the system clock frequency.

CB1 becomes an output which generates shift pulses for controlling external devices.

The shifting operation is triggered by either reading from or writing to the Shift Register.

Data is shifted, first into bit 0 and is then shifted into the next higher order bit of the shift register on the trailing edge of each PH02 clock pulse.

After 8 clock pulses, the shift register interrupt flag (IFR) will be set, and the output clock pulses on CB1 will stop. At this point the data is held in the Shift Register. Another read of the Shift Register is needed to retrieve the data to the CPU. Doing this read clears bit 2 of the IFR, and generates another 'shift-in' sequence, after which, bit 2 IFR is set again.


The challenge

Having work through the majority of the 6522 operating modes, the 'shift serial data in' mode posed its own challenge to me. Generating outputs from the 6522 can be monitored on an oscilloscope, or by using LEDs connected to the various ports. Generating parallel input data on Port A or B can be achieved manually. Getting the 6522 to generate a shift-in clock together with generating a 'trigger pulse' for an external device to respond with providing serial data to the 6522 was quite a challenge.


The experiment

Part of the experiment uses the idea put forward by Garth Wilson at:

http://wilsonminesco.com/6502primer/potpourri.html#22_SR_INnOUT

This uses a 74165 (parallel to serial shift register), to provide serial data and a 7474 (Dual D-Type Positive Edge Triggered Flip-Flops). Garth helpfully describes the rationale for using the 7474 to delay the data to ensure the data is shifted from the 74165 to the 6522 correctly.

The first experiment I tried was to manually configure the 74165 and wrote a short piece of code to read one byte into the 6522 Shift Register. This worked well and as expected.

The second experiment I tried was to use an Arduino board to generate 8-bit parallel data, connected to the 74165. The Arduino was used in an interrupt mode, triggered by a low pulse from the 6522, to generate an 8-bit byte to load into the 74165. After this operation, the 6522 is used to shift in the data and store the data in memory, and write out to LEDs connected to Port A.


The Block diagram:


The circuit diagram:

For clarity, the circuit diagram omits connections to power and many connections associated with the 6522 VIA and the Arduino.


The 6502 assembly code notes & process flow:

6522

CB1 used to generate the clock signal (8-bits)

CB2 used as the serial shift input from the 74165 shift register

CA2 used to enable the 74165 for loading data, and to trigger the Arduino to load the 74165 with 8-bit parallel data (by taking CA2 -ve).

CA2 defaults to HIGH, enabling the 74165 output mode.

Registers used:

ACR - Auxillary Control Register, write $08 Mode 2 =  Shift in under PH02 control

IFR- Interrupt Control Register, write $7F, Clear

IER - Interrupt Enable Register, write $84,  Set Shift Register IRQ

SR - Shift Register, read SR, generates shift clock on CB1, reads in data on CB2

PCR - Peripheral Control Register, write $0A, sets 'Pulse Output' mode - CA2 normally High

DDRA - Data Direction Register A, write $FF, set Port A as outputs

IRA - Input/Output Register A,  write $xx, sets data on Port A



Process flow

* Set up DDRA, IFR, IER, ACR, PCR

* Use the PCR to generate -ve pulse on CA2, enables the 74165 for loading, 'triggers' the Arduino to output data to the 74165 (loads the data into 74165)

* CA2 returning High enables serial o/p from 74165

* Write $00 to the 6522 SR, clears the SR and an 8-bit clock stream is generated on CB1, when complete the IFR bit 2 is set, the /IRQ line goes Low. The clock stream, applied to the 74165, clocks the parallel data on the 74165 to the 6522 Shift Register.

* To get the data from the SR to the CPU, read the SR. This resets the IFR and /IRQ goes High. Reading the SR also generates another 8 clock pulses. When complete, the IFR is set and /IRQ goes Low.

* A clear of the IFR is needed, this also sets /IRQ High.

* Store the read data value in memory and write data to port A outputs.

* Return to monitor [monitor re-entry address = $E070]



Oscilloscope traces:


Top trace: CA2 line drops -ve to trigger Arduino and enable 74165 for loading

Middle trace: Arduino bit-7 indicates when the Arduino data byte is complete for 74165 loading

Lower trace: CB1 Shift Clock pulses from 6522



Top trace: CA2 - after 74165 data loaded, CA2 returns High to allow 74165 serial output

Middle trace: CB1 Shift Clock pulses from 6522

Lower trace: CB2 Serial data from 74165/7474 to 6522 Shift Register In (data value $FA).

The second set of 8 clock pulses is the 6522 response from executing a read of the 6522 SR.




Zoomed in copy of previous display:

Top trace: CA2 - after 74165 data loaded, CA2 returns High to allow 74165 serial output

Middle trace: CB1 Shift Clock pulses from 6522

Lower trace: CB2 Serial data from 74165/7474 to 6522 Shift Register In (data value $FA).



6502 Serial Terminal Screen display (using 'screen' on RaspberryPi)

Running the 6502 code at $1000 runs through the 6502 code below once. Running this routine 4 times shifts in the 3 data byte values held by the Arduino ($FA, $FB and $FC). These values are stored in zeropage RAM at address $0016. The values are overwritten each time the 6502 routine is run.


Conclusions:

This experiment (for me) was quite challenging, and I learned so much doing the research, writing the 6502 and Arduino code and interpreting the oscilloscope displays to work out the various signal timings to ensure all the different elements worked together. Although unlikely to be a useable and practical way of getting data into the 6502 RAM, I envisage that with minor modification it would be possible to load large quantities of data from the Arduino to the 6502 computer.

The 6502 assembly code:

.target "6502"
.code
* = $1000
         ; zeropage locations
inb =  $0016  ; holds byte value received
         ; 6522 VIA settings
IRA = $D201  ; address of Input Port A
DDRA = $D203  ; data direction register for Port A
ACR = $D20B   ; Auxillary Ctrl Reg
IFR = $D20D  ; Interrupt Ctrl Reg
IER = $D20E  ; Inteerupt Enable Reg
T2 = $D208  ; T2 Low Timer/Counter [only needed if using T2 control]
SR = $D20A  ; Shift Register
PCR = $D20C  ; Peripheral Ctrl Reg
initvia:
lda #%11111111 ;
sta DDRA         ; set VIA Port A all outputs
sta IFR ; Clear IFR
lda #%10000100 ; enable Shift Register IRQ
sta IER ;
lda #%00001000 ; $08
sta ACR ; Shift In under PH02 Clock Ctrl
lda #%00001110 ; set CA2 high default
sta PCR ; sets SH line High, 74165 ready to shift

entry:
lda #%00001100 ; set CA2 Low
sta PCR ; a low on CA2 connected to the /LD on the 74165 and
                                        ; the Arduino UNO [falling IRQ] will trigger an ISR on 
                                        ; the UNO to send 8-bit byte parallel data to the 
                                        ; attached 74165
                                        ; a delay is needed of approx 40uSecs between setting
                                        ; CA2 LOW and returning it HIGH to allow time for the
                                        ; Arduino to load the data
                                        ; delay here
ldx #$F0         ; initialize xreg, $F0 = delay ~ 102uS
                                        ; [more than needed]
delay:
inx         ; increment
bne delay         ; loop if not zero
lda #%00001110 ; set CA2 high
sta PCR ; return SH/LD line High, 74165 ready to shift
lda #$00         ;
sta SR ; clear SR, this initiates clock pulses (x8)
loop1:
lda IFR ; check Interrupt Flag Register
and #$04         ; has the SR completed 8 shifts?
beq loop1         ; no, keep checking
        ; yes, IFR bit 2 set [and = 1]
        ; serial data read in to the SR on CB2, /IRQ = Low
lda SR ; read the SR, transfers data to the CPU
        ; IFR bit 2 is cleared, another 'shift-in' sequence occurs
        ; and sets IFR & /IRQ when sequence ends
nop         ; allow time for read & sequence to end
nop         ; otherwise the clear IFR /IRQ (below) is missed
nop         ;
sta inb ; store data byte received to zeropage store
sta IRA ; store byte value to port A LEDs
        ; using the PCR to control CA2 directly means that
                                        ; writing to IRA doesn't generate a -ve pulse on CA2,
                                        ; avoiding loading data to 74165
lda #%11111111 ; $FF
sta IFR ; Clear IFR (clear the interrupt condition/flag)
nomore:
jmp $E070 ; Go back to the monitor.


 Arduino UNO code:

// Interrupt routine
// Normal operation is just a void loop
// -ve pulse generated and applied to pin D2 - the CA2 line on the 6502
// interrupt occurs when -ve 'falling' level detected
// Interrupt Service Routine (ISR) is executed and when completed,
// returns control to the 'loop'
// On the Arduino UNO, pins 2 and 3 are capable of generating interrupts
// and they correspond to interrupt vectors 0 and 1 respectively.

const int pulsePin = 2; // -ve pulse here (6502 CA2 line)
int count = 0;         // data read count from array counter

// UNO data pins (5 to 12) connected to 74165 parallel data input pins
#define DATA_D0 5
#define DATA_D7 12

// put data here to 'shift' into attached 74165 shift register
byte digitsHEX[] = {0xFA, 0xFB, 0xFC};

void setup() {
 // pulse pin as an input
 pinMode(pulsePin, INPUT);
 // configure 8 data pins as o/ps
 for (int pin = DATA_D0; pin <= DATA_D7; pin +=1){
    pinMode(pin, OUTPUT);
 }
 // attach interrupt to the ISR vector
 attachInterrupt(digitalPinToInterrupt(pulsePin), pin_ISR1, FALLING);
}

void loop() {
  // nothing happening
}

void pin_ISR1(){
  // Interrupt Service Routine - get data from array, 
  // write single 8-bit byte value out
  writeData(digitsHEX[count]);
  count +=1;
  // test to ensure data array read doesn't go out of range
  if (count > 2) {
    count = 0;
  }
}

void writeData(byte data){
  // write out 8-bit data from array on digital pins 5 to 12 
  for (int pin = DATA_D0; pin <= DATA_D7; pin +=1){
    digitalWrite(pin, data & 1);
    data = data >> 1;
  }
}


1 comment:

  1. Forgive me if I missed a detail here but I actually just implemented SPI mode 3 using the 65C22 shift register, driving a DAC in my case, and I have it working currently without enabling the interrupt for the shift register, and thus, saving the clock cycles to push/pop the PC and so on and jump to and from the interrupt handler. I also replaced the polling loop for IFR with some nops :P To my delight, and surprise, it actually did get slightly faster -- around 8 microseconds in my case, with a system clock of 12 MHz.
    Here's the transfer routine I have currently: https://gist.github.com/sci4me/3b0261ee656bbb04cf8eb8fb8873a9ef

    ReplyDelete