-
@Gordon Checked with a multimeter, connections all look good, no bridges. Voltage is at ~3.3v on the 2 i2c pins, all the address pins on the PCA9685 are pulled down to ground with a 10k resistor and I'm using address 0x40. I switched out the 10k pullup resistors for 2.7k pullups with no change.
Doing a test with a digital write on each pin - when setting both on/off, both go 3.3v/0v. If I toggle one & not the other, sometimes they bind together, but when they don't I see minor fluctuations on the pin not being set when the other one changes, I'm guessing that'll be the bleed fanoush was talking about.
Is there anything else it makes sense for me to try, or is it time for me to bodge-wire to another 2 pins for i2c on the MDBT42Q? :)
-
-
Just to keep you posted, I flashed the cutting edge build onto the board & ran that code, I'm getting
fffffffe
when I runpeek32(0x1000120c).toString(16)
which is great, but when I try to connect to my i2c device I'm gettingUncaught ALLCALLADR!=0xE0 - PCA9685 connected?
which is progress but not super encouraging.There's definitely a chance its either the soldering on the board or some other issue so I'm going to inspect that again first. I've had a previous revision of this board working although using different pins for i2c. I'll try and rule out any soldering or connection issues before taking too much more of your time :)
-
Just picking this up now, thanks @fanoush& @Gordon for your assistance!
I've updated my board layout to use two other pins rather than 9 & 10 so this shouldn't be a problem in future, but I'm going to flash the cutting edge build & use the code you shared to get this revision working hopefully.
I'm using a pre-baked module from the Espruino store, can I flash a cutting edge build onto it with the ide similar to how I'd update the firmware normally or do I need a a jlink programmer or something more exciting?
Thanks again!
-
-
Hey!
I just wanted to ask if there are any gotchas to know about when using pins D9 & D10 on the MDBT42Q module.
I had a PCB fabricated & I'm trying to use those pins to start i2c communication with another device on the board, but I'm getting some exciting errors.
I noted in the docs it says those pins are used for NFC but I'm not using NFC in my code, could that be causing some weirdness? I'm really hoping the answer isn't "You can't use those pins since they're for NFC"Cheers!
-J
-
Just a quick question on the pinModes/setWatch method - is there a way to set an array of pin modes & watches like how I'm able to read an array of pins with digitalRead? I couldn't find anything about it in the docs, is it something on the roadmap? I'm trying to get rid of any unnecessary loops to get this to run faster and I think It'd be really useful :)
-
Hey! Just wanted to post an update, I had a go at implementing the suggestions yesterday evening & got something working - it catches even fast button presses but occasionally drops them which is something I'm looking into. I've attached some code, its still ugly POC code & I'm sure there are things I can do to make it run more efficiently :)
//======================================================================= // MATRIX //======================================================================= const matrix = (rows, cols) => { let _scanning; let _onStopScanning; let _numberOfIdleScansBeforeStop; let _numberOfActiveKeys; let _numberOfIdleScans; let _keyStates; let _rowStates; let _cols = cols; let _rows = rows; const defaultKeyStates = new Array(_rows.length).fill(new Array(_cols.length).fill(0)); // const resetKeyStates = () => _keyStates = defaultKeyStates; // Make this flexible for different rows/cols const resetKeyStates = () => _keyStates = [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ]; // Make this flexible for different rows/cols const resetRowStates = () => _rowStates = [ 0, 0, 0, 0 ]; const resetNumberOfIdleScans = () => _numberOfIdleScans = 0; const resetNumberOfActiveKeys = () => _numberOfActiveKeys = 0; const getMatrixState = () => _keyStates; const getKeyState = (row, col) => _keyStates[row][col]; const setPinModes = () => { "compiled"; // @TODO: Can we set an array of pinmodes instead of iterating? _rows.forEach(row => pinMode(row, 'output')); _cols.forEach(col => pinMode(col, 'input_pulldown')); }; const unsetPinModes = () => { "compiled"; _rows.forEach(row => pinMode(row, 'input_pulldown')); _cols.forEach(col => pinMode(col, 'output')); }; const setup = () => { "compiled"; resetNumberOfIdleScans(); resetNumberOfActiveKeys(); resetKeyStates(); resetRowStates(); }; const startScanning = (onChange, onStop, numberOfIdleScansBeforeStop, interval) => { "compiled"; console.log("======================"); setPinModes(); _numberOfIdleScansBeforeStop = numberOfIdleScansBeforeStop; _onStopScanning = onStop; if (!_scanning) _scanning = setInterval(() => scanMatrix(onChange), interval || 1); }; const stopScanning = () => { "compiled"; resetNumberOfIdleScans(); resetNumberOfActiveKeys(); resetKeyStates(); resetRowStates(); unsetPinModes(); if (_scanning) { clearInterval(_scanning); _scanning = undefined; } if (_onStopScanning) _onStopScanning(); }; const isScanning = () => !!_scanning; const updateNumberOfIdleScans = (activeKeys, callback) => { "compiled"; if (activeKeys === 0) _numberOfIdleScans = _numberOfIdleScans + 1; callback(); }; const stopOnIdle = () => { "compiled"; if (_numberOfIdleScansBeforeStop && (_numberOfIdleScans >= _numberOfIdleScansBeforeStop)) stopScanning(); }; const updateNumberOfActiveKeys = (isActive) => { "compiled"; if (isActive) {_numberOfActiveKeys = _numberOfActiveKeys + 1;} else if (_numberOfActiveKeys > 0) {_numberOfActiveKeys = _numberOfActiveKeys - 1;} }; const parseRowToString = (row) => { "compiled"; return row.toString(2); }; const detectChange = (rowState, currentRowIndex, onChange) => { "compiled"; if (rowState !== _rowStates[currentRowIndex]) { _rowStates[currentRowIndex] = rowState; const parsedRow = parseRowToString(rowState); // @TODO: This feels inefficient for (let colIndex = 0; colIndex < parsedRow.length; colIndex++) { const col = Number(parsedRow[colIndex]); if (col !== _keyStates[currentRowIndex][colIndex]) { _keyStates[currentRowIndex][colIndex] = col; onChange(currentRowIndex, colIndex, col); updateNumberOfActiveKeys(col); } } } }; const scanMatrix = (onChange) => { "compiled"; _rows.forEach((currentRow, currentRowIndex) => { digitalWrite(currentRow, 1); detectChange(digitalRead(_cols), currentRowIndex, onChange); digitalWrite(currentRow, 0); }); updateNumberOfIdleScans(_numberOfActiveKeys, stopOnIdle); }; setup(); return { getMatrixState: getMatrixState, getKeyState: getKeyState, startScanning: startScanning, stopScanning: stopScanning, isScanning: isScanning }; }; //======================================================================= // USAGE //======================================================================= const ROWS = [D14, D15, D17, D30]; const COLS = [D16, D18, D19, D20, D22, D25, D26, D27, D28, D29, D31]; let _rowWatchers; const myMatrix = matrix(ROWS, COLS); const onMatrixChange = (currentRow, currentCol, isActive) => { // Keep track of key press/release duration // Classify as tap or hold // keyPress when a key is pressed, payload containing key row/column // keyRelease when a key is released, payload containing row/column, duration of press // keyTap when a key press/release cycle is < 200ms, payload containing row/column // keyHoldStart when a key has been pressed for > 200ms, payload containing row/column // keyHoldEnd fired when held key is released, payload containing row/column, duration of press console.log(currentRow, currentCol, isActive); }; const onMatrixStop = () => { "compiled"; enable(); }; const handleWatchTrigger = (e) => { "compiled"; unsetWatches(_rowWatchers); setColsLow(COLS); myMatrix.startScanning(onMatrixChange, onMatrixStop, 2, 1); }; const setColsHigh = (cols) => { "compiled"; cols.forEach(col => digitalWrite(col, 1)); }; const setColsLow = (cols) => { "compiled"; cols.forEach(col => digitalWrite(col, 0)); }; const setWatches = (rows) => { "compiled"; _rowWatchers = rows.map(row => setWatch((e) => handleWatchTrigger(e), row, { repeat:false, edge:'rising', debounce: 10 })); }; // @TODO: Make this more resilient, this errors when it can't clear a watch const unsetWatches = (rowWatchers) => { "compiled"; rowWatchers.forEach(rowWatcher => clearWatch(rowWatcher)); }; const enable = () => { "compiled"; ROWS.forEach(row => pinMode(row, 'input_pulldown')); COLS.forEach(col => pinMode(col, 'output')); setColsHigh(COLS); setWatches(ROWS); }; enable();
-
Thanks, I'm looking forward to getting it working :)
I'm going to give the compiled JS a shot today hopefully! You're right that I wouldn't need the MCP chip to scan a 4x16 matrix, originally I wanted to do it that way for future expandability but the MDBT42Q has 32 GPIO pins, so that gives me the headroom to do a full size keyboard (6x22) with some extra GPIO to spare to do things like LED backlighting/dedicated buttons
So, set up watches on my columns, only scan when a watch gets triggered to stop us burning processor cycles at idle, and do the scanning in compiled JS or inline C to get a speed boost - that sounds like enough for me to get going with and see how far I get :)
Thanks for helping me out on this one, I might go quiet for a couple of days while I try this out but I'll post and update to let you know how its working out!
-
Thats a great suggestion, it sounds so obvious now you've said it :) I'll definitely be handling >1 press but that still takes a lot of load off the espruino, if nothing else it'll probably save me some power I would've burned sequentially turning pins on and off.
I'm guessing I'll still want to do some messing around doing the scanning with the low level NRF52 lib to offload that to a peripheral rather than having the core do it. I'll likely still want to do processing on the core while keys are pressed/held to send keycodes with modifiers and things, but if I only have to do that after a pin state change thats great.
My end goal is to build a mechanical keyboard firmware lib that handles the hardware level stuff & does a bit of extra helpful stuff like classify press/release cycles as a 'tap' or a 'hold' without the user having to do that, provides a lightweight mechanism for defining keymaps as a JSON file, and lets the user write their own javascript functions to make their keyboard do interesting stuff. This already exists in proper embedded code (QMK) but I'd love to do something similar on espruino since Javascript is so much more accessible :)
-
Thought I'd give the Espruino Jobs board a go to see if someone can give me a hand with a project I'm working on :)
I'm looking for a dev to write some code to scan a 4x16 matrix of keys using the Espruino MDBT42Q and an MCP23017 port expander chip.
The code should be split into a hardware handling module (communication with the MCP chip/driving the matrix/detecting state changes), and a metadata module (keeping track of if keys were pressed or released & how long they were pressed for, determining if they key was 'tapped' or 'held') which fires events.
The hardware handling module should:
- Drive the matrix row outputs sequentially from MDBT42Q GPIO pins
- Detect matrix column inputs from the MCP23017 (ideally using IRQs to handle state changes)
- Be efficient, keeping as much off the main Espruino thread as possible (likely using NRF52 Programmable Peripheral Interconnect events/tasks)
- Handle switch debouncing
- Only fire a key press or release event when a key state has changed, not on every matrix scan
- Emit hardwareKeyPress/hardwareKeyRelease events with the row & column in the data payload
- Expose a method to get the current state of a single key
- Expose a method to get the current state of all keys
The metadata module should:
- Keep track of when a key was pressed/released
- Keep track of how long a key was pressed for
- Figure out if a key press was a 'tap' or a 'hold', based on a configurable timeout (key press/release cycles below 200ms classed as a 'tap', above 200ms classed as a 'hold')
- Fire events for all of the above:
- keyPress when a key is pressed, payload containing key row/column
- keyRelease when a key is released, payload containing row/column, duration of press
- keyTap when a key press/release cycle is < 200ms, payload containing row/column
- keyHold when a a key press/release is > 200ms, payload containing row/column
- keyHoldStart when a key has been pressed for > 200ms, payload containing row/column
- keyHoldEnd fired when held key is released, payload containing row/column, duration of press
- keyPress when a key is pressed, payload containing key row/column
Nice-to-haves:
- Code should have unit tests, but I know that can be a little tricky with Espruino so I'm happy to drop this
- Code should be versioned in git if possible, but not essential
- Code should be reasonably 'tidy' and readable, split into multiple files if needed
- Code should have at least basic documentation so I can modify it in future, or alternatively a quick handover so I have a good understanding of your approach
I'm technical with a lot of commercial experience in Javascript & a decent amount with embedded code, but I'm super busy right now and some of this code is a little above my experience level (especially the NRF52 PPI). I can work with you to get this done or leave you to do it solo, whichever you prefer, and I'm pretty flexible on the requirements & implementation - if anything sounds crazy I'm open to a different approach :)
Reference:
Primer on keyboard matrix scanning: https://summivox.wordpress.com/2016/06/03/keyboard-matrix-scanning-and-debouncing/
Espruino NRF52 Programmable Peripheral Interconnect library: https://www.espruino.com/NRF52LL
Espruino MCP23017 library: https://www.espruino.com/MCP23xxx
Untested MCP23017 library with interrupt handling: http://forum.espruino.com/comments/14047043/ - Drive the matrix row outputs sequentially from MDBT42Q GPIO pins
-
Hi there Gordon! I wanted to ask your advice on the best approach to take writing some keyboard matrix scanning code.
I had a go at this a couple of years ago using an MCP23017, clocking the row outputs sequentially from the espruino & reading the column inputs on the MCP each cycle to determine which keys were pressed. This worked okay but I had a couple of weird quirks with the MCP chip & ended up burning way too many espruino cycles just turning the row lines on and off - not the best approach!
I'm taking another crack at it, this time using an MDBT42Q instead of a Pico, I think using the NRF52's PPI to keep as much off the processor as possible is the right approach. I was thinking of using the NRF52's GPIO pins to drive the rows sequentially again, then listening for an interrupt from the MCP to know when to read its buffer to get the column states, and if anything has changed send it back up to the processor properly to send a keycode. I wanted to ask if that sounds sensible and, if so, which events and tasks it would make sense for me to set up.
I appreciate that's a reeeeaallly long question, but I'd love a nudge in the right direction to get me going :)
-
Tried the cutting-edge build but still seeing a tiny bit of a flicker, but I'm happy with that being an inefficiency in my code rather than a problem with PWM on the MDBT42Q. I appreciate you getting a build out so fast, and I'm totally down with sharing my code if you wanted to try and reproduce what I was seeing :)
I dropped in a PCA9685 to handle the PWM control of my LED strips to free up the MDBT42Q and all is looking great now
-
I think my issue might've been with doing too many PWMs at once - I'm doing a very slow fade by PWMing 10 LED strips through a bank of mosfets (MDBT42Q pins D22, D20, D19, D18, D17, D25, D26, D27, D28, D29), if I do more than 3 with:
analogWrite(<PIN>, 0.1, {freq:100,soft:false} );
I get an Uncaught Error: "No free Hardware PWMs. Try not specifying a frequency, or using analogWrite(pin, val, {soft:true}) for Software PWM"
If I try forcing software PWM, if I try with more than 7 outputs like this:
analogWrite(<PIN>, 0.1, {freq:100,forceSoft:true} );
I get an Uncaught InternalError: Timeout on Utility Timer
however, just doing an analog write without an options object for all of the pins allows me to write to all of them but with a bit of a flicker to it. I'm running the LED strips with a 24v supply & I'm doing low-side switching with a bank of mosfets, but I'm powering the MDBT42Q with a separate 5v power supply with a common ground reference, so I guess there's also chance that theres some noise between my supplies.
I'm going to do a bit of digging and see if I can narrow down where the slight flicker is coming from, hopefully its just my code running a little inefficiently or a power supply thing :)
Thanks for getting back to me so quick, apologies for being slower to reply!
-
Hey there!
Just wanted to post a message about some funkiness I noticed with analogWrites on the MDBT42Q - there's a pretty solid chance I'm just misunderstanding some of the docs so apologies in advance if thats the case!
Doing an analogWrite with just the pin & value (between 0 & 1) works fine, albeit with some flickering when trying to update the value every 100ms but likely because its software PWM, so that comes with the territory.
The weirdness I noticed was with some of the options:
If I've already set the pinMode to "output", passing an options object to analogWrite ignores the value and sets the pin to 100% on, regardless of whats in it
If I've not already set the pinMode, passing an empty options object, or an options object containing forceSoft: false or soft: true/false to analogWrite ignores the value and sets the pin to 100% on.
If I've not already set the pinMode, passing an options object just containing forceSoft: true to analogWrite allows me to set the value to either 0 or 1, any number in between is interpreted as a 0
I'm 50/50 on if I've just badly misinterpreted what the options object is for, but I wanted to ask just in case this isn't expected behavior :)
-
Damn, thought we had this sorted but I've come across some other weirdness -_- First off, thanks again for all your help, I know everyone here is busy so I appreciate the time. Second, I've come across a couple of issues interfacing with two MCP23017s, one on address 0b0000101 and one on address 0b0000111, I'll list them below:
1) Address 0b0000000 fails to connect for some reason.
2) The 'writePort()' command doesn't seem to work, I've tested it with multiple values (0, 1, 0b0000000000000000, 0b1111111111111111) to no effect.
3) Port A on address 0b0000101 fails to read and write, tested with two chips in multiple places on the breadboard.
4) Writing to pins on the expanders intermittently fails for long periods, then suddenly starts working again after messing about with code and not moving the physical breadboard.
5) When reading/writing to the chips at sub-500ms intervals, after a short time the I2C bus packs up and gives a 'Uncaught InternalError: Timeout on I2C Write Transmit Mode 2' error. The bus occasionally recovers and begins re-scanning after a couple of minutes but usually stays down.I'm almost certain there's something underpinning all of this weirdness, but it feels like I'm working in the dark in terms of debugging - things seem to be happening at random
Cheers
-JBL
-
-
Definitely 23017 not 23S17, I checked that last night before posting to make sure I'd covered as many potentially silly issues as possible :).
Tried the software I2C trick, changed from I2C2 to new I2C() and everything worked perfectly. Then I tried changing back to hardware I2C and it worked perfectly too, so then I tried power-cycling and trying hardware I2C and it still works - I can't really explain what fixed the issue, perhaps establishing a solid software I2C link kicked something in the MCP chip and got it in a good state, but it's working now.
I'll keep an eye on it and monitor for any instability, thanks for your help :)
-
Updating to 1v95 is a good shout, as is setting the address to 0 instead of 0x20, I've tried setting the address to 0 (along with 1 to 7 for make sure I'm not on the wrong address) but I'm still getting the same error unfortunately. I've just updated to 1v95 this moment and re-tested and I'm getting the same issue, full code is below, it's pulled almost verbatim from the example:
Code:
var i2c=I2C2; i2c.setup({scl: B10, sda: B3}); var address = 0; var RST=A8; var port=require("MCP23017").connect(i2c,RST,address); port.A0.mode('input_pullup'); console.log(port.A0.read());
Error:
Uncaught InternalError: Timeout on I2C Write Transmit Mode 2 at line 1 col 42 this.i2c.writeTo(this.i2ca,c,[a&255,a>>8]) ^ in function "s" called from line 1 col 128 ...ut"==a?this.n&=~c:this.n|=c);this.s(12,"input_pullup"==a?thi... ^ in function "m" called from line 1 col 18 this.p.m(this.b,a) ^ in function "mode" called from line 1 col 28 port.A0.mode('input_pullup'); ^ Uncaught InternalError: Timeout on I2C Write Transmit Mode 2 at line 1 col 29 this.i2c.writeTo(this.i2ca,c);return this.i2c.readFrom(this.... ^ in function "r" called from line 1 col 53 ...8:this.b;return this.p.r(18)[255<this.b?1:0]&a?1:0 ^ in function "read" called from line 1 col 26 console.log(port.A0.read());
I've also attached a potato-quality photo of my wiring, note the reset pin is not connected - I've tried it both with and without, each result in the same error
-
Hi there!
I'm trying to interface and Espruino Pico with an MCP23017 and I'm running into a little trouble with the I2C communication (I2C2, pins B10 & B3, MCP device address 0x20)
The error I'm getting is as follows:
Uncaught InternalError: Timeout on I2C Write Transmit Mode 2 at line 1 col 43 {this.i2c.writeTo(this.i2ca,c,[a&255,a>>8])}
I've tried manually reading data from the MCP chip and that works as far as I can tell, my original assumption was incorrect wiring but I've been over half a dozen times and everything looks sound, 10k pullup resistors included on the i2c lines etc, and I can read data from the chip.
The problem only seems to occur when something tries to write to the chip, I've tested two different chips both giving the same error, does anyone think they could lend me a hand with this? It'll likely be something forehead-slappingly simple I've overlooked but on the off chance it isn't, I figured I'd post here
Cheers!
-Josh
@Gordon @fanoush thanks for your help again, I'll give those suggestions a try just to answer this if anyone else ever has the same issue :) I've soldered some bodge wires to use i2c on D7 & D8 and all is working perfectly, and I've updated my board design to use those pins to, so all it good now, but I really appreciate the assistance