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.

Resetting an I2C slave

I am controlling a Maxim DS1845 I2C digital potentiometer with an F2617.

I have discovered that if an I2C transfer sequence is interruptet for some reason, such as by me pressing the Debug button, the slave will become unresponsive, and so far only a powercycle of the slave has been able to fix this.

The Maxim DS1845 datasheet tells of the following reset procedure:

-------

2-Wire Interface Reset: After any interruption in protocol, power loss, or system reset, the following steps reset the DS1845.

1. Clock up to nine cycles.

2. Look for SDA high in each cycle while SCL is high.

3. Create a start condition while SDA is high.

-------

Any way to do this aside from turning the I2C lines into GPIO output and pulling the strings manually?

Thanks :)

  • A dummy start/address/stop cycle with the I2C hardware will do exactly that. Just that it will always clock 9 cycles.
    The described method will be the fastest oen if it is unclear whether the current transfer is read or write and wher ein the process you are. E.g. after a reset of the master. If you knwo where you are, usually simply send a stop after the current byte transfer.

    However, some slaves hold SCL low if busy ("Hold Master" function or similar). usually in the ACK cycle of the addressing. In this case there is nothing you can do, since if SCL is held low, no clock cycles can be generated.

  • Thanks, that was what I was looking for!

    I tried this:

    void I2C_reset_pot()

    {

    __delay_cycles(10000);

    UCB0CTL1 |= UCTR + UCTXSTT;

    while ((IFG2 & UCB0TXIFG) == 0);

    UCB0CTL1 |= UCTR + UCTXSTP;

    }

    But the UCTXSTT and UCTXSTP flags never clear, and the UCBBUSY flag is also set. I guess that could mean the slave is holding the clock. I will have to check that with a scope when I get a chance.

  • Jacob Jorvang said:
    I guess that could mean the slave is holding the clock. I will have to check that with a scope when I get a chance.

    You cna check the UCSCLLOW bit. It shuold be set when the USCI is releasign the clock but it is still low (held down by someone else)

    However, the bit is also set when the USCI is holdign clock because it waits for somethign to be wtitten to TXBUF, and may appear low for a short ime on the rising clock edge due to line capacitacne or internal logic racing conditions.
    However, if TXIFG is clear and UCSCLLOW stays set, someone else is holding the line low.

  • This was recording from the debugger while single-stepping though the program after the slave became unresponsive.

    When stuck polling UCB0TXIFG flag

    UCSCLLOW = 0

    UCBBUSY = 1

    After hitting Reset in debugger

    UCSCLLOW = 0

    UCBBUSY = 0

    After setting P3SEL |= BIT1 + BIT2;

    UCSCLLOW = 0

    UCBBUSY = 0

    After initialization when desetting UCB0CTL1 &= ~UCSWRST;

    UCSCLLOW = 0

    UCBBUSY = 1

    Then after writing UCTXSTT, UCTXSTT never goes low again, but the UCB0TXBUF clears (i poll the UCB0TXIFG flag), so I send the instruction (first data byte), but then I'm stuck waiting for UCB0TXBUF to clear (polling UCB0TXIFG flag) which never happens since the slave didn't respong to its address.

    Anyway, the slave apperently isn't holding the clock low, since UCSCLLOW is never set.

  • Hmm, the slave will just be able to run on the voltage from a GPIO, even when sourcing the 0.5mA it draws... ;-)

    That would _not_ be an elegant solution, though...

  • Jacob Jorvang said:
    That would _not_ be an elegant solution, though...

    Why not? I've seen much circuitry that was sourced from the pullup current on unused parallel port or serial port pins. Adn these pins didn't have explicit driving strength.
    Before USB provided a direct voltage supply, this was a common way to power devices. Including most serial PC mice (the PS/2 connector did provide 5V, the serial port not).
    So you're in good company.

    However, it might be that there is still a bug in your software.

    You asked for a way to reset a slave, but maybe the question should have been 'how to address a slave properly so it doesn't get stuck' :)

    You should post the code (or send it in a private conversation).

    However, I'm a bit behind with answering requests. Being on vacation is nice, but nobody does the work for you. In fact, even more than usual piles up, as you're not there to reject it.

  • Actually, at this point the digital potentiometer is working fine. It only gets stuck if the I2C communication is interrupted during communication, but before the stop condition gets sent, like by single-stepping in the debugger and then resetting the MSP, or if I try to write to it too often. Theres seems to be a practical limit of around 270 writes per second, each consisting of two bytes beside the address. Once its stuck, the symptoms are the same for the two cases.

    Heres the code anyway. Its not like its secret or anything :) Suggestions are welcome.

    #include "msp430.h"
    #include "inttypes.h"						// for DMA address pointers
    #include <limits.h>							// for MPPT functions
    
    #define ADC_REFERENCE 0						// 2 = 2.5V internal reference, 1 = 1.5V internal reference, 0 = external Vcc reference
    
    #define I2C_CLOCK_PRESCALER 12				// I2C clock = SMCLK / prescaler. Ex. 1.2GHz / 12 = 100kHz
    
    /*	Maxim DS1845 - 100 tap*/
    //#define I2C_POTMETER_TAPS (int)99			// # of taps minus 1
    //#define I2C_POTMETER_ADDRESS 0x50			// 7-bit addressing, do not include write-bit
    //#define I2C_POTMETER_INSTRUCTION 0xF9
    /*	Maxim DS1845 - 256 tap*/
    #define I2C_POTMETER_TAPS (int)255			// # of taps minus 1
    #define I2C_POTMETER_ADDRESS 0x50			// 7-bit addressing, do not include write-bit
    #define I2C_POTMETER_INSTRUCTION 0xF8
    /*	Analog Devices AD5242 - 256 tap*/
    //#define I2C_POTMETER_TAPS (int)255		// # of taps minus 1
    //#define I2C_POTMETER_ADDRESS 0x2C			// 7-bit addressing, do not include write-bit
    //#define I2C_POTMETER_INSTRUCTION 0x00		// RADC1, no reset, no shutdown, no logic pin outputs
    
    #define I2C_POTMETER_START_TAP ((int)(I2C_POTMETER_TAPS/2))
    
    /*	MPPT thresholds */
    #define SMALL_STEP 1
    #define LARGE_STEP 3
    
    /*	Sets the MPPT tracking frequency [Hz]. Recommended range is 20 - 500 for SMCLK = 1.2MHz.
    	Note that this is basically the ADC sampling frequency, and that the frequency with which
    	the digital potentiometer is written is only half of that.
    	Setting the value too low results in integer truncation (TACCR0 is a 16-bit timer compare register)
    	Setting the value too high results in the digi-pot becoming unresponsive, sometimes until it is power-cycled.
    	Up to 550Hz should be OK, but 600Hz is too much.
    	The math:
    	TRACKING_FREQUENCY = SMCLK_FREQUENCY / TACCR0_VALUE  <=>  TACCR0_VALUE = SMCLK_FREQUENCY / TRACKING_FREQUENCY */
    #define TRACKING_FREQUENCY I2C_POTMETER_TAPS
    #define SMCLK_FREQUENCY 1200000
    #define TACCR0_VALUE (SMCLK_FREQUENCY/TRACKING_FREQUENCY)
    
    
    
    /*	abs() generally has undefined behavior for abs(MIN), so a safe version is used instead */
    static long int safe_abs(long int i)
    {
        long int res;
        if (LONG_MIN == i)
            res = LONG_MAX;
        else
            res = i < 0 ? -i : i; // IF i<0 THEN return -i ELSE return i
        return res;
    }
    
    
    
    /*	USCI module is configured as an synchronous mode I2C master in a single-master enviroment.
    	Both master and slave use 7-bit addressing. Master does not respond to general calls.
    	Clock used is SMCLK/prescaler, ex. 1.2GHz/12 = 100kHz. Master is configured as transmitter.
    	P3.1 = SDA I2C data
    	P3.2 = SCL I2C clock */
    void I2C_init()
    {
    	UCB0CTL1 |= UCSWRST;					// Enable USCI SW reset
    	UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC;	// Single I2C master, synchronous mode, 7-bit addressing (both master and slave)
    	UCB0CTL1 = UCSSEL_2 + UCTR + UCSWRST;	// Use SMCLK, transmitter mode, keep SW reset
    	UCB0BR0 = I2C_CLOCK_PRESCALER;			// fSCL = SMCLK/prescaler
    	UCB0BR1 = 0;
    	UCB0I2CSA = I2C_POTMETER_ADDRESS;		// Slave address
    	P3SEL |= BIT1 + BIT2;					// Assign I2C pins to USCI_B0 (secondary peripheral module)
    	UCB0CTL1 &= ~UCSWRST;					// Clear USCI SW reset, resume operation
    }
    
    
    
    /*	The digital potmeter is written over I2C. Default is to write a new potmeter tap value. */
    void I2C_write_pot_value(int value)
    {
    	if (value > I2C_POTMETER_TAPS)
    		value = I2C_POTMETER_TAPS;
    	while (UCB0CTL1 & UCTXSTP);             // Ensure stop condition got sent
    	UCB0CTL1 |= UCTR + UCTXSTT;				// Switch to I2C transmit mode, send START condition and address
    	while ((IFG2 & UCB0TXIFG) == 0);		// Wait for transmit buffer to clear
    	UCB0TXBUF = I2C_POTMETER_INSTRUCTION;	// Send instruction
    	while ((IFG2 & UCB0TXIFG) == 0);		// Wait for transmit buffer to clear
    	UCB0TXBUF = value;						// Send data
    	while ((IFG2 & UCB0TXIFG) == 0);		// Wait for transmit buffer to clear
    	UCB0CTL1 |= UCTR + UCTXSTP;				// Send STOP condition
    }
    
    
    
    /*	Initiates the DMA controller to get the two ADC values when both are available and move
    	them to registers associated with the hardware multiplier, which will then multiply them.
    	The DMA raises an interrupt when the transfer is complete, since the multiplier cannot. */
    void DMA_init()
    {
    	DMA0SA = (__SFR_FARPTR) (uintptr_t) &ADC12MEM0;			// Source address = ADC0 result
    	DMA0DA = (__SFR_FARPTR) (uintptr_t) &MACS;				// Target address = Multiply-accumulate operand
    	DMA0SZ = 0x02;											// Transfer size in words
    	DMACTL0 = DMA0TSEL_6;									// ADC12IFGx triggers DMA0
    	DMACTL1 = DMAONFETCH + ENNMI;							// Allow CPU to finish current instruction, NMI (non-maskable interrupts, such as the reset pin) may interrupt DMA transfers
    	DMA0CTL = DMADT_5 + DMADSTINCR_3 + DMASRCINCR_3 + DMAEN + DMAIE;	// Repeated block transfer (= do not disable DMA after transfer), increment source and destination addresses, enable DMA, enable interrupt
    	RESLO = 0;												// Clear hardware multiplier result registers
    	RESHI = 0;
    }
    
    
    
    /*	Initiates the ADC12 for sequential sampling of P6.0 and P6.1.
    	reference = 2 => 2.5V internal reference
    	reference = 1 => 1.5V internal reference
    	reference = 0 => external Vcc reference */
    void ADC_init(int reference)
    {
    	P6SEL = BIT0 + BIT1;						// Enable A/D channel inputs for P6.0 and P6.1
    
    	// Turn on ADC12, set reference, extend sampling time to avoid overflow of results
    	if (reference == 2)
    		ADC12CTL0 = ADC12ON + MSC + SHT0_8 + REF2_5V + REFON;	// 2.5V internal reference
    	else if (reference == 1)
    		ADC12CTL0 = ADC12ON + MSC + SHT0_8 + REFON;				// 1.5V internal reference
    	else
    		ADC12CTL0 = ADC12ON + MSC + SHT0_8;						// External Vcc reference
    
    	ADC12CTL1 = SHP + CONSEQ_1;					// Use sampling timer, sequence of channels
    
    	if ((reference == 2) || (reference == 1))	// 2.5V or 1.5V internal reference
    	{
    		ADC12MCTL0 = SREF_1 + INCH_0;			// ref+ = Vref+, channel = A0
    		ADC12MCTL1 = SREF_1 + INCH_1 + EOS;		// ref+ = Vref+, channel = A1, end seq.
    		int i = 0;
    		for (i = 0xFFF; i > 0; i--);			// Time for VRef to settle
    	}
    	else										// External Vcc reference
    	{
    		ADC12MCTL0 = INCH_0;					// ref+ = AVcc, channel = A0
    		ADC12MCTL1 = INCH_1 + EOS;				// ref+ = AVcc, channel = A1, end seq.
    	}
    
    	ADC12IFG = 0x00;							// Reset ADC interrupt flags
    	ADC12CTL0 |= ENC;							// Enable conversions
    }
    
    
    
    void ADC_start()
    {
    	RESLO = 0;							// Clear hardware multiplier result registers
    	RESHI = 0;
    	ADC12CTL0 |= ADC12SC;				// Start convn - software trigger
    	return;
    }
    
    
    
    int mppt()
    {
    	static int R = I2C_POTMETER_START_TAP, R_old = I2C_POTMETER_START_TAP;
    	static int dR = 0;
    
    	static long int Px = 0, Pk = 0, dP = 0, dP1 = 0, dP2 = 0;
    
    	static unsigned int odd_even = 0;
    
    	volatile unsigned int *const hw_mult_RESLO = &RESLO;
    	volatile unsigned int *const hw_mult_RESHI = &RESHI;
    
    	odd_even ^= 0x0001;
    
    	// ODD
    	if (odd_even)
    	{
    		Px = *hw_mult_RESHI;	// Get calculated power from hardware multiplier
    		Px = Px << 16;
    		Px += *hw_mult_RESLO;
    		dP1 = Px - Pk;			// "Between samples" intermediate result
    
    		return -1;
    	}
    	// EVEN
    	else
    	{
    		Pk = *hw_mult_RESHI;	// Get calculated power from hardware multiplier
    		Pk = Pk << 16;
    		Pk += *hw_mult_RESLO;
    		dP2 = Pk - Px;			// Change in power due to change in irradiation
    
    		dP = dP1 - dP2;			// Change in power due to pertuberation
    		dR = R - R_old;
    
    		R_old = R;				// save current R for next loop
    
    		// if the change in power due to irradiation (|dP2|) is smaller
    		// than the change in power due to the MPPT perturbation (|dP|),
    		// it is considered to be slowly changing conditions
    		if (safe_abs(dP2) < safe_abs(dP))
    		{
    			if (dR >= 0)
    			{
    				if (dP >= 0)
    					R += SMALL_STEP;
    				else
    					R -= SMALL_STEP;
    			}
    			else if (dP >= 0)
    				R -= SMALL_STEP;
    			else
    				R += SMALL_STEP;
    		}
    		// otherwise it is considered to be rapidly changing conditions
    		else
    		{
    			if (dR >= 0)
    			{
    				if (dP >= 0)
    					R += LARGE_STEP;
    				else
    					R -= LARGE_STEP;
    			}
    			else if (dP >= 0)
    				R -= LARGE_STEP;
    			else
    				R += LARGE_STEP;
    		}
    
    		if (R > I2C_POTMETER_TAPS)
    			R = I2C_POTMETER_TAPS;
    		else if (R < 0)
    			R = 0;
    		return R;
    	}
    }
    
    
    
    void timer0_init()
    {
    	TACCTL0 = CCIE;						// CCR0 interrupt enabled
    	TACCR0 = TACCR0_VALUE;
    	TACTL = TASSEL_2 + MC_1;			// SMCLK, upmode
    }
    
    
    
    void main()
    {
    	WDTCTL = WDTPW + WDTHOLD;			// Stop WDT
    
    	int R_pot = 0;
    
    	DMA_init();
    	ADC_init(ADC_REFERENCE);
    	I2C_init();
    
    	I2C_write_pot_value(I2C_POTMETER_START_TAP);
    
    	timer0_init();
    
    	while(1)
    	{
    		_BIS_SR(LPM0_bits + GIE);		// Enter LPM0, Enable interrupts
    		R_pot = mppt();
    		if (R_pot != (-1))
    			I2C_write_pot_value(R_pot);	// Track MPP and update digital pot with new R over I2C
    	}
    }
    
    
    
    // DMA interrupt service routine
    #pragma vector = DMA_VECTOR
    __interrupt void DMA_ISR(void)
    {
    	DMA0CTL &= ~DMAIFG;					// Clear DMA0 interrupt flag
    	_BIC_SR_IRQ(LPM0_bits);				// Exit LPM0 on reti
    }
    
    
    
    // Timer A0 interrupt service routine
    #pragma vector=TIMERA0_VECTOR
    __interrupt void Timer_A (void)
    {
    	ADC_start();
    }
    

  • Doe sthe potentiometer save the programmed state in non-volatile internal memory (EEPROM/Flash)?
    If so, this write takes some time, and trying to access the device before the write is done will cause problems. 270writes per second are less than 4ms per write. This may well be the write time for an eeprom.
    Just an idea.

    In your write function, you do not check whether the slave sends you a NACK because it isn't ready for another write yet. You simply continue waiting for a TXIFG flag that will never come.

    The proper operation is:

    set STT
    write to TXBUF
    wait for STT clear
    check for NACKIFG
    If NACKIFG is set, the byte written to TXBUF was discarded and you have to start over by either first sending an STP or directly retry with another STT.
    If NACKIFG is clear, wait for TXIFG and write the next byte etc.

**Attention** This is a public forum