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
Looking at the PCB in the P170-DH calculator I found an
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
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
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.
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.
ReplyDeleteI 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.
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.
ReplyDeleteWhat should happen here is that edges should be reported only when the state output changes.