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
*/