搜索
bottom↓
回复: 11

FPGA驱动DS18b20&红外解码

[复制链接]

出0入0汤圆

发表于 2014-1-10 23:58:12 | 显示全部楼层 |阅读模式
FPGA驱动DS18b20的设计(与仿真)
一.任务解析
    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;      
  reg irda_reg1;      
  reg irda_reg2;      
  wire irda_neg_pulse;
  wire irda_pos_pulse;
  wire irda_chang;   
  
  reg[15:0] cnt_scan;
   
  always @ (posedge clk) //在此采用跟随寄存器
    if(!rst_n)
      begin
        irda_reg0 <= 1'b0;
        irda_reg1 <= 1'b0;
        irda_reg2 <= 1'b0;
      end
    else
      begin
        led_cs <= 4'b0000; //是数码管的位选择处于导通状态
        irda_reg0 <= IR;
        irda_reg1 <= irda_reg0;
        irda_reg2 <= irda_reg1;
      end
     
  assign irda_chang = irda_neg_pulse | irda_pos_pulse;  //IR接收信号的改变,上升或者下降
  assign irda_neg_pulse = irda_reg2 & (~irda_reg1);  //IR接收信号irda下降沿
  assign irda_pos_pulse = (~irda_reg2) & irda_reg1;      //IR接收信号irda上升沿


  reg [10:0] counter;  //分频1750次
  reg [8:0]  counter2; //计数分频后的点数
  wire check_9ms;  // check leader 9ms time
  wire check_4ms;  // check leader 4.5ms time
  wire low;        // check  data="0" time
  wire high;       // check  data="1" time

  //----------------------------------------------------------------------------
  //分频1750计数
  always @ (posedge clk)
    if (!rst_n)
      counter <= 11'd0;
    else if (irda_chang)  //irda电平跳变了,就重新开始计数
      counter <= 11'd0;
    else if (counter == 11'd1750)
      counter <= 11'd0;
    else
      counter <= counter + 1'b1;
  
  //----------------------------------------------------------------------------
  always @ (posedge clk)
    if (!rst_n)
      counter2 <= 9'd0;
    else if (irda_chang)  //irda电平跳变了,就重新开始计点
      counter2 <= 9'd0;
    else if (counter == 11'd1750)
      counter2 <= counter2 +1'b1;
  

  assign check_9ms = ((217 < counter2) & (counter2 < 297));
  //257  为了增加稳定性,取一定范围
  assign check_4ms = ((88 < counter2) & (counter2 < 168));  //128
  assign low  = ((6 < counter2) & (counter2 < 26));         // 16
  assign high = ((38 < counter2) & (counter2 < 58));        // 48

  //----------------------------------------------------------------------------
  // generate statemachine  状态机
    parameter IDLE       = 3'b000, //初始状态
              LEADER_9   = 3'b001, //9ms
              LEADER_4   = 3'b010, //4ms
              DATA_STATE = 3'b100; //传输数据

  always @ (posedge clk)
    if (!rst_n)
      cs <= IDLE;
    else
      cs <= ns; //状态位
     
  always @ ( * )
    case (cs)
      IDLE:
        if (~irda_reg1)
          ns = LEADER_9;
        else
          ns = IDLE;
   
      LEADER_9:
        if (irda_pos_pulse)   //leader 9ms check
          begin
            if (check_9ms)
              ns = LEADER_4;
            else
              ns = IDLE;
          end
        else  //完备的if---else--- ;防止生成latch
          ns =LEADER_9;
   
      LEADER_4:
        if (irda_neg_pulse)  // leader 4.5ms check
          begin
            if (check_4ms)
              ns = DATA_STATE;
            else
              ns = IDLE;
          end
        else
          ns = LEADER_4;
   
      DATA_STATE:
        if ((data_cnt == 6'd32) & irda_reg2 & irda_reg1)
          ns = IDLE;
        else if (error_flag)
          ns = IDLE;
        else
          ns = DATA_STATE;
      default:
        ns = IDLE;
    endcase

  //状态机中的输出,用时序电路来描述
  always @ (posedge clk)
    if (!rst_n)
      begin
        data_cnt <= 6'd0;
        get_data <= 32'd0;
        error_flag <= 1'b0;
      end
  
    else if (cs == IDLE)
      begin
        data_cnt <= 6'd0;
        get_data <= 32'd0;
        error_flag <= 1'b0;
      end
  
    else if (cs == DATA_STATE)
      begin
        if (irda_pos_pulse)  // low 0.56ms check
          begin
            if (!low)  //error
              error_flag <= 1'b1;
          end
        else if (irda_neg_pulse)  //check 0.56ms/1.68ms data 0/1
          begin
            if (low)
              get_data[0] <= 1'b0;
            else if (high)
              get_data[0] <= 1'b1;
            else
              error_flag <= 1'b1;
            
            get_data[31:1] <= get_data[30:0];
            data_cnt <= data_cnt + 1'b1;
          end
      end

  always @ (posedge clk)
    if (!rst_n)
      irda_data <= 16'd0;
    else if ((data_cnt ==6'd32) & irda_reg1)
  begin
   led1 <= get_data[7:0];  //数据反码
   led2 <= get_data[15:8]; //数据码
   led3 <= get_data[23:16];//用户码
   led4 <= get_data[31:24];
  end

//把遥控器的按下的键在数码管上面显示出来
always@(led2)
begin
        case(led2)
       
                             //在数码管上面显示0到9
        8'b01101000: //遥控板0的码值
                        led_db=8'b1100_0000;  //显示0

                8'b00110000: //遥控板1的码值
                        led_db=8'b1111_1001;  //显示1

                8'b00011000: //遥控板2的码值
                        led_db=8'b1010_0100;  //显示2

                8'b01111010: //遥控板3的码值
                        led_db=8'b1011_0000;  //显示3

                8'b00010000: //遥控板4的码值
                        led_db=8'b1001_1001;  //显示4

                8'b00111000: //遥控板5的码值
                        led_db=8'b1001_0010;  //显示5

                8'b01011010: //遥控板6的码值
                        led_db=8'b1000_0010;  //显示6

                8'b01000010: //遥控板7的码值
                        led_db=8'b1111_1000;  //显示7

                8'b01001010: //遥控板8的码值
                        led_db=8'b1000_0000;  //显示8

                8'b01010010: //遥控板9的码值
                        led_db=8'b1001_0000;  //显示9
                       
          //在没有按下键时,显示F
           default: led_db=8'b1000_1110;

         endcase
end

endmodule




经验总结:彻底感受到fpga功能的强大,虽然在处理乘除法的能力上fpga能力赶不上单片机,但是fpga完全可以构造出一个能力很强大的cpu,这就需要我们开动脑筋努力创造了,往后在EDA上还是要多花些功夫才行啊,呵呵!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

阿莫论坛20周年了!感谢大家的支持与爱护!!

曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……

出0入0汤圆

 楼主| 发表于 2014-1-11 00:04:12 | 显示全部楼层
附上程序,感觉论坛了玩fpga的显得有些冷清,平时潜水的时间比较多,一有好的东西就想拿来和大家一起分享呵呵,我也只是个初学者,微博李_Tech,平时都在线,求互粉啦,大家可以一起交流,代码备注很详细,阿莫老大,还请多多指教哦,初来乍到,不懂的地方多多包涵!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2014-1-11 05:43:56 | 显示全部楼层
支持下楼主。

出0入0汤圆

发表于 2014-1-11 11:28:43 来自手机 | 显示全部楼层
我也是刚开始学习fpga,感觉到在一些方面比stm32强大,思路要改变

出0入0汤圆

发表于 2014-1-13 14:04:59 | 显示全部楼层
下载,研究研究,谢谢楼主

出0入0汤圆

发表于 2014-1-13 14:40:10 来自手机 | 显示全部楼层
多谢楼主分享 ,但是  fpga处理乘除法赶不上单片机这句话全然错了

出0入0汤圆

 楼主| 发表于 2014-1-14 08:13:28 | 显示全部楼层
zkf0100007 发表于 2014-1-13 14:40
多谢楼主分享 ,但是  fpga处理乘除法赶不上单片机这句话全然错了

用乘除法会占好多资源,我是这么认为的,呵呵

出0入0汤圆

 楼主| 发表于 2014-1-14 08:13:54 | 显示全部楼层
cj85 发表于 2014-1-13 14:04
下载,研究研究,谢谢楼主

客气了。一起加油!

出0入0汤圆

 楼主| 发表于 2014-1-14 08:14:35 | 显示全部楼层
gonggu8181 发表于 2014-1-11 11:28
我也是刚开始学习fpga,感觉到在一些方面比stm32强大,思路要改变

恩恩!一起加油

出0入0汤圆

发表于 2014-1-14 15:47:36 | 显示全部楼层
mark!

出0入0汤圆

发表于 2014-1-16 23:10:53 | 显示全部楼层
单枪舞九州 发表于 2014-1-14 08:13
用乘除法会占好多资源,我是这么认为的,呵呵

可以用硬件乘法器

出0入0汤圆

发表于 2014-6-26 19:11:45 | 显示全部楼层
顶一下。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-8-27 00:17

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表