Shop Learn
MCP20317 Driver (I2C) — Parallax Forums

MCP20317 Driver (I2C)

MicksterMickster Posts: 1,974
edited 2021-12-17 06:42 in BASIC (for Propeller)

I would like to tackle this but does anyone have a generic I2C driver for P2-FB? @JRoark?

I think it would be a good idea to list drivers, as they are developed in the Discussion Title(?)

Also attaching the datasheet for convenience(?)

Comments

  • This is Spin1, so you'll have to translate it, but if you've got a handle on I2C you'll see that it's quite simple. I haven't used this code in ages, but when I did it was on a client project (commercial HVAC system).

  • This is Spin1, so you'll have to translate it, but if you've got a handle on I2C you'll see that it's quite simple. I haven't used this code in ages, but when I did it was on a client project (commercial HVAC system).

  • JonnyMacJonnyMac Posts: 7,924
    edited 2021-12-18 00:12

    Well... I tried to share my Spin1 driver for the chip that has been used in commercial products, but the forums tells me I am not allowed to upload files here. If you'd like that code you can send me a PM.

    Here's the source (needs an I2C driver).

    '' =================================================================================================
    ''
    ''   File....... jm_mcp23017.spin
    ''   Author..... Jon "JonnyMac" McPhalen
    ''               Copyright (c) 2011-2021 Jon McPhalen
    ''               -- see below for terms of use
    ''   E-mail..... jon.mcphalen@gmail.com
    ''   Started....
    ''   Updated.... 04 AUG 2013
    ''               -- modified to allow all addreses from single object
    ''               -- streamlined with wr_reg() and rd_reg()
    ''
    '' =================================================================================================
    
    
    con
    
      MCP23017 = %0100_000_0                                        ' I2C device ID for MCP23017
    
      ' command bytes for standard POR settings
      ' -- IOCON.BANK = 0
    
      IODIRA   = $00                                                ' command bytes
      IODIRB   = $01
      IPOLA    = $02
      IPOLB    = $03
      GPINTENA = $04
      GPINTENB = $05
      DEFVALA  = $06
      DEFVALB  = $07
      INTCONA  = $08
      INTCONB  = $09
      IOCON    = $0A
      GPPUA    = $0C
      GPPUB    = $0D
      INTFA    = $0E
      INTFB    = $0F
      INTCAPA  = $10
      INTCAPB  = $11
      GPIOA    = $12
      GPIOB    = $13
      OLATA    = $14
      OLATB    = $15
    
    
    obj
    
      i2c : "jm_i2c"
    
    
    pub null
    
    '' This is not a top-level object
    
    
    pub start(sclpin, sdapin)
    
    '' Start MCP23017 device
    '' -- creates slave ID for this object
    '' -- sclpin and sdapin are connections to I2C buss
    
      i2c.setupx(sclpin, sdapin)                                    ' connect to i2C
    
    
    pub wr_reg(reg, value, addr)
    
    '' Write value to register in device at addr
    '' -- see constants section for valid registers
    '' -- see MCP23017 docs for details on register value
    '' -- addr is device address, %000 to %111
    
      i2c.start                                                     ' start transaction
      i2c.write(MCP23017 | (addr << 1))                             ' write id
      i2c.write(reg)                                                ' write reg #
      i2c.write(value)                                              ' write reg value
      i2c.stop                                                      ' end transaction
    
    
    pub rd_reg(reg, addr) | id, value
    
    '' Read value from register in device at addr
    '' -- see constants section for valid registers
    '' -- see MCP23017 docs for details on register value
    '' -- addr is device address, %000 to %111
    
      id := MCP23017 | (addr << 1)                                  ' id for write
    
      i2c.start                                                     ' start transaction
      i2c.write(id)                                                 ' write id
      i2c.write(reg)                                                ' write reg #
      i2c.start                                                     ' re-start
      i2c.write(id | %1)                                            ' id for read
      value := i2c.read(i2c#NAK)                                    ' read value from reg
      i2c.stop                                                      ' end transaction
    
      return value                                                  ' return value to caller
    
    
    con
    
      { ------------------------------- }
      {  Named methods for easy access  }
      { ------------------------------- }
    
    
    pub cfg_dira(dirbits, addr)
    
    '' Configure GPIOA IO pins
    '' -- 0 bit = output, 1 bit = input
    '' -- addr is device address, %000 to %111
    
      wr_reg(IODIRA, dirbits, addr)
    
    
    pub cfg_pola(polbits, addr)
    
    '' Configure GP0 input polarity
    '' -- 0 bit = normal, 1 bit = inverted
    '' -- addr is device address, %000 to %111
    
      wr_reg(IPOLA, polbits, addr)
    
    
    pub cfg_iocon(iocbits, addr)
    
    '' Configure IOCON bits
    '' -- see documentation for details on iocon bits
    '' -- addr is device address, %000 to %111
    
      wr_reg(IOCON, iocbits, addr)
    
    
    pub cfg_pua(pubits, addr)
    
    '' Configure GPIOA pull-ups
    '' -- 1 bit = pull-up pin via 100K
    '' -- addr is device address, %000 to %111
    
      wr_reg(GPPUA, pubits, addr)
    
    
    pub wr_gpioa(outbits, addr)
    
    '' Write outbits to GPIOA pins
    '' -- only pins set as outputs affected
    '' -- addr is device address, %000 to %111
    
      wr_reg(GPIOA, outbits, addr)
    
    
    pub rd_gpioa(addr) | devid, inbits
    
    '' Read GPIOA bits
    '' -- addr is device address, %000 to %111
    
      return rd_reg(GPIOA, addr)
    
    
    pub cfg_dirb(dirbits, addr)
    
    '' Configure GPIOB IO pins
    '' -- 0 bit = output, 1 bit = input
    '' -- addr is device address, %000 to %111
    
      wr_reg(IODIRB, dirbits, addr)
    
    
    pub cfg_polb(polbits, addr)
    
    '' Configure GPIOB input polarity
    '' -- 0 bit = normal, 1 bit = inverted
    '' -- addr is device address, %000 to %111
    
      wr_reg(IPOLB, polbits, addr)
    
    
    pub cfg_pub(pubits, addr)
    
    '' Configure GPIOB pull-ups
    '' -- 1 bit = pull-up pin via 100K
    '' -- addr is device address, %000 to %111
    
      wr_reg(GPPUB, pubits, addr)
    
    
    pub wr_gpiob(outbits, addr)
    
    '' Write value to GPIOB
    '' -- addr is device address, %000 to %111
    
      wr_reg(GPIOB, outbits, addr)
    
    
    pub rd_gpiob(addr) | devid, inbits
    
    '' Read GPIOB bits
    '' -- addr is device address, %000 to %111
    
      return rd_reg(GPIOB, addr)
    
    
    con
    
    {{
    
      Terms of Use: MIT License
    
      Permission is hereby granted, free of charge, to any person obtaining a copy of this
      software and associated documentation files (the "Software"), to deal in the Software
      without restriction, including without limitation the rights to use, copy, modify,
      merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
      permit persons to whom the Software is furnished to do so, subject to the following
      conditions:
    
      The above copyright notice and this permission notice shall be included in all copies
      or substantial portions of the Software.
    
      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
      INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
      PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
      HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
      CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
      OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    
    }}
    

    My Spin1 I2C driver

    '' =================================================================================================
    ''
    ''   File....... jm_i2c.spin
    ''   Purpose.... Low-level I2C routines (requires pull-ups on SCL and SDA)
    ''   Author..... Jon "JonnyMac" McPhalen
    ''               Copyright (c) 2009-2021 Jon McPhalen
    ''               -- see below for terms of use
    ''               -- elements inspired by code from Mike Green & Chris Gadd
    ''   E-mail..... jon.mcphalen@gmail.com
    ''   Started.... 28 JUL 2009
    ''   Updated.... 01 MAR 2021
    ''
    ''   {$P1}
    ''
    '' =================================================================================================
    
    '  IMPORTANT Note: This code requires pull-ups on the SDA _and_ SCL lines -- it does not drive
    '  the SCL line high.
    '
    '  Restored clock stretch feature
    
    
    con { fixed io pins }
    
      RX1  = 31                                                     ' programming / terminal
      TX1  = 30
    
      SDA1 = 29                                                     ' eeprom / i2c
      SCL1 = 28
    
    
    con
    
      #0, ACK, NAK
    
    
    var
    
      long  sclpin                                                  ' bus pins
      long  sdapin
    
    
    pub null
    
    '' This is not a top-level object
    
    
    pub setup
    
    '' Setup I2C using Propeller EEPROM pins
    
      setupx(SCL1, SDA1)
    
    
    pub setupx(_scl, _sda)
    
    '' Define I2C SCL (clock) and SDA (data) pins
    
      longmove(@sclpin, @_scl, 2)                                   '  copy pins
      dira[sclpin] := 0                                             '  float to pull-up
      outa[sclpin] := 0                                             '  write 0 to output reg
      dira[sdapin] := 0
      outa[sdapin] := 0
    
      repeat 9
        if (ina[sdapin])
          quit                                                     ' reset device
        dira[sclpin] := 1
        dira[sclpin] := 0
    
    
    pub present(slaveid)
    
    '' Pings device, returns true if ACK
    
      start
    
      return (write(slaveid) == ACK)
    
    
    pub wait(slaveid) | ackbit
    
    '' Waits for I2C device to be ready for new command
    
      repeat
        start
        ackbit := write(slaveid)
      until (ackbit == ACK)
    
    
    pub start
    
    '' Create I2C start sequence
    '' -- will wait if I2C bus SCL pin is held low
    
      dira[sdapin] := 0                                             ' float SDA (1)
      dira[sclpin] := 0                                             ' float SCL (1)
    
      dira[sdapin] := 1                                             ' SDA low (0)
      dira[sclpin] := 1                                             ' SCL low (0)
    
    
    pub write(i2cbyte) | ackbit
    
    '' Write byte to I2C bus
    '' -- leaves SCL low
    
      i2cbyte := !i2cbyte << 24                                     ' move msb (bit7) to bit31
    
      repeat 8                                                      ' output eight bits
        dira[sdapin] := i2cbyte <-= 1                               ' send msb first
        dira[sclpin] := 0                                           ' SCL high (float to p/u)
        dira[sclpin] := 1                                           ' SCL low
    
      dira[sdapin] := 0                                             ' relase SDA to read ack bit
      dira[sclpin] := 0                                             ' SCL high (float to p/u)
      repeat while (ina[sclpin] == 0)                               ' hold for clock stretch
      ackbit := ina[sdapin]                                         ' read ack bit
      dira[sclpin] := 1                                             ' SCL low
    
      return ackbit
    
    
    pub read(ackbit) : i2cbyte
    
    '' Read byte from I2C bus
    
      dira[sdapin] := 0                                             ' make sdapin input
    
      repeat 8
        dira[sclpin] := 0                                           ' SCL high (float to p/u)
        i2cbyte := (i2cbyte << 1) | ina[sdapin]                     ' read the bit
        dira[sclpin] := 1                                           ' SCL low
    
      dira[sdapin] := !ackbit                                       ' output ack bit
      dira[sclpin] := 0                                             ' clock it
      repeat while (ina[sclpin] == 0)                               ' hold for clock stretch
      dira[sclpin] := 1
    
      return i2cbyte
    
    
    pub stop
    
    '' Create I2C stop sequence
    
      dira[sdapin] := 1                                             ' SDA low
      dira[sclpin] := 0                                             ' float SCL
      dira[sdapin] := 0                                             ' float SDA
    
    
    con { license }
    
    {{
    
      Terms of Use: MIT License
    
      Permission is hereby granted, free of charge, to any person obtaining a copy
      of this software and associated documentation files (the "Software"), to deal
      in the Software without restriction, including without limitation the rights
      to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
      copies of the Software, and to permit persons to whom the Software is
      furnished to do so, subject to the following conditions:
    
      The above copyright notice and this permission notice shall be included in all
      copies or substantial portions of the Software.
    
      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
      IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
      AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
      OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      SOFTWARE.
    
    }}
    

    Now that we can attach files... here they are.

  • @JonnyMac said:
    Well... I tried to share my Spin1 driver for the chip that has been used in commercial products, but the forums tells me I am not allowed to upload files here.

    Thanks for the alert Jon.
    Uploads are allowed now on all the new categories.

  • MicksterMickster Posts: 1,974
    edited 2021-12-20 05:54

    @JonnyMac

    Many thanks, Jon :+1:
    Very nice coding, as usual :smile:

    I have only just gotten back to this and quite surprised to find that the I2C driver appears to be bit-banging and not a special smartpin function, correct?

    Edit: Ah, it's P1 I see.

  • Here's my P2 I2C driver. There is no smart pin I2C mode, but the pin configuration bits allow for optional pull-ups which the code can use.

  • @JonnyMac said:
    Here's my P2 I2C driver. There is no smart pin I2C mode, but the pin configuration bits allow for optional pull-ups which the code can use.

    Cheers! I had already grabbed it from github :+1:

  • The one I attached is the latest. Added a constant called PU_EXT the other day.

  • Not sure if it was the P2 but I remember reading somewhere that external pull-ups were recommended(?)

  • @JonnyMac said:
    Here's my P2 I2C driver. There is no smart pin I2C mode, but the pin configuration bits allow for optional pull-ups which the code can use.

    Not to steal the thread, but... With flexprop, I'm not able to compile code that uses this version of jm_i2c.spin2 (the one in this thread as well as the one from the Spin 2 Beginner Series Part 12 download).

    flexprop doesn't like the line (the addition of '#0,", within the enum):

      #0, PU_NONE, #0, PU_EXT, PU_1K5, PU_3K3, PU_15K               ' pull-up options
    

    compile example:

    "~/flexspin" -2 -l --tabs=4 -D_BAUD=230400 -O1    --charset=utf8 -I "~/flexprop//include"  "~/Documents/P2ESCode/jm_p2_code/code-12_nextion-2/06_nextion_pixel_controller/jm_i2c.spin2"
    Propeller Spin/PASM Compiler 'FlexSpin' (c) 2011-2021 Total Spectrum Software Inc.
    Version 5.9.6 Compiled on: Nov 26 2021
    ~/Documents/P2ESCode/jm_p2_code/code-12_nextion-2/06_nextion_pixel_controller/jm_i2c.spin2:61: error: Duplicate case value
    ~/Documents/P2ESCode/jm_p2_code/code-12_nextion-2/06_nextion_pixel_controller/jm_i2c.spin2:62: error: Duplicate case value
    ~/Documents/P2ESCode/jm_p2_code/code-12_nextion-2/06_nextion_pixel_controller/jm_i2c.spin2:63: error: Duplicate case value
    jm_i2c.spin2
    child process exited abnormally
    

    To compile, I had to modify the line:

      #0, PU_NONE, PU_EXT, PU_1K5, PU_3K3, PU_15K               ' pull-up options
    

    A difference in how flexprop handle enumerators, I suppose.

    dgately

  • JonnyMacJonnyMac Posts: 7,924
    edited 2021-12-20 17:42

    flexprop doesn't like the line (the addition of '#0,", within the enum):

    That's a feature of Spin going back to Spin1. Hopefully, @ersmith can adjust FlexProp to handle that. In the meantime, I suggest modify your local copy like this.

      #0, PU_NONE, PU_1K5, PU_3K3, PU_15K               ' pull-up options
      #0, PU_EXT
    
  • @JonnyMac said:

    flexprop doesn't like the line (the addition of '#0,", within the enum):

    That's a feature of Spin going back to Spin1. Hopefully, @ersmith can adjust FlexProp to handle that. In the meantime, I suggest modify your local copy like this.

      #0, PU_NONE, PU_1K5, PU_3K3, PU_15K               ' pull-up options
      #0, PU_EXT
    

    Yep! Thanks!

  • ersmithersmith Posts: 5,193
    edited 2021-12-20 18:11

    @dgately and @JonnyMac : I think flexprop's error in this case is legit, and I'm surprised PNut didn't give at least a warning. The case statement starting at line 57 of jm_i2c.spin2 reads:

      case pullup
        PU_NONE : pullup := P_HIGH_FLOAT                            ' use external pull-up
        PU_EXT  : pullup := P_HIGH_FLOAT
        PU_1K5  : pullup := P_HIGH_1K5                              ' 1.5k
        PU_3K3  : pullup := P_HIGH_1MA                              ' acts like ~3.3k
        other   : pullup := P_HIGH_15K                              ' 15K
    

    but PU_NONE and PU_EXT are both equal to 0, so there are indeed two case statements with the same label. Is that legal in Spin? At the very least it seems like it should be a warning.

    In this case it does not, in fact, matter, since both cases do the exact same thing (set pullup to P_HIGH_FLOAT) but one can certainly imagine other cases where it could create unintended consequences.

  • JonnyMacJonnyMac Posts: 7,924
    edited 2021-12-20 19:22

    My mistake -- I thought Dennis was suggesting the enumerated constants definition was the problem. Propeller Tool doesn't complain about the redundant value in case, but maybe it should. Since PU_NONE and PU_EXT has the same value, I will put the case structure back to the way it was.

      case pullup
        PU_NONE : pullup := P_HIGH_FLOAT                            ' use external pull-up
        PU_1K5  : pullup := P_HIGH_1K5                              ' 1.5k
        PU_3K3  : pullup := P_HIGH_1MA                              ' acts like ~3.3k
        other   : pullup := P_HIGH_15K                              ' 15K
    
Sign In or Register to comment.