Verilog实现iic总线协议

初学者笔记,欢迎讨论(虽然大部分时间可能不在线)

一、iic总线时序
两根线:SDA、SCK
1、空闲状态:SDA、SCK为高电平
2、开始信号:SCK为高电平期间,SDA产生一个下降沿
3、发送数据:SCK为低电平期间,SDA可变
SCK为高电平期间,SDA不可改变,发送信号
4、应答信号:SDA改为输入(高阻状态,通过使能端改变)

下面贴张时序图

在这里插入图片描述
第一个传输的信号为地址位,这里用的是oled
第二个传输的信号为寄存器,一般有指令寄存器和数据寄存器(还有可能更多,小白还没接触太多的)
第三个传输的信号为想要写入的数据,如果是指令的话参考数据手册。

二、Verilog语句的实现
主要通过状态机实现,代码比较长,一般有注释,实现的波形为上方波形

reg tr_start,ack1,ack2,ack3;
reg sda_en;// 决定双向的输入输出  1 为输出 0 为高阻态 为输入
reg[3:0] cnt_count;
reg sclk,reg_sdat; 
reg[7:0] iic_data;
reg[7:0] iic_cmd_data;// 写入的
reg[3:0] byte_num; // 写入的 第几个字节	
always @(posedge clk_200k or negedge rst_n)
	begin
		if (~rst_n)
			begin
				tr_start <= 1;
				sda_en <= 1;
				ack1 <= 1;
				ack2 <= 1;
				ack3 <= 1;
				sclk <= 1;
				reg_sdat <= 1;
				cnt_count <= 0;
				byte_num <= 0;
				state_next <= 0;
			end
		else
			begin
				cnt_count <= cnt_count + 4'b1;
				case(state_next)
				
				0://空闲状态
					begin
						case (cnt_count)
						11:
							begin
								cnt_count <= 0;
								state_next <= 1;
							end
						default:
							begin
								sclk <= 1;
								reg_sdat <= 1;
							end
						endcase
					end
				1:// 开始传输
					begin
						tr_start <= 1;
						
						case (cnt_count)
						0,1,2:
							begin
								reg_sdat <= 1;
								sclk <= 1;
							end
						
						3,4,5,6,7,8:
							begin
								sclk <= 1;
								reg_sdat <= 0;// 拉低电位 开始传输
							end
						
						9,10:
							begin
								sclk <= 0;
								reg_sdat <= 0;
							end
						default://11
							begin
								sclk <= 0;
								reg_sdat <= 0;
								state_next <= 2;
								cnt_count <= 0; 
							end
						endcase
					end
				  
				2://写入地址
					begin 
						reg_sdat <= address[7-byte_num];
						
						case (cnt_count)
						0,1,2:
							begin
								sclk <= 0;
							end
						
						3,4,5,6,7,8:
							begin
								sclk <= 1;
							end
						
						9,10:
							begin
								sclk <= 0;
							end
						default://11
							begin
								sclk <= 0;
								cnt_count <= 0;
								
								if (byte_num == 7)//写完所有地址位
									begin
										byte_num <= 0;
										state_next <= 3;//进入应答状态
									end
								else
									begin
										byte_num <= byte_num + 1'b1;
									end
							end
						endcase
					end
					
					3://第一个应答信号
						begin
							case (cnt_count)
							0,1,2:
								begin
									reg_sdat <= 1;
									sclk <= 0;
									sda_en <= 0;//高阻态
								end
						
							3,4,5,6,7,8:
								begin
									reg_sdat <= 0;
									sclk <= 1;
									sda_en <= 0;
								end
						
							9,10:
								begin
									sclk <= 0;
									reg_sdat <= 0;
									sda_en <= 0;
									ack1 <= iic_sda;//读取应答信号
								end
							default://11
								begin
									sclk <= 0;
									reg_sdat <= 0;
									sda_en <= 1;//重新变为输出
									state_next <= 4;//下一个状态
									ack1 <= iic_sda;
									cnt_count <= 0;
								end
							endcase
						end
					 
					 4://           第二个数据  读或写
						begin 
						reg_sdat <= iic_cmd_data[7-byte_num];
						
						case (cnt_count)
						0,1,2:
							begin
								sclk <= 0;
							end
						
						3,4,5,6,7,8:
							begin
								sclk <= 1;
							end
						
						9,10:
							begin
								sclk <= 0;
							end
						default://11
							begin
								sclk <= 0;
								cnt_count <= 0;
								
								if (byte_num == 7)//写完所有地址位
									begin
										byte_num <= 0;
										state_next <= 5;//进入应答状态
									end
								else
									begin
										byte_num <= byte_num + 1'b1;
									end
							end
						endcase
					end
						
					5: // 第二个应答信号
						begin
							case (cnt_count)
							0,1,2:
								begin
									reg_sdat <= 0;
									sclk <= 0;
									sda_en <= 0;//高阻态
								end
						
							3,4,5,6,7,8:
								begin
									reg_sdat <= 0;
									sclk <= 1;
									sda_en <= 0;
								end
						
							9,10:
								begin
									sclk <= 0;
									reg_sdat <= 0;
									sda_en <= 0;
									ack2 <= iic_sda;//读取应答信号
								end
							default://11
								begin
									sclk <= 0;
									reg_sdat <= 0;
									sda_en <= 1;//重新变为输出
									state_next <= 6;//下一个状态
									ack2 <= iic_sda;
									cnt_count <= 0;
								end
							endcase
						end
					
					6: //				第三个数据
						begin 
						reg_sdat <= iic_data[7-byte_num];
						
						case (cnt_count)
						0,1,2:
							begin
								sclk <= 0;
							end
						
						3,4,5,6,7,8:
							begin
								sclk <= 1;
							end
						
						9,10:
							begin
								sclk <= 0;
							end
						default://11
							begin
								sclk <= 0;
								cnt_count <= 0;
								
								if (byte_num == 7)//写完所有地址位
									begin
										byte_num <= 0;
										state_next <= 7;//进入应答状态
									end
								else
									begin
										byte_num <= byte_num + 1'b1;
									end
							end
						endcase
					end
					
					7://第三个应答信号
						begin
							tr_start <= 0;
							
							case (cnt_count)
							0,1,2:
								begin
									sclk <= 0;
									sda_en <= 0;//高阻态
								end
						
							3,4,5,6,7,8:
								begin
									sclk <= 1;
									sda_en <= 0;
								end
						
							9,10:
								begin
									sclk <= 0;
									sda_en <= 0;
									ack3 <= iic_sda;//读取应答信号
								end
							default://11
								begin
									sclk <= 0;
									reg_sdat <= 0;
									sda_en <= 1;//重新变为输出
									state_next <= 8;//下一个状态
									ack3 <= iic_sda;
									cnt_count <= 0;
								end
							endcase
						end
					
					8:  //停止
						begin
							case (cnt_count)
						0,1,2:
							begin
								reg_sdat <= 0;
								sclk <= 0;
							end
						
						3,4,5:
							begin
								sclk <= 1;
								reg_sdat <= 0;
							end
						
						6,7,8:
							begin
								sclk <= 1;
								reg_sdat <= 1;//拉高电位结束传输
							end
						9,10:
							begin
								sclk <= 1;
								reg_sdat <= 1;
							end
						default://11
							begin
								sclk <= 1;
								reg_sdat <= 1;
								state_next <= 0;
								cnt_count <= 0;
							end
						endcase
						end
					default: state_next <= 0;
				endcase
		end
	end
				
assign iic_sda = sda_en ? reg_sdat:1'bz;
assign iic_sck = sclk;

你可能感兴趣的:(Verilog)