This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TM4C1233H6PM: Problem reading heading data from HMC6352 via I2C

Part Number: TM4C1233H6PM

Hi,

I've ran the I2C loopback example in the tivaware folder and taken what I could from that to try and set up communication with
a rather old compass sensor part HMC6352.

It works fine with an arduino sketch but I can't get it working with the tivaware. Here is my code. Have I missed or mistaken something?

Slave address for this device is 0x42 for write and 0x43 for read. I think this is taken care of with the true and false condition in the set slave address function.

The device is supposed to operate at 100kbps mode but not sure what the default is set for in the driverlib I2c.

//*****************************************************************************
// HMC6352.c
//*****************************************************************************

#include <stdbool.h>
#include <stdint.h>
#include "inc/hw_i2c.h"
#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "driverlib/gpio.h"
#include "driverlib/i2c.h"
#include "driverlib/pin_map.h"
#include "driverlib/sysctl.h"
#include "driverlib/uart.h"
#include "utils/uartstdio.h"

//*****************************************************************************

//*****************************************************************************
//
// Number of I2C data packets to receive.
//
//*****************************************************************************
#define NUM_I2C_DATA 2

//*****************************************************************************

//
// Set the address for slave module. This is a 7-bit address sent in the
// following format:
//                      [A6:A5:A4:A3:A2:A1:A0:RS]
//
// A zero in the "RS" position of the first byte means that the master
// transmits (sends) data to the selected slave, and a one in this position
// means that the master receives data from the slave.
//
//*****************************************************************************
#define SLAVE_ADDRESS 0x42

//*****************************************************************************
//
// This function sets up UART0 to be used for a console to display information
// as the example is running.
//
//*****************************************************************************
void
InitConsole(void)
{
    //
    // Enable GPIO port A which is used for UART0 pins.
    // TODO: change this to whichever GPIO port you are using.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);

    //
    // Configure the pin muxing for UART0 functions on port A0 and A1.
    // This step is not necessary if your part does not support pin muxing.
    // TODO: change this to select the port/pin you are using.
    //
    GPIOPinConfigure(GPIO_PA0_U0RX);
    GPIOPinConfigure(GPIO_PA1_U0TX);

    //
    // Enable UART0 so that we can configure the clock.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);

    //
    // Use the internal 16MHz oscillator as the UART clock source.
    //
    UARTClockSourceSet(UART0_BASE, UART_CLOCK_PIOSC);

    //
    // Select the alternate (UART) function for these pins.
    // TODO: change this to select the port/pin you are using.
    //
    GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);

    //
    // Initialize the UART for console I/O.
    //
    UARTStdioConfig(0, 115200, 16000000);
}

//*****************************************************************************
//
// Configure the I2C0 master.
//
//*****************************************************************************
int
main(void)
{
#if defined(TARGET_IS_TM4C129_RA0) ||                                         \
    defined(TARGET_IS_TM4C129_RA1) ||                                         \
    defined(TARGET_IS_TM4C129_RA2)
    uint32_t ui32SysClock;
#endif
    uint32_t pui32CmdTx;
    uint32_t pui32DataRx[NUM_I2C_DATA];
    uint32_t ui32Index;

    //
    // Set the clocking to run directly from the external crystal/oscillator.
    // TODO: The SYSCTL_XTAL_ value must be changed to match the value of the
    // crystal on your board.
    //
#if defined(TARGET_IS_TM4C129_RA0) ||                                         \
    defined(TARGET_IS_TM4C129_RA1) ||                                         \
    defined(TARGET_IS_TM4C129_RA2)
    ui32SysClock = SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ |
                                       SYSCTL_OSC_MAIN |
                                       SYSCTL_USE_OSC), 25000000);
#else
    SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN |
                   SYSCTL_XTAL_16MHZ);
#endif

    //
    // The I2C0 peripheral must be enabled before use.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);

    //
    // For this example I2C0 is used with PortB[3:2].  The actual port and
    // pins used may be different on your part, consult the data sheet for
    // more information.  GPIO port B needs to be enabled so these pins can
    // be used.
    // TODO: change this to whichever GPIO port you are using.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);

    //
    // Configure the pin muxing for I2C0 functions on port B2 and B3.
    // This step is not necessary if your part does not support pin muxing.
    // TODO: change this to select the port/pin you are using.
    //
    GPIOPinConfigure(GPIO_PB2_I2C0SCL);
    GPIOPinConfigure(GPIO_PB3_I2C0SDA);

    //
    // Select the I2C function for these pins.  This function will also
    // configure the GPIO pins pins for I2C operation, setting them to
    // open-drain operation with weak pull-ups.  Consult the data sheet
    // to see which functions are allocated per pin.
    // TODO: change this to select the port/pin you are using.
    //
    GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2);
    GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);
	
	    //
    // Enable and initialize the I2C0 master module.  Use the system clock for
    // the I2C0 module.  The last parameter sets the I2C data transfer rate.
    // If false the data rate is set to 100kbps and if true the data rate will
    // be set to 400kbps.  For this example we will use a data rate of 100kbps.
    //
