1
\$\begingroup\$

I'm making a library for my own MCP23S17 GPIO expander because no one library has everything I need... My main problem now is: what are the raw bits I need to send to SPI.transfer() inorder to read/write registers?

I have read the data sheet but I'm at the limit of what I can understand on my own. On page 16, if we say I want to set all pins to output I would send from left to right:

  • 0100
  • 3bit device address
  • 1bit R/W (write=0,read=1)
  • 8 bit register address
  • I dont know whats next - is it the new register data?
0100 001 0 00000000 00000000 // bits for setting gpioA to output
0100 001 0 00000001 00000000 // bits for setting gpioB to output

Is this correct?

And for reading a register, lets say read status for GPIO is:

  • 0100
  • 3bit device address
  • 1bit R/W (write=0,read=1)
  • 8 bit register address
  • and I assume the contents of the register is returned
0100 001 1 00010010  // bits for reading gpioA to output
0100 001 1 00010011  // bits for reading gpioB to output

Do I need to send dummy data when reading register in order to get the data back?

Bit are sent with most significant bit first, in SPI MODE 0

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

The mode of access can vary for these devices.

(1) Per page 15 3.2.3 the basic access-scheme is a frame of at least 3 bytes. OpCode, Address, Data. Please see figure 3-5 for the OpCode Byte and figure 3-7 for the OpCode/Address combination.

struct BasicIoFrame{
   unsigned char OpCode;
   unsigned char RegAdr;
   unsigned char Data;
}

(2) After these two bytes are send to the device, at least one data byte is required. If you declared a write-op the data send will be latched into the register given via the address. If you declared a read-op, the data within the given register is clocked out via MISO.

(3) The device supports a so called sequential-mode. Please see 3.2.3.3

The pointer to the register given with the initial address will auto-increment after each payload-byte received.

struct ExtendedIoFrame{
   unsigned char OpCode;
   unsigned char RegAdr;
   unsigned char Data[x];
}

E.G. You send a Write-To-Reg17 command with two data bytes (0x01, 0x02) - this way, Reg17 will read 0x01 and Reg18 will read 0x02.

E.G. You send a Read-From-Reg3 command with four additional bytes - this way, MISO will read Reg3, Reg4, Reg5 and Reg6.

(4) To set all Pins on Bank-A as outputs you will have to send 0100|XXX|0-0000|0000-0000|0000 The X's are the physical address pins of your IC. Please see page 1. This command will write 0x00 to IODIRA . See Table 3-5

void SPI_ReadWrite(U08* Buffer, U08 ByteCnt);

struct BasicIoFrame frame;
BasicIoFrame.OpCode = 0b0100XXX0;
BasicIoFrame.RegAdr = 0U;
BasicIoFrame.Data = 0U;

SPI_ReadWrite(&frame, 3)

NOTE: You can also use an extended Frame in sequential mode and write GPIOA and GPIOB in one transaction

(5) To get the current GPIO State of Bank-A you will have to send 0100|XXX|1-0x12-0x00 . This will read the GPIOA register. See Table 3-5. The GPIOA register contents are send on MISO when you send the 0x00 byte.

void SPI_ReadWrite(U08* Buffer, U08 ByteCnt);

struct BasicIoFrame frame;
BasicIoFrame.OpCode = 0b0100XXX1;
BasicIoFrame.RegAdr = 0x12U;
BasicIoFrame.Data = 0U;

SPI_ReadWrite(&frame, 3)

if(BasicIoFrame.Data & 0x01) { /* GPIO-A-0 is TRUE */ }

NOTE: You can also use an extended Frame in sequential mode and read GPIOA and GPIOB in one transaction

(6) As i dont know which controller you are using i will go with a 'naive' implementation for SPI_ReadWrite

void SPI_ReadWrite(U08* Buffer, U08 ByteCnt){
    Set_CS(0U);
    while(ByteCnt > 0U){
         Spi_DataReg = *Buffer;
         while( SPI_IsBusy == TRUE ){/*wait*/}
         *Buffer = Spi_DataReg;
         Buffer++;
         ByteCnt--;
    }
    Set_Cs(1U);
 }

(7) And this is how i would implemetn such an library in C

#define MCP23S17_READ (U08)(0x01U)
#define MCP23S17_WRITE (U08)(0x00U)

/* Not within .h file */
static void __MCP23S17_SpiReadWrite(U08* Buffer, U08 ByteCnt){
    Set_CS(0U);
    while(ByteCnt > 0U){
        Spi_DataReg = *Buffer;
        while( SPI_IsBusy == TRUE ){/*wait*/}
        *Buffer = Spi_DataReg;
        Buffer++;
        ByteCnt--;
    }
    Set_Cs(1U);
}
    
