Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I2C read fails on "readByte" from AM2315 #103

Open
samco182 opened this issue Apr 13, 2020 · 3 comments
Open

I2C read fails on "readByte" from AM2315 #103

samco182 opened this issue Apr 13, 2020 · 3 comments

Comments

@samco182
Copy link
Contributor

samco182 commented Apr 13, 2020

Board Type

Raspberry Pi 3B+

Operating System

Raspbian Buster 10

Swift Version

Swift pre-built version 5.1.3 from https://github.com/uraimo/buildSwiftOnARM/releases/tag/5.1.3

Description

I am trying to create a library to control the AM2315 temperature and humidity sensor using SwiftyGPIO.

I am using Arduino_AM2315 library as basis to understand how to obtain the sensor's readings. Here is the code snippet from the previously mentioned library:

  uint8_t reply[10];
  
  Wire.beginTransmission(AM2315_I2CADDR);
  Wire.write(AM2315_READREG);
  Wire.endTransmission();

  delay(50);

  // In the real code you will see the programmer does not know
  // why you call this two times, but actually is because you have to
  // first "wake up" the sensor cause it is "asleep" by default
  Wire.beginTransmission(AM2315_I2CADDR);
  Wire.write(AM2315_READREG);
  Wire.write(0x00);  // start at address 0x0
  Wire.write(4);  // request 4 bytes data
  Wire.endTransmission();
 
  delay(50); 

  Wire.requestFrom(AM2315_I2CADDR, 8);
  for (uint8_t i=0; i<8; i++) {
    reply[i] = Wire.read();
    //Serial.println(reply[i], HEX);
  }

And here is the Swift version of the code above:

import SwiftyGPIO
import Foundation

#if os(Linux)
    import Glibc
#else
    import Darwin.C
#endif

let i2cAddress = 0x5C
let readRegisterHex: UInt8 = 0x03

let i2cs = SwiftyGPIO.hardwareI2Cs(for: .RaspberryPi3)!
let i2c = i2cs[1]

do {
    // Wake up device, discard error
    try? i2c.writeByte(i2cAddress, command: readRegisterHex, value: 0x00)
    
    usleep(50_000)
    
    // TELL THE DEVICE WE WANT 4 BYTES OF DATA
    try i2c.writeByte(i2cAddress, command: readRegisterHex, value: 0x00)
    try i2c.writeByte(i2cAddress, command: readRegisterHex, value: 0x04)
} catch let error {
    print("ERROR writing: \(error)")
}

usleep(50_000)

do {
    for iteration in 0..<8 {
        let data = try i2c.readByte(i2cAddress)
        print("Iteration #: \(iteration), DATA: \(data)")
    }
} catch let error {
    print("ERROR reading: \(error)")
}

According to the documentation, I am expecting to receive a response in the format of:
Screen Shot 2020-04-12 at 9 51 46 PM

But whenever I run my Swift code, it only successfully retrieves the first byte of the expected sequence: 0x03. When it tries to read the second one onwards, it always crashes, and the error it outputs is:

➜  Example git:(develop) ✗ swift run
[3/3] Linking SwiftyAM2315Example
WRITE SUCCESSFULL: 0
WRITE SUCCESSFULL: 0
Iteration #: 0, DATA: 3
ERROR reading: IOError("I2C read failed: Remote I/O error")

It is worth to mention that I have also tried using the readByte(_ address: Int, command: UInt8) and readData(_ address: Int, command: UInt8) with no success. The former fails in the exact same manner as mentioned before, and the later just return a array full of zeroes.

@uraimo
Copy link
Owner

uraimo commented Apr 15, 2020

I haven't checked what that requestFrom actually does but I suspect it's doing an actual i2c request for those 8 bytes, those 8 read() could just be reading from an internal buffer or anyway, not actually sending a read request like you are doing in Swift.
I'd say your interpretation of the response in the datasheet is correct, but that's not a standard i2c data block... the first byte should have the length of the block and not that 0x3. You could try with the simplified readI2CData if you haven't already.
Alternatively, you could try reading each one of those 4 registers (temp H/L, RelHum H/L) with a separate readWord/readI2CData call (yes, two bytes... the initial 0x3 is still there), like Adafruit does in their python library.

@samco182
Copy link
Contributor Author

samco182 commented Apr 19, 2020

Thanks @uraimo I tried both of the options you suggested, but no luck. With the readI2CData call I kept receiving an array of zeroes, and with the individual register's readings, I received only the 0x03 for each of the calls with readWord and an array of zeroes with readI2CData.

