nesper
nesper copied to clipboard
Can't read from I2C
I'm trying to use a peripheral from Adafruit which communicates over I2C. The same setup works fine with their Arduino library. I copied the protocol from there, and I can't see anything wrong with my implementation, but it doesn't work via Nesper 🤷♂️ I've tried messing with the clock speed, different pins, different pullups, etc. I can tell there's some communication happening, because any other address hangs the system (wish I had an oscilloscope), but all I get is 0xFF
back for every byte for every register I try.
Any ideas? Am I doing something wrong?
import nesper/timers
import nesper/i2cs
import nesper
#
const
TAG*: cstring = "input"
SCL_PIN = gpio_num_t(22)
SDA_PIN = gpio_num_t(23)
SEESAW_ADC_BASE = 0x09'u8
SEESAW_GPIO_BASE = 0x01'u8
SEESAW_ADC_CHANNEL_OFFSET = 0x07'u8
SEESAW_GPIO_DIRSET_BULK = 0x02'u8
SEESAW_GPIO_DIRCLR_BULK = 0x03'u8
SEESAW_GPIO_BULK = 0x04'u8
SEESAW_GPIO_BULK_SET = 0x05'u8
SEESAW_GPIO_BULK_CLR = 0x06'u8
SEESAW_GPIO_BULK_TOGGLE = 0x07'u8
SEESAW_GPIO_INTENSET = 0x08'u8
SEESAW_GPIO_INTENCLR = 0x09'u8
SEESAW_GPIO_INTFLAG = 0x0A'u8
SEESAW_GPIO_PULLENSET = 0x0B'u8
SEESAW_GPIO_PULLENCLR = 0x0C'u8
BUTTON_RIGHT= 6
BUTTON_DOWN = 7
BUTTON_LEFT = 9
BUTTON_UP = 10
BUTTON_SEL = 14
button_mask: uint32 = (1 shl BUTTON_RIGHT) or (1 shl BUTTON_DOWN) or
(1 shl BUTTON_LEFT) or (1 shl BUTTON_UP) or (1 shl BUTTON_SEL)
let port1 = newI2CMaster(port = I2C_NUM_0,
sda_io_num = SDA_PIN, ## !< GPIO number for I2C sda signal
scl_io_num = SCL_PIN, ## !< GPIO number for I2C scl signal
clk_speed = 100_000.Hertz,
sda_pullup_en = false, ## !< Internal GPIO pull mode for I2C sda signal
scl_pullup_en = false, ## !< Internal GPIO pull mode for I2C scl signal
intr_alloc_flags = {})
proc seesawWrite(bs: seq[uint8]) =
var cmd = port1.newCmd()
cmd.start()
cmd.writeTo(0x49)
for b in bs:
cmd.writeByte(b)
cmd.stop()
port1.submit(cmd, 10.Millis)
proc seesawRead(bs: seq[uint8], size: uint): seq[uint8] =
result = newSeq[uint8](size)
seesawWrite(bs)
delayMillis(1)
var readCmd = port1.newCmd()
readCmd.start()
readCmd.readFrom(0x49)
readCmd.read(result, ACK)
readCmd.stop()
port1.submit(readCmd, 10.Millis)
proc initInput*() =
let maskcmd = @[uint8(button_mask shr 24), uint8(button_mask shr 16),
uint8(button_mask shr 8), uint8(button_mask and 0xFF)]
seesawWrite(@[SEESAW_GPIO_BASE, SEESAW_GPIO_DIRCLR_BULK] & maskCmd)
seesawWrite(@[SEESAW_GPIO_BASE, SEESAW_GPIO_PULLENSET] & maskCmd)
seesawWrite(@[SEESAW_GPIO_BASE, SEESAW_GPIO_BULK_SET] & maskCmd)
seesawWrite(@[SEESAW_GPIO_BASE, SEESAW_GPIO_INTENSET] & maskCmd)
proc printButtons*() =
echo(seesawRead(@[SEESAW_GPIO_BASE, SEESAW_GPIO_BULK], 8))
echo(seesawRead(@[SEESAW_ADC_BASE, SEESAW_ADC_CHANNEL_OFFSET], 2))
The esp-lib
has given me lots of headaches with I2C. It looks like you have sda_pullup_en
and scl_pullup_en
disabled. NVM, you said you tried out different configurations.
There's a few things to try. I had a lot of difficulty with I2C on esp-lib
which is what Nesper wraps around. AFAICT, it's with the esp-lib
itself and not the Nesper wrappers. The Arduino libraries define a pretty sane set of defaults for dealing with NACK
and ACK
's while the esp-lib
version are much lower level.
I actually moved to using esp-idf-lib
I2C drivers in my latest Nesper projects because it was more reliable. They wrap the ACK
/NACK
's for you and it works for most proper I2C devices. You can see an example on the devel
branch here.
- the clock speed is set to
100_000.Hertz
which is a bit slow for some devices, they sometimes prefer400_000.Hertz
- sometimes the
ACK
vsLAST_NACK
can be the difference. Most devices do aNACK
on the final read byte - similar thing for the last
writeByte
where you may need to do awriteByte(ack=false)
Also, you might consider trying Nephyr as it supports a lot of ESP32 features but is much more stable. It's what I'm using these days for new projects. :-)
Though for Nesper I'd recommend the esp-idf-lib
I2C driver as the way to go.