/* Not within .h file */
__attribute__((always_inline))  
static inline U08 __MCP23S17_GetOpCode(U08 DeviceAdr, U08 ReadWrite){
    return (U08)(0x40U | ( ( DeviceAdr & 0x07U ) << 1U ) | ( ReadWrite & 0x01U ));
}

/* Not within .h file */
static U08 __MCP23S17_GetRegX(U08 DeviceAdr, U08 RegAdr){
    unsigned char buf[3];
    buf[0] = __MCP23S17_GetOpCode( DeviceAdr , MCP23S17_READ );
    buf[1] = RegAdr; 
    buf[2] = 0U;
    __MCP23S17_SpiReadWrite(&buf,3);
    return buf[2];
}

/* Not within .h file */
static U08 __MCP23S17_SetRegX(U08 DeviceAdr, U08 RegAdr, U08 Data){
    unsigned char buf[3];
    buf[0] = __MCP23S17_GetOpCode( DeviceAdr , MCP23S17_WRITE );
    buf[1] = RegAdr; 
    buf[2] = Data;
    __MCP23S17_SpiReadWrite(&buf,3);
}

/* 
* Repeat for every Register available... 
* You can use excel to do this with string concat and the datasheet table 
* Make sure to declare these signatures in .h file
*/
__attribute__((always_inline))
inline U08 MCP23S17_GetRegXXXX(U08 DeviceAdr){
    return __MCP23S17_GetRegX(DeviceAdr , /* Specific Reg Address */);
}

__attribute__((always_inline))
inline void MCP23S17_SetRegXXXX(U08 DeviceAdr, U08 Data){
    __MCP23S17_SetRegX(DeviceAdr , /* Specific Reg Address */ , Data );
}

(8) I would then proceed with something like

/*
* To use it like 
*/
#define DISPLAY_GPIO    (U08)(0x01U)
#define KEYBOARD_GPIO   (U08)(0x01U)
#define FRONTPANEL_GPIO (U08)(0x01U)

if ( MCP23S17_GetRegGPIO_A(DISPLAY_GPIO) & 0x80 ){....}

...or...

MCP23S17_SetRegGPPU_A(FRONTPANEL_GPIO , 0x03);

/* 
* You can of course wrap this library again to end up with something like
*/

void FrontpanelIo_DoSuperDuperStuff(){
    MCP23S17_SetRegGPPU_A(FRONTPANEL_GPIO , 0x03);
    MCP23S17_SetRegXX(FRONTPANEL_GPIO , 0xXX);
    if ( MCP23S17_GetRegGPIO_A(FRONTPANEL_GPIO) & 0x80 ){....}
}

void KeyboardIo_DoXYZ(){
    ....
}

/*
* I then commonly wrap these again ....
* But now where are in the mists of software-engineering
*/
\$\endgroup\$
12
  • \$\begingroup\$ this is really well written, and i cant thank you enough for that. I do have one question, if you are familiar with the IC (i will be at awe if you just got all that with the datasheet alone). I have 3 of these on the same bus, and at power up, hardware addressing is not yet enabled (IOCON: bit3 HAEN) so i assume if i send a write command all 3 of those chips will update their registers correct? and for the initial write command i will jsut use 000 as the address? \$\endgroup\$
    – DrakeJest
    Commented Apr 11, 2023 at 15:38
  • \$\begingroup\$ You are welcome - glad it helps. I am not familiar with the IC, but judging from Page 20 3.5.5 i would assume so. BUT: Yo ucoudl get unlucky...I have seen ICs with similar addressing modes, which require to be addressed one at a time. Therefore, you must then hold all but one in reset until it is configured. You will have to try. But chances it will work are high i guess. \$\endgroup\$ Commented Apr 11, 2023 at 15:45
  • \$\begingroup\$ When the IOCON.HAEN is not set, then the device acts as if the A2, A1 and A0 were configured as 0 (that's section 3.5.6). Keep in mind that SPI is usually driven as a "totem pole", not as an "open collector" bus, so -- in the general case -- if you have several chips behind the same CS, it is dangerous to have more than one of them sending data to you at the same time. In other words, you will get a short circuit on your SO pin (MISO in other SPI terms) if you try to read data from several chips at once. \$\endgroup\$ Commented Apr 11, 2023 at 17:17
  • 1
    \$\begingroup\$ @DrakeJest Correct. After the 16th bit is clocked in (while CS is low) SO is asserted on the falling edge of SCK \$\endgroup\$ Commented Apr 12, 2023 at 16:32
  • 1
    \$\begingroup\$ I have a working board with a pair of MCP23S17 on the same CS pin, with different "address" pins. I'm using the driver in the Linux kernel, and it works just fine. Do not worry about that, just set your HAEN bits the first thing you do, and make sure you do not ever read before you do that. \$\endgroup\$ Commented Apr 13, 2023 at 0:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.