基于VHDL的I2C协议(结合实际工程)

        本文主要分为几部分,开始是老生常谈的I2C基础,然后是手撕I2C设计,最后是实际项目中使用的I2C协议:

一、I2C基础

        优快云一大堆这种帖子,实际毫无意义,最详细的建议查询对应FPGA或MCU手册,有最详细的协议相关(手册是个好东西,别啥都看网上,多看手册)。

      

一、I2C协议核心概念

  1. 基本定义
    I2C(Inter-Integrated Circuit)是一种由Philips(现NXP)开发的双线制串行通信协议,支持多主从设备连接,通过串行数据线(SDA)和串行时钟线(SCL)实现同步数据传输

  2. 物理层特性

    • 硬件结构:SDA和SCL均为开漏输出设计,需外接上拉电阻(通常4.7kΩ)维持高电平,实现线与逻辑(任一设备拉低总线则整体为低)。
    • 电气特性:支持电压范围2.5V-5.5V,空闲时SDA和SCL均为高电平。
  3. 协议层特性

    • 同步传输:数据有效性由SCL控制,SDA在SCL高电平时保持稳定,低电平时允许变化。
    • 多设备支持:通过唯一地址(7位或10位)识别从机,支持最多127个设备(7位地址模式)。

二、关键通信机制

  1. 起始与停止条件

    • 起始信号(Start):SCL高电平时,SDA从高→低跳变。
    • 停止信号(Stop):SCL高电平时,SDA从低→高跳变。
  2. 数据传输格式

    • 数据帧:以8位(1字节)为单位传输,高位(MSB)优先,每字节后需跟随应答位(ACK/NACK)。
    • 应答机制:接收方在第9个时钟周期拉低SDA为ACK,保持高电平为NACK。
  3. 寻址机制

    • 地址帧:起始信号后紧跟7位/10位设备地址+读写位(0写/1读)。
    • 广播模式:保留地址(如0000xxx)可用于全局复位或广播指令。

三、典型通信流程

  1. 写操作

    • 主机发送起始信号→设备地址+写指令→从机ACK→数据字节→从机ACK→停止信号。
  2. 读操作

    • 主机发送起始信号→设备地址+读指令→从机ACK→接收数据→主机回复NACK→停止信号。
  3. 复合操作

    • 先写寄存器地址,再读数据:两次起始信号,中间不发送停止信号。

四、硬件设计与速率模式

  1. 速率模式

    模式速率应用场景
    标准模式100kbps常规传感器、EEPROM
    快速模式400kbps高速传感器、显示器
    高速模式3.4Mbps视频设备、存储芯片
    超快模式5Mbps单向传输(如LED控制)
    (参考)
  2. 仲裁机制
    多主机竞争时,通过“线与逻辑”仲裁:若某主机发送高电平但检测到总线为低,则退出竞争


五、优缺点分析

  • 优点:仅需两根线、支持多主从、硬件复杂度低、可靠性高(ACK机制)。
  • 缺点:速率低于SPI、数据帧长度固定、多主机需额外仲裁逻辑。

六、典型应用场景

  • 传感器数据采集(如温度传感器、陀螺仪)
  • EEPROM存储芯片读写(如AT24C系列)
  • 显示器控制(如OLED屏驱动)

  二、手撕I2C要点

一、I2C协议手撕要点
  1. 状态机设计
    I2C协议本质是有限状态机(FSM),需划分以下核心状态:

    • 空闲(IDLE)
    • 起始位(START)
    • 地址传输(ADDR)
    • 数据读/写(DATA_RW)
    • 应答检测(ACK/NACK)
    • 停止位(STOP)
      示例状态转移逻辑:在SCL低电平时切换SDA电平实现START/STOP信号

  2. 时钟同步策略
    注意主时钟频率和SCL问题,这里我建议用计数器实现协议时钟。

  3. 三态门处理
    SDA需实现双向通信,VHDL中通过inout端口+使能信号控制。

    process(sda_enable)
    begin
      if sda_enable = '1' then
        sda <= sda_out;
      else
        sda <= 'Z';
      end if;
    end process;

    三、工程代码

  4. LIBRARY IEEE;
    USE IEEE.STD_LOGIC_1164.ALL;
    USE IEEE.STD_LOGIC_ARITH.ALL;
    USE IEEE.STD_LOGIC_UNSIGNED.ALL;
    --USE IEEE.STD_LOGIC_signed.ALL;
    use WORK.ChangeWorldCtrolBoard_PACKAGE.ALL;
    
    entity i2c_master is
    generic(ModuleBaseADDR: std_logic_vector(7 downto 0));
        Port ( 
    			sysreset	:		in std_logic;
    			sysclk		:		in std_logic;
    		
    			bus_addr	:		in std_logic_vector(25 downto 0);
    			bus_data_wr	:		in std_logic_vector(15 downto 0);
    			bus_data_rd	:		out std_logic_vector(15 downto 0);
    			bus_wren	:		in std_logic;  						--single pulse of rising_edge of BUS_WR signal of MCU.
    			bus_rden	:		in std_logic;						--single pulse of falling_edge of BUS_RD signal of MCU.
    			bus_cs		:		in std_logic;
    			
    			
    			
    			sda      	: 		inout STD_LOGIC;
    			scl      	: 		out STD_LOGIC);
    end i2c_master;
    architecture Behavioral of i2c_master is
    
        -- Internal Signals
        signal 	sda_i : std_logic := '1';
        signal 	scl_i : std_logic := '1';
        signal 	state : std_logic_vector(3 downto 0) := "0000";
        signal 	byte_counter : integer range 0 to 9 := 0;
        signal 	bit_counter : integer range 0 to 7 := 0;
        signal 	send_data : std_logic_vector(7 downto 0) := (others => '0');
        signal 	receive_data : std_logic_vector(7 downto 0) := (others => '0');
        signal 	current_byte : std_logic_vector(7 downto 0) := (others => '0');
        signal 	current_bit : std_logic := '0';
    	signal	addr     	: STD_LOGIC_VECTOR(6 downto 0);	
    	signal	rw       	: STD_LOGIC; -- '0' for write, '1' for read
    	signal	data_out 	: STD_LOGIC_VECTOR(7 downto 0);
    	signal	data_in  	: STD_LOGIC_VECTOR(7 downto 0);
    	signal	start,rise_start_en    	: STD_LOGIC;
    	signal	stop,I_O_Set	 	: STD_LOGIC;
    	signal  rise_get_data_en,get_data_en,rise_send_data_en,send_data_en,start_t: STD_LOGIC;
    	SIGNAL sclk_cnt: INTEGER RANGE 0 TO 400;
    	
    	--fifo信号
    	signal	FIFO_Data_send,FIFO_Data_get:		std_logic_vector(7 downto 0);
    	signal	FIFO_wrreq_send,FIFO_wrreq_get,rise_FIFO_wrreq_send:		STD_LOGIC;	
    	signal	LLD_FIFO_rdreq_send,LLD_FIFO_rdreq_get:		STD_LOGIC;	
    	signal	clear_send,clear_get				:		STD_LOGIC;
    	signal	numfifo_send,numfifo_get			:		std_logic_vector(12 downto 0);
    	signal	LLD_DATA_FIFORD_send,LLD_DATA_FIFORD_get:	std_logic_vector(7 downto 0);
    	signal	LLD_FIFO_empty_send,LLD_FIFO_empty_get	:		STD_LOGIC;
    	signal	LLD_FIFO_full_send,LLD_FIFO_full_get,LLD_accept_state:		STD_LOGIC;
    	
    	signal  data_length_send,data_length_get					:std_logic_vector(7 downto 0);
    	signal  WorkMode_send,WorkMode_get			:std_logic_vector(7 downto 0);
    	signal	byteDataReceivedOK,CRC_check,send_data_req,LLD_FIFO_rden_get,LLD_FIFO_rden_send,rise_FIFO_wrreq_get		:STD_LOGIC;--接收fifo使能信号
    	signal	LLD_accept		: 	STD_LOGIC_VECTOR(7 DOWNTO 0);
    
    begin
    	U1: _FIFO1  port map (Data=>FIFO_Data_send,Clk=>sysclk,WrEn=>rise_FIFO_wrreq_send,RdEn=>LLD_FIFO_rdreq_send,Reset=>clear_send,Wnum=>numfifo_send,Q=>LLD_DATA_FIFORD_send,Empty=>LLD_FIFO_empty_send,Full=>LLD_FIFO_full_send); 
    	U2: _FIFO2  port map (Data=>FIFO_Data_get,Clk=>sysclk,WrEn=>rise_FIFO_wrreq_get,RdEn=>LLD_FIFO_rdreq_get,Reset=>clear_get,Wnum=>numfifo_get,Q=>LLD_DATA_FIFORD_get,Empty=>LLD_FIFO_empty_get,Full=>LLD_FIFO_full_get);	
    	U3:	rising_edge_detect port map(nreset=>sysreset,clk=>sysclk,input=>LLD_FIFO_rdreq_get,output=>LLD_FIFO_rden_get);
    	U4:	rising_edge_detect port map(nreset=>sysreset,clk=>sysclk,input=>FIFO_wrreq_send,output=>rise_FIFO_wrreq_send);
    	U5:	rising_edge_detect port map(nreset=>sysreset,clk=>sysclk,input=>FIFO_wrreq_get,output=>rise_FIFO_wrreq_get);
    	U6:	falling_edge_detect port map(nreset=>sysreset,clk=>sysclk,input=>send_data_en,output=>rise_send_data_en);
    	U7:	rising_edge_detect port map(nreset=>sysreset,clk=>sysclk,input=>get_data_en,output=>rise_get_data_en);
    	U8:	rising_edge_detect port map(nreset=>sysreset,clk=>sysclk,input=>start,output=>rise_start_en);
    	process (sysclk,sysreset)
        begin
    		if sysreset = '0' then
    			addr    	<= (others => '0');
    			rw      	<= '0';
    			start   	<= '0';
    			
    		elsif rising_edge(sysclk) then
    			if bus_cs ='0'  and bus_wren ='1' and bus_addr(23 downto 16) =ModuleBaseADDR then
    					case bus_addr(15 downto 0) is
    						when 	x"F3FF"  =>	--0x00000E0. 
    						FIFO_Data_send 		<=bus_data_wr(7 downto 0);
    						FIFO_wrreq_send 	<='1';
    						when	x"0001" =>	
    								data_length_get		<=bus_data_wr(7 downto 0);
    						when	x"0002" =>	
    								data_length_send		<=bus_data_wr(7 downto 0);
    						when	x"0003"  =>
    								clear_send 			<=bus_data_wr(0);
    						when	x"0004"  =>
    								addr			<=bus_data_wr(6 downto 0);
    						when	x"0005"  =>
    								rw 				<=bus_data_wr(0);--写使能,过非门为片选信号
    						when 	x"0006"  =>	--0x00000E0. 
    								start 		<=bus_data_wr(0);
    						when	x"0007"  =>
    								clear_get 			<=bus_data_wr(0);			
    						when 	x"0008"  =>	--0x00000E0. 
    								FIFO_Data_send 		<=bus_data_wr(7 downto 0);
    								FIFO_wrreq_send 	<='1';
    						when others =>
    								NULL;
    							
    					end case;
    					elsif bus_cs ='1' then
    						FIFO_wrreq_send  <='0';
    			end if;
    		end if;
    	end process;	
    
    	process(sysclk,sysreset)
    	begin
    		if sysreset ='0' then
    			LLD_accept <= x"00";
    			CRC_check <= '0';
    		elsif rising_edge(sysclk) then
    			if	(LLD_FIFO_full_get = '1') and (FIFO_wrreq_get = '1') then
    					LLD_accept		<=x"01";--溢出
    			elsif (LLD_FIFO_empty_get = '0') and (FIFO_wrreq_get = '0') then
    					LLD_accept		<=x"02";--完成
    			elsif FIFO_wrreq_get = '1'	then
    					LLD_accept	<=x"03";--忙碌	
    			elsif (LLD_FIFO_empty_get = '1') and (FIFO_wrreq_get = '0')	then
    					LLD_accept		<=x"04";--空闲
    			elsif(CRC_check = '1') then 
    					LLD_accept		<=x"05";--CRC校验失败
    			else
    					LLD_accept		<=x"FF";--状态返回失败
    			end if;
    	    end if;
    	end process;
    	
    --I2C通讯过程
    
    --连接FIFO与I2C	
    	process (sysclk,sysreset)
        begin
            if sysreset ='0' then
    				data_out <= (others => '0');
    				FIFO_Data_get	<= (others => '0');
    				LLD_FIFO_rdreq_send <= '0';
    				FIFO_wrreq_get	<= '0';
    			elsif rising_edge(sysclk) then
    				data_out	<= LLD_DATA_FIFORD_send   ;
    				LLD_FIFO_rdreq_send <= rise_send_data_en ;
    				FIFO_Data_get		<=	data_in;
    				FIFO_wrreq_get		<=	rise_get_data_en;
    		end if;
    			
    	end process;
    --时钟计数器(80M分之一)
    	PROCESS(sysclk,sysreset)
    	BEGIN
    		IF(sysreset = '0') THEN 
    			sclk_cnt <= 0;
    		ELSIF(sysclk'EVENT AND sysclk = '1')THEN 
    			IF(start = '1') THEN
    				IF(sclk_cnt = 399)THEN 
    					sclk_cnt <= 0;
    				ELSE 
    					sclk_cnt <= sclk_cnt + 1;
    				END IF;
    			ELSE 
    				sclk_cnt <= sclk_cnt;
    			END IF;
    		END IF;
    	END PROCESS;
    	--时钟
    	PROCESS(sysclk,sysreset)
    	BEGIN 
    		if(sysreset = '0') THEN 
                    scl_i <= '1';
    		elsif rising_edge(sysclk) then 
    			if start_t = '1' then
    				if sclk_cnt = 199 then 
    				scl_i <= NOT scl_i;
    				end if;
    			end if;
    			if stop = '1' and state = "0000" then
    					scl_i <= '1';
    			end if;
    		END IF;
    	END PROCESS;
    	
    	--停止信号
    	PROCESS(sysclk,sysreset)
    	BEGIN 
    		if(sysreset = '0') THEN 
                    stop <= '0';
    		elsif rising_edge(sysclk) then 
    		if (LLD_FIFO_empty_send = '1' and state = "1001") or (data_length_get = numfifo_get and state = "1001")  then
    			stop <= '1';
    		elsif rise_start_en = '1' then
    			 stop <= '0';
    		end if;
    			END IF;
    	END PROCESS;
        -- Main Process
        process (sysclk,sysreset)
        begin
            if sysreset ='0' then
                    sda_i <= '1';
                    state <= "0000"; -- Idle
                    byte_counter <= 0;
                    bit_counter <= 0;
                    send_data <= (others => '0');
                    receive_data <= (others => '0');
                    current_byte <= (others => '0');
                    current_bit <= '0'; 
    				get_data_en <= '0';
    				start_t <= '0';
    				LLD_accept_state <= '0';
    				I_O_Set	<= '1';			
                elsif rising_edge(sysclk) then
    			-- if	sclk_cnt = 19 then
    			--测试ack信号
    			--	if state = "1001" and sclk_cnt = 175 then
    			--		sda_i <= '0';
    			--	end if;
    				
                    case state is
                        when "0000" => -- 开始
    						I_O_Set <='1';
    						if	sclk_cnt = 199 then
    							if start = '1' and stop /= '1' then
    								state <= "0001"; -- Start condition
    							end if;
    						end if;
    						if scl_i = '1' and  sclk_cnt = 200 then
    							sda_i <= '1' ;
    						end if;
                        when "0001" => -- sda拉低,为端口赋值
    						I_O_Set	<= '1';
    						if	sclk_cnt = 150 then
    							sda_i <= '0';
    							start_t <= '1'; 
    							if scl_i = '0' then
    								byte_counter <= 0;
    								current_byte <= (addr & rw);
    								state <= "0010";
    							end if;
    						end if;
                        when "0010" => -- 为sda赋值发送
    						if	sclk_cnt = 160 then
    							if scl_i = '0' then 
    								current_bit <= current_byte(7-bit_counter);
    								-- sda_i <= current_bit;
    								sda_i <= current_byte(7-bit_counter);
    								if bit_counter = 7 then
    									bit_counter <= 0;
    									state <= "1011"; -- Wait for ACK
    								else
    									bit_counter <= bit_counter + 1;
    								end if;
    							end if;
    						end if;
                        when "0011" => -- 拉高sda等待ack
    						if	sclk_cnt = 170 then
    							if rw = '0' then
    								send_data_en <= '1';
    							end if;
    							sda_i <= '1' ;--调试9.20
    							state <= "1001";
    					  end if;
    				--when "1001" =>
    				--if	sclk_cnt = 170 then
    				-- send_data_en <= '0';
    				--  if sda_i = '0' then --改为0等待ack
    				--	if scl_i = '0' then
    				--	send_data <= data_out;						
    				--		state <= "0100";
                    --    end if;
    				--  else
    				--		state <= "1111";
    				--		LLD_accept_state <= '1';
    				--  end if;
    				--end if;
    					
    					when "1001" =>
    					if  scl_i = '1' then
    						I_O_Set <='0';
    						if	sclk_cnt = 160 then
    							if sda = '1' then	--test
    								state <= "1111";
    								LLD_accept_state <= '1';
    							else
    								send_data_en <= '0';
    							end if;
    						elsif sclk_cnt = 175 then
    							send_data <= data_out;
    							state <= "0100";
    						end if;
    					end if;
    					
                        when "0100" => -- 等待ack,并为发送端口赋值以及发送
    						if	sclk_cnt = 180 then
    							if scl_i = '0' then
    								if rw = '0' then -- Write
    									I_O_Set <='1';
    									current_bit <= send_data(7-bit_counter);
    									sda_i <= send_data(7-bit_counter);
    									if bit_counter = 7 then
    										bit_counter <= 0;
    										state <= "1011"; -- Wait for ACK
    									else
    										bit_counter <= bit_counter + 1;
    									end if;
    								else -- Read
    									I_O_Set <='0';
    									current_bit <= sda_i;
    									receive_data(7-bit_counter) <= sda_i;
    									if bit_counter = 7 then
    										bit_counter <= 0;
    										state <= "0111"; -- send ACK
    									else
    										bit_counter <= bit_counter + 1;
    									end if;
    								end if;
    							end if;
    						end if;
    					 when "1011" => --为最后一位赋值
    							if	sclk_cnt = 200 then
    								if scl_i = '0' then
    									sda_i <= '1';
    									if numfifo_send = 0 then
    										state <= "0101";
    									else
    										state <= "0011";
    									end if;
    								end if;
    							end if;
                        when "0101" => -- 数据线拉高
    						I_O_Set <='0';
    						if	sclk_cnt = 170 then
    							if scl_i = '0' then
    								state <= "0110"; -- Wait for SCL low
    							end if;
    						end if;
                        when "0110" => -- 收到ack
    						if sda = '0' then	--test
    							I_O_Set <='1';
    							sda_i <= '0';
    							if	sclk_cnt = 170 then
    								if scl_i = '0' then
    									if stop = '1' then
    										state <= "1111"; -- Stop condition
    										LLD_accept_state <= '1';
    									end if;
    								else
    									state <= "0110";
    								end if;
    							end if;
    						end if;
                        when "0111" => -- 保存接收数据
    						I_O_Set <='1';
    						sda_i <= '0';
                            if scl_i = '0' then
                                data_in <= receive_data;
    							get_data_en <= '1'; 
    							if stop = '1' then
    								state <= "1111"; -- Stop condition
    								sda_i <= '1';
    							else
    								state <= "0100";
    							end if;
    						end if;
                        when "1111" => -- Stop condition
    						I_O_Set <='1';
    						sda_i <= '0';
    						byte_counter <= 0;
    						bit_counter <= 0;
    						send_data <= (others => '0');
    						receive_data <= (others => '0');
    						current_byte <= (others => '0');
    						current_bit <= '0'; 
    						get_data_en <= '0';
    						start_t <= '0';	
                            state <= "0000"; -- back to initial
                        when others => -- Error or invalid state
                            state <= "0000"; -- Reset to idle
                    end case;
                end if;
    	--	 end if;
        end process;
     -- Assign SDA
        sda <= sda_i when (I_O_Set ='1') else 'Z';
    	scl <= scl_i;
    
    end Behavioral;

    上位机通过FSMC总线发送数据到FPGA的FIFO,再通过I2C协议发送和接受下位机数据,这里根据项目需求对I2C时序和功能有一定修改,仅供参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值