Project

General

Profile

Proximity Plug and I2C

Added by michaelrommel almost 4 years ago

Hi,

I am trying to get a Proximity Plug working via Port 1.

Like Jean-Claude mentioned in the introductory post (http://jeelabs.org/2010/10/25/meet-the-proximity-plug/) the response from the device is IMHO not recognized correctly, as can be seen by the # at the beginning of the SINFO response, where I would expect something like an ‘F’.
I tried to get that working and have created a small program, which just loops and displays a few registers of interest:

void loop () {
    byte v;
    activityLed(1);
    touchsensor.send();
    touchsensor.write(ProximityPlug::SINFO);
    touchsensor.receive();
    for (;;) {
        char c = touchsensor.read(0);
        if (c == 0 ) {
            break;
        }
        if (' ' <= c && c <= '~') {
            Serial.print (c);
         } else {
            Serial.print ('<');
            Serial.print (c, HEX );
            Serial.print ('>');
        }
    }
    //touchsensor.read(1);
    touchsensor.stop();
    Serial.println();
    v = touchsensor.getReg(ProximityPlug::CONFIG);
    Serial.print( "CONFIG ");
    Serial.println( v, BIN );
    v = touchsensor.getReg(ProximityPlug::LPCR);
    Serial.print( "Low Power Config ");
    Serial.println( v, BIN );
    v = touchsensor.getReg(ProximityPlug::SCR);
    Serial.print( "Sounder Configuration ");
    Serial.println( v, BIN );
    v = touchsensor.getReg(ProximityPlug::MNTPR);
    Serial.print( "Maximum Number of Touches ");
    Serial.println( v, BIN );
    v = touchsensor.getReg(ProximityPlug::MTPR);
    Serial.print( "Master Tick Period ");
    Serial.println( v, BIN );
    v = touchsensor.getReg(ProximityPlug::TPSTATUS);
    Serial.print( "TPSTATUS ");
    Serial.println( v, BIN );
    v = touchsensor.getReg(ProximityPlug::TPCONFIG);
    Serial.print( "TPCONFIG ");
    Serial.println( v, BIN );
    activityLed(0);
    delay(2000);
}

It prints out the following:

#reescale,PN:MPR084,QUAL:EXTERNAL,VER:1_0_0
CONFIG 1010
Low Power Config 0
Sounder Configuration 1
Maximum Number of Touches 10
Master Tick Period 10
TPSTATUS 0
TPCONFIG 10110001
9eescale,PN:MPR084,QUAL:EXTERNAL,VER:1_0_0
CONFIG 1010
Low Power Config 0
Sounder Configuration 1
Maximum Number of Touches 10
Master Tick Period 10
TPSTATUS 1
TPCONFIG 10110001
9eescale,PN:MPR084,QUAL:EXTERNAL,VER:1_0_0
CONFIG 1010
Low Power Config 0
Sounder Configuration 1
Maximum Number of Touches 100
Master Tick Period 10
TPSTATUS 1
TPCONFIG 11111111
9eescale,PN:MPR084,QUAL:EXTERNAL,VER:1_0_0
CONFIG 1010
Low Power Config 0
Sounder Configuration 1
Maximum Number of Touches 10
Master Tick Period 10
TPSTATUS 1
TPCONFIG 10110001

Note that the first byte of the SINFO response changed, which made me dig deeper into it. The # is 0x23 or binary 0010 0011, whereas I expected something like an ‘F’, which is 0x46 or binary 0100 0110. So I thought maybe the bits are off by one. On the other hand the ‘9’ which is received is binary 0011 1001, which is strange, as I would expect two bytes there, like ‘Fr’.

According to the datasheet of the MPR084, the sequence how to read from the device is to signal the “START” condition, then transfer the address of the chip 0x5C with the read bit set and then I read it, that the master must put the command byte on the bus, which is ACKed by the slave and then immediately after that, the bytes can be read by the master.
The signals, which I see on the bus indicate, that the Jeenode makes two transfers, one to write the command byte and another one reading from the device, but not sending the command byte again. The command byte is autoincrementing, which would somehow explain, that another byte gets lost.

Am I completely wrong in my reading of the datasheet of the figure 12? How is that supposed to look really on the wire?

Thanks for any pointers to more information!

Michael.


Replies (11)

RE: Proximity Plug and I2C - Added by michaelrommel almost 4 years ago

Hi again,

other sources say, that this restart is correctly implemented, so that was a wrong assumption by me. But I am at a loss, why sometimes bytes get mangled or lost.
I also noticed that the datasheet says, there should be a 4,8k pullup on the SDA wire, which the module does not have.
And last, I was wondering, why there is in the source code the following construct:

/// Can be used to drive a software (bit-banged) I2C bus via a Port interface.
/// @todo Speed up the I2C bit I/O, it's far too slow right now.
class PortI2C : public Port {
    uint8_t uswait;
#if 0
// speed test with fast hard-coded version for Port 1:
    inline void hold() const
        { _delay_us(1); }
    inline void sdaOut(uint8_t value) const
        { bitWrite(DDRD, 4, !value); bitWrite(PORTD, 4, value); }
    inline uint8_t sdaIn() const
        { return bitRead(PORTD, 4); }
    inline void sclHi() const
        { hold(); bitWrite(PORTC, 0, 1); }
    inline void sclLo() const
        { hold(); bitWrite(PORTC, 0, 0); }
public:
    enum { KHZMAX, KHZ400, KHZ100, KHZ_SLOW };
#else
    inline void hold() const
        { delayMicroseconds(uswait); }
    inline void sdaOut(uint8_t value) const
        { mode(!value); digiWrite(value); }
    inline uint8_t sdaIn() const
        { return digiRead(); }
    inline void sclHi() const
        { hold(); digiWrite2(1); }
    inline void sclLo() const
        { hold(); digiWrite2(0); }
public:
    enum { KHZMAX = 1, KHZ400 = 2, KHZ100 = 9 };
#endif

The sdaOut function sets the mode of the SDA pin to
Or am I wrong again?

Michael.

RE: Proximity Plug and I2C - Added by michaelrommel almost 4 years ago

Okay, so the first bit is solved: I increased the rate from ‘9’ to ‘15’ us and now the string “Freescale” is correctly transmitted.
But still in the subsequent readouts the ‘F’ is still missing, I’ll dig deeper into this.

  • Michael.

RE: Proximity Plug and I2C - Added by martynj almost 4 years ago

_@michael,

> there should be a 4.8k pullup on the SDA wire

Isn’t the internal pull-up enabled on this pin?

RE: Proximity Plug and I2C - Added by michaelrommel almost 4 years ago

martynj, that’s why I was looking for the declaration of the sdaOut function. I thought, that the pull-up would be enabled only by setting the pinMode to INPUT_PULLUP, which would be 0x02, but I couldn’t find it anywhere in the Port.h/.cpp code… So I simply do not understand the thinking behind the mode(!value); statement… :-(
But if it works fine since years with all the other Jeelabs plugs? I don’t think that I would be the first one to discover any mishap there…

RE: Proximity Plug and I2C - Added by martynj almost 4 years ago

@michael,

This extract might be useful:

/// Initialise the SPI port for use by the RF12 driver.
void rf12_spiInit () {
bitSet(SS_PORT, cs_pin);
bitSet(SS_DDR, cs_pin);
digitalWrite(SPI_SS, 1);
pinMode(SPI_SS, OUTPUT);
pinMode(SPI_MOSI, OUTPUT);
pinMode(SPI_MISO, INPUT);
pinMode(SPI_SCK, OUTPUT);
#ifdef SPCR
SPCR = _BV(SPE) | _BV(MSTR);
#if F_CPU > 10000000
// use clk/2 (2x 1/4th) for sending (and clk/8 for recv, see rf12_xferSlow)
SPSR |= _BV(SPI2X);
#endif
#else
// ATtiny
USICR = bit(USIWM0);
#endif
pinMode(RFM_IRQ, INPUT);
digitalWrite(RFM_IRQ, 1); // pull-up
}

RE: Proximity Plug and I2C - Added by michaelrommel almost 4 years ago

@martyn,

thank you so much, now all makes sense again. I found the relevant section in the Atmel Manual, where it is being described how to enable the pull-up resistor.
Now I still have to track down, why there is still a character missing, but now I understand why the communication works at all :-)

Thanks!

RE: Proximity Plug and I2C - Added by martynj almost 4 years ago

@michael,

If you filter out the first two characters and print separately, either they are the “Fr” you expect (in which case perhaps a serial.flush will fix) or they are corrupt in the buffer (which implies there is still some issue with the I2C transfer).

RE: Proximity Plug and I2C - Added by michaelrommel almost 4 years ago

@martyn,

I have figured out what went wrong - I think it is a strange behaviour of the MPR084.
The manual says, that SINFO is terminated with . If you actually read out that byte, then on the next request for SINFO, the first byte of the string is missing, in fact, the more characters you read beyond the BUILD_NUMBER, the more characters are missing from the next request for SINFO, e.g. if I read 46 characters instead of 43, then the response for 4 subsequent requests for SINFO looks like this:

Freescale,PN:MPR084,QUAL:EXTERNAL,VER:1_0_0<0>Fr
escale,PN:MPR084,QUAL:EXTERNAL,VER:1_0_0<0>Frees
ale,PN:MPR084,QUAL:EXTERNAL,VER:1_0_0<0>Freescal
,PN:MPR084,QUAL:EXTERNAL,VER:1_0_0<0>Freescale,P

Note that in the second line one ‘e’ is missing, in the third line the ‘c’ is missing.
If you read exactly 43 bytes, the is silently discarded and the output looks ok then.

But during all of that debugging, I found quite some issues with properly sending the stop condition that I would like to summarize in a different post, as they are of general nature and probably not limited to the Proximity Plug. But it is too late now and I have to make some more screenshots of the logic analyzer. I have modified Ports.cpp and Ports.h and now it looks ok to me, but I would like to have the opinions of the experts here…

Cheers for now,

Michael.

RE: Proximity Plug and I2C - Added by martynj almost 4 years ago

@michael,

Good sleuthing… an interesting journey.
Yes, please publish your critique of the ‘stop’ command. Sometimes the last bugs only get squeezed out with a lot of persistence and the right monitoring tools.

SOLVED: Proximity Plug and I2C - Added by michaelrommel almost 4 years ago

@martyn,

So I finally solved all the riddles, that came up when trying this example. I’ll collect the information from the previous entries into this one…

Finding 1): The KHZ100 initialisation setting of 9 us is too fast for the MPR084. You need to adjust the rate by initialising it like this:

PortI2C myBus (1, 15);
ProximityPlug touchsensor (myBus);

Then the error with the garbled ‘#’ character is gone.

Finding 2): The commented out example should be looking more like this:

