Voltmeter M5Stack (ADS1115) interface



  • Hello,
    I try to interface the Voltemer based on ADS1115 in nodejs.
    The only given sample is based on C++ and Arduino code, but there is no detailled documentation on which command and parameters can be sent to register, in order to get measure for each range of voltage.
    I try to measure a battery voltage, like the picture here.
    0_1648305452141_2022-03-26-connection.png

    Here is my code. Can you give me more information ?

    • which element is to read : MUX_DIFF_0_1 : (0x0000), MUX_DIFF_0_3 : (0x1000), MUX_DIFF_1_3 : (0x2000), MUX_DIFF_2_3 : (0x3000), MUX_SINGLE_0 : (0x4000), MUX_SINGLE_1 : (0x5000), MUX_SINGLE_2 : (0x6000), MUX_SINGLE_3 : (0x7000) ?
    • which mathematical operation is to do with the value read in ther register ? multiply with what, depending of what (gain ?) ?
      Thank you for information.
    /* sample*/
    var ADS1X15 = {};
    
    /* Register values. Most of these aren't used
    and this is hidden, so it'll get optimised out
    when minified */
    ADS1X15.CONFIG = {
    	OS_MASK      : (0x8000),
    	OS_SINGLE    : (0x8000),  // Write: Set to start a single-conversion
    	OS_BUSY      : (0x0000),  // Read: Bit = 0 when conversion is in progress
    	OS_NOTBUSY   : (0x8000),  // Read: Bit = 1 when device is not performing a conversion					
    
    	MUX_MASK     : (0x7000),
    	MUX_DIFF_0_1 : (0x0000),  // Differential P = AIN0, N = AIN1 (default)
    	MUX_DIFF_0_3 : (0x1000),  // Differential P = AIN0, N = AIN3
    	MUX_DIFF_1_3 : (0x2000),  // Differential P = AIN1, N = AIN3
    	MUX_DIFF_2_3 : (0x3000),  // Differential P = AIN2, N = AIN3
    	MUX_SINGLE_0 : (0x4000),  // Single-ended AIN0
    	MUX_SINGLE_1 : (0x5000),  // Single-ended AIN1
    	MUX_SINGLE_2 : (0x6000),  // Single-ended AIN2
    	MUX_SINGLE_3 : (0x7000),  // Single-ended AIN3
    							
    	PGA_MASK     : (0x0E00),
    	PGA_6_144V   : (0x0000),  // +/-6.144V range = Gain 2/3
    	PGA_4_096V   : (0x0200),  // +/-4.096V range = Gain 1
    	PGA_2_048V   : (0x0400),  // +/-2.048V range = Gain 2 (default)
    	PGA_1_024V   : (0x0600),  // +/-1.024V range = Gain 4
    	PGA_0_512V   : (0x0800),  // +/-0.512V range = Gain 8
    	PGA_0_256V   : (0x0A00),  // +/-0.256V range = Gain 16
    							
    	MODE_MASK    : (0x0100),
    	MODE_CONTIN  : (0x0000),  // Continuous conversion mode
    	MODE_SINGLE  : (0x0100),  // Power-down single-shot mode (default)
    						
    	DR_MASK      : (0x00E0),  
    	DR_128SPS    : (0x0000),  // 128 samples per second
    	DR_250SPS    : (0x0020),  // 250 samples per second
    	DR_490SPS    : (0x0040),  // 490 samples per second
    	DR_920SPS    : (0x0060),  // 920 samples per second
    	DR_1600SPS   : (0x0080),  // 1600 samples per second (default)
    	DR_2400SPS   : (0x00A0),  // 2400 samples per second
    	DR_3300SPS   : (0x00C0),  // 3300 samples per second
    					 
    	CMODE_MASK   : (0x0010),
    	CMODE_TRAD   : (0x0000),  // Traditional comparator with hysteresis (default)
    	CMODE_WINDOW : (0x0010),  // Window comparator
    						
    	CPOL_MASK    : (0x0008),
    	CPOL_ACTVLOW : (0x0000),  // ALERT/RDY pin is low when active (default)
    	CPOL_ACTVHI  : (0x0008),  // ALERT/RDY pin is high when active
    					
    	CLAT_MASK    : (0x0004),  // Determines if ALERT/RDY pin latches once asserted
    	CLAT_NONLAT  : (0x0000),  // Non-latching comparator (default)
    	CLAT_LATCH   : (0x0004),  // Latching comparator
    						 
    	CQUE_MASK    : (0x0003),
    	CQUE_1CONV   : (0x0000),  // Assert ALERT/RDY after one conversions
    	CQUE_2CONV   : (0x0001),  // Assert ALERT/RDY after two conversions
    	CQUE_4CONV   : (0x0002),  // Assert ALERT/RDY after four conversions
    	CQUE_NONE    : (0x0003)  	// Disable the comparator and put ALERT/RDY in high state (default)
    };
    /*
    Voltage_measurement_range		Maximum_input_DC_voltage(V)		Minimum_resolution(mV)		Gain_factor
    PAG_4096(Calibrated)				±128													7.85											0.125
    2.048												64														3.93											0.0625
    1.024												32														1.96											0.03125
    0.512												16														0.98											0.015625
    PAG_256(Calibrated)					±8														0.49											0.007813
    */
    ADS1X15.GAINS = {
    	6144 : 	ADS1X15.CONFIG.PGA_6_144V,  // +/-6.144V range = Gain 2/3
    	4096 : 	ADS1X15.CONFIG.PGA_4_096V,  // +/-4.096V range = Gain 1
    	2048 : 	ADS1X15.CONFIG.PGA_2_048V,  // +/-2.048V range = Gain 2 (default)
    	1024 : 	ADS1X15.CONFIG.PGA_1_024V,  // +/-1.024V range = Gain 4
    	512 : 	ADS1X15.CONFIG.PGA_0_512V,  // +/-0.512V range = Gain 8
    	256 : 	ADS1X15.CONFIG.PGA_0_256V  // +/-0.256V range = Gain 16;
    };
    
    ADS1X15.VOLT_RANGE = {
    	"0_8V": 256,
    	"0_16V": 512,
    	"0_32V": 1024,
    	"0_64V": 2048,
    	"0_128V": 4096
    };
    
    ADS1X15.MULTICATOR = {
    	6144: 0.187500,
    	4096: 0.125000,
    	2048: 0.062500,
    	1024: 0.031250,
    	512: 0.015625,
    	256: 0.007813
    };
    
    ADS1X15.COEFFICIENT = 0.015918958;
    
    ADS1X15.DIFFS = {
    	"0,1" : ADS1X15.CONFIG.MUX_DIFF_0_1, // Differential P = AIN0, N = AIN1 (default)
    	"0,3" : ADS1X15.CONFIG.MUX_DIFF_0_3, // Differential P = AIN0, N = AIN3
    	"1,3" : ADS1X15.CONFIG.MUX_DIFF_1_3, // Differential P = AIN1, N = AIN3
    	"2,3" : ADS1X15.CONFIG.MUX_DIFF_2_3  // Differential P = AIN2, N = AIN3
    };
    
    ADS1X15.SINGLE_CHANELS = [
    	ADS1X15.CONFIG.MUX_SINGLE_0,
    	ADS1X15.CONFIG.MUX_SINGLE_1,
    	ADS1X15.CONFIG.MUX_SINGLE_2,
    	ADS1X15.CONFIG.MUX_SINGLE_3
    ];
    
    ADS1X15.REG = {
    	MASK : 3,
    	CONVERT : 0,
    	CONFIG : 1,
    	LOWTHRESH : 2,
    	HITHRESH : 3
    };
    
    // prototypes
    if (Number.prototype.toHexa === undefined) {
    	Number.prototype.toHexa = function( len) {
    		return "0x" + this.toString( 16).padStart( len, '0');
    	}
    }
    
    if (Buffer.prototype.toHexa === undefined) {
    	Buffer.prototype.toHexa = function() {
    		let str = "";
    		let arr = [...this];
    		arr.forEach( a => {
    			str = '0x' + a.toString(16).padStart( 2, '0') + ' ' + str;
    		});
    		return str;
    	}
    }
    
    // used internally for writing to the ADC
    function writeRegister( i2c, register, value) {
    	const funcName = "writeRegister";
    	
    	const buff = Buffer.from( [register & 3, value >> 8, value & 0xFF]);
    	console.log( `${funcName}> write to ${addr.toHexa(2)}, buff: ${buff.toHexa()}...`);
    	try {
    		let nbBytesWritten = i2c.i2cWriteSync( addr, 3, buff);
    		console.log( `${funcName}> ${nbBytesWritten} written`);
    	} catch( err1) {
    		console.log( `${funcName}> write FAILS > err1: ${err1}`);
    	}
    }
    
    // used internally for reading from the ADC
    function readRegister( i2c, register) {
    	const funcName = "readRegister";
    	let value = null;
    	
    	do {
    		const buffW = Buffer.from( [register] );
    		console.log( `${funcName}> write to ${addr.toHexa(2)} register: ${register.toHexa(2)}...`);
    		try {
    			i2c.i2cWriteSync( addr, 1, buffW);
    		} catch( err1) {
    			console.log( `${funcName}> write FAILS > err1: ${err1}`);
    			break;
    			//!!!!!
    		}
    		
    		let buffR = Buffer.from( [ 0, 0]);
    		try {
    			let nbBytesRead = i2c.i2cReadSync( addr, 2, buffR);				
    			value = (buffR[0] << 8) | buffR[1];
    			console.log( `${funcName}> ${nbBytesRead} read, buffR: ${buffR.toHexa()}, value: ${value}`);
    		} catch( err2) {
    			console.log( `${funcName}> read FAILS > err2: ${err2}`);
    			break;
    			//!!!!!
    		}
    	} while( false);
    	
    	return value;
    }
    
    function getADC( i2c, channelSpec, callback) {
    		
    	let config = ADS1X15.CONFIG.CQUE_NONE    | // Disable the comparator (default val)
    								ADS1X15.CONFIG.CLAT_NONLAT  | // Non-latching (default val)
    								ADS1X15.CONFIG.CPOL_ACTVLOW | // Alert/Rdy active low   (default val)
    								ADS1X15.CONFIG.CMODE_TRAD   | // Traditional comparator (default val)
    								ADS1X15.CONFIG.DR_1600SPS   | // rate: 1600 samples per second (default)
    								ADS1X15.CONFIG.MODE_SINGLE;   // Single-shot mode (default)
    										
    	// single ended (channelSpec is a number) or differential (channelSpec is array w/ valid channel duo)
    	if ("number" == typeof channelSpec) { // Set single-ended input channel
    		if ( ! ADS1X15.SINGLE_CHANELS.includes( channelSpec)) {
    			throw new Error( `Invalid single-ended channelSpec ${channelSpec.toHexa()} ! use one of ADS1X15.SINGLE_CHANELS ${ADS1X15.SINGLE_CHANELS}`);
    		}
    		config |= channelSpec;
    	} else { 
    		// Set differential input channels from channelSpec
    		if ( ! (channelSpec in ADS1X15.DIFFS)) {
    			throw new Error( `Invalid differention channelSpec ${channelSpec} ! use one of ADS1X15.DIFFS`);
    		}
    		let diffChannel = ADS1X15.DIFFS[ channelSpec];
    
    		config |= diffChannel;
    	} 
    
    	
    	// Set PGA/voltage range
    	config |= ADS1X15.GAINS[ gain];
    	
    	// Set 'start single-conversion' bit
    	config |= ADS1X15.CONFIG.OS_SINGLE;
    	
    	// Write config register to the ADC
    	writeRegister( i2c, ADS1X15.REG.CONFIG, config);
    	
    	// Wait for the conversion to complete
    	setTimeout(function() {
    		// Read the conversion results
    		let d = readRegister( i2c, ADS1X15.REG.CONVERT);
    		if ( d & 0x8000) {
    			d -= 65535; // sign
    		}
    		
    		let volts = parseFloat( d) * resolution;
    		console.log( `d: ${d}, resolution: ${resolution}, volts: ${volts}`);
    		
    		callback( volts); 
    	}, 8);
    }
    var i2cBus = null;
    var DEV_I2C = 1;
    var I2C_BUS = require("i2c-bus");
    console.log( `VoltMeter.js > on linux > i2c-bus loaded.`);
    
    var addr = 0x49;
    var voltRange = "0_8V";
    var gain = ADS1X15.VOLT_RANGE[ voltRange];
    var multiplicator = ADS1X15.MULTICATOR[ gain];
    var resolution = parseFloat( multiplicator / ADS1X15.COEFFICIENT);
    console.log( `gain: ${gain.toHexa()}, multiplicator: ${multiplicator}, COEFFICIENT: ${ADS1X15.COEFFICIENT}, resolution: ${resolution}...`);
    
    i2cBus = I2C_BUS.open( DEV_I2C, err1 => {
    	if (err1) {
    		console.log( `i2c.open FAILS > err1: ${err1}`);
    	} else {
    		
    		setInterval( function() {
    			// getADC( i2cBus, ADS1X15.CONFIG.MUX_SINGLE_0, function( value) {
    			getADC( i2cBus, "0,1", function( value) {
    				console.log( `${new Date().toISOString()} > mesure ${value} volts`);
    			});
    		}, 1000);
    	}
    });
    

    Best regards.