I need help with a design that I am currently working on. I am using a Spartan 3 that is on a custom board that checks 6 devices using I2C and these devices all have the same address so I am trying to use a mux to switch between devices.
From what I understand I need to use a tristate buffer to make it work but I couldn't get the hang of it.
As of now I am using a mux that switches states (devices) at every second and the output signal of that mux is connected to a tri state buffers input and the output is connected to the xps module. The tri state buffer is always on so no high z states on scl and sda.
I am new to embedded design so if there is any issue with my question please be understanding and let me know so that I can fix it.
To be more specific, without connecting to the mux; just to test the buffer with a single i2c device when I use the sda/scl ports as IO (from xps) the connection is ok. When I use the I O T ports and connect them to a basic IOBUF the connection is broken.
Thanks to @DaveTweed I got some code to work with here is the code that I am using. This is for switching between 2 devices by the way. If I can make this work I will modify it for 6 devices as my original question.
The tri-state buffer:
Library UNISIM;
use UNISIM.vcomponents.all;
-- <-----Cut code below this line and paste into the architecture body---->
-- IOBUF: Single-ended Bi-directional Buffer
-- Spartan-3
-- Xilinx HDL Language Template, version 14.7
IOBUF_inst : IOBUF
generic map (
DRIVE => 12,
IOSTANDARD => "DEFAULT",
SLEW => "SLOW")
port map (
O => O, -- Buffer output
IO => IO, -- Buffer inout port (connect directly to top-level port)
I => I, -- Buffer input
T => T -- 3-state enable input, high=input, low=output
);
The I2C Mux:
entity I2C_Mux is
Port (
SDA_In1, SDA_In2 : in STD_LOGIC;
SDA_T1, SDA_T2 : out STD_LOGIC;
SDA_Out1, SDA_Out2: out STD_LOGIC;
SCL_In1, SCL_In2: in STD_LOGIC;
SCL_T1, SCL_T2: out STD_LOGIC;
SCL_Out1, SCL_Out2: out STD_LOGIC;
Sel : in std_logic;
MUX_SDA_Out : in STD_LOGIC;
MUX_SDA_In : out STD_LOGIC;
MUX_SDA_T : in STD_LOGIC;
MUX_SCL_Out : in STD_LOGIC;
MUX_SCL_In : out STD_LOGIC;
MUX_SCL_T: in STD_LOGIC
);
end I2C_Mux;
architecture Behavioral of I2C_Mux is
begin
process(SDA_In1, SDA_In2,SCL_In1, SCL_In2, Sel, MUX_SDA_Out, MUX_SDA_T, MUX_SCL_Out, MUX_SCL_T)
begin
if Sel = '0' then
SDA_Out1 <= MUX_SDA_Out;
MUX_SDA_In <= SDA_In1;
SDA_T1 <= MUX_SDA_T;
SCL_Out1 <= MUX_SCL_Out;
MUX_SCL_In <= SCL_In1;
SCL_T1 <= MUX_SCL_T;
else
SDA_Out2 <= MUX_SDA_Out;
MUX_SDA_In <= SDA_In2;
SDA_T2 <= MUX_SDA_T;
SCL_Out2 <= MUX_SCL_Out;
MUX_SCL_In <= SCL_In2;
SCL_T2 <= MUX_SCL_T;
end if;
end process;
end Behavioral;
The XPS top module:
entity i2c_deneme_top is
port (
fpga_0_RS232_RX_pin : in std_logic;
fpga_0_RS232_TX_pin : out std_logic;
fpga_0_clk_1_sys_clk_pin : in std_logic;
fpga_0_rst_1_sys_rst_pin : in std_logic;
xps_iic_0_Sda_I_pin : in std_logic;
xps_iic_0_Sda_O_pin : out std_logic;
xps_iic_0_Scl_I_pin : in std_logic;
xps_iic_0_Scl_O_pin : out std_logic;
xps_iic_0_Sda_T_pin : out std_logic;
xps_iic_0_Scl_T_pin : out std_logic;
xps_gpio_0_GPIO_IO_I_pin : in std_logic;
xps_gpio_0_GPIO_IO_O_pin : out std_logic;
xps_gpio_0_GPIO_IO_T_pin : out std_logic
);
end i2c_deneme_top;
and the top module connections between these components.
-- I2C Ports
signal MUX_SCL_Out : std_logic;
signal MUX_SDA_Out : std_logic;
signal MUX_SCL_In : std_logic;
signal MUX_SDA_In : std_logic;
signal MUX_SCL_T : std_logic;
signal MUX_SDA_T : std_logic;
-- MUX I2C Ports
signal GPIO1 : std_logic;
signal SCL_Out1 : std_logic;
signal SDA_Out1 : std_logic;
signal SCL_In1 : std_logic;
signal SDA_In1 : std_logic;
signal SCL_Out2 : std_logic;
signal SDA_Out2 : std_logic;
signal SCL_In2 : std_logic;
signal SDA_In2 : std_logic;
-- I2C _T Signals
signal en1: std_logic;
signal en2: std_logic;
signal en3: std_logic;
signal en4: std_logic;
signal temp1: std_logic;
signal temp2: std_logic;
...
iic_top: i2c_deneme_top
Port map (
fpga_0_RS232_RX_pin => FPGA_RS232_1_RXD,
fpga_0_RS232_TX_pin => FPGA_RS232_1_TXD,
fpga_0_clk_1_sys_clk_pin => FPGA_CLK1,
fpga_0_rst_1_sys_rst_pin => nRESET,
xps_iic_0_Sda_I_pin => MUX_SDA_In,
xps_iic_0_Sda_O_pin => MUX_SDA_Out,
xps_iic_0_Scl_I_pin => MUX_SCL_In,
xps_iic_0_Scl_O_pin => MUX_SCL_Out,
xps_iic_0_Sda_T_pin => MUX_SDA_T,
xps_iic_0_Scl_T_pin => MUX_SCL_T,
xps_gpio_0_GPIO_IO_I_pin => temp1, --doesnt go anywhere
xps_gpio_0_GPIO_IO_O_pin => GPIO1,
xps_gpio_0_GPIO_IO_T_pin => temp2 --doesnt go anywhere
);
I2CMUX: I2C_Mux
Port map (
SDA_In1 => SDA_In1,
SDA_In2 => SDA_In2,
SDA_T1 => en1,
SDA_T2 => en3,
SDA_Out1 => SDA_Out1,
SDA_Out2 => SDA_Out2,
SCL_In1 => SCL_In1,
SCL_In2 => SCL_In2,
SCL_T1 => en2,
SCL_T2 => en4,
SCL_Out1 => SCL_Out1,
SCL_Out2 => SCL_Out2,
Sel => GPIO1,
MUX_SDA_Out => MUX_SDA_Out,
MUX_SDA_In => MUX_SDA_In,
MUX_SDA_T => MUX_SDA_T,
MUX_SCL_Out => MUX_SCL_Out,
MUX_SCL_In => MUX_SCL_In,
MUX_SCL_T => MUX_SCL_T
);
TRISTATE_SDA3: TriStateBuffer
Port map (
DataIn => SDA_In1,
Enable => en1,
DataOut => SDA_Out1,
IO => I2C_SDA
);
TRISTATE_SCL3: TriStateBuffer
Port map (
DataIn => SCL_In1,
Enable => en2,
DataOut => SCL_Out1,
IO => I2C_SCL
);
TRISTATE_SDA4: TriStateBuffer
Port map (
DataIn => SDA_In2,
Enable => en3,
DataOut => SDA_Out2,
IO => I2C_SDA
);
TRISTATE_SCL4: TriStateBuffer
Port map (
DataIn => SCL_In2,
Enable => en4,
DataOut => SCL_Out2,
IO => I2C_SCL
);
I was using the repeated start example for i2c with a single device and just added the GPIO bit to that code to make this work
#define GPIO_EXAMPLE_DEVICE_ID XPAR_XPS_GPIO_0_DEVICE_ID
...
XGpio Gpio;
...
int main(void) {
Status = XGpio_Initialize(&Gpio, GPIO_EXAMPLE_DEVICE_ID);
if (Status != XST_SUCCESS) {
return XST_FAILURE;
}
XGpio_SetDataDirection(&Gpio, 1, 0x0);
...
u32 Data = XGpio_DiscreteRead(&Gpio, 1);
xil_printf("Data: %d\n", Data);
XGpio_DiscreteWrite(&Gpio, 1, Data | 0x0);
...
Using the IOBUF primitive solved the issue. The problem was caused by me because I tried to use my own buffer. Thanks again to Dave for the solution.
-
\$\begingroup\$ Can you show a schematic of how the I2C devices are connected to the Spartan 3? From the initial description not sure if the "mux" is external to the Spartan 3. \$\endgroup\$Chester Gillon– Chester Gillon2023年10月06日 08:06:29 +00:00Commented Oct 6, 2023 at 8:06
-
\$\begingroup\$ The mux is internal which I created in ISE. The i2c module is from XPS which is connected to the microblaze. I cant share the schematics. Sorry about that. @ChesterGillon \$\endgroup\$newtoallthis– newtoallthis2023年10月06日 08:13:14 +00:00Commented Oct 6, 2023 at 8:13
-
\$\begingroup\$ Have you got available resources in the Spartan 3 to have multiple I2C modules, one per external I2C device? One I2C module is more FPGA resource intensive than a mux, but could also allow multiple I2C devices to be checked at once. This assumes the SCL and SDA from each external I2C device are connected to independent sets of FPGA pins. \$\endgroup\$Chester Gillon– Chester Gillon2023年10月06日 08:18:52 +00:00Commented Oct 6, 2023 at 8:18
-
\$\begingroup\$ @ChesterGillon I tried it but the spartan that i am using is small and with the other components there is no room for 6 modules. Thats why I am trying to use a mux to switch between devices. \$\endgroup\$newtoallthis– newtoallthis2023年10月06日 08:21:01 +00:00Commented Oct 6, 2023 at 8:21
-
\$\begingroup\$ What mux chip(s) are you currently using? Is it specifically for I2C? There are a few single chip I2C parts specifically for mux-ing I2C sets. For example TI TCA9548A, an 8 channel mux which itself is I2C controlled. \$\endgroup\$Nedd– Nedd2023年10月06日 10:26:11 +00:00Commented Oct 6, 2023 at 10:26
1 Answer 1
Your question and followup comments are so vague, it's difficult to get a clear picture of what you're trying to do. But I've worked with Microblaze before, so I think I've got the gist of it. Let me know if any of this is off-track, and I'll try to correct it.
You have a Spartan-3 FPGA, and in this FPGA, you have instantiated a Microblaze processor. Connected to this processor is one standard Xilinx I2C controller.
External to the FPGA, you have six I2C devices, each connected to its own pair of SCL/SDA pins on the FPGA (and all 12 of these pins have external pullup resistors).
You want to be able to logically connect the I2C controller to one pair of pins at a time, keeping the remaining five pairs in an inactive state. Is this correct so far?
When you connect the I2C controller directly to one pair of pins, everything works fine. In this case, the ISE software is automatically instantiating the IOBUFs for SCL and SDA.
However, when you try to insert logic to multiplex the I2C controller among the 6 pairs of pins, everything falls apart. In this case, you need to instantiate the 12 IOBUFs in your custom logic, which means that you need to correctly handle the I
, O
and T
ports on each that need to connect to the I2C controller, and connect the IO
port to the external pin.
Here's a block diagram of the kind of logic you'll require. I'll leave it to you translate it to your favorite language or whatever.
I'm assuming that there's a GPIO port somewhere on your Microblaze that can supply the "select" signals. This diagram only shows interfaces for two external devices; the concept can be extended to any number.
schematic
simulate this circuit – Schematic created using CircuitLab
Note that the select lines from the GPIO are active-low. This is because the inactive state of the T
port is high. Keeping the select lines high turns off all of the pin drivers. Drive one select line low to enable the corresponding external device.
AND1-AND2 and OR9-OR12 form a pair of multiplexers for the O
signals. These AND gates will get wider as you add more devices.
If I were going to do this in a VHDL project, I would code it as a separate module that goes between the IP and the pins:
-- This module allows a single I2C controller to be switched among multiple
-- physical sets of pins. "select_l" is a bit vector -- normally, only one
-- bit at a time should be low, but this module also allows "broadcasting"
-- to multiple ports simultaneously.
entity i2c_mux is
generic (
NPP : natural := 3 -- number of ports - 1
);
port (
-- connections to IP controller
ip_scl_o : out std_logic;
ip_scl_i : in std_logic;
ip_scl_t : in std_logic;
ip_sda_o : out std_logic;
ip_sda_i : in std_logic;
ip_sda_t : in std_logic;
-- connections to external pin drivers
pin_scl_o : in std_logic_vector (NPP downto 0);
pin_scl_i : out std_logic_vector (NPP downto 0);
pin_scl_t : out std_logic_vector (NPP downto 0);
pin_sda_o : in std_logic_vector (NPP downto 0);
pin_sda_i : out std_logic_vector (NPP downto 0);
pin_sda_t : out std_logic_vector (NPP downto 0);
-- port select bit vector
select_l : in std_logic_vector (NPP downto 0)
);
end i2c_mux;
architecture rtl of i2c_mux is
begin
-- signals from controller to pins
pin_scl_i <= select_l or ip_scl_i;
pin_scl_t <= select_l or ip_scl_t;
pin_sda_i <= select_l or ip_sda_i;
pin_sda_t <= select_l or ip_sda_t;
-- signals from pins to controller
ip_scl_o <= and (select_l or pin_scl_o);
ip_sda_o <= and (select_l or pin_sda_o);
end rtl;
-
\$\begingroup\$ Given the device is a Spartan-3, think that means that Xilinx ISE is used rather than Vivado. Not sure if that changes which needs to be done. \$\endgroup\$Chester Gillon– Chester Gillon2023年10月06日 18:52:23 +00:00Commented Oct 6, 2023 at 18:52
-
\$\begingroup\$ @ChesterGillon: Thanks, fixed. No, it doesn't change anything. \$\endgroup\$Dave Tweed– Dave Tweed2023年10月06日 18:59:38 +00:00Commented Oct 6, 2023 at 18:59
-
\$\begingroup\$ @DaveTweed Yeah that is correct. Sorry about the way that I asked my question. Its my first time here so I dont know the right way to ask the question. Also thanks for being patient. The way you described it is correct. The only difference is I think I can use 1 pair of iobufs which I can connect to the output of the mux. But if you think the other way is better I can use your method as well. This is just my idea. I tried reading the datasheet of the xps_iic module and other sources and couldnt find anything that I can use. \$\endgroup\$newtoallthis– newtoallthis2023年10月06日 21:05:11 +00:00Commented Oct 6, 2023 at 21:05
-
\$\begingroup\$ No, you can't connect the
IO
port of an IOBUF to anything other than a pin. Each IOBUF is physically located next to a pad on the die, and can only be connected to that pin. Therefore, your design needs to instantiate an IOBUF for each of the 12 pins. I will add detail to my answer shortly. \$\endgroup\$Dave Tweed– Dave Tweed2023年10月06日 22:00:48 +00:00Commented Oct 6, 2023 at 22:00 -
1\$\begingroup\$ @DaveTweed thanks for your answer. This is exactly what I was looking for. I will try it and get back to you if I have any problems or questions. Thanks again. \$\endgroup\$newtoallthis– newtoallthis2023年10月07日 08:20:48 +00:00Commented Oct 7, 2023 at 8:20