MCP23S17
Hello, this is a pre-version of a working module I worked on some months ago, and unfortunately, for various reasons, I never got time to work on it again.
In this article, I'm publishing two modules dealing with the Microchip MCP23S17 (SPI) port expander. first module handles only one MCP23S17 module. It's usefull when you need only one module in your design. the second module handles multiple MCP23S17 modules in the same circuit.
-Why two modules? simply because in the case of multiple modules, you have to pass an additional parameter to all routines: the additional param is the chip physical address. The single MCP23S17 module does not have this param in its routines, and thus, let you save some precious ram bytes that you may need for other stuff.
The single chip module has been also modded (some io direction registers buffered in local variables to speedup operations - mainly done for GLCD through SPI stuff).
Main Features:
1- Handle one or multiple MCP23S17 modules.
2- Use Hardware or Software SPI to manage the modules. Software SPI is usefull for little PICs that does not have SPI hardware module (like the little 18 pins 18F1320 mcu).
3- Most of functions/subs make the MCP chip act as additional PIC ports.
4- MCP chips are handled as two 8 bit ports.
5- All routines to read/write registers are available.
What's not handled:
- This is really a pre-version. Next version of the driver (under devlopment) handles also Interrupts.
Actually, interrupts can be handled by reading/writing concerned registers, it will be used transparently in upcomming drivers.
- Full support for SPI and I2C version of this chips. Switching from SPI to I2C version will be done by selecting only one OPTION !
This work has been done very quickly, when I was working on GLCD through SPI expander support. Instead of keeping all that on my PC, I prefer to share it in case anyone has urgent need to it. I'll also post all my GLCD stuff with all comments on results in the article session in next couple of days.
I hope you can find this helpfull.
For any comment, or feedback (or bug report), please post to Swordfish Forum.
VERY IMPORTANT NOTICE concerning Swordfish SPI module:
PS. I never got this module to work with official Swordfish hatdware SPI driver. I used instead a modified version of (hardware) SPI driver using some comments published by Steven on the forum (and sent to me by mail - Great thank Steven). The modified version of the SPI module is also published at the END of this article.
IMPORTANT Options
For Both drivers (Single Chip and Multi-Chip)
- option MCPSPI_SOFTWARE=true // use software SPI...
- option MCPSPI_SOFTWARE = false // Use Hardware SPI
- Option MCP_CS = PORTC.1 // Chip Select Pin
- Option MCP_RST = PORTC.2 // may be commented If RST is Not used
For the SINGLE MCP Chip driver, there is an additional option to set its physical address. Default is 0.
- option MCPCHIP_ADDRESS = 0
Single MCP23S17 chip module
connection to hardware SPI is shown in following diagram (for software SPI you can use Swordfish Options to modify pins affectation).
Reset pin can be ommited (saving one pic pin), and pulled up.
Module Code
{ ************************************************************************* * Name : MCP23S17.BAS * * Author : Ahmed Lazreg ([email protected]) * * http://www.pocketmt.com * * Notice : Copyright (c) 2006 Pocket MicroTechnics * * : All Rights Reserved * * Date : 01/03/2008 * * Version : 1.0 * * Modified: * * Notes : This module handles a SINGLE MCP23S17 - 16 pins I/O port * * expander from Microchip as 2x8 bits PortA/PortB. * * If you want to use multiple chips simultaniously, use * * MCP23S17_MULT.BAS instead. It handles multi_chip selection * * using chip address pins * ************************************************************************* } Module MCP23S17 // hardware or software SPI... #if IsOption(MCPSPI_SOFTWARE) And Not (MCPSPI_SOFTWARE in (true, false)) #error MCPSPI_SOFTWARE, "Invalid option, must be TRUE or FALSE." #endif #option MCPSPI_SOFTWARE = false #if MCPSPI_SOFTWARE=true // use software SPI... Include "sspi.bas" #else // use hardware SPI... Include "SPI.BAS" #EndIf #Option MCP_CS = PORTC.1 #Option MCP_RST = PORTC.2 // may be commented If RST is Not used // validate SCK pin... #If IsOption(MCP_CS) And Not IsValidPortPin(MCP_CS) #Error MCP_CS, "Invalid option. MCP_CS must be a valid port pin." #EndIf // validate RST pin... #If IsOption(MCP_RST) And Not IsValidPortPin(MCP_RST) #Error MCP_RST, "Invalid option. MCP_RST must be a valid port pin." #EndIf #if IsOption(MCPCHIP_ADDRESS) And Not (MCPCHIP_ADDRESS in (0 to 7)) #error MCPCHIP_ADDRESS, "Invalid option, MCPCHIP_ADDRESS must be between 0 and 7." #endif #if Not IsOption(MCPCHIP_ADDRESS) #option MCPCHIP_ADDRESS = 0 // Default chip address to [A2 A1 A0] = [000] // Disable the warning if you do not want it! #warning "MCPCHIP_ADDRESS option is set default to [0]." #endif Public Dim CS As MCP_CS.MCP_CS@ // Chip select #if IsOption(MCP_RST) Public Dim RST As MCP_RST.MCP_RST@ // RST pin #endif //---------------------------------------------------------------------------- //Please ignore these options ... only here for the GLCD through SPI stuff... #option MCPPortA0 = 0 #option MCPPortA1 = 1 #option MCPPortA2 = 2 #option MCPPortA3 = 3 #option MCPPortA4 = 4 #option MCPPortA5 = 5 #option MCPPortA6 = 6 #option MCPPortA7 = 7 #option MCPPortB0 = 8 #option MCPPortB1 = 9 #option MCPPortB2 = 10 #option MCPPortB3 = 11 #option MCPPortB4 = 12 #option MCPPortB5 = 13 #option MCPPortB6 = 14 #option MCPPortB7 = 15 //---------------------------------------------------------------------------- Public Const IODIRA = $00, IODIRB = $01, IPOLA = $02, IPOLB = $03, GPINTENA = $04, GPINTENB = $05, DEFVALA = $06, DEFVALB = $07, INTCONA = $08, INTCONB = $09, IOCONA = $0A, IOCONB = $0B, GPPUA = $0C, GPPUB = $0D, INTFA = $0E, INTFB = $0F, INTCAPA = $10, INTCAPB = $11, GPIOA = $12, GPIOB = $13, OLATA = $14, OLATB = $15 //Control Byte For SPI operations [0100][A2][A1][A0][R=1/W=0] Private Const CONTROL_BYTE_MASK_R As Byte = %01000001 Or (MCPCHIP_ADDRESS<<1) Private Const CONTROL_BYTE_MASK_W As Byte = %01000000 Or (MCPCHIP_ADDRESS<<1) Private Const CONTROL_IOCON = %00011000 // Init value for IOCON - bits details in following comment { Reg IOCON = [BANK][MIRROR][SEQOP][DISSLW][HAEN][ODR][INTPOL][-Unused] Bit 7 BANK: Controls how the registers are addressed 1 = The registers associated With each Port are separated into different banks 0 = The registers are in the same bank (addresses are sequential) Bit 6 MIRROR: INT Pins Mirror Bit 1 = The INT pins are internally connected 0 = The INT pins are Not connected. INTA is associated With PortA And INTB is associated With PortB Bit 5 SEQOP: Sequential Operation mode Bit. 1 = Sequential operation disabled, address pointer does Not increment. 0 = Sequential operation Enabled, address pointer increments. Bit 4 DISSLW: Slew Rate control Bit For SDA Output. 1 = Slew rate disabled. 0 = Slew rate Enabled. Bit 3 HAEN: Hardware Address Enable Bit (MCP23S17 only). Address pins are always Enabled on MCP23017. 1 = Enables the MCP23S17 address pins. 0 = Disables the MCP23S17 address pins. Bit 2 ODR: This Bit configures the INT pin As an open-drain Output. 1 = Open-drain Output (overrides the INTPOL Bit). 0 = Active driver Output (INTPOL Bit sets the polarity). Bit 1 INTPOL: This Bit sets the polarity of the INT Output pin. 1 = Active-High. 0 = Active-Low. Bit 0 Unimplemented: Read As ‘0’. } //Local Variables used to cache the state of Direction registers Dim DirRegA, DirRegB, DataRegA, DataRegB As Byte { **************************************************************************** * Name : WriteReg * * Purpose : Writes pData byte to register address given by pRegAddress * **************************************************************************** } Public Sub WriteReg(pRegAddress As Byte, pData As Byte) CS = 0 'DelayUS(5) 'delays can/should be added if 'you have problems at High Clock values WriteByte(CONTROL_BYTE_MASK_W) WriteByte(pRegAddress) WriteByte(pData) CS = 1 'DelayUS(5) End Sub { **************************************************************************** * Name : ReadReg * * Purpose : Read register address given by pRegAddress * **************************************************************************** } Public Function ReadReg(ByVal pRegAddress As Byte) As Byte CS = 0 'DelayUS(5) 'delays can/should be added if 'you have problems at High Clock values WriteByte(CONTROL_BYTE_MASK_R) WriteByte(pRegAddress) Result = ReadByte() CS = 1 'DelayUS(5) End Function { **************************************************************************** * Name : ReadPortA * * Purpose : Returns PortA reg value * **************************************************************************** } Public Inline Function ReadPortA() As Byte Result = ReadReg(GPIOA) End Function { **************************************************************************** * Name : ReadPortB * * Purpose : Returns PortB reg value * **************************************************************************** } Public Inline Function ReadPortB() As Byte Result = ReadReg(GPIOB) End Function { **************************************************************************** * Name : ReadPinPortA * * Purpose : Returns Value of a specified PortA Pin * **************************************************************************** } Public Inline Function ReadPinPortA(pPin As Byte) As Bit Dim tmp As Byte tmp = ReadReg(GPIOA) Result = tmp.Bits(pPin) End Function { **************************************************************************** * Name : ReadPinPortB * * Purpose : Returns Value of a specified PortB Pin * **************************************************************************** } Public Inline Function ReadPinPortB(pPin As Byte) As Bit Dim tmp As Byte tmp = ReadReg(GPIOB) Result = tmp.Bits(pPin) End Function { **************************************************************************** * Name : WritePortA * * Purpose : Writes PortA Latch register with pData value * **************************************************************************** } Public Inline Sub WritePortA(pData As Byte) WriteReg(OLATA, pData) // I prefer to write to latch instead of direct port // This is safer in case if some pins were inputs End Sub { **************************************************************************** * Name : WritePortB * * Purpose : Writes PortB Latch register with pData value * **************************************************************************** } Public Inline Sub WritePortB(pData As Byte) WriteReg(OLATB, pData) End Sub { **************************************************************************** * Name : SetPinPortA * * Purpose : Sets PortA pin given by pPin to High state * **************************************************************************** } Public Sub SetPinPortA(pPin As Byte) Dim tmp As Byte tmp = ReadPortA() Or (1<<pPin) WriteReg(OLATA, tmp) End Sub { **************************************************************************** * Name : SetPinPortB * * Purpose : Sets PortB pin given by pPin to High state * **************************************************************************** } Public Sub SetPinPortB(pPin As Byte) Dim tmp As Byte tmp = ReadPortB() Or (1<<pPin) WriteReg(OLATB, tmp) End Sub { **************************************************************************** * Name : SetPinPortAB * * Purpose : Sets PortA or PortB pin given by pPin to High state * * Pins are numbered from 0 to 15. * **************************************************************************** } Public Sub SetPinPortAB(pPin As Byte) If pPin<8 Then SetPinPortA(pPin) Else SetPinPortB(pPin) EndIf End Sub { **************************************************************************** * Name : ClearPinPortA * * Purpose : Sets PortA pin given by pPin to Low state * **************************************************************************** } Public Sub ClearPinPortA(pPin As Byte) Dim tmp As Byte tmp = ReadPortA() And Not (1<<pPin) WriteReg(OLATA, tmp) End Sub { **************************************************************************** * Name : ClearPinPortB * * Purpose : Sets PortB pin given by pPin to Low state * **************************************************************************** } Public Sub ClearPinPortB(pPin As Byte) Dim tmp As Byte tmp = ReadPortB() And Not (1<<pPin) WriteReg(OLATB, tmp) End Sub { **************************************************************************** * Name : ClearPinPortAB * * Purpose : Clears PortA or PortB pin given by pPin to low state * * Pins are numbered from 0 to 15. * **************************************************************************** } Public Sub ClearPinPortAB(pPin As Byte) If pPin<8 Then ClearPinPortA(pPin) Else ClearPinPortB(pPin) EndIf End Sub { **************************************************************************** * Name : SetTrisPortA * * Purpose : Sets PortA pins direction 0=Output 1=Input * **************************************************************************** } Public Inline Sub SetTrisPortA(pDirection As Byte) WriteReg(IODIRA, pDirection) DirRegA = pDirection End Sub { **************************************************************************** * Name : SetTrisPortB * * Purpose : Sets PortB pins direction 0=Output 1=Input * **************************************************************************** } Public Inline Sub SetTrisPortB(pDirection As Byte) WriteReg(IODIRB, pDirection) DirRegB = pDirection End Sub { **************************************************************************** * Name : GetTrisPortA * * Purpose : Gets PortA pins direction 0=Output 1=Input * **************************************************************************** } Public Inline Function GetTrisPortA() As Byte result = DirRegA End Function { **************************************************************************** * Name : GetTrisPortB * * Purpose : Gets PortB pins direction 0=Output 1=Input * **************************************************************************** } Public Inline Function GetTrisPortB() As Byte result = DirRegB End Function { **************************************************************************** * Name : SetPinTrisPortA * * Purpose : Sets the Tris of a Single Pin from PortA pins 0=Output 1=Input* **************************************************************************** } Public Sub SetPinTrisPortA(pPin As Byte, pDirection As Bit) DirRegA.bits(pPin) = pDirection WriteReg(IODIRA, DirRegA) End Sub { **************************************************************************** * Name : SetPinTrisPortB * * Purpose : Sets the Tris of a Single Pin from PortB pins 0=Output 1=Input* **************************************************************************** } Public Sub SetPinTrisPortB(pPin As Byte, pDirection As Bit) DirRegB.bits(pPin) = pDirection WriteReg(IODIRB, DirRegB) End Sub { **************************************************************************** * Name : SetPinTrisPortAB * * Purpose : Sets the Tris of a Single Pin from PortA or PortB * * 0=Output 1=Input Pins are numbered from 0 to 15 * **************************************************************************** } Public Sub SetPinTrisPortAB(pPin As Byte, pDirection As Bit) If pPin<8 Then SetPinTrisPortA(pPin, pDirection) Else SetPinTrisPortB(pPin, pDirection) End If End Sub { **************************************************************************** * Name : SetPullUpsPortA * * Purpose : Sets the Pullups resistors for any Pin of PortA * * Pullups can be enabled/disabled independently for any pin of * * the port 0=Disabled 1=Enabled * **************************************************************************** } Public Inline Sub SetPullUpsPortA(pPullUps As Byte) WriteReg(GPPUA, pPullUps) End Sub { **************************************************************************** * Name : SetPullUpsPortB * * Purpose : Sets the Pullups resistors for any Pin of PortB * * Pullups can be enabled/disabled independently for any pin of * * the port 0=Disabled 1=Enabled * **************************************************************************** } Public Inline Sub SetPullUpsPortB(pPullUps As Byte) WriteReg(GPPUB, pPullUps) End Sub { **************************************************************************** * Name : InitChip * * Purpose : Initialize the MCP23S17 CHIP * **************************************************************************** } Public Sub InitChip() #if IsOption(MCP_RST) // Reset the Chip RST = 0 DelayMS(10) RST = 1 DelayMS(10) #EndIf // At initialization, we must force CHIP_ADDRESS to "000" regardless of // actual MCPCHIP_ADDRESS value. After initialization, once IOCON.HAEN // enabled, MCPCHIP_ADDRESS is taken into account by the chip. CS = 0 //DelayUS(10) WriteByte(CONTROL_BYTE_MASK_W And %11110001) // Force ChipAdd to 000 WriteByte(IOCONA) // writing to IOCONA writes also to IOCONB WriteByte(CONTROL_IOCON) CS = 1 DelayMS(10) End Sub { **************************************************************************** * Name : Initialize * * Purpose : Initializes module and included modules * **************************************************************************** } Public Sub Initialize() #If MCPSPI_SOFTWARE SSPI.Initialize SSPI.SetClock(spiIdleHigh) #Else SPI.SetAsMaster(spiOscDiv4) SPI.SetClock(spiIdleHigh, spiRisingEdge) SPI.SetSample(spiSampleMiddle) EnableSPI() #EndIf Output(CS) #If IsOption(MCP_RST) Output(RST) #EndIf High(CS) High(RST) InitChip() SetTrisPortA($ff) 'intializes PortA direction register into RegDirA local variable SetTrisPortB($ff) 'caches PortB direction register into RegDirB local variable //SetPullUpsPortA($FF) //SetPullUpsPortB($00) End Sub Initialize()
Example Code
{ ***************************************************************************** * Name : test for Single chip MCP23S17 driver * * Author : Ahmed Lazreg ([email protected]) * * http://www.pocketmt.com * * Notice : Copyright (c) 2008 Pocket MicroTechnics * * : All Rights Reserved * * Date : 10/08/2008 * * Version : 1.0 * * Notes : * * : * ***************************************************************************** } Device = 18F452 Clock = 20 #option MCPSPI_SOFTWARE = false // set to true to use Software SPI #option MCPCHIP_ADDRESS = 0 //Physical address of the MCP23S17 chip Include "mcp23s17.bas" Dim dataIn As Byte MCP23S17.SetTrisPortA($00) // You can set the tris of anyPin separately MCP23S17.SetTrisPortB($FF) MCP23S17.SetPullUpsPortB($FF) // depending on your config, you can activate pullups MCP23S17.SetPullUpsPortA($00) // or disable them for "any PIN" independently While true dataIn = MCP23S17.ReadPortB() MCP23S17.WritePortA(dataIn) Wend
Multiple MCP23S17 chips module
Connection to hardware SPI is shown in following diagram (for software SPI you can use Swordfish Options to modify pins affectation).
Basically it's the same as the Single Chip module, you just have to pass as first param to all functions the chip address.
Module Code
{ ************************************************************************* * Name : MCP23S17_MULT.BAS * * Author : Ahmed Lazreg ([email protected]) * * http://www.pocketmt.com * * Notice : Copyright (c) 2006 Pocket MicroTechnics * * : All Rights Reserved * * Date : 01/03/2008 * * Version : 1.0 * * Modified: * * Notes : This module handles multiple MCP23S17 - 16 pins I/O port * * expander from Microchip as 2x8 bits PortA/PortB. * * The module shares the same Clk/SDI/SDO/CS/RST pins, * * selection is done using chip address pins. * ************************************************************************* } Module MCP23S17_MULT // hardware or software SPI... #if IsOption(MCPSPI_SOFTWARE) And Not (MCPSPI_SOFTWARE in (true, false)) #error MCPSPI_SOFTWARE, "Invalid option, must be TRUE or FALSE." #endif #option MCPSPI_SOFTWARE = false #if MCPSPI_SOFTWARE=true // use software SPI... Include "sspi.bas" #else // use hardware SPI... Include "SPI.BAS" #EndIf #Option MCP_CS = PORTC.1 #Option MCP_RST = PORTC.2 // may be commented If RST is Not used // validate SCK pin... #If IsOption(MCP_CS) And Not IsValidPortPin(MCP_CS) #Error MCP_CS, "Invalid option. MCP_CS must be a valid port pin." #EndIf // validate RST pin... #If IsOption(MCP_RST) And Not IsValidPortPin(MCP_RST) #Error MCP_RST, "Invalid option. MCP_RST must be a valid port pin." #EndIf Public Dim CS As MCP_CS.MCP_CS@ // Chip select #if IsOption(MCP_RST) Public Dim RST As MCP_RST.MCP_RST@ // RST pin #endif //---------------------------------------------------------------------------- //Please ignore these options ... only here for the GLCD through SPI stuff... #option MCPPortA0 = 0 #option MCPPortA1 = 1 #option MCPPortA2 = 2 #option MCPPortA3 = 3 #option MCPPortA4 = 4 #option MCPPortA5 = 5 #option MCPPortA6 = 6 #option MCPPortA7 = 7 #option MCPPortB0 = 8 #option MCPPortB1 = 9 #option MCPPortB2 = 10 #option MCPPortB3 = 11 #option MCPPortB4 = 12 #option MCPPortB5 = 13 #option MCPPortB6 = 14 #option MCPPortB7 = 15 //---------------------------------------------------------------------------- Public Const IODIRA = $00, IODIRB = $01, IPOLA = $02, IPOLB = $03, GPINTENA = $04, GPINTENB = $05, DEFVALA = $06, DEFVALB = $07, INTCONA = $08, INTCONB = $09, IOCONA = $0A, IOCONB = $0B, GPPUA = $0C, GPPUB = $0D, INTFA = $0E, INTFB = $0F, INTCAPA = $10, INTCAPB = $11, GPIOA = $12, GPIOB = $13, OLATA = $14, OLATB = $15 //Control Byte For SPI operations [0100][A2][A1][A0][R=1/W=0] Private Const CONTROL_BYTE_MASK_R As Byte = %01000001 Private Const CONTROL_BYTE_MASK_W As Byte = %01000000 Private Const CONTROL_IOCON = %00011000 // Init value for IOCON - bits details in following comment { Reg IOCON = [BANK][MIRROR][SEQOP][DISSLW][HAEN][ODR][INTPOL][-Unused] bit 7 BANK: Controls how the registers are addressed 1 = The registers associated with each port are separated into different banks 0 = The registers are in the same bank (addresses are sequential) bit 6 MIRROR: INT Pins Mirror bit 1 = The INT pins are internally connected 0 = The INT pins are not connected. INTA is associated with PortA and INTB is associated with PortB bit 5 SEQOP: Sequential Operation mode bit. 1 = Sequential operation disabled, address pointer does not increment. 0 = Sequential operation enabled, address pointer increments. bit 4 DISSLW: Slew Rate control bit for SDA output. 1 = Slew rate disabled. 0 = Slew rate enabled. bit 3 HAEN: Hardware Address Enable bit (MCP23S17 only). Address pins are always enabled on MCP23017. 1 = Enables the MCP23S17 address pins. 0 = Disables the MCP23S17 address pins. bit 2 ODR: This bit configures the INT pin as an open-drain output. 1 = Open-drain output (overrides the INTPOL bit). 0 = Active driver output (INTPOL bit sets the polarity). bit 1 INTPOL: This bit sets the polarity of the INT output pin. 1 = Active-high. 0 = Active-low. bit 0 Unimplemented: Read as ‘0’. } { **************************************************************************** * Name : WriteReg * * Purpose : Writes pData byte to register address given by pRegAddress * * pChipAddress is the address of concerned MCP23S17 chip * **************************************************************************** } Public Sub WriteReg(ByVal pChipAddress, pRegAddress As Byte, pData As Byte) Dim CntByte As Byte CntByte = CONTROL_BYTE_MASK_W Or (pChipAddress << 1) CS = 0 //DelayUS(10) 'delays can/should be added if 'you have problems at High Clock values WriteByte(CntByte) WriteByte(pRegAddress) WriteByte(pData) CS = 1 //DelayUS(10) End Sub { **************************************************************************** * Name : ReadReg * * Purpose : Read register address given by pRegAddress * **************************************************************************** } Public Function ReadReg(ByVal pChipAddress As Byte, ByVal pRegAddress As Byte) As Byte Dim CntByte As Byte CntByte = CONTROL_BYTE_MASK_R Or (pChipAddress << 1) CS = 0 //DelayUS(10) 'delays can/should be added if 'you have problems at High Clock values WriteByte(CntByte) WriteByte(pRegAddress) Result = ReadByte() CS = 1 //DelayUS(10) End Function { **************************************************************************** * Name : ReadPortA * * Purpose : Returns PortA reg value * **************************************************************************** } Public Inline Function ReadPortA(ByVal pChipAddress As Byte) As Byte Result = ReadReg(pChipAddress, GPIOA) End Function { **************************************************************************** * Name : ReadPortB * * Purpose : Returns PortB reg value * **************************************************************************** } Public Inline Function ReadPortB(ByVal pChipAddress As Byte) As Byte Result = ReadReg(pChipAddress, GPIOB) End Function { **************************************************************************** * Name : ReadPinPortA * * Purpose : Returns Value of a specified PortA Pin * **************************************************************************** } Public Inline Function ReadPinPortA(ByVal pChipAddress As Byte, pPin As Byte) As Bit Dim tmp As Byte tmp = ReadReg(pChipAddress, GPIOA) Result = tmp.Bits(pPin) End Function { **************************************************************************** * Name : ReadPinPortB * * Purpose : Returns Value of a specified PortB Pin * **************************************************************************** } Public Inline Function ReadPinPortB(ByVal pChipAddress As Byte, pPin As Byte) As Bit Dim tmp As Byte tmp = ReadReg(pChipAddress, GPIOB) Result = tmp.Bits(pPin) End Function { **************************************************************************** * Name : WritePortA * * Purpose : Writes PortA Latch register with pData value * **************************************************************************** } Public Inline Sub WritePortA(ByVal pChipAddress, pData As Byte) WriteReg(pChipAddress, OLATA, pData) // I prefer to write to latch instead of direct port // This is safer in case if some pins were inputs End Sub { **************************************************************************** * Name : WritePortB * * Purpose : Writes PortB Latch register with pData value * **************************************************************************** } Public Inline Sub WritePortB(ByVal pChipAddress, pData As Byte) WriteReg(pChipAddress, OLATB, pData) // I prefer to write to latch instead of direct port // This is safer in case if some pins were inputs End Sub { **************************************************************************** * Name : SetPinPortA * * Purpose : Sets PortA pin given by pPin to High state * **************************************************************************** } Public Sub SetPinPortA(ByVal pChipAddress, pPin As Byte) Dim tmp As Byte tmp = ReadPortA(pChipAddress) Or (1<<pPin) WriteReg(pChipAddress, OLATA, tmp) // I prefer to write to latch instead of direct port // This is safer in case if some pins were inputs End Sub { **************************************************************************** * Name : SetPinPortB * * Purpose : Sets PortB pin given by pPin to High state * **************************************************************************** } Public Sub SetPinPortB(ByVal pChipAddress, pPin As Byte) Dim tmp As Byte tmp = ReadPortB(pChipAddress) Or (1<<pPin) WriteReg(pChipAddress, OLATB, tmp) // I prefer to write to latch instead of direct port // This is safer in case if some pins were inputs End Sub { **************************************************************************** * Name : SetPinPortAB * * Purpose : Sets PortA or PortB pin given by pPin to High state * * Pins are numbered from 0 to 15. * **************************************************************************** } Public Sub SetPinPortAB(ByVal pChipAddress, pPin As Byte) If pPin<8 Then SetPinPortA(pChipAddress, pPin) Else SetPinPortB(pChipAddress, pPin) EndIf End Sub { **************************************************************************** * Name : ClearPinPortA * * Purpose : Sets PortA pin given by pPin to Low state * **************************************************************************** } Public Sub ClearPinPortA(ByVal pChipAddress, pPin As Byte) Dim tmp As Byte tmp = ReadPortA(pChipAddress) And Not (1<<pPin) WriteReg(pChipAddress, OLATA, tmp) // I prefer to write to latch instead of direct port // This is safer in case if some pins were inputs End Sub { **************************************************************************** * Name : ClearPinPortB * * Purpose : Sets PortB pin given by pPin to Low state * **************************************************************************** } Public Sub ClearPinPortB(ByVal pChipAddress, pPin As Byte) Dim tmp As Byte tmp = ReadPortB(pChipAddress) And Not (1<<pPin) WriteReg(pChipAddress, OLATB, tmp) // I prefer to write to latch instead of direct port // This is safer in case if some pins were inputs End Sub { **************************************************************************** * Name : ClearPinPortAB * * Purpose : Clears PortA or PortB pin given by pPin to low state * * Pins are numbered from 0 to 15. * **************************************************************************** } Public Sub ClearPinPortAB(ByVal pChipAddress, pPin As Byte) If pPin<8 Then ClearPinPortA(pChipAddress, pPin) Else ClearPinPortB(pChipAddress, pPin) EndIf End Sub { **************************************************************************** * Name : SetTrisPortA * * Purpose : Sets PortA pins direction 0=Output 1=Input * **************************************************************************** } Public Inline Sub SetTrisPortA(ByVal pChipAddress, pDirection As Byte) WriteReg(pChipAddress, IODIRA, pDirection) End Sub { **************************************************************************** * Name : SetTrisPortB * * Purpose : Sets PortB pins direction 0=Output 1=Input * **************************************************************************** } Public Inline Sub SetTrisPortB(ByVal pChipAddress, pDirection As Byte) WriteReg(pChipAddress, IODIRB, pDirection) End Sub { **************************************************************************** * Name : GetTrisPortA * * Purpose : Gets PortA pins direction 0=Output 1=Input * **************************************************************************** } Public Inline Function GetTrisPortA(ByVal pChipAddress As Byte) As Byte result = ReadReg(pChipAddress, IODIRA) End Function { **************************************************************************** * Name : GetTrisPortB * * Purpose : Gets PortB pins direction 0=Output 1=Input * **************************************************************************** } Public Inline Function GetTrisPortB(ByVal pChipAddress As Byte) As Byte result = ReadReg(pChipAddress, IODIRB) End Function { **************************************************************************** * Name : SetPinTrisPortA * * Purpose : Sets the Tris of a Single Pin from PortA pins 0=Output 1=Input* **************************************************************************** } Public Sub SetPinTrisPortA(ByVal pChipAddress As Byte, pPin As Byte, pDirection As Bit) Dim PortTris As Byte PortTris = ReadReg(pChipAddress, IODIRA) PortTris.Bits(pPin) = pDirection WriteReg(pChipAddress, IODIRA, PortTris) End Sub { **************************************************************************** * Name : SetPinTrisPortB * * Purpose : Sets the Tris of a Single Pin from PortB pins 0=Output 1=Input* **************************************************************************** } Public Sub SetPinTrisPortB(ByVal pChipAddress As Byte, pPin As Byte, pDirection As Bit) Dim PortTris As Byte PortTris = ReadReg(pChipAddress, IODIRB) PortTris.Bits(pPin) = pDirection WriteReg(pChipAddress, IODIRB, PortTris) End Sub { **************************************************************************** * Name : SetPinTrisPortAB * * Purpose : Sets the Tris of a Single Pin from PortA or PortB * * 0=Output 1=Input Pins are numbered from 0 to 15 * **************************************************************************** } Public Sub SetPinTrisPortAB(ByVal pChipAddress As Byte, pPin As Byte, pDirection As Bit) If pPin<8 Then SetPinTrisPortA(pChipAddress, pPin, pDirection) Else SetPinTrisPortB(pChipAddress, pPin, pDirection) End If End Sub { **************************************************************************** * Name : SetPullUpsPortA * * Purpose : Sets the Pullups resistors for any Pin of PortA * * Pullups can be enabled/disabled independently for any pin of * * the port 0=Disabled 1=Enabled * **************************************************************************** } Public Inline Sub SetPullUpsPortA(ByVal pChipAddress, pPullUps As Byte) WriteReg(pChipAddress, GPPUA, pPullUps) End Sub { **************************************************************************** * Name : SetPullUpsPortB * * Purpose : Sets the Pullups resistors for any Pin of PortB * * Pullups can be enabled/disabled independently for any pin of * * the port 0=Disabled 1=Enabled * **************************************************************************** } Public Inline Sub SetPullUpsPortB(ByVal pChipAddress, pPullUps As Byte) WriteReg(pChipAddress, GPPUB, pPullUps) End Sub { **************************************************************************** * Name : InitChip * * Purpose : Initialize ALL the MCP23S17 CHIP MODULES on the bus * **************************************************************************** } Public Sub InitChip() #if IsOption(MCP_RST) // Reset the Chips RST = 0 DelayMS(10) RST = 1 DelayMS(10) #EndIf // At initialization, IOCON.HAEN is not set, so chip Addresses are not // active. All chips are considered with CHIP_ADDRESS set to "000" // regardless of the state of their Adress [A2 A1 A0] pins. // After initialization, once IOCON.HAEN enabled, MCPCHIP_ADDRESS is // taken into account by the chip. WriteReg(%000, IOCONA, CONTROL_IOCON) // writing to IOCONA writes also to IOCONB DelayMS(10) End Sub { **************************************************************************** * Name : Initialize * * Purpose : Initializes module and included modules * **************************************************************************** } Public Sub Initialize() #If MCPSPI_SOFTWARE SSPI.Initialize SSPI.SetClock(spiIdleHigh) #Else SPI.SetAsMaster(spiOscDiv4) SPI.SetClock(spiIdleHigh, spiRisingEdge) SPI.SetSample(spiSampleMiddle) EnableSPI() #EndIf Output(CS) #If IsOption(MCP_RST) Output(RST) #EndIf High(CS) High(RST) InitChip() End Sub Initialize()
Example Code
{ ***************************************************************************** * Name : test for Multichip MCP23S17 driver * * Author : Ahmed Lazreg ([email protected]) * * http://www.pocketmt.com * * Notice : Copyright (c) 2008 Pocket MicroTechnics * * : All Rights Reserved * * Date : 10/08/2008 * * Version : 1.0 * * Notes : * * : * ***************************************************************************** } Device = 18F452 Clock = 20 #option MCPSPI_SOFTWARE = false Include "mcp23s17_mult.bas" Dim i1, i2 As Word Const mcp1 = %000, // First chip address mcp2 = %111 // Second chip address MCP23S17_MULT.SetTrisPortA(mcp1,0) MCP23S17_MULT.SetTrisPortB(mcp1,$FF) MCP23S17_MULT.SetPullUpsPortB(mcp1,$FF) MCP23S17_MULT.SetTrisPortA(mcp2,0) MCP23S17_MULT.SetTrisPortB(mcp2,$FF) MCP23S17_MULT.SetPullUpsPortB(mcp2,$FF) While true i1 = MCP23S17_MULT.ReadPortB(mcp1) MCP23S17_MULT.WritePortA(mcp1,i1) i2 = MCP23S17_MULT.ReadPortB(mcp2) MCP23S17_MULT.WritePortA(mcp2,i2) Wend
Modified Hardware SPI module
{ **************************************************************************** * Name : SPI.BAS * * Author : John Barrat * * : David John Barker * * Notice : Copyright (c) 2006 Mecanique * * : All Rights Reserved * * Date : 26/05/2006 * * Version : 1.1 Changed WriteByte() param to WREG for silicon workaround * * : 1.0 Release * * : ============================================================= * * Notes : This post was made on the Swordfish forum by Steve B on * * : April 8th 2007... * * : I was having some trouble with the SPI module as well. I am * * : using an 18F2620, Silicon A4, and was getting some * * : inconsistent reads/writes, especially when doing a lot of * * : TX/RX one after another. After looking at the errata, I * * : changed the ReadByte and WriteByte routines as follows... * * : * * : Public Function ReadByte() As SSPBuffer * * : SSPIF = 0 * * : SSPBuffer = 0 * * : Repeat * * : ClrWDT * * : Until SSPIF = 1 * * : End Function * * : * * : Public Sub WriteByte(pData As WREG) * * : SSPIF = 0 * * : SSPBuffer = pData * * : Repeat * * : ClrWDT * * : Until SSPIF = 1 * * : End Sub * * : * * : Now they use the SSP Interrupt flag instead of the Buffer * * : Full flag (SSPSTAT.0). I also added a subroutine to enable * * : the SPI module * * : * * : Public Sub EnableSPI() * * : Dim pDummy As Byte * * : Enabled = true * * : pDummy = SSPBuffer * * : SSPIF = 0 * * : End Sub * **************************************************************************** } Module SPI Include "system.bas" // map registers to SSP(x) #if _mssp = 0 #error _device + " does not support MSSP" // single SSP... #elseif _mssp = 1 Dim SSPControl1 As SSPCON1, SSPStatus As SSPSTAT, SSPBuffer As SSPBUF, SCK As PORTC.3, SDI As PORTC.4, SDO As PORTC.5, _SS As PORTA.5 // has more than one SSP module... #else Dim // -> MSSP2 SSPControl1 As SSP1CON1, // as SSP2CON1 SSPStatus As SSP1STAT, // as SSP2STAT SSPBuffer As SSP1BUF, // as SSP2BUF SCK As PORTC.3, SDI As PORTC.4, SDO As PORTC.5, _SS As PORTA.5 #endif // SSPSTAT bitnames Public Dim BF As SSPStatus.0, // buffer full (receive and transmit) SMP As SSPStatus.7, // read sample mode CKE As SSPStatus.6 // clock edge control // SSPCON1 bitnames, master mode only... Public Dim WCOL As SSPControl1.7, // write collision Detect SSPOV As SSPControl1.6, // receive overflow SSPEN As SSPControl1.5, // synchronous receive enable CKP As SSPControl1.4, // clock polarity // synchronous mode select bits, %00XX for master mode SSPM3 As SSPControl1.3, // always zero SSPM2 As SSPControl1.2, // slave Mode SSPM1 As SSPControl1.1, // clock Mode (MSB) SSPM0 As SSPControl1.0 // clock Mode (LSB) // interrupt flag Public Dim SSPIF As PIR1.3 Public Const spiOscDiv4 = 0, // master mode FOSC/4 spiOscDiv16 = 1, // master mode FOSC/16 spiOscDiv64 = 2, // master mode FOSC/64 spiOscTimer2 = 3, // master mode TMR2 provides clock spiSlaveSSEnabled = 4, // slave mode slave synch enabled spiSlaveSSDisabled = 5, // slave mode slave synch disabled // clock idle and edge settings (SPI Mode compatible) spiRisingEdge = $40, // data transmitted on rising edge of clock spiFallingEdge = $00, // data transmitted on falling edge of clock spiIdleHigh = $10, // idle state for clock is high spiIdleLow = $00, // idle state for clock is low // RX data sampling settings (SSPStatus.6 - SMP) spiSampleEnd = $80, // input data sampled at end of data output time spiSampleMiddle = $00 // input data sampled at middle of data output time // local helper aliases... Dim FBufferIsFull As SSPStatus.Booleans(0) // BF as boolean // public aliases... Public Dim Enabled As SSPControl1.Booleans(5), // SSPEN as boolean Overflow As SSPControl1.Booleans(6), // SSPOV as boolean WriteCollision As SSPControl1.Booleans(7) // WCOL as boolean { **************************************************************************** * Name : SetAsMaster * * Purpose : Set SPI to master mode * * : spiOscDiv4 (DEFAULT), spiOscDiv16, spiOscDiv64, spiOscTmr2 * **************************************************************************** } Public Sub SetAsMaster(pMode As Byte = spiOscDiv4) SSPControl1 = SSPControl1 And $F0 SSPControl1 = SSPControl1 Or pMode Output(SDO) Output(SCK) End Sub { **************************************************************************** * Name : SetAsSlave * * Purpose : Set SPI to slave mode * * : spiSlaveSSEnabled, spiSlaveSSDisabled (DEFAULT) * **************************************************************************** } Public Sub SetAsSlave(pMode As Byte = spiSlaveSSDisabled) SSPControl1 = SSPControl1 And $F0 SSPControl1 = SSPControl1 Or pMode Output(SDO) Input(SCK) Input(_SS) End Sub { **************************************************************************** * Name : SetSample * * Purpose : Sets when the SPI input data in sampled * * : spiSampleEnd, spiSampleMiddle. For slave mode, sample should * * : always be spiSampleMiddle * **************************************************************************** } Public Sub SetSample(pSample As Byte) SSPStatus = SSPStatus And $7F SSPStatus = SSPStatus Or pSample End Sub { **************************************************************************** * Name : SetClock * * Purpose : Set SPI idle state and data transmission clock edge * * : pIdle -> spiIdleHigh, spiIdleLow * * : pEdge -> spiRisingEdge, spiFallingEdge * **************************************************************************** } Public Sub SetClock(pIdle As Byte, pEdge As Byte) SSPControl1 = SSPControl1 And $EF SSPControl1 = SSPControl1 Or pIdle If pIdle = spiIdleHigh Then pEdge = Not pEdge And $BF EndIf SSPStatus = SSPStatus And $BF SSPStatus = SSPStatus Or pEdge End Sub { **************************************************************************** * Name : ReadByte * * Purpose : Read a single byte from the SPI bus * **************************************************************************** } 'Public Function ReadByte() As SSPBuffer ' SSPBuffer = 0 ' Repeat ' ClrWDT ' Until FBufferIsFull 'End Function Public Function ReadByte() As SSPBuffer SSPIF = 0 SSPBuffer = $FF Repeat ClrWDT Until SSPIF = 1 ReadByte = SSPBuffer End Function Public Sub WriteByte(pData As Byte) SSPIF = 0 SSPBuffer = pData Repeat ClrWDT Until SSPIF = 1 End Sub { **************************************************************************** * Name : WriteByte * * Purpose : Write a single byte to the SPI bus * **************************************************************************** } 'Public Sub WriteByte(pData As WREG) ' SSPBuffer = pData ' Repeat ' ClrWDT ' Until FBufferIsFull 'End Sub Public Sub EnableSPI() Dim pDummy As Byte Enabled = true pDummy = SSPBuffer SSPIF = 0 End Sub