156 lines
4.5 KiB
Lua
156 lines
4.5 KiB
Lua
|
--[[
|
||
|
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.
|
||
|
|
||
|
For register name <-> number mapping, see the MCP23017 data sheet, table
|
||
|
3-1 on page 12 (as of Revision C, July 2016). This module uses the
|
||
|
IOCON.BANK=0 numbering (the default) throughout.
|
||
|
|
||
|
@author Marcel P. | Plomi.net
|
||
|
@github https://github.com/plomi-net
|
||
|
@version 1.0.0
|
||
|
]]
|
||
|
|
||
|
local i2c, string, issetBit, setBit, clearBit =
|
||
|
i2c, string, bit.isset, bit.set, bit.clear
|
||
|
|
||
|
-- metatable
|
||
|
local mcp23017 = {
|
||
|
-- convenience parameter enumeration names
|
||
|
INPUT = true,
|
||
|
OUTPUT = false,
|
||
|
GPA = false,
|
||
|
GPB = true,
|
||
|
HIGH = true,
|
||
|
LOW = false
|
||
|
}
|
||
|
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
|
||
|
|
||
|
function mcp23017:writeIODIR(bReg, newByte)
|
||
|
writeByte(self.address, self.i2cId,
|
||
|
bReg and 0x1 --[[IODIRB register]] or 0x0 --[[IODIRA register]], newByte)
|
||
|
end
|
||
|
|
||
|
function mcp23017:writeGPIO(bReg, newByte)
|
||
|
writeByte(self.address, self.i2cId,
|
||
|
bReg and 0x13 --[[GPIOB register]] or 0x12 --[[GPIOA register]], newByte)
|
||
|
end
|
||
|
|
||
|
function mcp23017:readGPIO(bReg)
|
||
|
return readByte(self.address, self.i2cId,
|
||
|
bReg and 0x13 --[[GPIOB register]] or 0x12 --[[GPIOA register]])
|
||
|
end
|
||
|
|
||
|
-- read pin input
|
||
|
function mcp23017:getPinState(bReg, pin)
|
||
|
return issetBit(readByte(self.address, self.i2cId,
|
||
|
bReg and 0x13 --[[GPIOB register]] or 0x12 --[[GPIOA register]]),
|
||
|
checkPinIsInRange(pin))
|
||
|
end
|
||
|
|
||
|
-- set pin to low or high
|
||
|
function mcp23017:setPin(bReg, pin, state)
|
||
|
local a, i = self.address, self.i2cId
|
||
|
local inReq = bReg and 0x13 --[[GPIOB register]] or 0x12 --[[GPIOA register]]
|
||
|
local inPin = checkPinIsInRange(pin)
|
||
|
local response = readByte(a, i, inReq)
|
||
|
writeByte(a, i, inReq,
|
||
|
state and setBit(response, inPin) or clearBit(response, inPin))
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
-- set mode for a pin
|
||
|
function mcp23017:setMode(bReg, pin, mode)
|
||
|
local a, i = self.address, self.i2cId
|
||
|
local inReq = bReg and 0x1 --[[IODIRB register]] or 0x0 --[[IODIRA register]]
|
||
|
local inPin = checkPinIsInRange(pin)
|
||
|
local response = readByte(a, i, inReq)
|
||
|
writeByte(a, i, inReq,
|
||
|
mode and setBit(response, inPin) or clearBit(response, inPin))
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
-- reset gpio mode
|
||
|
function mcp23017:reset()
|
||
|
local a, i = self.address, self.i2cId
|
||
|
writeByte(a, i, 0x0 --[[IODIRA register]], 0xFF)
|
||
|
writeByte(a, i, 0x1 --[[IODIRB register]], 0xFF)
|
||
|
end
|
||
|
|
||
|
-- setup internal pullup
|
||
|
function mcp23017:setInternalPullUp(bReg, iByte)
|
||
|
writeByte(self.address, self.i2cId,
|
||
|
bReg and 0x7 --[[DEFVALB register]] or 0x6 --[[DEFVALA register]], iByte)
|
||
|
end
|
||
|
|
||
|
return function(address, i2cId)
|
||
|
local self = setmetatable({}, mcp23017)
|
||
|
|
||
|
-- 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")
|
||
|
end
|
||
|
|
||
|
self.address = address
|
||
|
self.i2cId = i2cId
|
||
|
|
||
|
self:reset()
|
||
|
return self
|
||
|
end
|
||
|
|
||
|
|