100         touchsensor.send();
101         touchsensor.write(ProximityPlug::SINFO);
102         touchsensor.receive();
103         for (byte n = 44; n>0; n--) {
104             char c = touchsensor.read(n==1);
105             if (' ' <= c && c <= '~') {
106                 Serial.print (c);
107              } else {
108                 Serial.print ('<');
109                 Serial.print (c, HEX );
110                 Serial.print ('>');
111             }
112         }
113         // touchsensor.stop();
114         Serial.println();

Then the missing characters are gone and the double STOP condition on the bus are gone, too.

Finding 3): In Ports.cpp the getReg() function should have line 930 removed.

925 byte ProximityPlug::getReg(byte reg) const {
926     send();
927     write(reg);
928     receive();
929     byte result = read(1);
930     stop();
931     return result;
932 }

This would remove another double STOP condition on the I2C bus. This could potentially also be applied to the DimmerPlug, but I do not have this module, so I can’t test it.

I’ll write another reply, which is longer and details the wrong assumptions that had me lead in a completely different direction for some time. Maybe it is of help for another maker to gain a better understanding by learning from my mistakes.

Michael.

RE: Proximity Plug and I2C - Added by michaelrommel almost 4 years ago

As promised here is the rest of the story for those not afraid of TL;DR

  1. Don’t rely on only one datasheet for understanding I2C. The MPR084 datasheet is imprecise and plain wrong in describing the I2C communication. Better read e.g. the datasheet of the Precision RTC DS3231, which is a lot better
  2. When reading from a slave device, the last byte read shall not be ACKed, but NACKed, directly followed by a RE-START or STOP condition. Not explained in the MPR datasheet but in the Maxim.
  3. When you do not NACK the last byte received, then two things happen:
  • the Jeelib library does not automatically send the STOP condition and
  • even if you try to send it manually by calling sensor.stop(), it will not work on the bus. Read on, to learn, why…

