继续上文内容:
https://blog.csdn.net/qq_46284844/article/details/121716619?spm=1001.2014.3001.5501
上文介绍了TDC7200的一些基本信息,本文说一下使用FPGA来控制TDC7200的过程。
TDC7200是使用SPI通信的,所以我们首先确定需要编写SPI通信协议,确定协议之后我们再根据数据手册的寄存器地址和相关的配置发送和接收数据。而对于FPGA的时序控制,我们还是一如既往的使用状态机来控制。
input wire sys_clk,
input wire sys_rst_n,
input wire tdc_interrupt,
input wire miso,
output reg mosi,
output reg cs,
output reg sck,
output reg tdc_en,
output wire [7:0] data_out,
output reg data_out_flag
如上所示,定义端口如下:
(1)时钟信号和复位信号;
(2)TDC中断输入端口用来响应测时完成信号;
(3)四个SPI口以及TDC7200的使能控制管脚;
(4)两个数据相关的端口,一个为数据,一个为数据有效性端口。
//STATES DEFINE
parameter DELY = 16'b0000_0000_0000_0000;
parameter INITIAL_IDLE = 16'b0000_0000_0000_0001;
parameter ENABLE_LOW = 16'b0000_0000_0000_0010;
parameter ENABLE_HIGH = 16'b0000_0000_0000_0100;
parameter CONFIG1_INIT = 16'b0000_0000_0000_1000;
parameter CONFIG1_IN_RD = 16'b0000_0000_0001_0000;
parameter CONFIG2_INIT = 16'b0000_0000_0010_0000;
parameter CONFIG2_IN_RD = 16'b0000_0000_0100_0000;
parameter INT_MASK_INIT = 16'b0000_0000_1000_0000;
parameter INT_MASK_IN_RD = 16'b0000_0001_0000_0000;
parameter CNTR_OVF_H = 16'b0000_0010_0000_0000;
parameter CNTR_OVF_H_RD = 16'b0000_0100_0000_0000;
parameter CNTR_OVF_L = 16'b0000_1000_0000_0000;
parameter CNTR_OVF_L_RD = 16'b0001_0000_0000_0000;
parameter CONFIG1_START = 16'b0010_0000_0000_0000;
parameter CONFIG1_STA_RD = 16'b0100_0000_0000_0000;
parameter WAITING_TDC = 16'b1000_0000_0000_0000;
parameter READING_TDC = 16'b1100_0000_0000_0000;
parameter WAITING_RESET = 16'b1110_0000_0000_0000;
状态定义包括:
(1)初始状态;
(2)使能信号拉低状态,上电后先拉低使能信号1s用以复位TDC;
(3)使能信号拉高状态,正如上一篇文章所说,TDC使能信号拉高之后需要一定的时间稳定,这边拉高后5ms时间后到下一状态;
(4)之后是一系列初始化的状态,根据SPI数据流转的特点以及TDC7200手册上的说明,FPGA每次向TDC写数据之后必须读回相应数据长度的数据才能进行下一次的读写,所以每一条指令我用了两个状态表示,一写一读;
(5)初始化之后就是等待TDC测量完毕的的状态,当tdc_interrupt端口被拉低之后跳转到下一状态;
(6)读TDC状态;
(7)等待复位状态。
//COMMAND DEFINE
parameter CONFIG1_COMMAND = 16'b0100_0000_1000_0000;
parameter CONFIG1_READING = 16'b0000_0000_0000_0000;
parameter CONFIG2_COMMAND = 16'b0100_0001_0000_0000;
parameter CONFIG2_READING = 16'b0000_0001_0000_0000;
parameter INTMASK_COMMAND = 16'b0100_0011_0000_0001;
parameter INTMASK_READING = 16'b0000_0011_0000_0000;
parameter CNTRO_H_COMMAND = 16'b0100_0100_1111_1111;
parameter CNTRO_H_READING = 16'b0000_0100_0000_0000;
parameter CNTRO_L_COMMAND = 16'b0100_0101_0000_0000;
parameter CNTRO_L_READING = 16'b0000_0101_0000_0000;
parameter CONFIG1_TO_STAR = 16'b0100_0000_1000_0001;
parameter READING_TIME1 = 16'b1001_0000_0000_0000;
parameter READING_CALI1 = 16'b1001_1011_0000_0000;
parameter READING_CALI2 = 16'b1001_1100_0000_0000;
parameter READING_SUPP = 16'b0000_0000_0000_0000;
前面十一条指令分别对应初始化指令的各个状态,之后三条为读取测量完毕之后的数据时的指令,因为SPI的环回数据格式,所以我们再定义一条全0的指令用以在读取的时候发送。
下面介绍一下指令的组成
还是回到这幅图,从这幅图中看出数据发送为先地址在数据,上升沿TDC读入,下降沿TDC写出。所以我们的指令格式都得遵循地址在前,数据在后的格式。确定数据格式之后,我们根据数据手册中寄存器的地址确定6位地址信息,外加最高位自动增长位和次高位读写位组成8位地址+8位数据的格式,作为一帧数据发送(如下图所示)。
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
sck_cnt <= 2'd0;
else if( (state == CONFIG1_INIT && (cnt_byte == 8'd1 || cnt_byte == 8'd2)) ||
(state == CONFIG1_IN_RD && (cnt_byte == 8'd6 || cnt_byte == 8'd7)) ||
(state == CONFIG2_INIT && (cnt_byte == 8'd11 || cnt_byte == 8'd12)) ||
(state == CONFIG2_IN_RD && (cnt_byte == 8'd16 || cnt_byte == 8'd17)) ||
(state == INT_MASK_INIT && (cnt_byte == 8'd21 || cnt_byte == 8'd22)) ||
(state == INT_MASK_IN_RD && (cnt_byte == 8'd26 || cnt_byte == 8'd27)) ||
(state == CNTR_OVF_H && (cnt_byte == 8'd31 || cnt_byte == 8'd32)) ||
(state == CNTR_OVF_H_RD && (cnt_byte == 8'd36 || cnt_byte == 8'd37)) ||
(state == CNTR_OVF_L && (cnt_byte == 8'd41 || cnt_byte == 8'd42)) ||
(state == CNTR_OVF_L_RD && (cnt_byte == 8'd46 || cnt_byte == 8'd47)) ||
(state == CONFIG1_START && (cnt_byte == 8'd51 || cnt_byte == 8'd52)) ||
(state == CONFIG1_STA_RD && (cnt_byte == 8'd56 || cnt_byte == 8'd57)) ||
(state == READING_TDC && (cnt_byte == 8'd60 || cnt_byte == 8'd61 ||
cnt_byte == 8'd62 || cnt_byte == 8'd63 ||
cnt_byte == 8'd67 || cnt_byte == 8'd68 ||
cnt_byte == 8'd69 || cnt_byte == 8'd70 ||
cnt_byte == 8'd74 || cnt_byte == 8'd75 ||
cnt_byte == 8'd76 || cnt_byte == 8'd77)) )
sck_cnt <= sck_cnt + 1'd1;
else
sck_cnt <= sck_cnt;
end
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
sck <= 1'b0;
else if(sck_cnt == 2'd2)
sck <= 1'b1;
else if(sck_cnt == 2'd0)
sck <= 1'b0;
else
sck <= sck;
end
简单起见,我采用直接对50MHz的晶振时钟计数分频得到12.5MHz的SPI时钟,且未超出TDC7200手册中规定的始终上限频率。
这边一大段cnt_byte有关代码请看下一部分讲解。
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
clk_cnt <= 5'd0;
else
if(state == INITIAL_IDLE || state == ENABLE_LOW || state == ENABLE_HIGH ||
state == WAITING_TDC || state == WAITING_RESET)
clk_cnt <= 5'd0;
else if(clk_cnt == 5'd31)
clk_cnt <= 5'd0;
else
clk_cnt <= clk_cnt + 1'd1;
end
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
cnt_byte <= 8'd0;
else
if(cnt_byte == 8'd78 && clk_cnt == 5'd31)
cnt_byte <= 8'd78;
else if(clk_cnt == 5'd31)
cnt_byte <= cnt_byte + 1'd1;
else
cnt_byte <= cnt_byte;
end
正如上面所说的那样,最长的时间就是数据发送的过程,因为TDC的寄存器要么是8位要么是24位。如果我们使用16位作为一个位单元将会十分不方便,所以我们采用每8位作为一个位单元。所以50MHz的主频时钟,12.5MHz的SCK时钟,50 / 12.5 * 8 = 32个单元。至于这边为什么使用全都用32作为一个时钟计数周期,参考火哥的SPI代码时序控制,因为整个时序中最长的时间就是数据发送的过程,那么我们每次都使用这个时间段作时序延时(比如说上一篇文章说到的建立时间、保持时间、CS拉高时间等),这样一来就大大方便了我们编写时序以及省去了大篇幅的代码。
cnt_byte与每个状态对应的时序图以及计数关系表如下所示:
根据上图我们编写状态跳转代码如下
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n) begin
state <= INITIAL_IDLE;
tdc_en <= 1'b0;
end
else begin
case(state)
INITIAL_IDLE:begin
state <= ENABLE_LOW;
end
ENABLE_LOW:begin
tdc_en <= 1'b0;
if(delay_cnt1 == DELAY_ONE_PART)//50_000_000
state <= ENABLE_HIGH;
else
state <= ENABLE_LOW;
end
ENABLE_HIGH:begin
tdc_en <= 1'b1;
if(delay_cnt2 == DELAY_TWO_PART)//250_000
state <= CONFIG1_INIT;
else
state <= ENABLE_HIGH;
end
CONFIG1_INIT:begin
if(cnt_byte == 8'd3 && clk_cnt == 5'd31)
state <= DELY;
else
state <= CONFIG1_INIT;
end
DELY:begin
if(cnt_byte == 8'd4 && clk_cnt == 5'd31)
state <= CONFIG1_IN_RD;
else if(cnt_byte == 8'd9 && clk_cnt == 5'd31)
state <= CONFIG2_INIT;
else if(cnt_byte == 8'd14 && clk_cnt == 5'd31)
state <= CONFIG2_IN_RD;
else if(cnt_byte == 8'd19 && clk_cnt == 5'd31)
state <= INT_MASK_INIT;//INT_MASK_INIT
else if(cnt_byte == 8'd24 && clk_cnt == 5'd31)
state <= INT_MASK_IN_RD;
else if(cnt_byte == 8'd29 && clk_cnt == 5'd31)
state <= CNTR_OVF_H;
else if(cnt_byte == 8'd34 && clk_cnt == 5'd31)
state <= CNTR_OVF_H_RD;
else if(cnt_byte == 8'd39 && clk_cnt == 5'd31)
state <= CNTR_OVF_L;//WAITING_TDC
else if(cnt_byte == 8'd44 && clk_cnt == 5'd31)
state <= CNTR_OVF_L_RD;
else if(cnt_byte == 8'd49 && clk_cnt == 5'd31)
state <= CONFIG1_START;
else if(cnt_byte == 8'd54 && clk_cnt == 5'd31)
state <= CONFIG1_STA_RD;
else if(cnt_byte == 8'd65 && clk_cnt == 5'd31)
state <= READING_TDC;
else if(cnt_byte == 8'd72 && clk_cnt == 5'd31)
state <= READING_TDC;
else
state <= DELY;
end
CONFIG1_IN_RD:begin
if(cnt_byte == 8'd8 && clk_cnt == 5'd31)
state <= DELY;
else
state <= CONFIG1_IN_RD;
end
CONFIG2_INIT:begin
if(cnt_byte == 8'd13 && clk_cnt == 5'd31)
state <= DELY;
else
state <= CONFIG2_INIT;
end
CONFIG2_IN_RD:begin
if(cnt_byte == 8'd18 && clk_cnt == 5'd31)
state <= DELY;
else
state <= CONFIG2_IN_RD;
end
INT_MASK_INIT:begin
if(cnt_byte == 8'd23 && clk_cnt == 5'd31)
state <= DELY;
else
state <= INT_MASK_INIT;
end
INT_MASK_IN_RD:begin
if(cnt_byte == 8'd28 && clk_cnt == 5'd31)
state <= DELY;
else
state <= INT_MASK_IN_RD;
end
CNTR_OVF_H:begin
if(cnt_byte == 8'd33 && clk_cnt == 5'd31)
state <= DELY;
else
state <= CNTR_OVF_H;
end
CNTR_OVF_H_RD:begin
if(cnt_byte == 8'd38 && clk_cnt == 5'd31)
state <= DELY;
else
state <= CNTR_OVF_H_RD;
end
CNTR_OVF_L:begin
if(cnt_byte == 8'd43 && clk_cnt == 5'd31)
state <= DELY;
else
state <= CNTR_OVF_L;
end
CNTR_OVF_L_RD:begin
if(cnt_byte == 8'd48 && clk_cnt == 5'd31)
state <= DELY;
else
state <= CNTR_OVF_L_RD;
end
CONFIG1_START:begin
if(cnt_byte == 8'd53 && clk_cnt == 5'd31)
state <= DELY;
else
state <= CONFIG1_START;
end
CONFIG1_STA_RD:begin
if(cnt_byte == 8'd58 && clk_cnt == 5'd31)
state <= WAITING_TDC;
else
state <= CONFIG1_STA_RD;
end
WAITING_TDC:begin
if(tdc_interrupt_flag)
state <= READING_TDC;
else
state <= WAITING_TDC;
end
READING_TDC:begin
if(cnt_byte == 8'd78 && clk_cnt == 5'd31)
state <= WAITING_RESET;
else if((cnt_byte == 8'd64 || cnt_byte == 8'd71) && clk_cnt == 5'd31)
state <= DELY;
else
state <= READING_TDC;
end
WAITING_RESET:begin
state <= WAITING_RESET;
end
endcase
end
end
初始化各个状态和DELY状态之间的跳转便不再多说了,我们看到WAITING_TDC状态下,若收到TDC测时完成的信号,便跳转到读TDC的状态READING_TDC,在这个状态下根据上一篇文章所述,我们需要读取三个寄存器TIME1、CALI1、CALI2所以有三个读取时序,这三个寄存器都是24位的,所以我们在将地址发送过去之后会有三个数据帧的读取(和之前每帧数据2个周期不同,这里会有4个周期),读取完三个寄存器数据之后计算即可得出时间间隔。
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
bit_cnt <= 4'd0; //because it is mode 0
else
case(cnt_byte)
8'd1,8'd2,8'd6,8'd7,8'd11,8'd12,8'd16,8'd17,8'd21,8'd22,8'd26,8'd27,
8'd31,8'd32,8'd36,8'd37,8'd41,8'd42,8'd46,8'd47,8'd51,8'd52,8'd56,8'd57,
8'd60,8'd61,8'd62,8'd63,8'd67,8'd68,8'd69,8'd70,8'd74,8'd75,8'd76,8'd77:begin
if(sck_cnt == 2'd3) //data updata
bit_cnt <= bit_cnt + 1'd1;
else
bit_cnt <= bit_cnt;
end
default:bit_cnt <= 4'd0;
endcase
end
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
mosi <= 1'b0;
else begin
case(cnt_byte)
8'd1, 8'd2: mosi <= CONFIG1_COMMAND[4'd15 - bit_cnt];
8'd6, 8'd7: mosi <= CONFIG1_READING[4'd15 - bit_cnt];
8'd11, 8'd12: mosi <= CONFIG2_COMMAND[4'd15 - bit_cnt];
8'd16, 8'd17: mosi <= CONFIG2_READING[4'd15 - bit_cnt];
8'd21, 8'd22: mosi <= INTMASK_COMMAND[4'd15 - bit_cnt];
8'd26, 8'd27: mosi <= INTMASK_READING[4'd15 - bit_cnt];
8'd31, 8'd32: mosi <= CNTRO_H_COMMAND[4'd15 - bit_cnt];
8'd36, 8'd37: mosi <= CNTRO_H_READING[4'd15 - bit_cnt];
8'd41, 8'd42: mosi <= CNTRO_L_COMMAND[4'd15 - bit_cnt];
8'd46, 8'd47: mosi <= CNTRO_L_READING[4'd15 - bit_cnt];
8'd51, 8'd52: mosi <= CONFIG1_TO_STAR[4'd15 - bit_cnt];
8'd56, 8'd57: mosi <= CONFIG1_READING[4'd15 - bit_cnt];
8'd60, 8'd61: mosi <= READING_TIME1[4'd15 - bit_cnt];
8'd62, 8'd63: mosi <= READING_SUPP[4'd15 - bit_cnt];
8'd67, 8'd68: mosi <= READING_CALI1[4'd15 - bit_cnt];
8'd69, 8'd70: mosi <= READING_SUPP[4'd15 - bit_cnt];
8'd74, 8'd75: mosi <= READING_CALI2[4'd15 - bit_cnt];
8'd76, 8'd77: mosi <= READING_SUPP[4'd15 - bit_cnt];
default: mosi <= 1'b0;
endcase
end
end
如上述代码所示,我们根据当前周期可以得到需要发送的数据,再根据高位在前的原则把数据发送出去即可。
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
miso_flag <= 1'b0;
else if(sck_cnt == 2'd1 && (cnt_byte == 8'd7 || cnt_byte == 8'd17 || cnt_byte == 8'd27 ||
cnt_byte == 8'd37 || cnt_byte == 8'd47 || cnt_byte == 8'd57 ||
cnt_byte == 8'd61 || cnt_byte == 8'd62 ||
cnt_byte == 8'd63 || cnt_byte == 8'd68 ||
cnt_byte == 8'd69 || cnt_byte == 8'd70 ||
cnt_byte == 8'd75 || cnt_byte == 8'd76 ||
cnt_byte == 8'd77))
miso_flag <= 1'b1;
else
miso_flag <= 1'b0;
end
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
data <= 8'd1;
else if(miso_flag == 1'b1)
data <= {data[6:0],miso};
end
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
data_ok_flag <= 1'b0;
else if((cnt_byte == 8'd7 || cnt_byte == 8'd17 ||
cnt_byte == 8'd27 || cnt_byte == 8'd37 ||
cnt_byte == 8'd47 || cnt_byte == 8'd57 ||
cnt_byte == 8'd61 || cnt_byte == 8'd63 ||
cnt_byte == 8'd68 || cnt_byte == 8'd70 ||
cnt_byte == 8'd75 || cnt_byte == 8'd77) && (bit_cnt == 4'd15) && (miso_flag == 1'b1))
data_ok_flag <= 1'b1;
else if((cnt_byte == 8'd62 ||
cnt_byte == 8'd69 ||
cnt_byte == 8'd76) && bit_cnt == 4'd7 && miso_flag == 1'b1)
data_ok_flag <= 1'b1;
else
data_ok_flag <= 1'b0;
end
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
data_out_flag <= 1'b0;
else
data_out_flag <= data_ok_flag;
end
assign data_out = data_ok_flag ? data : data_out;
因为在SCK下降沿数据被读出,跟警察前面SCK控制时序,当SCK_CNT为0时SCK被拉低,所以我们滞后一个时钟周期将数据读出,也就是通过位拼接实现移位,从而将数据读出,当8位也就是1帧数据被读出之后我们即可得到一次数据,将其传出。
module pulse_sim(
input wire sys_clk,
input wire sys_rst_n,
input wire [7:0] data_in,
output reg start,
output reg stop,
output reg interrupt
);
reg start_flag;
reg [25:0] delay1_reg;
reg interrupt_reg;
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
start_flag <= 1'b0;
else if(data_in == 8'h81)
start_flag <= 1'b1;
else
start_flag <= start_flag;
end
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
delay1_reg <= 26'd0;
else if(delay1_reg == 26'd70) //50_000_020
delay1_reg <= delay1_reg;
else if(start_flag)
delay1_reg <= delay1_reg + 1'd1;
else
delay1_reg <= 26'd0;
end
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)begin
start <= 1'b0;
stop <= 1'b0;
end
else begin
if(delay1_reg == 26'd50) //50_000_000
start <= 1'b1;
else if(delay1_reg == 26'd62) //50_000_012
stop <= 1'b1;
else begin
start <= start;
stop <= stop;
end
end
end
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
interrupt_reg <= 1'b1;
else if(stop)
interrupt_reg <= 1'b0;
else
interrupt_reg <= interrupt_reg;
end
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
interrupt <= 1'b1;
else
interrupt <= interrupt_reg;
end
endmodule
`timescale 1ns/1ns
module tb_tdc_ctrl();
reg sys_clk;
reg sys_rst_n;
reg miso;
always @(posedge sys_clk or negedge sys_rst_n)begin
if(!sys_rst_n)
miso <= 1'b0;
else
miso <= !miso;
end
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk = !sys_clk;
wire start;
wire stop;
wire mosi;
wire cs;
wire sck;
wire tdc_en;
wire [15:0] state = u_tdc_hy.u_tdc_ctrl.state;
wire [7:0] cnt_byte = u_tdc_hy.u_tdc_ctrl.cnt_byte;
wire [1:0] sck_cnt = u_tdc_hy.u_tdc_ctrl.sck_cnt;
wire miso_flag = u_tdc_hy.u_tdc_ctrl.miso_flag;
wire data_ok_flag = u_tdc_hy.u_tdc_ctrl.data_ok_flag;
wire [3:0] bit_cnt = u_tdc_hy.u_tdc_ctrl.bit_cnt;
wire [7:0] data = u_tdc_hy.u_tdc_ctrl.data;
tdc_hy u_tdc_hy(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.miso (miso),
.mosi (mosi),
.cs (cs),
.sck (sck),
.tdc_en (tdc_en),
.start (start),
.stop (stop)
);
endmodule
从使能信号被拉下之后的初始化,CS信号、SCK信号以及MOSI都正常。
START和STOP信号产生之后我们模拟了一个INTURRUPT信号,之后就是三个24bit的读取,仿真通过,接下来我们上板子来测试一下。
由于屏幕显示较为复杂,暂时先不写了,就先用SignalTap来显示一下结果。然后由于身边暂时没有时间间隔产生仪器,所以就先用FPGA自身产生脉冲来作为被测脉冲了。START和STOP间隔设为240ns(12个时钟周期)。
如上图所示为读到的数据,根据上篇文章提到的公式计算:
calCount = (cali2_data - cali1_data) / (2 - 1);
normLSB = CLOCKperiod / calCount;
TOP = TIME * normLSB;
所以有calCount = 289,236,normLSB = (1 / 16M) / 289,236, TOP = 1110528 * normLSB = 0.23997us = 239.97ns。
误差:abs(239.97 - 240.00) / 240 = 0.125‰;
更换时间间隔为200ns,再次测得数据如下所示:
同样的计算方法:
calCount = (cali2_data - cali1_data) / (2 - 1);
normLSB = CLOCKperiod / calCount;
TOP = TIME * normLSB;
有calCount = 289,237,normLSB = (1 / 16M) / 289,237, TOP = 926720 * normLSB = 0.20025us = 200.25ns。
误差:abs(200.25 -200.00) / 200 = 1.25‰;
(1)逻辑门时间可能会有延迟;
(2)TDC晶振本身精度有限;
(3)没有差分输入+杜邦线传输时钟脉冲导致信号走时不等。
附上本文工程代码,本文采用Altera的CYCLONE4系列EP4CE22作为控制器,编译环境为Quartus II 13.1。
链接:https://pan.baidu.com/s/1VQ-v_mxFfIkhN9Qj9XtEBQ
提取码:dpwe