综述
前段有个FPGA的开发任务,用到了ADI的DS2431和DS2408芯片,涉及到的难点有1-wire的读写操作和多个1-wire器件共用一根线进行通信,于是写一篇文章记录使用芯片中遇到的困难。
一、1-Wire概述
1-Wire总线是一种简单的信号交换架构,通过一条线路在主机与外围器件之间进行双向通信。所有的1-Wire总线都具有一个共同的特征:无论是芯片内还是iButton内,每个器件都有一个互不重复的、工厂光刻的序列号,因此,每个器件都是唯一的。这样就允许从众多连到同一总线的器件中独立选择任何一个器件。当1个、2个甚至多个1-Wire器件能共用一条线路进行通信,可以采用二进制位检索法依次查找每一个器件。一旦器件的序列号已知,通过寻址该序列号,就可以唯一地选出该器件进行通信。1-Wire软件资源指南和器件说明.
二、1-wire器件读写时序(DS2431 DS2408)
1-wire通信因为器件只有一根线,无法像SPI,IIC这样通过时钟同步数据来通信,只能通过一个判断周期内高低电平的时间来控制数据是0还是1。在正常速度下,发送1bit数据最小需要65us的时间,可以根据需要适当延长时间。
总线1-wire接口定义输入输出三态门接口, out_data_en
为1时总线接口为输出, out_data_en
为0时总线接口为输入。
inout ds2431_data;
//总线输入输出定义
reg out_data_en; //输出使能
wire in_data;
assign in_data = ds2431_data; //输入信号连续赋值
reg out_data; //输出数据寄存器
assign ds2431_data = out_data_en?out_data:1'bz;
而在正常速度下主机发送1bit“0”数据的周期内,低电平需要有60-120us;发送1bit“1”数据的周期内,低电平需要有1-15us,控制好时间,使主机发送“0”和“1”的周期尽量相同。
因为在整个读写中涉及到的发送、接收比较多,本人是FPGA开发新人,理不清更复杂的时序逻辑,故整个读写过程用状态机配合调用任务来实现,以下为主机发送1字节任务示例。
task master_tx; //主机发送数据
input [7:0]tend_data; //要发送的数据
reg [5:0]tx_state;
reg [9:0]tx_cnt; //发送计时寄存器
reg [3:0]i; //循环变量寄存器
case(tx_state)
state_0:begin
tx_over <= 0;
tx_state <= state_1;
i <= 0;
tx_cnt <= 0;
end
state_1:begin
if(i==8)begin //发送完毕
i <= 0;
tx_state <= state_6;
tx_over <= 1;
out_data_en <= 0;
end
else begin //未发送完毕
out_data_en <= 1;
out_data <= 0;
if(tend_data[i])
tx_state <= state_2;
else
tx_state <= state_4;
end
end
state_2:begin
if(tx_cnt == 24)begin //5us低电平
tx_state <= state_3;
tx_cnt <= 0;
end
else begin
out_data <= 0;
tx_state <= tx_state;
tx_cnt <= tx_cnt + 1;
end
end
state_3:begin
if(tx_cnt == 324)begin //65us高电平
tx_state <= state_1;
tx_cnt <= 0;
i <= i + 1;
end
else begin
out_data <= 1;
tx_state <= tx_state;
tx_cnt <= tx_cnt + 1;
end
end
state_4:begin
if(tx_cnt == 324)begin //65us低电平
tx_state <= state_5;
tx_cnt <= 0;
end
else begin
tx_state <= tx_state;
tx_cnt <= tx_cnt + 1;
out_data <= 0;
end
end
state_5:begin
if(tx_cnt == 24)begin //5us高电平
tx_state <= state_1;
tx_cnt <= 0;
i <= i + 1;
end
else begin
tx_state <= tx_state;
tx_cnt <= tx_cnt + 1;
out_data <= 1;
end
end
state_6:begin
tx_over <= 0;
tx_state <= state_7;
end
default:tx_state <= state_0;
endcase
endtask
与之对应的是主机接收1-wire器件返回的数据,主机一个周期开始的15us读取数据,读到的电平高低即为1-wire器件发送的数据“1”或“0”。
以下为主机接收1字节任务示例。
task master_rx; //主机接收数据任务
output reg [7:0]read_data; //接收数据
reg [5:0]rx_state;
reg [20:0]rx_cnt; //接收数据寄存器
reg [3:0]i; //循环寄存器
case(rx_state)
state_0:begin
rx_over <= 0;
i <= 0;
rx_state <= state_1;
rx_cnt <= 0;
end
state_1:begin
if(i<8)begin //未读取完毕
out_data_en <= 1;
out_data <= 1;
rx_state <= state_2;
end
else begin //读取数据完毕
rx_state <= state_5;
i <= 0;
rx_over <= 1;
end
end
state_2:begin
if(rx_cnt == 25)begin
out_data_en <= 0;
rx_state <= state_3;
rx_cnt <= 0;
end
else begin
out_data <= 0;
rx_cnt <= rx_cnt + 1;
rx_state <= rx_state;
end
end
state_3:begin
if(rx_cnt == 50)begin //10us采样
rx_state <= state_4;
rx_cnt <= 0;
if(in_data)
read_data[i] <= 1;
else if(!in_data)
read_data[i] <= 0;
else
read_data[i] <= 1'bz;
end
else begin
rx_cnt <= rx_cnt + 1;
rx_state <= rx_state;
end
end
state_4:begin
if(rx_cnt == 300)begin //60us延迟
rx_state <= state_1; //重复7次
rx_cnt <= 0;
i <= i + 1;
end
else begin
rx_cnt <= rx_cnt + 1;
rx_state <= rx_state;
end
end
state_5:begin
rx_over <= 0;
out_data_en <= 1;
out_data <= 1;
rx_state <= state_6;
end
default:rx_state <= state_0;
endcase
endtask
三、1-wire器件复位操作
从空闲状态唤醒时,1-Wire总线电压需要从VPUP降到VTL门限电压以下。从工作状态返回空闲状态时,电压需要从VLMAX上升至VTH门限电压以上。电压上升时间在图10中用ε表示,持续时间取决于所使用的上拉电阻(RPUP和1-Wire网络的附加电容。DS2431根据VMAx电压判断逻辑电平,不会触发任何事件。
这里复位的方式是在主机拉低1-wire总线的过程中,看是否接收到1-wire器件返回的高电平,来确定器件是否复位成功。正常速度下主机拉低1-wire总线在480-640us之间,1-wire器件返回的高电平时间在15-60us之间。
task reset; //复位任务
reg [3:0]rst_state;
reg [20:0]rst_cnt; //定义复位计时器
case(rst_state)
state_0:begin
rst_over <= 0;
out_data_en <= 1; //总线输出模式
out_data <= 0; //初始总线拉低
rst_cnt <= 0;
rst_state <= state_1;
end
state_1:begin
if(rst_cnt==2499)begin //拉低500us
rst_cnt <= 0;
rst_state <= state_2;
end
else begin
rst_cnt <= rst_cnt + 1;
rst_state <= rst_state;
end
end
state_2:begin
if(rst_cnt==74)begin //拉高15us
out_data_en <= 0; //总线改为输入模式
rst_cnt <= 0;
rst_state <= state_3;
end
else begin
out_data <= 1;
rst_cnt <= rst_cnt + 1;
end
end
state_3:begin
if(!in_data)begin
rst_state <= state_4;
end
else begin
if(rst_cnt==1199)begin //检测240us后仍无脉冲应答
rst_state <= state_0;
rst_cnt <= 0;
end
else begin
rst_cnt <= rst_cnt + 1;
rst_state <= rst_state;
end
end
end
state_4:begin
if(rst_cnt == 1499)begin //持续300us
rst_state <= state_5;
rst_cnt <= 0;
end
else begin
rst_cnt <= rst_cnt + 1;
rst_state <= rst_state;
end
end
state_5:begin
rst_over <= 1;
rst_state <= state_6;
end
default:begin
rst_state <= 0;
rst_over <= 0;
end
endcase
endtask
四、1-wire器件通讯协议
以下为DS2431的通讯协议,兼容ADI的其他1-wire器件;针对1-wire有多个器件来说的话,初次的操作指令应当为:复位-ROM寻址-指定某一器件ROM操作
,在寄存器内存储ROM地址之后,即可进行:复位-指定某一器件ROM操作
五、ROM寻址
每个DS2431都有一个唯一的64位ROM码,其中前8位是1-Wire家族码,中间48位是唯一的序列号,最后8位是前56位的循环冗余校验(CRC)码,详见图3所示。
对于1-wire器件来说,每个系列的器件都有各自独立的家族码,DS2431为2Dh,DS2408则为29h。
Search ROM【F0h
】系统刚启动时,总线主机可能并不知道1-Wire总线上挂接的器件数量及它们的注册码。主机可利用总线的线与特性,采用排除法来识别总线上所有从机的注册码。针对最低有效位在前的注册码的每一位,总线主机都发送三个时隙
。在第一个时隙,每个参与搜索的从机都输出各自注册码位的原码。在第二个时隙,每个参与搜索的从机都输出各自注册码位的补码值。在第三个时隙,主机写人所选位的原码。所有与由主机写人的该位不匹配的从机都不再参加搜索。如果主机两次读到的值均是0,则说明从机该位的两个状态都存在。总线主机通过写人的状态值来选择搜索ROM码树的不同分支。经过一次完整搜索过程,总线主机即可知道某个从机的注册码。另外的搜索过程可以识别其余从机的注册码。1-Wire搜索算法 | Analog Devices文章来源:https://www.toymoban.com/news/detail-845647.html
reg [1:0]return_state; //1bit数据
reg [6:0]rom_cnt1,rom_cnt2; //1-wire 器件rom码计时器
reg [63:0]rom_number1,rom_number2; //1wire 器件64位rom
reg [63:0]DS2408ROM,DS2431ROM; //两芯片ROM
reg roms_over;
task rom_sch; //rom搜寻
reg [4:0]rom_state;
reg [20:0]rom_cnt;
case(rom_state)
state_0:begin
rom_state <= state_1;
end
state_1:begin
reset; //复位
if(rst_over)begin
rom_state <= state_2;
end
else
rom_state <= rom_state;
end
state_2:begin
master_tx(8'hf0); //发出读取rom命令
if(tx_over)
rom_state <= state_3;
else
rom_state <= rom_state;
end
state_3:begin //而如果读到的位格式为 01、10 或 00,则表明1 wire总线有器件
master_rx_2bit(return_state);//发送2
if(rx_over)begin
rom_state <= state_4;
end
else
rom_state <= rom_state;
end
state_4:begin
if(return_state==2'b01|return_state==2'b10)begin
rom_state <= state_5; //检测到按键按下
end
else if(return_state==0)begin
rom_state <= state_5; //记录分叉的数据
rom_number2 <= rom_number1;
rom_cnt2 <= rom_cnt1;
end
else begin //检测未按下按键
rom_state <= state_1;
end
end
state_5:begin
master_tx_1bit(return_state[0]); //发送读取rom地址原码
if(tx_over)begin
if(rom_cnt1 < 63)begin
rom_cnt1 <= rom_cnt1 + 1;
rom_number1[rom_cnt1] <= return_state[0];
rom_state <= state_3;
end
else if(rom_cnt1 == 63)begin
rom_number1[rom_cnt1] <= return_state[0];
rom_state <= state_6;
end
else begin
rom_cnt1 <= rom_cnt1;
rom_state <= state_0;
end
end
else
rom_state <= rom_state;
end
state_6:begin
if(rom_cnt2 > 0)begin
rom_cnt1 <= 0;
rom_state <= state_7; //若有分岔口,返回寻找分岔口
end
else begin
rom_cnt1 <= 0;
rom_state <= state_13; //ROM选择
//调试
//rom_state <= state_0;
end
end
state_7:begin //100
reset; //复位
if(rst_over)begin
rom_state <= state_8;
end
else
rom_state <= rom_state;
end
state_8:begin
master_tx(8'hf0); //发出读取rom命令
if(tx_over)
rom_state <= state_9;
//调试
//rom_state <= state_6;
else
rom_state <= rom_state;
end
state_9:begin //而如果读到的位格式为 01、10 或 00,则表明1 wire总线有器件
master_rx_2bit(return_state);
if(rx_over)
rom_state <= state_10;
else
rom_state <= rom_state;
end
state_10:begin
if(return_state==2'b01|return_state==2'b10)begin
rom_state <= state_11; //检测到按键按下
end
else if(return_state==2'b00)begin
rom_state <= state_12; //记录分叉的数据
rom_number2 <= rom_number1;
rom_cnt2 <= rom_cnt1;
end
else begin //检测未按下按键
rom_state <= state_1;
end
end
state_11:begin
master_tx_1bit(return_state[0]); //发送读取rom地址原码
if(tx_over)begin
if(rom_cnt1 < 63)begin
rom_cnt1 <= rom_cnt1 + 1;
rom_number2[rom_cnt1] <= return_state[0];
rom_state <= state_9;
end
else if(rom_cnt1 == 63)begin
rom_number2[rom_cnt1] <= return_state[0];
rom_state <= state_6;
rom_cnt2 <= 0;
end
else begin
rom_cnt1 <= rom_cnt1;
rom_state <= state_0;
end
end
else
rom_state <= rom_state;
end
state_12:begin
master_tx_1bit(1); //发送读取rom地址反码
if(tx_over)begin
if(rom_cnt1 < 63)begin
rom_cnt1 <= rom_cnt1 + 1;
rom_number2[rom_cnt2] <= 1;
rom_state <= state_9;
end
else if(rom_cnt1 == 63)begin
rom_number2[rom_cnt2] <= 1;
rom_state <= state_6;
rom_cnt2 <= 0;
end
else begin
rom_cnt1 <= rom_cnt1;
rom_state <= state_0;
end
end
else
rom_state <= rom_state;
end
state_13:begin//rom赋值
if((rom_number1[7:0]==8'h2D)&(rom_number2[7:0]==8'h29))begin
DS2431ROM <= rom_number1;
DS2408ROM <= rom_number2;
rom_state <= state_14;
roms_over <= 1;
end
else if((rom_number1[7:0]==8'h29)&(rom_number2[7:0]==8'h2D))begin
DS2431ROM <= rom_number2;
DS2408ROM <= rom_number1;
rom_state <= state_14;
roms_over <= 1;
end
else begin
rom_state <= state_1; //重新复位
end
end
state_14:begin
roms_over <= 0;
rom_state <= 0;
end
default:begin
roms_over <= 0;
rom_state <= 0;
end
endcase
endtask
六、主状态机
在搜寻完ROM地址后,在ROM命令里即可发送特定命令55h
来指定某一器件;本文仅指定DS2431的ROM进行操作。文章来源地址https://www.toymoban.com/news/detail-845647.html
reg [6:0]ds_state; //主状态机0-127
always@(posedge clk or negedge rst_n)begin//主状态机变换
if(!rst_n)begin
ds_state <= 0;
end
else begin
if(rst_over&ds_state!=1)//寻址中状态机不变
ds_state <= ds_state + 1;
else if(tx_over&ds_state!=1)
ds_state <= ds_state + 1;
else if(rx_over&ds_state!=1)
ds_state <= ds_state + 1;
else if(skip_over&ds_state!=1)
ds_state <= ds_state + 1;
else if(roms_over&ds_state==1)//寻址完成
ds_state <= state_2;
else
ds_state <= ds_state;
end
end
reg [20:0]state_cnt; //主状态机定时器
reg [15:0]crc_write; //写crc校验
reg [7:0]ff_write; //ff校验
reg [63:0]reg_data; //暂存器数据
reg [20:0]aa_cnt; //读aa循环前计时器
reg [7:0]AA_loop; //AA循环数据
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
crc_write <= 0;
ff_write <= 0;
rom_data <= 0;
output_data <= 0;
out_data <= 1;
return_state <= 0;
rom_cnt1 <= 0;
rom_cnt2 <= 0;
rom_number1 <= 0;
rom_number2 <= 0;
DS2431ROM <= 0;
DS2408ROM <= 0;
end
else begin
case(ds_state)
state_0:reset;
state_1:rom_sch;
/************************************************************************************/
state_2:reset; //复位
state_3:master_tx(8'h55); //发出读取特定ROM命令
state_4:master_tx_64bit(DS2431ROM); //发出特定ROM地址
state_5:master_tx(8'h0f); //DS2431开始操作
state_6:master_tx(8'h10);
state_7:master_tx(8'h00);
state_8:master_tx(data1); //数据1-8字节
state_9:master_tx(data2);
state_10:master_tx(data3);
state_11:master_tx(data4);
state_12:master_tx(data5);
state_13:master_tx(data6);
state_14:master_tx(data7);
state_15:master_tx(data8);
state_16:master_rx(crc_write[15:8]); //crc校验
state_17:master_rx(crc_write[7:0]); //crc校验
state_18:master_rx(ff_write); //ff校验
/************************************************************************************/
state_19:reset; //复位
state_20:master_tx(8'h55); //发出读取特定ROM命令
state_21:master_tx_64bit(DS2431ROM); //发出特定ROM地址
state_22:master_tx(8'h55); //发出Issue “Copy Scratchpad” command
state_23:master_tx(8'h10); //地址1
state_24:master_tx(8'h00); //地址2
state_25:master_tx(8'h07); //发出ES
state_26:
if(aa_cnt < 29999) //等待数据复制
aa_cnt <= aa_cnt + 1;
else
master_skip;
state_27:begin aa_cnt <= 0;master_rx(AA_loop); end //接收AA循环数据
state_28:
if(AA_loop==8'haa)
master_skip;
else
master_skip;
default:;
/************************************************************************************/
endcase
end
end
到了这里,关于基于FPGA的DS2431 DS2408读写操作(1-wire多器件)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!