This is how the manufacturer's python library for the AM2315 reads the data.

    def _read_data(self):
        count = 0
        tmp = None
        powercyclecount = 0
        while count <= MAXREADATTEMPT:
            try:
                try:
                    self._device.write_byte_data(AM2315_I2CADDR, AM2315_READREG, 0x00)
                    time.sleep(0.050)
                except:
                    if (AM2315DEBUG == True):
                        print "Wake Byte Fail"
                    time.sleep(2.000)                    
                    self._device.write_byte_data(AM2315_I2CADDR, AM2315_READREG, 0x00)
                    time.sleep(0.001)
                    #self._device.write_byte_data(AM2315_I2CADDR, AM2315_READREG, 0x00)
                    #time.sleep(0.001)
                    #self._device.write_byte_data(AM2315_I2CADDR, AM2315_READREG, 0x00)
                    time.sleep(0.050)

                # TELL THE DEVICE WE WANT 4 BYTES OF DATA
                self._device.write_i2c_block_data(AM2315_I2CADDR, AM2315_READREG,[0x00, 0x04])
                #self._device.writeList(AM2315_READREG,[0x00, 0x04])
                time.sleep(0.09)
                tmp = self._device.am2315_read_i2c_block_data(AM2315_I2CADDR, AM2315_READREG,8)
                #tmp = self._device.readList(AM2315_READREG,8)
                self.temperature = (((tmp[4] & 0x7F) << 8) | tmp[5]) / 10.0
                self.humidity = ((tmp[2] << 8) | tmp[3]) / 10.0
                # check for > 10.0 degrees higher
                if (self.AM2315PreviousTemp != -1000):   # ignore first time
                        if (self.humidity <0.01 or self.humidity > 100.0):
                            # OK, humidity is bad.  Ignore
                            if (AM2315DEBUG == True):
                                print ">>>>>>>>>>>>>"
                                print "Bad AM2315 Humidity = ", self.temperature
                                print ">>>>>>>>>>>>>"
                                self.badreadings = self.badreadings+1
                                tmp = None
                        else:
                            if (abs(self.temperature - self.AM2315PreviousTemp) > 10.0):
                                # OK, temp is bad.  Ignore
                                if (AM2315DEBUG == True):
                                    print ">>>>>>>>>>>>>"
                                    print "Bad AM2315 Temperature = ", self.temperature
                                    print ">>>>>>>>>>>>>"
                                    self.badreadings = self.badreadings+1
                                    tmp = None
                            else:
                                # Good Temperature
                                self.AM2315PreviousTemp = self.temperature
                else:
                    # assume first is good temperature
                    self.AM2315PreviousTemp = self.temperature
                # IF WE HAVE DATA, LETS EXIT THIS LOOP
                if tmp != None:
                    break
            except Exception as ex:
                if (AM2315DEBUG == True):
                    template = "An exception of type {0} occurred. Arguments:\n{1!r}"
                    message = template.format(type(ex).__name__, ex.args)
                    print message
                    #print traceback.format_exc()
                    print "AM2315readCount = ", count
            count += 1
            self.retrys += 1
            time.sleep(0.10)
            # only do three power cycle attempts
            if (self.powerpin <> 0):
                if (count > MAXREADATTEMPT):
                    self.powerCycleAM2315()
                    if (powercyclecount <=2): 
                        powercyclecount +1
                        count = 0 
            
        if (AM2315DEBUG == True):
            print "--->looking at good data"

        # GET THE DATA OUT OF THE LIST WE READ
        self.humidity = ((tmp[2] << 8) | tmp[3]) / 10.0
        self.temperature = (((tmp[4] & 0x7F) << 8) | tmp[5]) / 10.0
        if (tmp[4] & 0x80):
            self.temperature = -self.temperature

        self.crc = ((tmp[7] << 8) | tmp[6]) 
        # Verify CRC here
        # force CRC error with the next line
        #tmp[0] = tmp[0]+1
        t = bytearray([tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5]])
        c = self.verify_crc(t)

        if (AM2315DEBUG == True):
            print "AM2315temperature=",self.temperature
            print "AM2315humdity=",self.humidity
            print "AM2315crc=",self.crc
            print "AM2315c=",c

        if (self.crc != c) or (c == 0):
        #if self.crc != c:
            if (AM2315DEBUG == True):
                print "AM2314 BAD CRC"
            self.badcrcs = self.badcrcs + 1
            self.crc = -1
        else:
            self.goodreads = self.goodreads+1

I tried to follow how they did it, but checking how they use ioctl calls are very different from SwiftyGPIO's implementation. Maybe they "tweaked" the ioctl implementation to be able to work with the weird I2C implementation the sensor has, and that is why I am not able to get readings with SwiftyGPIO (?)

@uraimo
Copy link
Owner

uraimo commented May 4, 2020

Ciao, sorry for the delay, it looks like they implemented most of the layer above the ioctl instead of using a python smbus library.

The problem is here:

      tmp = self._device.am2315_read_i2c_block_data(AM2315_I2CADDR, AM2315_READREG,8)

That's not actually a block_data_read... but an I2C_RDWR(0x707) that reads and writes at the same time... and that is not implemented in SwiftyGPIO (that implements only smbus ioctl calls) because I never found a device that supported it.

Our I2C_RDWR should look like this, starting from their implementation:

    private func i2c_read_i2c_rdwr_data(command: UInt8, values: inout [UInt8]) -> Int32 {
        if fd == -1 {
            openI2C()
        }

        var data = [UInt8](repeating:0, count: I2C_DEFAULT_PAYLOAD_LENGTH)
        var args = i2c_smbus_ioctl_data(read_write: I2C_SMBUS_READ,
                                        command: command,
                                        size: I2C_SMBUS_BLOCK_DATA,   // OR: I2C_DEFAULT_PAYLOAD_LENGTH
                                        data: &data)
        let r = ioctl(fd, I2C_RDWR, &args)

        if r >= 0 {
            for i in 0..<Int(data[0]) {
                values[i] = data[i+1]
            }
            return Int32(data[0])
        } else {
            return -1
        }
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants