MCP4726 - 12 Bit I2C DAC Driver for ESP8266 Modules - Page 1
Forum section: electronics / general

author: TJ232
author_rank: Frequent Contributor
timestamp: March 06, 2015, 01:33:48 pm
content: As a lot of people were looking for a DAC working with ESP8266 modules, please find below a quick driver implementation for Microchip MCP4726 12 bit DAC.Youtube related video :For more details :http://www.esp8266-projects.com/2015/03/mcp4726-12-bit-i2c-dac-driver-for.html

author: dannyf
author_rank: Super Contributor
timestamp: March 06, 2015, 01:38:21 pm
content: Nice work.

author: TJ232
author_rank: Frequent Contributor
timestamp: March 07, 2015, 10:40:00 am
content: Quote from: dannyf on March 06, 2015, 01:38:21 pmThank you dannyf.I hope you will like also Part 2, ESP8266 CBDB Module with MCP4726 12 Bit DAC generating some standard waveforms for testing DAC output accuracy:http://www.esp8266-projects.com/2015/03/mcp4726-12-bit-i2c-dac-driver-for_7.html

author: miguelvp
author_rank: Super Contributor
timestamp: March 14, 2015, 01:38:53 am
content: Funny, it seems we are playing with the same toys this week.After figuring out the Cypress USB-Serial configured as an USB to I2C controller that I can hook to Windows, Android, Linux or OSX I used a freebie given to me by blueskull when I purchased the DE0-Nano he had for sale:https://www.eevblog.com/forum/buysellwanted/fs-1*adafruit-mcp4725-2*26mhz-ocxo-1*terasic-de0-nano-fpga-board-(more)/He included an Adafruit MCP4725 DAC board for free in the order:http://www.adafruit.com/product/935Since I already had the RTC clock setup, I decided to modify the code to talk to this DAC.Yeah, there is a tiny board behind those cables and the scope probe I swear!Since there was not a lot of sample code and of course it wouldn't match my windows machine anyways I went for the datasheet and tired of the slow updates I discovered that it has a fast mode that you can just dump a bunch of writes continuously at a ~ 3.69140625 uS rate (~270,899.47 Hz) instead of using the other two modes that are very slow.Also the lag time between writes are captured here (45.3ms between writes):Code, not optimal and derived from my RTC project but I'm just playing around so added what I needed and put a bunch of #if blocks to do one thing or another, also it probably doesn't format right in the code block but I'm not retouching it to make things align or change my tabs to spaces.Code:#include "stdafx.h"#include <stdio.h>#include <windows.h>#include <dbt.h>#include <conio.h>#include "..\..\..\library\inc\CyUSBSerial.h"/** ****************Data Definitions*** **************** */// Define VID & PID// These numbers depends on individual products#define VID 0x04B4#define PID 0x0004//Variable to store cyHandle of the selected deviceCY_HANDLE cyHandle;//Varible to capture return values from USB Serial APICY_RETURN_STATUS cyReturnStatus;//CY_DEVICE_INFO provides additional details of each device such as product Name, serial number etc..CY_DEVICE_INFO cyDeviceInfo, cyDeviceInfoList;//Structure to store VID & PID defined in CyUSBSerial.hCY_VID_PID cyVidPid;CY_I2C_DATA_CONFIG cyI2CRWDataConfig;CY_DATA_BUFFER cyDataRWBuffer;//Variables used by applicationUINT8 cyNumDevices;unsigned char deviceID;/** ****************Functions*** **************** */#define changeIntToHex(dec)( (((dec)/10) <<4) + ((dec)%10) )#define converseIntToHex(dec)( (((dec)>>4) *10) + ((dec)%16) )#define changeHexToInt(hex)( (((hex)>>4) *16 ) + ((hex)%16) )#define converseHexToInt(hex)( (((hex)/10) <<4 ) + ((hex)%10) )#define CTRL_BUF10x00#define CTRL_BUF20x01#define SECOND_DATA_BUF0x02#define MINUTE_DATA_BUF0x03#define HOUR_DATA_BUF0x04#define DAY_DATA_BUF0x05#define WEEK_DATA_BUF0x06#define MONTH_DATA_BUF0x07#define YEAR_DATA_BUF0x08#define MINUTE_AE_BUF0x09#define HOUR_AE_BUF0x0A#define DAY_AE_BUF0x0B#define WEEK_AE_BUF0x0C#define CLK_FRQ_BUF0x0D#define TIMER_CTRL_BUF0x0E#define COUNT_VAL_BUF0x0Ftypedef unsigned char uchar;typedef unsigned int uint;#define MCP4726_CMD_WRITEDACFAST (0x00) // Writes data to the DAC#define MCP4726_CMD_WRITEDAC (0x40) // Writes data to the DAC#define MCP4726_CMD_WRITEDACEEPROM (0x60) // Writes data to the DAC and the EEPROM (persisting the assigned value after reset)void SetVoltage(UINT16 output, bool writeEEPROM){uchar buffer;if (writeEEPROM){buffer = MCP4726_CMD_WRITEDACEEPROM;}else{//buffer = MCP4726_CMD_WRITEDACFAST;buffer = MCP4726_CMD_WRITEDAC;}buffer = output / 16; // Upper data bits (D11.D10.D9.D8.D7.D6.D5.D4)buffer = (output % 16) << 4; // Lower data bits (D3.D2.D1.D0.x.x.x.x)cyDataRWBuffer.buffer = buffer;cyDataRWBuffer.length = 3;CyI2cWrite(cyHandle, &cyI2CRWDataConfig, &cyDataRWBuffer, 5000);}void SetVoltageFast(UINT16 *pOutput, UINT32 length){cyDataRWBuffer.buffer = (UCHAR *)pOutput;cyDataRWBuffer.length = length * 2;CyI2cWrite(cyHandle, &cyI2CRWDataConfig, &cyDataRWBuffer, 5000);}void WriteAByte(uchar wordAdr, uchar dat){uchar buffer;buffer = wordAdr;buffer = dat;cyDataRWBuffer.buffer = buffer;cyDataRWBuffer.length = 2;CyI2cWrite(cyHandle, &cyI2CRWDataConfig, &cyDataRWBuffer, 5000);//SMBus_Write(0xA2, wordAdr, dat, 2);}void ReadNByte(uchar wordAdr, uchar *pRdDat, uchar length){cyDataRWBuffer.buffer = &wordAdr;cyDataRWBuffer.length = 1;CyI2cWrite(cyHandle, &cyI2CRWDataConfig, &cyDataRWBuffer, 5000);//SMBus_Write(0xA2, wordAdr, 0, 1);cyDataRWBuffer.buffer = pRdDat;cyDataRWBuffer.length = length;CyI2cRead(cyHandle, &cyI2CRWDataConfig, &cyDataRWBuffer, 5000);//SMBus_Read(0xA3, pRdDat, 11);}void PCF8563_getTime(uchar *buf){ReadNByte(SECOND_DATA_BUF, buf, 11);buf = buf & 0x7f;//get second databuf = buf & 0x7f;//get minute databuf = buf & 0x3f;//get hour databuf = buf & 0x3f;//get day databuf = buf & 0x07;//get weekday databuf = buf & 0x9f;//get century_month data (most significant bit Century bit 0=2000 1=1900)//buf = buf;//get year databuf = buf & 0xff;//get minute_alarm databuf = buf & 0xaf;//get hour_alarm databuf = buf & 0xaf;//get day_alarm databuf = buf & 0x87;//get weekday_alarm databuf = converseIntToHex(buf);buf = converseIntToHex(buf);buf = converseIntToHex(buf);buf = converseIntToHex(buf);buf = converseIntToHex(buf);buf = converseIntToHex(buf);buf = converseIntToHex(buf);}void PCF8563_setTime(uchar hour, uchar minute, uchar second, uchar day, uchar weekday, uchar month, uint year){hour = changeIntToHex(hour);minute = changeIntToHex(minute);second = changeIntToHex(second);day = changeIntToHex(day);weekday = changeIntToHex(weekday);month = changeIntToHex(month);if (year < 2000){// if 19xx set Century bitmonth |= 0x80;}year %= 100;year = changeIntToHex(year);WriteAByte(HOUR_DATA_BUF, hour);WriteAByte(MINUTE_DATA_BUF, minute);WriteAByte(SECOND_DATA_BUF, second);WriteAByte(DAY_DATA_BUF, day);WriteAByte(WEEK_DATA_BUF, weekday);WriteAByte(MONTH_DATA_BUF, month);WriteAByte(YEAR_DATA_BUF, year);}void PCF8563_init(void){WriteAByte(CTRL_BUF1, 0x00);//basic settingWriteAByte(CTRL_BUF2, 0x12);//alarm enable}CY_RETURN_STATUS I2CMasterSetup(int deviceNumber, uchar slaveAddress){CY_I2C_CONFIG cyI2CConfig;CY_RETURN_STATUS rStatus;int interfaceNum = 0;// Configure the I2C read and write datacyI2CRWDataConfig.isStopBit = true;cyI2CRWDataConfig.isNakBit = false;cyI2CRWDataConfig.slaveAddress = slaveAddress;//cyI2CRWDataConfig.slaveAddress = 0xA2 >> 1;// Configure the I2Cprintf("Opening I2C device with device number %d...\n", deviceNumber);//Open the device at deviceNumberrStatus = CyOpen(deviceNumber, interfaceNum, &cyHandle);if (rStatus != CY_SUCCESS){printf("I2C Device open failed...\n");return rStatus;}// Get configurationprintf("I2C Open successfull. Invoking API for retrieving I2C configuration...\n");rStatus = CyGetI2cConfig(cyHandle, &cyI2CConfig);if (rStatus != CY_SUCCESS){printf("CyGetI2cConfig returned failure code.\n");return rStatus;}printf("I2C Frequency : %d , Slave address : %d\n",cyI2CConfig.frequency,cyI2CConfig.slaveAddress);// Set configuration// Configure I2C with different settingsprintf("Setting new I2C configuration...\n");cyI2CConfig.frequency = 400000;cyI2CConfig.slaveAddress = 0x60; // Ain't matter, this is for slave mode.cyI2CConfig.isMaster = 1;printf("New Configuration - I2C Frequency : %d , Slave address : %d, isMaster : %d\n",cyI2CConfig.frequency,cyI2CConfig.slaveAddress,cyI2CConfig.isMaster);rStatus = CySetI2cConfig(cyHandle, &cyI2CConfig);if (rStatus != CY_SUCCESS){printf("CySetI2cConfig returned failure code.\n");return rStatus;}printf("Setting new I2C configuration successful.\n");// Check to see if the new configuration is applied on I2Cprintf("Checking the new I2C configuration ...\n");rStatus = CyGetI2cConfig(cyHandle, &cyI2CConfig);if (rStatus != CY_SUCCESS){printf("CyGetI2cConfig returned failure code.\n");return rStatus;}printf("I2C Configuration retrieved - I2C Frequency : %d , Slave address : %d, isMaster : %d\n",cyI2CConfig.frequency,cyI2CConfig.slaveAddress,cyI2CConfig.isMaster);return rStatus;}/*Function Name: int FindDeviceAtSCB0()Purpose: Demonstates how to enumerate and find device from the list of devices.- Demonstrates how to use deviceBlock member in DEVICE_INFO structureArguments: NoneRetrun Code: returns -1, if no device is present or deviceIndex of the device.*/int FindDeviceAtSCB0(){CY_VID_PID cyVidPid;cyVidPid.vid = VID; //Defined as macrocyVidPid.pid = PID; //Defined as macro//Array size of cyDeviceInfoList is 16cyReturnStatus = CyGetDeviceInfoVidPid (cyVidPid, deviceID, (PCY_DEVICE_INFO)&cyDeviceInfoList, &cyNumDevices, 16);int deviceIndexAtSCB0 = -1;for (int index = 0; index < cyNumDevices; index++){printf ("\nNumber of interfaces: %d\n \Vid: 0x%X \n\Pid: 0x%X \n\Serial name is: %s\n\Manufacturer name: %s\n\Product Name: %s\n\SCB Number: 0x%X \n\Device Type: %d \n\Device Class: %d\n\n\n",cyDeviceInfoList.numInterfaces,cyDeviceInfoList.vidPid.vid,cyDeviceInfoList.vidPid.pid,cyDeviceInfoList.serialNum,cyDeviceInfoList.manufacturerName,cyDeviceInfoList.productName,cyDeviceInfoList.deviceBlock,cyDeviceInfoList.deviceType,cyDeviceInfoList.deviceClass);// Find the device at device index at SCB0if (cyDeviceInfoList.deviceBlock == SerialBlock_SCB0){deviceIndexAtSCB0 = index;}}return deviceIndexAtSCB0;}char *weekday[] ={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};char *month[] ={"January","February","March","April","May","June","July","August","September","October","November","December"};/** ********************************Application main() function*** ******************************** */int _tmain(int argc, _TCHAR* argv[]){//Assmumptions://1. SCB0 is configured as I2Cint deviceIndexAtSCB0 = FindDeviceAtSCB0();//Open the device at deviceIndexAtSCB0if (deviceIndexAtSCB0 >= 0){//Assuming that device at index is I2C device//Device Open, Close, Configuration, Data operations are handled in the function I2CMaster#if 1cyReturnStatus = I2CMasterSetup(deviceIndexAtSCB0, 0x62);if (cyReturnStatus == CY_SUCCESS){// Set HS mode, but find out what the high-speed (HS) Master Mode Code (HSMMC) value is// WriteAByte( 0x00, 0x08 ); // 00001XXX#if 1UINT16 counter;counter = 0;uchar buffer;for (int i = 0; i<8192; i+=2){counter = i >> 1;counter &= 0x0fff;buffer = counter / 16;buffer[i + 1] = (counter % 16) << 4;}while (1){SetVoltageFast((UINT16 *)buffer, 4096);}CyClose(cyHandle);#elseUINT16 counter;counter = 0;while (1){#if 1SetVoltage(counter, false);counter = ~counter;#elsecounter = 0;// Run through the full 12-bit scale for a triangle wavewhile (counter < 4095){SetVoltage(counter, false);counter += 16;}counter = 4095;while (counter > 15){SetVoltage(counter, false);counter -= 16;}#endif}CyClose(cyHandle);#endif}else{printf("\nI2CMaster returned failure code.\n");}#elsecyReturnStatus = I2CMasterSetup(deviceIndexAtSCB0, 0xA2 >> 1);if (cyReturnStatus == CY_SUCCESS){SYSTEMTIME localtime;uchar time, tmp = 0;char command;printf("\nI2CMaster returned success.\n");PCF8563_init();printf("\nType s to set, r to reset, anything else to resume current time\n");command = getch();if (command == 's'){GetLocalTime(&localtime);PCF8563_setTime(localtime.wHour, localtime.wMinute, localtime.wSecond, localtime.wDay, localtime.wDayOfWeek, localtime.wMonth, localtime.wYear);}else if (command == 'r'){// 23 hours, 59 minutes, 30 seconds, day 28, weekday Saturday(6), month 2, year 2015PCF8563_setTime(23, 59, 30, 28, 6, 2, 2015);}while (1){PCF8563_getTime(time);if (time != tmp){uint year = time > 80 ? 1900 : 2000;year += time;uint hour = ((time % 12) == 0) ? 12 : time % 12;printf("%s, %s %d, %04d %02d:%02d:%02d %s\n", weekday[time], month[((time > 80) ? time - 80 : time)-1], time, year, hour, time, time, (time>11) ? "PM" : "AM");}tmp = time;}CyClose(cyHandle);}else{printf("\nI2CMaster returned failure code.\n");}#endif} //cyNumDevices > 0 && cyReturnStatus == CY_SUCCESSreturn 0;}The important parts for the DAC are:Code:void SetVoltageFast(UINT16 *pOutput, UINT32 length){cyDataRWBuffer.buffer = (UCHAR *)pOutput;cyDataRWBuffer.length = length * 2;CyI2cWrite(cyHandle, &cyI2CRWDataConfig, &cyDataRWBuffer, 5000);}the 5000 is a time out value in milliseconds so it's giving the function 5 seconds to transfer the whole thing, 4096 words worth setup in the buffer with this:Code:UINT16 counter;uchar buffer;for (int i = 0; i<8192; i+=2){counter = i >> 1;counter &= 0x0fff;buffer = counter / 16;buffer[i + 1] = (counter % 16) << 4;}while (1){SetVoltageFast((UINT16 *)buffer, 4096);}Not bad for a 400Kbps I2C DAC, not sure how different the MCP4725 is from your MCP4726 I guess I'll read up the datasheet.But the fast mode runs circles vs the write dac (0x40) or the write dac eeprom (0x60).And since the fast mode is 0x00 I don't have to really do anything to the most significant nibble of the 16bit word holding the valueEdit: both the MCP4725 and the MCP4726 do support a High-Speed (HS) Mode with rates up to 3.4 Mbits/s for I2Cs SDA and SCL lines.I could use the PSoC4 I2C that allows up to 1Mbit/s or find a chip that can reach the maximum of 3.4 Mbits/s