I have gotten help from Nevyn and ShVerni. They have been very helpful.
- Netduino Forums
- → Viewing Profile: Likes: Greg Chao
Community Stats
- Group Members
- Active Posts 14
- Profile Views 24331
- Member Title Member
- Age Age Unknown
- Birthday Birthday Unknown
-
Gender
Not Telling
#64480 General support from Secret labs
Posted by Greg Chao on 10 November 2015 - 10:03 PM
#64446 More Digital I/O
Posted by Greg Chao on 04 November 2015 - 12:48 AM
Success! I have 16 additional digital I/O per MCP23S17 chip. Since there is a three bit address, I can multiply that by 8. Very cool...
Thank you ShVerni and Mark....
I used the Telerik converter to get the code to VB.net. Worked great. I ran into two issues. First, for some reason, the version of .NET that I have does not support shift operation so I had to write my own subroutine. Second, ShVerni's original code had some addressing issues with MCP23S17 since he was using a different chip for his project. I had to modify the addressing to track the 16 bits. Other than that, it works great.
Here is the code for anyone interested. You can copy it into a class module and call it from your main module as recommended by ShVerni.
Imports System Imports Microsoft.SPOT Imports Microsoft.SPOT.Hardware ''' <summary> ''' Driver for a MCP23S17 I/O expander (http://www.microchip.com/wwwproducts/Devices.aspx?product=MCP23S17) ''' </summary> Public Class MCP23S17 ' Enumeration of the GPIOs: 0-7 (GPA0-GPA7), 8-15 (GPB0-GPB7) Public Enum ports As Byte GP0 = &H0 ' GPA0 GP1 = &H1 GP2 = &H2 GP3 = &H3 GP4 = &H4 GP5 = &H5 GP6 = &H6 GP7 = &H7 ' GPA7 GP8 = &H8 ' GPB0 GP9 = &H9 GP10 = &HA GP11 = &HB GP12 = &HC GP13 = &HD GP14 = &HE GP15 = &HF ' GPB7 End Enum ' <summary> ' Device configuration settings ' </summary> Public Enum settings As UShort ' gc, to get logic right ' gc, if bit needs to be a 1, then all other bits are 0 ' gc, if bit needs to be a 0, the all other are 1 ' Set to two 8-bit banks BANK_SEP = &H8080 ' Set to one 16-bit bank (this driver is configured for 16-bit use) BANK_SAME = &H7F7F ' Enable mirroring of interrupt ports MIRROR_ENABLE = &H4040 ' Disable mirroring of interrupt ports MIRROR_DISABLE = &HBFBF ' Enable sequential operation mode (not recommended) SEQOP_ENABLE = &HDFDF ' Disable sequential operation mode SEQOP_DISABLE = &H2020 ' Enable slew control for SDA pins DISSLW_ENABLE = &HEFEF ' Disable slew control for SDA pins DISSLW_DISABLE = &H1010 ' Enable hardware address pins (allows multiple MCP23S17 on one chip select pin) HAEN_ENABLE = &H808 ' Disable hardware address pins HAEN_DISABLE = &HF7F7 ''' Set the interrupt pin to be open-drain ODR_OPEN_DRAIN = &H404 ' Set the interrupt pin to active driver ODR_ACTICE_DRIVER = &HFBFB ' Set interrupt pin to active high when in active driver mode INTPOL_ACTIVE_HIGH = &H202 ' Set interrupt pin to active low when in active driver mode INTPOL_ACTIVE_LOW = &HFDFD End Enum ' Port configuration settings Public Enum portConfig As Byte ' gc, assigned additional valus for B side ' set port as output OUTPUT = &H0 ' Set port as input INPUT = &H1 ' Set the polarity of the input to the same as the port state (i.e. a high port state is reported as 1, and vice versa) INPUT_POLARITY_SAME = &H2 ' Set the polarity of the input to the opposite as the port state (i.e. a high port state is reported as 0, and vice versa) INPUT_POLARITY_OPP = &H3 ' Enable interrupt on port ENABLE_INTERRUPT = &H4 ' Disable interrupt on port DISABLE_INTERRUPT = &H5 ' Set the port interrupt state to low INTTERUPT_LOW = &H6 ' Set the port interrupt state to high INTTERUPT_HIGH = &H7 ' Set the port interrupt state to both (i.e. change) INTERRUPT_BOTH = &H8 ' Enable port pull-up resistor (input only) PULL_UP_ENABLE = &H9 ' Disable port pull-up resistor (input only) PULL_UP_DISABLE = &HA End Enum ' Register map, see the data sheet for details Private Enum register As Byte IODIRA = &H0 IPOLA = &H2 GPINTENA = &H4 DEFVALA = &H6 INTCONA = &H8 IOCONA = &HA GPPUA = &HC INTFA = &HE INTCAPA = &H10 GPIOA = &H12 OLATA = &H14 'gc, added for addressing B side IODIRB = &H1 IPOLB = &H3 GPINTENB = &H5 DEFVALB = &H7 INTCONB = &H9 IOCONB = &HB GPPUB = &HD INTFB = &HF INTCAPB = &H11 GPIOB = &H13 OLATB = &H15 End Enum ' Store some settings Private _MCP23S17 As SPI ' MultiSPI can be swapped with regular SPI if using one device Private _opcodeW As Byte, _opcodeR As Byte ''' <summary> ''' Constructs a MCP23S17 object ''' </summary> ''' <param name="cs">The chip select pin used</param> ''' <param name="address">The address defined by pins A0, A1, and A2</param> Public Sub New(cs As Cpu.Pin, address As Byte) ' Can be replaced with regular SPI if using one device _MCP23S17 = New SPI(New SPI.Configuration(ChipSelect_Port:=cs, ChipSelect_ActiveState:=False, ChipSelect_SetupTime:=0, ChipSelect_HoldTime:=0, Clock_IdleState:=False, Clock_Edge:=True, Clock_RateKHz:=4800, SPI_mod:=SPI.SPI_module.SPI1)) ' The opcode is a combination 4 bit fix 0100 code plus a 3 bit address and 1 bit = 0 (write) or 1 (read) '_opcodeW = CByte((&H4 << 4) Or (address << 1)) 'gc, hardcoded since this will never change. _opcodeW = &H40 ' address set by hardware '_opcodeR = CByte(_opcodeW + 1) _opcodeR = &H41 ' ' 0 - gc, set up the 16 bits configure(MCP23S17.settings.BANK_SAME, 0) ' 1 -gc, mirror enables - int bit tied together - I am not using interrupts so it doesn't matter configure(MCP23S17.settings.MIRROR_ENABLE, 0) ' 1 -Disable sequential operation mode as this driver is not configured to use it configure(MCP23S17.settings.SEQOP_DISABLE, 0) ' 1 -gc, disable the slew rate configure(MCP23S17.settings.DISSLW_DISABLE, 0) ' 1 -Set up the expander to use its address by default configure(MCP23S17.settings.HAEN_ENABLE, 0) ' 0 gc, interrupt active driver, don't care configure(MCP23S17.settings.ODR_ACTICE_DRIVER, 0) ' 1 -gc, interrupt polarity as active high configure(MCP23S17.settings.INTPOL_ACTIVE_HIGH, 0) ' 0 - gc, set up the 16 bits configure(MCP23S17.settings.BANK_SAME, 1) ' 1 -gc, mirror enables - int bit tied together - I am not using interrupts so it doesn't matter configure(MCP23S17.settings.MIRROR_ENABLE, 1) ' 1 -Disable sequential operation mode as this driver is not configured to use it configure(MCP23S17.settings.SEQOP_DISABLE, 1) ' 1 -gc, disable the slew rate configure(MCP23S17.settings.DISSLW_DISABLE, 1) ' 1 -Set up the expander to use its address by default configure(MCP23S17.settings.HAEN_ENABLE, 1) ' 0 gc, interrupt active driver, don't care configure(MCP23S17.settings.ODR_ACTICE_DRIVER, 1) ' 1 -gc, interrupt polarity as active high configure(MCP23S17.settings.INTPOL_ACTIVE_HIGH, 1) End Sub ' Public Function bsu(ByVal value As UShort, ByVal shiftvalue As UShort) As UShort For i As Integer = 0 To CInt(shiftvalue) - 1 value = CUShort(value) * CUShort(2) Next i Return value End Function ' Configure settings for the MCP23S17 ' <param name="set">The setting to apply</param> Public Sub configure(sett As settings, AorB As Integer) Dim config As UShort If AorB = 0 Then config = read(register.IOCONA) Else config = read(register.IOCONB) End If Select Case CUShort(sett) Case &H8080 If CUShort(sett) = &H8080 Then config = config Or CUShort(sett) Else config = config And CUShort(sett) End If Case Else If CUShort(sett) < &H8080 Then ' gc, clever trick to distinguish setting a 1 or 0 ' gc, works for all cases except 8080 config = config Or CUShort(sett) Else config = config And CUShort(sett) End If End Select If AorB = 0 Then write(register.IOCONA, config) Else write(register.IOCONB, config) End If End Sub ' Configure the settings of a port ' <param name="port">The port to configure</param> ' gc, port is a single bit that you want to change ' gc, this technique will change only one bit amoung 8 bits ' <param name="config">The setting to apply</param> Public Sub setupPort(port As ports, config As portConfig) Dim buffer As UShort Dim offsetport As UShort Select Case config Case portConfig.OUTPUT If port < &H8 Then buffer = read(register.IODIRA) offsetport = &H0 'buffer = buffer And CUShort(Not (1 << CByte(port))) buffer = buffer And (Not bsu(1, CUShort(port) - offsetport + CUShort(&H8))) write(register.IODIRA, buffer) Else buffer = read(register.IODIRB) offsetport = &H8 'buffer = buffer And CUShort(Not (1 << CByte(port))) buffer = buffer And (Not bsu(1, CUShort(port) - offsetport + CUShort(&H8))) write(register.IODIRB, buffer) End If Case portConfig.INPUT If port < &H8 Then buffer = read(register.IODIRA) offsetport = &H0 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.IODIRA, buffer) Else buffer = read(register.IODIRB) offsetport = &H8 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CByte(port) - offsetport + CUShort(&H8)) write(register.IODIRB, buffer) End If Case portConfig.PULL_UP_DISABLE If port < &H8 Then buffer = read(register.GPPUA) offsetport = &H0 'buffer = buffer And CUShort(Not (1 << CByte(port))) buffer = buffer And (Not bsu(1, CUShort(port) - offsetport + CUShort(&H8))) write(register.GPPUA, buffer) Else buffer = read(register.GPPUB) offsetport = &H8 'buffer = buffer And CUShort(Not (1 << CByte(port))) buffer = buffer And (Not bsu(1, CUShort(port) - offsetport + CUShort(&H8))) write(register.GPPUB, buffer) End If Case portConfig.PULL_UP_ENABLE If port < &H8 Then buffer = read(register.GPPUA) offsetport = &H0 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.GPPUA, buffer) Else buffer = read(register.GPPUB) offsetport = &H8 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.GPPUB, buffer) End If Case portConfig.INPUT_POLARITY_SAME If port < &H8 Then buffer = read(register.IPOLA) offsetport = &H0 'buffer = buffer And CUShort(Not (1 << CByte(port))) buffer = buffer And (Not bsu(1, CUShort(port) - offsetport + CUShort(&H8))) write(register.IPOLA, buffer) Else buffer = read(register.IPOLB) offsetport = &H8 'buffer = buffer And CUShort(Not (1 << CByte(port))) buffer = buffer And (Not bsu(1, CUShort(port) - offsetport + CUShort(&H8))) write(register.IPOLB, buffer) End If Case portConfig.INPUT_POLARITY_OPP If port < &H8 Then buffer = read(register.IPOLA) offsetport = &H0 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.IPOLA, buffer) Else buffer = read(register.IPOLB) offsetport = &H8 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.IPOLB, buffer) End If Case portConfig.DISABLE_INTERRUPT If port < &H8 Then buffer = read(register.GPINTENA) offsetport = &H0 'buffer = buffer And CUShort(Not (1 << CByte(port))) buffer = buffer And (Not bsu(1, CUShort(port) - offsetport + CUShort(&H8))) write(register.GPINTENA, buffer) Else buffer = read(register.GPINTENB) offsetport = &H8 'buffer = buffer And CUShort(Not (1 << CByte(port))) buffer = buffer And (Not bsu(1, CUShort(port) - offsetport + CUShort(&H8))) write(register.GPINTENB, buffer) End If Case portConfig.ENABLE_INTERRUPT If port < &H8 Then buffer = read(register.GPINTENA) offsetport = &H0 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.GPINTENA, buffer) Else buffer = read(register.GPINTENB) offsetport = &H8 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.GPINTENB, buffer) End If Case portConfig.INTTERUPT_LOW If port < &H8 Then buffer = read(register.DEFVALA) offsetport = &H0 'buffer = buffer And CUShort(Not (1 << CByte(port))) buffer = buffer And (Not bsu(1, CUShort(port) - offsetport + CUShort(&H8))) write(register.DEFVALA, buffer) buffer = read(register.INTCONA) 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.INTCONA, buffer) Else buffer = read(register.DEFVALB) offsetport = &H8 'buffer = buffer And CUShort(Not (1 << CByte(port))) buffer = buffer And (Not bsu(1, CUShort(port) - offsetport + CUShort(&H8))) write(register.DEFVALB, buffer) buffer = read(register.INTCONB) 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.INTCONB, buffer) End If Case portConfig.INTTERUPT_HIGH If port < &H8 Then buffer = read(register.DEFVALA) offsetport = &H0 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.DEFVALA, buffer) buffer = read(register.INTCONA) 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.INTCONA, buffer) Else buffer = read(register.DEFVALB) offsetport = &H8 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.DEFVALB, buffer) buffer = read(register.INTCONB) 'buffer = buffer Or CUShort(1 << CByte(port)) buffer = buffer Or bsu(1, CUShort(port) - offsetport + CUShort(&H8)) write(register.INTCONB, buffer) End If Case portConfig.INTERRUPT_BOTH If port < &H8 Then buffer = read(register.INTCONA) offsetport = &H0 'buffer = buffer And CUShort(Not (1 << CByte(port))) buffer = buffer And (Not bsu(1, CUShort(port) - offsetport + CUShort(&H8))) write(register.INTCONA, buffer) Else buffer = read(register.INTCONB) offsetport = &H0 'buffer = buffer And CUShort(Not (1 << CByte(port))) buffer = buffer And (Not bsu(1, CUShort(port) - offsetport + CUShort(&H8))) write(register.INTCONB, buffer) End If End Select End Sub ' Set the states of the port latches, which correspond to the states of the ouput ports ' <param name="portSettings">The latch settings to apply to the ports</param> Public Sub writePorts(portSettings As UShort, AorB As Integer) If AorB = 0 Then write(register.OLATA, portsettings) Else write(register.OLATB, portsettings) End If End Sub ' Read the states of all the GPIO ports or the port latches ' <param name="latch">If true, returns the latch states for the ports instead of the port states</param> ' <returns>A ushort where each bit repersents the state of the corresponding port</returns> Public Function readPorts(Optional latch As Boolean = False, Optional AorB As Integer = 0) As UShort If AorB = 0 Then If latch Then Return read(register.OLATA) Else Return read(register.GPIOA) End If Else If latch Then Return read(register.OLATB) Else Return read(register.GPIOB) End If End If End Function '' Retrieves the parameters related to an interrupt event '' <returns>A ushort array containing two ushorts corresponding to the INTF and INTCAP registers respectively. See the data sheet for details</returns> 'Public Function checkInterrupt() As UShort() ' Dim buffer As UShort() = New UShort(1) {} ' buffer(0) = read(register.INTFA) ' buffer(1) = read(register.INTCAPA) ' Return buffer 'End Function ' Reads the data from a register ' <param name="reg">The register to read</param> ' <returns>The data in the register</returns> Private Function read(reg As register) As UShort 'Dim bufferW As UShort() = {CUShort((_opcodeR << 8) Or CByte(reg))} Dim bufferW As UShort() = {CUShort(&H4100 Or CByte(reg))} Dim bufferR As UShort() = New UShort(0) {} ' Need to wait for the opcode and register address to be sent before beginning to read data _MCP23S17.WriteRead(bufferW, bufferR, 1) Return bufferR(0) End Function ' Writes data to a register ' <param name="reg">The register to write</param> ' <param name="data">The data to write</param> Private Sub write(reg As register, data As UShort) 'If reg = register.INTCAP OrElse reg = register.INTF Then ' Throw New System.ArgumentException("INTCAP and INTF are read-only", "reg") 'End If 'Dim opcode As UShort = CUShort((_opcodeW << 8) Or CByte(reg)) Dim opcode As UShort = CUShort(&H4000 Or CByte(reg)) Dim buffer As UShort() = {opcode, data} _MCP23S17.Write(buffer) End Sub ' End Class ' Class for using the MCP23S17 ports like native input ports Public Class ExpandedInputPort ' Store some settings Private _port As UShort Private _expander17 As MCP23S17 = Nothing Private portAB As Byte ' Creates an input port on a MCP23S17 expander ' </summary> ' The port to use</param> '<param name="resistor">Enable the 100kOhm pull-up resistor</param> ' <param name="expander">The MCP23S17 with the port</param> Public Sub New(port__1 As MCP23S17.ports, resistor As Port.ResistorMode, expander As MCP23S17) '_port = CUShort(1 << CByte(port__1)) Dim portoffset As UShort If port__1 < &H8 Then portoffset = &H0 Else portoffset = &H8 End If _port = bsu(1, CUShort(port__1) - portoffset + CUShort(&H8)) _expander17 = expander _expander17.setupPort(port__1, MCP23S17.portConfig.INPUT) portAB = port__1 Select Case resistor Case Port.ResistorMode.Disabled _expander17.setupPort(port__1, MCP23S17.portConfig.PULL_UP_DISABLE) Exit Select Case Port.ResistorMode.PullUp _expander17.setupPort(port__1, MCP23S17.portConfig.PULL_UP_ENABLE) Exit Select Case Else Throw New System.NotSupportedException("Pull-down resistors not supported") End Select End Sub ' Public Function bsu(ByVal value As UShort, ByVal shiftvalue As UShort) As UShort For i As Integer = 0 To CInt(shiftvalue) - 1 value = CUShort(value) * CUShort(2) Next i Return value End Function ' Read the state of the port ' </summary> ' <returns>The state of the port</returns> Public Function Read() As Boolean Dim buffer As UShort If portAB < &H8 Then buffer = _expander17.readPorts(False, 0) 'very important to use false to set reading GPIO rather than Latches Else buffer = _expander17.readPorts(False, 1) End If Return _port = (buffer And _port) End Function ' Public Sub tester1() _expander17.setupPort(MCP23S17.ports.GP7, MCP23S17.portConfig.INPUT) End Sub ' Disposes of the port Public Sub Dispose() _expander17 = Nothing End Sub Protected Overrides Sub Finalize() Try Dispose() Finally MyBase.Finalize() End Try End Sub End Class ' Class for using the MCP23S08/MCP23S17 ports like native output ports Public Class ExpandedOuputPort ' Store some settings Private _port As UShort Private _expander17 As MCP23S17 = Nothing Private portAB As Byte ' Creates an output port on a MCP23S17 expander ' <param name="port">The port to use</param> ' <param name="initialState">The initial state of the port</param> ' <param name="expander">The MCP23S08 with the port</param> Public Sub New(port As MCP23S17.ports, initialState As Boolean, expander As MCP23S17) _expander17 = expander _expander17.setupPort(port, MCP23S17.portConfig.OUTPUT) portAB = port Dim buffer As UShort Dim portoffset As UShort If port < &H8 Then portoffset = &H0 '_port = CUShort(1 << CByte(port)) _port = bsu(1, CUShort(port) - portoffset + CUShort(&H8)) buffer = _expander17.readPorts(True, 0) If initialState Then buffer = buffer Or _port Else buffer = buffer And CUShort(Not _port) End If _expander17.writePorts(buffer, 0) Else portoffset = &H8 '_port = CUShort(1 << CByte(port)) _port = bsu(1, CUShort(port) - portoffset + CUShort(&H8)) buffer = _expander17.readPorts(True, 1) If initialState Then buffer = buffer Or _port Else buffer = buffer And CUShort(Not _port) End If _expander17.writePorts(buffer, 1) End If End Sub ' Public Function bsu(ByVal value As UShort, ByVal shiftvalue As UShort) As UShort For i As Integer = 0 To CInt(shiftvalue) - 1 value = CUShort(value) * CUShort(2) Next i Return value End Function ' Read the state of the port ' <returns>The state of the port</returns> Public Function Read() As Boolean Dim buffer As UShort If portAB < &H8 Then buffer = _expander17.readPorts(True, 0) Else buffer = _expander17.readPorts(True, 1) End If Return _port = (buffer And _port) End Function ' Write the state of the port ' <param name="state">The state to write</param> Public Sub Write(state As Boolean) Dim buffer As UShort If portAB <= &H8 Then buffer = _expander17.readPorts(True, 0) If state Then buffer = buffer Or _port Else buffer = buffer And CUShort(Not _port) End If 'buffer = _port _expander17.writePorts(buffer, 0) Else buffer = _expander17.readPorts(True, 1) If state Then buffer = buffer Or _port Else buffer = buffer And CUShort(Not _port) End If 'buffer = _port _expander17.writePorts(buffer, 1) End If End Sub ' Disposes of the port Public Sub Dispose() _expander17 = Nothing End Sub Protected Overrides Sub Finalize() Try Dispose() Finally MyBase.Finalize() End Try End Sub ' Public Sub tester() '_expander17.configure(MCP23S17.settings.BANK_SAME, 0) '1 -gc, mirror enables - int bit tied together - I am not using interrupts so it doesn't matter '_expander17.configure(MCP23S17.settings.MIRROR_ENABLE, 1) ' 1 -Disable sequential operation mode as this driver is not configured to use it '_expander17.configure(MCP23S17.settings.SEQOP_DISABLE, 0) ' 1 -gc, disable the slew rate '_expander17.configure(MCP23S17.settings.DISSLW_DISABLE, 0) ' 1 -Set up the expander to use its address by default '_expander17.configure(MCP23S17.settings.HAEN_ENABLE, 0) ' 0 gc, interrupt active driver, don't care '_expander17.configure(MCP23S17.settings.ODR_ACTICE_DRIVER, 0) ' 1 -gc, interrupt polarity as active high '_expander17.configure(MCP23S17.settings.INTPOL_ACTIVE_HIGH, 0) End Sub End Class '======================================================= 'Service provided by Telerik (www.telerik.com) 'Conversion powered by NRefactory. 'Twitter: @telerik 'Facebook: facebook.com/telerik '=======================================================
?
- ShVerni likes this
- Netduino Forums
- → Viewing Profile: Likes: Greg Chao
- Privacy Policy