#if defined(TARGET_IS_TM4C129_RA0) ||                                         \
    defined(TARGET_IS_TM4C129_RA1) ||                                         \
    defined(TARGET_IS_TM4C129_RA2)
    I2CMasterInitExpClk(I2C0_BASE, ui32SysClock, false);
#else
    I2CMasterInitExpClk(I2C0_BASE, SysCtlClockGet(), false);
#endif

    //
    // Tell the master module what address it will place on the bus when
    // communicating with the slave.  Set the address to SLAVE_ADDRESS
    // (as set in the slave module).  The receive parameter is set to false
    // which indicates the I2C Master is initiating a writes to the slave.  If
    // true, that would indicate that the I2C Master is initiating reads from
    // the slave.
    //
    I2CMasterSlaveAddrSet(I2C0_BASE, SLAVE_ADDRESS, false);

    //
    // Set up the serial console to use for displaying messages.  This is
    // just for this example program and is not needed for I2C operation.
    //
    InitConsole();

    //
    // Display the example setup on the console.
    //
    UARTprintf("Heading ->");
	
	pui32CmdTx = 'A';
	
	//
    // Place the data to be sent in the data register
    //
    I2CMasterDataPut(I2C0_BASE, pui32CmdTx);

    //
    // Initiate send of data from the master.
    //
    I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_SEND);
	
    //
    // Wait until the slave has received and acknowledged the data.
    //
    while(!(I2CSlaveStatus(I2C0_BASE) & I2C_SLAVE_ACT_RREQ))
    {
    }
	
	//
    // Modifiy the data direction to true, so that seeing the address will
    // indicate that the I2C Master is initiating a read from the slave.
    //
    I2CMasterSlaveAddrSet(I2C0_BASE, SLAVE_ADDRESS, true);
	
	for(ui32Index = 0; ui32Index < NUM_I2C_DATA; ui32Index++)
	{
		//
		// Tell the master to read data.
		//
		I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);
		
		//
        // Wait until the slave is done sending data.
        //
        while(!(I2CSlaveStatus(I2C0_BASE) & I2C_SLAVE_ACT_TREQ))
        {
        }
		
        //
        // Read the data from the master.
        //
        pui32DataRx[ui32Index] = I2CMasterDataGet(I2C0_BASE);
		//
        // Display the data that the slave has received.
        //

        UARTprintf("'%c'", pui32DataRx[ui32Index]);
	}
	
	//UARTprintf("\n");
	
}

  • That's a lot of code to review.     By a quick scan - nothing major jumps out.

    Two normal/customary I2C Debug Tactics include:

    • install EXTERNAL pull-up resistors upon each I2C line    (MCU's internal are 'too high' in value - not especially robust)
    • monitor both I2C lines w/a scope - to insure data's presence - and then data's appropriateness

    Serial interfaces - due to their 'connection ease' - are highly favored - yet (often) demand the 'monitoring of signal lines' - to insure correctness...

  • Hi Michael,

     Please see below source code for setting up the slave address. As you can see the 7-bit slave address is first left shifted by one bit before writing to the I2CMSA register. Your slave address should not be 0x42 as this is a 8-bit value that includes both the slave address and the R/W bit.  You should give the 7-bit address as #define SLAVE_ADDRESS 0x21. 

      Other advises from cb1 such as proper pull up resistors on the SDA and SCL buses are also important to check.  

    //*****************************************************************************
    //
    //! Sets the address that the I2C Master places on the bus.
    //!
    //! \param ui32Base is the base address of the I2C module.
    //! \param ui8SlaveAddr 7-bit slave address
    //! \param bReceive flag indicating the type of communication with the slave
    //!
    //! This function configures the address that the I2C Master places on the
    //! bus when initiating a transaction.  When the \e bReceive parameter is set
    //! to \b true, the address indicates that the I2C Master is initiating a
    //! read from the slave; otherwise the address indicates that the I2C
    //! Master is initiating a write to the slave.
    //!
    //! \return None.
    //
    //*****************************************************************************
    void
    I2CMasterSlaveAddrSet(uint32_t ui32Base, uint8_t ui8SlaveAddr,
                          bool bReceive)
    {
        //
        // Check the arguments.
        //
        ASSERT(_I2CBaseValid(ui32Base));
        ASSERT(!(ui8SlaveAddr & 0x80));
    
        //
        // Set the address of the slave with which the master will communicate.
        //
        HWREG(ui32Base + I2C_O_MSA) = (ui8SlaveAddr << 1) | bReceive;
    }
    

  • Thanks cb1_mobile for taking a look. I perhaps should have removed the comments as they make the code longer. I do have a cheap logic analyzer I could use to check the signals. I Just thought I'd ask in case there is something missing or incorrect in my code.

    best regards
    michael
  • Michael - DO NOTE the insightful observation from Vendor's Charles.    (also my friend)      He knows these chips far better than we outsiders - and I did not (delve) into the potential for "missing" the Slave Address!    Such was a BIG mistake - on my part.    Great that Charles came to   our rescue.

    Minus the proper Slave Address - even the 'VERY BEST SCOPE & PULL-UP R's' - CANNOT SAVE YOU!      (Although they CANNOT HURT!)     Kudos to Vendor's Charles!

  • Hi,

    I have changed the slave address to #define SLAVE_ADDRESS 0x21 and setting breakpoints I can see the correct write and read address is in the I2C_MSA register.But I cannot see the command 'A' for heading information in the I2C_MDR register at any point.

  • It appears that you are extracting that data - entirely - via the MCU's Registers.    The fact that the MCU (may) have produced (proper) data - speaks (pardon) 'Not at all' - to that data's having safely & properly arrived at - and been processed by - your I2C Slave.

    Again - even a simple (even kludged) Scope - trumps MCU Register Reads - as a Diagnostic Aid.

    Under those (so limited) Diagnostic Conditions - can you find the 'Simplest Slave Register' - which enables its (subsequent) most brief (single byte) Output - when addressed again?       Your 'recovery of such Slave Data' - will 'Kill MANY Diagnostic birds - w/that SINGLE Stone!'     If you can address - then recover Slave Data - you have verified (at least for that transaction) - that "ALL is (likely) WELL, signal & protocol-wise."     (law-school teaches the 'hedge' of likely...)

  • In your code you are waiting for the slave to receive data. However, you are not in loopback mode as in the example you copied the code from. The slave will not receive data. As suggested by cb1, the scope is one of the best tools to diagnose the problem.


    I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_SEND);
    while(!(I2CSlaveStatus(I2C0_BASE) & I2C_SLAVE_ACT_RREQ))
    {
    }

    Please try and see if it makes a difference.
    I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_SEND);
    SysCtlDelay(100);
    while(I2CMasterBusy(I2C0_BASE))
    {
    }
  • Hi Guys,

    I got it working in part due to your reminding me I had copied code from the master_slave_loopback sample. I found this document which is excellent at explaining the functions in I2C.c
    It also had great detailed images of what the signal should look like for various activities.

    It was kind of a got it working halfway and wondering why the data seemed odd. I kept getting the same number twice. like 55 66 or 77. This was only the first byte being returned into both
    bytes because I think I2C_MASTER_CMD_SINGLE_RECEIVE cannot be used in a loop to receive 2 bytes. Correct me if I'm wrong. But it now receives the individual bytes using I2C_MASTER_CMD_BURST_RECEIVE_START and I2C_MASTER_CMD_BURST_RECEIVE_FINISH.

    The code is in need of tidying perhaps but here it is should anybody else be looking for help with using a TM4C with HMC6352 compass. Next I guess would be figuring how to write this as
    a nonblocking piece of code. Any tips?

    //*****************************************************************************
    // HMC6352.c with Tiva TM4C1233H6PM or LM4F120HQ5R using Tivaware library
    // Reads 2 bytes heading data from compass and prints to a terminal
    // the value will be between 0 and 3599 and is accurate to tenths of a degree
    //
    //*****************************************************************************
    #include <stdbool.h>
    #include <stdint.h>
    #include "inc/hw_i2c.h"
    #include "inc/hw_memmap.h"
    #include "inc/hw_types.h"
    #include "driverlib/gpio.h"
    #include "driverlib/i2c.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/uart.h"
    #include "utils/uartstdio.h"

    //*****************************************************************************
    // Number of I2C data packets to receive.
    //*****************************************************************************
    #define NUM_I2C_DATA 2
    //
    // Set the address for slave module. This is a 7-bit address sent in the
    // following format:
    //                      [A6:A5:A4:A3:A2:A1:A0:RS]
    //
    // A zero in the "RS" position of the first byte means that the master
    // transmits (sends) data to the selected slave, and a one in this position
    // means that the master receives data from the slave.
    //
    //*****************************************************************************
    #define SLAVE_ADDRESS 0x21
    //*****************************************************************************
    //
    // This function sets up UART0 to be used for a console to display information
    // as the example is running.
    //
    //*****************************************************************************
    void
    InitConsole(void)
    {
        //
        // Enable GPIO port A which is used for UART0 pins.
        // TODO: change this to whichever GPIO port you are using.
        //
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
        //
        // Configure the pin muxing for UART0 functions on port A0 and A1.
        // This step is not necessary if your part does not support pin muxing.
        // TODO: change this to select the port/pin you are using.
        //
        GPIOPinConfigure(GPIO_PA0_U0RX);
        GPIOPinConfigure(GPIO_PA1_U0TX);
        //
        // Enable UART0 so that we can configure the clock.
        //
        SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
        //
        // Use the internal 16MHz oscillator as the UART clock source.
        //
        UARTClockSourceSet(UART0_BASE, UART_CLOCK_PIOSC);
        //
        // Select the alternate (UART) function for these pins.
        // TODO: change this to select the port/pin you are using.
        //
        GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
        //
        // Initialize the UART for console I/O.
        //
        UARTStdioConfig(0, 115200, 16000000);
    }
    //*****************************************************************************
    //
    // Configure the I2C0 master.
    //
    //*****************************************************************************
    int
    main(void)
    {
        uint32_t pui32CmdTx;
        uint16_t pui16DataRx[NUM_I2C_DATA];
        uint16_t pui16Heading;

        SysCtlClockSet(SYSCTL_SYSDIV_10 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN |
                       SYSCTL_XTAL_16MHZ);
        //
        // The I2C0 peripheral must be enabled before use.
        //
        SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
        //
        // reset module
        //
        SysCtlPeripheralReset(SYSCTL_PERIPH_I2C0);
        //
        // For this example I2C0 is used with PortB[3:2].  The actual port and
        // pins used may be different on your part, consult the data sheet for
        // more information.  GPIO port B needs to be enabled so these pins can
        // be used.
        // TODO: change this to whichever GPIO port you are using.
        //
        SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
        //
        // Configure the pin muxing for I2C0 functions on port B2 and B3.
        // This step is not necessary if your part does not support pin muxing.
        // TODO: change this to select the port/pin you are using.
        //
        GPIOPinConfigure(GPIO_PB2_I2C0SCL);
        GPIOPinConfigure(GPIO_PB3_I2C0SDA);
        //
        // Select the I2C function for these pins.  This function will also
        // configure the GPIO pins pins for I2C operation, setting them to
        // open-drain operation with weak pull-ups.  Consult the data sheet
        // to see which functions are allocated per pin.
        // TODO: change this to select the port/pin you are using.
        //
        GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2);
        GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);
        //
        // Enable and initialize the I2C0 master module.  Use the system clock for
        // the I2C0 module.  The last parameter sets the I2C data transfer rate.
        // If false the data rate is set to 100kbps and if true the data rate will
        // be set to 400kbps.  For this example we will use a data rate of 100kbps.
        //
        I2CMasterInitExpClk(I2C0_BASE, SysCtlClockGet(), false);

        SysCtlDelay(10000);
        //
        // Tell the master module what address it will place on the bus when
        // communicating with the slave.  Set the address to SLAVE_ADDRESS
        // (as set in the slave module).  The receive parameter is set to false
        // which indicates the I2C Master is initiating a writes to the slave.  If
        // true, that would indicate that the I2C Master is initiating reads from
        // the slave.
        //
        I2CMasterSlaveAddrSet(I2C0_BASE, SLAVE_ADDRESS, false);
        //
        // Set up the serial console to use for displaying messages.  This is
        // just for this example program and is not needed for I2C operation.
        //
        InitConsole();
        //
        // Display the example setup on the console.
        //
        UARTprintf("Heading ->");
        
        pui32CmdTx = 0x41;
        //
        // Place the data to be sent in the data register
        //
        I2CMasterDataPut(I2C0_BASE, pui32CmdTx);
        SysCtlDelay(1000);
        //
        // Initiate send of data from the master.
        //
        I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_SEND_START);
        //SysCtlDelay(1000);
        //
        // Wait until the slave has received and acknowledged the data.
        //
        while(I2CMasterBusy(I2C0_BASE));

        while(1)
        {
            //
            // Modifiy the data direction to true, so that seeing the address will
            // indicate that the I2C Master is initiating a read from the slave.
            //
            I2CMasterSlaveAddrSet(I2C0_BASE, SLAVE_ADDRESS, true);
            //
            // Tell the master to read data.
            //
            I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_RECEIVE_START);
            //
            // Wait until the slave is done sending data.
            //
            while(I2CMasterBusy(I2C0_BASE));
            //
            // Read the data from the master.
            //
            pui16DataRx[0] = I2CMasterDataGet(I2C0_BASE);
            //
            // Display the data that the slave has received.
            //
            I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_BURST_RECEIVE_FINISH);

            while(I2CMasterBusy(I2C0_BASE));

            pui16DataRx[1] = I2CMasterDataGet(I2C0_BASE);

            pui16Heading = pui16DataRx[0] * 256 + pui16DataRx[1];

            UARTprintf("%d", pui16Heading);

            UARTprintf("\n");

            SysCtlDelay(5000000);
        }
    }