Sunday, March 25, 2018

Matrix keypad experiments, part 2

Ever heard the saying, "The cobbler's children go barefoot"?

One of problems with being everybody's go-to guy for home tech problems is that there isn't anyone to call when your own tech develops problems. This morning I woke up to find my main computer and home network was  completely locked up. Several frustrating hours later I finally got back to working on the keypad scanner.

The pic shows the iCEblink board with the PmodKYPD keypad plugged in, and my scope probes attached to the column 3 and 4 outputs.

To make a long (4 hours!) story short, I finally discovered iCEcube2 doesn't like pin numbers that start with a leading zero in a pin constraints file (.pcf) and ignores them. This caused most of my matrix keypad column outputs and row inputs to be assigned to random, otherwise-unused pins. The one pin with valid output was connected to pin 10, which didn't have a leading zero. Correcting the pin assignments allowed the simple scanner to work as expected.

I can understand and fully support ignoring or issuing a warning for a pin definition that doesn't have a corresponding net in the design. But considering a parsing error to be anything but a fatal error is just beyond me.

So what about the decoder? Here's the code for that:

 1:  module column_decode #(  
 2:    parameter [31:0] KEY_CODES = 0  
 3:    ) (  
 4:    input wire        clk,            // Clock  
 5:    input wire        rst_n,          // Asynchronous reset active low  
 6:    input wire [4:1]  row_sense,      // Matrix row sense inputs  
 7:    input wire        col_drive,      // This column of the matrix is driven  
 8:    output reg [7:0]  col_code,       // Output key code (0 if col_active== 0)  
 9:    output reg        col_active,     // Key(s) in this column are active  
10:    output reg        col_error       // Multiple keys in this column are active  
11:    );  
12:    always @(posedge clk or negedge rst_n) begin  
13:      if (~rst_n) begin  
14:        col_code  <= 8'h00;  
15:        col_active <= 1'b0;  
16:        col_error <= 1'b1;  
17:      end else if (~col_drive) begin  
18:        col_code  <= 8'h00;  
19:        col_active <= 1'b1;  
20:        col_error <= 1'b0;  
21:        case (row_sense)  
22:          4'b1110 : col_code  <= KEY_CODES[31:24];   // Key in row 1 active  
23:          4'b1101 : col_code  <= KEY_CODES[23:16];   // Key in row 2 active  
24:          4'b1011 : col_code  <= KEY_CODES[15: 8];   // Key in row 3 active  
25:          4'b0111 : col_code  <= KEY_CODES[ 7: 0];   // Key in row 4 active  
26:          4'b1111 : col_active <= 1'b0;              // No keys active  
27:          default : col_error <= 1'b1;               // Multiple keys active  
28:        endcase  
29:      end  
30:    end  
31:  endmodule  

One instantiation of this module is required per column.

While the column this instantiation represents is active, "col_drive" will be 0. If a key in this column is pressed, the corresponding bit in "row_sense" will be zero. Lines 18-20 set up the default output, some of which may be overridden in the case statement of lines 21-28. The magic here is to recognize the five legal states (exactly one row active in lines 22-25, and no rows active in line 26), and allow the default clause on line 27 to catch any combination of multiple rows.

If we sampled the row inputs immediately after the column output goes low, we may get erroneous results. The real world is analog and not digital, and it may take some time for a relatively high-resistance "switch" of the conductive pad type like in the P170 to discharge the input capacitances. What we really want is to sample the row inputs at the end of the sense period, which in this case is 250us. Since the flip-flops are clocked at the rate of the "clk" input (3.3 MHz), we'll actually perform the decode portion 833 times. Unfortunately, 250us is far too short a period to handle debouncing (which can take 10ms or longer), so that will need to be performed at a higher level.

For completeness, here's an example of the instantiation of column_decode for the first column of the PmodKYPD keypad, showing how the KEY_CODES parameter is set:

 1:    column_decode #(  
 2:      .KEY_CODES ("1470")  
 3:    ) col1_decode (  
 4:      .clk        (clk),  
 5:      .rst_n      (rst_n),  
 6:      .row_sense  (row_sense),  
 7:      .col_drive  (col_drive[1]),  
 8:      .col_code   (col1_code),  
 9:      .col_active (col_active[1]),  
10:      .col_error  (col_error[1])  
11:    );  

This gives the results of only one column. This keypad has four columns and the P170 will have eight, and the outputs of all have to be merged to get a useful result.

A legitimate question might be, "Why are the outputs of the column_decode module clocked, rather than combinational?" The answer lies in the way we merge the outputs of the set of decode modules. This merging is done by bitwise-ORing the code and error outputs of all the columns, while the active outputs are checked to see if keys in multiple columns are pressed. Since each of the decode modules is activated in sequence and not all at the same time, the outputs must persist. Thus clocked (or "registered") outputs.

Here's the decode merge logic. Since its inputs are all registered in the decode modules, and its outputs will be registered by the debounce logic, this can be combinational logic:

 1:    // Merge the various column decoder outputs  
 2:    assign key_code = col1_code | col2_code | col3_code | col4_code;  
 3:    always @(*) begin  
 4:      if (col_error != 4'b0000) begin  
 5:        key_valid = 1'b0;  
 6:        key_error = 1'b1;  
 7:      end else begin  
 8:        key_valid = 1'b0;  
 9:        key_error = 1'b0;  
10:        case (col_active)  
11:          4'b0000 :  ;  // Neither valid nor error  
12:          4'b0001 :  key_valid = 1'b1;  
13:          4'b0010 :  key_valid = 1'b1;  
14:          4'b0100 :  key_valid = 1'b1;  
15:          4'b1000 :  key_valid = 1'b1;  
16:          default :  key_error = 1'b1;  
17:        endcase  
18:      end  
19:    end  


The next step will be to implement the debouncing of the outputs.

No comments:

Post a Comment