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.

i2c issue, Tiva Connected Launchpad

Other Parts Discussed in Thread: TM4C1294NCPDT

Hello again. I've got another question about the TM4C1294NCPDT / Tiva Connected Launchpad.

I'm prototyping a small expansion board for a Honeywill HIH-6121-021-001 temperature/humidity sensor over i2c. The Connected Launchpad pinout has an ideal spot for this on boosterpack 1 pins PL3, PL2, PL1, and PL0.

Layout

The HIH-6121 datasheet gives the pinout (p. 12 fig. 6).

I'm using PL3 as GPIO to power cycle the device. For 10 ms after power up the device can be put in "command mode."

  1. Vdd (3.3V) --- PL3 as GPIO; max current is 1mA (p. 8 table 4)
  2. Vss (GND) --- PL2 as GPIO
  3. SCL ------------ PL1 as i2c #2
  4. SDA ------------ PL0 as i2c #2

I'm prototyping it by connecting 4.7K ohm pull ups on PL1 and PL0, tied to PL3.

Problem

The I2C2 controller takes forever to get out of BUSY status on a very basic i2c command, the measurement request -- HIH-6121 i2c datasheet fig. 2 -- what should happen on the i2c bus:

  • i2c master: START
  • i2c master: 8 bits with LSB=0 for write and 7-bit address of 0x27
  • i2c slave: ACK
  • i2c master: STOP

What I'm seeing is that "done, err=00" is printed after at least 5 minutes (I don't know exactly how long it waits).

Code

Here's a simple example of what I'm doing. Once hih_flag & 4 is set, ROM_I2CMasterBusy() doesn't return 0 for quite a while.

I've tried a different i2c address, but the result is the same. Since no i2c slave is at that address I expect to see a NACK but I get "done, err=00".

#include <stdbool.h>
#include <stdint.h>

#include "inc/hw_ints.h"
#include "inc/hw_memmap.h"
#include "inc/hw_adc.h"
#include "inc/hw_types.h"
#include "inc/hw_udma.h"
#include "inc/hw_emac.h"
#include "inc/hw_i2c.h"
#include "driverlib/debug.h"
#include "driverlib/gpio.h"
#include "driverlib/interrupt.h"
#include "driverlib/pin_map.h"
#include "driverlib/rom.h"
#include "driverlib/sysctl.h"
#include "driverlib/uart.h"
#include "driverlib/adc.h"
#include "driverlib/udma.h"
#include "driverlib/emac.h"
#include "driverlib/i2c.h"


//*****************************************************************************
//
// The error routine that is called if the driver library encounters an error.
//
//*****************************************************************************
#ifdef DEBUG
void __error__(char *pcFilename, uint32_t ui32Line)
{
}
#endif

void UARTsend(char * str)
{
	while (*str) {
		ROM_UARTCharPut(UART0_BASE, *str++);
	}
}

volatile uint32_t hih_flag = 0;

void UART0IntHandler()
{
	uint32_t status = ROM_UARTIntStatus(UART0_BASE, true);
	ROM_UARTIntClear(UART0_BASE, status);
	while (ROM_UARTCharsAvail(UART0_BASE)) {
		char c = (char) ROM_UARTCharGetNonBlocking(UART0_BASE);
		ROM_UARTCharPutNonBlocking(UART0_BASE, c);

		if (c == 'h') {
			hih_flag |= 1;
		}
	}
}



const uint8_t lookup_hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

void u32tohex(char * out, uint32_t n)
{
	out[0] = lookup_hex[n >> 28]; out[1] = lookup_hex[(n >> 24) & 15]; out[2] = lookup_hex[(n >> 20) & 15]; out[3] = lookup_hex[(n >> 16) & 15];
	out[4] = lookup_hex[(n >> 12) & 15]; out[5] = lookup_hex[(n >> 8) & 15]; out[6] = lookup_hex[(n >> 4) & 15]; out[7] = lookup_hex[n & 15];
}

void u16tohex(char * out, uint32_t n)
{
	out[0] = lookup_hex[(n >> 12) & 15]; out[1] = lookup_hex[(n >> 8) & 15]; out[2] = lookup_hex[(n >> 4) & 15]; out[3] = lookup_hex[n & 15];
}

void u8tohex(char * out, uint32_t n)
{
	out[0] = lookup_hex[(n >> 4) & 15]; out[1] = lookup_hex[n & 15];
}








