Skip to content

Pius171/AD4112-STM32-HAL-LIBRARY

Repository files navigation

AD4112-STM32-HAL-LIBRARY

A library for the AD4112 Analog to Digital Converter. It is an adaptation from the c and headers files provided by Analog Devices, the manufacturer of the chip.

SPI Configuration in CUBEMX

Use SPI Mode: Full-Duplex-Master alt text

In the Parameter Settings SET: Prescaler(for Baud Rate): 64 CPOL: HIGH CPHA: 2 Edge alt text

Installation

  • Clone this repository to your pc. I suggest you clone to the driver folder in your STM32 project, but any directory in your PC should work as far as you add the path to your STM32 project as seen in the following steps.

  • In STM32CUBE IDE click on Project at the top bar

  • Select Properties alt text

  • click on Paths and Sysmbols alt text

  • Click on Add alt text

  • Select Add to all Configuration and Add to all languages

  • Click on filesystem and select the directory the library files are in. alt text

  • Click Apply and Close when done.

HOW TO USE THE CHIP

schematic diagram

Library initialisation

To initialise the library call the AD4112_Init function as seen below:

  int32_t ret;
  ret = AD4112_Init(&hspi1, AD4112_CS_GPIO_Port, AD4112_CS_Pin);
  printf("AD4112_Init returned: %ld\r\n", (long)ret);

Parmeters

Parameter Description
&hpi1 Adress of hardware SPI you are using
AD4112_CS_GPIO_Port Port of your Chip Select(CS) pin
AD4112_CS_PIN The pin you are using as Chip Select pin

Getting Device ID

Here is a sample code snippet to get the device ID.

int32_t ret =  AD717X_ReadRegister(ad4112_dev, AD717X_ID_REG);
if(ret <0){
	printf("unable to read read ID register: %ld\r\n", (long)ret);
}else{
	ad717x_st_reg *idReg = AD717X_GetReg(ad4112_dev, AD717X_ID_REG);
	if(idReg){
		printf("AD4112 ID is: %ld\r\n",idReg->value);
	}
}

Note: You should have intialised the library above this code snippet

AD717x

The ad717x.h and ad717x.c in the lib folder do most of the heavy lifting. I got from Analog Devices Website.

Although it is AD717x it still works for the following chip:

/*
 *@enum	ad717x_device_type
 *@details AD717x-AD411x Device definitions
**/
enum ad717x_device_type {
	ID_AD4111,
	ID_AD4112,
	ID_AD4114,
	ID_AD4115,
	ID_AD4116,
	ID_AD7172_2,
	ID_AD7172_4,
	ID_AD7173_8,
	ID_AD7175_2,
	ID_AD7175_8,
	ID_AD7176_2,
	ID_AD7177_2
};

Read Register Contents

int32_t AD717X_ReadRegister(ad717x_dev *device,
			    uint8_t addr);

This function is used to read the contents of any of the ADC registers. It takes two parameters the device, which for us is ad4112_dev and the address you want to read.

After you read a register, to acecess the contents we use the getRegister fucntion

Get Register Content

ad717x_st_reg *AD717X_GetReg(ad717x_dev *device,
			     uint8_t reg_address);

It is used to access the contents of a register that was read. If you call this function without reading the register first, you would get the value from the last read operation.

It takes two parameters: the device, which for us is ad4112_dev and the address you want to get values from.

It returns an ad717x_st_reg pointer, which is a struct with the following variables:

typedef struct {
	int32_t addr;
	int32_t value;
	int32_t size;
} ad717x_st_reg;

Write to Register

int32_t AD717X_WriteRegister(ad717x_dev *device,
			     uint8_t addr);

This function is used to write to a specific register on the AD4112 chip.

It takes two parameters: the device, which for us is ad4112_dev and the address you want to write values to.

It returns a 32bit integer whose value is 0 when writing is successful and negative if it fails.

Reset Device

/*! Resets the device. */
int32_t AD717X_Reset(ad717x_dev *device);

This command resets the device to its default state. The default values for all registers are listed in the register table summary under the Reset column of the datasheet. Reset values

It takes only one parameter, which is our device, ad4112_dev.

It returns 0 if reset is successful and negative if it fails.

Wait for device to be ready

int32_t AD717X_WaitForReady(ad717x_dev *device, uint32_t timeout);

This functions checks if the device is ready to receive any data by checking the values of the ready bit in the status register.

It takes two parameters: the device, which for us is ad4112_dev and a timeout duration. You can use the constant AD717X_CONV_TIMEOUT as the value for the timeout parameter

It returns 0 if successful and negative if it fails.

Read data register

/*! Reads the conversion result from the device. */
int32_t AD717X_ReadData(ad717x_dev *device,
			int32_t* pData);

This function reads the contents of the data register. By default the contents read from the data register is 24bits but if DATA_STA is enabled the content of the 8 bit status register is appended to it making it 32bit.

It takes two parameters: the device, which for us is ad4112_dev and a 32 bit integer pointer, which is where the read data is written to.

It returns 0 if successful and negative if it fails.

Compute data register size

/*! Computes data register read size to account for bit number and status
 *  read. */
int32_t AD717X_ComputeDataregSize(ad717x_dev *device);

This function computes the size of the data register, it is called anytime the AD717X_ReadData(ad717x_dev *device,int32_t* pData) is called so you wont ever have to explcitly call this function. You can always access the size of any register including the data register by checking the size property of the struct that is returned when you call the ad717x_st_reg *AD717X_GetReg(ad717x_dev *device,uint8_t reg_address);

typedef struct {
	int32_t addr;
	int32_t value;
	int32_t size; // check this property
} ad717x_st_reg;

It takes only one parameter, which is our device, ad4112_dev.

It returns 0 if reset is successful and negative if it fails.

tell gpt to convert to table when done

Enable/ Disable Channel

int ad717x_set_channel_status(ad717x_dev *device, uint8_t channel_id, bool channel_status);

A “channel” in the AD4112 is a programmable connection between an input pair and one of the ADC’s configuration setups. It tells the ADC what to measure, how to measure it, and whether it’s active.

The device parameter is still ad4112_dev, the are 8 channels named channel 0-7, set channel status to true to enable the channel.

It returns 0 if successful and negative if it fails.

Set ADC mode

/* Set ADC Mode */
int ad717x_set_adc_mode(ad717x_dev *device, enum ad717x_mode mode);

Here are the following modes for the ADC:

/*
 *@enum	ad717x_mode
 *@details ADC Modes of Operation
**/
enum ad717x_mode {
	CONTINUOUS, 			/* Continuous Mode- Default */
	SINGLE, 				/* Single Mode */
	STANDBY, 				/* Stand-by Mode */
	POWER_DOWN, 			/* Power Down Mode */
	INTERNAL_OFFSET_CALIB,	/* Internal Offset Calibration*/
	INTERNAL_GAIN_CALIB, 	/* Internal Gain Calibration */
	SYS_OFFSET_CALIB, 		/* System Offset Calibraion */
	SYS_GAIN_CALIB			/* System Gain Calibration */
};

It returns 0 if successful and negative if it fails.

Selecting an Input

/* Configure Analog inputs to channel */
int ad717x_connect_analog_input(ad717x_dev *device, uint8_t channel_id, union ad717x_analog_inputs analog_input);

This function configures which analog input pins are connected to a specific channel on the AD4112 (and similar ADCs in the AD717x family). It updates the channel mapping register corresponding to the specified channel so that the ADC measures the desired analog input pair.

Parameters:

  • ad717x_dev *device — Pointer to the device structure representing the ADC instance. It contains information about the active device, register map, and channel configuration.
  • uint8_t channel_id — The channel number (e.g., 0–15) whose analog input mapping is to be configured.
  • union ad717x_analog_inputs analog_input — Specifies which analog input(s) should be connected to the given channel. The union allows for different structures depending on the ADC model (for example, a single input pair for the AD4112 or separate positive and negative inputs for other models).

Return: Returns 0 on success or -EINVAL (invalid argument) if any parameter is invalid, the channel register cannot be retrieved, or the write operation to the device fails.

Assigning a setup to a channel

int ad717x_assign_setup(ad717x_dev *device, uint8_t channel_id,
			uint8_t setup);

This function assigns one of the AD4112’s configuration setups to a specified channel. Each channel on the AD4112 can be linked to one of eight setups, where each setup defines parameters such as gain, filter type, reference selection, and polarity mode. The function updates the channel’s mapping register to associate it with the chosen setup and writes the change to the ADC.

Parameters:

  • ad717x_dev *device — Pointer to the AD4112 device structure (always of type ad4112_dev). It holds register information, channel mappings, and device-specific context.
  • uint8_t channel_id — The channel number (0–15) whose setup configuration is to be assigned.
  • uint8_t setup — The setup index (0–7) that will be applied to the selected channel.

Return: Returns 0 on success or -EINVAL(it is a negative value) if any parameter is invalid, the channel register cannot be retrieved, or the write operation to the AD4112 fails.

Assiging Polarity to a Setup

/* Assign polarity to setup*/
int ad717x_set_polarity(ad717x_dev* device, bool bipolar uint8_t setup_id);

This function sets the polarity mode (bipolar or unipolar) for a specific setup configuration on the AD4112. Each setup defines how the ADC interprets its input voltage range. In bipolar mode, the ADC measures both positive and negative voltages around midscale, while in unipolar mode, it measures only positive voltages starting from zero. The function updates the setup configuration register to reflect the desired polarity mode and writes the change to the AD4112.

Parameters:

  • ad717x_dev *device — Pointer to the AD4112 device structure (always of type ad4112_dev). It provides access to register mappings and setup configurations.
  • bool bipolar — Determines the input polarity mode. Set to true for bipolar operation or false for unipolar.
  • uint8_t setup_id — The setup number (0–7) whose polarity setting is to be modified.

Return: Returns 0 on success, or a negative value (-EINVAL) if any parameter is invalid, the setup register cannot be retrieved, or the write operation to the AD4112 fails. A negative return value always indicates an error condition.

Assigning reference source to setup

/* Assign reference source to setup */
int ad717x_set_reference_source(ad717x_dev* device,enum ad717x_reference_source ref_source, uint8_t setup_id);

This function selects the reference voltage source used by a specific setup configuration on the AD4112. The reference source determines how the ADC scales and interprets the analog input voltage during conversion. Depending on the application, the reference can be internal, external, or buffered inputs. When the internal reference is selected, the function also enables it in the ADC mode register to ensure proper operation.

Parameters:

  • ad717x_dev *device — Pointer to the AD4112 device structure (always of type ad4112_dev). It provides access to all register mappings and setup configurations.
  • enum ad717x_reference_source ref_source — The reference source to use for the selected setup. This can typically be internal, external, or reference input pair depending on the hardware configuration.
/*
 *@enum	ad717x_reference_source
 *@details Type of ADC Reference
**/
enum ad717x_reference_source {
	EXTERNAL_REF = 0x0, /* External Reference REF+/-*/
	INTERNAL_REF = 0x2,	/* Internal 2.5V Reference */
	AVDD_AVSS = 0x3		/* AVDD - AVSS */
};
  • uint8_t setup_id — The setup number (0–7) to which the reference source will be applied.

Return: Returns 0 on success, or a negative value (-EINVAL) if any parameter is invalid, a register cannot be retrieved, or a write operation to the AD4112 fails. A negative return value always indicates an error condition.

Enabling Input and Reference Buffers

/* Enable/Disable input and reference buffers to setup */
int ad717x_enable_buffers(ad717x_dev* device, bool inbuf_en,bool refbuf_en, uint8_t setup_id);

This function enables or disables the analog input and reference input buffers for a specific setup configuration on the AD4112. These buffers isolate the ADC inputs from external circuitry, improving performance when dealing with high-impedance sources or noisy environments. Enabling the buffers increases input impedance and stability, while disabling them reduces power consumption and input delay.

Parameters:

  • ad717x_dev *device — Pointer to the AD4112 device structure (always of type ad4112_dev). It provides access to setup configurations and register mappings.
  • bool inbuf_en — Set to true to enable the analog input buffers (both positive and negative), or false to disable them.
  • bool refbuf_en — Set to true to enable the reference input buffers (both positive and negative), or false to disable them.
  • uint8_t setup_id — The setup number (0–7) whose input and reference buffer configuration is to be modified.

Return: Returns 0 on success, or a negative value (-EINVAL) if any parameter is invalid, the setup register cannot be retrieved, or the write operation to the AD4112 fails. A negative return value always indicates an error condition.

AD4112 Register Info

If you open the ad411x_regs.h you would see all the available registers. Some can be directly manipulated using some already explained functions. For Instance

The AD717X_SETUPCONX_REG register is control the following properties

  • enable or disable input buffers
  • enable or disable ref buffers
  • choose reference source (There is no special function for this)

There are already dedicated function for enabling input and reference buffers.

Here’s a full explanation of what every register in the ad4111_regs[] array (which also applies to the AD4112) does — from top to bottom. Each entry in this array corresponds to a hardware register inside the ADC, specifying its address, default value, and size (in bytes).

🧩 REGISTER-BY-REGISTER EXPLANATION

If you go to page 44 of the data sheet you will see all the register summary. You would notice that there are more fucntions the ADC can perform that are not available in the library.

You can access such functions by getting the register using the AD717X_GetReg() fucntion, edit the register values to you desired specifications and write them back using the AD717X_WriteRegister() function. Below is a sample code that can be used to manipulate any register

	  ad717x_st_reg *my_gain_reg = AD717X_GetReg(ad4112_dev, AD717X_GAIN0_REG);
	  if(my_gain_reg){
		
		  my_gain_reg->value =0x555519;
		  AD717X_WriteRegister(ad4112_dev, AD717X_GAIN0_REG);
	  }

Here I am writing a new value to the GAIN 0 register. You can apply the exact code to any register by changing AD717X_GAIN0_REG to the register you are pointing to

1. AD717X_IFMODE_REG

  • Size: 2 bytes

  • Purpose: Configures the SPI interface behavior.

  • Contains:

    • Continuous read enable.
    • CRC mode (none, 8-bit, 16-bit).
    • Data/stat appending.
    • DOUT polarity, data word order, etc.
  • Use: Define communication format between MCU and ADC. alt text alt text

Sample code:

	  ad717x_st_reg *my_if_reg = AD717X_GetReg(ad4112_dev, AD717X_IFMODE_REG);
	  if(my_if_reg){
		
		  my_if_reg->value =0x40;
		  AD717X_WriteRegister(ad4112_dev, AD717X_IFMODE_REG);
	  }

This should enable DATA_STA bit; hence appending a copy of the 8 bit status register to our data.

You should get the idea by now, So I wont be adding snap shot of the register table and code samples for subsequent registers.

2. AD717X_REGCHECK_REG

  • Size: 3 bytes
  • Purpose: Stores CRC check results for register integrity verification.
  • Use: Used when CRC protection is enabled to validate register writes.

3. AD717X_DATA_REG

  • Size: 3 bytes
  • Purpose: Holds the latest ADC conversion result.
  • Use: MCU reads this register to get measurement data.

4. AD717X_GPIOCON_REG

  • Size: 2 bytes

  • Purpose: Controls GPIO pin modes (input/output), synchronization, and excitation currents.

  • Contains:

    • Bits for digital I/O enable.
    • SYNC_EN (sync input enable).
    • Excitation current source control.
  • Use: Configure GPIOs for triggering, synchronization, or sensor biasing.


5. AD717X_ID_REG

  • Size: 2 bytes
  • Purpose: Stores device identification and revision information.
  • Use: Verify device type (e.g., AD4112 vs. AD7175) during initialization.

6. AD717X_FILTCON0_REGAD717X_FILTCON7_REG

  • Size: 2 bytes each

  • Purpose: Configure the digital filter and data rate for each setup.

  • Contains:

    • Filter type (sinc3, sinc5, enhanced).
    • Output data rate (ODR).
  • Use: Control conversion speed, filter shape, and noise performance.


7. AD717X_OFFSET0_REGAD717X_OFFSET7_REG

  • Size: 3 bytes each
  • Purpose: Store offset calibration coefficients for each setup.
  • Use: Applied automatically to conversion data to remove DC offset errors.

7. AD717X_GAIN0_REGAD717X_GAIN7_REG

  • Size: 3 bytes each
  • Purpose: Store gain calibration coefficients for each setup.
  • Use: Correct the ADC’s scaling error, ensuring accurate measurement across full input range.

In short:

  • Channel registers decide what to measure.
  • Setup registers decide how to measure it.
  • Filter registers decide how fast and clean the result should be.
  • Offset and Gain registers ensure accuracy.
  • ADCMode, IFMode, and GPIO registers control the device operation and interface.

Calibration

To get accurate readings, we need to calibrate first. There are four types of calibration for the AD4112 chip. They are as follows:

  • Internal zero scale (offset) calibration
  • Internal full scale (gain) calibration
  • System zero scale (offset) calibration
  • System full scale (gain) calibration

Difference between System and Internal Calibration

Internal: Only calibrates the ADC System: During Calibration it takes into consideration the whole system including what is connected to the system

Note: You only need to perform an offset and gain calibration. If you choose internal calibration then you perform only internal offset and internal gain calibration. The same applies to System calibration

I created a generic function to help carry out any of the four types of calibration.

int32_t calibrate(uint8_t channel, uint8_t setup_id, int refSource,
		bool bipolar, int input_pair, int mode) {

	int32_t ret = ad717x_set_channel_status(ad4112_dev, channel, true);
	if (ret < 0) {
		printf("setting channel %u failed: %ld\r\n", (unsigned) channel,
				(long) ret);
	}

	ret = ad717x_set_reference_source(ad4112_dev, refSource, setup_id);
	if (ret < 0) {
		printf("unable to set reference source: %ld\r\n", (long) ret);
	}
	ret = ad717x_set_polarity(ad4112_dev, bipolar, setup_id);
	if (ret < 0) {
		printf("ad717x_set_polarity failed: %ld\r\n", (long) ret);
	} else {
		printf("polarity: %s\r\n", bipolar ? "bipolar" : "unipolar");
	}
	ret = ad717x_assign_setup(ad4112_dev, channel, setup_id);
	if (ret < 0) {
		printf("ad717x_assign_setup failed: %ld\r\n", (long) ret);
	}
	union ad717x_analog_inputs analog_input;
	analog_input.analog_input_pairs = input_pair; // choose the correct pair for your wiring
	ret = ad717x_connect_analog_input(ad4112_dev, channel, analog_input);
	if (ret < 0)
		printf("ad717x_connect_analog_input err: %ld\r\n", (long) ret);

	printf("Performing  calibration...\r\n");
	ret = ad717x_set_adc_mode(ad4112_dev, mode);
	if (ret < 0)
		return ret;

	ret = AD717X_WaitForReady(ad4112_dev, AD717X_CONV_TIMEOUT);

	if (ret < 0) {
		printf(" calibration timeout\r\n");
		return ret;
	}
	printf("  calibration complete.\r\n");

	/* Restore normal conversion mode */
	ret = ad717x_set_adc_mode(ad4112_dev, CONTINUOUS);
	if (ret < 0)
		printf("Failed to restore mode: %ld\r\n", (long) ret);
	else
		printf("ADC mode restored to CONTINUOUS\r\n");

	if (mode == INTERNAL_GAIN_CALIB || mode == SYS_GAIN_CALIB) {
		// check if it performing a gain calibration
		// --- Read GAIN ---
		uint8_t gainRegValue = AD717X_GAIN0_REG + setup_id;
		ret = AD717X_ReadRegister(ad4112_dev, gainRegValue);
		if (ret == 0) {
			ad717x_st_reg *gainReg = AD717X_GetReg(ad4112_dev, gainRegValue);
			if (gainReg) {
				uint32_t gain_reg = gainReg->value & 0xFFFFFF;
				//gain_reg = 0x5837A0 & 0xFFFFFF;
				printf("Gain %i: 0x%06lX\r\n", setup_id,
						(unsigned long) gain_reg);
			}
		} else {
			printf("unable to get gain%i\r\n", setup_id);
		}
	}

	if (mode == INTERNAL_OFFSET_CALIB || mode == SYS_OFFSET_CALIB) {
		//check if it performing offset calib
		// --- Read OFFSET0 ---
		uint8_t offsetRegValue = AD717X_OFFSET0_REG+setup_id;
		ret = AD717X_ReadRegister(ad4112_dev, offsetRegValue);
		if (ret == 0) {
			ad717x_st_reg *offsetReg = AD717X_GetReg(ad4112_dev,
					offsetRegValue);
			if (offsetReg) {
				uint32_t offset_reg = offsetReg->value & 0xFFFFFF;
				//offset_reg = 0x83F878& 0xFFFFFF;
				printf("Offset %i: 0x%06lX\r\n", setup_id,
						(unsigned long) offset_reg);
			}
		} else {
			printf("unable to get offset%i\r\n", setup_id);
		}
	}

}

