Tuesday, April 3, 2018

M-32TL Printer contact debouncing

I got to thinking about the interface to the M-32TL printer from my Canon P170-DH calculator. Now that I have an async serial interface I thought I'd plumb it into the printer.

The tests I did last year with the printer were driven by a Microchip PIC 16LF872. I'm primarily a software engineer, so that environment is easier for me to play in. However, when I wrote the C code for that, I did it with some thought to converting it to Verilog. Thus I started out by copying my C code and set about doing a straight translation. For the most part I think it would have worked, but in the end I re-wrote most of it.

One of the parts that did survive mostly intact was the debouncing code. The M-32TL has a position sensor made up of a PCB disc with four electrical spring fingers (contacts) that ride on the surface of the disc. As the disc rotates, the fingers come into contact with the pattern on the disc, thus indicating where the print wheels are in their rotation. Like all electrical contacts, these fingers have some amount of bounce. This bouncing can result in incorrect indications.

Looking at the PCB in the P170-DH calculator I found an R-C debounce circuit on each of the contacts. This consists of a 15K ohm resistor to +5V and a 2.2nF capacitor to ground; the switch contacts pull the junction of these two components to ground when closed. The time constant for this is 33 microseconds, which is very short for debouncing, but the contacts are only closed for about 500 microseconds so the common rule of allowing 10 milliseconds for bounces clearly wouldn't work. However, these contacts slide rather than snap, and only move the thickness of the conductive plating on the PCB, so that appears to be sufficient.

My PIC test circuit used the 10K ohm resistors to +5V and no capacitors. The PIC pins I used have Schmitt trigger inputs which provide hysteresis, and I implemented a 4-bit shift register in software for debounce and edge detection. The scan rate was about 37 microseconds per cycle, so it would take about 111 microseconds to detect a valid edge. That seemed to work just fine for the printer.

The Lattice iCE40-HX provides about 50 millivolts of hysteresis, but the Xilinx Spartan-6 has no documented hysteresis. My plan for the final circuit is to use a 74LVC14 inverter with Schmitt trigger inputs between the contacts and the S6, but since that's not available in a convenient DIP package I'm using a 74AHC14 which is close enough. I haven't decided between using the R-C debounce and doing it in the FPGA, so I coded a debounce module in Verilog.

And here is the source code for that module:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
module debounce_index #(
    parameter DEPTH = 6         // 60us @ 10us/cycle
) (
    input  wire     clk,        // Clock
    input  wire     clk_en,     // Clock Enable
    input  wire     rst_n,      // Asynchronous reset active low

    input  wire     pin,        // External input pin
    output reg      state,      // Debounced state
    output reg      leading,    // going-Active edge
    output reg      trailing    // going-Inactive edge
);

    // Combination synchronizer and debouncer
    (* ASYNC_REG = "TRUE" *) reg              sync;
    (* ASYNC_REG = "TRUE" *) reg  [DEPTH-1:0] fifo;
                             reg              prev;

    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            leading <= 1'b0;
            trailing <= 1'b0;
            state <= 1'b0;
            sync <= 1'b0;
            fifo <= {DEPTH{1'b0}};
            prev <= 1'b0;
        end else if (clk_en) begin
            // Assume no edges
            leading <= 1'b0;
            trailing <= 1'b0;

            // Check for a stable 0 state
            if (~|fifo) begin
                state <= 1'b0;
                trailing <= prev;
            end

            // Check for a stable 1 state
            if (&fifo) begin
                state <= 1'b1;
                leading <= ~prev;
            end

            // Update the debounce FIFO
            {prev, fifo, sync} <= {fifo, sync, pin};
        end
    end

endmodule // debounce_index

Since the FPGA logic operates in parallel it can cycle much faster than the PIC. I chose a 10 microsecond cycle, with a deeper FIFO to achieve similar timing. This should make it more resistant to errors. The depth of the FIFO is a parameter defined on line 2. If the hardware debouncing is sufficient this can be set as low as 1 to minimize the latency.

Any time a signal arrives asynchronously with the system clock it's best to run it through a synchronizer to avoid metastability problems. My synchronizer is formed by the flip-flops inferred by "sync" and "fifo[0]" (lines 15 and 16). The "state" output is essentially an S-R flip-flop which is set or reset only when all of the bits in fifo are the same (lines 33-34, 39-40).  Edge detection is done by keeping one previous bit in "prev" (line 17); if it's different than the current state, we have an edge (lines 35, 41). The contents of "prev", "fifo", and "sync" are updated on line 45.

The only "magic" in any of this is the attribute "ASYNC_REG". This is a Xilinx-specific attribute that tells the place-and-route to keep the synchronizer flip-flops in physical proximity to each other to minimize the signal propagation lag between them. In the case of the Spartan-6 FPGA, Xilinx says this is only necessary at clock speeds above 300 MHz. For this project my expected clock rate is 50 MHz or slower, but I like this module so much it may become something of a stock item for me. I have another project where I'm getting close to this, so I'm just trying to be "proper" here.

2 comments:

  1. I breadboarded the printer position sensor circuit (four 15K resistors, no capacitors, a 74AHC14 Schmitt trigger inverter, and the printer) and connected my scope, hoping to see how long the contacts bounced. They don't. I was surprised. Maybe it's just that my printer is new, and there's no wear or dirt on the sensor disc or fingers, but the transitions were clean. Adding the 2.2nF capacitors did little to change that.

    I think I'll adopt the circuit from the original board, driving a 74LVC14, and configure the firmware debounce for a 1 bit FIFO. That seems safe, and I can always lengthen the FIFO later.

    ReplyDelete
  2. It occurs to me that the edge detection implemented here isn't good. Consider the case where the contact is closed and stable, so the fifo contains all zeros. Now a dirty spot passes under the fingers, causing a single sample to be a one. The state output will properly ignore this transient condition, but when the erroneous one reaches prev, an edge is reported.

    What should happen here is that edges should be reported only when the state output changes.

    ReplyDelete