I2C: Cannot use MasterWriteByte

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
lock attach
Attachments are accessible only for community members.
LoPo_4237926
Level 1
Level 1
First like received

I'm trying to implement a digital compass sensor using the QMC5883L (not HMC5883L). From the datasheet, the slave address is given as 0x0D.

Here is what I have so far:

   

         UART_Start();

    I2C_Start();

   

    int status;

    char temp[20];

   

    UART_PutString("--- Starting ---\n");

   

    // Initiate communication

    status = I2C_MasterSendStart(QMC5883L_ADDR, I2C_WRITE_XFER_MODE);

   

    if (status == I2C_MSTR_NO_ERROR){

        // Point to data register at 0x00

        UART_PutString("No error. Proceeding...\n");

        status = I2C_MasterWriteByte(0x00);      // Problem occurs here! Cannot write

       

        while (status == I2C_MSTR_NOT_READY){

            UART_PutString("ERROR: I2C_MSTR_NOT_READY\n");

            status = I2C_MasterWriteByte(0x00);

            I2C_MasterSendStop();

            CyDelay(200);

        }

    }

    else{

        sprintf(temp, "ERROR: %d\n", status);

        UART_PutString(temp);

    }

    while(1)

    {

        // Initiate reading

        I2C_MasterSendStart(QMC5883L_ADDR, I2C_READ_XFER_MODE);

        uint16  x = I2C_MasterReadByte(I2C_ACK_DATA);

                x = I2C_MasterReadByte(I2C_ACK_DATA) << 8 | x;

               

        uint16  y = I2C_MasterReadByte(I2C_ACK_DATA);

                y = I2C_MasterReadByte(I2C_ACK_DATA) << 8 | y;

               

        sprintf(temp, "x: %d, y: %d\n", x, y);

    }

The main problem so far is being unable to point to my data register at 0x00. It keeps throwing a I2C_MSTR_NOT_READY error. Not sure how to continue on as this is essentially how everyone is setting up their I2C connection 😕

0 Likes
1 Solution
lock attach
Attachments are accessible only for community members.
LoPo_4237926
Level 1
Level 1
First like received

Hi guys, after 4 hours of debugging and reading up different posts/arduino code online, I've finally gotten it to work!

A few things I've learnt (this might be specific to just my QMC5883L as I accidentally shorted it for a short while (pun intended) earlier):

- To initialise communication between the PSoC and the sensor, you first need to write a 0x01 to register 0x0B, then a 0x1D (to setup mode, sample ratio, scale, etc.) to register 0x09. Obviously, you can change this value to something else depending on your needs but the datasheet recommends this mode (0x1D).

- For my case, I only needed x and y values. However, the sensor provides z values as well. From what I've observed, you need to read values up till z before NAK-ing it. For example,

    x = I2C_MasterReadByte(I2C_ACK_DATA);

  x = I2C_MasterReadByte(I2C_ACK_DATA) << 8 | x;

           

  y = I2C_MasterReadByte(I2C_ACK_DATA);

  y = I2C_MasterReadByte(I2C_ACK_DATA) << 8 | y;

  z = I2C_MasterReadByte(I2C_ACK_DATA);

  z = I2C_MasterReadByte(I2C_NAK_DATA) << 8 | z;

  works, but

  x = I2C_MasterReadByte(I2C_ACK_DATA);

  x = I2C_MasterReadByte(I2C_ACK_DATA) << 8 | x;

           

  y = I2C_MasterReadByte(I2C_ACK_DATA);

  y = I2C_MasterReadByte(I2C_NAK_DATA) << 8 | y;

  does not.

- Didn't really test this but I'm using "Open drain, drives low" with an initial high state on my pins with an external pull up resistor (I used 4.7k) as MoTa_728816​ pointed out. Might work without external pull up resistors or even with the PSoC's resistive pull up drive mode.

- Communication with the QMC5883L has to be restarted after each NAK (aka pointing to the data register again).

TLDR: Everything works now, thanks everyone for the help. I am eternally grateful as I'm new to PSoC and have never even touched I2C until these past couple of days while working on this sensor. For those who just want the code, its attached on this post. Note that the function that you should be calling if you plan on simply copy and pasting my code is getHeading(int x, y). Have fun : )

View solution in original post

8 Replies
lock attach
Attachments are accessible only for community members.
MotooTanaka
Level 9
Level 9
Distributor - Marubun (Japan)
First comment on blog Beta tester First comment on KBA

Hi,

I tried your program using LM75B (address 0x48).

SDA and SCL are pulled-up to 3.3V externally.

And I modified some part of your program