int main(void)
{
	uint32_t sysclock;
	do {
		sysclock = ROM_SysCtlClockFreqSet(SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480, 120*1000*1000);
	} while (!sysclock);

	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOL);
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPION);
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C2);	// I2C #2 is on GPIOL pins PL0 (SDA), PL1 (SCL), but note that PL0 and PL1 are set to hi-Z initially
	ROM_SysCtlDelay(1);

	// configure PORTF for GPIO
	ROM_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_4);
	ROM_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_0 | GPIO_PIN_1, 0);
	ROM_GPIOPinTypeGPIOOutput(GPIO_PORTN_BASE, GPIO_PIN_0 | GPIO_PIN_1);
	ROM_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_0 | GPIO_PIN_1, GPIO_PIN_0);

	ROM_GPIOPinTypeGPIOInput(GPIO_PORTL_BASE, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);	// set PL0, PL1, PL2, PL3 to hi-Z initially
	ROM_I2CMasterEnable(I2C2_BASE);

	ROM_GPIOPinConfigure(GPIO_PA0_U0RX);
	ROM_GPIOPinConfigure(GPIO_PA1_U0TX);
	ROM_GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
	ROM_UARTConfigSetExpClk(UART0_BASE, sysclock, 115200, UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE);

	ROM_IntMasterEnable();
	ROM_IntEnable(INT_UART0);
	ROM_UARTIntEnable(UART0_BASE, UART_INT_RX | UART_INT_RT);

	uint8_t hih_init = 2;
	uint8_t hih_address = 0x27;

	for (;;) {
		char str[16];
		if (hih_init > 1) {
			UARTsend("press h to init ");
			u8tohex(str, hih_address);
			str[2] = 0;
			UARTsend(str);
			UARTsend(": ");
			hih_init--;
		}

		if (hih_flag && hih_init) {
			hih_init = 0;
			ROM_GPIOPinTypeI2C(GPIO_PORTL_BASE, GPIO_PIN_0);
			ROM_GPIOPinTypeI2CSCL(GPIO_PORTL_BASE, GPIO_PIN_1);
			ROM_GPIOPinConfigure(GPIO_PL0_I2C2SDA);	// PL0 = HIH6120 pin 4 (SDA)
			ROM_GPIOPinConfigure(GPIO_PL1_I2C2SCL);	// PL1 = HIH6120 pin 3 (SCL)
			ROM_I2CMasterInitExpClk(I2C2_BASE, sysclock, false /*100kHz clock rate*/);

			ROM_GPIOPinTypeGPIOOutput(GPIO_PORTL_BASE, GPIO_PIN_2 | GPIO_PIN_3);	// PL2 = HIH6120 pin 2 (Vss = GND)
			ROM_GPIOPinWrite(GPIO_PORTL_BASE, GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_3);	// PL3 = HIH6120 pin 1 (Vdd = 3.3V)

			// note: at this point, PL3 supplies 3.3V to the HIH6120 so it is now powered up

			UARTsend(" power on\r\n");
		}

		if (hih_flag & 1) {
			hih_flag &= ~1;
			if (!(hih_flag & 4)) {
				hih_flag |= 4;
				ROM_I2CMasterSlaveAddrSet(I2C2_BASE, hih_address, false /*write to slave*/);
				while (ROM_I2CMasterBusy(I2C2_BASE)) {}

				// which command? QUICK_COMMAND or SINGLE_SEND?
#if 1
				ROM_I2CMasterControl(I2C2_BASE, I2C_MASTER_CMD_QUICK_COMMAND);	// just send address and look for ACK
#else
				ROM_I2CMasterControl(I2C2_BASE, I2C_MASTER_CMD_SINGLE_SEND);	// just send address and look for ACK
#endif

				while (ROM_I2CMasterBusy(I2C2_BASE)) {}

				u8tohex(str, ROM_I2CMasterErr(I2C2_BASE));
				str[2] = '\r'; str[3] = '\n'; str[4] = 0;
				UARTsend("wait for ack. err=");
				UARTsend(str);
			}
		}

		if ((hih_flag & 4) && !ROM_I2CMasterBusy(I2C2_BASE)) {
			hih_flag &= ~4;

			u8tohex(str, ROM_I2CMasterErr(I2C2_BASE));
			str[2] = '\r'; str[3] = '\n'; str[4] = 0;
			UARTsend(" done, err=");
			UARTsend(str);
		}
	}
}

  • Hello David

    Can you scope and check if the external device is holding the SCL Low?

    Also since you are using the Quick Command, can you check if the command is being sent properly by using a scope?

    Also please do try to use the 3.3V and GND from the board rather than using a GPIO!!!

    Regards

    Amit

  • Hi David,

    i see that you're waiting twice for "ROM_I2CMasterBusy(I2C2_BASE)". Which one is it hanging in?

    Your initialization looks correct, only the duplicate waiting in your sending part i find strange. Here is how my routine works, maybe it helps for comparison?

    i2cError_t i2c_write(i2cAddr_t addr, uint8_t value) {
    	i2cError_t error = { 0 };
    	I2CMasterSlaveAddrSet(I2C0_BASE, addr, false);
    	I2CMasterDataPut(I2C0_BASE, value);
    	I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_SINGLE_SEND);
    	while (I2CMasterBusy(I2C0_BASE))
    		;
    	error.value |= I2CMasterErr(I2C0_BASE);
    	return error;
    }

    Cheers

    Janos

  • Thanks, Janos and Amit. Those are good suggestions.

    I removed the i2c slave device, so the i2c bus just has pull up resistors. I hope to still be able to "scan" the bus by doing QUICK_COMMAND to an address and get a NACK (I2C_MCS_ADRACK).

    So here's where I'm at code-wise:

    #include <stdbool.h>
    #include <stdint.h>
    
    #include "inc/hw_ints.h"
    #include "inc/hw_memmap.h"
    #include "inc/hw_adc.h"
    #include "inc/hw_types.h"
    #include "inc/hw_udma.h"
    #include "inc/hw_emac.h"
    #include "inc/hw_i2c.h"
    #include "driverlib/debug.h"
    #include "driverlib/gpio.h"
    #include "driverlib/interrupt.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/rom.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/uart.h"
    #include "driverlib/adc.h"
    #include "driverlib/udma.h"
    #include "driverlib/emac.h"
    #include "driverlib/i2c.h"
    
    
    //*****************************************************************************
    //
    // The error routine that is called if the driver library encounters an error.
    //
    //*****************************************************************************
    #ifdef DEBUG
    void __error__(char *pcFilename, uint32_t ui32Line)
    {
    }
    #endif
    
    void UARTsend(char * str)
    {
    	while (*str) {
    		ROM_UARTCharPut(UART0_BASE, *str++);
    	}
    }
    
    volatile uint32_t hih_flag = 0;
    
    void UART0IntHandler()
    {
    	uint32_t status = ROM_UARTIntStatus(UART0_BASE, true);
    	ROM_UARTIntClear(UART0_BASE, status);
    	while (ROM_UARTCharsAvail(UART0_BASE)) {
    		char c = (char) ROM_UARTCharGetNonBlocking(UART0_BASE);
    		ROM_UARTCharPutNonBlocking(UART0_BASE, c);
    
    		if (c == 'h') {
    			hih_flag |= 1;
    		}
    	}
    }
    
    
    
    const uint8_t lookup_hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
    
    void u32tohex(char * out, uint32_t n)
    {
    	out[0] = lookup_hex[n >> 28]; out[1] = lookup_hex[(n >> 24) & 15]; out[2] = lookup_hex[(n >> 20) & 15]; out[3] = lookup_hex[(n >> 16) & 15];
    	out[4] = lookup_hex[(n >> 12) & 15]; out[5] = lookup_hex[(n >> 8) & 15]; out[6] = lookup_hex[(n >> 4) & 15]; out[7] = lookup_hex[n & 15];
    }
    
    void u16tohex(char * out, uint32_t n)
    {
    	out[0] = lookup_hex[(n >> 12) & 15]; out[1] = lookup_hex[(n >> 8) & 15]; out[2] = lookup_hex[(n >> 4) & 15]; out[3] = lookup_hex[n & 15];
    }
    
    void u8tohex(char * out, uint32_t n)
    {
    	out[0] = lookup_hex[(n >> 4) & 15]; out[1] = lookup_hex[n & 15];
    }
    
    
    
    
    
    
    
    
    int main(void)
    {
    	uint32_t sysclock;
    	do {
    		sysclock = ROM_SysCtlClockFreqSet(SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480, 120*1000*1000);
    	} while (!sysclock);
    
    	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
    	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOL);
    	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPION);
    	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
    	ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C2);	// I2C #2 is on GPIOL pins PL0 (SDA), PL1 (SCL), but note that PL0 and PL1 are set to hi-Z initially
    	ROM_SysCtlDelay(1);
    
    	// configure PORTF for GPIO
    	ROM_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_4);
    	ROM_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_0 | GPIO_PIN_1, 0);
    	ROM_GPIOPinTypeGPIOOutput(GPIO_PORTN_BASE, GPIO_PIN_0 | GPIO_PIN_1);
    	ROM_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_0 | GPIO_PIN_1, GPIO_PIN_0);
    
    	ROM_GPIOPinTypeGPIOInput(GPIO_PORTL_BASE, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3);	// set PL0, PL1, PL2, PL3 to hi-Z initially
    	ROM_I2CMasterEnable(I2C2_BASE);
    
    	ROM_GPIOPinConfigure(GPIO_PA0_U0RX);
    	ROM_GPIOPinConfigure(GPIO_PA1_U0TX);
    	ROM_GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
    	ROM_UARTConfigSetExpClk(UART0_BASE, sysclock, 115200, UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE);
    
    	ROM_IntMasterEnable();
    	ROM_IntEnable(INT_UART0);
    	ROM_UARTIntEnable(UART0_BASE, UART_INT_RX | UART_INT_RT);
    
    	uint8_t hih_address = 0x26;
    
    	for (;;) {
    		char str[16];
    		UARTsend("press h to init ");
    		u8tohex(str, hih_address);
    		str[2] = 0;
    		UARTsend(str);
    		UARTsend(": ");
    
    		while (!(hih_flag & 1)) ;
    		hih_flag &= ~1;
    
    		ROM_GPIOPinTypeI2C(GPIO_PORTL_BASE, GPIO_PIN_0);
    		ROM_GPIOPinTypeI2CSCL(GPIO_PORTL_BASE, GPIO_PIN_1);
    		ROM_GPIOPinConfigure(GPIO_PL0_I2C2SDA);	// PL0 = HIH6120 pin 4 (SDA)
    		ROM_GPIOPinConfigure(GPIO_PL1_I2C2SCL);	// PL1 = HIH6120 pin 3 (SCL)
    		ROM_I2CMasterInitExpClk(I2C2_BASE, sysclock, false /*100kHz clock rate*/);
    
    		ROM_GPIOPinTypeGPIOOutput(GPIO_PORTL_BASE, GPIO_PIN_2 | GPIO_PIN_3);	// PL2 = HIH6120 pin 2 (Vss = GND)
    		ROM_GPIOPinWrite(GPIO_PORTL_BASE, GPIO_PIN_2 | GPIO_PIN_3, GPIO_PIN_3);	// PL3 = HIH6120 pin 1 (Vdd = 3.3V)
    
    		// note: at this point, PL3 supplies 3.3V via PL3 to the pullups on PL0 and PL1
    		UARTsend(" power on\r\n");
    
    		ROM_I2CMasterSlaveAddrSet(I2C2_BASE, hih_address, false /*write to slave*/);
    		ROM_I2CMasterControl(I2C2_BASE, I2C_MASTER_CMD_QUICK_COMMAND);	// just send address and look for ACK
    
    #if 0
    		while (ROM_I2CMasterBusy(I2C2_BASE)) {}
    		u8tohex(str, ROM_I2CMasterErr(I2C2_BASE));
    		str[2] = '\r'; str[3] = '\n'; str[4] = 0;
    		UARTsend("wait for ack. err=");
    		UARTsend(str);
    #endif
    
    		while (ROM_I2CMasterBusy(I2C2_BASE)) {}
    		u8tohex(str, ROM_I2CMasterErr(I2C2_BASE));
    		str[2] = '\r'; str[3] = '\n'; str[4] = 0;
    		UARTsend(" done, err=");
    		UARTsend(str);
    
    		hih_address++;
    	}
    }

    I get this output on the serial port:

    press h to init 26: h power on
     done, err=00
    press h to init 27: h power on

    Using ICDI I interrupted the code to find where it stops - line 154: while (ROM_I2CMasterBusy(I2C2_BASE)) {}

    I guess that's my first question: I'm only waiting for Busy once (thanks Janos) but it never clears - what should I do?

    I also tried wrapping an if() statement around all the lines between hih_flag &= ~1 and ROM_GPIOPinWrite() so they only get executed once, since they're just initialization. It still gets stuck on line 154.

    Secondly, how can I get I2C_MCS_ADRACK responses instead of 00?

  • Hi David,

    i just now noticed in your code, that you keep initializing the I2C interface in your scanner loop. I don't know what that does to the state machine, but it can't be good. Initialize once, then loop.

    I am working with the TM4C123 parts and i don't know if there are any differences involved at this front (don't think there are many). I prepared a little scanner example which works for me:

    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    
    #include "inc/hw_memmap.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/gpio.h"
    #include "driverlib/i2c.h"
    #include "driverlib/pin_map.h"
    
    int main(void) {
    
    	SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL | SYSCTL_XTAL_16MHZ | SYSCTL_OSC_MAIN);
    
    	SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
    	SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
    	GPIOPinConfigure(GPIO_PB2_I2C0SCL);
    	GPIOPinConfigure(GPIO_PB3_I2C0SDA);
    	GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);
    	GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2);
    	I2CMasterInitExpClk(I2C0_BASE, SysCtlClockGet(), true);
    
    	uint32_t addr;
    	for (addr = 1; addr < 128; addr++) {
    		printf("Scanning Addr 0x%02X:   ", addr);
    		I2CMasterSlaveAddrSet(I2C0_BASE, addr, false);
    		I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_QUICK_COMMAND);
    		while (I2CMasterBusy(I2C0_BASE));
    
    		uint32_t error = I2CMasterErr(I2C0_BASE);
    		if (error & I2C_MASTER_ERR_ADDR_ACK) {
    			printf("Error: 0x%02X\n", error);
    		}
    		else {
    			printf("ACK.\n");
    		}
    	}
    	while(1);
    }
    

    It gives me following output:

    Scanning Addr 0x01:   Error: 0x0C
    Scanning Addr 0x02:   Error: 0x0C
    Scanning Addr 0x03:   Error: 0x0C
    Scanning Addr 0x04:   Error: 0x0C
    Scanning Addr 0x05:   Error: 0x0C
    Scanning Addr 0x06:   Error: 0x0C
    Scanning Addr 0x07:   Error: 0x0C
    Scanning Addr 0x08:   Error: 0x0C
    Scanning Addr 0x09:   Error: 0x0C
    Scanning Addr 0x0A:   Error: 0x0C
    Scanning Addr 0x0B:   Error: 0x0C
    Scanning Addr 0x0C:   Error: 0x0C
    Scanning Addr 0x0D:   Error: 0x0C
    Scanning Addr 0x0E:   Error: 0x0C
    Scanning Addr 0x0F:   Error: 0x0C
    Scanning Addr 0x10:   Error: 0x0C
    Scanning Addr 0x11:   Error: 0x0C
    Scanning Addr 0x12:   Error: 0x0C
    Scanning Addr 0x13:   Error: 0x0C
    Scanning Addr 0x14:   Error: 0x0C
    Scanning Addr 0x15:   Error: 0x0C
    Scanning Addr 0x16:   Error: 0x0C
    Scanning Addr 0x17:   Error: 0x0C
    Scanning Addr 0x18:   Error: 0x0C
    Scanning Addr 0x19:   Error: 0x0C
    Scanning Addr 0x1A:   Error: 0x0C
    Scanning Addr 0x1B:   Error: 0x0C
    Scanning Addr 0x1C:   Error: 0x0C
    Scanning Addr 0x1D:   ACK.
    Scanning Addr 0x1E:   Error: 0x0C
    Scanning Addr 0x1F:   Error: 0x0C
    Scanning Addr 0x20:   Error: 0x0C
    Scanning Addr 0x21:   Error: 0x0C
    Scanning Addr 0x22:   Error: 0x0C
    Scanning Addr 0x23:   Error: 0x0C
    Scanning Addr 0x24:   Error: 0x0C
    Scanning Addr 0x25:   Error: 0x0C
    Scanning Addr 0x26:   Error: 0x0C
    Scanning Addr 0x27:   Error: 0x0C
    Scanning Addr 0x28:   Error: 0x0C
    Scanning Addr 0x29:   Error: 0x0C
    Scanning Addr 0x2A:   Error: 0x0C
    Scanning Addr 0x2B:   Error: 0x0C
    Scanning Addr 0x2C:   Error: 0x0C
    Scanning Addr 0x2D:   Error: 0x0C
    Scanning Addr 0x2E:   Error: 0x0C
    Scanning Addr 0x2F:   Error: 0x0C
    Scanning Addr 0x30:   Error: 0x0C
    Scanning Addr 0x31:   Error: 0x0C
    Scanning Addr 0x32:   Error: 0x0C
    Scanning Addr 0x33:   Error: 0x0C
    Scanning Addr 0x34:   Error: 0x0C
    Scanning Addr 0x35:   Error: 0x0C
    Scanning Addr 0x36:   Error: 0x0C
    Scanning Addr 0x37:   Error: 0x0C
    Scanning Addr 0x38:   Error: 0x0C
    Scanning Addr 0x39:   ACK.
    Scanning Addr 0x3A:   Error: 0x0C
    Scanning Addr 0x3B:   Error: 0x0C
    Scanning Addr 0x3C:   Error: 0x0C
    Scanning Addr 0x3D:   Error: 0x0C
    Scanning Addr 0x3E:   Error: 0x0C
    Scanning Addr 0x3F:   Error: 0x0C
    Scanning Addr 0x40:   Error: 0x0C
    Scanning Addr 0x41:   Error: 0x0C
    Scanning Addr 0x42:   Error: 0x0C
    Scanning Addr 0x43:   Error: 0x0C
    Scanning Addr 0x44:   Error: 0x0C
    Scanning Addr 0x45:   Error: 0x0C
    Scanning Addr 0x46:   Error: 0x0C
    Scanning Addr 0x47:   Error: 0x0C
    Scanning Addr 0x48:   ACK.
    Scanning Addr 0x49:   ACK.
    Scanning Addr 0x4A:   Error: 0x0C
    Scanning Addr 0x4B:   Error: 0x0C
    Scanning Addr 0x4C:   ACK.
    Scanning Addr 0x4D:   Error: 0x0C
    Scanning Addr 0x4E:   Error: 0x0C
    Scanning Addr 0x4F:   Error: 0x0C
    Scanning Addr 0x50:   Error: 0x0C
    Scanning Addr 0x51:   Error: 0x0C
    Scanning Addr 0x52:   Error: 0x0C
    Scanning Addr 0x53:   Error: 0x0C
    Scanning Addr 0x54:   Error: 0x0C
    Scanning Addr 0x55:   Error: 0x0C
    Scanning Addr 0x56:   Error: 0x0C
    Scanning Addr 0x57:   Error: 0x0C
    Scanning Addr 0x58:   Error: 0x0C
    Scanning Addr 0x59:   Error: 0x0C
    Scanning Addr 0x5A:   ACK.
    Scanning Addr 0x5B:   ACK.
    Scanning Addr 0x5C:   Error: 0x0C
    Scanning Addr 0x5D:   Error: 0x0C
    Scanning Addr 0x5E:   Error: 0x0C
    Scanning Addr 0x5F:   Error: 0x0C
    Scanning Addr 0x60:   Error: 0x0C
    Scanning Addr 0x61:   Error: 0x0C
    Scanning Addr 0x62:   Error: 0x0C
    Scanning Addr 0x63:   Error: 0x0C
    Scanning Addr 0x64:   Error: 0x0C
    Scanning Addr 0x65:   Error: 0x0C
    Scanning Addr 0x66:   Error: 0x0C
    Scanning Addr 0x67:   Error: 0x0C
    Scanning Addr 0x68:   Error: 0x0C
    Scanning Addr 0x69:   Error: 0x0C
    Scanning Addr 0x6A:   Error: 0x0C
    Scanning Addr 0x6B:   Error: 0x0C
    Scanning Addr 0x6C:   Error: 0x0C
    Scanning Addr 0x6D:   Error: 0x0C
    Scanning Addr 0x6E:   Error: 0x0C
    Scanning Addr 0x6F:   Error: 0x0C
    Scanning Addr 0x70:   Error: 0x0C
    Scanning Addr 0x71:   Error: 0x0C
    Scanning Addr 0x72:   Error: 0x0C
    Scanning Addr 0x73:   Error: 0x0C
    Scanning Addr 0x74:   Error: 0x0C
    Scanning Addr 0x75:   Error: 0x0C
    Scanning Addr 0x76:   Error: 0x0C
    Scanning Addr 0x77:   Error: 0x0C
    Scanning Addr 0x78:   Error: 0x0C
    Scanning Addr 0x79:   Error: 0x0C
    Scanning Addr 0x7A:   Error: 0x0C
    Scanning Addr 0x7B:   Error: 0x0C
    Scanning Addr 0x7C:   Error: 0x0C
    Scanning Addr 0x7D:   Error: 0x0C
    Scanning Addr 0x7E:   Error: 0x0C
    Scanning Addr 0x7F:   Error: 0x0C
    

    Cheers

    Janos

  • Hi Janos,


    Thanks! I tried out your code and was able to get it to work fairly easily. I did make some changes, so here's what I ended up with:

    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    #include "inc/hw_memmap.h"
    #include "inc/hw_ints.h"
    #include "driverlib/sysctl.h"
    #include "driverlib/gpio.h"
    #include "driverlib/i2c.h"
    #include "driverlib/pin_map.h"
    #include "driverlib/rom.h"
    #include "driverlib/uart.h"
    
    const uint8_t lookup_hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
    void u8tohex(char * out, uint32_t n)
    {
        out[0] = lookup_hex[(n >> 4) & 15]; out[1] = lookup_hex[n & 15]; out[2] = 0;
    }
    
    void UARTsend(char * str)
    {
        while (*str) {
            ROM_UARTCharPut(UART0_BASE, *str++);
        }
    }
    
    int main(void) {
        uint32_t sysclock;
        do {
            sysclock = ROM_SysCtlClockFreqSet(SYSCTL_XTAL_25MHZ | SYSCTL_OSC_MAIN | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_480, 120*1000*1000);
        } while (!sysclock);
        ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0);
        ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB);
        ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA);
        ROM_SysCtlPeripheralEnable(SYSCTL_PERIPH_UART0);
        while (!ROM_SysCtlPeripheralReady(SYSCTL_PERIPH_I2C0)) ;
        while (!ROM_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB)) ;
        while (!ROM_SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOA)) ;
        while (!ROM_SysCtlPeripheralReady(SYSCTL_PERIPH_UART0)) ;
        ROM_GPIOPinConfigure(GPIO_PB2_I2C0SCL);
        ROM_GPIOPinConfigure(GPIO_PB3_I2C0SDA);
        ROM_GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_3);
        ROM_GPIOPinTypeI2CSCL(GPIO_PORTB_BASE, GPIO_PIN_2);
        ROM_I2CMasterInitExpClk(I2C0_BASE, sysclock, true);
        ROM_GPIOPinConfigure(GPIO_PA0_U0RX);
        ROM_GPIOPinConfigure(GPIO_PA1_U0TX);
        ROM_GPIOPinTypeUART(GPIO_PORTA_BASE, GPIO_PIN_0 | GPIO_PIN_1);
        ROM_UARTConfigSetExpClk(UART0_BASE, sysclock, 115200, UART_CONFIG_WLEN_8 | UART_CONFIG_STOP_ONE | UART_CONFIG_PAR_NONE);
    
        uint32_t addr;
        for (addr = 1; addr < 128; addr++) {
            UARTsend("Scanning Addr ");
            char str[3];
            u8tohex(str, addr);
            UARTsend(str);
            UARTsend(":   ");
            ROM_I2CMasterSlaveAddrSet(I2C0_BASE, addr, false);
            ROM_I2CMasterControl(I2C0_BASE, I2C_MASTER_CMD_QUICK_COMMAND);
            while (ROM_I2CMasterBusy(I2C0_BASE));
            uint32_t error = ROM_I2CMasterErr(I2C0_BASE);
            if (error & I2C_MASTER_ERR_ADDR_ACK) {
                UARTsend("Error: ");
                u8tohex(str, error);
                UARTsend(str);
                UARTsend("\r\n");
            }
            else {
                UARTsend("ACK.\r\n");
            }
        }
        while(1);
    }

    This is definitely progress. The output is:

    Scanning Addr 01:   ACK.
    Scanning Addr 02:   ACK.
    Scanning Addr 03:   ACK.
    ...
    Scanning Addr 7e:   ACK.
    Scanning Addr 7f:   ACK.

    Any ideas on how to get Error 0C instead of ACK? I double checked with a scope and the pullup resistors on PB2 and PB3 are pulling the lines up to 3.3V when the bus is idle.

  • Hello David

    did you check on the scope if during the transaction for the non matching I2c addresses is the slave sending a NACK?

    Regards

    Amit

  • Hi Amit,

    Thanks for the suggestion. I am seeing a NACK with the scope.

    I'm testing it with nothing but pullup resistors connected. The SDA line gets pulled up to 3.3V after the 8-bit address (that should be a NACK, yes?).

    But I2C_MCS_ADRACK is not set.

    Regards,

    David

  • Hello David,

    The issue is that when the NACK is set then action of reading the I2CMasterBusy causes the NACK bit to get cleared. I am checking why this was not documented?

    Alternatively you can use the I2CMRIS bit to read the NACK condition and then clear it using the I2CMICR.

    uin32_t ui32Error = I2CMasterIntStatus(I2C0_BASE,false)

    if((ui32Error & I2C_MASTER_INT_NACK) == I2C_MASTER_INT_NACK)

    {

     UARTsend("error \n");

    }

    else

    {

     UARTsend("ACK\n");

    }

    I2CMasterIntClearEx(I2C0_BASE,ui32Error);

    Regards

    Amit

  • hello david ,

      This is code for I2c initialization,i2cBus Scan,I2c Read,write .I am Using lm3s microcontroller. I had one EEPROOM conneted via I2c .its working code.

      void Configure_I2C(void)
    {
    SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C0) ;
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB) ;

    GPIOPinConfigure(GPIO_PB2_I2C0SCL);
    GPIOPinConfigure(GPIO_PB3_I2C0SDA);
    GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_2 | GPIO_PIN_3) ;

    GPIODirModeSet(GPIO_PORTB_BASE, GPIO_PIN_3, GPIO_DIR_MODE_HW);
    GPIODirModeSet(GPIO_PORTB_BASE, GPIO_PIN_2, GPIO_DIR_MODE_HW);
    // Enable and initialise 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_MASTER_BASE, SysCtlClockGet(), 1); // 400kbps if 3rd args is 0 them 100Khz and if 1 then 400KHz
    I2CMasterEnable(I2C0_MASTER_BASE);
    }

    unsigned long
    I2CBusScan(unsigned long ulI2CBase)
    {
    unsigned char ucProbeAdress;
    unsigned long ucerrorstate;

    //
    // Wait until master module is done transferring.
    //
    while(I2CMasterBusy(ulI2CBase))
    {
    };

    //
    // I2C Addresses are 7-bit values
    // probe the address range of 0 to 127 to find I2C slave devices on the bus
    //
    for (ucProbeAdress = 0; ucProbeAdress < 127; ucProbeAdress++)
    {
    //
    // Tell the master module what address it will place on the bus when
    // writing to the slave.
    //
    I2CMasterSlaveAddrSet(ulI2CBase, ucProbeAdress, false);
    SysCtlDelay(TIMER_1_MS);

    //
    // Place the command to be sent in the data register.
    //
    I2CMasterDataPut(ulI2CBase, 0x00);

    //
    // Initiate send of data from the master.
    //
    I2CMasterControl(ulI2CBase, I2C_MASTER_CMD_BURST_SEND_START);

    //
    // Make some delay
    //
    SysCtlDelay(TIMER_1_MS);

    //
    // Read the I2C Master Control/Status (I2CMCS) Register to a local
    // variable
    //
    ucerrorstate = I2CMasterErr(ulI2CBase);

    //
    // Examining the content I2C Master Control/Status (I2CMCS) Register
    // to see if the ADRACK-Bit (Acknowledge Address) is TRUE (1)
    // ( 1: The transmitted address was not acknowledged by the slave)
    //
    if(ucerrorstate & I2C_MASTER_ERR_ADDR_ACK)
    {
    //
    // device at selected address did not acknowledge --> there's no device
    // with this address present on the I2C bus
    //
    //
    // Print a message to Stdio
    //
    //UARTprintf("Address not found: 0x%2x - %3d\n",ucProbeAdress,ucProbeAdress);
    //
    // Make some delay
    //
    //ROM_SysCtlDelay(1500000);
    }

    //
    // ( 0: The transmitted address was acknowledged by the slave)
    //
    else
    {
    //
    // device at selected address acknowledged --> there is a device
    // with this address present on the I2C bus
    //
    //
    // Print a message to Stdio
    //
    UARTprintf("Address found: 0x%2x - %3d\n\r",ucProbeAdress,ucProbeAdress);

    //
    // Make some delay
    //
    SysCtlDelay(TIMER_1_MS);
    }
    }

    //
    // End transfer of data from the master.
    //
    I2CMasterControl(ulI2CBase, I2C_MASTER_CMD_BURST_RECEIVE_FINISH);

    //
    // Print a message to Stdio
    //
    UARTprintf("I2C Bus-Scan done...\n");

    //
    // Return 1 if there is no error.
    //
    return TRUE;
    }

  • Hi Amit,

    Thanks! Reading HWREG(I2C0_BASE + I2C_O_MRIS) was the right answer for an i2c scan using I2C_MASTER_CMD_QUICK_COMMAND.

    Regards,

    David

  • Hi Amit,

    The thought crossed my mind that if I2C_O_MCS gets cleared after a read, maybe that should be on the wiki?

    I looked at http://processors.wiki.ti.com/index.php/Tiva_C_Series_LaunchPad and http://processors.wiki.ti.com/index.php/Tiva_C_Series_TM4C1294_LaunchPad but they don't seem like a good place to add in-depth notes like this.

    I was thinking of an "Implementing I2C Master" app note.

    Anyway, thanks for the help!

    Regards,

    David

  • Hello David,

    I am running the same with the design team to understand why the datasheet does not mention this behavior.

    Regards

    Amit

  • Hi Amit,


    If I understand you correctly then one may choose to ask whether the controller is busy or reading an acknowledge status from the last transmission? Am I correct in understanding that you can not do both within the same scope?

    So, if we want to check the acknowledge bit we must first send the command. Then wait until the transmission is complete without polling the controller, and then read the acknowledge status?

    What we are currently doing is polling the controller to know when to read the acknowledge status. This is a bloking operation, but it simplifies the code by being able to return the status information within the same task. The polling was a trade-off between code complexity and speed, and we are unfortunately not in a position to redesign our architecture. Re-writing the code to run via interrupt and having to modify callers to accomodate task switching is not a small task.


    Could you please elaborate further on this HW bug?

    Kind regards,

    Christian

  • Hello Christian,

    1. The BUSY and the ACK Status are in the same register. Reading the ACK status clears the bit, which unfortunately happens since the BUSY Status also has to be read and will be made 0 on STOP condition by which time the ACK which was showing 1 for NACK gets cleared.

    2. That is one way of doing. The other is to use the I2CMRIS

    3. Using an interrupt may be complicated as the code would need to be reworked... As for the "bug" the #1 actually covers it...

    Regards

    Amit

  • Amit,

    I'm seeing something even worse - The NACK bits don't even get set - I'm using this code to poll:

    do  {
       status = HWREG(base + I2C_O_MCS);
       if ( last_status != status ) {
       usprintf(trace_scribble,"Stat %x", status );
       trace_add(trace_scribble);
       last_status=status;
    } while ( status & I2C_MCS_BUSY );



    So I'm logging the MCS register every time it changes.     Normal Nack MCS register flow is 41->47.   That works.

    If I do this a lot (5-10 times), I see 41->1->20 eventually.  The MCS register goes from run to idle without ever setting the NACK bit.

     

  • Hello R

    I see a misplaced { with the if statement. Can you please check the same?

    Regards

    Amit

  • Thats just a code excerpt.   The code works - it records and responds to changes in the I2C_MCS register.

    Its the register that behaves badly. 

  • Here is the whole thing:

    uint32_t deadline = HWREG(SPIN_TIMER_BASE + TIMER_O_TAV) + \
    TIMER_MHZ *timeout_usec;

    do {
    status = HWREG(base + I2C_O_MCS);
    if ( last_status != status ) {
    // usprintf(trace_scribble,"Stat %x", status ); trace_add(trace_scribble);
    last_status=status;
    }

    // The data sheet claims that the status bits are bad if the
    // busy bit is set. Thats clearly nonsense.
    switch( status ) {
    case 0x01: // Running. Do nothing
    case 0x41: // Bus busy, and running.
    break;
    case 0x20: // We have achieved IDLE. All done.
    return(0);
    break;
    case 0x47: // Address ACK Error.
    return(-10);
    break;
    default: //
    usprintf(trace_scribble,"Stat! %x", status );
    trace_add(trace_scribble);
    return(-11);
    break;
    }
    if ( HWREG(SPIN_TIMER_BASE + TIMER_O_TAV) > deadline ) return(-12);
    } while ( status & I2C_MCS_BUSY );
    return(0);

  • Hello R Sexton,

    Can you do a print of the Status irrespective of the switch statement and have a Scope/LA plot also side by side. That would greatly aid the debug!!!

    Regards

    Amit

  • I can't do an unconditional print, as the register is being polled at a megahertz or more.     I've already moved on to attempting a workaround based upon using/clearing the MRIS register.   That doesn't get me there either. 

    I can make the I2C_MSC register fail to report a NACK on demand.  I do this by probing a device that is not on the bus.   Eventually I'll see a NACK on the wire, and the i2c hardware doesn't report it.

    It usually takes at least 100k-1M  tries, but it will eventually do it.  If I monitor the raw interrupt bit instead, I eventually get a failure too.   It looks broken to me.

    Code below.  You will have to remove the timer stuff if you don't have a 64-bit timer handy.

    uint32_t status_ring[10];
    
    int i2c_spin(unsigned long base, int timeout_usec, int flush) {
    	uint32_t status = HWREG(base + I2C_O_MCS);;
    	uint32_t last_status = status;
    	int i;
    	
    	for( i = 0; i < 10; i++ ) status_ring[i] = 0; 
    	i = 0;
    	
     	long long deadline = timer64value() + (TIMER_MHZ *timeout_usec);
    	
    	usprintf(trace_scribble,"STAT %2x %d", status, HWREG(SPIN_TIMER_BASE + TIMER_O_TAV) );
    	trace_add(trace_scribble); status_ring[i++] = status;
    
    	// Add a spin-loop while we wait for the interface to leave idle.
    	while ( status == 0x20 ) { 
    		if ( timer64value() > deadline ) return(-11);
    		status = HWREG(base + I2C_O_MCS);
    		if ( last_status != status ) {
    			usprintf(trace_scribble,"stat %2x %d", status, HWREG(SPIN_TIMER_BASE + TIMER_O_TAV));
    			trace_add(trace_scribble); status_ring[i++] = status;
    			last_status=status;
    			}
    		}
    
    	while ( status & I2C_MCS_BUSY ) { 
    		
    		// The data sheet claims that the status bits are bad if the 
    		// busy bit is set.  Thats clearly nonsense.	
    		switch( status ) {
    			case 0x01: // Running.  Do nothing
    			case 0x41: // Bus busy, and running.
    				break;
    			case 0x20: // We have achieved IDLE.  All done.
    				return(0);
    				break;
    			case 0x07: // Address ACK Error.
    			case 0x47: 
    				return(status * -100);
    				break;
    			default:  // 
    				usprintf(trace_scribble,"stat! %x", status );
    				trace_add(trace_scribble); 	status_ring[i++] = status;
    
    				return(status * -100);			
    				break;				
    			}
    		
    		if ( timer64value() > deadline ) return(-12);
    
    status = HWREG(base + I2C_O_MCS); if ( HWREG(base + I2C_O_MRIS) & I2C_MRIS_NACKRIS ) { status |= ( I2C_MCS_ERROR | I2C_MCS_ADRACK ); HWREG(base + I2C_O_MICR) |= I2C_MICR_NACKIC; } if ( last_status != status ) { usprintf(trace_scribble,"stat %2x %d", status, HWREG(SPIN_TIMER_BASE + TIMER_O_TAV)); trace_add(trace_scribble); status_ring[i++] = status; last_status=status; } } if ( status != 0x20 ) return( status * -100 ); else return(0); } // if ( !flush && ( HWREG(base + I2C_O_MBLEN) != HWREG(base + I2C_O_MBCNT)) ) return(0); int i2c_spin_wait(unsigned long base, int timeout_usec) { return(i2c_spin(base, timeout_usec, 1)); }
    int i2c_spin_wait_bus(unsigned long base, int timeout_usec) {
     	long long deadline = timer64value() + TIMER_MHZ *timeout_usec;
    
    	// Special Case.  This isn't a multi-master system.
    	if ( ! ROM_I2CMasterBusBusy(base) ) return(0);
    	 	
     	while(ROM_I2CMasterBusBusy(base)) {
        	if ( timer64value() > deadline ) {
        		return(timeout_usec);
        		break;
        		}
      		}  
        return(0); // Success
    	}
    
    int i2c_probe(unsigned long base, unsigned long addr) {
    
    	uint8_t buf;
    	int ret;
    	
    	if ( i2c_spin_wait_bus(base, I2C_TIMEOUT_USEC) != 0 ) { return(-1); }
    	
    	I2CMasterSlaveAddrSet(base, addr, true);
    	
    	usprintf(trace_scribble,"Probe   %d", HWREG(SPIN_TIMER_BASE + TIMER_O_TAV) );
    	trace_add(trace_scribble);
    	
    	I2CMasterControl(base, I2C_MASTER_CMD_SINGLE_RECEIVE);
    		// This timeout should be generous.  An interrupt could show up at exactly 
    		// the wrong time and screw up the timeout.
            if ( (ret = i2c_spin(base,10*1000,1)) != 0 ) {
            return(ret);
            }
    		
    	// Read and pitch the garbage byte that may be in the FIFO.
    	I2CFIFODataGetNonBlocking(base,&buf);
    	return(0);
    

    }

  • Hello R Sexton,

    This is impossible.

    "It usually takes at least 100k-1M  tries, but it will eventually do it.  If I monitor the raw interrupt bit instead, I eventually get a failure too.   It looks broken to me."

    The I2C Controller will detect a NACK every time in both MCS and MRIS.

    Since you are using TM4C129, are you sure you have the System Clock and I2C Controller correctly configured?

    Regards

    Amit

  • Hi David,

    I am using TIVA C series Microcontroller TM4C1294ncpdt. As Amit mentioned i tried by using Raw Interrupt Status Register to know the Acknowledgement Status but still its not working for me.

    Can you please post the source code that u have tried which is working fine for u.

    Thanks,

    Jeke.

  • Hello R Sexton,

    For clarification: If the MCS register is polled then 1 in 100K-1M a NAK gets missed. If the RIS is polled then it never gets missed. Is that correct?

    Regards
    Amit
  • Thats correct.   The only bit in the MCS register that works (as in, your code can reliably do something with it) is the BUSY bit.   Its entirely possible that the NAK bit is self-clearing while my system is servicing an interrupt.   I can't turn off interrupts in my world.   

    So the working recipe in my case is to spin on the I2C_MCS_BUSY bit until it clears, and then check RIS to see how the transaction ended.

  • Hello R Sexton

    There is going to be an errata as we have found a corner condition, in which polling of the MCS may actually cause the NACK RIS not to get set. The window is very narrow and may not be reproducible in silicon easily. Since you are using interrupts, it would be better that the DATA interrupt be used in place of BUSY polling to get around the issue altogether

    Regards
    Amit