Browse Source

Add mcp23017 Lua module (#3197)

Marcel P 3 years ago
parent
commit
7f30381301

+ 230 - 0
docs/lua-modules/mcp23017.md

@@ -0,0 +1,230 @@
+# Lua MCP23017 Module for NodeMCU / ESP8266
+
+| Since  | Origin / Contributor  | Maintainer  | Source  |
+| :----- | :-------------------- | :---------- | :------ |
+| 2020-04-10 | [Marcel P.](https://github.com/plomi-net) | [Marcel P.](https://github.com/plomi-net) | [mcp23017.lua](../../lua_modules/mcp23017/mcp23017.lua) |
+
+
+This Lua module provides access to the MCP23017 I²C I/O Expander.
+
+The [MCP23017](http://ww1.microchip.com/downloads/en/devicedoc/20001952c.pdf) is a port expander and provides 16 channels for inputs and outputs. 
+Up to 8 devices (128 channels) are possible by the configurable address (A0 - A2).
+
+Due to the 16 channels, 2 bytes are required for switching outputs or reading input signals. These are A and B. 
+A single pin can be set or a whole byte. 
+
+The numbering of the individual pins starts at 0 and ends with 7.
+The numbers are for each register GPIO A and GPIO B.
+
+
+!!! important
+	The module requires `i2c` and `bit` C module built into firmware.
+
+
+### Require
+```lua
+mcp = require "mcp23017"
+```
+
+## Example Script
+The example script can be found [here](../../lua_examples/mcp23017/mcp23017_example.lua)
+
+## setup()
+Configures the address of the module and tests the connection to the i2c bus. 
+The i2c id is required for an existing i2c interface, alternatively the sda and scl pins can be specified.
+Then this function will establish the connection.
+Automatically resets the device state (see `mcp23017:reset()`)
+
+#### Syntax
+`mcp23017:setup(address, i2c_id)`
+
+#### Parameter
+- `address` address for MCP23017, default: 0x20 (should be between 0x20 and 0x27)
+- `i2c_id`  id for the i2c bus connection (remember to call i2c.setup before)
+
+#### Return
+`true` if device found, otherwise `false`.
+
+#### possible Errors
+- `MCP23017 device on address not found`
+- `MCP23017 address is out of range`
+
+#### Example
+```lua
+local mcp23017 = require "mcp23017"
+
+local address = 0x20
+local cSCL = 1
+local cSDA = 2
+local i2c_instance = 0
+
+-- setup i2c bus and create instance for mcp23017 (assigned to mcp)
+i2c.setup(i2c_instance, cSDA, cSCL, i2c.SLOW)
+local mcp = mcp23017(address, i2c_instance)
+```
+
+## setMode()
+Set the mode of a single channel. This can be OUTPUT or INPUT.
+
+#### Syntax
+`mcp23017:setMode(register, pin, mode)`
+
+#### Parameter
+- `register` the side of channels (GPA or GPB)
+- `pin` the number to be set for the channel (0-15)
+- `mode` the mode for the channel. This can be `mcp23017.INPUT` or `mcp23017.OUTPUT`
+
+#### Return
+`true`, in case of error `nil`.
+
+#### Example
+```lua
+-- set pin 7 and 8 to output (GPA7 and GPB0) and GPB1 to input 
+mcp:setMode(mcp.GPA, 7, mcp.OUTPUT)
+mcp:setMode(mcp.GPB, 0, mcp.OUTPUT)
+mcp:setMode(mcp.GPB, 1, mcp.INPUT)
+```
+
+## setPin()
+Set the state of a single channel. This can be HIGH or LOW.
+
+#### Syntax
+`mcp23017:setMode(register, pin, state)`
+
+#### Parameter
+- `register` the side of channels (GPA or GPB)
+- `pin` the number to be set for the channel (0-15)
+- `state` the state for the channel. This can be `mcp23017.HIGH` or `mcp23017.LOW`
+
+#### Return
+`true`, in case of error `nil`.
+
+#### Example
+```lua
+-- set pin 7 to high (GPA7)
+mcp:setPin(mcp.GPA, 7, mcp.HIGH)
+-- set pin 8 to low (GPB0)
+mcp:setPin(mcp.GPB, 0, mcp.LOW)
+```
+
+## getPinState()
+get the state for a single channel. This can be HIGH or LOW.
+
+#### Syntax
+`mcp23017:getPinState(register, pin)`
+
+#### Parameter
+- `register` the side of channels (GPA or GPB)
+- `pin` the number for which a state is to be queried (0-15)
+
+#### Return
+`true` for HIGH, `false` for LOW, in case of error `nil`.
+
+#### Example
+```lua
+-- get the state for pin 9 (GPB1)
+print(mcp:getPinState(mcp.GPB, 1))
+```
+
+## reset()
+By calling this function, a safe state is established. 
+All channels are set to input.
+This function can be used for a panic program.
+
+#### Syntax
+`mcp23017:reset()`
+
+#### Parameter
+None
+
+#### Return
+None
+
+#### Example
+```lua
+-- reset the mcp23017 to startup defaults
+mcp:reset()
+```
+
+## setInternalPullUp()
+Enable or disable the internal pullup resistors.
+
+#### Syntax
+`mcp23017:setInternalPullUp(register, byte)`
+
+#### Parameter
+- `register` the side of channels (GPA or GPB)
+- `byte` byte to set the pullup resistors
+
+#### Return
+None
+
+#### Example
+```lua
+-- enable all pullup resistors for GPA
+print(mcp:setInternalPullUp(mcp.GPA, 0xFF))
+-- disable all pullup resistors for GPA
+print(mcp:setInternalPullUp(mcp.GPA, 0x00))
+```
+
+## writeIODIR()
+Setup the mode of the channels with a whole byte.
+
+
+#### Syntax
+`mcp23017:writeIODIR(register, byte)`
+
+#### Parameter
+- `register` the side of channels (GPA or GPB)
+- `byte` byte to set the mode for all channels for this register
+
+#### Return
+None
+
+#### Example
+```lua
+-- set all GPA to input
+print(mcp:writeIODIR(mcp.GPA, 0xFF))
+-- set all GPA to output
+print(mcp:writeIODIR(mcp.GPA, 0x00))
+```
+
+## writeGPIO()
+Setup the output state of the channels with a whole byte.
+
+#### Syntax
+`mcp23017:writeGPIO(register, byte)`
+
+#### Parameter
+- `register` the side of channels (GPA or GPB)
+- `byte` byte to set the state for all channels for this register
+
+#### Return
+None
+
+#### Example
+```lua
+-- set all GPA to HIGH
+print(mcp:writeGPIO(mcp.GPA, 0xFF))
+-- set all GPA to LOW
+print(mcp:writeGPIO(mcp.GPA, 0x00))
+```
+
+## readGPIO()
+Read the input states of the channels with a whole byte.
+
+#### Syntax
+`mcp23017:readGPIO(register)`
+
+#### Parameter
+- `register` the side of channels (GPA or GPB)
+
+#### Return
+byte with states
+
+#### Example
+```lua
+-- get states for GPA
+print(mcp:readGPIO(mcp.GPA))
+```
+

+ 77 - 0
lua_examples/mcp23017/mcp23017_example.lua

@@ -0,0 +1,77 @@
+--[[
+    This example demonstrates how to use the different functions of the mcp23017 lua module.
+
+    @author Marcel P. | Plomi.net
+	@github https://github.com/plomi-net
+	@version 1.0.0
+]]
+
+--[[
+ initialize and setup
+]]
+-- initialize module
+local mcp23017 = require "mcp23017"
+local address = 0x20 -- the address for MCP23017 (between 0x20 and 0x27)
+local cSCL = 1 -- SCL pin = 1 = D1 / GPIO 5 (ESP8266)
+local cSDA = 2 -- SDA pin = 2 = D2 / GPIO4 (ESP8266)
+local i2cId = 0 -- i2c id
+-- setup i2c bus and create instance for mcp23017 (assigned to mcp)
+i2c.setup(i2cId, cSDA, cSCL, i2c.SLOW)
+local mcp = mcp23017(address, i2cId)
+
+--[[
+ set output and input channels
+]]
+-- set pin 7 and 8 to output (GPA7 and GPB0) and GPB1 to input
+mcp:setMode(mcp.GPA, 7, mcp.OUTPUT)
+mcp:setMode(mcp.GPB, 0, mcp.OUTPUT)
+mcp:setMode(mcp.GPB, 1, mcp.INPUT)
+
+--[[
+ set output channels to high and low
+]]
+-- set pin 7 to high (GPA7)
+mcp:setPin(mcp.GPA, 7, mcp.HIGH)
+-- set pin 8 to low (GPB0)
+mcp:setPin(mcp.GPB, 0, mcp.LOW)
+
+--[[
+ toggle pin 6 channel state every second (blinking)
+]]
+local currentPin = 6
+local currentReg = mcp.GPA
+local currentState = false
+
+mcp:setMode(currentReg, currentPin, mcp.OUTPUT)
+tmr.create():alarm(1000, tmr.ALARM_AUTO, function()
+    if currentState == true then
+        -- print("set to low")
+        mcp:setPin(currentReg, currentPin, mcp.LOW)
+        currentState = false
+    else
+        -- print("set to high")
+        mcp:setPin(currentReg, currentPin, mcp.HIGH)
+        currentState = true
+    end
+end)
+
+--[[
+ read input channels and display every 7 seconds
+]]
+-- read input register
+tmr.create():alarm(7000, tmr.ALARM_AUTO, function()
+    local a = mcp:readGPIO(mcp.GPA)
+    if a ~= nil then
+        print("GPIO A input states: " .. a)
+    else
+        print("GPIO A unreadable, check device")
+    end
+
+    local b = mcp:readGPIO(mcp.GPB)
+    if b ~= nil then
+        print("GPIO B input states: " .. b)
+    else
+        print("GPIO B unreadable, check device")
+    end
+    print(" ")
+end)

+ 3 - 0
lua_modules/mcp23017/README.md

@@ -0,0 +1,3 @@
+# MCP23017 Module
+
+Documentation for this Lua module is available in the [mcp23017.md](../../docs/lua-modules/mcp23017.md) file and in the [Official NodeMCU Documentation](https://nodemcu.readthedocs.io/) in `Lua Modules` section.

+ 185 - 0
lua_modules/mcp23017/mcp23017.lua

@@ -0,0 +1,185 @@
+--[[
+	This Lua module provides access to the MCP23017 module.
+
+    The MCP23017 is a port expander and provides 16 channels for inputs and outputs.
+    Up to 8 devices (128 channels) are possible by the configurable address (A0 - A2 Pins).
+
+    Due to the 16 channels, 2 bytes are required for switching outputs or reading input signals. These are A and B.
+    A single pin can be set or a whole byte.
+
+    The numbering of the individual pins starts at 0 and ends with 7.
+    The numbers are for each register GPIO A and GPIO B.
+
+	The module requires `i2c` and `bit` C module built into firmware.
+
+	@author Marcel P. | Plomi.net
+	@github https://github.com/plomi-net
+	@version 1.0.0
+]]
+
+local i2c, string, issetBit, setBit, clearBit, error =
+      i2c, string, bit.isset, bit.set, bit.clear, error
+local isINPUT, isGPB, isHIGH = true, true, true
+
+-- registers (not used registers are commented out)
+local MCP23017_IODIRA = 0x00
+local MCP23017_IODIRB = 0x01
+local MCP23017_DEFVALA = 0x06
+local MCP23017_DEFVALB = 0x07
+local MCP23017_GPIOA = 0x12
+local MCP23017_GPIOB = 0x13
+--[[
+local MCP23017_IPOLA = 0x02
+local MCP23017_IPOLB = 0x03
+local MCP23017_GPINTENA = 0x04
+local MCP23017_GPINTENB = 0x05
+local MCP23017_DEFVALA = 0x06
+local MCP23017_DEFVALB = 0x07
+local MCP23017_INTCONA = 0x08
+local MCP23017_INTCONB = 0x09
+local MCP23017_IOCON = 0x0A
+local MCP23017_IOCON2 = 0x0B
+local MCP23017_GPPUA = 0x0C
+local MCP23017_GPPUB = 0x0D
+local MCP23017_INTFA = 0x0E
+local MCP23017_INTFB = 0x0F
+local MCP23017_INTCAPA = 0x10
+local MCP23017_INTCAPB = 0x11
+local MCP23017_OLATA = 0x14
+local MCP23017_OLATB = 0x15
+]]
+
+-- metatable
+local mcp23017 = {
+    INPUT = isINPUT,
+    OUTPUT = not isINPUT,
+    GPA = not isGPB,
+    GPB = isGPB,
+    HIGH = isHIGH,
+    LOW = not isHIGH
+}
+mcp23017.__index = mcp23017
+
+-- check device is available on address
+local function checkDevice(address, i2cId)
+    i2c.start(i2cId)
+    local response = i2c.address(i2cId, address, i2c.TRANSMITTER)
+    i2c.stop(i2cId)
+    return response
+end
+
+-- write byte
+local function writeByte(address, i2cId, registerAddr, val)
+    i2c.start(i2cId)
+    i2c.address(i2cId, address, i2c.TRANSMITTER)
+    i2c.write(i2cId, registerAddr)
+    i2c.write(i2cId, val)
+    i2c.stop(i2cId)
+end
+
+-- read byte
+local function readByte(address, i2cId, registerAddr)
+    i2c.start(i2cId)
+    i2c.address(i2cId, address, i2c.TRANSMITTER)
+    i2c.write(i2cId, registerAddr)
+    i2c.stop(i2cId)
+    i2c.start(i2cId)
+    i2c.address(i2cId, address, i2c.RECEIVER)
+    local data = i2c.read(i2cId, 1)
+    i2c.stop(i2cId)
+    return string.byte(data)
+end
+
+-- check pin is in range
+local function checkPinIsInRange(pin)
+    if pin > 7 or pin < 0 then
+        error("MCP23017 the pin must be between 0 and 7")
+    end
+    return pin
+end
+
+local function reset(address, i2cId)
+    writeByte(address, i2cId, MCP23017_IODIRA, 0xFF)
+    writeByte(address, i2cId, MCP23017_IODIRB, 0xFF)
+end
+
+-- setup device
+local function setup(address, i2cId)
+
+    -- check device address (0x20 to 0x27)
+    if (address < 32 or address > 39) then
+        error("MCP23017 address is out of range")
+    end
+
+    if (checkDevice(address, i2cId) ~= true) then
+        error("MCP23017 device on " .. string.format('0x%02X', address) .. " not found")
+    else
+        reset(address, i2cId)
+        return 1
+    end
+end
+
+return function(address, i2cId)
+    local self = setmetatable({}, mcp23017)
+
+    if setup(address, i2cId) then
+        self.writeIODIR = function(sf, bReg, newByte) -- luacheck: no unused
+            writeByte(address, i2cId,
+                bReg == isGPB and MCP23017_IODIRB or MCP23017_IODIRA,
+                newByte)
+        end
+
+        self.writeGPIO = function(sf, bReg, newByte) -- luacheck: no unused
+            writeByte(address, i2cId,
+                bReg == isGPB and MCP23017_GPIOB or MCP23017_GPIOA, newByte)
+        end
+
+        self.readGPIO = function(sf, bReg) -- luacheck: no unused
+            return readByte(address, i2cId, -- upvals
+                bReg == isGPB and MCP23017_GPIOB or MCP23017_GPIOA)
+        end
+
+        -- read pin input
+        self.getPinState = function(sf, bReg, pin) -- luacheck: no unused
+            return issetBit(readByte(address, i2cId,
+                bReg == isGPB and MCP23017_GPIOB or MCP23017_GPIOA),
+                checkPinIsInRange(pin))
+        end
+
+        -- set pin to low or high
+        self.setPin = function(sf, bReg, pin, state) -- luacheck: no unused
+            local inReq = bReg == isGPB and MCP23017_GPIOB or MCP23017_GPIOA
+            local inPin = checkPinIsInRange(pin)
+            local response = readByte(address, i2cId, inReq)
+            writeByte(address, i2cId, inReq,
+                state == isHIGH and setBit(response, inPin) or clearBit(response, inPin))
+            return true
+        end
+
+        -- set mode for a pin
+        self.setMode = function(sf, bReg, pin, mode) -- luacheck: no unused
+            local inReq = bReg == isGPB and MCP23017_IODIRB or MCP23017_IODIRA
+            local inPin = checkPinIsInRange(pin)
+            local response = readByte(address, i2cId, inReq)
+            writeByte(address, i2cId, inReq,
+                mode == isINPUT and setBit(response, inPin) or clearBit(response, inPin))
+            return true
+        end
+
+        -- reset gpio mode
+        self.reset = function(sf) -- luacheck: no unused
+            reset(address, i2cId)
+        end
+
+        -- setup internal pullup
+        self.setInternalPullUp = function(sf, bReg, iByte) -- luacheck: no unused
+            writeByte(address, i2cId,
+                bReg == isGPB and MCP23017_DEFVALB or MCP23017_DEFVALA, iByte)
+        end
+
+        return self
+    end
+    return nil
+end
+
+

+ 1 - 0
mkdocs.yml

@@ -62,6 +62,7 @@ pages:
     - 'liquidcrystal': 'lua-modules/liquidcrystal.md'
     - 'lm92': 'lua-modules/lm92.md'
     - 'mcp23008': 'lua-modules/mcp23008.md'
+    - 'mcp23017': 'lua-modules/mcp23017.md'
     - 'redis': 'lua-modules/redis.md'
     - 'telnet': 'lua-modules/telnet.md'
     - 'yeelink': 'lua-modules/yeelink.md'