diff --git a/.gitignore b/.gitignore index 350abb0..4990803 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.idea build/ deps/ diff --git a/README.md b/README.md index 6c0d0a2..13f7b60 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ A Mongoose library for various `I2C` speaking ADCs from Texas Instruments: +* ADS1119 - 16bit, 1000 Samples/sec, 2 differential / 4 single-ended, programmable gain. * ADS1115 - 16bit, 860 Samples/sec, 2 differential / 4 single-ended, programmable gain * ADS1114 - 16bit, 860 Samples/sec, 1 differential / 1 single-ended, programmable gain * ADS1113 - 16bit, 860 Samples/sec, 1 differential / 1 single-ended, no gain @@ -17,7 +18,7 @@ The driver takes care of exposing the correct fuctionality based on which `type` is created. Differential measurements can be taken on all devices, but only `ADS1x15` has multiple options. -## API Description +## ADS 1013-1115 API First, create a device using `mgos_ads1x1x_create()` by specifying the type of chip you're using. Take some measurements using `mgos_ads1x1x_read()`, and @@ -43,7 +44,7 @@ the differential voltage between two channels, typically `Chan0` and `Chan1`. Several channel pairs are allowed, see the include file for details. Note, that this function is only available on `ADS1X15` chips. -## Example application +#### Example application (!ADS1119) ``` #include "mgos.h" @@ -80,6 +81,83 @@ enum mgos_app_init_result mgos_app_init(void) { ``` +## ADS 1119 API + +TI has simplified the command API and configuration registers during the development of the ADS1119 so it works quite differently to prior chips. + +First, create a device using `mgos_ads1119_create()` by specifying the type of +chip you're using. Take some measurements using `mgos_ads1119_read()`, and +clean up the driver by using `mgos_ads1x1x_destroy()`. + +#### Creation + +During initiation you can specify the settings such as data rate, gain, single shot vs continuous and whether to use the internal 2.048v vREF or an external vREF. + +For example: `mgos_ads1119_create(mgos_i2c_get_global(), 0x40, ADC_ADS1119, MGOS_ADS1119_GAIN_1, MGOS_ADS1X1X_SPS_20, MGOS_ADS1119_CM_SS, MGOS_ADS1119_VREF_EXT)` + + +``` +if (!(ads = mgos_ads1119_create(mgos_i2c_get_global(), 0x40, ADC_ADS1119, MGOS_ADS1119_GAIN_1, MGOS_ADS1X1X_SPS_20, MGOS_ADS1119_CM_SS, MGOS_ADS1119_VREF_EXT))) { + LOG(LL_ERROR, ("I2C Could not create ADS1119")); +} +else { + LOG(LL_INFO, ("I2C Have created ADS1119 device successfully")); +} +``` + +#### Reading values +To read either a single or differential value you need to supply the MUX value per 'Configuration Register Field Descriptions' in the TI datasheet. + +The function is of the form `mgos_ads1119_read(struct mgos_ads1x1x* dev, uint8_t mux, int16_t* result)`. + +For example to read single ended AIN3 & GND, that corresponds to a mux value of binary 110 / decimal 7, and would have a call like: +`int16_t res; mgos_ads1119_read(ads, 7, &res);` + +#### Example application + +This example reads all the channels as single ended. As the 5th result element it reads the voltage offset value (AINP and AINN shorted to AVDD / 2) to enable calibration. + +``` +#include "mgos.h" +#include "mgos_config.h" +#include "mgos_ads1x1x.h" + +void timer_cb(void *data) { + struct mgos_ads1x1x *d = (struct mgos_ads1x1x *)data; + int16_t res[5]; + + if (!d) return; + + for(int i=0; i<5; i++) { + if (!mgos_ads1119_read(s_adc, i+3, &res[i])) { + LOG(LL_ERROR, ("Could not read device")); + return; + } + } + LOG(LL_INFO, ("chan={%6d, %6d, %6d, %6d} offset=%d", res[0], res[1], res[2], res[3], res[4])); +} + +enum mgos_app_init_result mgos_app_init(void) { + struct mgos_ads1x1x *d = NULL; + + if (!(d = mgos_ads1119_create(mgos_i2c_get_global(), 0x40, ADC_ADS1119, MGOS_ADS1119_GAIN_1, MGOS_ADS1X1X_SPS_20, MGOS_ADS1119_CM_SS, MGOS_ADS1119_VREF_EXT))) { + LOG(LL_ERROR, ("Could not create ADS1119")); + return MGOS_APP_INIT_ERROR; + } + + mgos_set_timer(100, true, timer_cb, d); + + return MGOS_APP_INIT_SUCCESS; +} + +``` + +Notes: +- TI Datasheet: http://www.ti.com/lit/ds/symlink/ads1119.pdf +- Have observed that it will return a negative reading if a thermistor is unplugged, these values are currently simplified to -1. +- TODO: switch the mgos_msleep value between the START/SYNC command and taking the reading based on SPS + + # Disclaimer This project is not an official Google project. It is not supported by Google diff --git a/include/mgos_ads1x1x.h b/include/mgos_ads1x1x.h index 122b451..7acaf88 100644 --- a/include/mgos_ads1x1x.h +++ b/include/mgos_ads1x1x.h @@ -31,7 +31,8 @@ enum mgos_ads1x1x_type { ADC_ADS1015, ADC_ADS1113, ADC_ADS1114, - ADC_ADS1115 + ADC_ADS1115, + ADC_ADS1119 }; enum mgos_ads1x1x_fsr { @@ -50,19 +51,41 @@ enum mgos_ads1x1x_dr { MGOS_ADS1X1X_SPS_MIN = 0, // 8SPS for ADS111X, 128SPS for ADS101X MGOS_ADS1X1X_SPS_8, // 8SPS, ADS111X only MGOS_ADS1X1X_SPS_16, // 16SPS, ADS111X only + MGOS_ADS1X1X_SPS_20, // 20SPS, ADS1119 only (default) MGOS_ADS1X1X_SPS_32, // 32SPS, ADS111X only MGOS_ADS1X1X_SPS_64, // 64SPS, ADS111X only + MGOS_ADS1X1X_SPS_90, // 90SPS, ADS1119 only MGOS_ADS1X1X_SPS_128, // 128SPS, both ADS111X and ADS101X MGOS_ADS1X1X_SPS_250, // 250SPS, both ADS111X and ADS101X + MGOS_ADS1X1X_SPS_330, // 330SPS, ADS1119 only MGOS_ADS1X1X_SPS_475, // 475SPS, ADS111X only MGOS_ADS1X1X_SPS_490, // 490SPS, ADS101X only MGOS_ADS1X1X_SPS_860, // 860SPS, ADS111X only MGOS_ADS1X1X_SPS_920, // 920SPS, ADS101X only + MGOS_ADS1X1X_SPS_1000, // 1000SPS, ADS1119 only MGOS_ADS1X1X_SPS_1600, // 1600SPS, ADS101X only MGOS_ADS1X1X_SPS_2400, // 2400SPS, ADS101X only MGOS_ADS1X1X_SPS_3300, // 3300SPS, ADS101X only - MGOS_ADS1X1X_SPS_DEFAULT, // 128SPS for ADS111X, 1600SPS for ADS101X - MGOS_ADS1X1X_SPS_MAX, // 860SPS for ADS111X, 3300SPS for ADS101X + MGOS_ADS1X1X_SPS_DEFAULT, // 128SPS for ADS111X, 1600SPS for ADS101X, 20SPS for ADS1119 + MGOS_ADS1X1X_SPS_MAX, // 860SPS for ADS111X, 3300SPS for ADS101X, 1000SPS for ADS1119 +}; + +enum mgos_ads1119_gain +{ + MGOS_ADS1119_GAIN_1 = 0, + MGOS_ADS1119_GAIN_4 +}; + +enum mgos_ads1119_conversion_mode +{ + MGOS_ADS1119_CM_SS = 0, + MGOS_ADS1119_CM_CONT +}; + +enum mgos_ads1119_vref +{ + MGOS_ADS1119_VREF_INT = 0, // Internal 2.048v + MGOS_ADS1119_VREF_EXT }; /* @@ -71,7 +94,20 @@ enum mgos_ads1x1x_dr { * validity, upon success a new `struct mgos_ads1x1x` is allocated and * returned. If the device could not be found, NULL is returned. */ -struct mgos_ads1x1x *mgos_ads1x1x_create(struct mgos_i2c *i2c, uint8_t i2caddr, enum mgos_ads1x1x_type type); +struct mgos_ads1x1x* +mgos_ads1x1x_create(struct mgos_i2c* i2c, uint8_t i2caddr, enum mgos_ads1x1x_type type); + +/* + * Initialize a ADS1119 on the I2C bus `i2c` at address specified in `i2caddr` + * parameter (default ADS1X1X is on address 0x48). The device will be polled for + * validity, upon success a new `struct mgos_ads1x1x` is allocated and + * returned. If the device could not be found, NULL is returned. + * Refer to TI product PDF, Configuration Register, for information on what to use for mux/gain/DR/CM/VREF. + * Note, use the decimal value for each, ie AinP = AIN2, AinN=AGND is decimal 5 (101 binary). + * Mux is provided at read time + */ +//struct mgos_ads1x1x* mgos_ads1119_create(struct mgos_i2c* i2c, uint8_t i2caddr, enum mgos_ads1x1x_type type, enum mgos_ads1119_gain gain = MGOS_ADS1119_GAIN_1, enum mgos_ads1x1x_dr dataRate = MGOS_ADS1X1X_SPS_20, enum mgos_ads1119_conversion_mode conversionMode = MGOS_ADS1119_CM_SS, enum mgos_ads1119_vref vRef = MGOS_ADS1119_VREF_INT); +struct mgos_ads1x1x* mgos_ads1119_create(struct mgos_i2c* i2c, uint8_t i2caddr, enum mgos_ads1x1x_type type, enum mgos_ads1119_gain gain, enum mgos_ads1x1x_dr dataRate, enum mgos_ads1119_conversion_mode conversionMode, enum mgos_ads1119_vref vRef); /* * Destroy the data structure associated with a ADS1X1X device. The reference @@ -108,6 +144,8 @@ bool mgos_ads1x1x_get_dr(struct mgos_ads1x1x *dev, enum mgos_ads1x1x_dr *dr); */ bool mgos_ads1x1x_read(struct mgos_ads1x1x *dev, uint8_t chan, int16_t *result); +bool mgos_ads1119_read(struct mgos_ads1x1x* dev, uint8_t mux, int16_t* result); + /* Read a 2-channel differential from the ADC and return the read value in `result`. * If the channel pair invalid, or an error occurred, false is returned and the * result cannot be relied upon. Upon success, true is returned. @@ -119,6 +157,11 @@ bool mgos_ads1x1x_read(struct mgos_ads1x1x *dev, uint8_t chan, int16_t *result); */ bool mgos_ads1x1x_read_diff(struct mgos_ads1x1x *dev, uint8_t chanP, uint8_t chanN, int16_t *result); +/* Centralised helper function to write configuration to an ADS1119 +* Sends the command for WREG first +*/ +bool mgos_ads1119_write_conf(struct mgos_ads1x1x * dev, uint8_t value); + #ifdef __cplusplus } #endif diff --git a/src/mgos_ads1x1x.c b/src/mgos_ads1x1x.c index 1be3a8d..fd8a370 100644 --- a/src/mgos_ads1x1x.c +++ b/src/mgos_ads1x1x.c @@ -35,6 +35,8 @@ static char *mgos_ads1x1x_type_str(struct mgos_ads1x1x *dev) { case ADC_ADS1115: return "ADS1115"; + case ADC_ADS1119: return "ADS1119"; + default: return "UNKNOWN"; } } @@ -44,13 +46,24 @@ static bool mgos_ads1x1x_reset(struct mgos_ads1x1x *dev) { return false; } - return mgos_i2c_write_reg_w(dev->i2c, dev->i2caddr, MGOS_ADS1X1X_REG_POINTER_CONF, 0x8583); + if (dev->type == ADC_ADS1119) { + LOG(LL_DEBUG, ("mgos_ads1x1x_reset for ADS1119")); + + uint8_t val1[] = { 0x06 }; + return mgos_i2c_write(dev->i2c, dev->i2caddr, val1, sizeof(val1), true); + + } else { + LOG(LL_DEBUG, ("mgos_ads1x1x_reset for generic 1x1x")); + + return mgos_i2c_write_reg_w(dev->i2c, dev->i2caddr, MGOS_ADS1X1X_REG_POINTER_CONF, 0x8583); + } + } struct mgos_ads1x1x *mgos_ads1x1x_create(struct mgos_i2c *i2c, uint8_t i2caddr, enum mgos_ads1x1x_type type) { struct mgos_ads1x1x *dev = NULL; - if (!i2c || type == ADC_NONE || type > ADC_ADS1115) { + if (!i2c || type == ADC_NONE || type > ADC_ADS1119) { return NULL; } @@ -66,6 +79,7 @@ struct mgos_ads1x1x *mgos_ads1x1x_create(struct mgos_i2c *i2c, uint8_t i2caddr, switch (type) { case ADC_ADS1015: case ADC_ADS1115: + case ADC_ADS1119: dev->channels = 4; break; @@ -82,6 +96,64 @@ struct mgos_ads1x1x *mgos_ads1x1x_create(struct mgos_i2c *i2c, uint8_t i2caddr, return dev; } +struct mgos_ads1x1x* mgos_ads1119_create(struct mgos_i2c * i2c, uint8_t i2caddr, enum mgos_ads1x1x_type type, enum mgos_ads1119_gain gain, enum mgos_ads1x1x_dr dataRate, enum mgos_ads1119_conversion_mode conversionMode, enum mgos_ads1119_vref vRef) +{ + struct mgos_ads1x1x* dev = mgos_ads1x1x_create(i2c, i2caddr, type); + + // Note: we choose mux at read time + dev->gain = gain; + dev->dataRate = dataRate; + dev->conversionMode = conversionMode; + dev->vRef = vRef; + + LOG(LL_DEBUG, ("I2C ADS ADS1119 set conf gain:%d DR:%d conversionMode:%d vRef:%d", gain, dataRate, conversionMode, vRef)); + + dev->configuration[0] = 0; + + if (gain) { + LOG(LL_DEBUG, ("Set ADS1119 gain to 1")); + dev->configuration[0] |= (1 << 4); // binary:00010000 Turn bit 0 (position) to 1 if it isn't already + } else { + //LOG(LL_INFO, ("Set ADS1119 gain to 0")); + } + + uint8_t dataRateVal = 0; + if (dataRate) { + switch (dataRate) { + case MGOS_ADS1X1X_SPS_90: + dataRateVal = 1; + break; + case MGOS_ADS1X1X_SPS_330: + dataRateVal = 2; + break; + case MGOS_ADS1X1X_SPS_1000: + case MGOS_ADS1X1X_SPS_MAX: + dataRateVal = 3; + break; + case MGOS_ADS1X1X_SPS_MIN: + case MGOS_ADS1X1X_SPS_20: + default: + dataRateVal = 0; + } + + LOG(LL_DEBUG, ("I2C ADS1119 new config dataRateVal %d", dataRateVal)); + + dev->configuration[0] |= (dataRateVal << 2); // shift our simple 1 decimal value over 1 bit + } + + if (conversionMode) { // neatly limits it to 0 or >0 values with the explicit 1 << 1 below + dev->configuration[0] |= (1 << 1); // shift our simple 1 decimal value over 1 bit + } + + if (vRef) { // neatly limits it to 0 or >0 values with the explicit 1 << 1 below + dev->configuration[0] |= 1; + } + + mgos_ads1119_write_conf(dev, dev->configuration[0]); + + return dev; +} + bool mgos_ads1x1x_destroy(struct mgos_ads1x1x **dev) { if (!*dev) { return false; @@ -315,6 +387,82 @@ bool mgos_ads1x1x_read(struct mgos_ads1x1x *dev, uint8_t chan, int16_t *result) return mgos_ads1x1x_read_diff(dev, chan, 0xff, result); } +bool mgos_ads1119_write_conf(struct mgos_ads1x1x* dev, uint8_t value) { + uint8_t tmp[2] = { 0x40, (uint8_t)value }; // Command byte, then the new configuration byte + + bool res = false; + res = mgos_i2c_write(dev->i2c, dev->i2caddr, tmp, sizeof(tmp), true /* stop */); + + if (res) { + LOG(LL_INFO, ("I2C ADS1119 write conf (%d) succeeded", value)); + } else { + LOG(LL_INFO, ("I2C ADS1119 write conf (%d) failed", value)); + } + return res; +} +bool mgos_ads1119_read(struct mgos_ads1x1x* dev, uint8_t mux, int16_t* result) { + int16_t result_val; + *result = 0; // default value + + uint8_t chan = mux - 3; // human friendly map from MUX to AINx + + // Clear and set the bits for MUX + dev->configuration[0] |= (7 << 5); // make it all 1's for MUX + dev->configuration[0] &= ~(7 << 5); // set MUX fields to 1, then mirror to NOT, then apply AND so anything aside from MUX will stay if previously set + dev->configuration[0] |= (mux << 5); // now apply mux fields + + if (!mgos_ads1119_write_conf(dev, dev->configuration[0])){ + LOG(LL_INFO, ("i2c mgos_ads1119_read had error when updating mux, config: %d mux:%d AIN%d", dev->configuration[0], mux, chan)); + return false; + } else { + LOG(LL_INFO, ("i2c mgos_ads1119_read success updating mux, config: %d mux:%d AIN%d", dev->configuration[0], mux, chan)); + } + + // Send start/sync 0000 100x command + uint8_t val1[] = { 0x08 }; + if (mgos_i2c_write(dev->i2c, dev->i2caddr, val1, sizeof(val1), true)){ + // Wait for conversion to complete + if (dev->dataRate >= MGOS_ADS1X1X_SPS_90){ + LOG(LL_INFO, ("i2c mgos_ads1119_read success sending 0x08 start command, waiting 55usleep")); + // TODO: switch on delay based on data rate. Someone is welcome to, I prefer using the 20SPS digital filter features + mgos_msleep(20); // NOTE usleep (microseconds ) + } else { + LOG(LL_INFO, ("i2c mgos_ads1119_read success sending 0x08 start command, waiting 80msleep")); + mgos_msleep(50 + 10); // Milliseconds! it's a touch over 50ms for 20SPS, data sheet warns its not precisely 1 second / 20! + } + } + + // Tell it we're going to read with RDATA command + uint8_t reg = 0x10; + if (!mgos_i2c_write(dev->i2c, dev->i2caddr, ®, 1, false /* stop */)) { + LOG(LL_INFO, ("i2c mgos_ads1119_read prewrite returned error, mux: %d", mux)); + *result = -1; + return false; + } + + // Now read the actual value + uint8_t tmp[2]; + if (!mgos_i2c_read(dev->i2c, dev->i2caddr, tmp, 2, true /* stop */)){ + LOG(LL_INFO, ("i2c mgos_ads1119_read read request returned error, mux: %d", mux)); + *result = -1; + return false; + } + + // Convert from our 2 element byte array to a integer with some left bit shifting + result_val = (((uint16_t) tmp[0]) << 8) | tmp[1]; + + // Let's return -1 when we get a negative number as it means it's floating unconnected + if (result_val < 0){ + LOG(LL_DEBUG, ("i2c mgos_ads1119_read returned negative, likely unplugged: val %d, mux: %d", result_val, mux)); + *result = -1; + return false; + } else { + LOG(LL_DEBUG, ("i2c mgos_ads1119_read returned val: %d, mux %d AIN%d", result_val, mux, chan)); + *result = result_val; + return true; + } +} + // if chanN is 0xff, perform a single ended read (with chanP against GND) bool mgos_ads1x1x_read_diff(struct mgos_ads1x1x *dev, uint8_t chanP, uint8_t chanN, int16_t *result) { uint16_t conf_val; diff --git a/src/mgos_ads1x1x_internal.h b/src/mgos_ads1x1x_internal.h index 9966894..fd0ebfd 100644 --- a/src/mgos_ads1x1x_internal.h +++ b/src/mgos_ads1x1x_internal.h @@ -21,12 +21,13 @@ extern "C" { #endif -// ADS1X1X I2C address +// ADS1X1X I2C address, note default for ADS1119 with A0 & A1 tied to GND is 0x40 #define MGOS_ADS1X1X_I2C_ADDR (0x48) // Registers #define MGOS_ADS1X1X_REG_POINTER_MASK (0x03) #define MGOS_ADS1X1X_REG_POINTER_CONV (0x00) +#define MGOS_ADS1119_REG_POINTER_COMMAND (0x00) #define MGOS_ADS1X1X_REG_POINTER_CONF (0x01) #define MGOS_ADS1X1X_REG_POINTER_LO_T (0x02) #define MGOS_ADS1X1X_REG_POINTER_HI_T (0x03) @@ -36,6 +37,12 @@ struct mgos_ads1x1x { uint8_t i2caddr; enum mgos_ads1x1x_type type; uint8_t channels; + uint8_t configuration[2]; // allow for 16 bit configuration with an array. ADS1119 only uses configuration[0] + uint8_t mux; + uint8_t gain; + uint8_t dataRate; + uint8_t conversionMode; + uint8_t vRef; }; /* Mongoose OS initializer */