uci-mcp/lib/mcp23008-17.js

255 lines
7.4 KiB
JavaScript

'use strict'
const Device = require('@uci/i2c').Device,
portpin = require('./port-pin'), // classes for MCP port and pins
_u = require('@uci/utils')
const
Port = portpin.Port,
pinConfigs = portpin.configs, // for export so pinConfigs can be accessed at runtime
pause = _u.pPause
class MCP23008 extends Device {
constructor(busObj, i2cAddress, opts = {}) {
super(busObj, i2cAddress, opts)
// opts could include options passed on to ports and pin including custom pin config, pin ids...see gpio.js
this.chip_config = opts.chip_config // TODO allow opts.chip_config to be a byte instead of a string pointer
this.ports = {}
opts.portID = 'A'
opts.pids = opts.pids ? opts.pids : opts.pidsA
this.ports.A = new Port(opts)
this.ports.A.state = new _u.Byte(opts.stateA)
this.ports.A.interrupt = opts.interruptA ? opts.interruptA : opts.interrupt
} // end constructor
// helpers
pin(id) { return this.ports.A.pin(id) } // get a reference to a particular pin's object
pid(address) { return this.ports.A.pid(address) } // return pin id for a given address on a port
portByPin(id) {
if (this.ports.A.pin(id)) { return 'A' }
return false
}
state(port = 'A') {
return this.ports[port].state.value
}
// get a handle to the ports interrupt
inter(port = 'A') {
return this.ports[port].interrupt
}
// Must call after instantiating.
async init() {
await this.writeChipCfg(this.chip_config) // chip settings
await this.writePinsCfg()
for (let port in this.ports) {
await this.writePort(this.state(port), 'force', port)
}
}
// must call after init if using interrupts
async start() {
// console.log(`starting ${ this.id }`)
for (let port of this.ports) {
// if there are interrupts being used then start them and listeners
if (this.inter(port)) {
await this.interruptReset(port)
await this.inter(port).start()
// bind handler to the chip so handler can read/write to chip/bank when interrupt is emitted
let ihandler = this.inter(port).handler.bind(this)
// inside the listener `this` is the interrupt not the chip/bank
this.inter(port).on('fired', function () {
console.log(`interrupt from ${this.pin_number}`)
ihandler(port)
})
}
}
}
async interruptReset(port = 'A') {
await this.read(portReg(0x08, port))
await pause(100) // give enough time for mcp to reset its interupt
// console.log(`interrupt reset on ${this.id} port ${port}`)
}
async writeChipCfg(cfg = 'default') {
// console.log(`writing mcp chip config ${this.id}`)
let setting = chip_config[cfg]
let byte = _u.byteFormat(setting.val, { in: setting.fmt, out: 'DEC' })
await this.write(chip_config.cmd, byte)
}
// pin configurations should already be set before calling
async writePinsCfg() {
// console.log(`writing mcp pins config ${this.id}`)
for (let port in this.ports) {
for (let setting in registers.pin_config) {
let reg = registers.pin_config[setting]
// TODO 0x10 should be based on chip config
let byte = 0
for (let pin of this.ports[port].pins) {
byte += pin.address * pin.cfg[setting]
// console.log(`port: ${ port } pin: ${pin.id} setting: ${ setting } reg: ${ reg } byte: ${ byte }`)
}
await this.write(portReg(reg, port), byte)
}
}
} // end writePinsCfg
async readPort(port, opts = {}) {
if ('AB'.indexOf(port) === -1) {
opts = port ? port : {}
port = 'A'
}
let cmd = opts.cmd ? opts.cmd : 'gpio'
let result = await this.read(portReg(registers.pin_cmd[cmd], port))
this.ports[port].state.value = result
return opts.format ? this.ports[port].state.toFmt(opts.format) : result
}
async writePort(byte, op, port = 'A') {
// byte MUST be a decimal
// console.log(`current\n ${this.ports[port].state.toFmt('PLC') }`)
// console.log(`byte passed ${op}\n ${ _u.byteFormat(byte, { in: 'DEC', out: 'PLC'})}`)
if (op !== 'force') {
byte = this.ports[port].state.bwOp(byte, op)
}
// console.log('\nbyte to write decimal \n', byte, op, port)
// console.log(`byte to write\n ${ _u.byteFormat(byte, { in: 'DEC', out: 'PLC' }) }`)
await this.write(portReg(registers.pin_cmd.gpio, port), byte)
//update the saved state
this.ports[port].state.value = byte
// console.log('\n\n')
}
async on(pins, port, format) {
if ('AB'.indexOf(port) === -1) {
format = port
port = 'A'
}
if (format) { pins = _u.byteFormat(pins, { in: format, out: 'DEC' }) }
await this.writePort(pins, 'on', port)
}
async allOn(port = 'A') {
let pins = 255
await this.writePort(pins, 'force', port)
}
async off(pins, port, format) {
if ('AB'.indexOf(port) === -1) {
format = port
port = 'A'
}
if (format) { pins = _u.byteFormat(pins, { in: format, out: 'DEC' }) }
await this.writePort(pins, 'off', port)
}
async allOff(port = 'A') {
let pins = 0
await this.writePort(pins, 'force', port)
}
async toggle(pins, port, format) {
if ('AB'.indexOf(port) === -1) {
format = port
port = 'A'
}
if (format) { pins = _u.byteFormat(pins, { in: format, out: 'DEC' }) }
await this.writePort(pins, 'toggle', port)
}
async allToggle(port = 'A') {
let pins = 255
await this.writePort('toggle', port)
}
async force(pins, port, format) {
if ('AB'.indexOf(port) === -1) {
format = port
port = 'A'
}
if (format) { pins = _u.byteFormat(pins, { in: format, out: 'DEC' }) }
await this.writePort(pins, 'force', port)
}
} // end 23008
class MCP23017 extends MCP23008 {
constructor(busObj, i2cAddress, opts) {
super(busObj, i2cAddress, opts)
// add a second port
opts.portID = 'B'
opts.pids = opts.pidsB
this.ports.B = new Port(opts)
this.ports.B.state = new _u.Byte(opts.stateB)
this.ports.B.interrupt = opts.interruptB ? opts.interruptB : opts.interrupt
}
pin(id, port) {
if (!port) {
return this.ports.A.pin(id) ? this.ports.A.pin(id) : this.ports.B.pin(id)
}
return this.ports[port].pin(id)
}
} // end MCP23017 Class
// EXPORTS
module.exports = {
MCP23008,
MCP23017,
pinConfigs
}
// Local Functions and Settings=============================
function portReg(reg, port) {
// TODO what to return for port B will depend on chip configuration
// if not letter assume index 0,1.
if ((port === 'B') || (port === 1)) { return reg += 0x10 } else { return reg }
}
/* ww1.microchip.com/downloads/en/DeviceDoc/21952b.pdf
* or see MCP23017.pdf and MCP 23008.pdf in /docs
* see table 1.5 and details in sections 1.6.x following
* !!! for 23017 MUST initialize with bit 7 "BANK" of IOCON = 1 for the addresses below
* then for all registers add 16 (0x10) to each reg address to talk to PortB pins
* this will make reg addresses be equilvant for 23008 and PortA of 23017
* reg addresses in the config objects are all in Hexidecminal
*/
// Chip Configuration to be used with Register See Page 18 of 23017 doc
let chip_config = {
// byte: ['NULL','INTPOL','ODR','HAEN','DISSLW','SEQOP','MIRROR','BANK'] // see page 18 of 23017 datasheet for 8 setting details
cmd: 0x0A, // IOCON.BANK=0 (msb) at powerup so need to use 0x0A, if set to 1 then use
default: {
val: '10100010', // Split Banks port A + 0x10 = Port B,(ignored by 23008), Sequential operation disabled, active high=pulldown
fmt: 'STR'
},
oneint: {
val: '11100100', // same as default execpt int pins connected
fmt: 'STR'
}
}
let registers = {
pin_config: {
dir: 0,
ivrt: 1,
pullup: 6,
intr: 2,
usedef: 4,
defval: 3,
},
pin_cmd: {
intf: 7, // readonly
intcap: 8, // readonly
gpio: 9, // read/write
olat: 10 // read only
}
}