This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Cap sense using single pin per sensor

Hi,

  I'm using the nRF52840 dev kit for a BLE-based application.  I need to implement a capacitive touch button for detecting button presses and releases (no sliding).  I only have 1 analog input pin connected to my sensor.  I've looked at the nrf52-capsense-example project here, but it is based on SDK-12. 

  Is this still the recommended method to follow when only a single pin is connected to the touch sensor?

  Are there any issues using this method along with BLE (SoftDevice S140)?

  Is there any way to use the csense library in this configuration?

  Thanks...

Brian

Parents
  • I work with Brian at Simplexity and we were able to get the capacitive touch button working with a second GPIO that stimulates the pad through a 1M resistor.  This leverages the existing csense driver.  I've attached source code here, in case anyone else wants to leverage it.

    /**
     * Copyright (c) 2016 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, 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.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 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.
     *
     */
    
    // When we update the SDK, we'll need to add these changes to
    // get the cap sensor code working since the embedded current
    // source is broken.  TODO: trim down this complex code and
    // put it in the main code branch so we don't have to update
    // vendor-supplied files
    #define SIMPLEXITY_CHANGES
    
    #include "sdk_common.h"
    #if NRF_MODULE_ENABLED(NRF_DRV_CSENSE)
    #include "nrf_drv_csense.h"
    #include "nrf_peripherals.h"
    #include "nrf_gpio.h"
    #include "app_error.h"
    #include "app_util_platform.h"
    #include "nrf_assert.h"
    #include "string.h"
    #include <stdio.h>
    
    #if defined(__CORTEX_M) && (__CORTEX_M < 4)
    #ifndef ARM_MATH_CM0PLUS
    #define ARM_MATH_CM0PLUS
    #endif
    /*lint -save -e689 */
    #include "arm_math.h"
    /*lint -restore */
    #endif
    
    #if USE_COMP
    #include "nrf_drv_comp.h"
    #include "nrf_drv_ppi.h"
    #include "nrf_drv_timer.h"
    #ifdef SIMPLEXITY_CHANGES
    #include "nrfx_gpiote.h"
    #endif
    #endif //USE_COMP
    
    #if USE_COMP == 0
    #ifdef ADC_PRESENT
    #include "nrfx_adc.h"
    
    /**
     * @defgroup adc_defines ADC defines to count input voltage.
     * @{
     */
    #define ADC_RES_10BIT           1024
    #define ADC_INPUT_PRESCALER     3
    #define ADC_REF_VBG_VOLTAGE     1.2
    /* @} */
    
    /* ADC channel used to call conversion. */
    static nrfx_adc_channel_t adc_channel = NRFX_ADC_DEFAULT_CHANNEL(NRF_ADC_CONFIG_INPUT_0);
    #elif defined(SAADC_PRESENT)
    #include "nrf_drv_saadc.h"
    
    /**
     * @defgroup saadc_defines SAADC defines to count input voltage.
     * @{
     */
    #define SAADC_RES_10BIT           1024
    #define SAADC_INPUT_PRESCALER     3
    #define SAADC_REF_VBG_VOLTAGE     0.6
    /* @} */
    
    /* SAADC channel used to call conversion. */
    static nrf_saadc_channel_config_t saadc_channel = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);
    #endif //ADC_PRESENT
    #endif //USE_COMP
    
    #if USE_COMP
    /* Number of channels required by PPI. */
    #ifdef SIMPLEXITY_CHANGES
    #define PPI_REQUIRED_CHANNELS 5
    #else
    #define PPI_REQUIRED_CHANNELS 3
    #endif
    
    /* Array of PPI channels. */
    static nrf_ppi_channel_t m_ppi_channels[PPI_REQUIRED_CHANNELS];
    
    
    /**
     * @defgroup timer_instances Timer instances.
     * @{
     */
    static const nrf_drv_timer_t m_timer0 = NRF_DRV_TIMER_INSTANCE(TIMER0_FOR_CSENSE);
    static const nrf_drv_timer_t m_timer1 = NRF_DRV_TIMER_INSTANCE(TIMER1_FOR_CSENSE);
    #ifdef SIMPLEXITY_CHANGES
    static nrfx_gpiote_out_config_t m_gpiote = NRFX_GPIOTE_CONFIG_OUT_TASK_HIGH;
    #endif
    /* @} */
    #endif //USE_COMP
    
    /* Configuration of the capacitive sensor module. */
    typedef struct
    {
        volatile nrfx_drv_state_t       module_state;                       /**< State of the module. */
        nrf_drv_csense_event_handler_t  event_handler;                      /**< Event handler for capacitor sensor events. */
        uint16_t                        analog_values[MAX_ANALOG_INPUTS];   /**< Array containing analog values measured on the corresponding COMP/ADC channel. */
        volatile bool                   busy;                               /**< Indicates state of module - busy if there are ongoing conversions. */
        volatile uint8_t                cur_chann_idx;                      /**< Current channel to be read if enabled. */
        volatile uint8_t                adc_channels_input_mask;            /**< Enabled channels. */
        uint8_t                         output_pin;                         /**< Pin to generate signal charging capacitors. */
        uint8_t                         channels_to_read;                   /**< Mask of channels remaining to be read in the current measurement. */
        volatile bool                   timers_powered_on;                  /**< Flag to indicate if timers were already started. */
    }csense_t;
    
    static csense_t m_csense;
    
    /**
     * @brief Function for determining the next analog channel to be read.
     */
    __STATIC_INLINE void calculate_next_channel(void)
    {
        m_csense.cur_chann_idx = 31 - __CLZ(m_csense.channels_to_read);
    }
    
    /**
     * @brief Function for handling conversion values.
     *
     * @param[in] val                Value received from ADC or COMP.
     */
    static void conversion_handler(uint16_t val)
    {
        nrf_drv_csense_evt_t event_struct;
    
    #if USE_COMP == 0
        nrf_gpio_pin_set(m_csense.output_pin);
    #endif //USE_COMP
    
        m_csense.analog_values[m_csense.cur_chann_idx] = val;
    
        event_struct.read_value = val;
        event_struct.analog_channel = m_csense.cur_chann_idx;
    
        m_csense.channels_to_read &= ~(1UL<<m_csense.cur_chann_idx);
    
        // decide if there will be more conversions
        if (m_csense.channels_to_read == 0)
        {
            m_csense.busy = false;
    #if USE_COMP == 0 && defined(SAADC_PRESENT)
            nrf_saadc_disable();
    #endif
        }
    
        m_csense.event_handler(&event_struct);
    
        if (m_csense.channels_to_read > 0)     // Start new conversion.
        {
            ret_code_t err_code;
            calculate_next_channel();
            err_code = nrf_drv_csense_sample();
            if (err_code != NRF_SUCCESS)
            {
                return;
            }
        }
    }
    
    #if USE_COMP
    /**
     * @brief Timer0 interrupt handler.
     *
     * @param[in] event_type Timer event.
     * @param[in] p_context  General purpose parameter set during initialization of
     *                       the timer. This parameter can be used to pass
     *                       additional information to the handler function, for
     *                       example, the timer ID.
     */
    static void counter_compare_handler(nrf_timer_event_t event_type, void* p_context)
    {
        if (event_type == NRF_TIMER_EVENT_COMPARE0)
        {
            uint16_t val =  nrf_drv_timer_capture_get(&m_timer1, NRF_TIMER_CC_CHANNEL1);
            nrf_drv_timer_pause(&m_timer1);
            nrf_drv_timer_clear(&m_timer1);
    
            /* Handle finished measurement. */
            conversion_handler(val);
        }
    }
    
    /**
     * @brief Dummy handler.
     *
     * @param[in] event_type Timer event.
     * @param[in] p_context  General purpose parameter set during initialization of
     *                       the timer. This parameter can be used to pass
     *                       additional information to the handler function, for
     *                       example, the timer ID.
     */
    static void dummy_handler(nrf_timer_event_t event_type, void* p_context){}
    
    /**
     * @brief Function for initializing timers.
     *
     * @retval NRF_ERROR_INTERNAL            If there were error initializing timers.
     * @retval NRF_SUCCESS                   If timers were initialized successfully.
     */
    static ret_code_t timer_init(void)
    {
        ret_code_t err_code;
    
        //set first timer in timer mode to get period of relaxation oscillator
        nrf_drv_timer_config_t timer_config = NRF_DRV_TIMER_DEFAULT_CONFIG;
        timer_config.mode = NRF_TIMER_MODE_TIMER;
        err_code = nrf_drv_timer_init(&m_timer1, &timer_config, dummy_handler);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
    
        //set second timer in counter mode and generate event on tenth period
        timer_config.mode = NRF_TIMER_MODE_COUNTER;
        err_code = nrf_drv_timer_init(&m_timer0, &timer_config, counter_compare_handler);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
        nrf_drv_timer_extended_compare(&m_timer0, NRF_TIMER_CC_CHANNEL0, MEASUREMENT_PERIOD, (nrf_timer_short_mask_t)(NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK | NRF_TIMER_SHORT_COMPARE0_STOP_MASK), true);
    
        return NRF_SUCCESS;
    }
    
    /**
     * @brief Function for initializing and enabling PPI channels.
     *
     * @retval NRF_ERROR_INTERNAL            If there were error initializing or enabling PPI channels.
     * @retval NRF_SUCCESS                   If PPI channels were initialized and enabled successfully.
     */
    static ret_code_t ppi_init(void)
    {
        ret_code_t err_code;
        uint8_t i;
    
        err_code = nrf_drv_ppi_init();
        if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_MODULE_ALREADY_INITIALIZED))
        {
            return NRF_ERROR_INTERNAL;
        }
    
        for (i = 0; i < PPI_REQUIRED_CHANNELS ; i++)
        {
            err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channels[i]);
            if (NRF_SUCCESS != err_code)
            {
                return NRF_ERROR_INTERNAL;
            }
        }
    
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channels[0], nrf_drv_comp_event_address_get(NRF_COMP_EVENT_CROSS), nrf_drv_timer_task_address_get(&m_timer0, NRF_TIMER_TASK_COUNT));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channels[1], nrf_drv_timer_event_address_get(&m_timer0, NRF_TIMER_EVENT_COMPARE0), nrf_drv_timer_task_address_get(&m_timer1, NRF_TIMER_TASK_CAPTURE1));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
        err_code = nrf_drv_ppi_channel_fork_assign(m_ppi_channels[1], nrf_drv_comp_task_address_get(NRF_COMP_TASK_STOP));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channels[2], nrf_drv_comp_event_address_get(NRF_COMP_EVENT_READY), nrf_drv_timer_task_address_get(&m_timer0, NRF_TIMER_TASK_CLEAR));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
        err_code = nrf_drv_ppi_channel_fork_assign(m_ppi_channels[2], nrf_drv_timer_task_address_get(&m_timer1, NRF_TIMER_TASK_CLEAR));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
    
    #ifdef SIMPLEXITY_CHANGES
        // Simplexity added: To compensate for the failing current source,
        // we use the UP and DOWN events from the COMP block to reset/set a GPIO
        // in the GPIOTE block
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channels[3], nrf_drv_comp_event_address_get(NRF_COMP_EVENT_UP), nrfx_gpiote_clr_task_addr_get(NRF_CSENSE_OUTPUT_PIN));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
    
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channels[4], nrf_drv_comp_event_address_get(NRF_COMP_EVENT_DOWN), nrfx_gpiote_set_task_addr_get(NRF_CSENSE_OUTPUT_PIN));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
    #endif
    
        for (i = 0; i < PPI_REQUIRED_CHANNELS ; i++)
        {
            err_code = nrf_drv_ppi_channel_enable(m_ppi_channels[i]);
            if (NRF_SUCCESS != err_code)
            {
                return NRF_ERROR_INTERNAL;
            }
        }
    
        return NRF_SUCCESS;
    }
    
    /**
     * @brief Dummy handler for COMP events.
     *
     * @param[in] event         COMP event.
     */
    static void comp_event_handler(nrf_comp_event_t event){}
    
    /**
     * @brief Function for initializing COMP module in relaxation oscillator mode.
     *
     * @note The frequency of the oscillator depends on threshold voltages, current source and capacitance of pad and can be calculated as f_OSC = I_SOURCE / (2C·(VUP-VDOWN) ).
     *
     * @retval NRF_ERROR_INTERNAL                If there were error while initializing COMP driver.
     * @retval NRF_SUCCESS                       If the COMP driver initialization was successful.
     */
    static ret_code_t comp_init(void)
    {
        ret_code_t err_code;
        nrf_drv_comp_config_t m_comp_config = NRF_DRV_COMP_DEFAULT_CONFIG(NRF_COMP_INPUT_0);
    
        /* Workaround for Errata 12 "COMP: Reference ladder is not correctly calibrated" found at the Errata document
           for your device located at https://infocenter.nordicsemi.com/ */
        *(volatile uint32_t *)0x40013540 = (*(volatile uint32_t *)0x10000324 & 0x00001F00) >> 8;
    
    #ifdef SIMPLEXITY_CHANGES
        // since the current source isn't working, we use thresholds relative to external reference
        // This is simply 1/3 and 2/3 of VDD respectively
        m_comp_config.threshold.th_down = NRFX_VOLTAGE_THRESHOLD_TO_INT(1.0,3.0);
        m_comp_config.threshold.th_up   = NRFX_VOLTAGE_THRESHOLD_TO_INT(2.0,3.0);
    #else
        m_comp_config.isource = NRF_COMP_ISOURCE_Ien10uA;
    #endif
    
        err_code = nrf_drv_comp_init(&m_comp_config, comp_event_handler);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
    
        return NRF_SUCCESS;
    }
    
    #ifdef SIMPLEXITY_CHANGES
    /**
     * @brief Function for initializing GPIOTE module in relaxation oscillator mode.
     *
     *
     * @retval NRF_ERROR_INTERNAL                If there were error while initializing COMP driver.
     * @retval NRF_SUCCESS                       If the COMP driver initialization was successful.
     */
    static ret_code_t gpiote_init(void)
    {
        ret_code_t err_code;
    
        // initialize basic block
        err_code = nrfx_gpiote_init();
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
        
        // configure task for low to high if OUT[] called, SET[] and CLR[] work as usual
        err_code = nrfx_gpiote_out_init(NRF_CSENSE_OUTPUT_PIN, &m_gpiote);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
    
        nrfx_gpiote_out_task_enable(NRF_CSENSE_OUTPUT_PIN);
    
        return NRF_SUCCESS;
    }
    #endif //SIMPLEXITY_CHANGES
    #endif //USE_COMP
    
    #if USE_COMP == 0
    #ifdef ADC_PRESENT
    /**
     * @brief ADC handler.
     *
     * @param[in] p_event                Pointer to analog-to-digital converter driver event.
     */
    void adc_handler(nrfx_adc_evt_t const * p_event)
    {
        nrf_gpio_pin_set(m_csense.output_pin);
        uint16_t val;
        val = (uint16_t)(p_event->data.sample.sample *
                         ADC_REF_VBG_VOLTAGE * 1000 *
                         ADC_INPUT_PRESCALER / ADC_RES_10BIT);
        conversion_handler(val);
    }
    
    /**
     * @brief Function for initializing ADC.
     */
    static ret_code_t adc_init(void)
    {
        ret_code_t err_code;
    
        adc_channel.config.config.input = NRF_ADC_CONFIG_SCALING_INPUT_ONE_THIRD;
    
        nrfx_adc_config_t const adc_config = NRFX_ADC_DEFAULT_CONFIG;
        err_code = nrfx_adc_init(&adc_config, adc_handler);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
    
        nrf_gpio_pin_set(m_csense.output_pin);
    
        return NRF_SUCCESS;
    }
    #elif defined(SAADC_PRESENT)
    /**
     * @brief SAADC handler.
     *
     * @param[in] p_event                Pointer to analog-to-digital converter driver event.
     */
    void saadc_handler(nrf_drv_saadc_evt_t const * p_event)
    {
        nrf_gpio_pin_set(m_csense.output_pin);
        uint16_t val;
        (void)nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, 1);
        val = (uint16_t)(*p_event->data.done.p_buffer *
                          SAADC_REF_VBG_VOLTAGE * 1000 *
                          SAADC_INPUT_PRESCALER / SAADC_RES_10BIT);
        conversion_handler(val);
    }
    
    /**
     * @brief Function for initializing SAADC.
     */
    static ret_code_t saadc_init(void)
    {
        ret_code_t err_code;
        static nrf_saadc_value_t saadc_value;
    
        saadc_channel.gain = NRF_SAADC_GAIN1_3;
    
       err_code = nrf_drv_saadc_init(NULL, saadc_handler);
       if (err_code != NRF_SUCCESS)
       {
           return NRF_ERROR_INTERNAL;
       }
    
        nrf_gpio_pin_set(m_csense.output_pin);
    
        err_code = nrf_drv_saadc_channel_init(0, &saadc_channel);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
    
        err_code = nrf_drv_saadc_buffer_convert(&saadc_value, 1);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
    
        nrf_saadc_disable();
    
        return NRF_SUCCESS;
    }
    #endif //ADC_PRESENT
    #endif //USE_COMP
    
    ret_code_t nrf_drv_csense_init(nrf_drv_csense_config_t const * p_config, nrf_drv_csense_event_handler_t event_handler)
    {
        ASSERT(m_csense.module_state == NRFX_DRV_STATE_UNINITIALIZED);
        ASSERT(p_config->output_pin <= NUMBER_OF_PINS);
    
        ret_code_t err_code;
    
        if (p_config == NULL)
        {
            return NRF_ERROR_INVALID_PARAM;
        }
    
        if (event_handler == NULL)
        {
            return NRF_ERROR_INVALID_PARAM;
        }
    
        m_csense.busy = false;
    
    #if USE_COMP == 0
        m_csense.output_pin = p_config->output_pin;
        nrf_gpio_cfg_output(m_csense.output_pin);
        nrf_gpio_pin_set(m_csense.output_pin);
    #endif //COMP_PRESENT
    
        m_csense.event_handler = event_handler;
    
    #if USE_COMP
    
    #ifdef SIMPLEXITY_CHANGES
        err_code = gpiote_init();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    #endif
    
        err_code = comp_init();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
        err_code = timer_init();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
        err_code = ppi_init();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    #else
    #ifdef ADC_PRESENT
        err_code = adc_init();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    #elif defined(SAADC_PRESENT)
        err_code = saadc_init();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    #endif //ADC_PRESENT
    #endif //USE_COMP
    
        m_csense.module_state = NRFX_DRV_STATE_INITIALIZED;
    
        return NRF_SUCCESS;
    }
    
    ret_code_t nrf_drv_csense_uninit(void)
    {
        ASSERT(m_csense.module_state != NRFX_DRV_STATE_UNINITIALIZED);
    
        nrf_drv_csense_channels_disable(0xFF);
    
    #if USE_COMP
        ret_code_t err_code;
        uint8_t i;
    
        nrf_drv_timer_uninit(&m_timer0);
        nrf_drv_timer_uninit(&m_timer1);
        nrf_drv_comp_uninit();
        for (i =0; i < 3; i++)
        {
            err_code = nrf_drv_ppi_channel_free(m_ppi_channels[i]);
            if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
        }
        err_code = nrf_drv_ppi_uninit();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    #else
    #ifdef ADC_PRESENT
        nrfx_adc_uninit();
    #elif defined(SAADC_PRESENT)
        nrf_drv_saadc_uninit();
    #endif //ADC_PRESENT
    #endif //USE_COMP
    
        m_csense.module_state = NRFX_DRV_STATE_UNINITIALIZED;
    
        memset((void*)&m_csense, 0, sizeof(m_csense));
    
        return NRF_SUCCESS;
    }
    
    void nrf_drv_csense_channels_enable(uint8_t channels_mask)
    {
        ASSERT(m_csense.module_state != NRFX_DRV_STATE_UNINITIALIZED);
    
        m_csense.busy = true;
    
        m_csense.module_state = NRFX_DRV_STATE_POWERED_ON;
    
        m_csense.adc_channels_input_mask |= channels_mask;
    
        m_csense.busy = false;
    }
    
    void nrf_drv_csense_channels_disable(uint8_t channels_mask)
    {
        ASSERT(m_csense.module_state == NRFX_DRV_STATE_POWERED_ON);
    
        m_csense.adc_channels_input_mask &= ~channels_mask;
    
        if (m_csense.adc_channels_input_mask == 0)
        {
            m_csense.module_state = NRFX_DRV_STATE_INITIALIZED;
        }
    }
    
    uint16_t nrf_drv_csense_channel_read(uint8_t csense_channel)
    {
        return m_csense.analog_values[csense_channel];
    }
    
    ret_code_t nrf_drv_csense_sample(void)
    {
        ASSERT(m_csense.module_state == NRFX_DRV_STATE_POWERED_ON);
    
        if (m_csense.adc_channels_input_mask != 0)
        {
            if (m_csense.channels_to_read == 0)
            {
    #if USE_COMP == 0 && defined(SAADC_PRESENT)
                nrf_saadc_enable();
    #endif
                if (nrf_drv_csense_is_busy() == true)
                {
                    return NRF_ERROR_BUSY;
                }
                m_csense.busy = true;
                m_csense.channels_to_read = m_csense.adc_channels_input_mask;
                calculate_next_channel();
            }
    
    #if USE_COMP
            if (!m_csense.timers_powered_on)
            {
                nrf_drv_timer_enable(&m_timer0);
                nrf_drv_timer_enable(&m_timer1);
                m_csense.timers_powered_on = true;
            }
            else
            {
                nrf_drv_timer_resume(&m_timer0);
                nrf_drv_timer_resume(&m_timer1);
            }
            nrf_drv_comp_pin_select((nrf_comp_input_t)m_csense.cur_chann_idx);
    #ifdef SIMPLEXITY_CHANGES
            // we need the COMP events to be enabled when starting
            nrf_drv_comp_start(NRFX_COMP_EVT_EN_CROSS_MASK |
                               NRFX_COMP_EVT_EN_UP_MASK |
                               NRFX_COMP_EVT_EN_DOWN_MASK,
                               0);
            // toggle GPIO high to kick circuit into operation
            // Note, that since we have GPIO tasks enabled for this pin, setting the
            // raw output register is ignored, we need to use the task
            //
            if(nrfx_comp_sample()) // currently high, so set low
            {
                nrfx_gpiote_clr_task_trigger(NRF_CSENSE_OUTPUT_PIN);
            }
            else // currently low, so set high
            {
                nrfx_gpiote_set_task_trigger(NRF_CSENSE_OUTPUT_PIN);
            }
    #else
            nrf_drv_comp_start(0, 0);
    #endif
            
    #else
            ret_code_t err_code;
    #ifdef ADC_PRESENT
            adc_channel.config.config.ain = (nrf_adc_config_input_t)(1<<m_csense.cur_chann_idx);
            nrf_gpio_pin_clear(m_csense.output_pin);
            err_code = nrfx_adc_sample_convert(&adc_channel, NULL);
    #elif defined(SAADC_PRESENT)
            saadc_channel.pin_p = (nrf_saadc_input_t)(m_csense.cur_chann_idx + 1);
            nrf_saadc_channel_input_set(0, saadc_channel.pin_p, NRF_SAADC_INPUT_DISABLED);
            nrf_gpio_pin_clear(m_csense.output_pin);
            err_code = nrf_drv_saadc_sample();
    #endif //ADC_PRESENT
            if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
    #endif //USE_COMP
        }
    
        return NRF_SUCCESS;
    }
    
    bool nrf_drv_csense_is_busy(void)
    {
        return m_csense.busy;
    }
    #endif //NRF_MODULE_ENABLED(NRF_DRV_CSENSE)
    

