UNPKG

mcp23017-promise

Version:

MCP23017 library with promises.

396 lines (361 loc) 12.7 kB
import { BusInterface, Device, DeviceInterface } from "async-i2c-bus"; export const A0 = 1 << 0; export const A1 = 1 << 1; export const A2 = 1 << 2; export const A3 = 1 << 3; export const A4 = 1 << 4; export const A5 = 1 << 5; export const A6 = 1 << 6; export const A7 = 1 << 7; export const B0 = 1 << 8; export const B1 = 1 << 9; export const B2 = 1 << 10; export const B3 = 1 << 11; export const B4 = 1 << 12; export const B5 = 1 << 13; export const B6 = 1 << 14; export const B7 = 1 << 15; const PINS_A = 0xFF; const PINS_B = 0xFF00; /** Direction of the I/O pins */ export enum Direction { /** Output pin */ OUT, /** Input pin without pull-up */ IN, /** Input pin with pull-up */ IN_UP, } /** Interupt mode */ export enum InteruptMode { /** No interupt (used to reset) */ INT_None, /** Interupt when not one */ INT_One, /** Interupt when not zero */ INT_Zero, /** Interupt on change */ INT_Change, } /** An interupt definition */ export interface InteruptDefinition { /** Pin number */ pin: number; /** Interupt mode of the pin */ mode: InteruptMode; /** Callback if any */ callback?: (val: number) => void; } /** Interupt handler to use as an abstract type */ export interface InteruptHandler { /** Map from pin number to callback */ [pin: number]: (val: number) => void; } /** Pin value */ export enum Level { /** Low or 0 */ LOW = 0, /** High or 1 */ HIGH, } /** Options for the MCP23017 object */ export interface IMCP23017Options { /** Address of the chip on the I2C bus */ address?: number; /** Use of the interleaved (default) or separate bank mode */ separate?: boolean; } interface IRegisterPair { A: number; B: number; } interface IRegisterConfiguration { IODIR: IRegisterPair; IPOL: IRegisterPair; GPINTEN: IRegisterPair; DEFVAL: IRegisterPair; INTCON: IRegisterPair; GPPU: IRegisterPair; INTF: IRegisterPair; INTCAP: IRegisterPair; GPIO: IRegisterPair; OLAT: IRegisterPair; IOCON: number; } export const IOCON_INTPOL = 1 << 1; export const IOCON_ODR = 1 << 2; export const IOCON_HAEN = 1 << 3; export const IOCON_DISSLW = 1 << 4; export const IOCON_SEQOP = 1 << 5; export const IOCON_MIRROR = 1 << 6; export const IOCON_BANK = 1 << 7; const bankSeparate: IRegisterConfiguration = { DEFVAL: { A: 0x03, B: 0x13 }, GPINTEN: { A: 0x02, B: 0x12 }, GPIO: { A: 0x09, B: 0x19 }, GPPU: { A: 0x06, B: 0x16 }, INTCAP: { A: 0x08, B: 0x18 }, INTCON: { A: 0x04, B: 0x14 }, INTF: { A: 0x07, B: 0x17 }, IOCON: 0x05, IODIR: { A: 0x00, B: 0x10 }, IPOL: { A: 0x01, B: 0x11 }, OLAT: { A: 0x0A, B: 0x1A }, }; const bankInterleaved: IRegisterConfiguration = { DEFVAL: { A: 0x06, B: 0x07 }, GPINTEN: { A: 0x04, B: 0x05 }, GPIO: { A: 0x12, B: 0x13 }, GPPU: { A: 0x0C, B: 0x0D }, INTCAP: { A: 0x10, B: 0x11 }, INTCON: { A: 0x08, B: 0x09 }, INTF: { A: 0x0E, B: 0x0F }, IOCON: 0x0A, IODIR: { A: 0x00, B: 0x01 }, IPOL: { A: 0x02, B: 0x03 }, OLAT: { A: 0x14, B: 0x15 }, }; interface IntState { inten: number; intcon: number; defval: number; handler: InteruptHandler; } /** Options for the driver */ export interface Options { /** Address of the MCP (0x20 if not provided) */ address?: number; /** Should the two banks be seaparate or interleaved (default false) */ separate?: boolean; } /** This class drives a single MCP23017 GPIO extender */ export class MCP23017 { private device: DeviceInterface; private separate: boolean; private bank: IRegisterConfiguration; private cache: Buffer; /** Constructor. * * @param bus an instance of i2c bus. May be closed at this point * @param options options for the driver. */ constructor(bus: BusInterface, options: Options = {}) { this.device = Device({bus, address: options.address || 0x20 }); this.separate = options.separate || false; this.bank = this.separate ? bankSeparate : bankInterleaved; this.cache = Buffer.allocUnsafe(0); } /** Initialize the class * * Must be called with an open bus */ public async init(): Promise<void> { this.cache = await this.initCache(); } /** Reads the configuration byte of the chip * @return the value of the configuration as a byte in a promise. */ public readConfigByte(): Promise<number> { return this.device.readByte(this.bank.IOCON); } /** Change the way register banks are organize. * @param separate set to true for registers organized as two * independent banks and false for interleaved (default) * @return a promisse fullfilled when the operation is performed. */ public async setSeparate(separate: boolean): Promise<void> { const iocon = this.bank.IOCON; if (this.separate !== separate) { this.separate = separate; this.bank = this.separate ? bankSeparate : bankInterleaved; const ioconVal = await this.device.readByte(iocon); await this.writeByte(iocon, ioconVal ^ IOCON_BANK); this.cache = await this.initCache(); } } /** Sets the direction of a set of GPI/O pins and eventually pull-up status. * @param pins: the set of pins (number used as a bit set) * @param direction: the direction of the pin (can be IN OUT or IN_UP) * @return a promisse fullfilled when the operation is performed. */ public async setDirection( pins: number, direction: Direction, ): Promise<void> { const iodir = this.readCache(this.bank.IODIR); const newIodir = direction === Direction.OUT ? iodir & ~pins : iodir | pins; const changesIodir = newIodir ^ iodir; await this.writeBanks(newIodir, changesIodir, this.bank.IODIR); if (direction !== Direction.OUT) { const pullup = this.readCache(this.bank.GPPU); const newPullup = ( direction === Direction.IN_UP ? pullup | pins : pullup & ~pins); const changesPullup = pullup ^ newPullup; await this.writeBanks(newPullup, changesPullup, this.bank.GPPU); } } /** Set the polarity of the output (ie XOR with it) * @param polarity of each pin as a bit array */ public async setPolarity(pins: number): Promise<void> { const ipol = this.readCache(this.bank.IPOL); const changes = ipol ^ pins; await this.writeBanks(pins, changes, this.bank.IPOL); } /** Reads the GPIO * @return the value of the GPIO Pin in a promise. */ public async read(): Promise<number> { return await this.readBanks(true, true, this.bank.GPIO); } /** Modify the GPIO * @param set: bits to set as a bit field * @param unset: bits to set as a bit field */ public async write(set: number, unset: number): Promise<void> { const gpio = this.readCache(this.bank.OLAT); const newGpio = (gpio | set) & ~unset; const changesGpio = gpio ^ newGpio; await this.writeBanks(newGpio, changesGpio, this.bank.OLAT); } /** Register changes to the interupt handling * @param intDefs: a list of interupt handling description that * will be (dis)activated * @param handler: an optional interupt handler that was in place * @return the new interupt handler. */ public async register( intDefs: InteruptDefinition[], handler: InteruptHandler = {}): Promise<InteruptHandler> { function addDef( intState: IntState, intDef: InteruptDefinition, ): IntState { switch (intDef.mode) { case InteruptMode.INT_None: intState.inten &= ~intDef.pin; delete intState.handler[intDef.pin]; break; case InteruptMode.INT_Change: intState.inten |= intDef.pin; intState.intcon &= ~intDef.pin; if (intDef.callback) { intState.handler[intDef.pin] = intDef.callback; } break; case InteruptMode.INT_Zero: intState.inten |= intDef.pin; intState.intcon |= intDef.pin; intState.defval &= ~intDef.pin; if (intDef.callback) { intState.handler[intDef.pin] = intDef.callback; } break; case InteruptMode.INT_One: intState.inten |= intDef.pin; intState.intcon |= intDef.pin; intState.defval |= intDef.pin; if (intDef.callback) { intState.handler[intDef.pin] = intDef.callback; } break; } return intState; } const inten = this.readCache(this.bank.GPINTEN); const intcon = this.readCache(this.bank.INTCON); const defval = this.readCache(this.bank.DEFVAL); const state = intDefs.reduce(addDef, {inten, intcon, defval, handler}); await this.writeBanks( state.defval, defval ^ state.defval, this.bank.DEFVAL); await this.writeBanks( state.defval, intcon ^ state.intcon, this.bank.INTCON); await this.writeBanks( state.defval, inten ^ state.inten, this.bank.GPINTEN); return state.handler; } /** Handle interupts received. * @param handler: an interupt handler built during the registration. */ public async handle(handler: InteruptHandler): Promise<void> { const intf = await this.readBanks(true, true, this.bank.INTF); const intcap = await this.readBanks(true, true, this.bank.INTCAP); let mask = 1; for (let pin = 0; pin < 16; pin++) { if (mask & intf) { const callback = handler[pin]; if (callback) { callback(mask & intcap ? Level.HIGH : Level.LOW); } } mask <<= 1; } } private readCache(regs: IRegisterPair): number { return this.cache[regs.A] | (this.cache[regs.B] << 8); } private async initCache(): Promise<Buffer> { const buffer = Buffer.alloc(0x1B); if (this.separate) { const bufA = await this.readBuffer(0, 0xA); const bufB = await this.readBuffer(0x10, 0xA); bufA.copy(buffer, 0); bufB.copy(buffer, 0x10); } else { const buf = await this.readBuffer(0, 0x16); buf.copy(buffer, 0); } return buffer; } private async readBuffer(register: number, len: number): Promise<Buffer> { const buffer = Buffer.alloc(len); const lenRes = await this.device.readI2cBlock(register, len, buffer); if (lenRes !== len) { throw new Error("Bad length"); } return buffer; } private async writeByte(register: number, byte: number): Promise<void> { await this.device.writeByte(register, byte); this.cache[register] = byte; } private async writeWord(register: number, word: number): Promise<void> { await this.device.writeWord(register, word); this.cache[register] = word & 0xff; this.cache[register + 1] = (word >> 8) & 0xff; } private async writeBanks( pins: number, changes: number, regs: IRegisterPair): Promise<void> { const needsA = (changes & PINS_A) !== 0; const needsB = (changes & PINS_B) !== 0; if (this.separate) { if (needsA) { await this.writeByte(regs.A, pins & PINS_A); } if (needsB) { await this.writeByte(regs.B, (pins & PINS_B) >> 8); } } else { await this.writeWord(regs.A, pins & 0xFFFF); } } private async readBanks( needsA: boolean, needsB: boolean, regs: IRegisterPair): Promise<number> { if (this.separate) { let valA = 0; if (needsA) { valA = await this.device.readByte(regs.A); } let valB = 0; if (needsB) { valB = await this.device.readByte(regs.B); } return valA | (valB << 8); } else { return await this.device.readWord(regs.A); } } }