程序描述:
// Description
//---------------------------------------------------------------------------
// This module reads the pulse width of a repetitive variable duty cycle
// digital input. Pulse Width Modulated (PWM) inputs are produced by many
// devices, including certain real world sensors.
// In this case, the specific sensor for which this module was created
// is the Analog Devices ADXL202e (a +/- 2g. dual accelerometer.)
//
// Operation of this module is very simple:
//
// An up-counter (T) counts clocks in one period of the incoming PWM signal.
// The T counter is reset with each rising edge of the incoming PWM waveform.
//
// Another (clock-enabled) up-counter (S) counts the clocks during the time
// when the PWM signal input is high.
// The S counter is also reset at the rising edge of the incoming PWM wave.
//
// To derive the duty cycle as a fraction, a serial divider calculates S/T.
//
// Once the fraction is obtained, the output is presented at "dat_o."
// The signal "stb_o" (strobe output) indicates that a valid reading is
// present on the data bus output. The strobe will remain high until the
// reading is acknowledged by pulsing "ack_i" for one clock cycle.
//
// If "stb_o" is tied directly to "ack_i" then each time a new reading is
// obtained a pulse will be produced at "stb_o" which is one single clock wide.
//
// It is important to note that in the absence of any ack_i signal, the unit
// will hold the contents of the last reading, and any new readings from the
// PWM device will be missed.
//
//---------------------------------------------------------------------------
/* ____Th____ ____Th____
| | | |
| | | |
| |_____Tl______| |
*/
本程序關鍵在於計算Th和TL。
方法:實例化兩個計數器,在上升沿清零計數值並啓動計數,
計數器1:增量計數器,PWM高電平計數,記錄高電平時長Th。只在高電平計數。
計數器2:增量計數器,記錄PWM的週期 T。
則PWM的佔空比是D=Th/T。
module pwm_reader (
clk_i,
clk_en_i,
rst_i,
pwm_signal_i,
ack_i,
dat_o,
stb_o
);
parameter COUNTER_WIDTH_PP = 10; // Size to hold (max_pwm_period)*(Fclk_i)
parameter DAT_WIDTH_PP = 8; // Size of fraction from divider
parameter DIV_COUNT_WIDTH_PP = 3; // Must be enough bits to hold the number:
// DAT_WIDTH_PP-1
input clk_i;
input clk_en_i;
input rst_i;
input pwm_signal_i;
input ack_i;
output [DAT_WIDTH_PP-1:0] dat_o;
output stb_o;
// Local signals
reg [COUNTER_WIDTH_PP-1:0] s_cnt;
reg [COUNTER_WIDTH_PP-1:0] t_cnt;
reg pwm_ff_1;
reg pwm_ff_2;
reg ack_r;
wire rising_edge;
wire divide_done;
//-----------------------------------------------------------------------
// Try to avoid metastability by running the input signal through
// a D flip flop.
always @(posedge clk_i)
begin
if (rst_i) pwm_ff_1 <= 0;
else pwm_ff_1 <= pwm_signal_i;
end
// This is the rising edge detection
always @(posedge clk_i)
begin
if (rst_i) pwm_ff_2 <= 0;
else pwm_ff_2 <= pwm_ff_1;
end
assign rising_edge = pwm_ff_1 && ~pwm_ff_2;
// This is period counter
always @(posedge clk_i)
begin
if (rst_i || rising_edge) t_cnt <= 0;
else if (clk_en_i) t_cnt <= t_cnt + 1;
end
// This is duty cycle counter
always @(posedge clk_i)
begin
if (rst_i || rising_edge) s_cnt <= 0;
else if (clk_en_i && pwm_signal_i) s_cnt <= s_cnt + 1;
end
// This unit provides for the division s_cnt/t_cnt.
// The values in the counters are latched into the divider whenever
// the divide_i input is pulsed.
// The divider ignores the inputs whenever "divide_i" is not pulsed, so
// there is no problem with the counters continually advancing until
// the division is started.
//
// Since the result is expected to be purely fractional, the quotient part
// is all zero, and is thrown away on purpose. (A special divider could be
// build possibly to avoid this?)
serial_divide_uu #(
COUNTER_WIDTH_PP, // M_PP (dividend width)
COUNTER_WIDTH_PP, // N_PP (divisor width)
DAT_WIDTH_PP, // R_PP (remainder width)
COUNTER_WIDTH_PP, // S_PP (skip integer part of quotient)
DIV_COUNT_WIDTH_PP, // counter bits
1 // need output holding buffers?
)
divider_unit
(
.clk_i(clk_i),
.clk_en_i(1'b1),
.rst_i(rst_i),
// New divide is not allowed until previous value is read or acknowledged
.divide_i(rising_edge && ~stb_o),
.dividend_i(s_cnt),
.divisor_i(t_cnt),
.quotient_o(dat_o),
.done_o(divide_done)
);
// This latches the ack_i input, so that at least one pulse of stb_o is produced
// before the ack_i takes it down low again, in case the stb_o output is wired
// directly to the ack_i input.
always @(posedge clk_i)
begin
if (rst_i || (rising_edge && ~stb_o)) ack_r <= 0;
else if (stb_o) ack_r <= ack_i;
end
assign stb_o = divide_done && ~ack_r;
endmodule
計算佔空比使用了串行除法運算