Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MCP23017 module refactorings to save some heap #3317

Merged
merged 2 commits into from
Oct 29, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 87 additions & 117 deletions lua_modules/mcp23017/mcp23017.lua
Original file line number Diff line number Diff line change
@@ -1,62 +1,40 @@
--[[
This Lua module provides access to the MCP23017 module.
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).
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.
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 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.
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
]]
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.

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
@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 = {
INPUT = isINPUT,
OUTPUT = not isINPUT,
GPA = not isGPB,
GPB = isGPB,
HIGH = isHIGH,
LOW = not isHIGH
-- convenience parameter enumeration names
INPUT = true,
OUTPUT = false,
GPA = false,
GPB = true,
HIGH = true,
LOW = false
}
mcp23017.__index = mcp23017

Expand Down Expand Up @@ -98,13 +76,65 @@ local function checkPinIsInRange(pin)
return pin
end

local function reset(address, i2cId)
writeByte(address, i2cId, MCP23017_IODIRA, 0xFF)
writeByte(address, i2cId, MCP23017_IODIRB, 0xFF)
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

-- setup device
local function setup(address, i2cId)
-- 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that if the module fails to setup properly (fail in address range check or in checkDevice) it should return nil like in the previous version of the code - without that it's possible to setup module with address out of possible range and that definitely shouldn't return the same thing as correctly initialized module.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I think I am misunderstanding your comment. The version of the code now in dev errors out in these cases; it did not, AFAICT, "return nil" despite appearances: setup never returned false. This patch preserves those behaviors (or, well, is at least meant to; did I break something?).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setup never returned false

I missed it completely! Now I wonder if that should be the case - if the module is not initialised correctly then it shouldn't return the same thing as correctly initialized one. It this a desired behaviour @plomi-net ? I think this can both use error and return nil but that would be up to discussion.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a mistake. Of course, in case of error nil can be returned. I assumed that throwing an error would be enough.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am inclined to think that throwing an error is also enough, yes.

Expand All @@ -113,73 +143,13 @@ local function setup(address, i2cId)

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)
self.address = address
self.i2cId = i2cId

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
self:reset()
return self
end