Add mcp23017 Lua module (#3197)

This commit is contained in:
Marcel P 2020-10-25 12:48:34 +01:00 committed by GitHub
parent f8baf63a73
commit ef353809eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 496 additions and 0 deletions

View File

@ -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))
```

View File

@ -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)

View File

@ -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.

View File

@ -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

View File

@ -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'