What I did and learned: Started out by following Jean-Claude’s example:

    // sensor.send();
    // sensor.write(ProximityPlug::SINFO);
    // sensor.receive();
    // for (;;) {
    //     byte c = sensor.read(0);
    //     if (c == 0)
    //         break;
    //     if (' ' <= c && c <= '~')
    //         Serial.print(c);
    //     else {
    //         Serial.print('<');
    //         Serial.print(c, HEX);
    //         Serial.print('>');
    //     }
    // }
    // sensor.read(1);
    // sensor.stop();
    // Serial.println();

I noticed that on the bus I saw the trailing expected ‘’ but then I also saw an ‘F’ coming along! What happened was, that the code read bytes until the terminating zero came along, then it broke out of the loop and finished reading by calling sensor.read(1). Since the preceding read(0) of the zero sent an ACK to the slave, the slave did two things:
* it auto-incremented the word address (to speak in Maxim’s terms) and
* it kept control of the SDA line.

The auto-increment of the word address wrapped around and started again at address 0x14 and the read(1) then actually read again the ‘F’ from Freescale. That letter did not get printed anywhere so I did only notice it on the wire.

Then I removed the read(1), because I thought: well, we do not need the byte and the STOP condition, because we send it anyway in the next line of code - which made things worse. Since there was now no NACK, the slave did not release the SDA line, because it wanted to place the first MSB on the wire for the next read. Therefore the sensor.stop() command had no effect on the wire, because Jeelib contains this code:

    inline void sdaOut(uint8_t value) const
        { mode(!value); digiWrite(value); }
    ...
    void PortI2C::stop() const {
        sdaOut(0);
        sclHi();
        sdaOut(1);
    }
    ...
    uint8_t PortI2C::read(uint8_t last) const {
        uint8_t data = 0;
        for (uint8_t mask = 0x80; mask != 0; mask >>= 1) {
            sclHi();
            if (sdaIn())
                data |= mask;
            sclLo();
        }
        sdaOut(last);
        sclHi();
        sclLo();
        if (last)
            stop();
        sdaOut(1);
        return data;
    }
}

