I am working with a MCP23S17 SPI I/O expander chip in a VHDL project on my Basys 2.
At first glance I thought this was just a simple SPI interface where I put the chip select low and it will give me the data on the MISO line but it looks like it is bit more complicated with commands and initialization needed.
I added some setup bits ("0100" & "000" & "1") that pop out on the MOSI line once when you try to read data. but nothing has changed. There seems to be a lot of registers to hold settings but I have no clue on how to set these.
Here is a diagram of how I have it all hooked up. The testing I/O is only to make sure I have some known bits that should show up if the transaction succeeds. I will be using the B side of the chip so if something special needs to happen to read that then please explain.
What needs to happen to read data from the chip?
Here is the SPI module (SPI.vhd) that I have written so far.
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
-- Uncomment the following library declaration if using
-- arithmetic functions with Signed or Unsigned values
--use IEEE.NUMERIC_STD.ALL;
-- Uncomment the following library declaration if instantiating
-- any Xilinx primitives in this code.
--library UNISIM;
--use UNISIM.VComponents.all;
entity SPI is
Generic (
dataWidthN : positive := 8
);
port(
sck: in std_logic; -- clock
mosi: out std_logic; -- data going into slave
miso: in std_logic; -- data coming out of slave
cs: in std_logic; -- chip select
address: in std_logic_vector(2 downto 0); -- 0 - 7
data: out std_logic_vector(dataWidthN-1 downto 0);
debug: out std_logic_vector(1 downto 0)
);
end SPI;
architecture Behavioral of SPI is
signal data_reg : STD_LOGIC_VECTOR (dataWidthN-1 downto 0);
begin
data <= data_reg;
process (sck)
variable isSetup: std_logic := '0';
variable setupBits: std_logic_vector(7 downto 0) := "0100" & address & "1";
variable setupBitCount: natural := 0;
begin
if rising_edge(sck) then -- rising edge of SCK
if (cs = '0') then -- SPI CS must be selected
if (isSetup = '0' and setupBitCount < 7) then
mosi <= setupBits(7-setupBitCount);
setupBitCount := setupBitCount + 1;
else
isSetup := '1';
setupBitCount := 0;
end if;
if isSetup = '1' then
debug <= "11";
-- shift serial data into dat_reg on each rising edge
-- of SCK, MSB first
data_reg <= data_reg(dataWidthN-2 downto 0) & miso;
else
debug <= "10";
end if;
end if;
end if;
end process;
end Behavioral;
I have not found many articles talking about this chip using code. I found some Arduino stuff but they all use the SPI library which doesn't help explain what exactly is happening. Here are the few links that I have found:
- http://www.electrosome.com/expanding-io-ports-of-pic-microcontroller/
- http://playground.arduino.cc/Main/MCP23S17
- http://www.phatio.com/ideas/mcp23X17/
- http://tech-tut.com/how-to-expand-inputs-and-outputs-using-mcp23s17/
Edit:
Alright after working on what Dave Tweed said to do. I am able to send and produce the commands on MOSI but nothing comes back on the MISO line. Keep in mind that the FPGA should get the data and I have a logic analyzer that will show the bits if something comes out and my FPGA code is wrong.
CS: 1111000000000000000000000000
MOSI: xxxx0100aaa10000110000000000
MISO: xxxxxxxxxxxxxxxxxxxxxxxxxxxx
Here is the simulation in ISim. **This will not return data on MISO because it is just a simulation with no chip to actually send back proper data.*
And from a logic analyzer in the real world:
Here is the update SPI.vhd module code:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
entity SPI is
Generic (
dataWidthN : integer := 8
);
port(
sck: in std_logic; -- clock
mosi: out std_logic; -- data going into slave
miso: in std_logic; -- data coming out of slave
cs: in std_logic; -- chip select
address: in std_logic_vector(2 downto 0); -- 0 - 7
data: out std_logic_vector(dataWidthN-1 downto 0);
debug: out std_logic_vector(1 downto 0)
);
end SPI;
architecture Behavioral of SPI is
type state_type is (idle, s_readSetup, s_read);
signal data_reg : STD_LOGIC_VECTOR (dataWidthN-1 downto 0);
begin
data <= data_reg;
spi_read: process (sck)
variable transactionComplete: std_logic := '0';
variable setupBits: std_logic_vector(15 downto 0);
variable setupCmdBitCount: natural := 0; -- setup command is 16 in length
variable readCmdBitCount: natural := 0; -- A command is same as dataWidthN
variable currState: state_type := idle;
begin
setupBits := "0100" & address & "1" & "00001100";
if falling_edge(sck) then -- rising edge of SCK
case currState is
when s_readSetup =>
if (cs = '0') then -- SPI CS must be selected
debug <= "10";
mosi <= setupBits(setupBits'length-1-setupCmdBitCount);
setupCmdBitCount := setupCmdBitCount + 1;
-- Move to the next state
if setupCmdBitCount >= setupBits'length then
setupCmdBitCount := 0;
currState := s_read;
end if;
else
currState := idle;
end if;
when s_read =>
if (cs = '0') then -- SPI CS must be selected
debug <= "11";
-- shift serial data into dat_reg on each rising edge
-- of SCK, MSB first
data_reg <= data_reg(dataWidthN-2 downto 0) & miso;
readCmdBitCount := readCmdBitCount + 1;
if readCmdBitCount >= data'length then
readCmdBitCount := 0;
transactionComplete := '1';
currState := idle;
end if;
else
currState := idle;
end if;
-- Idle state: if the state is unknown then we just go idle
when others =>
debug <= "00";
setupCmdBitCount := 0;
readCmdBitCount := 0;
mosi <= '0';
if cs = '0' and transactionComplete = '0' then
mosi <= setupBits(setupBits'length-1-setupCmdBitCount);
setupCmdBitCount := setupCmdBitCount + 1;
currState := s_readSetup;
elsif cs = '1' and transactionComplete = '1' then
transactionComplete := '0';
end if;
end case;
end if;
end process;
end Behavioral;