nesper icon indicating copy to clipboard operation
nesper copied to clipboard

Can't read from I2C

Open khogeland opened this issue 2 years ago • 1 comments

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))

khogeland avatar Nov 19 '21 01:11 khogeland

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 prefer 400_000.Hertz
  • sometimes the ACK vs LAST_NACK can be the difference. Most devices do a NACK on the final read byte
  • similar thing for the last writeByte where you may need to do a writeByte(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.

elcritch avatar Nov 20 '21 23:11 elcritch