When the stop() function tries to set SDA to high (1) what actually gets executed is the following (very clever and concise, by the way):
* mode(!value) means mode(0) means set this pin to INPUT!
* digiWrite(value) means ‘write 1 to the port configured as INPUT, thereby activating the chip internal pull-up resistor’!

In other words the Atmel would try to let the internal pull-up resistor pull the SDA line high, but since the I2C slave (=MPR084) did not relinquish the SDA line, this line never gets HIGH and the I2C STOP condition never appears on the bus.

Finally understanding what was going on, I modified the code (see last reply), so that a predetermined number of bytes get read by the master and that the last read actually was a read(1) call. Since the read() function also contains an embedded call to stop(), the calling code does not need to supply another one.

Some screenshots are attached, so you can see the Logic traces.

On a final note: JCW wanted to know about the sensitivity of the chip. My application is to attach 8 copper plates beneath an 8mm glass printed with an image and back-coated with white and black paint to make it less transparent for LEDs that get mounted behind it. The copper plates will be arranged at the border of that framed glass, to enable invisible touch areas for advanced functions, like control of the LED backlight etc. I disabled the auto-calibration of the chip and found that the sensor detected a nearby hand/fingers almost 5cm above the glass. A sensitivity setting of 15 in STRx register detected the finger roughly 5mm above the glass and I now settled to a setting of 24-32, which detects already light touches of the glass surface. I need to tinker with the IRQ, to have an immediate reaction, but since I am going to hook up an DS3231 AND the MPR084 on the same I2C bus, I probably need to find a way to figure out in the ISR or in the code which gets activated by setting a flag in the ISR which chip actually caused the IRQ. Still some tinkering to be done…

Happy tinkering to all of you!

Michael.

Screen Shot 2015-04-07 at 20.26.30.png (264 KB) Screen Shot 2015-04-07 at 20.26.30.png No STOP condition anywhere
Screen Shot 2015-04-07 at 20.18.30.png (287 KB) Screen Shot 2015-04-07 at 20.18.30.png Additional 'F' on the wire
Screen Shot 2015-04-07 at 20.29.00.png (253 KB) Screen Shot 2015-04-07 at 20.29.00.png Correct termination of the read of SINFO
CapSensor-0003-00464.jpg (1.81 MB) CapSensor-0003-00464.jpg Application picture for capacitive touch sensor
2310
2311
2312
2313
    (1-11/11)