Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 10 of 10

Thread: Programming the I2C address of a MCP4728 4-channel DAC using i2c_t3 ?

  1. #1
    Senior Member
    Join Date
    Jan 2013
    Posts
    966

    Programming the I2C address of a MCP4728 4-channel DAC using i2c_t3 ?

    The MCP4728 allows programing the chip with a different I2C address. This would be straight forward if not for the LDAC pin on that chip:

    "LDAC pin makes a transition from “High” to “Low” during the negative pulse of the 8th clock of the 2nd byte (just before the rising edge of the 9th clock), and stays “Low” until the rising edge of the 9th clock of the 3rd byte.

    There is a chart accompanying the text (or rather vice versa) in the spec sheet on page 42.

    How can this be accomplished using the i2c_t3 library ?

    Any hints are appreciated!

  2. #2
    Senior Member
    Join Date
    Mar 2013
    Location
    Austin TX
    Posts
    428
    Your question is basically how to coordinate the LDAC pin transition with the I2C clocks, is that right?

    Unfortunately there is no provision in the library to do that, in fact the library doesn't directly control the SCL at all, that is handled by the I2C hardware. What I would do is change the I2C pins to direct I/O, bit-bang the special signaling, then re-initialize the I2C, using begin(), and go on as normal. If you are dealing with multi-master or clock-stretching slaves then it might be more difficult, but otherwise I would just try bit-banging first.

  3. #3
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    23,021
    Some time ago, I saw a special library for this chip which used software bit-bashing to emulate I2C with the special timing needed to configure this chip. That's really the best way to make this work. Then you can use normal, efficient I2C.

  4. #4
    Senior Member
    Join Date
    Jan 2013
    Posts
    966
    Thanks for the feedback. I am currently using a modified version of the SoftI2Cmaster library to do just that ( bit banging) to program the address of these DACs on my High Power RGB LED shields. This works fine with an Arduino UNO, however I could not get it to work with a Teensy++2.
    I had hoped to be able to find a solution that is not tuned to a specific microcontroller. I did not write the code to do this myself but will post it soon. Perhaps there is a better more reliable way to archive the same result across differnt boards and microcontrollers.

  5. #5
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    716
    Has anyone got this library to work on a teensy 3?

    https://github.com/TrippyLighting/So...tI2cMaster.cpp

    Another option might be to use the normal i2c_t3 library but while the bits are going out (as timed by the hardware), turn off the LDAC line at the right time. Either by monitoring the scl pin state and counting transitions (preferred) or by carefully timing it (with interrupts disabled). 100 khz isn't very fast.
    Last edited by jonr; 03-18-2016 at 02:20 PM.

  6. #6
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    716
    The above version of SoftI2cMastter worked for me. Using the normal i2c_t3 library + a delay looked fine on the scope, but didn't actually work (probably some small bug). I didn't pursue it.

  7. #7
    Senior Member
    Join Date
    May 2015
    Location
    USA
    Posts
    716
    Well, turns out that the bit banging approach wasn't reliable enough. So I fixed my method, which is:




    // i2c bus is set for 100 khz

    digitalWriteFast(ldac_pin, HIGH); // set LDAC high

    Wire.beginTransmission((unsigned char)(0B01100000 | (oldAddress << 0))); // 7 bit address 0xC0
    Wire.send( (unsigned char)(0B01100001 | (oldAddress << 2))); // LDAC should go low at the end of this byte, 0x61
    Wire.send( (unsigned char)(0B01100010 | (newAddress << 2))); // 0x66 for addr = 1
    Wire.send( (unsigned char)(0B01100011 | (newAddress << 2))); // 0x67 for addr = 1

    // send it
    noInterrupts(); // timing is critical
    Wire.sendTransmission();

    // set LDAC low at exactly the right time
    // use a scope or logic analyzer to verify against clock and data lines
    // could also poll the clock pin for 18 falling edges

    delayMicroseconds(-1 + 10 * 18); // 10 usec per bit @ 100 khz i2c bus speed
    digitalWriteFast(ldac_pin, LOW); // set LDAC low

    interrupts();
    Last edited by jonr; 03-18-2016 at 02:21 PM.

  8. #8
    Junior Member
    Join Date
    Oct 2020
    Posts
    4

    Here's a sketch that bit-bangs the I2C bus to set a new address.

    Quote Originally Posted by jonr View Post
    Well, turns out that the bit banging approach wasn't reliable enough. So I fixed my method, which is:




    // i2c bus is set for 100 khz

    digitalWriteFast(ldac_pin, HIGH); // set LDAC high

    Wire.beginTransmission((unsigned char)(0B01100000 | (oldAddress << 0))); // 7 bit address 0xC0
    Wire.send( (unsigned char)(0B01100001 | (oldAddress << 2))); // LDAC should go low at the end of this byte, 0x61
    Wire.send( (unsigned char)(0B01100010 | (newAddress << 2))); // 0x66 for addr = 1
    Wire.send( (unsigned char)(0B01100011 | (newAddress << 2))); // 0x67 for addr = 1

    // send it
    noInterrupts(); // timing is critical
    Wire.sendTransmission();

    // set LDAC low at exactly the right time
    // use a scope or logic analyzer to verify against clock and data lines
    // could also poll the clock pin for 18 falling edges

    delayMicroseconds(-1 + 10 * 18); // 10 usec per bit @ 100 khz i2c bus speed
    digitalWriteFast(ldac_pin, LOW); // set LDAC low

    interrupts();

    Code:
    /*
      Sets the MCP4728 DAC address to a new value by using the LDAC pin
      Adjust the pins you will use for the SDA and SCL bus
      Adjust the IO line you will use for the LDAC signal
      Just run this once on the DAC you want to remap the address of.
      Or include in a larger project and call as required...
     */
    
    uint8_t SCLpin = 19;
    uint8_t SDApin = 18;
    uint8_t LDACpin = 14;			// pulled LOW on PCB so drive when needed Hi
    uint8_t LEDpin = 13;			// Teensy LED (Hi logic = lit)
    
    uint8_t I2C_OldAddress = 0;		// normally 0
    uint8_t I2C_NewAddress = 7;		// any value 0-7
    
    int qflash = 50;		// 50ms
    int sflash = 250;		// 250ms
    int LEDhold = 1000;		// 1s
    int I2Cdelay = 1;		// 1ms
    int FailByte = 0;		// which byte we failed at (if we failed)
    int Success;
    
    void setup() {
    
    	uint8_t flish;
    	uint8_t outchar;
    	uint8_t tmpchar;
    
    	Serial.begin(115200);	// initialize serial interface for print()
    
    	Success = 0;		// until it works
    
    	// initialize the digital pins as an output
    	pinMode(SCLpin, OUTPUT);
    	pinMode(SDApin, OUTPUT);
    	pinMode(LDACpin, OUTPUT);
    	pinMode(LEDpin, OUTPUT);
    
    	digitalWrite(SCLpin, HIGH);   	// SCL + SDA high to start
    	digitalWrite(SDApin, HIGH);   	// SCL + SDA high to start
    	digitalWrite(LDACpin, LOW);   	// LDAC Low to start
    
    	// Inform the operation to be done...
    	Serial.println("Setting DAC Address as follows:");
    	Serial.print("Existing DAC Address = ");
    	Serial.println(I2C_OldAddress, DEC);
    	Serial.print("New DAC Address = ");
    	Serial.println(I2C_NewAddress, DEC);
        Serial.println("");
    	
    	// Flash LED number of times for new address (double flash then 0 to 7 flashes)
    	for(flish = 0; flish<2; flish++){
    		digitalWrite(LEDpin, HIGH);
    		delay(qflash);
    		digitalWrite(LEDpin, LOW);
    		delay(qflash);    
    	}
    	delay(LEDhold);	
    	for(flish = 0; flish<I2C_NewAddress; flish++){
    		digitalWrite(LEDpin, HIGH);
    		delay(sflash);
    		digitalWrite(LEDpin, LOW);
    		delay(sflash);    
    	}
    	delay(LEDhold);
    	
    	// set LDAC Hi
    	digitalWrite(LDACpin, HIGH);   	// LDAC Low to start
    	delay(I2Cdelay);
    	// send a start condition
    	digitalWrite(SDApin, LOW);
    	delay(I2Cdelay); 
    	digitalWrite(SCLpin, LOW);
    	delay(I2Cdelay); 
    	// byte 1	
    	outchar = I2C_OldAddress;
    	outchar <<= 1;				// shift L by 1
    	outchar |= 0xc0;			// or in 0x60 to address bits
    	tmpchar = I2CwriteByte(outchar);		// write byte 1 (expect slave to ACK and returns 1 if it does, 0 of not)
    	FailByte = 1;
    	if (tmpchar == 1) {		// success
    		// byte 2
    		outchar = I2C_OldAddress;
    		outchar <<= 2;				// shift L by 2
    		outchar |= 0x61;			// or in 0x61 to set the address change command
    		tmpchar = LDACwriteByte(outchar);		// funky write byte 2 which sets LDAC low after the 8th clock (expect slave to ACK and returns 1 if it does, 0 of not)
    		FailByte = 2;
    		if (tmpchar == 1) {		// success
    			// byte 3
    			outchar = I2C_NewAddress;
    			outchar <<= 2;				// shift L by 2
    			outchar |= 0x62;			// or in 0x62 to set the address change command and signify byte 3 (b1 set, b0 clear)
    			tmpchar = I2CwriteByte(outchar);		// write byte 3 (expect slave to ACK only if written correctly)
    			FailByte = 3;
    			if (tmpchar == 1) {		// success
    				// byte 4
    				outchar = I2C_NewAddress;
    				outchar <<= 2;				// shift L by 2
    				outchar |= 0x63;			// or in 0x63 to set the address change command and signify byte 4 (b1 set, b1 set)
    				tmpchar = I2CwriteByte(outchar);		// write byte 4 (expect slave to ACK)
    				FailByte = 4;
    				if (tmpchar == 1) {		// success
    					Success = 1;
    					FailByte = 0;
    				}
    			}
    		}
    	}
    	// send a stop condition
    	// SCL will be Low
    	digitalWrite(SDApin, LOW);	// SDA low in case it's Hi
    	delay(I2Cdelay); 
    	digitalWrite(SCLpin, HIGH);
    	delay(I2Cdelay); 	
    	digitalWrite(SDApin, HIGH);	// SCL Hi
    	delay(I2Cdelay); 
    	
    	// now set the LDAC low in case it is still high (failed on byte 1)
    	digitalWrite(LDACpin, LOW);   	// LDAC Low
    	// now announce the status...
    	if (Success == 1) {
    		Serial.println("DAC Address succesfully set to new value.");
    	}
    	else {
    		Serial.print("DAC Address Set procedure FAILED!!  I2C ACK was missing at Byte number ");
    		Serial.println(FailByte, DEC);
    	}
    }
    
    uint8_t I2CwriteByte(uint8_t outchar){
    	// Enters with SCL LOW, SDA undefined Hi or Low
    	// SDA set to output
    	// 
    	// Leaves with SCL LOW, SDA HIGH
    	// SDA set to output
    	//
    	// returns 1 if acked OK, 0 if not
    		
    	int shuffle;
    	uint8_t MyAck;
    	int tempint;
    	
    	shuffle = 9;		// 8 data bits
    	while (--shuffle) {
    		if (outchar & 0x80)
    			digitalWrite(SDApin, HIGH);
    		else
    			digitalWrite(SDApin, LOW);
    		delay(I2Cdelay);
    		digitalWrite(SCLpin, HIGH);
    		delay(I2Cdelay);
    		outchar <<= 1;						// shift up data for bext bit
    		digitalWrite(SCLpin, LOW);
    		delay(I2Cdelay);
    	}
    	// Completed sending byte, now allow slave to ACK
    	pinMode(SDApin, INPUT);			// turn SDA to input for ACK
    	digitalWrite(SCLpin, HIGH);
    	delay(I2Cdelay);
    	// ACK received here with a bit of luck (we may test some day)
    	// read input line
    	tempint = digitalRead(SDApin);
    	if (tempint == 0) {		// acked OK
    		MyAck = 1;
    	}
    	else {		// failed
    		MyAck = 0;
    	}
    	digitalWrite(SCLpin, LOW);
    	delay(I2Cdelay);
    	digitalWrite(SDApin, HIGH);			// set output register high before shanging DDR
    	pinMode(SDApin, OUTPUT);			// turn SDA back to an output
    	digitalWrite(SDApin, HIGH);			// set output high to exit
    	delay(I2Cdelay);
    	return MyAck;
    }
    
    uint8_t LDACwriteByte(uint8_t outchar){
    	// Enters with SCL LOW, SDA undefined Hi or Low, LDAC High
    	// SDA set to output
    	// 
    	// Leaves with SCL LOW, SDA HIGH, LDAC Low
    	// SDA set to output
    	//
    	// returns 1 if acked OK, 0 if not
    		
    	int shuffle;
    	uint8_t MyAck;
    	int tempint;
    
    	shuffle = 9;		// 8 clocks
    	while (--shuffle) {
    		if (outchar & 0x80)
    			digitalWrite(SDApin, HIGH);
    		else
    			digitalWrite(SDApin, LOW);
    		delay(I2Cdelay);
    		digitalWrite(SCLpin, HIGH);
    		delay(I2Cdelay);
    		outchar <<= 1;						// shift up data for bext bit
    		digitalWrite(SCLpin, LOW);
    		delay(I2Cdelay);
    	}
    	// Now we drop the LDAC pin to initiate the EEPROM command
    	digitalWrite(LDACpin, LOW);   	// LDAC Low
    	delay(I2Cdelay);
    	// Completed sending byte, now allow slave to ACK
    	pinMode(SDApin, INPUT);			// turn SDA to input for ACK
    	digitalWrite(SCLpin, HIGH);
    	// ACK received here with a bit of luck (we may test some day)
    	// read input line
    	tempint = digitalRead(SDApin);
    	if (tempint == 0) {		// acked OK
    		MyAck = 1;
    	}
    	else {		// failed
    		MyAck = 0;
    	}
    	delay(I2Cdelay);
    	digitalWrite(SCLpin, LOW);
    	delay(I2Cdelay);
    	digitalWrite(SDApin, HIGH);			// set output register high before shanging DDR
    	pinMode(SDApin, OUTPUT);			// turn SDA back to an output
    	digitalWrite(SDApin, HIGH);			// set output high to exit
    	delay(I2Cdelay);
    	return MyAck;
    }
    
    void loop() {
    	// continually announce the status...
    	if (Success == 1) {
    		Serial.println("DAC Address succesfully set to new value.");
    	}
    	else {
    		Serial.print("DAC Address Set procedure FAILED!!  I2C ACK was missing at Byte number ");
    		Serial.println(FailByte, DEC);
    	}
      	delay(3000);               // wait for a few seconds
      	// Our work is done.  Power cycle and programme another
    }

  9. #9
    Junior Member
    Join Date
    Oct 2020
    Posts
    4

    What you can do with multiple DACs (8 chips, 32 channels updated at 1MHz)

    Here's a bus with 8 x 4-channel DACs attached, running at 1MHz (not in HS-mode)

    Click image for larger version. 

Name:	32 DACs.jpg 
Views:	8 
Size:	146.0 KB 
ID:	22188

    Timing of the 8 x 4-channel FastWrite calls (this is just the SCL line for checking timing):

    Click image for larger version. 

Name:	32 DAC updates in 667us.jpg 
Views:	8 
Size:	150.2 KB 
ID:	22189

  10. #10
    Senior Member
    Join Date
    Nov 2015
    Location
    Cold hollow VT
    Posts
    169
    Just in case someone else needs to use a bunch of these DACs in their project, Microchip direct sells these parts pre-programmed to different I2C addresses for about the same cost as they can be purchased from DigiKey. No minimum order required.

    I am a user of Microchip parts (sometimes). I do not work for them or get any financial benefit from making this post. I don't even own any of their stock.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •