artemisamsx / artemisa

Schematics, PCB designs and HDL simulations to build a 8-bits MSX computer
Other
62 stars 11 forks source link

Research how to provide a PS2 keyboard interface based on 74670 #144

Closed apoloval closed 4 years ago

apoloval commented 4 years ago

Model 201 would have the PS2 keyboard integrated in the board. 211 might have an integrated keyboard.

The array of 11 74595s seems not to be the best option in this particular case.

Looking around, I realised the existence of register file ICs: integrated circuits that contain several addressable registers that typically can be accessed with dual ports.

The best choice of register files seems to be the 74HC670. This is a 4x 4-bit registers, with separated ports for reading and writing.

If 74HC670 is used, we will need a pair of ICs to cover 4 full keyboard rows. To cover at least 11 rows, we need 6 ICs. This is almost half of those used in the original design of the PS2 Keyboard Adapter.

We could investigate a little more for other parts, but I could not find something better except things not in production today. The DS1690 is a perfect fit. 256 bytes, dual port. A single chip would do the job. But it is an obsolete part and no other replacement is offered.

apoloval commented 4 years ago

Thinking about that... It could be possible that we do not even need any interface. The ATMega328 could be enough.

Let's say we connect the PD port (8 bits) of the Mega to the PPI port B where keyboard columns must be received. The port PC (7 bits) is connected to the low nibble of the PPI port C where keyboard row is selected. The /PPIWR signal is connected to some pin of the Mega to trigger an interrupt.

The point is that the Mega changes PD with the contents of the keyboard row indicated by PC on every raise edge of /PPIWR. The write operation on the PPI might not be for port C, but we do not care. There is no effect on updating when keyboard row is not modified.

The Z80 can change the selected keyboard row by writing in C port of the PPI. This requires an OUT instruction. After that, if it wants to read the keyboard columns, another IN instruction is required.

In the best case, an INand OUT instructions take 11 cycles. 12 cycles in case of MSX due to the additional wait cycle. With a CPU clock of 3.58Mhz, this is 3.35uS. This is the time we have for the ATMega328 to execute the interrupt handler.

I think 3.35uS could be enough time for an ATMega328 at 16Mhz to read one port, fetch an array item, and write it to another port. I have to check the ASM specs of the microcontroller to confirm this.

apoloval commented 4 years ago

As I read in the ATMega328 datasheet, most instructiosn take 1 clock cycle:

image

Also, the interruption responds in no less than 4 cycles:

image

IN and OUT operations take 1 cycle:

image

And load indirect operations take 2 cycles.

image

In summary, it sounds we could update the keyboard columns in something more than 8 cycles. At 16Mhz, this is 0.5uS.

This is something we have to experiment. An Arduino Uno, a testing sketch and a logic analyzer. And see how much time it takes to perform what we expect from it.

apoloval commented 4 years ago

This doesn't look well:

image

Interrupt handler is just:

void interrupt_handler() {
  PORTC = rows[index];
  index++;
}
apoloval commented 4 years ago

Surprisingly, using assembler code is even slower. From 12us to 17us:

image

The code:

void interrupt_handler() {
  asm volatile(
    "OUT %[port], %[data] \n\t"
    ::
    [port]      "I" (_SFR_IO_ADDR(PORTC)),
    [data]      "r" (index)
  );

  index++;
}
apoloval commented 4 years ago

Just in case the logic analyzer is not good enough and is showing bad results, I double checked with the oscilloscope. Same results:

IMAGE 2020-05-23 18:31:00

apoloval commented 4 years ago

Well. There was several wrong things in the experiment.

First, I was configuring the ports direction wrongly. However, this causes the signal to be written at the output, but with a sort of capacitor-load effect.

Secondly, using a mechanical switch to simulate the /PPIWR pulses doesn't work. It seems to have a very high capacitance, and the signals takes several microseconds to raise. I replaced the switch by a digital output from the Arduino board. A pulse is produced every second, and it is externally connected to the port where external interrupt is configured.

After fixing both things, this is what the oscilloscope shows:

IMAGE 2020-05-23 19:31:40

The value can be updated 2.4uS after the raise of /PPIWR.

These are great news, because we are below the 3.35uS limit. But there are still some other considerations. One of them, that the ATMega328 could be running an interruption from the PS2 port when /PPIWR is asserted. If that happens, we should interrupt the PS2 interruption. And this is risky.

apoloval commented 4 years ago

BTW, this is a complete guide of interrupts in Arduino.

It tells a very interesting fact. The ISR requires 1.43uS to save the context in the stack. Makes sense and matches the observed delays.

apoloval commented 4 years ago

Another interesting observation. Using attachInterrupt() instead of direct ISR declaration increases the delay from 2.0uS to 3.6uS.

The guide linked above explains the reason. This method does not bind the function as ISR, but uses its own and keeps the function passed as argument in a table. It has to lookup to find it and then call it. A that takes more time.

apoloval commented 4 years ago

I wonder that is the propagation delay from /PPIWR to PPI port C data lines. Because if it is significant, it might compromise the system if just ATMega is used.

This is it. Yello is /PPIWR, blue is PPI port C data 0:

IMAGE 2020-05-24 16:42:56

Less than 40ns. Not significant. We might use either the /PPIWR or the port C data lines as interrupt indistinctly.

apoloval commented 4 years ago

BTW, this is the confirmation that the keyboard row write is followed by a column read after 3.35uS:

IMAGE 2020-05-24 17:43:02

apoloval commented 4 years ago

I am afraid I bring more problems to the table.

This is the pinout of the ATMega328/Arduino Uno:

image

As we can see, there is just one por with 8-bits fully available: Port D. Port C is 7-bit, with one bit assigned to the /RESET signal. Port B has 8-bits, but two of them are used for the oscillator inputs.

The bad news is that Port D cannot be used as keyboard column data bus. Because the interrupt pins are placed there. And we need at least one of them for PS2 interruptions.

The alternative would be to separate the column data bus in two nibbles. For example using PORTB for the lower 4-bits and PORTC for the upper 4-bits. This is the timing diagram of this. Yellow is /PPIWR, blue is the last bit to be updated:

IMAGE 2020-05-24 19:08:41

4.8 fucking microseconds. Far above the 3.35uS limit.

apoloval commented 4 years ago

I've been investigating about implementing the ISR in pure assembler instead of C/C++. In order to avoid innecesary context saving and reduce the number of instructions executed before the actual code.

Using Atmel Studio, I could have a look to the output listing of a raw C ISR declaration. It looks like:

ISR(INT0_vect) {
  80:   1f 92           push    r1
  82:   0f 92           push    r0
  84:   0f b6           in  r0, 0x3f    ; 63
  86:   0f 92           push    r0
  88:   11 24           eor r1, r1
  8a:   8f 93           push    r24
    PORTB = 42;
  8c:   8a e2           ldi r24, 0x2A   ; 42
  8e:   85 b9           out 0x05, r24   ; 5
}

As we can see, the prelude is the same described in the Gammon guide. But the last pushes before the actual code of the ISR. I see they are not fixed, but depend on the registers used in the ISR code. In this case, only R24 is used. So this is the only register saved. Makes sense.

The rest of the code is saving the status register and resetting R0. Both things are required I think in order to work.

Hence, I think I couldn't write a faster ISR in assembler than the one provided by the C compiler.

I am afraid the best choice is using the 74HC670 registers for a safe and trusted interface between the microcontroller and the PPI.

apoloval commented 4 years ago

After this analysis, I am pretty sure not having a proper interface between the PPI and the uController will give a lot of problems.

An interface design has been added in b233b2aa731761b718c923b65be64de378a5456f.