Friday, April 3, 2020

Keypad remapping

When I wrote the previous blog entry Sunday I fully expected I'd implement the first option I listed, which was to scan and decode the Canon P170-DH keypad matrix, then convert these keycodes back to Busicom 141-PF matrix signals. As usual, things did not go according to plan.

The idea for the second option, to create a virtual 141-PF keyboard and update it by scanning the P170-DH keypad asynchronously, occurred to me while I was writing the post and I included it at the last minute. If I'd implemented this in software rather than HDL I probably would have implemented the first option without further concern. But the more I thought about the circuitry required the more interested I became in the virtual keyboard idea. So that's what I've implemented.

The remapping code took about 130 lines of Verilog. The design centers around the virtual 141-PF keyboard, which is structured as a 8 column by 4 row matrix. Thus I defined the virtual keyboard matrix as an eight element array of 4-bit vectors:
reg  [3:0] kb_matrix [0:7]; 
Since I'm scanning the P170-DH keypad asynchronously to the software scanning of the virtual keyboard, this obviously needs to be latched or clocked logic; I opted to infer clocked flip-flops:
always @(posedge sys_clk) begin
    if (sample) begin
        case (1'b0)
            kp_col[1] : begin
                kb_feed         <= kp_row[1];   // feed
                kb_matrix[7][2] <= kp_row[2];   // CE <- right arrow
                kb_matrix[1][1] <= kp_row[3];   // %+-
                kb_matrix[2][0] <= kp_row[4];   // #/diamond
                kb_matrix[7][3] <= kp_row[5];   // C  <- CE/C
            end

    ... similar code for kp_col[2] through kp_col[7] here ...

            kp_col[8] : begin
                // kb_matrix[][] <= kp_row[1];  // Tax-
                kb_matrix[0][2] <= kp_row[2];   // M- <- ~
                kb_matrix[0][3] <= kp_row[3];   // M+ <- Mdiamond
                kb_matrix[1][2] <= kp_row[4];   // M-=
                kb_matrix[1][3] <= kp_row[5];   // M+=
            end
        endcase
    end
end

The kp_col vector drives the column outputs to the keypad. In sequence, at 250us intervals, one of these outputs is driven low by the FPGA logic. The row tracks pass through a Schmitt Trigger inverter, so if a key on that column is pressed, the corresponding kp_row input to the FPGA will go high. The "sample" signal goes high at the end of each interval, clocking the state of the kp_row bits into the appropriate flip-flops in the kb_matrix virtual keyboard.
 
The i4004 software scans the keyboard matrix by walking a "0" bit down an i4003 shift register, which has 10 outputs. Each of the first 8 outputs of the i4003 selects one column of the keyboard matrix, while the remaining two outputs select the encoded slide switch outputs.

In this emulation, the virtual keyboard matrix is read through a classic "sum of products" multiplexer: a set of AND gates whose outputs are ORed together. For clarity I chose to code this with "if" statements rather than boolean logic:
// kb_col selects which column is muxed onto kb_row
always @(*) begin
    kb_row = 4'b0000;
    if (~kb_col[0]) kb_row = kb_row | kb_matrix[0];
    if (~kb_col[1]) kb_row = kb_row | kb_matrix[1];
    if (~kb_col[2]) kb_row = kb_row | kb_matrix[2];
    if (~kb_col[3]) kb_row = kb_row | kb_matrix[3];
    if (~kb_col[4]) kb_row = kb_row | kb_matrix[4];
    if (~kb_col[5]) kb_row = kb_row | kb_matrix[5];
    if (~kb_col[6]) kb_row = kb_row | kb_matrix[6];
    if (~kb_col[7]) kb_row = kb_row | kb_matrix[7];
    if (~kb_col[8]) kb_row = kb_row | kb_decimal;
    if (~kb_col[9]) kb_row = kb_row | kb_round;
end
As planned, I drove the "kp_col" and "sample" signals with 250us interval steps, scanning the entire keypad at a 500Hz rate. For testing I drove the "kb_col" signals with 325us interval steps, or about 308Hz, to emulate what I suspected the i4004 software might do from a quick look at the software. More recently I found the owner's manual for a Unicom 141-PF which claims the keypad is scanned at a 40Hz rate, but that would be fine too. [See note below]

I also connected the kp_col, kp_row, kb_col, and kp_row signals to my debug header, and set up my logic analyzer to display these signals. By triggering on the falling edge of kp_col[1] I could easily watch the scanning of the P170-DH keypad, and by triggering on the falling edge of kb_col[0] I could watch the output of the virtual keyboard being scanned and validate that the remapping was correct.

Resource requirements for the keypad remap code are quite low: 28 slices of the 1,430 available. That includes the 32 flip-flops in the virtual keyboard matrix, the logic to encode the two slide switches to BCD, and the output mux.

The test code occupied another 22 slices. This includes the clock dividers, the kp_col and kb_col shifters, the kp_row input buffers, and the debug header output drivers.

Current draw on my bench power supply was about 25mA at about 7V during this test.


Note: The Busicom software scans the keyboard once each time the printer drum sector signal goes inactive. My emulation of the EP102 printer does this every 28ms (35.7Hz) because that is what the analysis team documented.

With the i4004 running with a clock period of 1.4us, each keyboard column line is driven low for 358.4us, or 32 instruction cycles. So so my guess of 29 instruction cycles (325us) per column was pretty close. With a clock period of 1.35us, 32 instructions would take 345.6us.

No comments:

Post a Comment