How to Use M5Stack Voltmeter

Hello,
I have bought the M5Stack Voltmeter based on ADS1115 component.
I have not clearly found documentation on dedicated API to interface it. There are codes mixing C++ and arduino program on M5Stack site, there are somewhere else “ads1115” samples (in python), but mainly with an other I2C address (0x48), so I suppose it is not clearly for this M5Stack Voltmeter.
I try first to measure tension on small battery (i.e. 6 v). Perharps It can not work because there is no load ?
Here is my sample code in nodejs, which return a stable value, but not corresponding to battery (measure is 0.1960614xxx Volts).

Do you have a running-code sample dedicated for this device , in python or C or node js (without external library except i2c) ?

Best regards.

Hi, have you seen all these contents related to M5stack? m5-docs

1 Like

I,
Of course, but the only sample is a mix of c++ and Arduino. Or with usage of M5 API/library. I search free sample without any API.
The connection schema in order to test a battery is as following:

My code sample is as following.
First question : on which interface (single or differencial measure is to use for the connection of the picture ? MUX_DIFF_xx or MUX_SINGLE_xx ?
Second : Which operations to do on 16 bits value read from the register ? multiply with something ? multiplicator depending of gain ?..

/* 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.

Ok, I am not from the Robotshop team.
Mr. @cbenson , could you please take a look into this matter?

Thank you.
If you find enough information to allow interface, il will share the working code.

As you’re aware, the module on its own cannot interface directly with a computer, so there’s no specific API (that I’m aware of). Using an Arduino board is a very easy way to get the necessary intermediate hardware, which is why the manufacturer has sample Arduino code. M5 also has a “UIFlow” example for their system, as well as Windows EasyLoader sample code.
https://docs.m5stack.com/en/unit/vmeter

They don’t seem to describe the I2C configuration on that page, so it’s likely that they expect you to use one of the sample code. What are you using as intermediate hardware?

Hello Mr BENSON,

Thank you for your answer.

I don’t use specific hardware:

  • On a Rapsberry Pi4, I connect the SU087 on GPIO/I2C pins
  • I code in NodeJs, but this is not really different from Python (I uselly to switch from one to other language).
  • I only request, through basic I2C function, to read and write registers in boards.
    → write GAIN, write RATE, write MODE, set board in state of acquiring voltage, waiting a few time (conversion delay), and then read the voltage in register. Here is a sofware conversion (multiplication ) to get volts.

The problem is that the operations in the registers are “masqued” by M5Stack library.
And sample mixing C++ and Arduino code use “magic” const without explaination.

Do you have other clients using this device without M5Stack library ?

Best regards.

PS : The M5stack page writes at one place a maximum input voltage at +/- 128v, and at an other place “maximum measuring voltage 16V”…

Did you verify the communication voltage level? On a Pi it tends to be 3V, whereas many devices like this operate at 5V.

Do you have other clients using this device without M5Stack library ?

Unknown.

Hello Mr Benson,
The device clearly shows that we have to use the DC5V of the Raspberry.
image
And the M5STACK page confirms that:

I think that my principles (in previous code) runs, because device answer me something.
But it is not the correct value.

Do you think, because I am testing a battery (6V), that I need to put a “load” (like a resistance or a light) on the battery ?

Best regards.

Do you think, because I am testing a battery (6V), that I need to put a “load” (like a resistance or a light) on the battery ?

If this is true, that would be incredibly odd since any standard voltmeter can easily measure a battery’s voltage without a load.

To confirm, I meant the SDA and SCL pins on the module might be 5V pins whereas on the Pi they are 3V (would need to read if they are 5V tolerant). If you are getting a value however (and a different value for a different battery), perhaps there’s a calibration needed (though the manufacturer clearly states that it is calibrated from the factory and to NOT write to the EEPROM), or a simple mathematical conversion from the output to an actual voltage?

1 Like

It seems like M5Stack assumes you’d be using the module with their system and provides sample code accordingly. Those customers who want to use it separately will need to understand the schematic and read through the datasheet for the ADS111x (take a look at page 22):


You can also reach out to the manufacturer directly at [email protected]

1 Like

Ok.
It is not a good new.
When I buy such a packaged device , I hope to get a small documentation better than to read TI technical sheets.
And with a ready-to-use sample, and documented API.
I’m disappointed.
Regards.

Hello,
I have read some information about ADS1115, and it is clearly written that “If you are supplied with 5V, the voltage to be measured must not exceed 5.3 volts”.
But the M5STACK site (Voltmeter Unit (ADS1115) | m5stack-store) displays: Measuring range ±36V.

And it is the same on your site :

So, the ADS1115 itself doesn’t allow such voltage.

So if M5STACK technical sheet is true, so there is an other way than Texas Instrument documentation of the ads1115 to access to such measure.

I think you could verify near you provider this point. Otherwise, the information on M5STACK site is false. And that is a big problem, becaus i want measure two battery of 12V in serial.

Best regards.

The additional circuitry around this chip likely allows for that functionality (or there is some confusion between “supply voltage” and “measured voltage”. Either way, unfortunately we are not in a position to investigate the details. We’ve reached out to the manufacturer in the hope that they will provide some insight into the issues / complaints you have.

Thank you. I have tried some other program yesterday evening, with a standard nodejs module, without success. I always mesure 12,18xx v on a 6 v battrey.
If I found a solution (I have e.mailed to m5stack assistance too), I will share it.
Best regards.