Explaining function parameters

Channels

There are 16 ADC channels in the AD4112, each channel can be assigned to any of the input pins on the ADC.

Setup ID

There are 8 setup configurations available for the AD4112 each having a unique setup ID from 0-7. Starting from address 0x20 . Setup are like configuration files that affects how a channel should work.

This is advantgeous for situations where you have different types of sensors with unique characteristics that needs to be connected to the ADC, you can create a setup for each of the sensor an assign the setup to the channel your sensor is connected to. alt text A setup configuration can be applied to single or multiple channels . Each setup comes with various configurations optionsas seen below: alt text I will go into the details about the configurations in later sections.

RefSource: The AD4112 comes with three options for Reference source

/*
 *@enum	ad717x_reference_source
 *@details Type of ADC Reference
**/
enum ad717x_reference_source {
	EXTERNAL_REF = 0x0, /* External Reference REF+/-*/
	INTERNAL_REF = 0x2,	/* Internal 2.5V Reference */
	AVDD_AVSS = 0x3		/* AVDD - AVSS */
};
  1. Internal reference (2.5 V)

    • Generated inside the ADC (enable with REF_EN).
    • Convenient, no external parts.
    • Moderate accuracy and drift.
  2. External reference (REFIN+ / REFIN− pins)

    • You provide a precision reference IC or circuit.
    • Highest accuracy and stability (best for precision measurement).
    • Requires extra hardware.
  3. AVDD–AVSS (supply rails as reference)

    • Uses the ADC’s analog supply voltage difference (e.g., 5 V if AVDD=5 V, AVSS=0 V).
    • No extra components needed.
    • Accuracy depends directly on how stable and quiet your supply is (usually the noisiest / least stable option).

👉 In short:

  • Internal → easiest.
  • External → most precise.
  • AVDD–AVSS → free but least accurate.

by default using internal reference is disabled, but if you want to able to select an internal reference for your setup, you can enable in the lib/ad4112.c file.

 ad717x_init_param init_param = {
        .spi_init = spi_param,
        .regs = ad4111_regs,
        .num_regs = sizeof(ad4111_regs)/sizeof(ad4111_regs[0]),
		.active_device = ID_AD4112,
        .ref_en = false, //REFRENCE ENABLE
        .num_channels = 16,
        .num_setups = 8,
    };

Polarity setting (in SETUPCONx → BI_UNIPOLAR bit)

This tells the ADC how to interpret the input range and how to code the digital output.

1. Bipolar mode (BI_UNIPOLAR = 0)

  • Input range is centered around 0 V (differential).
  • You can measure both positive and negative voltages
  • Useful when your signal swings around 0.

2. Unipolar mode (BI_UNIPOLAR = 1)

  • Input range starts at 0 V and goes positive only.
  • You can measure only 0 → +FS (e.g., 0 to +10 V).
  • Useful when your signal is always positive (like sensor outputs, 4–20 mA loops converted to voltage, etc.).

Perfect — let’s clear this up carefully 👍. The AD4112 (and similar sigma-delta ADCs) supports input pairs that can be used as either differential or single-ended connections.

Input Pairs

The AD4112 inputs works as pairs, this pairs can be:

1. Differential Inputs

  • Definition: You measure the difference between two input pins: VIN+ – VIN−.

  • The ADC ignores any voltage common to both pins (common-mode), which gives better noise immunity.

  • Example pair: AIN0 (VIN+) and AIN1 (VIN−).

    • If VIN+ = 2.5 V and VIN− = 1.0 V → differential input = +1.5 V.
    • If VIN+ = 1.0 V and VIN− = 2.5 V → differential input = –1.5 V.
  • Use case: Sensors or signals that swing above and below a reference point, or where noise rejection is important.

