logo资料库

FPGA驱动DS18b20的设计(与仿真).pdf

第1页 / 共13页
第2页 / 共13页
第3页 / 共13页
第4页 / 共13页
第5页 / 共13页
第6页 / 共13页
第7页 / 共13页
第8页 / 共13页
资料共13页,剩余部分请下载后查看
FPGA驱动驱动DS18b20的设计(与仿真) 的设计(与仿真) 彻底感受到fpga功能的强大,虽然在处理乘除法的能力上fpga能力赶不上单片机,但是fpga完全可以构造出一个 能力很强大的cpu,这就需要我们开动脑筋努力创造了,往后在EDA上还是要多花些功夫才行啊,呵呵! 一.任务解析 DS18B20是DALLAS公司生产的一线式数字温度传感器,采用3引脚T0-92型小体积封装;温度测量范围为-55℃~+125~C, 可编程为9位~12位A/D转换精度,测温分辨率可达0.0625℃,被测温度用符号扩展的16位数字量方式串行输出。 一线式(1-WIRE)串行总线是利用1条信号线就可以与总线上若干器件进行通信。具体应用中可以利用微处理器的I/O端口对 DS18B20直接进行通信,也可以通过现场可编程门阵列(FPGA)等可编程逻辑器件(PLD)实现对1-WIRE器件的通信。 二.方案论证 DS18B20的内部结构如图1所示,主要由以下几部分组成:64位ROM、温度传感器、非挥发的温度报警触发器TH(温度高)和 TL(温度低)、配置寄存器、暂存寄存器(SCRATCHPAD)、存储器控制逻辑。DQ为数字信号输入/输出端。 ROM中的64(8位产品家族编号、48位ID号、8位CRC)位序列号是出厂前刻好的,这64位序列号具有惟一性,每个DS18B20的 64位序列号均不相同。8位CRC生成器可以完成通信时的校验。暂存寄存器有9个字节,包含温度测量结果、温度报警寄存 器、CRC校验码等内容。 2.1操作步骤 对DS18B20的操作分为3个步骤:初始化、ROM命令和DS18B20功能命令。 2.2.1初始化 FPGA要与DS18B20通信,首先必须完成初始化。FPGA产生复位信号,DS18B20返回响应脉冲。 2.2.2ROM命令 该步骤完成FPGA与总线上的某一具体DS18B20建立联系。ROM命令有搜寻ROM(SEARCH ROM)、读ROM(READ ROM)、 匹配ROM(MATCH ROM)、忽略ROM(SKIP ROM)、报警查找等命令(ALARM SEARCH)。 这里,FPGA只连接1个DS18B20,因此只使用读ROM命令,来读取DS18B20的48位ID号。 2.2.3 DS18B20功能命令 FPGA在该步骤中完成温度转换(CONVERTT)、写暂存寄存器(WRITE SCRATCHPAD)、读暂存寄存器(READ SCRATCHPAD)、拷贝暂存寄存器(COPYSCRATCHPAD)、装载暂存器寄存器(RECALL E2)、读供电模式命令(READ POWER SUPPLY)。 文中不用温度报警功能,因此在本步骤中只需完成温度转换,然后通过读暂存寄存器命令完成温度转化的结果。 2.3操作时序 DS18B20的一线式操作时序如图2所示。从时序图中可以看出,对DS18B20的操作时序要求比较严格。利用FPGA可以实现这 些操作时序。 3 FPGA与DS18B20的通信 3.1 DS18B20的操作模块 FPGA需要完成DS18B20的初始化、读取DS18B20的48位ID号、启动DS18B20温度转换、读取温度转化结果。读取48位ID号 和读取温度转换结果过程中,FPGA还要实现CRC校验码的计算,保证通信数据的可靠性。 以上操作反复进行,可以用状态机来实现。状态机的各种状态如下: RESET1:对DS18B20进行第一次复位,然后进入DELAY状态,等待若干时后,进入CMD33状态。 CMD33:对DS18B20发出0×33命令,读取48位ID值。 GET_ID:从DS18B20中读取48位ID值。 RESET2:对DS18B20进行第二次复位,然后进入DELAY状态等待800μs后,进入CMDCC状态。 CMDCC:向DS18B20发出忽略ROM命令,为进入下一状态作准备。 CMD44:向DS18B20发出启动温度转换命令,然后进入DELAY状态等待900ms后进入下一状态。
RESET3:对DS18B20进行第三次复位。 CMDCC2:向DS18B20发出忽略ROM命令,为了进入下一状态作准备。 GET_TEMP:从DS18B20中读取温度测量数值。 DELAY:等待状态。 WRITE_BIT:向DS18B20中写入数据位状态。 READ_BIT:从DS18B20中读取数据位状态。在该状态中每读取1位数据,同时完成该数据位的CRC校验计算。所有数据都读 取后,还要读取8位CRC校验位。这8位校验位也经过CRC校验计算,如果通信没有错误,总的CRC校验结果应该是0。这时 可将通信正确的数据保存到id和temp_data寄存器中。 三.方案实施 主要程序module DS18B20( clk, // 50MHz 时钟 rst_n, // 异步复位 one_wire, // One-Wire 总线 dataout, //数码管的段码输出 en //数码管的位选使能输出 ); input clk; // 50MHz 时钟 input rst_n; // 异步复位 inout one_wire; // One-Wire 总线 output[7:0] dataout; //数码管的段码输出 output[3:0] en ; //数码管的位选使能输出 reg[3:0] en; reg[7:0] dataout; reg[3:0] dataout_buf; reg [17:0] count; //分频计数器 reg [17:0] cnt_scan; //数码管的扫描显示计数器 分频器50MHz->1MHz 开始 reg [5:0] cnt; // 计数子 always@(posedge clk, negedge rst_n) if (!rst_n) cnt <= 0; else if (cnt == 49) cnt <= 0; else cnt <= cnt + 1'b1; reg clk_1us; // 1MHz 时钟 always @ (posedge clk, negedge rst_n)
if (!rst_n) clk_1us <= 0; else if (cnt <= 24) // 24 = 50/2 - 1 clk_1us <= 0; else clk_1us <= 1; 延时模块: reg [19:0] cnt_1us; // 1us 延时计数子 reg cnt_1us_clear; // 请1us 延时计数子 always @ (posedge clk_1us) if (cnt_1us_clear) cnt_1us <= 0; else cnt_1us <= cnt_1us + 1'b1; //-------------------------------------- DS18B20 状态机开始 // 格雷码 parameter S00 = 5'h00; parameter S0 = 5'h01; parameter S1 = 5'h03; parameter S2 = 5'h02; parameter S3 = 5'h06; parameter S4 = 5'h07; parameter S5 = 5'h05; parameter S6 = 5'h04; parameter S7 = 5'h0C; parameter WRITE0 = 5'h0D; parameter WRITE1 = 5'h0F; parameter WRITE00 = 5'h0E; parameter WRITE01 = 5'h0A; parameter READ0 = 5'h0B; parameter READ1 = 5'h09; parameter READ2 = 5'h08; parameter READ3 = 5'h18; reg [4:0] state; // 状态寄存器 reg one_wire_buf; // One-Wire 总线缓存寄存器
reg [15:0] temperature_buf; // 采集到的温度值缓存器(未处理) reg [15:0] DS18B20_DATA_buf; // 采集到的温度值缓存器(未处理) reg [5:0] step; // 子状态寄存器 0~50 // 写状态机 WRITE0 : begin cnt_1us_clear <= 0; one_wire_buf <= 0; // 输出0 if (cnt_1us == 80) // 延时80us begin cnt_1us_clear <= 1; one_wire_buf <= 1'bZ; // 释放总线,自动拉高 state <= WRITE00; end end WRITE00 : // 空状态 state <= S5; WRITE01 : // 空状态 state <= WRITE1; WRITE1 : begin cnt_1us_clear <= 0; one_wire_buf <= 1'bZ; // 输出1 释放总线,自动拉高 if (cnt_1us == 80) // 延时80us begin cnt_1us_clear <= 1; state <= S5; end End 中间有50个状态机的程序,限于篇幅就不一一介绍了! // 读状态机 READ0 : state <= READ1; // 空延时状态 READ1 : begin cnt_1us_clear <= 0; one_wire_buf <= 1'bZ; // 释放总线 if (cnt_1us == 10) // 再延时10us
begin cnt_1us_clear <= 1; state <= READ2; end end READ2 : // 读取数据 begin temperature_buf[bit_valid] <= one_wire; state <= READ3; end READ3 : begin cnt_1us_clear <= 0; if (cnt_1us == 55) // 再延时55us begin cnt_1us_clear <= 1; state <= S7; end end // 读状态机 default : state <= S00; endcase end end assign one_wire = one_wire_buf; // 注意双向口的使用 // 对采集到的温度进行处理开始 wire [15:0] t_buf = temperature_buf & 16'h07FF; // 对采集到的温度进行处理计算 always@(posedge clk or negedge rst_n) begin if(!rst_n) begin DS18B20_DATA_buf[15:0] =16'b0000000000000000; //复位后全部清零 end else begin DS18B20_DATA_buf[3:0] = (t_buf[3:0] * 10) >> 4; // 小数点后一位
DS18B20_DATA_buf[7:4] = (t_buf[7:4] >= 10) ? (t_buf[7:4] - 10) : t_buf[7:4]; // 个位 DS18B20_DATA_buf[11:8] = (t_buf[7:4] >= 10) ? (t_buf[11:8] + 1) : t_buf[11:8]; // 十位 // DS18B20_DATA_buf[15:12] = temperature_buf[12] ? 1 : 0; // 这里是显示正数还是负数的。我们做正数处理 end end //分频计数器 always @ ( posedge clk or negedge rst_n) begin if(!rst_n) begin count<=0; end else begin count<=count+1; //这里这个计数器的目的是为了在显示数码管的十位 个位 小数点 小数点后一位同步 //我们在下面的数码管显示扫描也用到了一样位宽的计数器。 end end ///////////////////////////////////////////////////////////////////////////////////////////////// always @ ( posedge clk) begin case ( count[17:16] ) // case ( count[17:16] )这一句希望初学者看明白, // 也是分频的关键 // 通过计数器分频,在相同时间间隔内,显示温度的十位个位 小数点 小数点后一位 0: dataout_buf<=DS18B20_DATA_buf[3:0]; //小数点后 1: dataout_buf<=4'b1010; //小数点 2: dataout_buf<=DS18B20_DATA_buf[7:4]; //个位 3: dataout_buf<=DS18B20_DATA_buf[11:8]; //十位 endcase end //下面是数码管扫描显示 //分频计数器 always@(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt_scan<=0;
end else begin cnt_scan<=cnt_scan+1; //这里这个计数器的目的是为了在显示数码管的十位 个位 小数点 小数点后一位和读取DS18B20 的温度值同步 //我们在上面分时段的读取DS18B20 的温度值也用到了一样位宽的计数器。 end end always @(cnt_scan) begin case(cnt_scan[17:16]) //case 语句的功能是把数码管轮流点亮。 //修改cnt_scan[17:16],可以修改数码管的显示频率。 3'b000 : en = 4'b1110; //点亮第一位数码管 .显示小数点后面一位 3'b001 : en = 4'b1101; //点亮第二位数码管,但是他的目的是显示小数点 3'b010 : en = 4'b1101; //点亮第二位数码管,但是他的目的是显示个位 3'b011 : en = 4'b1011; //点亮第三位数码管,是显示十位 default : en = 4'b1111; //点亮第八位数码管 endcase end always@(dataout_buf) begin case(dataout_buf) 4'b0000: dataout=8'b1100_0000; //共阳数码管显示0 的段码 4'b0001: dataout=8'b1111_1001; //共阳数码管显示1 的段码 4'b0010: dataout=8'b1010_0100; //共阳数码管显示2 的段码 4'b0011: dataout=8'b1011_0000; //共阳数码管显示3 的段码 4'b0100: dataout=8'b1001_1001; //共阳数码管显示4 的段码
4'b0101: dataout=8'b1001_0010; //共阳数码管显示5 的段码 4'b0110: dataout=8'b1000_0010; //共阳数码管显示6 的段码 4'b0111: dataout=8'b1111_1000; //共阳数码管显示7 的段码 4'b1000: dataout=8'b1100_0000; //共阳数码管显示8 的段码 4'b1001: dataout=8'b1001_0000; //共阳数码管显示9 的段码 4'b1010: dataout=8'b0111_1111; //共阳数码管显示小数点的段码 default: dataout=8'b1000_0000; endcase end endmodule 四.经验总结 程序的思想不难,从最先开始参考了好几个论坛的帖子,到逐渐学会用状态机来写,到最后一步一步,摸着石头过河整理出完 整的程序,不敢说全部都是完完全全的原创,但还是花了很多心思在上面,也明白写程序上还存在许多欠缺,希望以后一步一 个脚印,继续努力加油!在这里特别感谢大学生电子实验室和电子园这两个论坛,有很多思想和程序都是参考了各位前辈的, 文章的很多内容也借鉴了他们的劳动成果,在此特别感谢! Fpga驱动的红外解码也是模仿单片机的中断写的: module IR(clk,rst_n,IR,led_cs,led_db); input clk; input rst_n; input IR; output [3:0] led_cs; output [7:0] led_db; reg [3:0] led_cs; reg [7:0] led_db; reg [7:0] led1,led2,led3,led4; reg [15:0] irda_data; reg [31:0] get_data; reg [5:0]data_cnt; reg [2:0]cs,ns; reg error_flag; //---------------------------------------------------------------------------- reg irda_reg0;
分享到:
收藏