===================

#include "project.h"

#include "stdio.h"

// #define QMC5883L_ADDR 0x0D

#define QMC5883L_ADDR 0x48 // I used LM75B

int main(void)

{

    CyGlobalIntEnable; /* Enable global interrupts. */

    UART_Start();

    I2C_Start();

  

    int status;

    char temp[20];

  

    UART_PutString("--- Starting ---\n");

  

    // Initiate communication

    status = I2C_MasterSendStart(QMC5883L_ADDR, I2C_WRITE_XFER_MODE);

  

    if (status == I2C_MSTR_NO_ERROR){

        // Point to data register at 0x00

        UART_PutString("No error. Proceeding...\n");

        status = I2C_MasterWriteByte(0x00);      // Problem occurs here! Cannot write

      

        while (status == I2C_MSTR_NOT_READY){

            UART_PutString("ERROR: I2C_MSTR_NOT_READY\n");

            status = I2C_MasterWriteByte(0x00);

            I2C_MasterSendStop();

            CyDelay(200);

        }

        I2C_MasterSendStop(); // <----- added

        CyDelay(200);        // <----- added

    }

    else{

        sprintf(temp, "ERROR: %d\n", status);

        UART_PutString(temp);

    }

    while(1)

    {

        // Initiate reading

        I2C_MasterSendStart(QMC5883L_ADDR, I2C_READ_XFER_MODE);

      

        uint16  x = I2C_MasterReadByte(I2C_ACK_DATA);

                x = I2C_MasterReadByte(I2C_ACK_DATA) << 8 | x;

              

        uint16  y = I2C_MasterReadByte(I2C_ACK_DATA);

                y = I2C_MasterReadByte(I2C_NAK_DATA) << 8 | y; // <---- modified for NAK

        I2C_MasterSendStop() ;  // <----- added

              

        sprintf(temp, "x: %d, y: %d\n", x, y);

        UART_PutString(temp);                                  // <----- added

        CyDelay(1000) ;                                        // <----- added

    }

}

===================

Then the program seems to be working.

Although I prefer to set PIN to open-drain drivings to low, your resistor pull-up also worked.

