基于FPGA的DS2431 DS2408读写操作(1-wire多器件)

综述

前段有个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

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进行操作。

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
  • 32
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值