• PicŪ Basic


  • Proton MCP23017 port expander library

    Here's a simpler Library that I've just knocked up for the MCP23017, and it has been tested. It can use either the software I2C commands (Busin\Busout) or the peripheral I2C commands (HBusin\HBusout), depending if "$define MCP_Software_I2C" is commented out. The software I2C commands work just as well as the I2C peripheral commands. The compiler's internal library routines remain the same for all 18F devices, so if it works on one device, it will work on another, unless the device is multiplexing with the pins, as some of the strange USB types do for some pins. Check the datasheet thoroughly and read the errata for the device, just to make sure.

    Here's the simple demo program:
    Code:
    '
    ' MCP23017 interface library demo
    ' Written by Les Johnson for the Proton compiler
    '
        Device = 18F25K20
        Declare Xtal = 64
        
        Declare Hserial_Baud = 9600         ' Set the baud rate
    
    $define MCP_Software_I2C                ' Comment to use the hardware I2C peripherals
        
        Include "MCP23017.inc"              ' Load the MCP23017 routines into the program
    '
    ' Create variables for the demo
    '
        Dim PinIndex As Byte                ' Holds the pin number
        Dim MyByteIn As Byte                ' Holds the byte read from the MCP23017
                 
    '-------------------------------------------------------------------------------    
    ' The main program starts here
    '
    Main:
        
        MCP_WriteTRISA($00)                 ' Make PORTA (GPIOA) all outputs
        MCP_WritePORTA($00)                 ' Pull all pins of PORTA low (GPIOA) 
        
        MCP_WriteTRISB($FF)                 ' Make PORTB (GPIOB) all inputs
        MCP_PullUpsPORTB($FF)               ' Enable the pullup resistors on all of PORTB (GPIOB) 
        
        Do                                  ' Create a loop
            '
            ' Read all of PORTB (GPIOB)
            '
            MyByteIn = MCP_ReadPORTB()      ' Read PORTB (GPIOB)
            HRSOutLn Bin8 MyByteIn          ' Display the port pins on the serial terminal
            '
            ' Toggle all pins of PORTA (GPIOA) high to low
            '
            MCP_WritePORTA($FF)             ' Set all pins of PORTA (GPIOA) high
            DelayMS 300
            MCP_WritePORTA($00)             ' Pull all pins of PORTA (GPIOA) low
            DelayMS 300
            '
            ' Toggle individual pins of PORTA (GPIOA) High to Low
            '
            For PinIndex = 0 To  7                    ' Create a loop for the pins
                MCP_WritePinOfPORTA(PinIndex,  1 )    ' Set a pin high
                DelayMS  300 
                MCP_WritePinOfPORTA(PinIndex,  0 )    ' Pull a pin low
                DelayMS  300 
            Next
        Loop
        
    '------------------------------------------------------------------------------- 
    ' Set for external oscillator and PLL enabled
    '
    Config_Start
        FOSC = HSPLL        ' HS oscillator, PLL enabled and under software control
        Debug = Off         ' Background debugger disabled' RB6 and RB7 configured as general purpose I/O pins
        XINST = Off         ' Instruction set extension and Indexed Addressing mode disabled
        STVREN = Off        ' Reset on stack overflow/underflow disabled
        WDTEN = Off         ' WDT disabled (control is placed on SWDTEN bit)
        FCMEN = Off         ' Fail-Safe Clock Monitor disabled    
        IESO = Off          ' Two-Speed Start-up disabled
        WDTPS = 128         ' Watchdog is 1:128
        BOREN = Off         ' Brown-out Reset disabled in hardware and software
        BORV = 18           ' VBOR set to 1.8 V nominal
        MCLRE = On          ' MCLR pin enabled, RE3 input pin disabled
        HFOFST = Off        ' The system clock is held Off until the HF-INTOSC is stable
        LPT1OSC = Off       ' Timer1 operates in standard power mode
        PBADEN = Off        ' PORTB<4:0> pins are configured as digital I/O on Reset
        CCP2MX = PORTC      ' CCP2 input/output is multiplexed with RC1
        LVP = Off           ' Single-Supply ICSP disabled
        Cp0 = Off           ' Block 0 (000800-001FFFh) not code-protected
        CP1 = Off           ' Block 1 (002000-003FFFh) not code-protected
        CPB = Off           ' Boot block (000000-0007FFh) not code-protected
        CPD = Off           ' Data eeprom not code-protected
        WRT0 = Off          ' Block 0 (000800-001FFFh) not write-protected
        WRT1 = Off          ' Block 1 (002000-003FFFh) not write-protected
        WRTB = Off          ' Boot block (000000-0007FFh) not write-protected
        WRTC = Off          ' Configuration registers (300000-3000FFh) not write-protected
        WRTD = Off          ' Data eeprom not write-protected
        EBTR0 = Off         ' Block 0 (000800-001FFFh) not protected from table reads executed in other blocks
        EBTR1 = Off         ' Block 1 (002000-003FFFh) not protected from table reads executed in other blocks
        EBTRB = Off         ' Boot block (000000-0007FFh) not protected from table reads executed in other blocks
    Config_End
    And here's the code for the library. Name it "MCP23017.inc":
    Code:
    $ifndef _MCP23017_INC_
    $define _MCP23017_INC_
    '
    ' Routines to Interface with a Microchip MCP23017 16-bit I2C I/O peripheral
    ' Written by Les Johnson for the Proton BASIC compiler
    '
    ' Define the pins used for Busin and Busout
    '
        Declare SDA_Pin = PORTC.4
        Declare SCL_Pin = PORTC.3
    
    '$define MCP_Software_I2C   ' Comment to use the hardware I2C peripheral
    
    $define cMCP_Write $40      ' I2C Address of MCP23017 Chip for writing to it
    $define cMCP_Read  $41      ' I2C Address of MCP23017 Chip for reading from it
    '
    ' MCP23017 registers
    '
    $define cIODIRA   $00
    $define cIODIRB   $01
    $define cIPOLA    $02
    $define cIPOLB    $03
    $define cGPINTENA $04
    $define cGPINTENB $05
    $define cDEFVALA  $06
    $define cDEFVALB  $07
    $define cINTCONA  $08
    $define cINTCONB  $09
    $define cIOCONA   $0A
    $define cIOCONB   $0B
    $define cGPPUA    $0C
    $define cGPPUB    $0D
    $define cINTFA    $0E
    $define cINTFB    $0F
    $define cINTCAPA  $10
    $define cINTCAPB  $11
    $define cGPIOA    $12
    $define cGPIOB    $13
    $define cOLATA    $14
    $define cOLATB    $15
    '
    ' Variables used to cache the state of Direction and Port registers
    '
        Dim MCP_bShadowDirRegA As Byte
        Dim MCP_bShadowDirRegB As Byte
        Dim MCP_bShadowDataRegA As Byte
        Dim MCP_bShadowDataRegB As Byte
    '
    ' Variables used for passing parameters to the macros (better left as System types so they are in bankless access RAM)
    '
        Dim MCP_bRegAddress As Byte System
        Dim MCP_bDataByte As Byte System
        Dim MCP_bTemp As MCP_bDataByte
    
    '----------------------------------------------------------------------------
        GoTo MCP23017_Init                               ' Jump over the subroutines
    
    '------------------------------------------------------------------------------------------
    ' Write to one of the MCP23017 registers
    ' Input   : MCP_bRegAddress holds the address to write
    '         : MCP_bDataByte holds the data to write to the register
    ' Output  : None
    ' Notes   : None
    '
    $ifdef MCP_Software_I2C         ' Are we using the software I2C routines?
    $define MCP_Write(pRegAddress, pDataByte) BusOut cMCP_Write, pRegAddress, [pDataByte]
    $else                       ' Otherwise... Use the hardware I2C routines
    $define MCP_Write(pRegAddress, pDataByte) HBusOut cMCP_Write, pRegAddress, [pDataByte]
    $endif
    '------------------------------------------------------------------------------------------
    ' Read from one of the MCP23017 registers
    ' Input   : MCP_bRegAddress holds the adddress To write
    ' Output  : pReturn holds the byte read from the MCP device
    ' Notes   : None
    '
    $ifdef MCP_Software_I2C         ' Are we using the software I2C routines?
    $define MCP_Read(pRegAddress, pReturn) BusIn cMCP_Read, pRegAddress, [pReturn]
    $else                       ' Otherwise... Use the hardware I2C routines
    $define MCP_Read(pRegAddress, pReturn) HBusIn cMCP_Read, pRegAddress, [pReturn]
    $endif
    
    '------------------------------------------------------------------------------------------
    ' Reads PORTA (GPIOA)
    ' Input     : None
    ' Output    : Reads the contents of PORTA (GPIOA)
    ' Notes     : Saves the port value to a shadow variable
    '
    $define MCP_ReadPORTA() _MCP_ReadPORTA
    
    _MCP_ReadPORTA Macro- \Byte
        MCP_Read(cGPIOA, WREG)
        MCP_bShadowDataRegA = WREG
        Return_WREG
    Endm
    
    '------------------------------------------------------------------------------------------
    ' Reads PORTB (GPIOB)
    ' Input     : None
    ' Output    : Reads the contents of PORTB (GPIOB)
    ' Notes     : Saves the port value to a shadow variable
    '
    $define MCP_ReadPORTB() _MCP_ReadPORTB
    
    _MCP_ReadPORTB Macro- \Byte
        MCP_Read(cGPIOB, WREG)
        MCP_bShadowDataRegB = WREG
        Return_WREG
    Endm
    
    '------------------------------------------------------------------------------------------
    ' Writes PORTA (GPIOA) Latch register with pDataByte value
    ' Input     : pDataByte holds the value to write to PORTA (GPIOA)
    ' Output    : None
    ' Notes     : Saves the port value to a shadow variable
    '
    $define MCP_WritePORTA(pDataByte)   '
        MCP_bShadowDataRegA = pDataByte '
        MCP_Write(cOLATA, pDataByte)
    
    '------------------------------------------------------------------------------------------
    ' Writes PORTB (GPIOB) Latch register with pDataByte value
    ' Input     : pDataByte holds the value to write to PORTB
    ' Output    : None
    ' Notes     : Saves the port value to a shadow variable
    '
    $define MCP_WritePORTB(pDataByte)   '
        MCP_bShadowDataRegB = pDataByte '
        MCP_Write(cOLATB, pDataByte)
    
    '------------------------------------------------------------------------------------------
    ' Sets PORTA (GPIOA) pins direction 0 = Output 1 = Input
    ' Input     : pDirection holds the value to write to the direction register
    ' Output    : None
    ' Notes     : Saves the TRIS value to a shadow variable
    '
    $define MCP_WriteTRISA(pDirection) '
        MCP_Write(cIODIRA, pDirection) '
        MCP_bShadowDirRegA = pDirection
    
    '------------------------------------------------------------------------------------------
    ' Sets PORTB (GPIOB) pins direction 0 = Output 1 = Input
    ' Input     : pDirection holds the value to write to the direction register
    ' Output    : None
    ' Notes     : Saves the TRIS value to a shadow variable
    '
    $define MCP_WriteTRISB(pDirection) '
        MCP_Write(cIODIRB, pDirection) '
        MCP_bShadowDirRegB = pDirection
    
    '------------------------------------------------------------------------------------------
    ' Write to a bit of PORTA (GPIOA)
    ' Input     : pPin Output pin to set or clear
    '           : pHiLo 1 = high, 0 = low
    ' Output    : None
    ' Notes     : Uses the shadow port to read the previous state
    '
    $define MCP_WritePinOfPORTA(pPin, pHiLo) '
        MCP_bTemp = pHiLo                    '
        WREG = pPin                          '
        _MCP_WritePinOfPORTA
    
    _MCP_WritePinOfPORTA Macro-
        GoSub __MCP_WritePinOfPORTA
    Endm
    
    #ifMacro- _MCP_WritePinOfPORTA
    __MCP_WritePinOfPORTA:
        PRODL = WREG
        PRODH =  1 << PRODL
        If MCP_bTemp = 1 Then
            PRODH = PRODH | MCP_bShadowDataRegA
        Else
            PRODH = ~PRODH
            PRODH = PRODH & MCP_bShadowDataRegA
        EndIf
        MCP_Write(cOLATA, PRODH)
        Return
    #endIfMacro
    
    '------------------------------------------------------------------------------------------
    ' Write to a bit of PORTB (GPIOB)
    ' Input     : pPin Output pin to set or clear
    '           : pHiLo 1 = high, 0 = low
    ' Output    : None
    ' Notes     : Uses the shadow port to read the previous state
    '
    $define MCP_WritePinOfPORTB(pPin, pHiLo) '
        MCP_bTemp = pHiLo                    '
        WREG = pPin                          '
        _MCP_WritePinOfPORTB
    
    _MCP_WritePinOfPORTB Macro-
        GoSub __MCP_WritePinOfPORTB
    Endm
    
    #ifMacro- _MCP_WritePinOfPORTB
    __MCP_WritePinOfPORTB:
        PRODL = WREG
        PRODH =  1 << PRODL
        If MCP_bTemp = 1 Then
            PRODH = PRODH | MCP_bShadowDataRegB
        Else
            PRODH = ~PRODH
            PRODH = PRODH & MCP_bShadowDataRegB
        EndIf
        MCP_Write(cOLATB, PRODH)
        Return
    #endIfMacro
    
    '------------------------------------------------------------------------------------------
    ' Sets or clears the TRISA of a Single Pin from PORTA pins
    ' Input     : pPin TRISA pin to set or clear (0 - 7)
    '           : pDirection 1 = Input, 0 = Output
    ' Output    : None
    ' Notes     : None
    '
    $define MCP_WritePinOfTRISA(pPin, pDirection) '
        MCP_bTemp = pDirection                  '
        WREG = pPin                             '
        _MCP_WritePinOfTRISA
    
    _MCP_WritePinOfTRISA Macro-
        GoSub __MCP_WritePinOfTRISA
    Endm
    
    #ifMacro- _MCP_WritePinOfTRISA
    __MCP_WritePinOfTRISA:
        PRODL = WREG
        PRODH =  1 << PRODL
        If MCP_bTemp = 1 Then
            PRODH = PRODH | MCP_bShadowDirRegA
        Else
            PRODH = ~PRODH
            PRODH = PRODH & MCP_bShadowDirRegA
        EndIf
        MCP_bShadowDirRegA = PRODH
        MCP_Write(cIODIRA, PRODH)
        Return
    #endIfMacro
    
    '------------------------------------------------------------------------------------------
    ' Sets or clears the TRISB of a Single Pin from PORTB pins
    ' Input     : pPin TRISB pin to set or clear (0 - 7)
    '           : pDirection. 1 = Input, 0 = Output
    ' Output    : None
    ' Notes     : None
    '
    $define MCP_WritePinOfTRISB(pPin, pDirection) '
        MCP_bTemp = pDirection                  '
        WREG = pPin                             '
        _MCP_WritePinOfTRISB
    
    _MCP_WritePinOfTRISB Macro-
        GoSub __MCP_WritePinOfTRISB
    Endm
    
    #ifMacro- _MCP_WritePinOfTRISB
    __MCP_WritePinOfTRISB:
        PRODL = WREG
        PRODH =  1 << PRODL
        If MCP_bTemp = 1 Then
            PRODH = PRODH | MCP_bShadowDirRegB
        Else
            PRODH = ~PRODH
            PRODH = PRODH & MCP_bShadowDirRegB
        EndIf
        MCP_bShadowDirRegB = PRODH
        MCP_Write(cIODIRB, PRODH)
        Return
    #endIfMacro
    
    '------------------------------------------------------------------------------------------
    ' Gets PORTA (GPIOA) pins direction. 0 = Output 1 = Input
    ' Input     : None
    ' Output    : Returns the value of TRISA
    ' Notes     : Reads the shadow register instead of reading the register
    '
    $define MCP_ReadTRISA() MCP_bShadowDirRegA
    
    '------------------------------------------------------------------------------------------
    ' Gets PORTB (GPIOB) pins direction. 0 = Output 1 = Input
    ' Input     : None
    ' Output    : Returns the value of TRISA
    ' Notes   : Reads the shadow register instead of reading the register
    '
    $define MCP_ReadTRISA() MCP_bShadowDirRegB
    
    '------------------------------------------------------------------------------------------
    ' Set the Pullups resistors for any Pin of PORTA (GPIOA)
    ' Input     : pPullUps holds the value to write to the register
    ' Output    : None
    ' Notes     : Pullups can be enabled/disabled independently
    '           : for any pin of the port. 0 = Disabled  1 = Enabled
    '
    $define MCP_PullUpsPORTA(pPullUps) MCP_Write(cGPPUA, pPullUps)
    
    '------------------------------------------------------------------------------------------
    ' Sets the Pullups resistors  for any Pin of PORTB (GPIOB)
    ' Input     : pPullUps holds the value to write to the register
    ' Output    : None
    ' Notes     : Pullups can be enabled/disabled independently
    '           : for any pin of the port. 0 = Disabled  1 = Enabled
    '
    $define MCP_PullUpsPORTB(pPullUps) MCP_Write(cGPPUB, pPullUps)
    
    '------------------------------------------------------------------------------------------
    ' Initialise the MCP23017
    ' Input     : None
    ' Output    : None
    ' Notes     : None
    '
    MCP23017_Init:
    
    $ifdef MCP_Reset_Pin                            ' Is a reset line being used?
        Low MCP_Reset_Pin                           ' Yes. So pull the reset line low
        DelayMS 10                                  ' Wait 10 milliseconds
        Set MCP_Reset_Pin                           ' Bring the device out of reset
    $endif
        MCP_bShadowDirRegA = $FF
        MCP_bShadowDirRegB = $FF
        MCP_bShadowDataRegA = $00
        MCP_bShadowDataRegB = $00
    $endif
    The library has many comments so you should be able to work out each routine.

    And here's a screenshot of it working in the simulator:
    This article was originally published in forum thread: MCP23017 port pin expander not working started by Dave-S View original post
  • Recent Activity

    See_Mos-247

    Mysterious PORTB problem

    Thread Starter: xldaedalus

    I'm using Proton+ to develop firmware for a product with switches. The MCU is an 18F26K22. Most of the switches reside on PORTB. I am NOT using a...

    See_Mos Today, 14:44 Go to last post
    Mellbreak-21950

    Watchdog timer

    Thread Starter: joesaliba

    I have a code that basically looks for four input and four outputs, depends on various timing and input conditions. I use interrupt and some delays...

    Mellbreak Today, 11:30 Go to last post