Reply
  • I work with Brian at Simplexity and we were able to get the capacitive touch button working with a second GPIO that stimulates the pad through a 1M resistor.  This leverages the existing csense driver.  I've attached source code here, in case anyone else wants to leverage it.

    /**
     * Copyright (c) 2016 - 2019, Nordic Semiconductor ASA
     *
     * All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without modification,
     * are permitted provided that the following conditions are met:
     *
     * 1. Redistributions of source code must retain the above copyright notice, this
     *    list of conditions and the following disclaimer.
     *
     * 2. Redistributions in binary form, except as embedded into a Nordic
     *    Semiconductor ASA integrated circuit in a product or a software update for
     *    such product, 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.
     *
     * 3. Neither the name of Nordic Semiconductor ASA nor the names of its
     *    contributors may be used to endorse or promote products derived from this
     *    software without specific prior written permission.
     *
     * 4. This software, with or without modification, must only be used with a
     *    Nordic Semiconductor ASA integrated circuit.
     *
     * 5. Any software provided in binary form under this license must not be reverse
     *    engineered, decompiled, modified and/or disassembled.
     *
     * THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
     * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
     * DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 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.
     *
     */
    
    // When we update the SDK, we'll need to add these changes to
    // get the cap sensor code working since the embedded current
    // source is broken.  TODO: trim down this complex code and
    // put it in the main code branch so we don't have to update
    // vendor-supplied files
    #define SIMPLEXITY_CHANGES
    
    #include "sdk_common.h"
    #if NRF_MODULE_ENABLED(NRF_DRV_CSENSE)
    #include "nrf_drv_csense.h"
    #include "nrf_peripherals.h"
    #include "nrf_gpio.h"
    #include "app_error.h"
    #include "app_util_platform.h"
    #include "nrf_assert.h"
    #include "string.h"
    #include <stdio.h>
    
    #if defined(__CORTEX_M) && (__CORTEX_M < 4)
    #ifndef ARM_MATH_CM0PLUS
    #define ARM_MATH_CM0PLUS
    #endif
    /*lint -save -e689 */
    #include "arm_math.h"
    /*lint -restore */
    #endif
    
    #if USE_COMP
    #include "nrf_drv_comp.h"
    #include "nrf_drv_ppi.h"
    #include "nrf_drv_timer.h"
    #ifdef SIMPLEXITY_CHANGES
    #include "nrfx_gpiote.h"
    #endif
    #endif //USE_COMP
    
    #if USE_COMP == 0
    #ifdef ADC_PRESENT
    #include "nrfx_adc.h"
    
    /**
     * @defgroup adc_defines ADC defines to count input voltage.
     * @{
     */
    #define ADC_RES_10BIT           1024
    #define ADC_INPUT_PRESCALER     3
    #define ADC_REF_VBG_VOLTAGE     1.2
    /* @} */
    
    /* ADC channel used to call conversion. */
    static nrfx_adc_channel_t adc_channel = NRFX_ADC_DEFAULT_CHANNEL(NRF_ADC_CONFIG_INPUT_0);
    #elif defined(SAADC_PRESENT)
    #include "nrf_drv_saadc.h"
    
    /**
     * @defgroup saadc_defines SAADC defines to count input voltage.
     * @{
     */
    #define SAADC_RES_10BIT           1024
    #define SAADC_INPUT_PRESCALER     3
    #define SAADC_REF_VBG_VOLTAGE     0.6
    /* @} */
    
    /* SAADC channel used to call conversion. */
    static nrf_saadc_channel_config_t saadc_channel = NRF_DRV_SAADC_DEFAULT_CHANNEL_CONFIG_SE(NRF_SAADC_INPUT_AIN0);
    #endif //ADC_PRESENT
    #endif //USE_COMP
    
    #if USE_COMP
    /* Number of channels required by PPI. */
    #ifdef SIMPLEXITY_CHANGES
    #define PPI_REQUIRED_CHANNELS 5
    #else
    #define PPI_REQUIRED_CHANNELS 3
    #endif
    
    /* Array of PPI channels. */
    static nrf_ppi_channel_t m_ppi_channels[PPI_REQUIRED_CHANNELS];
    
    
    /**
     * @defgroup timer_instances Timer instances.
     * @{
     */
    static const nrf_drv_timer_t m_timer0 = NRF_DRV_TIMER_INSTANCE(TIMER0_FOR_CSENSE);
    static const nrf_drv_timer_t m_timer1 = NRF_DRV_TIMER_INSTANCE(TIMER1_FOR_CSENSE);
    #ifdef SIMPLEXITY_CHANGES
    static nrfx_gpiote_out_config_t m_gpiote = NRFX_GPIOTE_CONFIG_OUT_TASK_HIGH;
    #endif
    /* @} */
    #endif //USE_COMP
    
    /* Configuration of the capacitive sensor module. */
    typedef struct
    {
        volatile nrfx_drv_state_t       module_state;                       /**< State of the module. */
        nrf_drv_csense_event_handler_t  event_handler;                      /**< Event handler for capacitor sensor events. */
        uint16_t                        analog_values[MAX_ANALOG_INPUTS];   /**< Array containing analog values measured on the corresponding COMP/ADC channel. */
        volatile bool                   busy;                               /**< Indicates state of module - busy if there are ongoing conversions. */
        volatile uint8_t                cur_chann_idx;                      /**< Current channel to be read if enabled. */
        volatile uint8_t                adc_channels_input_mask;            /**< Enabled channels. */
        uint8_t                         output_pin;                         /**< Pin to generate signal charging capacitors. */
        uint8_t                         channels_to_read;                   /**< Mask of channels remaining to be read in the current measurement. */
        volatile bool                   timers_powered_on;                  /**< Flag to indicate if timers were already started. */
    }csense_t;
    
    static csense_t m_csense;
    
    /**
     * @brief Function for determining the next analog channel to be read.
     */
    __STATIC_INLINE void calculate_next_channel(void)
    {
        m_csense.cur_chann_idx = 31 - __CLZ(m_csense.channels_to_read);
    }
    
    /**
     * @brief Function for handling conversion values.
     *
     * @param[in] val                Value received from ADC or COMP.
     */
    static void conversion_handler(uint16_t val)
    {
        nrf_drv_csense_evt_t event_struct;
    
    #if USE_COMP == 0
        nrf_gpio_pin_set(m_csense.output_pin);
    #endif //USE_COMP
    
        m_csense.analog_values[m_csense.cur_chann_idx] = val;
    
        event_struct.read_value = val;
        event_struct.analog_channel = m_csense.cur_chann_idx;
    
        m_csense.channels_to_read &= ~(1UL<<m_csense.cur_chann_idx);
    
        // decide if there will be more conversions
        if (m_csense.channels_to_read == 0)
        {
            m_csense.busy = false;
    #if USE_COMP == 0 && defined(SAADC_PRESENT)
            nrf_saadc_disable();
    #endif
        }
    
        m_csense.event_handler(&event_struct);
    
        if (m_csense.channels_to_read > 0)     // Start new conversion.
        {
            ret_code_t err_code;
            calculate_next_channel();
            err_code = nrf_drv_csense_sample();
            if (err_code != NRF_SUCCESS)
            {
                return;
            }
        }
    }
    
    #if USE_COMP
    /**
     * @brief Timer0 interrupt handler.
     *
     * @param[in] event_type Timer event.
     * @param[in] p_context  General purpose parameter set during initialization of
     *                       the timer. This parameter can be used to pass
     *                       additional information to the handler function, for
     *                       example, the timer ID.
     */
    static void counter_compare_handler(nrf_timer_event_t event_type, void* p_context)
    {
        if (event_type == NRF_TIMER_EVENT_COMPARE0)
        {
            uint16_t val =  nrf_drv_timer_capture_get(&m_timer1, NRF_TIMER_CC_CHANNEL1);
            nrf_drv_timer_pause(&m_timer1);
            nrf_drv_timer_clear(&m_timer1);
    
            /* Handle finished measurement. */
            conversion_handler(val);
        }
    }
    
    /**
     * @brief Dummy handler.
     *
     * @param[in] event_type Timer event.
     * @param[in] p_context  General purpose parameter set during initialization of
     *                       the timer. This parameter can be used to pass
     *                       additional information to the handler function, for
     *                       example, the timer ID.
     */
    static void dummy_handler(nrf_timer_event_t event_type, void* p_context){}
    
    /**
     * @brief Function for initializing timers.
     *
     * @retval NRF_ERROR_INTERNAL            If there were error initializing timers.
     * @retval NRF_SUCCESS                   If timers were initialized successfully.
     */
    static ret_code_t timer_init(void)
    {
        ret_code_t err_code;
    
        //set first timer in timer mode to get period of relaxation oscillator
        nrf_drv_timer_config_t timer_config = NRF_DRV_TIMER_DEFAULT_CONFIG;
        timer_config.mode = NRF_TIMER_MODE_TIMER;
        err_code = nrf_drv_timer_init(&m_timer1, &timer_config, dummy_handler);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
    
        //set second timer in counter mode and generate event on tenth period
        timer_config.mode = NRF_TIMER_MODE_COUNTER;
        err_code = nrf_drv_timer_init(&m_timer0, &timer_config, counter_compare_handler);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
        nrf_drv_timer_extended_compare(&m_timer0, NRF_TIMER_CC_CHANNEL0, MEASUREMENT_PERIOD, (nrf_timer_short_mask_t)(NRF_TIMER_SHORT_COMPARE0_CLEAR_MASK | NRF_TIMER_SHORT_COMPARE0_STOP_MASK), true);
    
        return NRF_SUCCESS;
    }
    
    /**
     * @brief Function for initializing and enabling PPI channels.
     *
     * @retval NRF_ERROR_INTERNAL            If there were error initializing or enabling PPI channels.
     * @retval NRF_SUCCESS                   If PPI channels were initialized and enabled successfully.
     */
    static ret_code_t ppi_init(void)
    {
        ret_code_t err_code;
        uint8_t i;
    
        err_code = nrf_drv_ppi_init();
        if ((err_code != NRF_SUCCESS) && (err_code != NRF_ERROR_MODULE_ALREADY_INITIALIZED))
        {
            return NRF_ERROR_INTERNAL;
        }
    
        for (i = 0; i < PPI_REQUIRED_CHANNELS ; i++)
        {
            err_code = nrf_drv_ppi_channel_alloc(&m_ppi_channels[i]);
            if (NRF_SUCCESS != err_code)
            {
                return NRF_ERROR_INTERNAL;
            }
        }
    
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channels[0], nrf_drv_comp_event_address_get(NRF_COMP_EVENT_CROSS), nrf_drv_timer_task_address_get(&m_timer0, NRF_TIMER_TASK_COUNT));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channels[1], nrf_drv_timer_event_address_get(&m_timer0, NRF_TIMER_EVENT_COMPARE0), nrf_drv_timer_task_address_get(&m_timer1, NRF_TIMER_TASK_CAPTURE1));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
        err_code = nrf_drv_ppi_channel_fork_assign(m_ppi_channels[1], nrf_drv_comp_task_address_get(NRF_COMP_TASK_STOP));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channels[2], nrf_drv_comp_event_address_get(NRF_COMP_EVENT_READY), nrf_drv_timer_task_address_get(&m_timer0, NRF_TIMER_TASK_CLEAR));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
        err_code = nrf_drv_ppi_channel_fork_assign(m_ppi_channels[2], nrf_drv_timer_task_address_get(&m_timer1, NRF_TIMER_TASK_CLEAR));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
    
    #ifdef SIMPLEXITY_CHANGES
        // Simplexity added: To compensate for the failing current source,
        // we use the UP and DOWN events from the COMP block to reset/set a GPIO
        // in the GPIOTE block
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channels[3], nrf_drv_comp_event_address_get(NRF_COMP_EVENT_UP), nrfx_gpiote_clr_task_addr_get(NRF_CSENSE_OUTPUT_PIN));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
    
        err_code = nrf_drv_ppi_channel_assign(m_ppi_channels[4], nrf_drv_comp_event_address_get(NRF_COMP_EVENT_DOWN), nrfx_gpiote_set_task_addr_get(NRF_CSENSE_OUTPUT_PIN));
        if (NRF_SUCCESS != err_code)
        {
           return NRF_ERROR_INTERNAL;
        }
    #endif
    
        for (i = 0; i < PPI_REQUIRED_CHANNELS ; i++)
        {
            err_code = nrf_drv_ppi_channel_enable(m_ppi_channels[i]);
            if (NRF_SUCCESS != err_code)
            {
                return NRF_ERROR_INTERNAL;
            }
        }
    
        return NRF_SUCCESS;
    }
    
    /**
     * @brief Dummy handler for COMP events.
     *
     * @param[in] event         COMP event.
     */
    static void comp_event_handler(nrf_comp_event_t event){}
    
    /**
     * @brief Function for initializing COMP module in relaxation oscillator mode.
     *
     * @note The frequency of the oscillator depends on threshold voltages, current source and capacitance of pad and can be calculated as f_OSC = I_SOURCE / (2C·(VUP-VDOWN) ).
     *
     * @retval NRF_ERROR_INTERNAL                If there were error while initializing COMP driver.
     * @retval NRF_SUCCESS                       If the COMP driver initialization was successful.
     */
    static ret_code_t comp_init(void)
    {
        ret_code_t err_code;
        nrf_drv_comp_config_t m_comp_config = NRF_DRV_COMP_DEFAULT_CONFIG(NRF_COMP_INPUT_0);
    
        /* Workaround for Errata 12 "COMP: Reference ladder is not correctly calibrated" found at the Errata document
           for your device located at https://infocenter.nordicsemi.com/ */
        *(volatile uint32_t *)0x40013540 = (*(volatile uint32_t *)0x10000324 & 0x00001F00) >> 8;
    
    #ifdef SIMPLEXITY_CHANGES
        // since the current source isn't working, we use thresholds relative to external reference
        // This is simply 1/3 and 2/3 of VDD respectively
        m_comp_config.threshold.th_down = NRFX_VOLTAGE_THRESHOLD_TO_INT(1.0,3.0);
        m_comp_config.threshold.th_up   = NRFX_VOLTAGE_THRESHOLD_TO_INT(2.0,3.0);
    #else
        m_comp_config.isource = NRF_COMP_ISOURCE_Ien10uA;
    #endif
    
        err_code = nrf_drv_comp_init(&m_comp_config, comp_event_handler);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
    
        return NRF_SUCCESS;
    }
    
    #ifdef SIMPLEXITY_CHANGES
    /**
     * @brief Function for initializing GPIOTE module in relaxation oscillator mode.
     *
     *
     * @retval NRF_ERROR_INTERNAL                If there were error while initializing COMP driver.
     * @retval NRF_SUCCESS                       If the COMP driver initialization was successful.
     */
    static ret_code_t gpiote_init(void)
    {
        ret_code_t err_code;
    
        // initialize basic block
        err_code = nrfx_gpiote_init();
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
        
        // configure task for low to high if OUT[] called, SET[] and CLR[] work as usual
        err_code = nrfx_gpiote_out_init(NRF_CSENSE_OUTPUT_PIN, &m_gpiote);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
    
        nrfx_gpiote_out_task_enable(NRF_CSENSE_OUTPUT_PIN);
    
        return NRF_SUCCESS;
    }
    #endif //SIMPLEXITY_CHANGES
    #endif //USE_COMP
    
    #if USE_COMP == 0
    #ifdef ADC_PRESENT
    /**
     * @brief ADC handler.
     *
     * @param[in] p_event                Pointer to analog-to-digital converter driver event.
     */
    void adc_handler(nrfx_adc_evt_t const * p_event)
    {
        nrf_gpio_pin_set(m_csense.output_pin);
        uint16_t val;
        val = (uint16_t)(p_event->data.sample.sample *
                         ADC_REF_VBG_VOLTAGE * 1000 *
                         ADC_INPUT_PRESCALER / ADC_RES_10BIT);
        conversion_handler(val);
    }
    
    /**
     * @brief Function for initializing ADC.
     */
    static ret_code_t adc_init(void)
    {
        ret_code_t err_code;
    
        adc_channel.config.config.input = NRF_ADC_CONFIG_SCALING_INPUT_ONE_THIRD;
    
        nrfx_adc_config_t const adc_config = NRFX_ADC_DEFAULT_CONFIG;
        err_code = nrfx_adc_init(&adc_config, adc_handler);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
    
        nrf_gpio_pin_set(m_csense.output_pin);
    
        return NRF_SUCCESS;
    }
    #elif defined(SAADC_PRESENT)
    /**
     * @brief SAADC handler.
     *
     * @param[in] p_event                Pointer to analog-to-digital converter driver event.
     */
    void saadc_handler(nrf_drv_saadc_evt_t const * p_event)
    {
        nrf_gpio_pin_set(m_csense.output_pin);
        uint16_t val;
        (void)nrf_drv_saadc_buffer_convert(p_event->data.done.p_buffer, 1);
        val = (uint16_t)(*p_event->data.done.p_buffer *
                          SAADC_REF_VBG_VOLTAGE * 1000 *
                          SAADC_INPUT_PRESCALER / SAADC_RES_10BIT);
        conversion_handler(val);
    }
    
    /**
     * @brief Function for initializing SAADC.
     */
    static ret_code_t saadc_init(void)
    {
        ret_code_t err_code;
        static nrf_saadc_value_t saadc_value;
    
        saadc_channel.gain = NRF_SAADC_GAIN1_3;
    
       err_code = nrf_drv_saadc_init(NULL, saadc_handler);
       if (err_code != NRF_SUCCESS)
       {
           return NRF_ERROR_INTERNAL;
       }
    
        nrf_gpio_pin_set(m_csense.output_pin);
    
        err_code = nrf_drv_saadc_channel_init(0, &saadc_channel);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
    
        err_code = nrf_drv_saadc_buffer_convert(&saadc_value, 1);
        if (err_code != NRF_SUCCESS)
        {
            return NRF_ERROR_INTERNAL;
        }
    
        nrf_saadc_disable();
    
        return NRF_SUCCESS;
    }
    #endif //ADC_PRESENT
    #endif //USE_COMP
    
    ret_code_t nrf_drv_csense_init(nrf_drv_csense_config_t const * p_config, nrf_drv_csense_event_handler_t event_handler)
    {
        ASSERT(m_csense.module_state == NRFX_DRV_STATE_UNINITIALIZED);
        ASSERT(p_config->output_pin <= NUMBER_OF_PINS);
    
        ret_code_t err_code;
    
        if (p_config == NULL)
        {
            return NRF_ERROR_INVALID_PARAM;
        }
    
        if (event_handler == NULL)
        {
            return NRF_ERROR_INVALID_PARAM;
        }
    
        m_csense.busy = false;
    
    #if USE_COMP == 0
        m_csense.output_pin = p_config->output_pin;
        nrf_gpio_cfg_output(m_csense.output_pin);
        nrf_gpio_pin_set(m_csense.output_pin);
    #endif //COMP_PRESENT
    
        m_csense.event_handler = event_handler;
    
    #if USE_COMP
    
    #ifdef SIMPLEXITY_CHANGES
        err_code = gpiote_init();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    #endif
    
        err_code = comp_init();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
        err_code = timer_init();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
        err_code = ppi_init();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    #else
    #ifdef ADC_PRESENT
        err_code = adc_init();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    #elif defined(SAADC_PRESENT)
        err_code = saadc_init();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    #endif //ADC_PRESENT
    #endif //USE_COMP
    
        m_csense.module_state = NRFX_DRV_STATE_INITIALIZED;
    
        return NRF_SUCCESS;
    }
    
    ret_code_t nrf_drv_csense_uninit(void)
    {
        ASSERT(m_csense.module_state != NRFX_DRV_STATE_UNINITIALIZED);
    
        nrf_drv_csense_channels_disable(0xFF);
    
    #if USE_COMP
        ret_code_t err_code;
        uint8_t i;
    
        nrf_drv_timer_uninit(&m_timer0);
        nrf_drv_timer_uninit(&m_timer1);
        nrf_drv_comp_uninit();
        for (i =0; i < 3; i++)
        {
            err_code = nrf_drv_ppi_channel_free(m_ppi_channels[i]);
            if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
        }
        err_code = nrf_drv_ppi_uninit();
        if (err_code != NRF_SUCCESS)
        {
            return err_code;
        }
    #else
    #ifdef ADC_PRESENT
        nrfx_adc_uninit();
    #elif defined(SAADC_PRESENT)
        nrf_drv_saadc_uninit();
    #endif //ADC_PRESENT
    #endif //USE_COMP
    
        m_csense.module_state = NRFX_DRV_STATE_UNINITIALIZED;
    
        memset((void*)&m_csense, 0, sizeof(m_csense));
    
        return NRF_SUCCESS;
    }
    
    void nrf_drv_csense_channels_enable(uint8_t channels_mask)
    {
        ASSERT(m_csense.module_state != NRFX_DRV_STATE_UNINITIALIZED);
    
        m_csense.busy = true;
    
        m_csense.module_state = NRFX_DRV_STATE_POWERED_ON;
    
        m_csense.adc_channels_input_mask |= channels_mask;
    
        m_csense.busy = false;
    }
    
    void nrf_drv_csense_channels_disable(uint8_t channels_mask)
    {
        ASSERT(m_csense.module_state == NRFX_DRV_STATE_POWERED_ON);
    
        m_csense.adc_channels_input_mask &= ~channels_mask;
    
        if (m_csense.adc_channels_input_mask == 0)
        {
            m_csense.module_state = NRFX_DRV_STATE_INITIALIZED;
        }
    }
    
    uint16_t nrf_drv_csense_channel_read(uint8_t csense_channel)
    {
        return m_csense.analog_values[csense_channel];
    }
    
    ret_code_t nrf_drv_csense_sample(void)
    {
        ASSERT(m_csense.module_state == NRFX_DRV_STATE_POWERED_ON);
    
        if (m_csense.adc_channels_input_mask != 0)
        {
            if (m_csense.channels_to_read == 0)
            {
    #if USE_COMP == 0 && defined(SAADC_PRESENT)
                nrf_saadc_enable();
    #endif
                if (nrf_drv_csense_is_busy() == true)
                {
                    return NRF_ERROR_BUSY;
                }
                m_csense.busy = true;
                m_csense.channels_to_read = m_csense.adc_channels_input_mask;
                calculate_next_channel();
            }
    
    #if USE_COMP
            if (!m_csense.timers_powered_on)
            {
                nrf_drv_timer_enable(&m_timer0);
                nrf_drv_timer_enable(&m_timer1);
                m_csense.timers_powered_on = true;
            }
            else
            {
                nrf_drv_timer_resume(&m_timer0);
                nrf_drv_timer_resume(&m_timer1);
            }
            nrf_drv_comp_pin_select((nrf_comp_input_t)m_csense.cur_chann_idx);
    #ifdef SIMPLEXITY_CHANGES
            // we need the COMP events to be enabled when starting
            nrf_drv_comp_start(NRFX_COMP_EVT_EN_CROSS_MASK |
                               NRFX_COMP_EVT_EN_UP_MASK |
                               NRFX_COMP_EVT_EN_DOWN_MASK,
                               0);
            // toggle GPIO high to kick circuit into operation
            // Note, that since we have GPIO tasks enabled for this pin, setting the
            // raw output register is ignored, we need to use the task
            //
            if(nrfx_comp_sample()) // currently high, so set low
            {
                nrfx_gpiote_clr_task_trigger(NRF_CSENSE_OUTPUT_PIN);
            }
            else // currently low, so set high
            {
                nrfx_gpiote_set_task_trigger(NRF_CSENSE_OUTPUT_PIN);
            }
    #else
            nrf_drv_comp_start(0, 0);
    #endif
            
    #else
            ret_code_t err_code;
    #ifdef ADC_PRESENT
            adc_channel.config.config.ain = (nrf_adc_config_input_t)(1<<m_csense.cur_chann_idx);
            nrf_gpio_pin_clear(m_csense.output_pin);
            err_code = nrfx_adc_sample_convert(&adc_channel, NULL);
    #elif defined(SAADC_PRESENT)
            saadc_channel.pin_p = (nrf_saadc_input_t)(m_csense.cur_chann_idx + 1);
            nrf_saadc_channel_input_set(0, saadc_channel.pin_p, NRF_SAADC_INPUT_DISABLED);
            nrf_gpio_pin_clear(m_csense.output_pin);
            err_code = nrf_drv_saadc_sample();
    #endif //ADC_PRESENT
            if (err_code != NRF_SUCCESS)
            {
                return err_code;
            }
    #endif //USE_COMP
        }
    
        return NRF_SUCCESS;
    }
    
    bool nrf_drv_csense_is_busy(void)
    {
        return m_csense.busy;
    }
    #endif //NRF_MODULE_ENABLED(NRF_DRV_CSENSE)
    

Children
No Data
Related