(Please don't care about the value of x and y as we are reading Temp & Humidity Sensor ;-P)

000-TeraTerm-log.JPG

moto

0 Likes

Hi, thanks for helping out. I saw that most of the code that you've added addresses the problems after pointing to the data register. However, the problem I have is that I can't point to my data register that I wish to read. Essentially, my code is getting stuck on my while loop for error checking:

while (status == I2C_MSTR_NOT_READY){

and I can't read my data register because of this.

Perhaps a log of my UART terminal would give a better view of the problem:

--- Starting ---

No error. Proceeding...

ERROR: I2C_MSTR_NOT_READY

ERROR: I2C_MSTR_NOT_READY

ERROR: I2C_MSTR_NOT_READY

ERROR: I2C_MSTR_NOT_READY

ERROR: I2C_MSTR_NOT_READY

ERROR: I2C_MSTR_NOT_READY

ERROR: I2C_MSTR_NOT_READY

ERROR: I2C_MSTR_NOT_READY

ERROR: I2C_MSTR_NOT_READY

ERROR: I2C_MSTR_NOT_READY

ERROR: I2C_MSTR_NOT_READY

From what I've read, the HMC5883L chip uses the I2C address 0x1E but they've stopped manufacturing it and have licensed the manufacturing of the chip to another company, which produces the QMC5883L (the one I'm using) and uses the address 0x0D. I've tested it with the 0x1E address but to no avail. The initial I2C_MasterSendStart is successful (I2C_MSTR_NO_ERROR) which means connection to the I2C device at address 0x0D was successful. The problem only comes after that where I can't write to my slave to tell it which register I'd like to read later.

0 Likes
MotooTanaka
Level 9
Level 9
Distributor - Marubun (Japan)
First comment on blog Beta tester First comment on KBA

Hi,

> "ERROR: I2C_MSTR_NOT_READY"

In my experience this condition often happens when SDA is stuck at GND.

From what I noticed in your project, you specified the pin types of

SDA and SCL as resistive pull up. I wonder if you have external pull-up resistors.

I recommend you (if you have not done so)

(1) Add external pull-up resistors (2K ~ 10K) to SDA line and SCL line.

001-connection.JPG

(2) Change pin types to Open Drain Driving to Low

002-SDA.JPG

003-SCL.JPG

moto

0 Likes

Few comments about original code:

1. This while loop can't recover I2C because after you call I2C_MasterSendStop() the I2C transfer is completed - the Stop condition is sent on the bus. So you need to do Start condition again calling I2C_MasterSendStart

while (status == I2C_MSTR_NOT_READY){

            UART_PutString("ERROR: I2C_MSTR_NOT_READY\n");

            status = I2C_MasterWriteByte(0x00);

            I2C_MasterSendStop();

            CyDelay(200);

        }

2. The reading should end with NACK so code should fixed

uint16  y = I2C_MasterReadByte(I2C_ACK_DATA);

        y = I2C_MasterReadByte(I2C_NAK_DATA) << 8 | y;

        I2C_MasterSendStop()

3. Based on symptoms, it is not clear what is going on the I2C bus. Can you add the scope diagram of the I2C write transfer?

0 Likes

Hi, by scope diagram do you mean literally the waveform on an oscilloscope? If that's the case I'm afraid I don't have access to an oscilloscope with me until Monday.

0 Likes

Yes. This is what I meant: waveform on an oscilloscope - it would help to define what is going on the I2C bus.

The I2C_MasterSendStart needs the address as right justified 7-bit format, so please make sure that sensor provides I2C address in the same format or you need to adjust the address.

You can scan try all addressed 0-128 and see which slave ACKs  the address (see pseudo code below):

// Adjust per your I2C bus

#define NUMBER_OF_I2C_SLAVES_ON_THE_BUS (10U)

uint8_t addr = 0;

uint8_t i = 0;

uint8_t slaves[NUMBER_OF_I2C_SLAVES_ON_THE_BUS];

for (addr= 0; addr < 128; addr++)

{

    status = I2C_MasterSendStart(addr, I2C_WRITE_XFER_MODE);

    I2C_MasterSendStop();

    if (status != I2C_MSTR_ERR_LB_NAK)

    {

        // This slave respond to the master

        slaves = addr;

        i++;

    }

    CyDelay(100);

}

0 Likes

Hi, I've just tried using external pull-up resistors (2.2k) and it now gives me I2C_MSTR_ERR_LB_NAK on I2C_MasterSendStart. Seeing as I can't even connect to my sensor now, I don't think the issue is with the pull-up resistor. I'm tempted to use an external pull-up resistor in addition to setting the "Drive mode" of my pins to "Resistive pull up" as I'm running out of ideas :') (though I reckon I'd be reducing the overall pull-up resistance since they're in parallel and I doubt that's a good idea).

0 Likes
lock attach
Attachments are accessible only for community members.
LoPo_4237926
Level 1
Level 1
First like received

Hi guys, after 4 hours of debugging and reading up different posts/arduino code online, I've finally gotten it to work!

A few things I've learnt (this might be specific to just my QMC5883L as I accidentally shorted it for a short while (pun intended) earlier):

- To initialise communication between the PSoC and the sensor, you first need to write a 0x01 to register 0x0B, then a 0x1D (to setup mode, sample ratio, scale, etc.) to register 0x09. Obviously, you can change this value to something else depending on your needs but the datasheet recommends this mode (0x1D).

- For my case, I only needed x and y values. However, the sensor provides z values as well. From what I've observed, you need to read values up till z before NAK-ing it. For example,

    x = I2C_MasterReadByte(I2C_ACK_DATA);

  x = I2C_MasterReadByte(I2C_ACK_DATA) << 8 | x;

           

  y = I2C_MasterReadByte(I2C_ACK_DATA);

  y = I2C_MasterReadByte(I2C_ACK_DATA) << 8 | y;

  z = I2C_MasterReadByte(I2C_ACK_DATA);

  z = I2C_MasterReadByte(I2C_NAK_DATA) << 8 | z;

  works, but

  x = I2C_MasterReadByte(I2C_ACK_DATA);

  x = I2C_MasterReadByte(I2C_ACK_DATA) << 8 | x;

           

  y = I2C_MasterReadByte(I2C_ACK_DATA);

  y = I2C_MasterReadByte(I2C_NAK_DATA) << 8 | y;

  does not.

- Didn't really test this but I'm using "Open drain, drives low" with an initial high state on my pins with an external pull up resistor (I used 4.7k) as MoTa_728816​ pointed out. Might work without external pull up resistors or even with the PSoC's resistive pull up drive mode.

- Communication with the QMC5883L has to be restarted after each NAK (aka pointing to the data register again).

TLDR: Everything works now, thanks everyone for the help. I am eternally grateful as I'm new to PSoC and have never even touched I2C until these past couple of days while working on this sensor. For those who just want the code, its attached on this post. Note that the function that you should be calling if you plan on simply copy and pasting my code is getHeading(int x, y). Have fun : )