功能简介
本文基于Xilinx Virtex Ultrascale+HBM VCU128 FPGA开发板,通过利用开发板上的TCA6416A芯片,对I2C通信方式进行学习。
根据VCU128用户手册,128中具有两条I2C总线,其中一条连接有4个I2C芯片,能够与系统监视器(SYSMON)、EEPROM、QSFP等硬件资源进行通信。
根据手册介绍,TCA6416A芯片为I/O拓展芯片,PCA9544A、PCA9548A芯片为1-4、1-8通道总线交换芯片。两块TCA9548A芯片的使用需要先保证TCA6416A芯片的ICC_MUX_RESET-B引脚为高阻态或高电平,因此本文对利用FPGA控制TCA6416A芯片的I2C通信进行介绍。
在实际使用中,Xilinx配置TCA6416A芯片的ADDR引脚为0,在FPGA上访问TCA6416A芯片的SCL、SDA引脚可通过如下约束设置,对应BANK67的BL28、BM27引脚。
set_property BOARD_PART_PIN IIC0_SCL_MAIN [get_ports i2c_0_scl]
set_property BOARD_PART_PIN IIC0_SDA_MAIN [get_ports i2c_0_sda]
TCA6416A芯片介绍
TCA6416A为TI公司生产的I2C转16比特I/O拓展芯片,芯片手册,该芯片能够利用I2C协议进行16个通用引脚(分为2组:P00-P07,P10-P17)的读写访问,每个引脚支持设置为输入、输出两种模式。
ADDR引脚用于设置TCA6416A的I2C通信地址,如下图所示,TCA6416A的地址包含一个可编程位,在VCU128中,该位被设置为0。
TCA6416A的I/O Port为2组8比特通用IO引脚,在通过I2C通信进行使用时会通过8种指令读写8个寄存器,进而实现IO引脚的间接访问。这些操作会通过组的形式对2组8比特IO引脚进行访问。
在使用时,TCA6416A的I2C总线包含标准模式、快速模式两种,其中标准模式下SCL的时钟频率不能超过100KHz,快速模式下SCL的时钟频率不能超过400KHz。本文使用标准模式,时钟频率100KHz。
I2C发送端逻辑实现
I2C是一种采用报文方式传输的串行通信方式,每次发送过程包含一个发送端和一个接收端。从发送端角度看由输出引脚SCL与输入输出引脚SDA构成,其中SDA为每次传输的数据,SCL为代表每次传输数据有效的高电平有效选通信号(时钟信号)。除了报文的开始位和结束位以外,SDA只能在SCL为低电平时发生变化,在SCL为高电平时应保持不变。
I2C传输的报文格式如下,包含1个开始位(S)、8个数据位(1个字节)、1个确认位(ACK)、1个停止位(P)。开始位由发送端发送,在发送完成后,可以进行多次数据位、确认位的交替传输,在最终交互完成后,由发送端发送停止位表示本次通信结束。在发送端发送完8个字节数据后,接收端会占用SDA发送确认位用于表明当前状态,类似一种响应信号。发送端可以根据接收端发送的确认位决定下一步的行动。
对于TCA6416A芯片,I2C传输可分为两种类型的操作。
写操作
根据手册,对于TCA6416A芯片的写操作格式如下,Target Address为TCA6416A芯片的地址,Command Byte为本次访问的操作名称,后续Data0、Data1为写入的数据。下图展示了利用0x20指令写端口0的指令。
下面展示了进行I2C写操作的状态机:
always_comb begin
case (fsm_r)
RESET: begin
if (itf_rst_done) begin
fsm_s = IDLE;
end else begin
fsm_s = RESET;
end
end
IDLE: begin
if (single_bit_hold_flag_r) begin
if (START_flag_r) begin
fsm_s = ADDR_START;
end else begin
fsm_s = IDLE;
end
end else begin
fsm_s = IDLE;
end
end
ADDR_START: begin
if (single_bit_hold_flag_r) begin
fsm_s = ADDR_DATA;
end else begin
fsm_s = ADDR_START;
end
end
ADDR_DATA: begin
if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
fsm_s = ADDR_ACK;
end else begin
fsm_s = ADDR_DATA;
end
end
ADDR_ACK: begin
if (single_bit_hold_flag_r) begin
// if (STOP_flag_r) begin
if (1'b0) begin
fsm_s = ADDR_STOP;
end else begin
fsm_s = CMD_DATA; // ADDR_STOP;
end
end else begin
fsm_s = ADDR_ACK;
end
end
ADDR_STOP: begin
if (single_bit_hold_flag_r) begin
fsm_s = IDLE;
end else begin
fsm_s = ADDR_STOP;
end
end
CMD_DATA: begin
if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
fsm_s = CMD_ACK;
end else begin
fsm_s = CMD_DATA;
end
end
CMD_ACK: begin
if (single_bit_hold_flag_r) begin
// if (STOP_flag_r) begin
if (1'b0) begin
fsm_s = CMD_STOP;
end else begin
if (RWn_flag_r) begin
fsm_s = SECADDR_START; // ADDR_STOP;
end else begin
fsm_s = WR_DATA; // ADDR_STOP;
end
end
end else begin
fsm_s = CMD_ACK;
end
end
CMD_STOP: begin
if (single_bit_hold_flag_r) begin
fsm_s = IDLE;
end else begin
fsm_s = CMD_STOP;
end
end
WR_DATA: begin
if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
fsm_s = WR_ACK;
end else begin
fsm_s = WR_DATA;
end
end
WR_ACK: begin
if (single_bit_hold_flag_r) begin
if (STOP_flag_r) begin
fsm_s = WR_STOP;
end else begin
fsm_s = WR_DATA;
end
end else begin
fsm_s = WR_ACK;
end
end
WR_STOP: begin
if (single_bit_hold_flag_r) begin
fsm_s = IDLE;
end else begin
fsm_s = WR_STOP;
end
end
default: begin
fsm_s = RESET;
end
endcase
end
读操作
TCA6416A芯片的读操作格式如下,在进行读操作前,需要先通过写操作设置指令。使得TCA6416A芯片得知后续对哪个寄存器进行读操作。之后再利用开始位(S)重启读操作。下图展示了利用0x20指令读端口0的I2C格式。
在读操作中,在FPGA进行I2C读操作时,后续I2C总线将由TCA6416A芯片发送8个数据位,由FPGA发送确认位(ACK),如下图所示。
下面展示了进行I2C读操作的状态机:
always_comb begin
case (fsm_r)
RESET: begin
if (itf_rst_done) begin
fsm_s = IDLE;
end else begin
fsm_s = RESET;
end
end
IDLE: begin
if (single_bit_hold_flag_r) begin
if (START_flag_r) begin
fsm_s = ADDR_START;
end else begin
fsm_s = IDLE;
end
end else begin
fsm_s = IDLE;
end
end
ADDR_START: begin
if (single_bit_hold_flag_r) begin
fsm_s = ADDR_DATA;
end else begin
fsm_s = ADDR_START;
end
end
ADDR_DATA: begin
if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
fsm_s = ADDR_ACK;
end else begin
fsm_s = ADDR_DATA;
end
end
ADDR_ACK: begin
if (single_bit_hold_flag_r) begin
// if (STOP_flag_r) begin
if (1'b0) begin
fsm_s = ADDR_STOP;
end else begin
fsm_s = CMD_DATA; // ADDR_STOP;
end
end else begin
fsm_s = ADDR_ACK;
end
end
ADDR_STOP: begin
if (single_bit_hold_flag_r) begin
fsm_s = IDLE;
end else begin
fsm_s = ADDR_STOP;
end
end
CMD_DATA: begin
if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
fsm_s = CMD_ACK;
end else begin
fsm_s = CMD_DATA;
end
end
CMD_ACK: begin
if (single_bit_hold_flag_r) begin
// if (STOP_flag_r) begin
if (1'b0) begin
fsm_s = CMD_STOP;
end else begin
if (RWn_flag_r) begin
fsm_s = SECADDR_START; // ADDR_STOP;
end else begin
fsm_s = WR_DATA; // ADDR_STOP;
end
end
end else begin
fsm_s = CMD_ACK;
end
end
CMD_STOP: begin
if (single_bit_hold_flag_r) begin
fsm_s = IDLE;
end else begin
fsm_s = CMD_STOP;
end
end
SECADDR_START: begin
if (single_bit_hold_flag_r) begin
fsm_s = SECADDR_DATA;
end else begin
fsm_s = SECADDR_START;
end
end
SECADDR_DATA: begin
if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
fsm_s = SECADDR_ACK;
end else begin
fsm_s = SECADDR_DATA;
end
end
SECADDR_ACK: begin
if (single_bit_hold_flag_r) begin
// if (STOP_flag_r) begin
if (1'b0) begin
fsm_s = SECADDR_STOP;
end else begin
fsm_s = RD_DATA; // ADDR_STOP;
end
end else begin
fsm_s = SECADDR_ACK;
end
end
SECADDR_STOP: begin
if (single_bit_hold_flag_r) begin
fsm_s = IDLE;
end else begin
fsm_s = SECADDR_STOP;
end
end
RD_DATA: begin
if (single_bit_hold_flag_r && pos_cnt_r == 4'd7) begin
fsm_s = RD_ACK;
end else begin
fsm_s = RD_DATA;
end
end
RD_ACK: begin
if (single_bit_hold_flag_r) begin
if (STOP_flag_r) begin
fsm_s = RD_STOP;
end else begin
fsm_s = RD_DATA;
end
end else begin
fsm_s = RD_ACK;
end
end
RD_STOP: begin
if (single_bit_hold_flag_r) begin
fsm_s = IDLE;
end else begin
fsm_s = RD_STOP;
end
end
default: begin
fsm_s = RESET;
end
endcase
end
仿真测试
仿真模拟了利用VIO输入0x02端口0引脚读写指令后FPGA的工作过程 。其中蓝色高阻态用于TCA6416A发来的响应信息。
initial begin // write to Output Port Register
force i2c_top_inst.CMD_vec_r = 8'b00000010;
force i2c_top_inst.wrDATA_vec_r = 8'h6a;
force i2c_top_inst.START_flag_r = 1'b1;
force i2c_top_inst.RWn_flag_r = 1'b1;
force i2c_top_inst.wrACK_flag_r = 1'b0;
force i2c_top_inst.STOP_flag_r = 1'b1;
end
上板测试
这里以0x02端口0引脚读写指令为例,进行I2C通信测试,初始状态下端口0的8个引脚值为0x77。
通过设置读写模式选择RWn_flag_r为0,指令CMD_vec_r为0x20,写数据wrDATA_vec_r为0x6A,点击START_flag_r控制FPGA发送I2C写端口0的8bit为6A。
通过设置RWn_flag_r为1,其余保持不变,点击START_flag_r控制FPGA发送I2C读端口0的8bit,得到rdDATA_vec_r结果为6A。
完整代码
工程代码可于同名公众号回复I2C_TCA6416A下载。