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

Status
Not open for further replies.

Headroom

Well-known member
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!
 
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.
 
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.
 
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.
 
Has anyone got this library to work on a teensy 3?

https://github.com/TrippyLighting/SoftI2cMaster/blob/master/SoftI2cMaster.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:
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.
 
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:
Here's a sketch that bit-bangs the I2C bus to set a new address.

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
}
 
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)

32 DACs.jpg

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

32 DAC updates in 667us.jpg
 
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.
 
Status
Not open for further replies.
Back
Top