IIC 简介
IC(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。I2C总线产生于在80年代,最初为音频和视频设备开发,如今主要在服务器管理中使用,其中包括单个组件状态的通信。例如管理员可对各个组件进行查询,以管理系统的配置或掌握组件的功能状态,如电源和系统风扇。可随时监控内存、硬盘、网络、系统温度等多个参数,增加了系统的安全性,方便了管理。IIC数据传输速率有标准模式(100 kbps)、快速模式(400 kbps)和高速模式(3.4 Mbps),另外一些变种实现了低速模式(10 kbps)和快速+模式(1 Mbps)。
下图是一个嵌入式系统中处理器仅通过2根线的IIC总线控制多个IIC外设的典型应用图
特点
- 简单性和有效性
由于接口直接在组件之上,因此 IIC 总线占用的空间非常小,减少了电路板的空间和芯片管脚的数量,降低了互联成本。总线的长度可高达 25 英尺,并且能够以 10Kbps 的最大传输速率支持 40 个组件。
- 多主控(multimastering)
其中任何能够进行发送和接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控。
IIC 通信协议
IIC 串行总线一般有两根信号线,一根是双向的数据线 SDA ,另一根是时钟线 SCL ,其时钟信号是由主控器件产生。所有接到 IIC 总线设备上的串行数据 SDA 都接到总线的 SDA 上,各设备的时钟线 SCL 接到总线的 SCL 上。对于并联在一条总线上的每个 IIC 都有唯一的地址。
IIC 总线在传输数据的过程中一共有三种类型信号,分别为:开始信号、结束信号 和 应答信号。这些信号中,起始信号是必需的,结束信号和应答信号,都可以不需要。同时还有空闲状态、数据的有效性、数据传输。
- 起始信号
当时钟线SCL为高期间,数据线SDA由高到低的跳变。
- 停止信号
当时钟线SCL为高期间,数据线SDA由低到高的跳变。
- 空闲状态
当 IIC 总线的数据线 SDA 和时钟线 SCL 两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。
- 应答信号
发送器每发送一个字节( 8个bit ),就在时钟脉冲 9 期间释放数据线,由接收器反馈一个应答信号。
-
应答信号为低电平时,规定为有效应答位( ACK ,简称应答位),表示接收器已经成功地接收了该字节;
-
应答信号为高电平时,规定为非应答位( NACK ),一般表示接收器接收该字节没有成功。
IIC 总线操作
对 IIC 总线的操作实际就是主从设备之间的读写操作。大致可分为以下三种操作情况:
主设备往从设备中写数据
数据包括从机寄存器地址和需要写入寄存器的数据data
主设备从从设备中读数据
数据包括从机寄存器地址和需要从机读数据data
重复读写数据
主设备往从设备中写数据,然后重启起始条件,紧接着从从设备中读取数据;或者是主设备从从设备中读数据,然后重启起始条件,紧接着主设备往从设备中写数据。数据传输格式如下
第三种操作在单个主设备系统中,重复的开启起始条件机制要比用STOP终止传输后又再次开启总线更有效率
FPGA 程序实现
物理层源码
--------------------------------------------------------------------------------
--
-- FileName: i2c_master.vhd
-- Dependencies: none
-- Design Software: Quartus II 64-bit Version 13.1 Build 162 SJ Full Version
--
-- HDL CODE IS PROVIDED "AS IS." DIGI-KEY EXPRESSLY DISCLAIMS ANY
-- WARRANTY OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT NOT
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
-- PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL DIGI-KEY
-- BE LIABLE FOR ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL
-- DAMAGES, LOST PROFITS OR LOST DATA, HARM TO YOUR EQUIPMENT, COST OF
-- PROCUREMENT OF SUBSTITUTE GOODS, TECHNOLOGY OR SERVICES, ANY CLAIMS
-- BY THIRD PARTIES (INCLUDING BUT NOT LIMITED TO ANY DEFENSE THEREOF),
-- ANY CLAIMS FOR INDEMNITY OR CONTRIBUTION, OR OTHER SIMILAR COSTS.
--
-- Version History
-- Version 1.0 11/1/2012 Scott Larson
-- Initial Public Release
-- Version 2.0 06/20/2014 Scott Larson
-- Added ability to interface with different slaves in the same transaction
-- Corrected ack_error bug where ack_error went 'Z' instead of '1' on error
-- Corrected timing of when ack_error signal clears
-- Version 2.1 10/21/2014 Scott Larson
-- Replaced gated clock with clock enable
-- Adjusted timing of SCL during start and stop conditions
-- Version 2.2 12/24/2014 Steffen Mauch
-- fixed bug during stop condition
--
--------------------------------------------------------------------------------
LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.std_logic_unsigned.all;
ENTITY i2c_master IS
GENERIC(
input_clk : INTEGER := 50_000_000; --input clock speed from user logic in Hz
bus_clk : INTEGER := 400_000); --speed the i2c bus (scl) will run at in Hz
PORT(
clk : IN STD_LOGIC; --system clock
reset_n : IN STD_LOGIC; --active low reset
ena : IN STD_LOGIC; --latch in command
addr : IN STD_LOGIC_VECTOR(6 DOWNTO 0); --address of target slave
rw : IN STD_LOGIC; --'0' is write, '1' is read
data_wr : IN STD_LOGIC_VECTOR(7 DOWNTO 0); --data to write to slave
busy : OUT STD_LOGIC; --indicates transaction in progress
data_rd : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); --data read from slave
ack_error : OUT STD_LOGIC; --flag if improper acknowledge from slave
sda : INOUT STD_LOGIC; --serial data output of i2c bus
scl : INOUT STD_LOGIC); --serial clock output of i2c bus
END i2c_master;
ARCHITECTURE logic OF i2c_master IS
CONSTANT divider : INTEGER := (input_clk/bus_clk)/4; --number of clocks in 1/4 cycle of scl
TYPE machine IS(ready, start, command, slv_ack1, wr, rd, slv_ack2, mstr_ack, stop); --needed states
SIGNAL state : machine; --state machine
SIGNAL data_clk : STD_LOGIC; --data clock for sda
SIGNAL data_clk_prev : STD_LOGIC; --data clock during previous system clock
SIGNAL scl_clk : STD_LOGIC; --constantly running internal scl
SIGNAL scl_ena : STD_LOGIC := '0'; --enables internal scl to output
SIGNAL sda_int : STD_LOGIC := '1'; --internal sda
SIGNAL sda_ena_n : STD_LOGIC; --enables internal sda to output
SIGNAL addr_rw : STD_LOGIC_VECTOR(7 DOWNTO 0); --latched in address and read/write
SIGNAL data_tx : STD_LOGIC_VECTOR(7 DOWNTO 0); --latched in data to write to slave
SIGNAL data_rx : STD_LOGIC_VECTOR(7 DOWNTO 0); --data received from slave
SIGNAL bit_cnt : INTEGER RANGE 0 TO 7 := 7; --tracks bit number in transaction
SIGNAL stretch : STD_LOGIC := '0'; --identifies if slave is stretching scl
signal ack_error_int : std_logic;
BEGIN
ack_error <= ack_error_int;
--generate the timing for the bus clock (scl_clk) and the data clock (data_clk)
PROCESS(clk, reset_n)
VARIABLE count : INTEGER RANGE 0 TO divider*4; --timing for clock generation
BEGIN
IF(reset_n = '0') THEN --reset asserted
stretch <= '0';
count := 0;
ELSIF(clk'EVENT AND clk = '1') THEN
data_clk_prev <= data_clk; --store previous value of data clock
IF(count = divider*4-1) THEN --end of timing cycle
count := 0; --reset timer
ELSIF(stretch = '0') THEN --clock stretching from slave not detected
count := count + 1; --continue clock generation timing
END IF;
CASE count IS
WHEN 0 TO divider-1 => --first 1/4 cycle of clocking
scl_clk <= '0';
data_clk <= '0';
WHEN divider TO divider*2-1 => --second 1/4 cycle of clocking
scl_clk <= '0';
data_clk <= '1';
WHEN divider*2 TO divider*3-1 => --third 1/4 cycle of clocking
scl_clk <= '1'; --release scl
IF(scl = '0') THEN --detect if slave is stretching clock
stretch <= '1';
ELSE
stretch <= '0';
END IF;
data_clk <= '1';
WHEN OTHERS => --last 1/4 cycle of clocking
scl_clk <= '1';
data_clk <= '0';
END CASE;
END IF;
END PROCESS;
--state machine and writing to sda during scl low (data_clk rising edge)
PROCESS(clk, reset_n)
BEGIN
IF(clk'EVENT AND clk = '1') THEN
IF(reset_n = '0') THEN --reset asserted
state <= ready; --return to initial state
busy <= '1'; --indicate not available
scl_ena <= '0'; --sets scl high impedance
sda_int <= '1'; --sets sda high impedance
ack_error_int <= '0'; --clear acknowledge error flag
bit_cnt <= 7; --restarts data bit counter
data_rd <= "00000000"; --clear data read port
elsIF(data_clk = '1' AND data_clk_prev = '0') THEN --data clock rising edge
CASE state IS
WHEN ready => --idle state
scl_ena <= '0';
IF(ena = '1') THEN --transaction requested
busy <= '1'; --flag busy
addr_rw <= addr & rw; --collect requested slave address and command
data_tx <= data_wr; --collect requested data to write
state <= start; --go to start bit
ELSE --remain idle
busy <= '0'; --unflag busy
state <= ready; --remain idle
END IF;
WHEN start => --start bit of transaction
busy <= '1'; --resume busy if continuous mode
sda_int <= addr_rw(bit_cnt); --set first address bit to bus
state <= command; --go to command
WHEN command => --address and command byte of transaction
IF(bit_cnt = 0) THEN --command transmit finished
sda_int <= '1'; --release sda for slave acknowledge
bit_cnt <= 7; --reset bit counter for "byte" states
state <= slv_ack1; --go to slave acknowledge (command)
ELSE --next clock cycle of command state
bit_cnt <= bit_cnt - 1; --keep track of transaction bits
sda_int <= addr_rw(bit_cnt-1); --write address/command bit to bus
state <= command; --continue with command
END IF;
WHEN slv_ack1 => --slave acknowledge bit (command)
IF(addr_rw(0) = '0') THEN --write command
sda_int <= data_tx(bit_cnt); --write first bit of data
state <= wr; --go to write byte
ELSE --read command
sda_int <= '1'; --release sda from incoming data
state <= rd; --go to read byte
END IF;
WHEN wr => --write byte of transaction
busy <= '1'; --resume busy if continuous mode
IF(bit_cnt = 0) THEN --write byte transmit finished
sda_int <= '1'; --release sda for slave acknowledge
bit_cnt <= 7; --reset bit counter for "byte" states
state <= slv_ack2; --go to slave acknowledge (write)
ELSE --next clock cycle of write state
bit_cnt <= bit_cnt - 1; --keep track of transaction bits
sda_int <= data_tx(bit_cnt-1); --write next bit to bus
state <= wr; --continue writing
END IF;
WHEN rd => --read byte of transaction
busy <= '1'; --resume busy if continuous mode
IF(bit_cnt = 0) THEN --read byte receive finished
IF(ena = '1' AND addr_rw = addr & rw) THEN --continuing with another read at same address
sda_int <= '0'; --acknowledge the byte has been received
ELSE --stopping or continuing with a write
sda_int <= '1'; --send a no-acknowledge (before stop or repeated start)
END IF;
bit_cnt <= 7; --reset bit counter for "byte" states
data_rd <= data_rx; --output received data
state <= mstr_ack; --go to master acknowledge
ELSE --next clock cycle of read state
bit_cnt <= bit_cnt - 1; --keep track of transaction bits
state <= rd; --continue reading
END IF;
WHEN slv_ack2 => --slave acknowledge bit (write)
IF(ena = '1') THEN --continue transaction
busy <= '0'; --continue is accepted
addr_rw <= addr & rw; --collect requested slave address and command
data_tx <= data_wr; --collect requested data to write
IF(addr_rw = addr & rw) THEN --continue transaction with another write
sda_int <= data_wr(bit_cnt); --write first bit of data
state <= wr; --go to write byte
ELSE --continue transaction with a read or new slave
state <= start; --go to repeated start
END IF;
ELSE --complete transaction
state <= stop; --go to stop bit
END IF;
WHEN mstr_ack => --master acknowledge bit after a read
IF(ena = '1') THEN --continue transaction
busy <= '0'; --continue is accepted and data received is available on bus
addr_rw <= addr & rw; --collect requested slave address and command
data_tx <= data_wr; --collect requested data to write
IF(addr_rw = addr & rw) THEN --continue transaction with another read
sda_int <= '1'; --release sda from incoming data
state <= rd; --go to read byte
ELSE --continue transaction with a write or new slave
state <= start; --repeated start
END IF;
ELSE --complete transaction
state <= stop; --go to stop bit
END IF;
WHEN stop => --stop bit of transaction
busy <= '0'; --unflag busy
state <= ready; --go to idle state
END CASE;
ELSIF(data_clk = '0' AND data_clk_prev = '1') THEN --data clock falling edge
CASE state IS
WHEN start =>
IF(scl_ena = '0') THEN --starting new transaction
scl_ena <= '1'; --enable scl output
ack_error_int <= '0'; --reset acknowledge error output
END IF;
WHEN slv_ack1 => --receiving slave acknowledge (command)
IF(sda /= '0' OR ack_error_int = '1') THEN --no-acknowledge or previous no-acknowledge
ack_error_int <= '1'; --set error output if no-acknowledge
END IF;
WHEN rd => --receiving slave data
data_rx(bit_cnt) <= sda; --receive current slave data bit
WHEN slv_ack2 => --receiving slave acknowledge (write)
IF(sda /= '0' OR ack_error_int = '1') THEN --no-acknowledge or previous no-acknowledge
ack_error_int <= '1'; --set error output if no-acknowledge
END IF;
WHEN stop =>
scl_ena <= '0'; --disable scl
WHEN OTHERS =>
NULL;
END CASE;
END IF;
END IF;
END PROCESS;
--set sda output
WITH state SELECT
sda_ena_n <= data_clk_prev WHEN start, --generate start condition
NOT data_clk_prev WHEN stop, --generate stop condition
sda_int WHEN OTHERS; --set to internal sda signal
--set scl and sda outputs
scl <= '0' WHEN (scl_ena = '1' AND scl_clk = '0') ELSE '1';
sda <= '0' WHEN sda_ena_n = '0' ELSE 'Z';
END logic;
结果如下图所示
参考链接
- IIC 通讯总结
https://blog.csdn.net/zuo_an/article/details/89139445?ops_request_misc=&request_id=&biz_id=102&utm_term=IIC&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-5-89139445
- I2C 总线协议图解
https://www.cnblogs.com/aaronLinux/p/6218660.html
- IIC总线的原理与Verilog实现
https://www.cnblogs.com/liujinggang/p/9656358.html
工程源码获取
获取资料方法一:集赞
关注小编公众号后,将本文转发至朋友圈,集齐6个赞,截图发送到后台,小编会在24小时之内回复。备注:【领取IIC源码】即可领取资料
获取资料方法二:转发群
关注小编公众号后,将本文转发至不低于100人的群(FPGA相关行业),截图发送到后台,小编会在24小时之内回复。备注:【领取IIC源码】即可领取资料