2.Single-Ended Inputs

  • Definition: You measure the voltage of one pin relative to a common reference pin (usually VINCOM or AVSS).

  • Example: AIN0 vs VINCOM.

    • If AIN0 = 2.5 V, VINCOM = 0 V → input = +2.5 V.
  • The other pin of the pair is tied to VINCOM.

  • Drawback: More sensitive to noise, since common-mode rejection is lost.

  • Use case: When the signal is always positive and referenced to ground (like sensor outputs, unipolar ranges).

enum ad717x_analog_input_pairs {
	VIN0_VIN1 = 0x1,
	VIN0_VINCOM = 0x10, // single ended
	VIN1_VIN0 = 0x20,
	VIN1_VINCOM = 0x30, // single ended
	VIN2_VIN3 = 0x43,
	VIN2_VINCOM = 0x50, // single ended
	VIN3_VIN2 = 0x62,
	VIN3_VINCOM = 0x70, // single ended
	VIN4_VIN5 = 0x85,
	VIN4_VINCOM = 0x90, // single ended
	VIN5_VIN4 = 0xA4,
	VIN5_VINCOM = 0xB0, // single ended
	VIN6_VIN7 = 0xC7,
	VIN6_VINCOM = 0xD0, // single ended
	VIN7_VIN6 = 0xE6,
	VIN7_VINCOM = 0xF0, // single ended
	IIN3P_IIN3M = 0x18B, 
	IIN2P_IIN2M = 0x1AA, 
	IIN1P_IIN1M = 0x1C9, 
	IIN0P_IIN0M = 0x1E8, // single ended
	TEMPERATURE_SENSOR = 0x232, // single ended
	REFERENCE = 0x2B6 // single ended
};

/*
 *@enum	ad717x_analog_input
 *@details Positive/Negative Analog Input to channels for the AD717x Family
**/
enum ad717x_analog_input {
	AIN0 = 0x0,
	AIN1 = 0x1,
	AIN2 = 0x2,
	AIN3 = 0x3,
	AIN4 = 0x4,
	TEMP_SENSOR_P = 0x11,
	TEMP_SENSOR_M = 0x12,
	AVDD_AVSS_P = 0x13,
	AVDD_AVSS_M = 0x14,
	REF_P = 0x15,
	REF_M = 0x16
};

mode

The ADC has 8 different modes. The modes for calibration have been explained earlier.

/*
 *@enum	ad717x_mode
 *@details ADC Modes of Operation
**/
enum ad717x_mode {
	CONTINUOUS, 			/* Continuous Mode- Default */
	SINGLE, 				/* Single Mode */
	STANDBY, 				/* Stand-by Mode */
	POWER_DOWN, 			/* Power Down Mode */
	INTERNAL_OFFSET_CALIB,	/* Internal Offset Calibration*/
	INTERNAL_GAIN_CALIB, 	/* Internal Gain Calibration */
	SYS_OFFSET_CALIB, 		/* System Offset Calibraion */
	SYS_GAIN_CALIB			/* System Gain Calibration */
};

1. Continuous mode (default)

  • The ADC runs free-running conversions on the selected channel(s).
  • After each conversion completes, the result is placed in the data register automatically.
  • Best for continuous monitoring applications (e.g., sensor logging).

2. Single mode

  • The ADC performs one conversion only, then automatically stops.
  • To get another result, you must re-trigger the conversion.
  • Useful when you want to save power or only sample occasionally.

3. Standby mode

  • The ADC’s core is idled to reduce power consumption, but it can wake up quickly (much faster than power-down).
  • Keeps configuration registers and reference circuits alive.
  • Good trade-off when you need low power but fast resume.

4. Power-down mode

  • The ADC is fully powered down, consuming the least current.
  • All conversions stop, internal clocks and circuits are disabled.
  • Waking up takes longer (requires reinitialization of parts of the ADC).
  • Best when you don’t need the ADC for long periods.

After running the calibration function, the gain and offset values will be printed. Note this values or store them somewhere. The gain and offset registered get reset whenever a reset or power on cycle occurs, so you will need to write the offset and gain values back to their respective registers before taking any measurements.

How to Perform Internal Gain and Offset Calibration

Just call the above function for gain calibration and then call the function again for offset calibration with appropriate parameters

Function parametes to perform internal gain calibration

calibrate(
0,  // channel for calibration
0, // using SETUPCON 0
EXTERNAL_REF, // I used an external reference source, this would vary depending on the ref source you have available
true, // polarity is bipolar 
VIN0_VIN1, // input pairs, this doesn't do anything during internal calib
INTERNAL_GAIN_CALIB // performing internal gain calibration
);

function parameters to perform internal offset calibration

calibrate(
0,  // channel for calibration
0, // using SETUPCON 0
EXTERNAL_REF, // I used an external reference source, this would vary depending on the ref source you have available
true, // polarity is bipolar 
VIN0_VIN1, // input pairs, this doesn't do anything during internal calib
INTERNAL_OFFSET_CALIB // performing internal offset calibration
);

You can run both functions in same codebase i.e after running offset calib you can immediately call the function again with different set of parameters for internal gain calibration. However this approach would be incorrect for system calibration as you would need the inputs.

How to Perfom Systme Gain and Offset Calibration

System Offset Calibration

Since we are using VIN0_VIN1 input pair, connect both VIN0 and VIN1 to the GND or to the same voltage source such that their potential difference is 0. Then call the fucntion with the following parameters:

calibrate(
0,  // channel for calibration
0, // using SETUPCON 0
EXTERNAL_REF, // I used an external reference source, this would vary depending on the ref source you have available
true, // polarity is bipolar 
VIN0_VIN1, // input pairs, this doesn't do anything during internal calib
SYSTEM_OFFSET_CALIB // performing internal offset calibration
);

Note that you can't just initiate SYSTEM GAIN CALIBARTION just after running SYSTEM OFFSET CALIBRATION as you would have to connect your input to a voltage reference source during SYSTEM GAIN CALIBARTION.

Measuring Voltage

Before measuring voltage to need to have a

  • Set the parameters for your desired SetupCONx, x= 0-8
    • enable input buffers
    • set references source
    • set polarity
    • Assign input pairs
  • assign setupCONx to channel
  • write your gain and offset calibration values to the gain and offset registers of the setup you are using.

If you calibrated using setup 0 the gain and offset values will be written to GAIN 0 and OFFSET 0, but you can apply this values to any SETUPX by just writing the same offset and gain values to the respective registers. Athough, I haven't tested it, it should work.

  • read voltage from your channel

Each setup has a corresponding gain and offset register. Setup 0 using GAIN0 and OFFSET0 registers. The same applies to other Setups

Below is code sample to write to GAIN and OFFSET register, you can apply this same code to write to other GAIN and OFFSET registers by choosing the desired AD717X_GAINX_REG. Where X is from 0-7

	  ad717x_st_reg *my_gain_reg = AD717X_GetReg(ad4112_dev, AD717X_GAIN0_REG);
	  if(my_gain_reg){
		 
		  my_gain_reg->value =0x555519; // gain value
		  AD717X_WriteRegister(ad4112_dev, AD717X_GAIN0_REG);
	  }

	  ad717x_st_reg *my_offset_reg = AD717X_GetReg(ad4112_dev, AD717X_OFFSET0_REG);
	  if(my_offset_reg){
		
		  my_offset_reg->value =0x7FFF97; // offset value
		  AD717X_WriteRegister(ad4112_dev, AD717X_OFFSET0_REG);
	  }

Formular

To get Input Voltgae(Vin) or Input Current(Iin), just make them subject of formular.

Unipolar

$Code=(2^NVin0.1)/Vref$
$Code=(2^NIin50Ω)/Vref$

For Bipolar

$Code=2^{N-1}((Vin0.1/Vref)+1)$
$Code=2^{N-1}((Iin50Ω/Vref)+1)$

Read Voltage

I highly recommend this method for reading data, it enables the channel, assigns a setup to a channel for you, gets the channel readings and then diasables the channel.

With this method, you have to epxlicitly state the channel you are using. This is advantageous as you can always tell what channel your data is coming from.

// first set the parameters for your setupCONX
	  //voltage measurement
	    bool input_en = true;
	    bool ref_en = false;
	    int voltage_input_pair = VIN0_VIN1;
		uint8_t setup_id = 0;
	    uint8_t voltage_channel = 0;

	    ret = ad717x_enable_input_buffer(ad4112_dev, input_en, ref_en, setup_id);
	    if (ret < 0)
	      printf("ad717x_enable_input_buffer err: %ld\r\n", (long)ret);
	    
	  ret = ad717x_set_reference_source(ad4112_dev, EXTERNAL_REF, setup_id);
	  if (ret < 0) {
	    printf("unable to set reference source: %ld\r\n", (long)ret);
	  }
	  ret = ad717x_set_polarity(ad4112_dev, bipolar, setup_id);
	  if (ret < 0) {
	    printf("ad717x_set_polarity failed: %ld\r\n", (long)ret);
	  } else {
	    printf("polarity: %s\r\n", bipolar ? "bipolar" : "unipolar");
	  }
	  ret = ad717x_assign_setup(ad4112_dev, voltage_channel, setup_id);
	  if (ret < 0) {
	    printf("ad717x_assign_setup failed: %ld\r\n", (long)ret);
	  }
	  union ad717x_analog_inputs analog_input;
	  analog_input.analog_input_pairs = voltage_input_pair; // choose the correct pair for your wiring

	  ret = ad717x_connect_analog_input(ad4112_dev, voltage_channel, analog_input);
	  if (ret < 0)
	    printf("ad717x_connect_analog_input err: %ld\r\n", (long)ret);

	// now using your configured setup, read your channel
		  ret = ad717x_single_read(ad4112_dev, voltage_channel, &pdata);
	  if (ret < 0) {
	      printf("ad717x_read error: %ld\r\n", (long)ret);
	      HAL_Delay(200);
	      continue;
	  }

	  printf("Raw sample: %ld\r\n", (long)pdata);

	voltage =(((double)pdata/0x800000)-1)*(10*Vref);//formula for voltage bipolar.
	printf("Calculated voltage: %.6f V\n", voltage);

