Mbed Development branch for MeasrueWare

Revision:
5:0728bde67bdb
Parent:
4:2ca06eee5735
Child:
6:9d393a9677f4
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/admw_1001.c	Wed Jun 05 05:39:15 2019 +0000
@@ -0,0 +1,3342 @@
+/*
+Copyright 2018 (c) Analog Devices, Inc.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+  - Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+  - Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in
+    the documentation and/or other materials provided with the
+    distribution.
+  - Neither the name of Analog Devices, Inc. nor the names of its
+    contributors may be used to endorse or promote products derived
+    from this software without specific prior written permission.
+  - The use of this software may or may not infringe the patent rights
+    of one or more patent holders. This license does not release you
+    from the requirement that you obtain separate licenses from these
+    patent holders to use this software.
+  - Use of the software either in source or binary form, must be run
+    on or directly connected to an Analog Devices Inc. component.
+
+THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT,
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/******************************************************************************
+Copyright 2017 (c) Analog Devices, Inc.
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+  - Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+  - Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in
+    the documentation and/or other materials provided with the
+    distribution.
+  - Neither the name of Analog Devices, Inc. nor the names of its
+    contributors may be used to endorse or promote products derived
+    from this software without specific prior written permission.
+  - The use of this software may or may not infringe the patent rights
+    of one or more patent holders. This license does not release you
+    from the requirement that you obtain separate licenses from these
+    patent holders to use this software.
+  - Use of the software either in source or binary form, must be run
+    on or directly connected to an Analog Devices Inc. component.
+
+THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT,
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, INTELLECTUAL PROPERTY RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *****************************************************************************/
+
+/*!
+ ******************************************************************************
+ * @file:
+ * @brief: ADMW API implementation for ADSNS1000
+ *-----------------------------------------------------------------------------
+ */
+
+#include <float.h>
+#include <math.h>
+#include <string.h>
+
+#include "admw_platform.h"
+#include "admw_api.h"
+#include "admw1001/admw1001_api.h"
+
+#include "admw1001/ADMW1001_REGISTERS_typedefs.h"
+#include "admw1001/ADMW1001_REGISTERS.h"
+#include "admw1001/admw1001_lut_data.h"
+#include "admw1001/admw1001_host_comms.h"
+
+#include "crc16.h"
+
+
+uint32_t    getDataCnt = 0;
+
+/*
+ * The following macros are used to encapsulate the register access code
+ * to improve readability in the functions further below in this file
+ */
+#define STRINGIFY(name) #name
+
+/* Expand the full name of the reset value macro for the specified register */
+#define REG_RESET_VAL(_name) REG_##_name##_RESET
+
+/* Checks if a value is outside the bounds of the specified register field */
+#define CHECK_REG_FIELD_VAL(_field, _val)                               \
+    do {                                                                \
+        uint32_t _mask  = BITM_##_field;                       \
+        uint32_t _shift = BITP_##_field;                       \
+        if ((((_val) << _shift) & ~(_mask)) != 0) {                     \
+            ADMW_LOG_ERROR("Value 0x%08X invalid for register field %s", \
+                                (uint32_t)(_val),                       \
+                                STRINGIFY(ADMW_##_field));          \
+            return ADMW_INVALID_PARAM;                             \
+        }                                                               \
+    } while(false)
+
+/*
+ * Encapsulates the write to a specified register
+ * NOTE - this will cause the calling function to return on error
+ */
+#define WRITE_REG(_hdev, _val, _name, _type)                            \
+    do {                                                                \
+        ADMW_RESULT _res;                                          \
+        _type _regval = _val;                                           \
+        _res = admw1001_WriteRegister((_hdev),                    \
+                                            REG_##_name,       \
+                                            &_regval, sizeof(_regval)); \
+        if (_res != ADMW_SUCCESS)                                  \
+            return _res;                                                \
+    } while(false)
+
+/* Wrapper macro to write a value to a uint32_t register */
+#define WRITE_REG_U32(_hdev, _val, _name)       \
+    WRITE_REG(_hdev, _val, _name, uint32_t)
+/* Wrapper macro to write a value to a uint16_t register */
+#define WRITE_REG_U16(_hdev, _val, _name)       \
+    WRITE_REG(_hdev, _val, _name, uint16_t)
+/* Wrapper macro to write a value to a uint8_t register */
+#define WRITE_REG_U8(_hdev, _val, _name)        \
+    WRITE_REG(_hdev, _val, _name, uint8_t)
+/* Wrapper macro to write a value to a float32_t register */
+#define WRITE_REG_FLOAT(_hdev, _val, _name)     \
+    WRITE_REG(_hdev, _val, _name, float32_t)
+
+/*
+ * Encapsulates the read from a specified register
+ * NOTE - this will cause the calling function to return on error
+ */
+#define READ_REG(_hdev, _val, _name, _type)                             \
+    do {                                                                \
+        ADMW_RESULT _res;                                          \
+        _type _regval;                                                  \
+        _res = admw1001_ReadRegister((_hdev),                     \
+                                           REG_##_name,        \
+                                           &_regval, sizeof(_regval));  \
+        if (_res != ADMW_SUCCESS)                                  \
+            return _res;                                                \
+        _val = _regval;                                                 \
+    } while(false)
+
+/* Wrapper macro to read a value from a uint32_t register */
+#define READ_REG_U32(_hdev, _val, _name)        \
+    READ_REG(_hdev, _val, _name, uint32_t)
+/* Wrapper macro to read a value from a uint16_t register */
+#define READ_REG_U16(_hdev, _val, _name)        \
+    READ_REG(_hdev, _val, _name, uint16_t)
+/* Wrapper macro to read a value from a uint8_t register */
+#define READ_REG_U8(_hdev, _val, _name)         \
+    READ_REG(_hdev, _val, _name, uint8_t)
+/* Wrapper macro to read a value from a float32_t register */
+#define READ_REG_FLOAT(_hdev, _val, _name)      \
+    READ_REG(_hdev, _val, _name, float32_t)
+
+/*
+ * Wrapper macro to write an array of values to a uint8_t register
+ * NOTE - this is intended only for writing to a keyhole data register
+ */
+#define WRITE_REG_U8_ARRAY(_hdev, _arr, _len, _name)                \
+    do {                                                            \
+        ADMW_RESULT _res;                                      \
+        _res = admw1001_WriteRegister(_hdev,                  \
+                                            REG_##_name,   \
+                                            _arr, _len);            \
+        if (_res != ADMW_SUCCESS)                              \
+            return _res;                                            \
+    } while(false)
+
+/*
+ * Wrapper macro to read an array of values from a uint8_t register
+ * NOTE - this is intended only for reading from a keyhole data register
+ */
+#define READ_REG_U8_ARRAY(_hdev, _arr, _len, _name)                 \
+    do {                                                            \
+        ADMW_RESULT _res;                                      \
+        _res = admw1001_ReadRegister((_hdev),                 \
+                                           REG_##_name,    \
+                                           _arr, _len);             \
+        if (_res != ADMW_SUCCESS)                              \
+            return _res;                                            \
+    } while(false)
+
+#define ADMW1001_CHANNEL_IS_ADC(c)                                \
+    ((c) >= ADMW1001_CHANNEL_ID_CJC_0 && (c) <= ADMW1001_CHANNEL_ID_CURRENT_0)
+
+#define ADMW1001_CHANNEL_IS_ADC_CJC(c)                            \
+    ((c) >= ADMW1001_CHANNEL_ID_CJC_0 && (c) <= ADMW1001_CHANNEL_ID_CJC_1)
+
+#define ADMW1001_CHANNEL_IS_ADC_SENSOR(c)                         \
+    ((c) >= ADMW1001_CHANNEL_ID_SENSOR_0 && (c) <= ADMW1001_CHANNEL_ID_SENSOR_3)
+
+#define ADMW1001_CHANNEL_IS_ADC_VOLTAGE(c)    \
+    ((c) == ADMW1001_CHANNEL_ID_VOLTAGE_0)
+
+#define ADMW1001_CHANNEL_IS_ADC_CURRENT(c)    \
+    ((c) == ADMW1001_CHANNEL_ID_CURRENT_0)
+
+#define ADMW1001_CHANNEL_IS_VIRTUAL(c)                            \
+    ((c) == ADMW1001_CHANNEL_ID_SPI_1 || (c) == ADMW1001_CHANNEL_ID_SPI_2)
+
+typedef struct
+{
+    unsigned nDeviceIndex;
+    ADMW_SPI_HANDLE hSpi;
+    ADMW_GPIO_HANDLE hGpio;
+} ADMW_DEVICE_CONTEXT;
+
+static ADMW_DEVICE_CONTEXT gDeviceCtx[ADMW_PLATFORM_MAX_DEVICES];
+
+/*
+ * Open an ADMW device instance.
+ */
+ADMW_RESULT admw_Open(
+    unsigned                   const nDeviceIndex,
+    ADMW_CONNECTION     * const pConnectionInfo,
+    ADMW_DEVICE_HANDLE  * const phDevice)
+{
+    ADMW_DEVICE_CONTEXT *pCtx;
+    ADMW_RESULT eRet;
+
+    if (nDeviceIndex >= ADMW_PLATFORM_MAX_DEVICES)
+        return ADMW_INVALID_DEVICE_NUM;
+
+    pCtx = &gDeviceCtx[nDeviceIndex];
+    pCtx->nDeviceIndex = nDeviceIndex;
+
+    eRet = admw_LogOpen(&pConnectionInfo->log);
+    if (eRet != ADMW_SUCCESS)
+        return eRet;
+
+    eRet = admw_GpioOpen(&pConnectionInfo->gpio, &pCtx->hGpio);
+    if (eRet != ADMW_SUCCESS)
+        return eRet;
+
+    eRet = admw_SpiOpen(&pConnectionInfo->spi, &pCtx->hSpi);
+    if (eRet != ADMW_SUCCESS)
+        return eRet;
+
+    *phDevice = pCtx;
+    return ADMW_SUCCESS;
+}
+
+/*
+ * Get the current state of the specified GPIO input signal.
+ */
+ADMW_RESULT admw_GetGpioState(
+    ADMW_DEVICE_HANDLE   const hDevice,
+    ADMW_GPIO_PIN        const ePinId,
+    bool                  * const pbAsserted)
+{
+    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
+
+    return admw_GpioGet(pCtx->hGpio, ePinId, pbAsserted);
+}
+
+/*
+ * Register an application-defined callback function for GPIO interrupts.
+ */
+ADMW_RESULT admw_RegisterGpioCallback(
+    ADMW_DEVICE_HANDLE          const hDevice,
+    ADMW_GPIO_PIN               const ePinId,
+    ADMW_GPIO_CALLBACK          const callbackFunction,
+    void                           * const pCallbackParam)
+{
+    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
+
+    if (callbackFunction)
+    {
+        return admw_GpioIrqEnable(pCtx->hGpio, ePinId, callbackFunction,
+                                       pCallbackParam);
+    }
+    else
+    {
+        return admw_GpioIrqDisable(pCtx->hGpio, ePinId);
+    }
+}
+
+/*
+ * Reset the specified ADMW device.
+ */
+ADMW_RESULT admw_Reset(
+    ADMW_DEVICE_HANDLE    const hDevice)
+{
+    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
+    ADMW_RESULT eRet;
+
+    /* Pulse the Reset GPIO pin low for a minimum of 4 microseconds */
+    eRet = admw_GpioSet(pCtx->hGpio, ADMW_GPIO_PIN_RESET, false);
+    if (eRet != ADMW_SUCCESS)
+        return eRet;
+
+    admw_TimeDelayUsec(4);
+
+    eRet = admw_GpioSet(pCtx->hGpio, ADMW_GPIO_PIN_RESET, true);
+    if (eRet != ADMW_SUCCESS)
+        return eRet;
+
+    return ADMW_SUCCESS;
+}
+
+
+/*!
+ * @brief Get general status of ADISense module.
+ *
+ * @param[in]
+ * @param[out] pStatus : Pointer to CORE Status struct.
+ *
+ * @return Status
+ *         - #ADMW_SUCCESS Call completed successfully.
+ *         - #ADMW_FAILURE If status register read fails.
+ *
+ * @details Read the general status register for the ADISense
+ *          module. Indicates Error, Alert conditions, data ready
+ *          and command running.
+ *
+ */
+ADMW_RESULT admw_GetStatus(
+    ADMW_DEVICE_HANDLE    const hDevice,
+    ADMW_STATUS         * const pStatus)
+{
+    CORE_Status_t statusReg;
+    READ_REG_U8(hDevice, statusReg.VALUE8, CORE_STATUS);
+
+    CORE_Alert_Status_2_t alert2Reg;
+    READ_REG_U16(hDevice, alert2Reg.VALUE16, CORE_ALERT_STATUS_2);
+
+    memset(pStatus, 0, sizeof(*pStatus));
+
+    if (!statusReg.Cmd_Running) /* Active-low, so invert it */
+        pStatus->deviceStatus |= ADMW_DEVICE_STATUS_BUSY;
+    if (statusReg.Drdy)
+        pStatus->deviceStatus |= ADMW_DEVICE_STATUS_DATAREADY;
+    if (statusReg.FIFO_Error)
+        pStatus->deviceStatus |= ADMW_DEVICE_STATUS_FIFO_ERROR;
+    if (alert2Reg.Ext_Flash_Error)
+        pStatus->deviceStatus |= ADMW_DEVICE_STATUS_EXT_FLASH_ERROR;
+    if (statusReg.Alert_Active)
+    {
+        pStatus->deviceStatus |= ADMW_DEVICE_STATUS_ALERT;
+
+        CORE_Alert_Code_t alertCodeReg;
+        READ_REG_U16(hDevice, alertCodeReg.VALUE16, CORE_ALERT_CODE);
+        pStatus->alertCode = alertCodeReg.Alert_Code;
+
+        CORE_Channel_Alert_Status_t channelAlertStatusReg;
+        READ_REG_U16(hDevice, channelAlertStatusReg.VALUE16,
+                     CORE_CHANNEL_ALERT_STATUS);
+
+        for (unsigned i = 0; i < ADMW1001_MAX_CHANNELS; i++)
+        {
+            if (channelAlertStatusReg.VALUE16 & (1 << i))
+            {
+                CORE_Alert_Code_Ch_t channelAlertCodeReg;
+                READ_REG_U16(hDevice, channelAlertCodeReg.VALUE16, CORE_ALERT_CODE_CHn(i));
+                pStatus->channelAlertCodes[i] = channelAlertCodeReg.Alert_Code_Ch;
+
+                CORE_Alert_Detail_Ch_t alertDetailReg;
+                READ_REG_U16(hDevice, alertDetailReg.VALUE16,
+                             CORE_ALERT_DETAIL_CHn(i));
+
+                if (alertDetailReg.Time_Out)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_TIMEOUT;
+                if (alertDetailReg.Under_Range)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_UNDER_RANGE;
+                if (alertDetailReg.Over_Range)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_OVER_RANGE;
+                if (alertDetailReg.Low_Limit)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_LOW_LIMIT;
+                if (alertDetailReg.High_Limit)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_HIGH_LIMIT;
+                if (alertDetailReg.Sensor_Open)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_SENSOR_OPEN;
+                if (alertDetailReg.Ref_Detect)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_REF_DETECT;
+                if (alertDetailReg.Config_Err)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_CONFIG_ERR;
+                if (alertDetailReg.LUT_Error_Ch)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_LUT_ERR;
+                if (alertDetailReg.Sensor_Not_Ready)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_SENSOR_NOT_READY;
+                if (alertDetailReg.Comp_Not_Ready)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_COMP_NOT_READY;
+                if (alertDetailReg.Correction_UnderRange)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_LUT_UNDER_RANGE;
+                if (alertDetailReg.Correction_OverRange)
+                    pStatus->channelAlerts[i] |= ADMW_CHANNEL_ALERT_LUT_OVER_RANGE;
+            }
+        }
+
+        if (alert2Reg.Configuration_Error)
+            pStatus->deviceStatus |= ADMW_DEVICE_STATUS_CONFIG_ERROR;
+        if (alert2Reg.LUT_Error)
+            pStatus->deviceStatus |= ADMW_DEVICE_STATUS_LUT_ERROR;
+    }
+
+    if (statusReg.Error)
+    {
+        pStatus->deviceStatus |= ADMW_DEVICE_STATUS_ERROR;
+
+        CORE_Error_Code_t errorCodeReg;
+        READ_REG_U16(hDevice, errorCodeReg.VALUE16, CORE_ERROR_CODE);
+        pStatus->errorCode = errorCodeReg.Error_Code;
+
+        CORE_Diagnostics_Status_t diagStatusReg;
+        READ_REG_U16(hDevice, diagStatusReg.VALUE16, CORE_DIAGNOSTICS_STATUS);
+
+        if (diagStatusReg.Diag_Checksum_Error)
+            pStatus->diagnosticsStatus |= ADMW_DIAGNOSTICS_STATUS_CHECKSUM_ERROR;
+        if (diagStatusReg.Diag_Comms_Error)
+            pStatus->diagnosticsStatus |= ADMW_DIAGNOSTICS_STATUS_COMMS_ERROR;
+        if (diagStatusReg.Diag_Supply_Monitor_Error)
+            pStatus->diagnosticsStatus |= ADMW_DIAGNOSTICS_STATUS_SUPPLY_MONITOR_ERROR;
+        if (diagStatusReg.Diag_Supply_Cap_Error)
+            pStatus->diagnosticsStatus |= ADMW_DIAGNOSTICS_STATUS_SUPPLY_CAP_ERROR;
+        if (diagStatusReg.Diag_Conversion_Error)
+            pStatus->diagnosticsStatus |= ADMW_DIAGNOSTICS_STATUS_CONVERSION_ERROR;
+        if (diagStatusReg.Diag_Calibration_Error)
+            pStatus->diagnosticsStatus |= ADMW_DIAGNOSTICS_STATUS_CALIBRATION_ERROR;
+    }
+
+    if (statusReg.Alert_Active || statusReg.Error)
+    {
+        CORE_Debug_Code_t debugCodeReg;
+        READ_REG_U32(hDevice, debugCodeReg.VALUE32, CORE_DEBUG_CODE);
+        pStatus->debugCode = debugCodeReg.Debug_Code;
+    }
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw_GetCommandRunningState(
+    ADMW_DEVICE_HANDLE hDevice,
+    bool *pbCommandRunning)
+{
+    CORE_Status_t statusReg;
+
+    READ_REG_U8(hDevice, statusReg.VALUE8, CORE_STATUS);
+
+    /* We should never normally see 0xFF here if the module is operational */
+    if (statusReg.VALUE8 == 0xFF)
+        return ADMW_ERR_NOT_INITIALIZED;
+
+    *pbCommandRunning = !statusReg.Cmd_Running; /* Active-low, so invert it */
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT executeCommand(
+    ADMW_DEVICE_HANDLE const hDevice,
+    CORE_Command_Special_Command const command,
+    bool const bWaitForCompletion)
+{
+    CORE_Command_t commandReg;
+    bool bCommandRunning;
+    ADMW_RESULT eRet;
+
+    /*
+     * Don't allow another command to be issued if one is already running, but
+     * make an exception for CORE_COMMAND_NOP which can be used to
+     * request a running command to be stopped (e.g. continuous measurement)
+     */
+    if (command != CORE_COMMAND_NOP)
+    {
+        eRet = admw_GetCommandRunningState(hDevice, &bCommandRunning);
+        if (eRet)
+            return eRet;
+
+        if (bCommandRunning)
+            return ADMW_IN_USE;
+    }
+
+    commandReg.Special_Command = command;
+    WRITE_REG_U8(hDevice, commandReg.VALUE8, CORE_COMMAND);
+
+    if (bWaitForCompletion)
+    {
+        do {
+            /* Allow a minimum 50usec delay for status update before checking */
+            admw_TimeDelayUsec(50);
+
+            eRet = admw_GetCommandRunningState(hDevice, &bCommandRunning);
+            if (eRet)
+                return eRet;
+        } while (bCommandRunning);
+    }
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw_Shutdown(
+    ADMW_DEVICE_HANDLE const hDevice)
+{
+    return executeCommand(hDevice, CORE_COMMAND_POWER_DOWN, false);
+}
+
+
+ADMW_RESULT admw_ApplyConfigUpdates(
+    ADMW_DEVICE_HANDLE const hDevice)
+{
+    return executeCommand(hDevice, CORE_COMMAND_LATCH_CONFIG, true);
+}
+
+/*!
+ * @brief Start a measurement cycle.
+ *
+ * @param[out]
+ *
+ * @return Status
+ *         - #ADMW_SUCCESS Call completed successfully.
+ *         - #ADMW_FAILURE
+ *
+ * @details Sends the latch config command. Configuration for channels in
+ *          conversion cycle should be completed before this function.
+ *          Channel enabled bit should be set before this function.
+ *          Starts a conversion and configures the format of the sample.
+ *
+ */
+ADMW_RESULT admw_StartMeasurement(
+    ADMW_DEVICE_HANDLE    const hDevice,
+    ADMW_MEASUREMENT_MODE const eMeasurementMode)
+{
+    switch (eMeasurementMode)
+    {
+    case ADMW_MEASUREMENT_MODE_HEALTHCHECK:
+        return executeCommand(hDevice, CORE_COMMAND_SYSTEM_CHECK, false);
+    case ADMW_MEASUREMENT_MODE_NORMAL:
+        return executeCommand(hDevice, CORE_COMMAND_CONVERT_WITH_RAW, false);
+    case ADMW_MEASUREMENT_MODE_OMIT_RAW:
+        return executeCommand(hDevice, CORE_COMMAND_CONVERT, false);
+    case ADMW_MEASUREMENT_MODE_FFT:
+        return executeCommand(hDevice, CORE_COMMAND_CONVERT_FFT, false);
+    default:
+        ADMW_LOG_ERROR("Invalid measurement mode %d specified",
+                            eMeasurementMode);
+        return ADMW_INVALID_PARAM;
+    }
+}
+
+/*
+ * Store the configuration settings to persistent memory on the device.
+ * The settings can be saved to 4 different flash memory areas (slots).
+ * No other command must be running when this is called.
+ * Do not power down the device while this command is running.
+ */
+ADMW_RESULT admw_SaveConfig(
+    ADMW_DEVICE_HANDLE    const hDevice,
+    ADMW_USER_CONFIG_SLOT const eSlotId)
+{
+    switch (eSlotId)
+    {
+        case ADMW_FLASH_CONFIG_1:
+            return executeCommand(hDevice, CORE_COMMAND_SAVE_CONFIG_1, true);
+        case ADMW_FLASH_CONFIG_2:
+            return executeCommand(hDevice, CORE_COMMAND_SAVE_CONFIG_2, true);
+        case ADMW_FLASH_CONFIG_3:
+            return executeCommand(hDevice, CORE_COMMAND_SAVE_CONFIG_3, true);
+        case ADMW_FLASH_CONFIG_4:
+            return executeCommand(hDevice, CORE_COMMAND_SAVE_CONFIG_4, true);
+        default:
+            ADMW_LOG_ERROR("Invalid user config target slot %d specified",
+                                eSlotId);
+            return ADMW_INVALID_PARAM;
+    }
+}
+
+/*
+ * Restore the configuration settings from persistent memory on the device.
+ * No other command must be running when this is called.
+ */
+ADMW_RESULT admw_RestoreConfig(
+    ADMW_DEVICE_HANDLE    const hDevice,
+    ADMW_USER_CONFIG_SLOT const eSlotId)
+{
+    switch (eSlotId)
+    {
+        case ADMW_FLASH_CONFIG_1:
+            return executeCommand(hDevice, CORE_COMMAND_LOAD_CONFIG_1, true);
+        case ADMW_FLASH_CONFIG_2:
+            return executeCommand(hDevice, CORE_COMMAND_LOAD_CONFIG_2, true);
+        case ADMW_FLASH_CONFIG_3:
+            return executeCommand(hDevice, CORE_COMMAND_LOAD_CONFIG_3, true);
+        case ADMW_FLASH_CONFIG_4:
+            return executeCommand(hDevice, CORE_COMMAND_LOAD_CONFIG_4, true);
+        default:
+            ADMW_LOG_ERROR("Invalid user config source slot %d specified",
+                                eSlotId);
+            return ADMW_INVALID_PARAM;
+    }
+}
+
+/*
+ * Erase the entire external flash memory.
+ * No other command must be running when this is called.
+ */
+ADMW_RESULT admw_EraseExternalFlash(
+    ADMW_DEVICE_HANDLE    const hDevice)
+{
+    return executeCommand(hDevice, CORE_COMMAND_ERASE_EXTERNAL_FLASH, true);
+}
+
+/*
+ * Read the number of samples stored in external flash memory.
+ * No other command must be running when this is called.
+ */
+ADMW_RESULT admw_GetExternalFlashSampleCount(
+    ADMW_DEVICE_HANDLE    const hDevice,
+    uint32_t * nSampleCount)
+{
+    CORE_Ext_Flash_Sample_Count_t nCount;
+
+    READ_REG_U32(hDevice, nCount.VALUE32, CORE_EXT_FLASH_SAMPLE_COUNT);
+
+    *nSampleCount = nCount.VALUE32;
+
+    return ADMW_SUCCESS;
+}
+
+// DEBUG - TO BE DELETED
+ADMW_RESULT admw_SetExternalFlashIndex(
+    ADMW_DEVICE_HANDLE    const hDevice,
+    uint32_t nStartIndex)
+{
+    WRITE_REG_U32(hDevice, nStartIndex, CORE_EXT_FLASH_INDEX);
+
+    return ADMW_SUCCESS;
+}
+
+/*
+ * Read a set of data samples stored in the device external flash memory.
+ * This may be called at any time.
+ */
+ADMW_RESULT admw_GetExternalFlashData(
+    ADMW_DEVICE_HANDLE    const hDevice,
+    ADMW_DATA_SAMPLE    * const pSamples,
+    uint32_t                   const nStartIndex,
+    uint32_t                   const nRequested,
+    uint32_t                 * const pnReturned)
+{
+    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
+    uint16_t command = ADMW1001_HOST_COMMS_READ_CMD |
+        (REG_CORE_EXT_FLASH_DATA & ADMW1001_HOST_COMMS_ADR_MASK);
+    uint8_t commandData[2] = {
+        command >> 8,
+        command & 0xFF
+    };
+    uint8_t commandResponse[2];
+    unsigned nValidSamples = 0;
+    ADMW_RESULT eRet = ADMW_SUCCESS;
+
+    /* Setup initial sample */
+    WRITE_REG_U32(hDevice, nStartIndex, CORE_EXT_FLASH_INDEX);
+
+    /* Send flash read command */
+    do {
+        eRet = admw_SpiTransfer(pCtx->hSpi, commandData, commandResponse,
+                                     sizeof(command), false);
+        if (eRet)
+        {
+            ADMW_LOG_ERROR("Failed to send read command for external flash");
+            return eRet;
+        }
+        
+        admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
+    } while ((commandResponse[0] != ADMW1001_HOST_COMMS_CMD_RESP_0) ||
+             (commandResponse[1] != ADMW1001_HOST_COMMS_CMD_RESP_1));
+
+    /* Read samples from external flash memory */
+    for (unsigned i = 0; i < nRequested; i++)
+    {
+        ADMW1001_Sensor_Result_t sensorResult;
+        bool bHoldCs = true;
+
+        /* Keep the CS signal asserted for all but the last sample */
+        if ((i + 1) == nRequested)
+            bHoldCs = false;
+
+        eRet = admw_SpiTransfer(pCtx->hSpi, NULL, (uint8_t *) (&sensorResult),
+                                     8, bHoldCs);
+        if (eRet)
+        {
+            ADMW_LOG_ERROR("Failed to read data from external flash");
+            return eRet;
+        }
+
+        ADMW_DATA_SAMPLE *pSample = &pSamples[nValidSamples];
+
+        pSample->status = (ADMW_DEVICE_STATUS_FLAGS)0;
+        if (sensorResult.Ch_Error)
+            pSample->status |= ADMW_DEVICE_STATUS_ERROR;
+        if (sensorResult.Ch_Alert)
+            pSample->status |= ADMW_DEVICE_STATUS_ALERT;
+
+        if (sensorResult.Ch_Raw)
+            pSample->rawValue = sensorResult.Raw_Sample;
+        else
+            pSample->rawValue = 0;
+
+        pSample->channelId = sensorResult.Channel_ID;
+        pSample->processedValue = sensorResult.Sensor_Result;
+
+        nValidSamples++;
+    }
+    *pnReturned = nValidSamples;
+
+    admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
+
+    return eRet;
+}
+
+
+/*
+ * Store the LUT data to persistent memory on the device.
+ * No other command must be running when this is called.
+ * Do not power down the device while this command is running.
+ */
+ADMW_RESULT admw_SaveLutData(
+    ADMW_DEVICE_HANDLE    const hDevice)
+{
+    return executeCommand(hDevice, CORE_COMMAND_SAVE_LUT, true);
+}
+
+/*
+ * Restore the LUT data from persistent memory on the device.
+ * No other command must be running when this is called.
+ */
+ADMW_RESULT admw_RestoreLutData(
+    ADMW_DEVICE_HANDLE    const hDevice)
+{
+    return executeCommand(hDevice, CORE_COMMAND_LOAD_LUT, true);
+}
+
+/*
+ * Stop the measurement cycles on the device.
+ * To be used only if a measurement command is currently running.
+ */
+ADMW_RESULT admw_StopMeasurement(
+    ADMW_DEVICE_HANDLE    const hDevice)
+{
+    return executeCommand(hDevice, CORE_COMMAND_NOP, true);
+}
+
+/*
+ * Run built-in diagnostic checks on the device.
+ * Diagnostics are executed according to the current applied settings.
+ * No other command must be running when this is called.
+ */
+ADMW_RESULT admw_RunDiagnostics(
+    ADMW_DEVICE_HANDLE    const hDevice)
+{
+    return executeCommand(hDevice, CORE_COMMAND_RUN_DIAGNOSTICS, true);
+}
+
+/*
+ * Run self-calibration routines on the device.
+ * Calibration is executed according to the current applied settings.
+ * No other command must be running when this is called.
+ */
+ADMW_RESULT admw_RunCalibration(
+    ADMW_DEVICE_HANDLE    const hDevice)
+{
+    return executeCommand(hDevice, CORE_COMMAND_SELF_CALIBRATION, true);
+}
+
+/*
+ * Run digital calibration routines on the device.
+ * Calibration is executed according to the current applied settings.
+ * No other command must be running when this is called.
+ */
+ADMW_RESULT admw_RunDigitalCalibration(
+    ADMW_DEVICE_HANDLE    const hDevice)
+{
+    return executeCommand(hDevice, CORE_COMMAND_CALIBRATE_DIGITAL, true);
+}
+
+/*
+ * Read a set of data samples from the device.
+ * This may be called at any time.
+ */
+ADMW_RESULT admw_GetData(
+    ADMW_DEVICE_HANDLE    const hDevice,
+    ADMW_MEASUREMENT_MODE const eMeasurementMode,
+    ADMW_DATA_SAMPLE    * const pSamples,
+    uint8_t                    const nBytesPerSample,
+    uint32_t                   const nRequested,
+    uint32_t                 * const pnReturned)
+{
+    ADMW1001_Sensor_Result_t sensorResult;
+    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
+    uint16_t command = ADMW1001_HOST_COMMS_READ_CMD |
+        (REG_CORE_DATA_FIFO & ADMW1001_HOST_COMMS_ADR_MASK);
+    uint8_t commandData[2] = {
+        command >> 8,
+        command & 0xFF
+    };
+    uint8_t commandResponse[2];
+    unsigned nValidSamples = 0;
+    ADMW_RESULT eRet = ADMW_SUCCESS;
+
+    do {
+        eRet = admw_SpiTransfer(pCtx->hSpi, commandData, commandResponse,
+                                     sizeof(command), false);
+        if (eRet)
+        {
+            ADMW_LOG_ERROR("Failed to send read command for FIFO register");
+            return eRet;
+        }
+        admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
+    } while ((commandResponse[0] != ADMW1001_HOST_COMMS_CMD_RESP_0) ||
+             (commandResponse[1] != ADMW1001_HOST_COMMS_CMD_RESP_1));
+
+    for (unsigned i = 0; i < nRequested; i++)
+    {
+        bool bHoldCs = true;
+
+        /* Keep the CS signal asserted for all but the last sample */
+        if ((i + 1) == nRequested)
+            bHoldCs = false;
+
+        getDataCnt++;
+
+        eRet = admw_SpiTransfer(pCtx->hSpi, NULL, &sensorResult,
+                                     nBytesPerSample, bHoldCs);
+        if (eRet)
+        {
+            ADMW_LOG_ERROR("Failed to read data from FIFO register");
+            return eRet;
+        }
+
+        if (! sensorResult.Ch_Valid)
+        {
+            /*
+             * Reading an invalid sample indicates that there are no
+             * more samples available or we've lost sync with the device.
+             * In the latter case, it might be recoverable, but return here
+             * to let the application check the device status and decide itself.
+             */
+            eRet = ADMW_INCOMPLETE;
+            break;
+        }
+
+        ADMW_DATA_SAMPLE *pSample = &pSamples[nValidSamples];
+
+        pSample->status = (ADMW_DEVICE_STATUS_FLAGS)0;
+        if (sensorResult.Ch_Error)
+            pSample->status |= ADMW_DEVICE_STATUS_ERROR;
+        if (sensorResult.Ch_Alert)
+            pSample->status |= ADMW_DEVICE_STATUS_ALERT;
+
+        if (sensorResult.Ch_Raw)
+            pSample->rawValue = sensorResult.Raw_Sample;
+        else
+            pSample->rawValue = 0;
+
+        pSample->channelId = sensorResult.Channel_ID;
+        pSample->processedValue = sensorResult.Sensor_Result;
+
+        nValidSamples++;
+
+        admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
+    }
+    *pnReturned = nValidSamples;
+
+    return eRet;
+}
+
+/*
+ * Close the given ADMW device.
+ */
+ADMW_RESULT admw_Close(
+    ADMW_DEVICE_HANDLE    const hDevice)
+{
+    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
+
+    admw_GpioClose(pCtx->hGpio);
+    admw_SpiClose(pCtx->hSpi);
+    admw_LogClose();
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_WriteRegister(
+    ADMW_DEVICE_HANDLE hDevice,
+    uint16_t nAddress,
+    void *pData,
+    unsigned nLength)
+{
+    ADMW_RESULT eRet;
+    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
+    uint16_t command = ADMW1001_HOST_COMMS_WRITE_CMD |
+        (nAddress & ADMW1001_HOST_COMMS_ADR_MASK);
+    uint8_t commandData[2] = {
+        command >> 8,
+        command & 0xFF
+    };
+    uint8_t commandResponse[2];
+
+    do {
+        eRet = admw_SpiTransfer(pCtx->hSpi, commandData, commandResponse,
+                                     sizeof(command), false);
+        if (eRet)
+        {
+            ADMW_LOG_ERROR("Failed to send write command for register %u",
+                                nAddress);
+            return eRet;
+        }
+
+        admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
+    } while ((commandResponse[0] != ADMW1001_HOST_COMMS_CMD_RESP_0) ||
+             (commandResponse[1] != ADMW1001_HOST_COMMS_CMD_RESP_1));
+
+    eRet = admw_SpiTransfer(pCtx->hSpi, pData, NULL, nLength, false);
+    if (eRet)
+    {
+        ADMW_LOG_ERROR("Failed to write data (%dB) to register %u",
+                            nLength, nAddress);
+        return eRet;
+    }
+
+    admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_ReadRegister(
+    ADMW_DEVICE_HANDLE hDevice,
+    uint16_t nAddress,
+    void *pData,
+    unsigned nLength)
+{
+    ADMW_RESULT eRet;
+    ADMW_DEVICE_CONTEXT *pCtx = hDevice;
+    uint16_t command = ADMW1001_HOST_COMMS_READ_CMD |
+        (nAddress & ADMW1001_HOST_COMMS_ADR_MASK);
+    uint8_t commandData[2] = {
+        command >> 8,
+        command & 0xFF
+    };
+    uint8_t commandResponse[2];
+
+    do {
+        eRet = admw_SpiTransfer(pCtx->hSpi, commandData, commandResponse,
+                                     sizeof(command), false);
+        if (eRet)
+        {
+            ADMW_LOG_ERROR("Failed to send read command for register %u",
+                                nAddress);
+            return eRet;
+        }
+
+        admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
+    } while ((commandResponse[0] != ADMW1001_HOST_COMMS_CMD_RESP_0) ||
+             (commandResponse[1] != ADMW1001_HOST_COMMS_CMD_RESP_1));
+
+    eRet = admw_SpiTransfer(pCtx->hSpi, NULL, pData, nLength, false);
+    if (eRet)
+    {
+        ADMW_LOG_ERROR("Failed to read data (%uB) from register %u",
+                            nLength, nAddress);
+        return eRet;
+    }
+
+    admw_TimeDelayUsec(ADMW1001_HOST_COMMS_XFER_DELAY);
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw_GetDeviceReadyState(
+    ADMW_DEVICE_HANDLE   const hDevice,
+    bool                  * const bReady)
+{
+    ADMW_SPI_Chip_Type_t chipTypeReg;
+
+    READ_REG_U8(hDevice, chipTypeReg.VALUE8, SPI_CHIP_TYPE);
+    /* If we read this register successfully, assume the device is ready */
+    *bReady = (chipTypeReg.VALUE8 == REG_SPI_CHIP_TYPE_RESET);
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_GetDataReadyModeInfo(
+    ADMW_DEVICE_HANDLE         const hDevice,
+    ADMW_MEASUREMENT_MODE      const eMeasurementMode,
+    ADMW1001_OPERATING_MODE * const peOperatingMode,
+    ADMW1001_DATAREADY_MODE * const peDataReadyMode,
+    uint32_t                      * const pnSamplesPerDataready,
+    uint32_t                      * const pnSamplesPerCycle,
+    uint8_t                       * const pnBytesPerSample)
+{
+    unsigned nChannelsEnabled = 0;
+    unsigned nSamplesPerCycle = 0;
+
+    CORE_Mode_t modeReg;
+    READ_REG_U8(hDevice, modeReg.VALUE8, CORE_MODE);
+
+    if ((eMeasurementMode == ADMW_MEASUREMENT_MODE_HEALTHCHECK) ||
+        (modeReg.Conversion_Mode == CORE_MODE_SINGLECYCLE))
+        *peOperatingMode = ADMW1001_OPERATING_MODE_SINGLECYCLE;
+    else if (modeReg.Conversion_Mode == CORE_MODE_MULTICYCLE)
+        *peOperatingMode = ADMW1001_OPERATING_MODE_MULTICYCLE;
+    else
+        *peOperatingMode = ADMW1001_OPERATING_MODE_CONTINUOUS;
+
+
+    /* FFT mode is quite different to the other modes:
+     * - Each FFT result produces a batch of samples
+     * - The size of the batch depends on selected FFT size and output config options
+     * - DATAREADY will fire for each FFT result (once per channel)
+     * - The size of the cycle depends on the number of channels enabled for FFT
+     */
+    if (eMeasurementMode == ADMW_MEASUREMENT_MODE_FFT)
+    {
+        CORE_FFT_Config_t fftConfigReg;
+
+        unsigned nFftChannels;
+        unsigned nSamplesPerChannel;
+
+        READ_REG_U32(hDevice, fftConfigReg.VALUE32, CORE_FFT_CONFIG);
+
+        nFftChannels = fftConfigReg.FFT_Num_Channels + 1;
+
+        if (fftConfigReg.FFT_Output == CORE_FFT_CONFIG_FFT_OUTPUT_MAX16)
+        {
+            nSamplesPerChannel = 16;
+            *pnBytesPerSample = 8;
+        }
+        else if (fftConfigReg.FFT_Output == CORE_FFT_CONFIG_FFT_OUTPUT_FULL)
+        {
+            nSamplesPerChannel = (256 << fftConfigReg.FFT_Num_Bins) >> 1;
+            *pnBytesPerSample = 5;
+        }
+        else if (fftConfigReg.FFT_Output == CORE_FFT_CONFIG_FFT_OUTPUT_FULL_WITH_RAW)
+        {
+            nSamplesPerChannel = (256 << fftConfigReg.FFT_Num_Bins);
+            *pnBytesPerSample = 8;
+        }
+        else
+        {
+            ADMW_LOG_ERROR("Invalid FFT output format option %d configured",
+                                fftConfigReg.FFT_Output);
+            return ADMW_INVALID_PARAM;
+        }
+
+        *pnSamplesPerDataready = nSamplesPerChannel;
+        *pnSamplesPerCycle = nSamplesPerChannel * nFftChannels;
+
+        *peDataReadyMode = ADMW1001_DATAREADY_PER_CYCLE;
+
+        if (modeReg.FFT_Mode == CORE_MODE_FFT_MODE_CONTINUOUS)
+        {
+            *peOperatingMode = ADMW1001_OPERATING_MODE_CONTINUOUS;
+        }
+        else
+        {
+            *peOperatingMode = ADMW1001_OPERATING_MODE_SINGLECYCLE;
+        }
+    }
+    else
+    {
+        if (eMeasurementMode == ADMW_MEASUREMENT_MODE_OMIT_RAW)
+        {
+            *pnBytesPerSample = 5;
+        }
+        else
+        {
+            *pnBytesPerSample = 8;
+        }
+
+        for (ADMW1001_CHANNEL_ID chId = ADMW1001_CHANNEL_ID_CJC_0;
+             chId < ADMW1001_MAX_CHANNELS;
+             chId++)
+        {
+            CORE_Sensor_Details_t sensorDetailsReg;
+            CORE_Channel_Count_t channelCountReg;
+
+            if (ADMW1001_CHANNEL_IS_VIRTUAL(chId))
+                continue;
+
+            READ_REG_U8(hDevice, channelCountReg.VALUE8, CORE_CHANNEL_COUNTn(chId));
+            READ_REG_U32(hDevice, sensorDetailsReg.VALUE32, CORE_SENSOR_DETAILSn(chId));
+
+            if (channelCountReg.Channel_Enable && !sensorDetailsReg.Do_Not_Publish)
+            {
+                CORE_Sensor_Type_t sensorTypeReg;
+                unsigned nActualChannels = 1;
+
+                READ_REG_U16(hDevice, sensorTypeReg.VALUE16, CORE_SENSOR_TYPEn(chId));
+
+                if (chId == ADMW1001_CHANNEL_ID_SPI_0)
+                {
+                    /* Some sensors automatically generate samples on additional "virtual" channels
+                     * so these channels must be counted as active when those sensors are selected
+                     * and we use the count from the corresponding "physical" channel */
+                    if ((sensorTypeReg.Sensor_Type >=
+                         CORE_SENSOR_TYPE_SENSOR_SPI_ACCELEROMETER_A_DEF_L1) &&
+                        (sensorTypeReg.Sensor_Type <=
+                         CORE_SENSOR_TYPE_SENSOR_SPI_ACCELEROMETER_B_ADV_L2))
+                        nActualChannels += 2;
+                }
+
+                nChannelsEnabled += nActualChannels;
+                if (eMeasurementMode == ADMW_MEASUREMENT_MODE_HEALTHCHECK)
+                    /* Assume a single sample per channel in test mode */
+                    nSamplesPerCycle += nActualChannels;
+                else
+                    nSamplesPerCycle += nActualChannels *
+                        (channelCountReg.Channel_Count + 1);
+            }
+        }
+
+        if (nChannelsEnabled == 0)
+        {
+            *pnSamplesPerDataready = 0;
+            *pnSamplesPerCycle = 0;
+            return ADMW_SUCCESS;
+        }
+
+        *pnSamplesPerCycle = nSamplesPerCycle;
+
+        if (modeReg.Drdy_Mode == CORE_MODE_DRDY_PER_CONVERSION)
+        {
+            *pnSamplesPerDataready = 1;
+        }
+        else if (modeReg.Drdy_Mode == CORE_MODE_DRDY_PER_CYCLE)
+        {
+            *pnSamplesPerDataready = nSamplesPerCycle;
+        }
+        else
+        {
+            /* Assume DRDY will be asserted after max. 1 cycle in test mode */
+            if (eMeasurementMode == ADMW_MEASUREMENT_MODE_HEALTHCHECK)
+            {
+                *pnSamplesPerDataready = nSamplesPerCycle;
+            }
+            else
+            {
+                CORE_Fifo_Num_Cycles_t fifoNumCyclesReg;
+                READ_REG_U8(hDevice, fifoNumCyclesReg.VALUE8, CORE_FIFO_NUM_CYCLES);
+
+                *pnSamplesPerDataready =
+                nSamplesPerCycle * fifoNumCyclesReg.Fifo_Num_Cycles;
+            }
+        }
+
+        if (modeReg.Drdy_Mode == CORE_MODE_DRDY_PER_CONVERSION)
+            *peDataReadyMode = ADMW1001_DATAREADY_PER_CONVERSION;
+        else if (modeReg.Drdy_Mode == CORE_MODE_DRDY_PER_CYCLE)
+            *peDataReadyMode = ADMW1001_DATAREADY_PER_CYCLE;
+        else
+        {
+            /* Assume DRDY will be asserted after max. 1 cycle in test mode */
+            if (eMeasurementMode == ADMW_MEASUREMENT_MODE_HEALTHCHECK)
+                *peDataReadyMode = ADMW1001_DATAREADY_PER_CYCLE;
+            else
+                *peDataReadyMode = ADMW1001_DATAREADY_PER_MULTICYCLE_BURST;
+        }
+    }
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw_GetProductID(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW_PRODUCT_ID *pProductId)
+{
+    ADMW_SPI_Product_ID_L_t productIdLoReg;
+    ADMW_SPI_Product_ID_H_t productIdHiReg;
+
+    READ_REG_U8(hDevice, productIdLoReg.VALUE8, SPI_PRODUCT_ID_L);
+    READ_REG_U8(hDevice, productIdHiReg.VALUE8, SPI_PRODUCT_ID_H);
+
+    *pProductId = (ADMW_PRODUCT_ID)((productIdHiReg.VALUE8 << 8)
+                                         | productIdLoReg.VALUE8);
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetPowerMode(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_POWER_MODE powerMode)
+{
+    CORE_Power_Config_t powerConfigReg;
+
+    if (powerMode == ADMW1001_POWER_MODE_LOW)
+    {
+        powerConfigReg.Power_Mode_ADC = CORE_POWER_CONFIG_ADC_LOW_POWER;
+    }
+    else if (powerMode == ADMW1001_POWER_MODE_MID)
+    {
+        powerConfigReg.Power_Mode_ADC = CORE_POWER_CONFIG_ADC_MID_POWER;
+    }
+    else if (powerMode == ADMW1001_POWER_MODE_FULL)
+    {
+        powerConfigReg.Power_Mode_ADC = CORE_POWER_CONFIG_ADC_FULL_POWER;
+    }
+    else
+    {
+        ADMW_LOG_ERROR("Invalid power mode %d specified", powerMode);
+        return ADMW_INVALID_PARAM;
+    }
+
+    WRITE_REG_U8(hDevice, powerConfigReg.VALUE8, CORE_POWER_CONFIG);
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetPowerConfig(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_POWER_CONFIG *pPowerConfig)
+{
+    ADMW_RESULT eRet;
+
+    eRet = admw_SetPowerMode(hDevice, pPowerConfig->powerMode);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set power mode");
+        return eRet;
+    }
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetMode(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_OPERATING_MODE eOperatingMode,
+    ADMW1001_DATAREADY_MODE eDataReadyMode,
+    ADMW1001_CALIBRATION_MODE eCalibrationMode,
+    bool bEnableExtFlash)
+{
+    CORE_Mode_t modeReg;
+
+    modeReg.VALUE8 = REG_RESET_VAL(CORE_MODE);
+
+    if (eOperatingMode == ADMW1001_OPERATING_MODE_SINGLECYCLE)
+    {
+        modeReg.Conversion_Mode = CORE_MODE_SINGLECYCLE;
+    }
+    else if (eOperatingMode == ADMW1001_OPERATING_MODE_CONTINUOUS)
+    {
+        modeReg.Conversion_Mode = CORE_MODE_CONTINUOUS;
+    }
+    else if (eOperatingMode == ADMW1001_OPERATING_MODE_MULTICYCLE)
+    {
+        modeReg.Conversion_Mode = CORE_MODE_MULTICYCLE;
+    }
+    else
+    {
+        ADMW_LOG_ERROR("Invalid operating mode %d specified",
+                            eOperatingMode);
+        return ADMW_INVALID_PARAM;
+    }
+
+    if (eDataReadyMode == ADMW1001_DATAREADY_PER_CONVERSION)
+    {
+        modeReg.Drdy_Mode = CORE_MODE_DRDY_PER_CONVERSION;
+    }
+    else if (eDataReadyMode == ADMW1001_DATAREADY_PER_CYCLE)
+    {
+        modeReg.Drdy_Mode = CORE_MODE_DRDY_PER_CYCLE;
+    }
+    else if (eDataReadyMode == ADMW1001_DATAREADY_PER_MULTICYCLE_BURST)
+    {
+        if (eOperatingMode != ADMW1001_OPERATING_MODE_MULTICYCLE)
+        {
+            ADMW_LOG_ERROR(
+                "Data-ready mode %d cannot be used with operating mode %d",
+                eDataReadyMode, eOperatingMode);
+            return ADMW_INVALID_PARAM;
+        }
+        else
+        {
+            modeReg.Drdy_Mode = CORE_MODE_DRDY_PER_FIFO_FILL;
+        }
+    }
+    else
+    {
+        ADMW_LOG_ERROR("Invalid data-ready mode %d specified", eDataReadyMode);
+        return ADMW_INVALID_PARAM;
+    }
+
+    if (eCalibrationMode == ADMW1001_NO_CALIBRATION)
+    {
+        modeReg.Calibration_Method = CORE_MODE_NO_CAL;
+    }
+    else if (eCalibrationMode == ADMW1001_DO_CALIBRATION)
+    {
+        modeReg.Calibration_Method = CORE_MODE_DO_CAL;
+    }
+    else
+    {
+        ADMW_LOG_ERROR("Invalid calibration mode %d specified",
+                            eCalibrationMode);
+        return ADMW_INVALID_PARAM;
+    }
+
+    modeReg.Ext_Flash_Store = (bEnableExtFlash ?
+                                CORE_MODE_EXT_FLASH_USED :
+                                CORE_MODE_EXT_FLASH_NOT_USED);
+
+    WRITE_REG_U8(hDevice, modeReg.VALUE8, CORE_MODE);
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw_SetCycleControl(
+    ADMW_DEVICE_HANDLE hDevice,
+    uint32_t nCycleInterval,
+
+#ifdef __V2_3_CFG_FMT__    
+    ADMW1001_CYCLE_TYPE eCycleType,
+    ADMW1001_FILTER_SETTLING eFilterSettling)
+#else
+    ADMW1001_CYCLE_TYPE eCycleType)
+#endif
+{
+    CORE_Cycle_Control_t cycleControlReg;
+
+    cycleControlReg.VALUE16 = REG_RESET_VAL(CORE_CYCLE_CONTROL);
+
+    if (nCycleInterval < (1 << 12))
+    {
+        cycleControlReg.Cycle_Time_Units = CORE_CYCLE_CONTROL_MICROSECONDS;
+    }
+    else if (nCycleInterval < (1000 * (1 << 12)))
+    {
+        cycleControlReg.Cycle_Time_Units = CORE_CYCLE_CONTROL_MILLISECONDS;
+        nCycleInterval /= 1000;
+    }
+    else
+    {
+        cycleControlReg.Cycle_Time_Units = CORE_CYCLE_CONTROL_SECONDS;
+        nCycleInterval /= 1000000;
+    }
+
+    CHECK_REG_FIELD_VAL(CORE_CYCLE_CONTROL_CYCLE_TIME, nCycleInterval);
+    cycleControlReg.Cycle_Time = nCycleInterval;
+
+    if (eCycleType == ADMW1001_CYCLE_TYPE_SWITCH)
+    {
+        cycleControlReg.Cycle_Type = CORE_CYCLE_CONTROL_CYCLE_TYPE_SWITCH;
+    }
+    else if (eCycleType == ADMW1001_CYCLE_TYPE_FULL)
+    {
+        cycleControlReg.Cycle_Type = CORE_CYCLE_CONTROL_CYCLE_TYPE_FULL;
+    }
+    else
+    {
+        ADMW_LOG_ERROR("Invalid cycle type %d specified", eCycleType);
+        return ADMW_INVALID_PARAM;
+    }
+
+#ifdef __V2_3_CFG_FMT__   
+    if (eFilterSettling == ADMW1001_FILTER_SETTLING_ALWAYS)
+    {
+        cycleControlReg.Filter_Settling = CORE_CYCLE_CONTROL_FILTER_SETTLING_SETTLED;
+    }
+    else if (eFilterSettling == ADMW1001_FILTER_SETTLING_FAST)
+    {
+        cycleControlReg.Filter_Settling = CORE_CYCLE_CONTROL_FILTER_SETTLING_FAST;
+    }
+    else
+    {
+        ADMW_LOG_ERROR("Invalid filter settling option %d specified", eFilterSettling);
+        return ADMW_INVALID_PARAM;
+    }
+#endif
+    
+    WRITE_REG_U16(hDevice, cycleControlReg.VALUE16, CORE_CYCLE_CONTROL);
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetMultiCycleConfig(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_MULTICYCLE_CONFIG *pMultiCycleConfig)
+{
+    CHECK_REG_FIELD_VAL(CORE_FIFO_NUM_CYCLES_FIFO_NUM_CYCLES,
+                        pMultiCycleConfig->cyclesPerBurst);
+
+    WRITE_REG_U8(hDevice, pMultiCycleConfig->cyclesPerBurst,
+                 CORE_FIFO_NUM_CYCLES);
+
+    WRITE_REG_U32(hDevice, pMultiCycleConfig->burstInterval,
+                  CORE_MULTI_CYCLE_REPEAT_INTERVAL);
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetExternalReferenceValues(
+    ADMW_DEVICE_HANDLE hDevice,
+    float32_t externalRef1Value,
+    float32_t externalRef2Value)
+{
+    WRITE_REG_FLOAT(hDevice, externalRef1Value, CORE_EXTERNAL_REFERENCE1);
+    WRITE_REG_FLOAT(hDevice, externalRef2Value, CORE_EXTERNAL_REFERENCE2);
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetMeasurementConfig(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_MEASUREMENT_CONFIG *pMeasConfig)
+{
+    ADMW_RESULT eRet;
+
+    eRet = admw_SetMode(hDevice,
+                             pMeasConfig->operatingMode,
+                             pMeasConfig->dataReadyMode,
+                             pMeasConfig->calibrationMode,
+                             pMeasConfig->enableExternalFlash);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set operating mode");
+        return eRet;
+    }
+
+    eRet = admw_SetCycleControl(hDevice,
+                                     pMeasConfig->cycleInterval,
+                                     pMeasConfig->cycleType);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set cycle control");
+        return eRet;
+    }
+
+    if (pMeasConfig->operatingMode == ADMW1001_OPERATING_MODE_MULTICYCLE)
+    {
+        eRet = admw_SetMultiCycleConfig(hDevice,
+                                            &pMeasConfig->multiCycleConfig);
+        if (eRet != ADMW_SUCCESS)
+        {
+            ADMW_LOG_ERROR("Failed to set multi-cycle configuration");
+            return eRet;
+        }
+    }
+
+    eRet = admw_SetExternalReferenceValues(hDevice,
+                                                pMeasConfig->externalRef1Value,
+                                                pMeasConfig->externalRef2Value);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set external reference values");
+        return eRet;
+    }
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetDiagnosticsConfig(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_DIAGNOSTICS_CONFIG *pDiagnosticsConfig)
+{
+    CORE_Diagnostics_Control_t diagnosticsControlReg;
+
+    diagnosticsControlReg.VALUE16 = REG_RESET_VAL(CORE_DIAGNOSTICS_CONTROL);
+
+    if (pDiagnosticsConfig->disableGlobalDiag)
+        diagnosticsControlReg.Diag_Global_En = 0;
+    else
+        diagnosticsControlReg.Diag_Global_En = 1;
+
+    if (pDiagnosticsConfig->disableMeasurementDiag)
+        diagnosticsControlReg.Diag_Meas_En = 0;
+    else
+        diagnosticsControlReg.Diag_Meas_En = 1;
+
+    switch (pDiagnosticsConfig->osdFrequency)
+    {
+    case ADMW1001_OPEN_SENSOR_DIAGNOSTICS_DISABLED:
+        diagnosticsControlReg.Diag_OSD_Freq = CORE_DIAGNOSTICS_CONTROL_OCD_OFF;
+        break;
+    case ADMW1001_OPEN_SENSOR_DIAGNOSTICS_PER_CYCLE:
+        diagnosticsControlReg.Diag_OSD_Freq = CORE_DIAGNOSTICS_CONTROL_OCD_PER_1_CYCLE;
+        break;
+    case ADMW1001_OPEN_SENSOR_DIAGNOSTICS_PER_100_CYCLES:
+        diagnosticsControlReg.Diag_OSD_Freq = CORE_DIAGNOSTICS_CONTROL_OCD_PER_100_CYCLES;
+        break;
+    case ADMW1001_OPEN_SENSOR_DIAGNOSTICS_PER_1000_CYCLES:
+        diagnosticsControlReg.Diag_OSD_Freq = CORE_DIAGNOSTICS_CONTROL_OCD_PER_1000_CYCLES;
+        break;
+    default:
+        ADMW_LOG_ERROR("Invalid open-sensor diagnostic frequency %d specified",
+                            pDiagnosticsConfig->osdFrequency);
+        return ADMW_INVALID_PARAM;
+    }
+
+    WRITE_REG_U16(hDevice, diagnosticsControlReg.VALUE16, CORE_DIAGNOSTICS_CONTROL);
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetFftConfig(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_FFT_CONFIG *pFftConfig,
+    ADMW1001_CHANNEL_CONFIG *pChannels)
+{
+    CORE_FFT_Config_t fftConfigReg;
+    CORE_Mode_t modeReg;
+    uint32_t numFftChannels = 0;
+
+    fftConfigReg.VALUE32 = REG_RESET_VAL(CORE_FFT_CONFIG);
+
+    for (ADMW1001_CHANNEL_ID id = ADMW1001_CHANNEL_ID_CJC_0;
+         id < ADMW1001_MAX_CHANNELS;
+         id++)
+    {
+        if (pChannels[id].enableFFT)
+        {
+            if (numFftChannels >= 4) /* TODO - temporary limit */
+            {
+                ADMW_LOG_ERROR("Maximum limit of 4 FFT channels exceeded");
+                return ADMW_INVALID_PARAM;
+            }
+
+            numFftChannels++;
+        }
+    }
+
+    if (numFftChannels > 0)
+    {
+        fftConfigReg.FFT_Num_Channels = numFftChannels - 1;
+
+        switch (pFftConfig->size)
+        {
+        case ADMW1001_FFT_SIZE_256:
+            fftConfigReg.FFT_Num_Bins = CORE_FFT_CONFIG_FFT_BINS_256;
+            break;
+        case ADMW1001_FFT_SIZE_512:
+            fftConfigReg.FFT_Num_Bins = CORE_FFT_CONFIG_FFT_BINS_512;
+            break;
+        case ADMW1001_FFT_SIZE_1024:
+            fftConfigReg.FFT_Num_Bins = CORE_FFT_CONFIG_FFT_BINS_1024;
+            break;
+        case ADMW1001_FFT_SIZE_2048:
+            fftConfigReg.FFT_Num_Bins = CORE_FFT_CONFIG_FFT_BINS_2048;
+            break;
+        default:
+            ADMW_LOG_ERROR("Invalid FFT size option %d specified",
+                                pFftConfig->size);
+            return ADMW_INVALID_PARAM;
+        }
+
+        switch (pFftConfig->window)
+        {
+        case ADMW1001_FFT_WINDOW_NONE:
+            fftConfigReg.FFT_Window = CORE_FFT_CONFIG_FFT_WINDOW_NONE;
+            break;
+        case ADMW1001_FFT_WINDOW_HANN:
+            fftConfigReg.FFT_Window = CORE_FFT_CONFIG_FFT_WINDOW_HANN;
+            break;
+        case ADMW1001_FFT_WINDOW_BLACKMAN_HARRIS:
+            fftConfigReg.FFT_Window = CORE_FFT_CONFIG_FFT_WINDOW_BLACKMANN_HARRIS;
+            break;
+        default:
+            ADMW_LOG_ERROR("Invalid FFT window option %d specified",
+                                pFftConfig->window);
+            return ADMW_INVALID_PARAM;
+        }
+
+        switch (pFftConfig->output)
+        {
+        case ADMW1001_FFT_OUTPUT_FULL:
+            fftConfigReg.FFT_Output = CORE_FFT_CONFIG_FFT_OUTPUT_FULL;
+            break;
+        case ADMW1001_FFT_OUTPUT_MAX16:
+            fftConfigReg.FFT_Output = CORE_FFT_CONFIG_FFT_OUTPUT_MAX16;
+            break;
+        case ADMW1001_FFT_OUTPUT_FULL_WITH_RAW:
+            fftConfigReg.FFT_Output = CORE_FFT_CONFIG_FFT_OUTPUT_FULL_WITH_RAW;
+            break;
+        default:
+            ADMW_LOG_ERROR("Invalid FFT output format option %d specified",
+                                pFftConfig->output);
+            return ADMW_INVALID_PARAM;
+        }
+    }
+    WRITE_REG_U32(hDevice, fftConfigReg.VALUE32, CORE_FFT_CONFIG);
+
+    if (numFftChannels > 0)
+    {
+        READ_REG_U8(hDevice, modeReg.VALUE8, CORE_MODE);
+
+        if (pFftConfig->mode == ADMW1001_FFT_MODE_SINGLE)
+        {
+            modeReg.FFT_Mode = CORE_MODE_FFT_MODE_SINGLE;
+        }
+        else if (pFftConfig->mode == ADMW1001_FFT_MODE_CONTINUOUS)
+        {
+            modeReg.FFT_Mode = CORE_MODE_FFT_MODE_CONTINUOUS;
+        }
+        else
+        {
+            ADMW_LOG_ERROR("Invalid FFT mode %d specified",
+                                pFftConfig->mode);
+            return ADMW_INVALID_PARAM;
+        }
+
+        WRITE_REG_U8(hDevice, modeReg.VALUE8, CORE_MODE);
+    }
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetChannelCount(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    uint32_t nMeasurementsPerCycle)
+{
+    CORE_Channel_Count_t channelCountReg;
+
+    channelCountReg.VALUE8 = REG_RESET_VAL(CORE_CHANNEL_COUNTn);
+
+    if (nMeasurementsPerCycle > 0)
+    {
+        nMeasurementsPerCycle -= 1;
+
+        CHECK_REG_FIELD_VAL(CORE_CHANNEL_COUNT_CHANNEL_COUNT,
+                            nMeasurementsPerCycle);
+
+        channelCountReg.Channel_Enable = 1;
+        channelCountReg.Channel_Count = nMeasurementsPerCycle;
+    }
+    else
+    {
+        channelCountReg.Channel_Enable = 0;
+    }
+
+    WRITE_REG_U8(hDevice, channelCountReg.VALUE8, CORE_CHANNEL_COUNTn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetChannelOptions(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_CHANNEL_PRIORITY ePriority,
+    bool bEnableFft)
+{
+    CORE_Channel_Options_t channelOptionsReg;
+
+    channelOptionsReg.VALUE8 = REG_RESET_VAL(CORE_CHANNEL_OPTIONSn);
+
+    CHECK_REG_FIELD_VAL(CORE_CHANNEL_OPTIONS_CHANNEL_PRIORITY, ePriority);
+    channelOptionsReg.Channel_Priority = ePriority;
+    channelOptionsReg.FFT_Enable_Ch = bEnableFft ? 1 : 0;
+
+    WRITE_REG_U8(hDevice, channelOptionsReg.VALUE8, CORE_CHANNEL_OPTIONSn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetChannelSkipCount(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    uint32_t nCycleSkipCount)
+{
+    CORE_Channel_Skip_t channelSkipReg;
+
+    channelSkipReg.VALUE16 = REG_RESET_VAL(CORE_CHANNEL_SKIPn);
+
+    CHECK_REG_FIELD_VAL(CORE_CHANNEL_SKIP_CHANNEL_SKIP, nCycleSkipCount);
+
+    channelSkipReg.Channel_Skip = nCycleSkipCount;
+
+    WRITE_REG_U16(hDevice, channelSkipReg.VALUE16, CORE_CHANNEL_SKIPn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetChannelAdcSensorType(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_ADC_SENSOR_TYPE sensorType)
+{
+    CORE_Sensor_Type_t sensorTypeReg;
+
+    sensorTypeReg.VALUE16 = REG_RESET_VAL(CORE_SENSOR_TYPEn);
+
+    /* Ensure that the sensor type is valid for this channel */
+    switch(sensorType)
+    {
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_J_DEF_L1:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_K_DEF_L1:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_T_DEF_L1:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_1_DEF_L2:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_2_DEF_L2:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_3_DEF_L2:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_4_DEF_L2:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_J_ADV_L1:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_K_ADV_L1:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_T_ADV_L1:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_1_ADV_L2:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_2_ADV_L2:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_3_ADV_L2:
+    case ADMW1001_ADC_SENSOR_THERMOCOUPLE_4_ADV_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_1_DEF_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_2_DEF_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_3_DEF_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_4_DEF_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_1_ADV_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_2_ADV_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_3_ADV_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_4WIRE_4_ADV_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_1_DEF_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_2_DEF_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_3_DEF_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_4_DEF_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_1_ADV_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_2_ADV_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_3_ADV_L2:
+    case ADMW1001_ADC_SENSOR_BRIDGE_6WIRE_4_ADV_L2:
+        if (! ADMW1001_CHANNEL_IS_ADC_SENSOR(eChannelId))
+        {
+            ADMW_LOG_ERROR(
+                "Invalid ADC sensor type %d specified for channel %d",
+                sensorType, eChannelId);
+            return ADMW_INVALID_PARAM;
+        }
+        break;
+    case ADMW1001_ADC_SENSOR_RTD_2WIRE_PT100_DEF_L1:
+    case ADMW1001_ADC_SENSOR_RTD_2WIRE_PT1000_DEF_L1:
+    case ADMW1001_ADC_SENSOR_RTD_2WIRE_1_DEF_L2:
+    case ADMW1001_ADC_SENSOR_RTD_2WIRE_2_DEF_L2:
+    case ADMW1001_ADC_SENSOR_RTD_2WIRE_3_DEF_L2:
+    case ADMW1001_ADC_SENSOR_RTD_2WIRE_4_DEF_L2:
+    case ADMW1001_ADC_SENSOR_RTD_2WIRE_PT100_ADV_L1:
+    case ADMW1001_ADC_SENSOR_RTD_2WIRE_PT1000_ADV_L1:
+    case ADMW1001_ADC_SENSOR_RTD_2WIRE_1_ADV_L2:
+    case ADMW1001_ADC_SENSOR_RTD_2WIRE_2_ADV_L2:
+    case ADMW1001_ADC_SENSOR_RTD_2WIRE_3_ADV_L2:
+    case ADMW1001_ADC_SENSOR_RTD_2WIRE_4_ADV_L2:
+    case ADMW1001_ADC_SENSOR_RTD_3WIRE_PT100_DEF_L1:
+    case ADMW1001_ADC_SENSOR_RTD_3WIRE_PT1000_DEF_L1:
+    case ADMW1001_ADC_SENSOR_RTD_3WIRE_1_DEF_L2:
+    case ADMW1001_ADC_SENSOR_RTD_3WIRE_2_DEF_L2:
+    case ADMW1001_ADC_SENSOR_RTD_3WIRE_3_DEF_L2:
+    case ADMW1001_ADC_SENSOR_RTD_3WIRE_4_DEF_L2:
+    case ADMW1001_ADC_SENSOR_RTD_3WIRE_PT100_ADV_L1:
+    case ADMW1001_ADC_SENSOR_RTD_3WIRE_PT1000_ADV_L1:
+    case ADMW1001_ADC_SENSOR_RTD_3WIRE_1_ADV_L2:
+    case ADMW1001_ADC_SENSOR_RTD_3WIRE_2_ADV_L2:
+    case ADMW1001_ADC_SENSOR_RTD_3WIRE_3_ADV_L2:
+    case ADMW1001_ADC_SENSOR_RTD_3WIRE_4_ADV_L2:
+    case ADMW1001_ADC_SENSOR_RTD_4WIRE_PT100_DEF_L1:
+    case ADMW1001_ADC_SENSOR_RTD_4WIRE_PT1000_DEF_L1:
+    case ADMW1001_ADC_SENSOR_RTD_4WIRE_1_DEF_L2:
+    case ADMW1001_ADC_SENSOR_RTD_4WIRE_2_DEF_L2:
+    case ADMW1001_ADC_SENSOR_RTD_4WIRE_3_DEF_L2:
+    case ADMW1001_ADC_SENSOR_RTD_4WIRE_4_DEF_L2:
+    case ADMW1001_ADC_SENSOR_RTD_4WIRE_PT100_ADV_L1:
+    case ADMW1001_ADC_SENSOR_RTD_4WIRE_PT1000_ADV_L1:
+    case ADMW1001_ADC_SENSOR_RTD_4WIRE_1_ADV_L2:
+    case ADMW1001_ADC_SENSOR_RTD_4WIRE_2_ADV_L2:
+    case ADMW1001_ADC_SENSOR_RTD_4WIRE_3_ADV_L2:
+    case ADMW1001_ADC_SENSOR_RTD_4WIRE_4_ADV_L2:
+            if (!ADMW1001_CHANNEL_IS_ADC_CJC(eChannelId))
+        {
+            ADMW_LOG_ERROR(
+                "Invalid ADC sensor type %d specified for channel %d",
+                sensorType, eChannelId);
+            return ADMW_INVALID_PARAM;
+        }
+        break;
+    case ADMW1001_ADC_SENSOR_DIODE_2C_TYPEA_DEF_L1:
+    case ADMW1001_ADC_SENSOR_DIODE_3C_TYPEA_DEF_L1:
+    case ADMW1001_ADC_SENSOR_DIODE_2C_1_DEF_L2:
+    case ADMW1001_ADC_SENSOR_DIODE_3C_1_DEF_L2:
+    case ADMW1001_ADC_SENSOR_DIODE_2C_TYPEA_ADV_L1:
+    case ADMW1001_ADC_SENSOR_DIODE_3C_TYPEA_ADV_L1:
+    case ADMW1001_ADC_SENSOR_DIODE_2C_1_ADV_L2:
+    case ADMW1001_ADC_SENSOR_DIODE_3C_1_ADV_L2:
+    case ADMW1001_ADC_SENSOR_THERMISTOR_A_10K_DEF_L1:
+    case ADMW1001_ADC_SENSOR_THERMISTOR_B_10K_DEF_L1:
+    case ADMW1001_ADC_SENSOR_THERMISTOR_1_DEF_L2:
+    case ADMW1001_ADC_SENSOR_THERMISTOR_2_DEF_L2:
+    case ADMW1001_ADC_SENSOR_THERMISTOR_3_DEF_L2:
+    case ADMW1001_ADC_SENSOR_THERMISTOR_4_DEF_L2:
+    case ADMW1001_ADC_SENSOR_THERMISTOR_A_10K_ADV_L1:
+    case ADMW1001_ADC_SENSOR_THERMISTOR_B_10K_ADV_L1:
+    case ADMW1001_ADC_SENSOR_THERMISTOR_1_ADV_L2:
+    case ADMW1001_ADC_SENSOR_THERMISTOR_2_ADV_L2:
+    case ADMW1001_ADC_SENSOR_THERMISTOR_3_ADV_L2:
+    case ADMW1001_ADC_SENSOR_THERMISTOR_4_ADV_L2:
+        if (! (ADMW1001_CHANNEL_IS_ADC_SENSOR(eChannelId) ||
+               ADMW1001_CHANNEL_IS_ADC_CJC(eChannelId)))
+        {
+            ADMW_LOG_ERROR(
+                "Invalid ADC sensor type %d specified for channel %d",
+                sensorType, eChannelId);
+            return ADMW_INVALID_PARAM;
+        }
+        break;
+    case ADMW1001_ADC_SENSOR_VOLTAGE:
+    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_A_DEF_L1:
+    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_B_DEF_L1:
+    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_1_DEF_L2:
+    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_2_DEF_L2:
+    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_A_ADV_L1:
+    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_B_ADV_L1:
+    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_1_ADV_L2:
+    case ADMW1001_ADC_SENSOR_VOLTAGE_PRESSURE_2_ADV_L2:
+        if (! ADMW1001_CHANNEL_IS_ADC_VOLTAGE(eChannelId))
+        {
+            ADMW_LOG_ERROR(
+                "Invalid ADC sensor type %d specified for channel %d",
+                sensorType, eChannelId);
+            return ADMW_INVALID_PARAM;
+        }
+        break;
+    case ADMW1001_ADC_SENSOR_CURRENT:
+    case ADMW1001_ADC_SENSOR_CURRENT_PRESSURE_A_DEF_L1:
+    case ADMW1001_ADC_SENSOR_CURRENT_PRESSURE_1_DEF_L2:
+    case ADMW1001_ADC_SENSOR_CURRENT_PRESSURE_2_DEF_L2:
+    case ADMW1001_ADC_SENSOR_CURRENT_PRESSURE_A_ADV_L1:
+    case ADMW1001_ADC_SENSOR_CURRENT_PRESSURE_1_ADV_L2:
+    case ADMW1001_ADC_SENSOR_CURRENT_PRESSURE_2_ADV_L2:
+        if (! ADMW1001_CHANNEL_IS_ADC_CURRENT(eChannelId))
+        {
+            ADMW_LOG_ERROR(
+                "Invalid ADC sensor type %d specified for channel %d",
+                sensorType, eChannelId);
+            return ADMW_INVALID_PARAM;
+        }
+        break;
+    default:
+        ADMW_LOG_ERROR("Invalid/unsupported ADC sensor type %d specified",
+                            sensorType);
+        return ADMW_INVALID_PARAM;
+    }
+
+    sensorTypeReg.Sensor_Type = sensorType;
+
+    WRITE_REG_U16(hDevice, sensorTypeReg.VALUE16, CORE_SENSOR_TYPEn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetChannelAdcSensorDetails(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
+/*
+ * TODO - it would be nice if the general- vs. ADC-specific sensor details could be split into separate registers
+ * General details:
+ * - Measurement_Units
+ * - Compensation_Channel
+ * - CJC_Publish (if "CJC" was removed from the name)
+ * ADC-specific details:
+ * - PGA_Gain
+ * - Reference_Select
+ * - Reference_Buffer_Disable
+ * - Vbias
+ */
+{
+    ADMW1001_ADC_CHANNEL_CONFIG *pAdcChannelConfig = &pChannelConfig->adcChannelConfig;
+    ADMW1001_ADC_REFERENCE_CONFIG *pRefConfig = &pAdcChannelConfig->reference;
+    CORE_Sensor_Details_t sensorDetailsReg;
+
+    sensorDetailsReg.VALUE32 = REG_RESET_VAL(CORE_SENSOR_DETAILSn);
+
+    switch(pChannelConfig->measurementUnit)
+    {
+    case ADMW1001_MEASUREMENT_UNIT_FAHRENHEIT:
+        sensorDetailsReg.Measurement_Units = CORE_SENSOR_DETAILS_UNITS_DEGF;
+        break;
+    case ADMW1001_MEASUREMENT_UNIT_CELSIUS:
+        sensorDetailsReg.Measurement_Units = CORE_SENSOR_DETAILS_UNITS_DEGC;
+        break;
+    case ADMW1001_MEASUREMENT_UNIT_UNSPECIFIED:
+        sensorDetailsReg.Measurement_Units = CORE_SENSOR_DETAILS_UNITS_UNSPECIFIED;
+        break;
+    default:
+        ADMW_LOG_ERROR("Invalid measurement unit %d specified",
+                            pChannelConfig->measurementUnit);
+        return ADMW_INVALID_PARAM;
+    }
+
+    if (pChannelConfig->compensationChannel == ADMW1001_CHANNEL_ID_NONE)
+    {
+        sensorDetailsReg.Compensation_Disable = 1;
+        sensorDetailsReg.Compensation_Channel = 0;
+    }
+    else
+    {
+        sensorDetailsReg.Compensation_Disable = 0;
+        sensorDetailsReg.Compensation_Channel = pChannelConfig->compensationChannel;
+    }
+
+    switch(pRefConfig->type)
+    {
+    case ADMW1001_ADC_REFERENCE_RESISTOR_INTERNAL_1:
+        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_RINT1;
+        break;
+    case ADMW1001_ADC_REFERENCE_RESISTOR_INTERNAL_2:
+        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_RINT2;
+        break;
+    case ADMW1001_ADC_REFERENCE_VOLTAGE_INTERNAL:
+        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_INT;
+        break;
+    case ADMW1001_ADC_REFERENCE_VOLTAGE_AVDD:
+        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_AVDD;
+        break;
+    case ADMW1001_ADC_REFERENCE_RESISTOR_EXTERNAL_1:
+        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_REXT1;
+        break;
+    case ADMW1001_ADC_REFERENCE_RESISTOR_EXTERNAL_2:
+        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_REXT2;
+        break;
+    case ADMW1001_ADC_REFERENCE_VOLTAGE_EXTERNAL_1:
+        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_VEXT1;
+        break;
+    case ADMW1001_ADC_REFERENCE_VOLTAGE_EXTERNAL_2:
+        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_VEXT2;
+        break;
+    case ADMW1001_ADC_REFERENCE_BRIDGE_EXCITATION:
+        sensorDetailsReg.Reference_Select = CORE_SENSOR_DETAILS_REF_EXC;
+        break;
+    default:
+        ADMW_LOG_ERROR("Invalid ADC reference type %d specified",
+                            pRefConfig->type);
+        return ADMW_INVALID_PARAM;
+    }
+
+    switch(pAdcChannelConfig->gain)
+    {
+    case ADMW1001_ADC_GAIN_1X:
+        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_1;
+        break;
+    case ADMW1001_ADC_GAIN_2X:
+        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_2;
+        break;
+    case ADMW1001_ADC_GAIN_4X:
+        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_4;
+        break;
+    case ADMW1001_ADC_GAIN_8X:
+        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_8;
+        break;
+    case ADMW1001_ADC_GAIN_16X:
+        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_16;
+        break;
+    case ADMW1001_ADC_GAIN_32X:
+        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_32;
+        break;
+    case ADMW1001_ADC_GAIN_64X:
+        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_64;
+        break;
+    case ADMW1001_ADC_GAIN_128X:
+        sensorDetailsReg.PGA_Gain = CORE_SENSOR_DETAILS_PGA_GAIN_128;
+        break;
+    default:
+        ADMW_LOG_ERROR("Invalid ADC gain %d specified",
+                            pAdcChannelConfig->gain);
+        return ADMW_INVALID_PARAM;
+    }
+
+    if (pAdcChannelConfig->enableVbias)
+        sensorDetailsReg.Vbias = 1;
+    else
+        sensorDetailsReg.Vbias = 0;
+
+    if (pAdcChannelConfig->reference.disableBuffer)
+        sensorDetailsReg.Reference_Buffer_Disable = 1;
+    else
+        sensorDetailsReg.Reference_Buffer_Disable = 0;
+
+    if (pChannelConfig->disablePublishing)
+        sensorDetailsReg.Do_Not_Publish = 1;
+    else
+        sensorDetailsReg.Do_Not_Publish = 0;
+
+    if (pChannelConfig->enableUnityLut)
+        sensorDetailsReg.Unity_LUT_Select = 1;
+    else
+        sensorDetailsReg.Unity_LUT_Select = 0;
+
+    WRITE_REG_U32(hDevice, sensorDetailsReg.VALUE32, CORE_SENSOR_DETAILSn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetChannelAdcFilter(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_ADC_FILTER_CONFIG *pFilterConfig)
+{
+    CORE_Filter_Select_t filterSelectReg;
+
+    filterSelectReg.VALUE32 = REG_RESET_VAL(CORE_FILTER_SELECTn);
+
+    if (pFilterConfig->type == ADMW1001_ADC_FILTER_SINC4)
+    {
+        filterSelectReg.ADC_Filter_Type = CORE_FILTER_SELECT_FILTER_SINC4;
+        filterSelectReg.ADC_FS = pFilterConfig->fs;
+    }
+    else if (pFilterConfig->type == ADMW1001_ADC_FILTER_FIR_20SPS)
+    {
+        filterSelectReg.ADC_Filter_Type = CORE_FILTER_SELECT_FILTER_FIR_20SPS;
+    }
+    else if (pFilterConfig->type == ADMW1001_ADC_FILTER_FIR_25SPS)
+    {
+        filterSelectReg.ADC_Filter_Type = CORE_FILTER_SELECT_FILTER_FIR_25SPS;
+    }
+    else
+    {
+        ADMW_LOG_ERROR("Invalid ADC filter type %d specified",
+                            pFilterConfig->type);
+        return ADMW_INVALID_PARAM;
+    }
+
+    WRITE_REG_U32(hDevice, filterSelectReg.VALUE32, CORE_FILTER_SELECTn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetChannelAdcCurrentConfig(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_ADC_EXC_CURRENT_CONFIG *pCurrentConfig)
+{
+    CORE_Channel_Excitation_t channelExcitationReg;
+
+    channelExcitationReg.VALUE8 = REG_RESET_VAL(CORE_CHANNEL_EXCITATIONn);
+
+    if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_NONE)
+    {
+        channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_OFF;
+    }
+    else
+    {
+        if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_50uA)
+            channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_50UA;
+        else if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_100uA)
+            channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_100UA;
+        else if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_250uA)
+            channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_250UA;
+        else if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_500uA)
+            channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_500UA;
+        else if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_750uA)
+            channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_750UA;
+        else if (pCurrentConfig->outputLevel == ADMW1001_ADC_EXC_CURRENT_1000uA)
+            channelExcitationReg.IOUT_Excitation_Current = CORE_CHANNEL_EXCITATION_IEXC_1000UA;
+        else
+        {
+            ADMW_LOG_ERROR("Invalid ADC excitation current %d specified",
+                                pCurrentConfig->outputLevel);
+            return ADMW_INVALID_PARAM;
+        }
+    }
+
+    if (pCurrentConfig->diodeRatio == ADMW1001_ADC_EXC_CURRENT_IOUT_DIODE_DEFAULT)
+    {
+        channelExcitationReg.IOUT_Diode_Ratio = 0;
+    }
+    else
+    {
+        channelExcitationReg.IOUT_Diode_Ratio = 1;
+    }
+
+    WRITE_REG_U8(hDevice, channelExcitationReg.VALUE8, CORE_CHANNEL_EXCITATIONn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw_SetAdcChannelConfig(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
+{
+    ADMW_RESULT eRet;
+    ADMW1001_ADC_CHANNEL_CONFIG *pAdcChannelConfig =
+        &pChannelConfig->adcChannelConfig;
+
+    eRet = admw_SetChannelAdcSensorType(hDevice, eChannelId,
+                                             pAdcChannelConfig->sensor);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set ADC sensor type for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetChannelAdcSensorDetails(hDevice, eChannelId,
+                                                pChannelConfig);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set ADC sensor details for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetChannelAdcFilter(hDevice, eChannelId,
+                                         &pAdcChannelConfig->filter);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set ADC filter for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetChannelAdcCurrentConfig(hDevice, eChannelId,
+                                                &pAdcChannelConfig->current);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set ADC current for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetChannelDigitalSensorDetails(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
+{
+    CORE_Sensor_Details_t sensorDetailsReg;
+
+    sensorDetailsReg.VALUE32 = REG_RESET_VAL(CORE_SENSOR_DETAILSn);
+
+    if (pChannelConfig->compensationChannel == ADMW1001_CHANNEL_ID_NONE)
+    {
+        sensorDetailsReg.Compensation_Disable = 1;
+        sensorDetailsReg.Compensation_Channel = 0;
+    }
+    else
+    {
+        ADMW_LOG_ERROR("Invalid compensation channel specified for digital sensor");
+        return ADMW_INVALID_PARAM;
+    }
+
+    if (pChannelConfig->measurementUnit == ADMW1001_MEASUREMENT_UNIT_UNSPECIFIED)
+    {
+        sensorDetailsReg.Measurement_Units = CORE_SENSOR_DETAILS_UNITS_UNSPECIFIED;
+    }
+    else
+    {
+        ADMW_LOG_ERROR("Invalid measurement unit specified for digital channel");
+        return ADMW_INVALID_PARAM;
+    }
+
+    if (pChannelConfig->disablePublishing)
+        sensorDetailsReg.Do_Not_Publish = 1;
+    else
+        sensorDetailsReg.Do_Not_Publish = 0;
+
+    if (pChannelConfig->enableUnityLut)
+        sensorDetailsReg.Unity_LUT_Select = 1;
+    else
+        sensorDetailsReg.Unity_LUT_Select = 0;
+
+    sensorDetailsReg.Vbias = 0;
+    sensorDetailsReg.Reference_Buffer_Disable = 1;
+
+    WRITE_REG_U32(hDevice, sensorDetailsReg.VALUE32, CORE_SENSOR_DETAILSn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetDigitalSensorCommands(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_DIGITAL_SENSOR_COMMAND *pConfigCommand,
+    ADMW1001_DIGITAL_SENSOR_COMMAND *pDataRequestCommand)
+{
+    CORE_Digital_Sensor_Num_Cmds_t numCmdsReg;
+
+    numCmdsReg.VALUE8 = REG_RESET_VAL(CORE_DIGITAL_SENSOR_NUM_CMDSn);
+
+    CHECK_REG_FIELD_VAL(CORE_DIGITAL_SENSOR_NUM_CMDS_DIGITAL_SENSOR_NUM_CFG_CMDS,
+                        pConfigCommand->commandLength);
+    CHECK_REG_FIELD_VAL(CORE_DIGITAL_SENSOR_NUM_CMDS_DIGITAL_SENSOR_NUM_READ_CMDS,
+                        pDataRequestCommand->commandLength);
+
+    numCmdsReg.Digital_Sensor_Num_Cfg_Cmds = pConfigCommand->commandLength;
+    numCmdsReg.Digital_Sensor_Num_Read_Cmds = pDataRequestCommand->commandLength;
+
+    WRITE_REG_U8(hDevice, numCmdsReg.VALUE8,
+                 CORE_DIGITAL_SENSOR_NUM_CMDSn(eChannelId));
+
+    /*
+     * NOTE - the fall-through cases in the switch statement below are
+     * intentional, so temporarily disable related compiler warnings which may
+     * be produced here by GCC
+     */
+#ifndef __CC_ARM
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
+#endif
+
+    switch (pConfigCommand->commandLength)
+    {
+    case 7:
+        WRITE_REG_U8(hDevice, pConfigCommand->command[6],
+                     CORE_DIGITAL_SENSOR_COMMAND7n(eChannelId));
+    case 6:
+        WRITE_REG_U8(hDevice, pConfigCommand->command[5],
+                     CORE_DIGITAL_SENSOR_COMMAND6n(eChannelId));
+    case 5:
+        WRITE_REG_U8(hDevice, pConfigCommand->command[4],
+                     CORE_DIGITAL_SENSOR_COMMAND5n(eChannelId));
+    case 4:
+        WRITE_REG_U8(hDevice, pConfigCommand->command[3],
+                     CORE_DIGITAL_SENSOR_COMMAND4n(eChannelId));
+    case 3:
+        WRITE_REG_U8(hDevice, pConfigCommand->command[2],
+                     CORE_DIGITAL_SENSOR_COMMAND3n(eChannelId));
+    case 2:
+        WRITE_REG_U8(hDevice, pConfigCommand->command[1],
+                     CORE_DIGITAL_SENSOR_COMMAND2n(eChannelId));
+    case 1:
+        WRITE_REG_U8(hDevice, pConfigCommand->command[0],
+                     CORE_DIGITAL_SENSOR_COMMAND1n(eChannelId));
+    case 0:
+    default:
+        break;
+    };
+
+    switch (pDataRequestCommand->commandLength)
+    {
+    case 7:
+        WRITE_REG_U8(hDevice, pDataRequestCommand->command[6],
+                     CORE_DIGITAL_SENSOR_READ_CMD7n(eChannelId));
+    case 6:
+        WRITE_REG_U8(hDevice, pDataRequestCommand->command[5],
+                     CORE_DIGITAL_SENSOR_READ_CMD6n(eChannelId));
+    case 5:
+        WRITE_REG_U8(hDevice, pDataRequestCommand->command[4],
+                     CORE_DIGITAL_SENSOR_READ_CMD5n(eChannelId));
+    case 4:
+        WRITE_REG_U8(hDevice, pDataRequestCommand->command[3],
+                     CORE_DIGITAL_SENSOR_READ_CMD4n(eChannelId));
+    case 3:
+        WRITE_REG_U8(hDevice, pDataRequestCommand->command[2],
+                     CORE_DIGITAL_SENSOR_READ_CMD3n(eChannelId));
+    case 2:
+        WRITE_REG_U8(hDevice, pDataRequestCommand->command[1],
+                     CORE_DIGITAL_SENSOR_READ_CMD2n(eChannelId));
+    case 1:
+        WRITE_REG_U8(hDevice, pDataRequestCommand->command[0],
+                     CORE_DIGITAL_SENSOR_READ_CMD1n(eChannelId));
+    case 0:
+    default:
+        break;
+    };
+
+    /* Re-enable the implicit-fallthrough warning */
+#ifndef __CC_ARM
+#pragma GCC diagnostic pop
+#endif
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetDigitalSensorFormat(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_DIGITAL_SENSOR_DATA_FORMAT *pDataFormat)
+{
+    CORE_Digital_Sensor_Config_t sensorConfigReg;
+
+    sensorConfigReg.VALUE16 = REG_RESET_VAL(CORE_DIGITAL_SENSOR_CONFIGn);
+
+    if (pDataFormat->coding != ADMW1001_DIGITAL_SENSOR_DATA_CODING_NONE)
+    {
+        if (pDataFormat->frameLength == 0)
+        {
+            ADMW_LOG_ERROR("Invalid frame length specified for digital sensor data format");
+            return ADMW_INVALID_PARAM;
+        }
+        if (pDataFormat->numDataBits == 0)
+        {
+            ADMW_LOG_ERROR("Invalid frame length specified for digital sensor data format");
+            return ADMW_INVALID_PARAM;
+        }
+
+        CHECK_REG_FIELD_VAL(CORE_DIGITAL_SENSOR_CONFIG_DIGITAL_SENSOR_READ_BYTES,
+                            pDataFormat->frameLength - 1);
+        CHECK_REG_FIELD_VAL(CORE_DIGITAL_SENSOR_CONFIG_DIGITAL_SENSOR_DATA_BITS,
+                            pDataFormat->numDataBits - 1);
+        CHECK_REG_FIELD_VAL(CORE_DIGITAL_SENSOR_CONFIG_DIGITAL_SENSOR_BIT_OFFSET,
+                            pDataFormat->bitOffset);
+
+        sensorConfigReg.Digital_Sensor_Read_Bytes = pDataFormat->frameLength - 1;
+        sensorConfigReg.Digital_Sensor_Data_Bits = pDataFormat->numDataBits - 1;
+        sensorConfigReg.Digital_Sensor_Bit_Offset = pDataFormat->bitOffset;
+        sensorConfigReg.Digital_Sensor_Left_Aligned = pDataFormat->leftJustified ? 1 : 0;
+        sensorConfigReg.Digital_Sensor_Little_Endian = pDataFormat->littleEndian ? 1 : 0;
+
+        switch (pDataFormat->coding)
+        {
+        case ADMW1001_DIGITAL_SENSOR_DATA_CODING_UNIPOLAR:
+            sensorConfigReg.Digital_Sensor_Coding = CORE_DIGITAL_SENSOR_CONFIG_CODING_UNIPOLAR;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_DATA_CODING_TWOS_COMPLEMENT:
+            sensorConfigReg.Digital_Sensor_Coding = CORE_DIGITAL_SENSOR_CONFIG_CODING_TWOS_COMPL;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_DATA_CODING_OFFSET_BINARY:
+            sensorConfigReg.Digital_Sensor_Coding = CORE_DIGITAL_SENSOR_CONFIG_CODING_OFFSET_BINARY;
+            break;
+        default:
+            ADMW_LOG_ERROR("Invalid coding specified for digital sensor data format");
+            return ADMW_INVALID_PARAM;
+        }
+    }
+    else
+    {
+        sensorConfigReg.Digital_Sensor_Coding = CORE_DIGITAL_SENSOR_CONFIG_CODING_NONE;
+    }
+
+    WRITE_REG_U16(hDevice, sensorConfigReg.VALUE16,
+                  CORE_DIGITAL_SENSOR_CONFIGn(eChannelId));
+
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetDigitalCalibrationParam(
+        ADMW_DEVICE_HANDLE hDevice,
+        ADMW1001_CHANNEL_ID eChannelId,
+        ADMW1001_DIGITAL_CALIBRATION_COMMAND *pCalibrationParam)
+{
+    CORE_Calibration_Parameter_t calibrationParamReg;
+
+    calibrationParamReg.VALUE32 = REG_RESET_VAL(CORE_CALIBRATION_PARAMETERn);
+
+    if (pCalibrationParam->enableCalibrationParam == false)
+        calibrationParamReg.Calibration_Parameter_Enable = 0;
+    else
+        calibrationParamReg.Calibration_Parameter_Enable = 1;
+
+    CHECK_REG_FIELD_VAL(CORE_CALIBRATION_PARAMETER_CALIBRATION_PARAMETER,
+        pCalibrationParam->calibrationParam);
+
+    calibrationParamReg.Calibration_Parameter = pCalibrationParam->calibrationParam;
+
+    WRITE_REG_U32(hDevice, calibrationParamReg.VALUE32,
+                  CORE_CALIBRATION_PARAMETERn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetChannelI2cSensorType(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_I2C_SENSOR_TYPE sensorType)
+{
+    CORE_Sensor_Type_t sensorTypeReg;
+
+    sensorTypeReg.VALUE16 = REG_RESET_VAL(CORE_SENSOR_TYPEn);
+
+    /* Ensure that the sensor type is valid for this channel */
+    switch(sensorType)
+    {
+    case ADMW1001_I2C_SENSOR_HUMIDITY_A_DEF_L1:
+    case ADMW1001_I2C_SENSOR_HUMIDITY_B_DEF_L1:
+    case ADMW1001_I2C_SENSOR_HUMIDITY_A_DEF_L2:
+    case ADMW1001_I2C_SENSOR_HUMIDITY_B_DEF_L2:
+    case ADMW1001_I2C_SENSOR_HUMIDITY_A_ADV_L1:
+    case ADMW1001_I2C_SENSOR_HUMIDITY_B_ADV_L1:
+    case ADMW1001_I2C_SENSOR_HUMIDITY_A_ADV_L2:
+    case ADMW1001_I2C_SENSOR_HUMIDITY_B_ADV_L2:
+    case ADMW1001_I2C_SENSOR_AMBIENTLIGHT_A_DEF_L1:
+    case ADMW1001_I2C_SENSOR_AMBIENTLIGHT_A_DEF_L2:
+    case ADMW1001_I2C_SENSOR_AMBIENTLIGHT_A_ADV_L1:
+    case ADMW1001_I2C_SENSOR_AMBIENTLIGHT_A_ADV_L2:
+        sensorTypeReg.Sensor_Type = sensorType;
+        break;
+    default:
+        ADMW_LOG_ERROR("Unsupported I2C sensor type %d specified", sensorType);
+        return ADMW_INVALID_PARAM;
+    }
+
+    WRITE_REG_U16(hDevice, sensorTypeReg.VALUE16, CORE_SENSOR_TYPEn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetChannelI2cSensorAddress(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    uint32_t deviceAddress)
+{
+    CHECK_REG_FIELD_VAL(CORE_DIGITAL_SENSOR_ADDRESS_DIGITAL_SENSOR_ADDRESS, deviceAddress);
+    WRITE_REG_U8(hDevice, deviceAddress, CORE_DIGITAL_SENSOR_ADDRESSn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetDigitalChannelComms(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_DIGITAL_SENSOR_COMMS *pDigitalComms)
+{
+    CORE_Digital_Sensor_Comms_t digitalSensorComms;
+
+    digitalSensorComms.VALUE16 = REG_RESET_VAL(CORE_DIGITAL_SENSOR_COMMSn);
+
+    if(pDigitalComms->useCustomCommsConfig)
+    {
+        digitalSensorComms.Digital_Sensor_Comms_En = 1;
+
+        if(pDigitalComms->i2cClockSpeed == ADMW1001_DIGITAL_SENSOR_COMMS_I2C_CLOCK_SPEED_100K)
+        {
+            digitalSensorComms.I2C_Clock = CORE_DIGITAL_SENSOR_COMMS_I2C_100K;
+        }
+        else if(pDigitalComms->i2cClockSpeed == ADMW1001_DIGITAL_SENSOR_COMMS_I2C_CLOCK_SPEED_400K)
+        {
+            digitalSensorComms.I2C_Clock = CORE_DIGITAL_SENSOR_COMMS_I2C_400K;
+        }
+        else
+        {
+            ADMW_LOG_ERROR("Invalid I2C clock speed %d specified",
+                                pDigitalComms->i2cClockSpeed);
+            return ADMW_INVALID_PARAM;
+        }
+
+        if(pDigitalComms->spiMode == ADMW1001_DIGITAL_SENSOR_COMMS_SPI_MODE_0)
+        {
+            digitalSensorComms.SPI_Mode = CORE_DIGITAL_SENSOR_COMMS_SPI_MODE_0;
+        }
+        else if(pDigitalComms->spiMode == ADMW1001_DIGITAL_SENSOR_COMMS_SPI_MODE_1)
+        {
+            digitalSensorComms.SPI_Mode = CORE_DIGITAL_SENSOR_COMMS_SPI_MODE_1;
+        }
+        else if(pDigitalComms->spiMode == ADMW1001_DIGITAL_SENSOR_COMMS_SPI_MODE_2)
+        {
+            digitalSensorComms.SPI_Mode = CORE_DIGITAL_SENSOR_COMMS_SPI_MODE_2;
+        }
+        else if(pDigitalComms->spiMode == ADMW1001_DIGITAL_SENSOR_COMMS_SPI_MODE_3)
+        {
+            digitalSensorComms.SPI_Mode = CORE_DIGITAL_SENSOR_COMMS_SPI_MODE_3;
+        }
+        else
+        {
+            ADMW_LOG_ERROR("Invalid SPI mode %d specified",
+                                pDigitalComms->spiMode);
+            return ADMW_INVALID_PARAM;
+        }
+
+        switch (pDigitalComms->spiClock)
+        {
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_13MHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_13MHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_6_5MHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_6_5MHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_3_25MHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_3_25MHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_1_625MHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_1_625MHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_812KHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_812KHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_406KHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_406KHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_203KHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_203KHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_101KHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_101KHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_50KHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_50KHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_25KHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_25KHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_12KHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_12KHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_6KHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_6KHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_3KHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_3KHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_1_5KHZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_1_5KHZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_793HZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_793HZ;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_SPI_CLOCK_396HZ:
+            digitalSensorComms.SPI_Clock = CORE_DIGITAL_SENSOR_COMMS_SPI_396HZ;
+            break;
+        default:
+            ADMW_LOG_ERROR("Invalid SPI clock %d specified",
+                                pDigitalComms->spiClock);
+            return ADMW_INVALID_PARAM;
+        }
+
+        switch (pDigitalComms->uartLineConfig)
+        {
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8N1:
+            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8N1;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8N2:
+            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8N2;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8N3:
+            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8N3;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8E1:
+            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8E1;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8E2:
+            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8E2;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8E3:
+            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8E3;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8O1:
+            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8O1;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8O2:
+            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8O2;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_LINE_CONFIG_8O3:
+            digitalSensorComms.Uart_Mode = CORE_DIGITAL_SENSOR_COMMS_LINECONTROL_8O3;
+            break;
+        default:
+            ADMW_LOG_ERROR("Invalid UART mode %d specified",
+                                pDigitalComms->uartLineConfig);
+            return ADMW_INVALID_PARAM;
+        }
+
+        switch (pDigitalComms->uartBaudRate)
+        {
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_115200:
+            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_115200;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_57600:
+            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_57600;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_38400:
+            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_38400;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_19200:
+            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_19200;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_9600:
+            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_9600;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_4800:
+            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_4800;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_2400:
+            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_2400;
+            break;
+        case ADMW1001_DIGITAL_SENSOR_COMMS_UART_BAUD_RATE_1200:
+            digitalSensorComms.Uart_Baud = CORE_DIGITAL_SENSOR_COMMS_UART_1200;
+            break;
+        default:
+            ADMW_LOG_ERROR("Invalid UART baud rate %d specified",
+                                pDigitalComms->uartBaudRate);
+            return ADMW_INVALID_PARAM;
+        }
+    }
+    else
+    {
+        digitalSensorComms.Digital_Sensor_Comms_En = 0;
+    }
+
+    WRITE_REG_U16(hDevice, digitalSensorComms.VALUE16, CORE_DIGITAL_SENSOR_COMMSn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw_SetI2cChannelConfig(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
+{
+    ADMW_RESULT eRet;
+    ADMW1001_I2C_CHANNEL_CONFIG *pI2cChannelConfig =
+        &pChannelConfig->i2cChannelConfig;
+
+    eRet = admw_SetChannelI2cSensorType(hDevice, eChannelId,
+                                            pI2cChannelConfig->sensor);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set I2C sensor type for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetChannelI2cSensorAddress(hDevice, eChannelId,
+                                               pI2cChannelConfig->deviceAddress);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set I2C sensor address for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetChannelDigitalSensorDetails(hDevice, eChannelId,
+                                                    pChannelConfig);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set I2C sensor details for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetDigitalSensorCommands(hDevice, eChannelId,
+                                              &pI2cChannelConfig->configurationCommand,
+                                              &pI2cChannelConfig->dataRequestCommand);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set I2C sensor commands for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetDigitalSensorFormat(hDevice, eChannelId,
+                                            &pI2cChannelConfig->dataFormat);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set I2C sensor data format for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetDigitalCalibrationParam(hDevice, eChannelId,
+                                            &pI2cChannelConfig->digitalCalibrationParam);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set I2C digital calibration param for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetDigitalChannelComms(hDevice, eChannelId,
+                                            &pI2cChannelConfig->configureComms);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set I2C comms for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetChannelSpiSensorType(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_SPI_SENSOR_TYPE sensorType)
+{
+    CORE_Sensor_Type_t sensorTypeReg;
+
+    sensorTypeReg.VALUE16 = REG_RESET_VAL(CORE_SENSOR_TYPEn);
+
+    /* Ensure that the sensor type is valid for this channel */
+    switch(sensorType)
+    {
+    case ADMW1001_SPI_SENSOR_PRESSURE_A_DEF_L1:
+    case ADMW1001_SPI_SENSOR_PRESSURE_A_DEF_L2:
+    case ADMW1001_SPI_SENSOR_PRESSURE_A_ADV_L1:
+    case ADMW1001_SPI_SENSOR_PRESSURE_A_ADV_L2:
+    case ADMW1001_SPI_SENSOR_ACCELEROMETER_A_DEF_L1:
+    case ADMW1001_SPI_SENSOR_ACCELEROMETER_B_DEF_L1:
+    case ADMW1001_SPI_SENSOR_ACCELEROMETER_A_DEF_L2:
+    case ADMW1001_SPI_SENSOR_ACCELEROMETER_B_DEF_L2:
+    case ADMW1001_SPI_SENSOR_ACCELEROMETER_A_ADV_L1:
+    case ADMW1001_SPI_SENSOR_ACCELEROMETER_B_ADV_L1:
+    case ADMW1001_SPI_SENSOR_ACCELEROMETER_A_ADV_L2:
+    case ADMW1001_SPI_SENSOR_ACCELEROMETER_B_ADV_L2:
+        sensorTypeReg.Sensor_Type = sensorType;
+        break;
+    default:
+        ADMW_LOG_ERROR("Unsupported SPI sensor type %d specified", sensorType);
+        return ADMW_INVALID_PARAM;
+    }
+
+    WRITE_REG_U16(hDevice, sensorTypeReg.VALUE16, CORE_SENSOR_TYPEn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw_SetSpiChannelConfig(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
+{
+    ADMW_RESULT eRet;
+    ADMW1001_SPI_CHANNEL_CONFIG *pSpiChannelConfig =
+        &pChannelConfig->spiChannelConfig;
+
+    eRet = admw_SetChannelSpiSensorType(hDevice, eChannelId,
+                                             pSpiChannelConfig->sensor);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set SPI sensor type for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetChannelDigitalSensorDetails(hDevice, eChannelId,
+                                                    pChannelConfig);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set SPI sensor details for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetDigitalSensorCommands(hDevice, eChannelId,
+                                              &pSpiChannelConfig->configurationCommand,
+                                              &pSpiChannelConfig->dataRequestCommand);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set SPI sensor commands for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetDigitalSensorFormat(hDevice, eChannelId,
+                                            &pSpiChannelConfig->dataFormat);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set SPI sensor data format for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetDigitalCalibrationParam(hDevice, eChannelId,
+                                            &pSpiChannelConfig->digitalCalibrationParam);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set SPI digital calibration param for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetDigitalChannelComms(hDevice, eChannelId,
+                                            &pSpiChannelConfig->configureComms);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set SPI comms for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    return ADMW_SUCCESS;
+}
+
+static ADMW_RESULT admw_SetChannelUartSensorType(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_UART_SENSOR_TYPE sensorType)
+{
+    CORE_Sensor_Type_t sensorTypeReg;
+
+    sensorTypeReg.VALUE16 = REG_RESET_VAL(CORE_SENSOR_TYPEn);
+
+    /* Ensure that the sensor type is valid for this channel */
+    switch(sensorType)
+    {
+    case ADMW1001_UART_SENSOR_UART_CO2_A_DEF_L1:
+    case ADMW1001_UART_SENSOR_UART_CO2_B_DEF_L1:
+    case ADMW1001_UART_SENSOR_UART_CO2_A_DEF_L2:
+    case ADMW1001_UART_SENSOR_UART_CO2_B_DEF_L2:
+    case ADMW1001_UART_SENSOR_UART_CO2_A_ADV_L1:
+    case ADMW1001_UART_SENSOR_UART_CO2_B_ADV_L1:
+    case ADMW1001_UART_SENSOR_UART_CO2_A_ADV_L2:
+    case ADMW1001_UART_SENSOR_UART_CO2_B_ADV_L2:
+        sensorTypeReg.Sensor_Type = sensorType;
+        break;
+    default:
+        ADMW_LOG_ERROR("Unsupported UART sensor type %d specified", sensorType);
+        return ADMW_INVALID_PARAM;
+    }
+
+    WRITE_REG_U16(hDevice, sensorTypeReg.VALUE16, CORE_SENSOR_TYPEn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw_SetUartChannelConfig(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
+{
+    ADMW_RESULT eRet;
+    ADMW1001_UART_CHANNEL_CONFIG *pUartChannelConfig =
+        &pChannelConfig->uartChannelConfig;
+
+    eRet = admw_SetChannelUartSensorType(hDevice, eChannelId,
+                                             pUartChannelConfig->sensor);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set UART sensor type for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetChannelDigitalSensorDetails(hDevice, eChannelId,
+                                                    pChannelConfig);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set UART sensor details for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetDigitalCalibrationParam(hDevice, eChannelId,
+                                            &pUartChannelConfig->digitalCalibrationParam);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set UART digital calibration param for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+    eRet = admw_SetDigitalChannelComms(hDevice, eChannelId,
+                                            &pUartChannelConfig->configureComms);
+    if (eRet != ADMW_SUCCESS)
+    {
+        ADMW_LOG_ERROR("Failed to set UART comms for channel %d",
+                            eChannelId);
+        return eRet;
+    }
+
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetChannelThresholdLimits(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    float32_t fHighThresholdLimit,
+    float32_t fLowThresholdLimit)
+{
+    /*
+     * If the low/high limits are *both* set to 0 in memory, or NaNs, assume
+     * that they are unset, or not required, and use infinity defaults instead
+     */
+    if (fHighThresholdLimit == 0.0f && fLowThresholdLimit == 0.0f)
+    {
+        fHighThresholdLimit = INFINITY;
+        fLowThresholdLimit = -INFINITY;
+    }
+    else
+    {
+        if (isnan(fHighThresholdLimit))
+            fHighThresholdLimit = INFINITY;
+        if (isnan(fLowThresholdLimit))
+            fLowThresholdLimit = -INFINITY;
+    }
+
+    WRITE_REG_FLOAT(hDevice, fHighThresholdLimit,
+                    CORE_HIGH_THRESHOLD_LIMITn(eChannelId));
+    WRITE_REG_FLOAT(hDevice, fLowThresholdLimit,
+                    CORE_LOW_THRESHOLD_LIMITn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetOffsetGain(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    float32_t fOffsetAdjustment,
+    float32_t fGainAdjustment)
+{
+    /* Replace with default values if NaNs are specified (or 0.0 for gain) */
+    if (isnan(fGainAdjustment) || (fGainAdjustment == 0.0f))
+        fGainAdjustment = 1.0f;
+    if (isnan(fOffsetAdjustment))
+        fOffsetAdjustment = 0.0f;
+
+    WRITE_REG_FLOAT(hDevice, fGainAdjustment, CORE_SENSOR_GAINn(eChannelId));
+    WRITE_REG_FLOAT(hDevice, fOffsetAdjustment, CORE_SENSOR_OFFSETn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetSensorParameter(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    float32_t fSensorParam)
+{
+    if (fSensorParam == 0.0f)
+        fSensorParam = NAN;
+
+    WRITE_REG_FLOAT(hDevice, fSensorParam, CORE_SENSOR_PARAMETERn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetChannelSettlingTime(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    uint32_t nSettlingTime)
+{
+    CORE_Settling_Time_t settlingTimeReg;
+
+    if (nSettlingTime < (1 << 12))
+    {
+        settlingTimeReg.Settling_Time_Units = CORE_SETTLING_TIME_MICROSECONDS;
+    }
+    else if (nSettlingTime < (1000 * (1 << 12)))
+    {
+        settlingTimeReg.Settling_Time_Units = CORE_SETTLING_TIME_MILLISECONDS;
+        nSettlingTime /= 1000;
+    }
+    else
+    {
+        settlingTimeReg.Settling_Time_Units = CORE_SETTLING_TIME_SECONDS;
+        nSettlingTime /= 1000000;
+    }
+
+    CHECK_REG_FIELD_VAL(CORE_SETTLING_TIME_SETTLING_TIME, nSettlingTime);
+    settlingTimeReg.Settling_Time = nSettlingTime;
+
+    WRITE_REG_U16(hDevice, settlingTimeReg.VALUE16, CORE_SETTLING_TIMEn(eChannelId));
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetChannelConfig(
+    ADMW_DEVICE_HANDLE hDevice,
+    ADMW1001_CHANNEL_ID eChannelId,
+    ADMW1001_CHANNEL_CONFIG *pChannelConfig)
+{
+    ADMW_RESULT eRet;
+
+    if (! ADMW1001_CHANNEL_IS_VIRTUAL(eChannelId))
+    {
+        eRet = admw1001_SetChannelCount(hDevice, eChannelId,
+                                              pChannelConfig->enableChannel ?
+                                              pChannelConfig->measurementsPerCycle : 0);
+        if (eRet != ADMW_SUCCESS)
+        {
+            ADMW_LOG_ERROR("Failed to set measurement count for channel %d",
+                                eChannelId);
+            return eRet;
+        }
+
+        eRet = admw1001_SetChannelOptions(hDevice, eChannelId,
+                                                pChannelConfig->priority,
+                                                pChannelConfig->enableFFT);
+        if (eRet != ADMW_SUCCESS)
+        {
+            ADMW_LOG_ERROR("Failed to set priority for channel %d",
+                                eChannelId);
+            return eRet;
+        }
+
+        /* If the channel is not enabled, we can skip the following steps */
+        if (pChannelConfig->enableChannel || pChannelConfig->enableFFT)
+        {
+            eRet = admw1001_SetChannelSkipCount(hDevice, eChannelId,
+                                                      pChannelConfig->cycleSkipCount);
+            if (eRet != ADMW_SUCCESS)
+            {
+                ADMW_LOG_ERROR("Failed to set cycle skip count for channel %d",
+                                    eChannelId);
+                return eRet;
+            }
+
+            switch (eChannelId)
+            {
+            case ADMW1001_CHANNEL_ID_CJC_0:
+            case ADMW1001_CHANNEL_ID_CJC_1:
+            case ADMW1001_CHANNEL_ID_SENSOR_0:
+            case ADMW1001_CHANNEL_ID_SENSOR_1:
+            case ADMW1001_CHANNEL_ID_SENSOR_2:
+            case ADMW1001_CHANNEL_ID_SENSOR_3:
+            case ADMW1001_CHANNEL_ID_VOLTAGE_0:
+            case ADMW1001_CHANNEL_ID_CURRENT_0:
+                eRet = admw_SetAdcChannelConfig(hDevice, eChannelId, pChannelConfig);
+                break;
+            case ADMW1001_CHANNEL_ID_I2C_0:
+            case ADMW1001_CHANNEL_ID_I2C_1:
+                eRet = admw_SetI2cChannelConfig(hDevice, eChannelId, pChannelConfig);
+                break;
+            case ADMW1001_CHANNEL_ID_SPI_0:
+                eRet = admw_SetSpiChannelConfig(hDevice, eChannelId, pChannelConfig);
+                break;
+            case ADMW1001_CHANNEL_ID_UART:
+                eRet = admw_SetUartChannelConfig(hDevice, eChannelId, pChannelConfig);
+                break;
+            default:
+                ADMW_LOG_ERROR("Invalid channel ID %d specified", eChannelId);
+                return ADMW_INVALID_PARAM;
+            }
+
+            eRet = admw1001_SetChannelSettlingTime(hDevice, eChannelId,
+                                                         pChannelConfig->extraSettlingTime);
+            if (eRet != ADMW_SUCCESS)
+            {
+                ADMW_LOG_ERROR("Failed to set settling time for channel %d",
+                                    eChannelId);
+                return eRet;
+            }
+        }
+    }
+
+    if (pChannelConfig->enableChannel || pChannelConfig->enableFFT)
+    {
+        /* Threshold limits can be configured individually for virtual channels */
+        eRet = admw1001_SetChannelThresholdLimits(hDevice, eChannelId,
+                                                        pChannelConfig->highThreshold,
+                                                        pChannelConfig->lowThreshold);
+        if (eRet != ADMW_SUCCESS)
+        {
+            ADMW_LOG_ERROR("Failed to set threshold limits for channel %d",
+                                eChannelId);
+            return eRet;
+        }
+
+        /* Offset and gain can be configured individually for virtual channels */
+        eRet = admw1001_SetOffsetGain(hDevice, eChannelId,
+                                            pChannelConfig->offsetAdjustment,
+                                            pChannelConfig->gainAdjustment);
+        if (eRet != ADMW_SUCCESS)
+        {
+            ADMW_LOG_ERROR("Failed to set offset/gain for channel %d",
+                                eChannelId);
+            return eRet;
+        }
+
+        /* Set sensor specific parameter */
+        eRet = admw1001_SetSensorParameter(hDevice, eChannelId,
+                                                 pChannelConfig->sensorParameter);
+        if (eRet != ADMW_SUCCESS)
+        {
+            ADMW_LOG_ERROR("Failed to set sensor parameter for channel %d",
+                                eChannelId);
+            return eRet;
+        }
+    }
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw_SetConfig(
+    ADMW_DEVICE_HANDLE    const hDevice,
+    ADMW_CONFIG         * const pConfig)
+{
+    ADMW1001_CONFIG *pDeviceConfig;
+    ADMW_PRODUCT_ID productId;
+    ADMW_RESULT eRet;
+
+    if (pConfig->productId != ADMW_PRODUCT_ID_ADMW1001)
+    {
+        ADMW_LOG_ERROR("Configuration Product ID (0x%X) is not supported (0x%0X)",
+                            pConfig->productId, ADMW_PRODUCT_ID_ADMW1001);
+        return ADMW_INVALID_PARAM;
+    }
+
+    /* Check that the actual Product ID is a match? */
+    eRet = admw_GetProductID(hDevice, &productId);
+    if (eRet)
+    {
+        ADMW_LOG_ERROR("Failed to read device Product ID register");
+        return eRet;
+    }
+    if (pConfig->productId != productId)
+    {
+        ADMW_LOG_ERROR("Configuration Product ID (0x%X) does not match device (0x%0X)",
+                            pConfig->productId, productId);
+        return ADMW_INVALID_PARAM;
+    }
+
+    pDeviceConfig = &pConfig->admw1001;
+
+    eRet = admw1001_SetPowerConfig(hDevice, &pDeviceConfig->power);
+    if (eRet)
+    {
+        ADMW_LOG_ERROR("Failed to set power configuration");
+        return eRet;
+    }
+
+    eRet = admw1001_SetMeasurementConfig(hDevice, &pDeviceConfig->measurement);
+    if (eRet)
+    {
+        ADMW_LOG_ERROR("Failed to set measurement configuration");
+        return eRet;
+    }
+
+    eRet = admw1001_SetDiagnosticsConfig(hDevice, &pDeviceConfig->diagnostics);
+    if (eRet)
+    {
+        ADMW_LOG_ERROR("Failed to set diagnostics configuration");
+        return eRet;
+    }
+
+    for (ADMW1001_CHANNEL_ID id = ADMW1001_CHANNEL_ID_CJC_0;
+         id < ADMW1001_MAX_CHANNELS;
+         id++)
+    {
+        eRet = admw1001_SetChannelConfig(hDevice, id,
+                                               &pDeviceConfig->channels[id]);
+        if (eRet)
+        {
+            ADMW_LOG_ERROR("Failed to set channel %d configuration", id);
+            return eRet;
+        }
+    }
+
+    eRet = admw1001_SetFftConfig(hDevice, &pDeviceConfig->fft,
+                                       pDeviceConfig->channels);
+    if (eRet)
+    {
+        ADMW_LOG_ERROR("Failed to set FFT configuration");
+        return eRet;
+    }
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetLutData(
+    ADMW_DEVICE_HANDLE    const hDevice,
+    ADMW1001_LUT       * const pLutData)
+{
+    ADMW1001_LUT_HEADER *pLutHeader = &pLutData->header;
+    ADMW1001_LUT_TABLE *pLutTable = pLutData->tables;
+    unsigned actualLength = 0;
+
+    if (pLutData->header.signature != ADMW_LUT_SIGNATURE)
+    {
+        ADMW_LOG_ERROR("LUT signature incorrect (expected 0x%X, actual 0x%X)",
+                            ADMW_LUT_SIGNATURE, pLutHeader->signature);
+        return ADMW_INVALID_SIGNATURE;
+    }
+
+    for (unsigned i = 0; i < pLutHeader->numTables; i++)
+    {
+        ADMW1001_LUT_DESCRIPTOR *pDesc = &pLutTable->descriptor;
+        ADMW1001_LUT_TABLE_DATA *pData = &pLutTable->data;
+        unsigned short calculatedCrc;
+
+        switch (pDesc->geometry)
+        {
+        case ADMW1001_LUT_GEOMETRY_COEFFS:
+                switch (pDesc->equation)
+                {
+                case ADMW1001_LUT_EQUATION_POLYN:
+                case ADMW1001_LUT_EQUATION_POLYNEXP:
+                case ADMW1001_LUT_EQUATION_QUADRATIC:
+                case ADMW1001_LUT_EQUATION_STEINHART:
+                case ADMW1001_LUT_EQUATION_LOGARITHMIC:
+                case ADMW1001_LUT_EQUATION_BIVARIATE_POLYN:
+                break;
+                default:
+                    ADMW_LOG_ERROR("Invalid equation %u specified for LUT table %u",
+                                        pDesc->equation, i);
+                    return ADMW_INVALID_PARAM;
+                }
+            break;
+        case ADMW1001_LUT_GEOMETRY_NES_1D:
+        case ADMW1001_LUT_GEOMETRY_NES_2D:
+        case ADMW1001_LUT_GEOMETRY_ES_1D:
+        case ADMW1001_LUT_GEOMETRY_ES_2D:
+                if (pDesc->equation != ADMW1001_LUT_EQUATION_LUT) {
+                    ADMW_LOG_ERROR("Invalid equation %u specified for LUT table %u",
+                                        pDesc->equation, i);
+                    return ADMW_INVALID_PARAM;
+                }
+            break;
+        default:
+            ADMW_LOG_ERROR("Invalid geometry %u specified for LUT table %u",
+                                pDesc->geometry, i);
+            return ADMW_INVALID_PARAM;
+        }
+
+        switch (pDesc->dataType)
+        {
+        case ADMW1001_LUT_DATA_TYPE_FLOAT32:
+        case ADMW1001_LUT_DATA_TYPE_FLOAT64:
+            break;
+        default:
+            ADMW_LOG_ERROR("Invalid vector format %u specified for LUT table %u",
+                                pDesc->dataType, i);
+            return ADMW_INVALID_PARAM;
+        }
+
+        calculatedCrc = admw_crc16_ccitt(pData, pDesc->length);
+        if (calculatedCrc != pDesc->crc16)
+        {
+            ADMW_LOG_ERROR("CRC validation failed on LUT table %u (expected 0x%04X, actual 0x%04X)",
+                                i, pDesc->crc16, calculatedCrc);
+            return ADMW_CRC_ERROR;
+        }
+
+        actualLength += sizeof(*pDesc) + pDesc->length;
+
+        /* Move to the next look-up table */
+        pLutTable = (ADMW1001_LUT_TABLE *)((uint8_t *)pLutTable + sizeof(*pDesc) + pDesc->length);
+    }
+
+    if (actualLength != pLutHeader->totalLength)
+    {
+        ADMW_LOG_ERROR("LUT table length mismatch (expected %u, actual %u)",
+                            pLutHeader->totalLength, actualLength);
+        return ADMW_WRONG_SIZE;
+    }
+
+    if (sizeof(*pLutHeader) + pLutHeader->totalLength > ADMW_LUT_MAX_SIZE)
+    {
+        ADMW_LOG_ERROR("Maximum LUT table length (%u bytes) exceeded",
+                            ADMW_LUT_MAX_SIZE);
+        return ADMW_WRONG_SIZE;
+    }
+
+    /* Write the LUT data to the device */
+    unsigned lutSize = sizeof(*pLutHeader) + pLutHeader->totalLength;
+    WRITE_REG_U16(hDevice, 0, CORE_LUT_OFFSET);
+    WRITE_REG_U8_ARRAY(hDevice, (uint8_t *)pLutData, lutSize, CORE_LUT_DATA);
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_SetLutDataRaw(
+    ADMW_DEVICE_HANDLE    const hDevice,
+    ADMW1001_LUT_RAW   * const pLutData)
+{
+    return admw1001_SetLutData(hDevice,
+                                     (ADMW1001_LUT *)pLutData);
+}
+
+static ADMW_RESULT getLutTableSize(
+    ADMW1001_LUT_DESCRIPTOR * const pDesc,
+    ADMW1001_LUT_TABLE_DATA * const pData,
+    unsigned *pLength)
+{
+    switch (pDesc->geometry)
+    {
+    case ADMW1001_LUT_GEOMETRY_COEFFS:
+        if (pDesc->equation == ADMW1001_LUT_EQUATION_BIVARIATE_POLYN)
+            *pLength = ADMW1001_LUT_2D_POLYN_COEFF_LIST_SIZE(pData->coeffList2d);
+        else
+            *pLength = ADMW1001_LUT_COEFF_LIST_SIZE(pData->coeffList);
+        break;
+    case ADMW1001_LUT_GEOMETRY_NES_1D:
+        *pLength = ADMW1001_LUT_1D_NES_SIZE(pData->lut1dNes);
+        break;
+    case ADMW1001_LUT_GEOMETRY_NES_2D:
+        *pLength = ADMW1001_LUT_2D_NES_SIZE(pData->lut2dNes);
+        break;
+    case ADMW1001_LUT_GEOMETRY_ES_1D:
+        *pLength = ADMW1001_LUT_1D_ES_SIZE(pData->lut1dEs);
+        break;
+    case ADMW1001_LUT_GEOMETRY_ES_2D:
+        *pLength = ADMW1001_LUT_2D_ES_SIZE(pData->lut2dEs);
+        break;
+    default:
+        ADMW_LOG_ERROR("Invalid LUT table geometry %d specified\r\n",
+                            pDesc->geometry);
+        return ADMW_INVALID_PARAM;
+    }
+
+    return ADMW_SUCCESS;
+}
+
+ADMW_RESULT admw1001_AssembleLutData(
+    ADMW1001_LUT                  * pLutBuffer,
+    unsigned                              nLutBufferSize,
+    unsigned                        const nNumTables,
+    ADMW1001_LUT_DESCRIPTOR * const ppDesc[],
+    ADMW1001_LUT_TABLE_DATA * const ppData[])
+{
+    ADMW1001_LUT_HEADER *pHdr = &pLutBuffer->header;
+    uint8_t *pLutTableData = (uint8_t *)pLutBuffer + sizeof(*pHdr);
+
+    if (sizeof(*pHdr) > nLutBufferSize)
+    {
+        ADMW_LOG_ERROR("Insufficient LUT buffer size provided");
+        return ADMW_INVALID_PARAM;
+    }
+
+    /* First initialise the top-level header */
+    pHdr->signature = ADMW_LUT_SIGNATURE;
+    pHdr->version.major = 1;
+    pHdr->version.minor = 0;
+    pHdr->numTables = 0;
+    pHdr->totalLength = 0;
+
+    /*
+     * Walk through the list of table pointers provided, appending the table
+     * descriptor+data from each one to the provided LUT buffer
+     */
+    for (unsigned i = 0; i < nNumTables; i++)
+    {
+        ADMW1001_LUT_DESCRIPTOR * const pDesc = ppDesc[i];
+        ADMW1001_LUT_TABLE_DATA * const pData = ppData[i];
+        ADMW_RESULT res;
+        unsigned dataLength = 0;
+
+        /* Calculate the length of the table data */
+        res = getLutTableSize(pDesc, pData, &dataLength);
+        if (res != ADMW_SUCCESS)
+            return res;
+
+        /* Fill in the table descriptor length and CRC fields */
+        pDesc->length = dataLength;
+        pDesc->crc16 = admw_crc16_ccitt(pData, dataLength);
+
+        if ((sizeof(*pHdr) + pHdr->totalLength + sizeof(*pDesc) + dataLength) > nLutBufferSize)
+        {
+            ADMW_LOG_ERROR("Insufficient LUT buffer size provided");
+            return ADMW_INVALID_PARAM;
+        }
+
+        /* Append the table to the LUT buffer (desc + data) */
+        memcpy(pLutTableData + pHdr->totalLength, pDesc, sizeof(*pDesc));
+        pHdr->totalLength += sizeof(*pDesc);
+        memcpy(pLutTableData + pHdr->totalLength, pData, dataLength);
+        pHdr->totalLength += dataLength;
+
+        pHdr->numTables++;
+    }
+
+    return ADMW_SUCCESS;
+}
+