MCP4728簡介如下:
Features
• 12-Bit Voltage Output DAC with Four Buffered
Outputs
• On-Board Nonvolatile Memory (EEPROM) for
DAC Codes and I2C™ Address Bits
• Internal or External Voltage Reference Selection
• Output Voltage Range:
- Using Internal VREF (2.048V):
0.000V to 2.048V with Gain Setting = 1
0.000V to 4.096V with Gain Setting = 2
- Using External VREF (VDD):
0.000V to VDD
• ±0.2 Least Significant Bit (LSB) Differential
Nonlinearity (DNL) (typical)
• Fast Settling Time: 6 µs (typical)
• Normal or Power-Down Mode
• Low Power Consumption
• Single-Supply Operation: 2.7V to 5.5V
• I2C Interface:
- Address bits: User Programmable to
EEPROM
- Standard (100 kbps), Fast (400 kbps) and
High Speed (HS) Mode (3.4 Mbps)
• 10-Lead MSOP Package
• Extended Temperature Range: -40°C to +125°C
資料來源:
http://ww1.microchip.com/downloads/en/DeviceDoc/22187E.pdf
它是一顆擁有四個類比輸出的DAC,透過I2C介面做控制。
12Bit 解析度,可以把輸出的電壓分成4096等份,例如輸出0~5V, 則可以將電壓的精準度
達到
0.001。
可有以下幾種模式(透過指令即可變更):
使用外部參考電壓,例如5V 。
使用內部參考電壓 Gain =1
0.000V to 2.048V
使用內部參考電壓 Gain =2
0.000V to 4.096V
如果要求精準,建議使用內部參考電壓 Gain =1 ,並透過穩壓IC供電給DAC。
如果要使用多組DAC在電路中,則需要改變MCP4728的I2C位置,我們先來看一下
datasheet中的說明
MCP4728預設的I2C位置為0x61 , 也就是1100001 ,也就是A2=0 , A1=0 ,A0=1 。
如果今天我要把它改成0x60 (1100000), New Address bit A2=0, A1=0 , A0=0 。
把現有的位置和新的位置依照上面填空後,就是4個byte的指令,接下來要接上
LDAC這支Pin腳,它需要在第2個byte傳送到第8個bit時,將LDAC由HIGH轉LOW,
否則怎麼傳送指令都會失敗,注意必須在MCP4728回傳ACK 之前就要轉LOW,
所以用Arduino內建的Wire.Write會失敗,弄了2天,請教了Firmware朋友,得到的
答案建議自己去控制I2C通訊時的HIGH ,LOW ,才能在第八個bit時將LDAC轉LOW,
等到ACK回來就來不了。來看一下接線
Arduino UNO MCP4728
VCC VDD
GND VSS
2 SCL
3 SDA
8 LDAC
接下來是我去網路上找的一份程式碼,整理之後,確認可以成功。
將預設的0x61位置改成0x60
// R/W direction bit to OR with address for start or restart
#define I2C_READ 1
#define I2C_WRITE 0
#define SOFT_I2C_MASTER
#define I2C_DELAY_USEC 10
#define ldac_pin 8
uint8_t sclPin_;
uint8_t sdaPin_;
unsigned char OldAddr = 0x1; //0x61 default I2C Address
unsigned char NewAddr = 0x0; //0x60 new I2C Address
//I2C Init
void init(uint8_t sclPin, uint8_t sdaPin)
{
sclPin_ = sclPin;
sdaPin_ = sdaPin;
pinMode(sclPin_, OUTPUT);
digitalWrite(sdaPin_, HIGH); //Mark_H fix
pinMode(sdaPin_, OUTPUT);
digitalWrite(sclPin_, HIGH);
digitalWrite(sdaPin_, HIGH);
}
//------------------------------------------------------------------------------
// read a byte and send Ack if last is false else Nak to terminate read
uint8_t read(uint8_t last)
{
uint8_t b = 0;
// make sure pullup enabled
digitalWrite(sdaPin_, HIGH);
pinMode(sdaPin_, INPUT);
// read byte
for (uint8_t i = 0; i < 8; i++) {
// don't change this loop unless you verify the change with a scope
b <<= 1;
delayMicroseconds(I2C_DELAY_USEC);
digitalWrite(sclPin_, HIGH);
if (digitalRead(sdaPin_)) b |= 1;
digitalWrite(sclPin_, LOW);
}
// send Ack or Nak
digitalWrite(sdaPin_, HIGH);
pinMode(sdaPin_, OUTPUT);
digitalWrite(sdaPin_, last);
digitalWrite(sclPin_, HIGH);
delayMicroseconds(I2C_DELAY_USEC);
digitalWrite(sclPin_, LOW);
digitalWrite(sdaPin_, HIGH);
return b;
}
//------------------------------------------------------------------------------
// send new address and read/write without stop
uint8_t restart(uint8_t addressRW)
{
digitalWrite(sclPin_, HIGH);
return start(addressRW);
}
//------------------------------------------------------------------------------
// issue a start condition for i2c address with read/write bit
uint8_t start(uint8_t addressRW)
{
digitalWrite(sdaPin_, LOW);
delayMicroseconds(I2C_DELAY_USEC);
digitalWrite(sclPin_, LOW);
return write(addressRW);
}
//------------------------------------------------------------------------------
// issue a stop condition
void stop(void)
{
digitalWrite(sdaPin_, LOW);
delayMicroseconds(I2C_DELAY_USEC);
digitalWrite(sclPin_, HIGH);
delayMicroseconds(I2C_DELAY_USEC);
digitalWrite(sdaPin_, HIGH);
delayMicroseconds(I2C_DELAY_USEC);
}
//------------------------------------------------------------------------------
// write byte and return true for Ack or false for Nak
uint8_t write(uint8_t b)
{
// write byte
for (uint8_t m = 0X80; m != 0; m >>= 1) {
digitalWrite(sdaPin_, m & b);
digitalWrite(sclPin_, HIGH);
delayMicroseconds(I2C_DELAY_USEC);
digitalWrite(sclPin_, LOW);
}
// get Ack or Nak
digitalWrite(sdaPin_, HIGH);
pinMode(sdaPin_, INPUT);
digitalWrite(sclPin_, HIGH);
b = digitalRead(sdaPin_);
digitalWrite(sclPin_, LOW);
digitalWrite(sdaPin_, HIGH);
pinMode(sdaPin_, OUTPUT);
return b == 0;
}
//------------------------------------------------------------------------------
// write byte and return true for Ack or false for Nak
uint8_t ldacwrite(uint8_t b, uint8_t ldacpin)
{
// write byte
for (uint8_t m = 0X80; m != 0; m >>= 1) {
// don't change this loop unless you verivy the change with a scope
digitalWrite(sdaPin_, m & b);
digitalWrite(sclPin_, HIGH);
delayMicroseconds(I2C_DELAY_USEC);
digitalWrite(sclPin_, LOW);
}
// get Ack or Nak
digitalWrite(ldacpin, LOW);
digitalWrite(sdaPin_, HIGH);
pinMode(sdaPin_, INPUT);
digitalWrite(sclPin_, HIGH);
// digitalWrite(bsy_pin,LOW);
b = digitalRead(sdaPin_);
digitalWrite(sclPin_, LOW);
digitalWrite(sdaPin_, HIGH);
pinMode(sdaPin_, OUTPUT);
return b == 0;
}
void setup() {
Serial.begin(9600);
pinMode(ldac_pin,OUTPUT);
digitalWrite(ldac_pin,HIGH);
delay(10);
init (2,3);
uint8_t data1=(unsigned char)(0B11000000 | (OldAddr << 1));
uint8_t data2=(unsigned char)(0B01100001 | (OldAddr << 2));
uint8_t data3=(unsigned char)(0B01100010 | (NewAddr << 2));
uint8_t data4=(unsigned char)(0B01100011 | (NewAddr << 2));
uint8_t ack1=start(data1);
uint8_t ack2=ldacwrite(data2,ldac_pin);
uint8_t ack3=write(data3);
uint8_t ack4=write( data4);
stop();
Serial.println(data1);
Serial.println(data2);
Serial.println(data3);
Serial.println(data4);
Serial.println(ack1);
Serial.println(ack2);
Serial.println(ack3);
Serial.println(ack4);
delay(10);
Serial.println("FINISH");
}
void loop() {
// put your main code here, to run repeatedly:
}
執行完畢後,
請記得將Arduino 斷電,重新上電後用I2C Scanner 範例程式來確認是否有改成功。
參考來源:
https://forum.pjrc.com/threads/28424-Programming-the-I2C-address-of-a-MCP4728-4-channel-DAC-using-i2c_t3
https://github.com/TrippyLighting/SoftI2cMaster/blob/master/SoftI2cMaster.cpp