Measuring Current

When measuring current make sure input buffers are disabled and use unipolar mode.

  uint8_t current_channel = 1;
  uint8_t current_setup_id=1;
  bool bipolar = false;
  bool current_setup_input_buf_en = false;
  bool current_setup_ref_buf_en= false;

/*
* Note that we are using channel 1, you can use any other channel you desire. Since we are using channel 1 the gain and offset registers will be GAIN 1 register and OFFSET 1 register.

There is no need to calibrate for current all gain and offset registers have been factory calibrate to give appropritae current values, you only calbirate when u want to measure voltage

Hence, you would notice I did not bother about the gain and offset registers when measuring current.
*/

	int32_t ret = ad717x_enable_buffers(ad4112_dev, current_setup_input_buf_en, current_setup_ref_buf_en, current_setup_id);
	    if (ret < 0)
	      printf("ad717x_enable_input_buffer err: %ld\r\n", (long)ret);

	  ret = ad717x_set_reference_source(ad4112_dev, EXTERNAL_REF, current_setup_id);
	  if (ret < 0) {
	    printf("unable to set reference source: %ld\r\n", (long)ret);
	  }
	  ret = ad717x_set_polarity(ad4112_dev, bipolar, current_setup_id);
	  if (ret < 0) {
	    printf("ad717x_set_polarity failed: %ld\r\n", (long)ret);
	  } else {
	    printf("polarity: %s\r\n", bipolar ? "bipolar" : "unipolar");
	  }
	  ret = ad717x_assign_setup(ad4112_dev, current_channel, current_setup_id);
	  if (ret < 0) {
	    printf("ad717x_assign_setup failed: %ld\r\n", (long)ret);
	  }
	  union ad717x_analog_inputs analog_input_current;
	  analog_input_current.analog_input_pairs = IIN0P_IIN0M; // choose the correct pair for your wiring
	  //analog_input_current.ainp=
	  ret = ad717x_connect_analog_input(ad4112_dev, current_channel, analog_input_current);
	  if (ret < 0)
	    printf("ad717x_connect_analog_input err: %ld\r\n", (long)ret);

//read current in unipolar mode
while(1){
	int32_t ret = ad717x_single_read(ad4112_dev, current_channel, &pdata);
	current = (Vref*(double)pdata)/(50*0x1000000);
	printf("current %.10f A\n", current);
}

Buffers Usage Guidelines

My general rule of thumb for the buffers are:

  • Always use input buffers for volatge measurement channels
  • Dont use Input buffers for current measurement
  • If you are using the recommeded external reference chip, ADR4525ARZ, disable reference buffer.

For a more better understanding, below is a table I whopped up using ChatGpt.

Buffer Type When to Enable When to Disable
Input Buffers - Source has high output impedance (e.g., sensors, dividers, RTDs).
- You need high input impedance or want to simplify the driver design.
- Channel accuracy is more critical than power consumption.
- Source can drive ≥370 kΩ directly.
- You want lower noise and reduced power consumption.
- Using low-impedance sources like op-amp outputs.
Reference Buffers - External reference has limited drive capability.
- You want high input impedance at REF+/REF–.
- Improved reference stability and reduced loading effects are desired.
- External reference can drive heavy loads (≤350 kΩ).
- Low-power mode operation.
- You prefer direct reference drive for slightly lower noise.

Extras

There are a lot more functionalities for the AD4112 chip I just listed a few. I recommend visiting the datasheet for a complete overview of the features of the AD4112 chip, but all features can be access through the library.

Tip

If a function you need is not explicity defined in the library. You can use the get register function to get the adress of the register you want to edit, edit it and write it back. Here is an example using the interface mode register:

	  ad717x_st_reg *my_IFMODE_reg = AD717X_GetReg(ad4112_dev, AD717X_IFMODE_REG);
	  if(my_IFMODE_reg){
	
		  my_IFMODE_reg->value =0x40;
		  AD717X_WriteRegister(ad4112_dev, AD717X_IFMODE_REG);
	  }

Support

I developed this driver toward the end of my internship, so I didn’t have as much time to explore the AD4112 chip in depth. I also only began writing this documentation near the conclusion of my internship.

I would like to further develop this documentation—improving its clarity, coherence, and structure—and also create a C++ version that applies object-oriented principles for better organization and maintainability. However, I currently don’t have access to an AD4112 chip.

There is an evaluation board available for the AD4112, but it’s quite expensive (around $156, including shipping). If you’d be willing to support me in acquiring the evaluation board, I would be sincerely grateful.

You can contact me at: onyemandkwu@gmail.com

Improvements

I’m open to any suggestions on how to improve this library. This is only the second driver I’ve developed—the first being for the LTC2959, a battery gas gauge—so I’m still relatively new to driver development and always eager to learn from others’ insights and feedback.

About

A library for the AD4112 Analog to Digital Converter. It is an adaptation from the c and headers files provided by Analog Devices, the manufacturer of the chip.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages