Some checks failed
Basic Checks / license-check (push) Has been cancelled
Basic Checks / include-check (push) Has been cancelled
Basic Checks / style-check (push) Has been cancelled
Basic Checks / docs-check (push) Has been cancelled
Basic Checks / python-tests (push) Has been cancelled
Basic Checks / pin-validation (push) Has been cancelled
Basic Checks / cmake-checks (push) Has been cancelled
Basic Checks / frozen-tools-check (push) Has been cancelled
Publish or Update docker image for head of branch / prepare-tags (push) Has been cancelled
Release or update docker image for a released mbed-os version / prepare-tags (push) Has been cancelled
Publish or Update docker image for head of branch / build-container (push) Has been cancelled
Publish or Update docker image for head of branch / test-container (linux/amd64) (push) Has been cancelled
Publish or Update docker image for head of branch / test-container (linux/arm64) (push) Has been cancelled
Publish or Update docker image for head of branch / deploy-container (push) Has been cancelled
Release or update docker image for a released mbed-os version / build-container (push) Has been cancelled
Release or update docker image for a released mbed-os version / test-container (linux/amd64) (push) Has been cancelled
Release or update docker image for a released mbed-os version / test-container (linux/arm64) (push) Has been cancelled
Release or update docker image for a released mbed-os version / deploy-container (push) Has been cancelled
Prune temporary docker images / prune-images (push) Has been cancelled
1736 lines
62 KiB
C
1736 lines
62 KiB
C
/***************************************************************************/ /**
|
|
* \file cyhal_adc.c
|
|
*
|
|
* \brief
|
|
* Provides a high level interface for interacting with the Cypress Analog/Digital
|
|
* convert. This interface abstracts out the chip specific details. If any chip
|
|
* specific functionality is necessary, or performance is critical the low level
|
|
* functions can be used directly.
|
|
*
|
|
********************************************************************************
|
|
* \copyright
|
|
* Copyright 2018-2021 Cypress Semiconductor Corporation
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*******************************************************************************/
|
|
|
|
/**
|
|
* \addtogroup group_hal_impl_adc ADC (Analog Digital Converter)
|
|
* \ingroup group_hal_impl
|
|
* \{
|
|
* \section cyhal_adc_impl_features Features
|
|
* The CAT1/CAT2 (PMG/PSoC 4/PSoC 6) ADC supports the following features:
|
|
* * Resolution: 12 bit
|
|
* * Only @ref CYHAL_POWER_LEVEL_DEFAULT and CYHAL_POWER_LEVEL_OFF are defined. The default power
|
|
* level will automatically adjust based on smple rate.
|
|
* * Average counts: 2, 4, 8, 16, 32, 64, 128, 256
|
|
* * Up to four unique acquisition times.
|
|
* * DMA-based transfer when using @ref cyhal_adc_read_async. When using @ref cyhal_adc_read_async_uv,
|
|
* only interrupt-driven software copy is supported.
|
|
* \} group_hal_impl_adc
|
|
*/
|
|
|
|
#include <cmsis_compiler.h>
|
|
|
|
#include "cyhal_adc.h"
|
|
#include "cyhal_analog_common.h"
|
|
#include "cyhal_clock.h"
|
|
#include "cyhal_dma.h"
|
|
#include "cyhal_gpio.h"
|
|
#include "cyhal_hwmgr.h"
|
|
#include "cyhal_utils.h"
|
|
#include "cyhal_interconnect.h"
|
|
#include "cyhal_syspm.h"
|
|
#include "cyhal_system.h"
|
|
#include <string.h>
|
|
|
|
#if defined(CY_IP_MXS40PASS_SAR_INSTANCES)
|
|
#define _CYHAL_ADC_SAR_INSTANCES CY_IP_MXS40PASS_SAR_INSTANCES
|
|
#elif defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
#define _CYHAL_ADC_SAR_INSTANCES CY_IP_M0S8PASS4A_INSTANCES
|
|
#endif
|
|
|
|
#if defined(_CYHAL_ADC_SAR_INSTANCES)
|
|
|
|
#if defined(__cplusplus)
|
|
extern "C"
|
|
{
|
|
#endif
|
|
|
|
// The PDL for M0S8 PASS doesn't take the register as an argument; it always writes to MUX_SWITCH0
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
#define _CYHAL_ADC_SARSEQ_STATE(state) (state)
|
|
#define _CYHAL_ADC_SWITCH_STATE(state) (state)
|
|
#define _CYHAL_ADC_SET_SWITCH(base, mask, state) Cy_SAR_SetAnalogSwitch((base), (mask), (state))
|
|
#else
|
|
#define _CYHAL_ADC_SWITCH_STATE(state) ((state) ? CY_SAR_SWITCH_CLOSE : CY_SAR_SWITCH_OPEN)
|
|
#define _CYHAL_ADC_SARSEQ_STATE(state) ((state) ? CY_SAR_SWITCH_SEQ_CTRL_ENABLE : CY_SAR_SWITCH_SEQ_CTRL_DISABLE)
|
|
#define _CYHAL_ADC_SET_SWITCH(base, mask, state) Cy_SAR_SetAnalogSwitch((base), CY_SAR_MUX_SWITCH0, (mask), _CYHAL_ADC_SWITCH_STATE((state)))
|
|
#endif
|
|
|
|
static SAR_Type *const _cyhal_adc_base[] =
|
|
{
|
|
#if (CY_IP_MXS40PASS_SAR_INSTANCES == 1)
|
|
SAR,
|
|
#else
|
|
#if (_CYHAL_ADC_SAR_INSTANCES >= 1)
|
|
SAR0,
|
|
#endif
|
|
#if (_CYHAL_ADC_SAR_INSTANCES >= 2)
|
|
SAR1,
|
|
#endif
|
|
#if (_CYHAL_ADC_SAR_INSTANCES >= 3)
|
|
#warning Unhandled SAR instance count
|
|
#endif
|
|
#endif
|
|
};
|
|
|
|
static const en_clk_dst_t _cyhal_adc_clock[] =
|
|
{
|
|
#if (CY_IP_MXS40PASS_SAR_INSTANCES == 1)
|
|
PCLK_PASS_CLOCK_SAR,
|
|
#elif (CY_IP_M0S8PASS4A_INSTANCES == 1)
|
|
PCLK_PASS0_CLOCK_SAR,
|
|
#elif (CY_IP_MXS40PASS_SAR_INSTANCES == 2)
|
|
PCLK_PASS_CLOCK_SAR0,
|
|
PCLK_PASS_CLOCK_SAR1,
|
|
#elif (CY_IP_M0S8PASS4A_INSTANCES == 2)
|
|
PCLK_PASS0_CLOCK_SAR,
|
|
PCLK_PASS1_CLOCK_SAR,
|
|
#else
|
|
#warning Unhandled SAR instance count
|
|
#endif
|
|
};
|
|
|
|
static const cyhal_source_t _cyhal_adc_tr_out[] =
|
|
{
|
|
#if (CY_IP_MXS40PASS_SAR_INSTANCES == 1)
|
|
CYHAL_TRIGGER_PASS_TR_SAR_OUT,
|
|
#elif (CY_IP_M0S8PASS4A_INSTANCES == 1)
|
|
CYHAL_TRIGGER_PASS0_TR_SAR_OUT,
|
|
#elif (CY_IP_MXS40PASS_SAR_INSTANCES == 2)
|
|
CYHAL_TRIGGER_PASS_TR_SAR_OUT0,
|
|
CYHAL_TRIGGER_PASS_TR_SAR_OUT1,
|
|
#elif (CY_IP_M0S8PASS4A_INSTANCES == 2)
|
|
CYHAL_TRIGGER_PASS0_TR_SAR_OUT,
|
|
CYHAL_TRIGGER_PASS1_TR_SAR_OUT,
|
|
#else
|
|
#warning Unhandled SAR instance count
|
|
#endif
|
|
};
|
|
|
|
static const cyhal_dest_t _cyhal_adc_tr_in[] =
|
|
{
|
|
#if (CY_IP_MXS40PASS_SAR_INSTANCES == 1)
|
|
CYHAL_TRIGGER_PASS_TR_SAR_IN,
|
|
#elif (CY_IP_M0S8PASS4A_INSTANCES == 1)
|
|
CYHAL_TRIGGER_PASS0_TR_SAR_IN,
|
|
#elif (CY_IP_MXS40PASS_SAR_INSTANCES == 2)
|
|
CYHAL_TRIGGER_PASS_TR_SAR_IN0,
|
|
CYHAL_TRIGGER_PASS_TR_SAR_IN1,
|
|
#elif (CY_IP_M0S8PASS4A_INSTANCES == 2)
|
|
CYHAL_TRIGGER_PASS0_TR_SAR_IN,
|
|
CYHAL_TRIGGER_PASS1_TR_SAR_IN,
|
|
#else
|
|
#warning Unhandled SAR instance count
|
|
#endif
|
|
};
|
|
|
|
static cyhal_adc_t* _cyhal_adc_config_structs[_CYHAL_ADC_SAR_INSTANCES];
|
|
|
|
static const IRQn_Type _cyhal_adc_irq_n[] =
|
|
{
|
|
#if (CY_IP_MXS40PASS_SAR_INSTANCES == 1)
|
|
pass_interrupt_sar_IRQn,
|
|
#elif (CY_IP_M0S8PASS4A_INSTANCES == 1)
|
|
pass_0_interrupt_sar_IRQn,
|
|
#elif (CY_IP_MXS40PASS_SAR_INSTANCES == 2)
|
|
pass_interrupt_sar_0_IRQn,
|
|
pass_interrupt_sar_1_IRQn,
|
|
#elif (CY_IP_M0S8PASS4A_INSTANCES == 2)
|
|
pass_0_interrupt_sar_IRQn,
|
|
pass_0_interrupt_sar_IRQn,
|
|
#else
|
|
#warning Unhandled SAR instance count
|
|
#endif
|
|
};
|
|
|
|
static uint8_t _cyhal_adc_get_block_from_irqn(IRQn_Type irqn)
|
|
{
|
|
switch (irqn)
|
|
{
|
|
#if (CY_CPU_CORTEX_M4 || CY_IP_M0S8PASS4A_INSTANCES) // M0S8 only has one processor, a CM0 variant
|
|
#if (CY_IP_MXS40PASS_SAR_INSTANCES == 1)
|
|
case pass_interrupt_sar_IRQn:
|
|
return 0;
|
|
#elif (CY_IP_M0S8PASS4A_INSTANCES == 1)
|
|
case pass_0_interrupt_sar_IRQn:
|
|
return 0;
|
|
#elif (CY_IP_MXS40PASS_SAR_INSTANCES == 2)
|
|
case pass_interrupt_sar_0_IRQn:
|
|
return 0;
|
|
case pass_interrupt_sar_1_IRQn:
|
|
return 1;
|
|
#elif (CY_IP_M0S8PASS4A_INSTANCES == 2)
|
|
case pass_0_interrupt_sar_IRQn:
|
|
return 0;
|
|
case pass_1_interrupt_sar_IRQn:
|
|
return 1;
|
|
#else
|
|
#warning Unhandled SAR instance count
|
|
#endif
|
|
#endif /* (CY_CPU_CORTEX_M4 || CY_IP_M0S8PASS4A_INSTANCES) */
|
|
default:
|
|
CY_ASSERT(false); // Should never be called with a non-SAR IRQn
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#if defined(CY_IP_MXS40PASS_SAR_INSTANCES)
|
|
/* OR in the following user-configurable values: vref, bypass, vneg, */
|
|
#define _CYHAL_ADC_DEFAULT_CTRL ((uint32_t)CY_SAR_VREF_PWR_100 | (uint32_t)CY_SAR_VREF_SEL_BGR \
|
|
| (uint32_t)CY_SAR_BYPASS_CAP_DISABLE | (uint32_t)CY_SAR_CTRL_NEGVREF_HW \
|
|
| (uint32_t)CY_SAR_CTRL_COMP_DLY_12 | (uint32_t)CY_SAR_COMP_PWR_100 \
|
|
| (uint32_t)CY_SAR_DEEPSLEEP_SARMUX_OFF | (uint32_t)CY_SAR_SARSEQ_SWITCH_ENABLE)
|
|
|
|
/* Default configuration. OR in the average count, and average mode */
|
|
#define _CYHAL_ADC_DEFAULT_SAMPLE ((uint32_t)CY_SAR_RIGHT_ALIGN | (uint32_t)CY_SAR_TRIGGER_MODE_FW_ONLY \
|
|
| (uint32_t)CY_SAR_SINGLE_ENDED_SIGNED | (uint32_t)CY_SAR_DIFFERENTIAL_SIGNED \
|
|
| (uint32_t)CY_SAR_TRIGGER_MODE_FW_ONLY)
|
|
|
|
static const cy_stc_sar_config_t _CYHAL_ADC_DEFAULT_PDL_CONFIG =
|
|
{
|
|
/* .ctrl is populated from _CYHAL_ADC_DEFAULT_CTRL plus the user's configuration */
|
|
/* .sampleCtrl is puopulated from _CYHAL_ADC_DEFAULT_SAMPLE plus the user's configuration */
|
|
.sampleTime01 = (10UL << SAR_SAMPLE_TIME01_SAMPLE_TIME0_Pos), // Sample times 1, 2, and 3 are not used
|
|
.sampleTime23 = 0UL,
|
|
.rangeThres = (0UL << CY_SAR_RANGE_HIGH_SHIFT) | (0UL << CY_SAR_RANGE_LOW_SHIFT),
|
|
.rangeCond = CY_SAR_RANGE_COND_BELOW,
|
|
.chanEn = 0UL,
|
|
.chanConfig = { 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL, 0UL},
|
|
.intrMask = (uint32_t) CY_SAR_INTR_EOS,
|
|
.satIntrMask = 0UL,
|
|
.rangeIntrMask = 0UL,
|
|
.muxSwitch = 0UL,
|
|
.muxSwitchSqCtrl = 0UL,
|
|
.configRouting = true,
|
|
/* .vrefMvValue is populated from the user's configuration */
|
|
};
|
|
|
|
#endif
|
|
|
|
#define _CYHAL_ADC_RESOLUTION 12u
|
|
#define _CYHAL_ADC_INTERNAL_VREF_MV 1200UL
|
|
#define _CYHAL_ADC_CONVERSION_CYCLES (_CYHAL_ADC_RESOLUTION + 2)
|
|
|
|
static const cyhal_adc_config_t _CYHAL_ADC_DEFAULT_CONFIG =
|
|
{
|
|
.resolution = _CYHAL_ADC_RESOLUTION,
|
|
.average_count = 1,
|
|
.average_mode_flags = CYHAL_ADC_AVG_MODE_AVERAGE,
|
|
.continuous_scanning = true,
|
|
.vneg = CYHAL_ADC_VNEG_VREF,
|
|
.vref = CYHAL_ADC_REF_INTERNAL,
|
|
.ext_vref = NC,
|
|
.ext_vref_mv = 0u,
|
|
.is_bypassed = false,
|
|
.bypass_pin = NC,
|
|
};
|
|
|
|
/*******************************************************************************
|
|
* Internal helper functions
|
|
*******************************************************************************/
|
|
static uint8_t _cyhal_adc_max_configured_channel(const cyhal_adc_t* obj)
|
|
{
|
|
uint8_t max = 0;
|
|
for(uint8_t i = 0; i < CY_SAR_SEQ_NUM_CHANNELS; ++i)
|
|
{
|
|
if(NULL != obj->channel_config[i])
|
|
{
|
|
max = i;
|
|
}
|
|
}
|
|
return max;
|
|
}
|
|
|
|
static uint32_t _cyhal_adc_get_mux_switch_control(cyhal_gpio_t gpio)
|
|
{
|
|
static const uint32_t mux_lookup[] =
|
|
{
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
SAR_MUX_SWITCH_HW_CTRL_MUX_HW_CTRL_P0_Msk,
|
|
SAR_MUX_SWITCH_HW_CTRL_MUX_HW_CTRL_P1_Msk,
|
|
SAR_MUX_SWITCH_HW_CTRL_MUX_HW_CTRL_P2_Msk,
|
|
SAR_MUX_SWITCH_HW_CTRL_MUX_HW_CTRL_P3_Msk,
|
|
SAR_MUX_SWITCH_HW_CTRL_MUX_HW_CTRL_P4_Msk,
|
|
SAR_MUX_SWITCH_HW_CTRL_MUX_HW_CTRL_P5_Msk,
|
|
SAR_MUX_SWITCH_HW_CTRL_MUX_HW_CTRL_P6_Msk,
|
|
SAR_MUX_SWITCH_HW_CTRL_MUX_HW_CTRL_P7_Msk,
|
|
#else
|
|
(uint32_t)CY_SAR_MUX_SQ_CTRL_P0,
|
|
(uint32_t)CY_SAR_MUX_SQ_CTRL_P1,
|
|
(uint32_t)CY_SAR_MUX_SQ_CTRL_P2,
|
|
(uint32_t)CY_SAR_MUX_SQ_CTRL_P3,
|
|
(uint32_t)CY_SAR_MUX_SQ_CTRL_P4,
|
|
(uint32_t)CY_SAR_MUX_SQ_CTRL_P5,
|
|
(uint32_t)CY_SAR_MUX_SQ_CTRL_P6,
|
|
(uint32_t)CY_SAR_MUX_SQ_CTRL_P7
|
|
#endif
|
|
};
|
|
uint8_t pin = CYHAL_GET_PIN(gpio);
|
|
CY_ASSERT(pin < sizeof(mux_lookup)/sizeof(mux_lookup[0]));
|
|
return mux_lookup[pin];
|
|
}
|
|
|
|
static uint32_t _cyhal_adc_get_fw_switch_control(cyhal_gpio_t gpio, bool is_vplus)
|
|
{
|
|
static const uint32_t vplus_lookup[] =
|
|
{
|
|
(uint32_t)CY_SAR_MUX_FW_P0_VPLUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P1_VPLUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P2_VPLUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P3_VPLUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P4_VPLUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P5_VPLUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P6_VPLUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P7_VPLUS
|
|
};
|
|
|
|
static const uint32_t vminus_lookup[] =
|
|
{
|
|
(uint32_t)CY_SAR_MUX_FW_P0_VMINUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P1_VMINUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P2_VMINUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P3_VMINUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P4_VMINUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P5_VMINUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P6_VMINUS,
|
|
(uint32_t)CY_SAR_MUX_FW_P7_VMINUS
|
|
};
|
|
|
|
uint8_t pin = CYHAL_GET_PIN(gpio);
|
|
CY_ASSERT(pin < sizeof(vplus_lookup)/sizeof(vplus_lookup[0]));
|
|
return (uint32_t)(is_vplus ? vplus_lookup[pin] : vminus_lookup[pin]);
|
|
}
|
|
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
static uint32_t _cyhal_adc_get_pin_addr(cyhal_gpio_t gpio)
|
|
{
|
|
// On this version of the PASS, there is no explicit vminus address; it is implied by vplus
|
|
static const cy_en_sar_chan_config_port_pin_addr_t vplus_lookup[] =
|
|
{
|
|
CY_SAR_ADDR_SARMUX_0,
|
|
CY_SAR_ADDR_SARMUX_1,
|
|
CY_SAR_ADDR_SARMUX_2,
|
|
CY_SAR_ADDR_SARMUX_3,
|
|
CY_SAR_ADDR_SARMUX_4,
|
|
CY_SAR_ADDR_SARMUX_5,
|
|
CY_SAR_ADDR_SARMUX_6,
|
|
CY_SAR_ADDR_SARMUX_7
|
|
};
|
|
uint8_t pin = CYHAL_GET_PIN(gpio);
|
|
CY_ASSERT(pin < sizeof(vplus_lookup)/sizeof(vplus_lookup[0]));
|
|
return (uint32_t)vplus_lookup[pin];
|
|
}
|
|
#else
|
|
static uint32_t _cyhal_adc_get_pin_addr(cyhal_gpio_t gpio, bool is_vplus)
|
|
{
|
|
static const cy_en_sar_chan_config_pos_pin_addr_t vplus_lookup[] =
|
|
{
|
|
CY_SAR_CHAN_POS_PIN_ADDR_0,
|
|
CY_SAR_CHAN_POS_PIN_ADDR_1,
|
|
CY_SAR_CHAN_POS_PIN_ADDR_2,
|
|
CY_SAR_CHAN_POS_PIN_ADDR_3,
|
|
CY_SAR_CHAN_POS_PIN_ADDR_4,
|
|
CY_SAR_CHAN_POS_PIN_ADDR_5,
|
|
CY_SAR_CHAN_POS_PIN_ADDR_6,
|
|
CY_SAR_CHAN_POS_PIN_ADDR_7
|
|
};
|
|
|
|
static const cy_en_sar_chan_config_neg_pin_addr_t vminus_lookup[] =
|
|
{
|
|
CY_SAR_CHAN_NEG_PIN_ADDR_0,
|
|
CY_SAR_CHAN_NEG_PIN_ADDR_1,
|
|
CY_SAR_CHAN_NEG_PIN_ADDR_2,
|
|
CY_SAR_CHAN_NEG_PIN_ADDR_3,
|
|
CY_SAR_CHAN_NEG_PIN_ADDR_4,
|
|
CY_SAR_CHAN_NEG_PIN_ADDR_5,
|
|
CY_SAR_CHAN_NEG_PIN_ADDR_6,
|
|
CY_SAR_CHAN_NEG_PIN_ADDR_7
|
|
};
|
|
uint8_t pin = CYHAL_GET_PIN(gpio);
|
|
CY_ASSERT(pin < sizeof(vplus_lookup)/sizeof(vplus_lookup[0]));
|
|
uint32_t pin_addr = is_vplus ? (uint32_t)vplus_lookup[pin] : (uint32_t)vminus_lookup[pin];
|
|
return is_vplus ? (pin_addr | CY_SAR_POS_PORT_ADDR_SARMUX) : (pin_addr | CY_SAR_NEG_PORT_ADDR_SARMUX);
|
|
}
|
|
#endif
|
|
|
|
static cy_en_sar_ctrl_vref_sel_t _cyhal_adc_convert_vref(cyhal_adc_vref_t vref)
|
|
{
|
|
switch(vref)
|
|
{
|
|
case CYHAL_ADC_REF_INTERNAL:
|
|
return CY_SAR_VREF_SEL_BGR;
|
|
case CYHAL_ADC_REF_EXTERNAL:
|
|
return CY_SAR_VREF_SEL_EXT;
|
|
case CYHAL_ADC_REF_VDDA:
|
|
return CY_SAR_VREF_SEL_VDDA;
|
|
case CYHAL_ADC_REF_VDDA_DIV_2:
|
|
return CY_SAR_VREF_SEL_VDDA_DIV_2;
|
|
default:
|
|
CY_ASSERT(false);
|
|
return CY_SAR_VREF_SEL_BGR;
|
|
}
|
|
}
|
|
|
|
static cy_rslt_t _cyhal_adc_convert_average_count(uint32_t hal_count, cy_en_sar_sample_ctrl_avg_cnt_t* pdl_count)
|
|
{
|
|
switch(hal_count)
|
|
{
|
|
case 1: /* Average count of 1 is achieved by disabling averaging for all channels */
|
|
case 2:
|
|
*pdl_count = CY_SAR_AVG_CNT_2;
|
|
return CY_RSLT_SUCCESS;
|
|
case 4:
|
|
*pdl_count = CY_SAR_AVG_CNT_4;
|
|
return CY_RSLT_SUCCESS;
|
|
case 8:
|
|
*pdl_count = CY_SAR_AVG_CNT_8;
|
|
return CY_RSLT_SUCCESS;
|
|
case 16:
|
|
*pdl_count = CY_SAR_AVG_CNT_16;
|
|
return CY_RSLT_SUCCESS;
|
|
case 32:
|
|
*pdl_count = CY_SAR_AVG_CNT_32;
|
|
return CY_RSLT_SUCCESS;
|
|
case 64:
|
|
*pdl_count = CY_SAR_AVG_CNT_64;
|
|
return CY_RSLT_SUCCESS;
|
|
case 128:
|
|
*pdl_count = CY_SAR_AVG_CNT_128;
|
|
return CY_RSLT_SUCCESS;
|
|
case 256:
|
|
*pdl_count = CY_SAR_AVG_CNT_256;
|
|
return CY_RSLT_SUCCESS;
|
|
default:
|
|
return CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
}
|
|
|
|
static uint32_t _cyhal_adc_get_vref_mv(const cyhal_adc_config_t* hal_config)
|
|
{
|
|
switch(hal_config->vref)
|
|
{
|
|
case CYHAL_ADC_REF_INTERNAL:
|
|
return _CYHAL_ADC_INTERNAL_VREF_MV;
|
|
case CYHAL_ADC_REF_EXTERNAL:
|
|
CY_ASSERT(hal_config->ext_vref_mv > 0); // Should have been error checked already
|
|
return hal_config->ext_vref_mv;
|
|
case CYHAL_ADC_REF_VDDA_DIV_2:
|
|
return cyhal_syspm_get_supply_voltage(CYHAL_VOLTAGE_SUPPLY_VDDA) / 2;
|
|
default:
|
|
CY_ASSERT(CYHAL_ADC_REF_VDDA == hal_config->vref);
|
|
return cyhal_syspm_get_supply_voltage(CYHAL_VOLTAGE_SUPPLY_VDDA);
|
|
}
|
|
}
|
|
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
static cy_rslt_t _cyhal_adc_convert_resolution(uint8_t hal_resolution, cy_en_sar_sample_ctrl_sub_resolution_t *pdl_resolution)
|
|
{
|
|
switch(hal_resolution)
|
|
{
|
|
case 10:
|
|
*pdl_resolution = CY_SAR_SUB_RESOLUTION_10B;
|
|
break;
|
|
case 8:
|
|
*pdl_resolution = CY_SAR_SUB_RESOLUTION_8B;
|
|
break;
|
|
default:
|
|
return CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
|
|
return CY_RSLT_SUCCESS;
|
|
}
|
|
|
|
static void _cyhal_adc_extract_channel_conf(cyhal_adc_t* adc, cy_stc_sar_channel_config_t* channel_configs)
|
|
{
|
|
for(uint8_t i = 0; i < CY_SAR_SEQ_NUM_CHANNELS; ++i)
|
|
{
|
|
channel_configs[i].addr = (cy_en_sar_chan_config_port_pin_addr_t)_FLD2VAL(SAR_CHAN_CONFIG_PIN_ADDR, adc->base->CHAN_CONFIG[i]);
|
|
channel_configs[i].addr |= (cy_en_sar_chan_config_port_pin_addr_t)_FLD2VAL(SAR_CHAN_CONFIG_PORT_ADDR, adc->base->CHAN_CONFIG[i]);
|
|
|
|
channel_configs[i].avgEn = _FLD2BOOL(SAR_CHAN_CONFIG_AVG_EN, adc->base->CHAN_CONFIG[i]);
|
|
channel_configs[i].sampleTimeSel = _FLD2VAL(SAR_CHAN_CONFIG_SAMPLE_TIME_SEL, adc->base->CHAN_CONFIG[i]);
|
|
channel_configs[i].differential = _FLD2BOOL(SAR_CHAN_CONFIG_DIFFERENTIAL_EN, adc->base->CHAN_CONFIG[i]);
|
|
channel_configs[i].resolution = (cy_en_sar_channel_ctrl_resolution_t)_FLD2VAL(SAR_CHAN_CONFIG_RESOLUTION, adc->base->CHAN_CONFIG[i]);
|
|
}
|
|
}
|
|
|
|
static cy_rslt_t _cyhal_adc_populate_pdl_config(const cyhal_adc_config_t* hal_config, cy_stc_sar_config_t* pdl_config,
|
|
cy_stc_sar_channel_config_t* channel_configs /* Array, length CY_SAR_SEQ_NUM_CHANNELS */)
|
|
{
|
|
memset(pdl_config, 0, sizeof(cy_stc_sar_config_t));
|
|
|
|
cy_rslt_t result = CY_RSLT_SUCCESS;
|
|
pdl_config->vrefSel = _cyhal_adc_convert_vref(hal_config->vref);
|
|
pdl_config->vrefBypCapEn = hal_config->is_bypassed;
|
|
pdl_config->negSel = (hal_config->vneg == CYHAL_ADC_VNEG_VSSA) ? CY_SAR_NEG_SEL_VSSA_KELVIN : CY_SAR_NEG_SEL_VREF;
|
|
pdl_config->negVref = CY_SAR_NEGVREF_HW;
|
|
pdl_config->boostPump = true;
|
|
pdl_config->power = CY_SAR_NORMAL_PWR;
|
|
pdl_config->sarMuxDsEn = false; // This only enables the routing in deepsleep, not the SAR. And we only use the routing with the SAR
|
|
pdl_config->switchDisable = false; // We are using the SARSEQ
|
|
if(hal_config->resolution != _CYHAL_ADC_RESOLUTION)
|
|
{
|
|
result = _cyhal_adc_convert_resolution(hal_config->resolution, &pdl_config->subResolution);
|
|
}
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
pdl_config->leftAlign = false;
|
|
pdl_config->singleEndedSigned = true;
|
|
pdl_config->differentialSigned = true;
|
|
pdl_config->avgShift = true;
|
|
result = _cyhal_adc_convert_average_count(hal_config->average_count, &pdl_config->avgCnt);
|
|
}
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
pdl_config->trigMode = CY_SAR_TRIGGER_MODE_FW_ONLY;
|
|
pdl_config->eosEn = false;
|
|
// Will be updated after configuration when we populate sample times after adding channels
|
|
pdl_config->sampleTime0 = pdl_config->sampleTime1 = pdl_config->sampleTime2 = pdl_config->sampleTime3 = 10u;
|
|
// Skipping range thresholds because we don't expose the range detect feature.
|
|
// We always need to populate channel configuration structs here because otherwise the PDL will not
|
|
// initialize the default gain value that the channels will need when and if we add them later.
|
|
for(uint8_t i = 0; i < CY_SAR_SEQ_NUM_CHANNELS; ++i)
|
|
{
|
|
pdl_config->channelConfig[i] = &channel_configs[i];
|
|
}
|
|
// Routing will be configured and channels will be enabled as channels are added
|
|
pdl_config->vrefMvValue = _cyhal_adc_get_vref_mv(hal_config);
|
|
}
|
|
return result;
|
|
}
|
|
#else
|
|
static uint32_t _cyhal_adc_convert_average_mode(uint32_t average_mode_flags)
|
|
{
|
|
uint32 result = 0;
|
|
if(0u != (average_mode_flags & CYHAL_ADC_AVG_MODE_ACCUMULATE))
|
|
{
|
|
result |= CY_SAR_AVG_MODE_SEQUENTIAL_ACCUM;
|
|
}
|
|
else if(0u != (average_mode_flags & CYHAL_ADC_AVG_MODE_INTERLEAVED))
|
|
{
|
|
/* INTERLEAVED on its own does not divide the result back down */
|
|
result |= (CY_SAR_AVG_MODE_INTERLEAVED | SAR_SAMPLE_CTRL_AVG_SHIFT_Msk);
|
|
}
|
|
else
|
|
{
|
|
result |= CY_SAR_AVG_MODE_SEQUENTIAL_FIXED;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/* Populates the PDL config struct with settings from the ADC config struct */
|
|
static cy_rslt_t _cyhal_adc_populate_pdl_config(const cyhal_adc_config_t* hal_config, cy_stc_sar_config_t* pdl_config)
|
|
{
|
|
memset(pdl_config, 0, sizeof(cy_stc_sar_config_t));
|
|
if(hal_config->resolution != _CYHAL_ADC_RESOLUTION) /* SAR does not support configurable resolution */
|
|
{
|
|
return CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
|
|
if (((hal_config->average_mode_flags & CYHAL_ADC_AVG_MODE_ACCUMULATE) > 0) &&
|
|
((hal_config->average_mode_flags & CYHAL_ADC_AVG_MODE_INTERLEAVED) > 0))
|
|
{
|
|
/* Accumulate mode is not compatible with interleaved averaging */
|
|
return CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
|
|
*pdl_config = _CYHAL_ADC_DEFAULT_PDL_CONFIG;
|
|
uint32_t ctrl = _CYHAL_ADC_DEFAULT_CTRL;
|
|
ctrl |= (uint32_t)_cyhal_adc_convert_vref(hal_config->vref);
|
|
ctrl |= (hal_config->is_bypassed) ? CY_SAR_BYPASS_CAP_ENABLE : CY_SAR_BYPASS_CAP_DISABLE;
|
|
ctrl |= (hal_config->vneg == CYHAL_ADC_VNEG_VSSA) ? CY_SAR_NEG_SEL_VSSA_KELVIN : CY_SAR_NEG_SEL_VREF;
|
|
|
|
uint32_t sample_ctrl = _CYHAL_ADC_DEFAULT_SAMPLE;
|
|
cy_en_sar_sample_ctrl_avg_cnt_t pdl_avg;
|
|
cy_rslt_t result = _cyhal_adc_convert_average_count(hal_config->average_count, &pdl_avg);
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
sample_ctrl |= (uint32_t)pdl_avg;
|
|
sample_ctrl |= _cyhal_adc_convert_average_mode(hal_config->average_mode_flags);
|
|
|
|
pdl_config->ctrl = ctrl;
|
|
pdl_config->sampleCtrl = sample_ctrl;
|
|
pdl_config->vrefMvValue = _cyhal_adc_get_vref_mv(hal_config);
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
static void _cyhal_adc_irq_handler(void)
|
|
{
|
|
/* The only enabled event is scan finished */
|
|
cyhal_adc_event_t hal_event = CYHAL_ADC_EOS;
|
|
|
|
IRQn_Type irqn = _CYHAL_UTILS_GET_CURRENT_IRQN();
|
|
uint8_t block = _cyhal_adc_get_block_from_irqn(irqn);
|
|
cyhal_adc_t* obj = _cyhal_adc_config_structs[block];
|
|
Cy_SAR_ClearInterrupt(obj->base, CY_SAR_INTR_EOS);
|
|
obj->conversion_complete = true;
|
|
|
|
uint8_t num_channels = _cyhal_adc_max_configured_channel(obj) + 1;
|
|
if(obj->async_scans_remaining > 0)
|
|
{
|
|
/* Can't read millivolts out via DMA */
|
|
if(CYHAL_ASYNC_SW == obj->async_mode || obj->async_transfer_in_uv)
|
|
{
|
|
for(uint8_t i = 0; i < num_channels; ++i)
|
|
{
|
|
int32_t counts = Cy_SAR_GetResult32(obj->base, i);
|
|
*obj->async_buff_next = obj->async_transfer_in_uv ? Cy_SAR_CountsTo_uVolts(obj->base, i, counts) : counts;
|
|
++obj->async_buff_next;
|
|
}
|
|
--(obj->async_scans_remaining);
|
|
|
|
if(0 == obj->async_scans_remaining)
|
|
{
|
|
obj->async_buff_next = obj->async_buff_orig = NULL;
|
|
hal_event |= CYHAL_ADC_ASYNC_READ_COMPLETE;
|
|
}
|
|
else if(false == obj->continuous_scanning)
|
|
{
|
|
Cy_SAR_StartConvert(obj->base, CY_SAR_START_CONVERT_SINGLE_SHOT);
|
|
}
|
|
/* If we're continously scanning, another scan will be kicked off automatically
|
|
* so we don't need to do anything */
|
|
}
|
|
else
|
|
{
|
|
CY_ASSERT(CYHAL_ASYNC_DMA == obj->async_mode);
|
|
|
|
#if defined(CY_IP_M0S8CPUSSV3_DMAC) || defined(CY_IP_M4CPUSS_DMA) || defined(CY_IP_M4CPUSS_DMAC)
|
|
cyhal_dma_cfg_t dma_config =
|
|
{
|
|
.src_addr = (uint32_t)obj->base->CHAN_RESULT,
|
|
.src_increment = 1u,
|
|
.dst_addr = (uint32_t)obj->async_buff_next,
|
|
.dst_increment = 1u,
|
|
.transfer_width = 32u,
|
|
.length = num_channels,
|
|
.burst_size = 0u,
|
|
.action = CYHAL_DMA_TRANSFER_FULL
|
|
};
|
|
|
|
// Configure needs to happen after we've manipulated the descriptor config
|
|
cy_rslt_t result = cyhal_dma_configure(&(obj->dma), &dma_config);
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
result = cyhal_dma_start_transfer(&(obj->dma));
|
|
}
|
|
CY_ASSERT(CY_RSLT_SUCCESS == result);
|
|
|
|
/* Don't increment the buffer here - do that when the DMA completes */
|
|
|
|
if(false == obj->continuous_scanning)
|
|
{
|
|
Cy_SAR_StartConvert(obj->base, CY_SAR_START_CONVERT_SINGLE_SHOT);
|
|
}
|
|
#else
|
|
CY_ASSERT(false); // DMA not supported on the current device
|
|
#endif //defined(CY_IP_M0S8CPUSSV3_DMAC) || defined(CY_IP_M4CPUSS_DMA) || defined(CY_IP_M4CPUSS_DMAC)
|
|
}
|
|
}
|
|
|
|
if(0 != (hal_event & ((cyhal_adc_event_t)obj->user_enabled_events)))
|
|
{
|
|
cyhal_adc_event_callback_t callback = (cyhal_adc_event_callback_t)obj->callback_data.callback;
|
|
if(NULL != callback)
|
|
{
|
|
callback(obj->callback_data.callback_arg, (cyhal_adc_event_t)(hal_event & obj->user_enabled_events));
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#if defined(CY_IP_M0S8CPUSSV3_DMAC) || defined(CY_IP_M4CPUSS_DMA) || defined(CY_IP_M4CPUSS_DMAC)
|
|
static void _cyhal_adc_dma_handler(void* arg, cyhal_dma_event_t event)
|
|
{
|
|
CY_ASSERT(CYHAL_DMA_TRANSFER_COMPLETE == event);
|
|
CY_UNUSED_PARAMETER(event);
|
|
cyhal_adc_t* obj = (cyhal_adc_t*)arg;
|
|
CY_ASSERT(CYHAL_ASYNC_DMA == obj->async_mode);
|
|
|
|
uint8_t num_channels = _cyhal_adc_max_configured_channel(obj) + 1;
|
|
CY_ASSERT(false == obj->async_transfer_in_uv);
|
|
obj->async_buff_next += num_channels;
|
|
--(obj->async_scans_remaining);
|
|
|
|
if(0 == obj->async_scans_remaining)
|
|
{
|
|
// DMA doesn't sign extend when we copy from 16 to 32 bits, so do the sign extension
|
|
// ourselves once all channel scans are complete.
|
|
while(obj->async_buff_orig != obj->async_buff_next)
|
|
{
|
|
// Mask off the upper two bytes because those contain mirrored status bits which
|
|
// are not part of the ADC counts
|
|
int16_t sar_result = (int16_t)(0xFFFF & *(obj->async_buff_orig));
|
|
*(obj->async_buff_orig) = sar_result;
|
|
++(obj->async_buff_orig);
|
|
}
|
|
obj->async_buff_next = obj->async_buff_orig = NULL;
|
|
if(0 != (CYHAL_ADC_ASYNC_READ_COMPLETE & ((cyhal_adc_event_t)obj->user_enabled_events)))
|
|
{
|
|
cyhal_adc_event_callback_t callback = (cyhal_adc_event_callback_t)obj->callback_data.callback;
|
|
if(NULL != callback)
|
|
{
|
|
callback(obj->callback_data.callback_arg, CYHAL_ADC_ASYNC_READ_COMPLETE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*******************************************************************************
|
|
* ADC HAL Functions
|
|
*******************************************************************************/
|
|
|
|
cy_rslt_t cyhal_adc_init(cyhal_adc_t *obj, cyhal_gpio_t pin, const cyhal_clock_t *clk)
|
|
{
|
|
const uint32_t DESIRED_DIVIDER = 8000000u; // 8 MHz. Required range is 1.7 - 18
|
|
|
|
CY_ASSERT(NULL != obj);
|
|
|
|
cy_rslt_t result = CY_RSLT_SUCCESS;
|
|
|
|
memset(obj, 0, sizeof(cyhal_adc_t));
|
|
obj->clock.reserved = false;
|
|
obj->resource.type = CYHAL_RSC_INVALID;
|
|
obj->async_mode = CYHAL_ASYNC_SW;
|
|
obj->source = CYHAL_TRIGGER_CPUSS_ZERO;
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
obj->resolution = _CYHAL_ADC_RESOLUTION;
|
|
obj->ext_vref = NC;
|
|
obj->bypass_pin = NC;
|
|
#endif
|
|
|
|
const cyhal_resource_pin_mapping_t* map = _cyhal_utils_try_alloc(pin, cyhal_pin_map_pass_sarmux_pads, sizeof(cyhal_pin_map_pass_sarmux_pads) / sizeof(cyhal_pin_map_pass_sarmux_pads[0]));
|
|
|
|
if (NULL == map)
|
|
result = CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
|
|
cyhal_resource_inst_t adc_inst;
|
|
if (CY_RSLT_SUCCESS == result)
|
|
{
|
|
adc_inst = *map->inst;
|
|
/* No need to reserve - try_alloc did so for us already */
|
|
}
|
|
|
|
en_clk_dst_t pclk = (en_clk_dst_t)0;
|
|
if (CY_RSLT_SUCCESS == result)
|
|
{
|
|
obj->resource = adc_inst;
|
|
|
|
obj->base = _cyhal_adc_base[adc_inst.block_num];
|
|
pclk = (en_clk_dst_t)(_cyhal_adc_clock[adc_inst.block_num]);
|
|
if (NULL != clk)
|
|
{
|
|
obj->clock = *clk;
|
|
obj->dedicated_clock = false;
|
|
}
|
|
else if (CY_RSLT_SUCCESS ==
|
|
(result = _cyhal_utils_allocate_clock(&(obj->clock), &(obj->resource), CYHAL_CLOCK_BLOCK_PERIPHERAL_16BIT, true)))
|
|
{
|
|
obj->dedicated_clock = true;
|
|
}
|
|
}
|
|
|
|
if (CY_RSLT_SUCCESS == result)
|
|
{
|
|
if (CY_SYSCLK_SUCCESS != Cy_SysClk_PeriphAssignDivider(pclk, (cy_en_divider_types_t)obj->clock.block, obj->clock.channel))
|
|
result = CYHAL_ADC_RSLT_FAILED_CLOCK;
|
|
}
|
|
|
|
if (CY_RSLT_SUCCESS == result)
|
|
{
|
|
if(obj->dedicated_clock)
|
|
{
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
uint32_t source_hz = Cy_SysClk_ClkSysGetFrequency();
|
|
#else
|
|
uint32_t source_hz = Cy_SysClk_ClkPeriGetFrequency();
|
|
#endif
|
|
uint32_t div = source_hz / DESIRED_DIVIDER;
|
|
if (0 == div ||
|
|
CY_SYSCLK_SUCCESS != Cy_SysClk_PeriphSetDivider((cy_en_divider_types_t)obj->clock.block, obj->clock.channel, div - 1) ||
|
|
CY_SYSCLK_SUCCESS != Cy_SysClk_PeriphEnableDivider((cy_en_divider_types_t)obj->clock.block, obj->clock.channel))
|
|
{
|
|
result = CYHAL_ADC_RSLT_FAILED_CLOCK;
|
|
}
|
|
}
|
|
}
|
|
|
|
cy_stc_sar_config_t pdl_config;
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
cy_stc_sar_channel_config_t chan_configs[CY_SAR_SEQ_NUM_CHANNELS];
|
|
memset(chan_configs, 0, sizeof(chan_configs));
|
|
// No channels have actually been configured yet, so an empty set of config structs is fine here
|
|
if (CY_RSLT_SUCCESS == result)
|
|
{
|
|
result = _cyhal_adc_populate_pdl_config(&_CYHAL_ADC_DEFAULT_CONFIG, &pdl_config, chan_configs);
|
|
}
|
|
#else
|
|
if (CY_RSLT_SUCCESS == result)
|
|
{
|
|
result = _cyhal_adc_populate_pdl_config(&_CYHAL_ADC_DEFAULT_CONFIG, &pdl_config);
|
|
}
|
|
#endif
|
|
|
|
if (result == CY_RSLT_SUCCESS)
|
|
{
|
|
result = (cy_rslt_t)Cy_SAR_Init(obj->base, &pdl_config);
|
|
}
|
|
|
|
if (result == CY_RSLT_SUCCESS)
|
|
{
|
|
Cy_SAR_SetVssaSarSeqCtrl(obj->base, _CYHAL_ADC_SARSEQ_STATE(true));
|
|
Cy_SAR_SetVssaVminusSwitch(obj->base, _CYHAL_ADC_SWITCH_STATE(true));
|
|
|
|
_cyhal_analog_init();
|
|
|
|
_cyhal_adc_config_structs[obj->resource.block_num] = obj;
|
|
cy_stc_sysint_t irqCfg = { _cyhal_adc_irq_n[obj->resource.block_num], CYHAL_ISR_PRIORITY_DEFAULT };
|
|
Cy_SysInt_Init(&irqCfg, _cyhal_adc_irq_handler);
|
|
NVIC_EnableIRQ(_cyhal_adc_irq_n[obj->resource.block_num]);
|
|
|
|
/* We always need to listen to the EOS interrupt for internal bookkeeping */
|
|
Cy_SAR_SetInterruptMask(obj->base, CY_SAR_INTR_EOS);
|
|
Cy_SAR_Enable(obj->base);
|
|
}
|
|
else
|
|
{
|
|
cyhal_adc_free(obj);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void cyhal_adc_free(cyhal_adc_t *obj)
|
|
{
|
|
if (NULL != obj && NULL != obj->base)
|
|
{
|
|
IRQn_Type irqn = _cyhal_adc_irq_n[obj->resource.block_num];
|
|
NVIC_DisableIRQ(irqn);
|
|
_cyhal_adc_config_structs[obj->resource.block_num] = NULL;
|
|
|
|
cy_rslt_t rslt;
|
|
rslt = cyhal_adc_disable_output(obj, CYHAL_ADC_OUTPUT_SCAN_COMPLETE);
|
|
CY_ASSERT(CY_RSLT_SUCCESS == rslt);
|
|
if (CYHAL_TRIGGER_CPUSS_ZERO != obj->source)
|
|
{
|
|
rslt = cyhal_adc_disconnect_digital(obj, obj->source, CYHAL_ADC_INPUT_START_SCAN);
|
|
CY_ASSERT(CY_RSLT_SUCCESS == rslt);
|
|
}
|
|
(void)rslt; // Disable compiler warning in release build
|
|
|
|
Cy_SAR_SetVssaSarSeqCtrl(obj->base, _CYHAL_ADC_SARSEQ_STATE(false));
|
|
Cy_SAR_SetVssaVminusSwitch(obj->base, _CYHAL_ADC_SWITCH_STATE(false));
|
|
Cy_SAR_Disable(obj->base);
|
|
|
|
if(obj->dedicated_clock)
|
|
{
|
|
Cy_SysClk_PeriphDisableDivider((cy_en_divider_types_t)obj->clock.block, obj->clock.channel);
|
|
cyhal_clock_free(&obj->clock);
|
|
}
|
|
|
|
_cyhal_analog_free();
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
_cyhal_utils_release_if_used(&(obj->ext_vref));
|
|
_cyhal_utils_release_if_used(&(obj->bypass_pin));
|
|
#endif
|
|
cyhal_hwmgr_free(&obj->resource);
|
|
obj->base = NULL;
|
|
}
|
|
}
|
|
|
|
cy_rslt_t _cyhal_adc_populate_acquisition_timers(cyhal_adc_t* obj)
|
|
{
|
|
const uint32_t ACQUISITION_CLOCKS_MIN = 2;
|
|
const uint32_t ACQUISITION_CLOCKS_MAX = 1023;
|
|
|
|
cy_rslt_t result = CY_RSLT_SUCCESS;
|
|
|
|
uint32_t clock_frequency_hz = cyhal_clock_get_frequency(&(obj->clock));
|
|
uint32_t clock_period_ns = (clock_frequency_hz > 0)
|
|
? _CYHAL_UTILS_NS_PER_SECOND / clock_frequency_hz
|
|
: 0;
|
|
uint16_t sample_timer_ns[] = { 0u, 0u, 0u, 0u };
|
|
uint8_t assigned_timer[CY_SAR_SEQ_NUM_CHANNELS];
|
|
for(uint8_t channel = 0; channel < CY_SAR_SEQ_NUM_CHANNELS; ++channel)
|
|
{
|
|
cyhal_adc_channel_t* chan_config = obj->channel_config[channel];
|
|
assigned_timer[channel] = 0u;
|
|
/* If the channel isn't in use, what we select doesn't matter */
|
|
if(NULL != chan_config)
|
|
{
|
|
bool found = false;
|
|
for(uint8_t timer = 0; timer < sizeof(sample_timer_ns) / sizeof(sample_timer_ns[0]); ++timer)
|
|
{
|
|
if(chan_config->minimum_acquisition_ns == sample_timer_ns[timer])
|
|
{
|
|
/* Matched a pre-existing timer; use that */
|
|
assigned_timer[channel] = timer;
|
|
found = true;
|
|
break;
|
|
}
|
|
else if(0 == sample_timer_ns[timer])
|
|
{
|
|
/* Found a free timer - allocate and use that */
|
|
sample_timer_ns[timer] = chan_config->minimum_acquisition_ns;
|
|
assigned_timer[channel] = timer;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(false == found)
|
|
{
|
|
/* Ran out of acquisition timers */
|
|
result = CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
uint16_t sample_timer_clocks[sizeof(sample_timer_ns) / sizeof(sample_timer_ns[0])];
|
|
for(uint8_t i = 0; i < sizeof(sample_timer_clocks) / sizeof(sample_timer_clocks[0]); ++i)
|
|
{
|
|
/* Convert from nanoseconds to clock cycles, rounding up */
|
|
uint32_t clock_cycles = (sample_timer_ns[i] + (clock_period_ns - 1)) / clock_period_ns;
|
|
if(clock_cycles < ACQUISITION_CLOCKS_MIN)
|
|
{
|
|
clock_cycles = ACQUISITION_CLOCKS_MIN;
|
|
}
|
|
else if(clock_cycles > ACQUISITION_CLOCKS_MAX)
|
|
{
|
|
clock_cycles = ACQUISITION_CLOCKS_MAX;
|
|
}
|
|
/* Per the register map, this should be one greater than the actual desired sampling cycle count */
|
|
sample_timer_clocks[i] = clock_cycles + 1;
|
|
}
|
|
|
|
obj->base->SAMPLE_TIME01 = (sample_timer_clocks[0] << SAR_SAMPLE_TIME01_SAMPLE_TIME0_Pos)
|
|
| (sample_timer_clocks[1] << SAR_SAMPLE_TIME01_SAMPLE_TIME1_Pos);
|
|
obj->base->SAMPLE_TIME23 = (sample_timer_clocks[2] << SAR_SAMPLE_TIME23_SAMPLE_TIME2_Pos)
|
|
| (sample_timer_clocks[3] << SAR_SAMPLE_TIME23_SAMPLE_TIME3_Pos);
|
|
|
|
for(uint8_t i = 0; i < CY_SAR_SEQ_NUM_CHANNELS; ++i)
|
|
{
|
|
obj->base->CHAN_CONFIG[i] &= ~SAR_CHAN_CONFIG_SAMPLE_TIME_SEL_Msk;
|
|
obj->base->CHAN_CONFIG[i] |= assigned_timer[i] << SAR_CHAN_CONFIG_SAMPLE_TIME_SEL_Pos;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static const cyhal_resource_pin_mapping_t* _cyhal_adc_find_matching_resource(cyhal_resource_inst_t* adc, cyhal_gpio_t pin, const cyhal_resource_pin_mapping_t *pin_map, size_t count)
|
|
{
|
|
for (uint32_t i = 0; i < count; i++)
|
|
{
|
|
if (pin == pin_map[i].pin && _cyhal_utils_resources_equal(adc, pin_map[i].inst))
|
|
{
|
|
return &pin_map[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
cy_rslt_t cyhal_adc_configure(cyhal_adc_t *obj, const cyhal_adc_config_t *config)
|
|
{
|
|
cy_rslt_t result = CY_RSLT_SUCCESS;
|
|
#if defined(CYHAL_PIN_MAP_PASS_SAR_EXT_VREF0)
|
|
if(NC != config->ext_vref)
|
|
{
|
|
// If this pin wasn't used in the previous config for either vref or bypass, reserve it
|
|
if(NC == obj->ext_vref && config->ext_vref != obj->bypass_pin)
|
|
{
|
|
const cyhal_resource_pin_mapping_t* ext_vref_map =
|
|
_cyhal_adc_find_matching_resource(&(obj->resource), config->ext_vref, cyhal_pin_map_pass_sar_ext_vref0,
|
|
sizeof(cyhal_pin_map_pass_sar_ext_vref0)/sizeof(cyhal_pin_map_pass_sar_ext_vref0[0]));
|
|
|
|
if (NULL == ext_vref_map)
|
|
{
|
|
result = CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
else
|
|
{
|
|
result = _cyhal_utils_reserve_and_connect(config->ext_vref, ext_vref_map);
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
obj->ext_vref = config->ext_vref;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(NC != obj->ext_vref) // We used to have an external vref pin - free it
|
|
{
|
|
// If the same pin was used as bypass, mark it freed now too
|
|
if(obj->ext_vref == obj->bypass_pin)
|
|
{
|
|
obj->bypass_pin = NC;
|
|
}
|
|
// It is okay to do this without checking if the pin is still used for bypass,
|
|
// because in that case we will just re-reserve the pin below
|
|
cyhal_gpio_free(obj->ext_vref);
|
|
obj->ext_vref = NC;
|
|
}
|
|
|
|
// If external vref exists as a GPIO, it's an error to set vref to external without passing in the pin
|
|
if(CYHAL_ADC_REF_EXTERNAL == config->vref)
|
|
{
|
|
result = CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
}
|
|
|
|
if(NC != config->bypass_pin)
|
|
{
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
// Bypass and ext_vref share the same hard-wired IO connection
|
|
const cyhal_resource_pin_mapping_t* bypass_map =
|
|
_cyhal_adc_find_matching_resource(&(obj->resource), config->bypass_pin, cyhal_pin_map_pass_sar_ext_vref0,
|
|
sizeof(cyhal_pin_map_pass_sar_ext_vref0)/sizeof(cyhal_pin_map_pass_sar_ext_vref0[0]));
|
|
|
|
if (NULL == bypass_map)
|
|
{
|
|
result = CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
else if(config->bypass_pin != config->ext_vref) // It's valid to use the same pin for both ext_vref and bypass
|
|
{
|
|
result = _cyhal_utils_reserve_and_connect(config->bypass_pin, bypass_map);
|
|
}
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
obj->bypass_pin = config->bypass_pin;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We used to have an external vref pin - free it, unless it's still used for ext_vref
|
|
if(NC != obj->bypass_pin && obj->ext_vref != obj->bypass_pin)
|
|
{
|
|
cyhal_gpio_free(obj->bypass_pin);
|
|
obj->bypass_pin = NC;
|
|
}
|
|
|
|
// If bypass exists as a GPIO, it's an error to enable bypass without passing in the pin
|
|
if(config->is_bypassed)
|
|
{
|
|
result = CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
}
|
|
#else
|
|
/* No GPIO pins for VREF - it must be using a dedicated pad */
|
|
if(config->bypass_pin != NC || config->ext_vref != NC)
|
|
{
|
|
result = CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
#endif
|
|
|
|
if(false == ((CYHAL_ADC_REF_EXTERNAL == config->vref) ^ (0u == config->ext_vref_mv)))
|
|
{
|
|
/* Must have exactly one of: ext vref selected, ext vref voltage unspecified */
|
|
result = CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
|
|
cy_stc_sar_config_t pdl_config;
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
obj->resolution = config->resolution;
|
|
cy_stc_sar_channel_config_t chan_configs[CY_SAR_SEQ_NUM_CHANNELS];
|
|
_cyhal_adc_extract_channel_conf(obj, chan_configs);
|
|
result = _cyhal_adc_populate_pdl_config(config, &pdl_config, chan_configs);
|
|
}
|
|
#else
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
result = _cyhal_adc_populate_pdl_config(config, &pdl_config);
|
|
}
|
|
#endif
|
|
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
/* Save and restore channel configs */
|
|
pdl_config.chanEn = obj->base->CHAN_EN;
|
|
|
|
/* Don't deinit routing or change the channel config - we're going
|
|
* to turn the SAR back on in a minute */
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
pdl_config.routingConfig = NULL;
|
|
#else
|
|
pdl_config.configRouting = false;
|
|
/* On M0S8 we already extracted the channel configuration as part
|
|
* of assembling the PDL config struct. On MxS40 we need to do it here */
|
|
for(uint8_t i = 0; i < CY_SAR_SEQ_NUM_CHANNELS; ++i)
|
|
{
|
|
pdl_config.chanConfig[i] = obj->base->CHAN_CONFIG[i];
|
|
}
|
|
#endif
|
|
result = (cy_rslt_t)Cy_SAR_Init(obj->base, &pdl_config);
|
|
_cyhal_adc_populate_acquisition_timers(obj);
|
|
Cy_SAR_SetInterruptMask(obj->base, CY_SAR_INTR_EOS);
|
|
Cy_SAR_Enable(obj->base);
|
|
}
|
|
|
|
if(obj->continuous_scanning)
|
|
{
|
|
obj->conversion_complete = false;
|
|
Cy_SAR_StartConvert(obj->base, CY_SAR_START_CONVERT_CONTINUOUS);
|
|
}
|
|
else
|
|
{
|
|
Cy_SAR_StopConvert(obj->base);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
cy_rslt_t cyhal_adc_set_power(cyhal_adc_t *obj, cyhal_power_level_t power)
|
|
{
|
|
// The SAR doesn't have selectable power levels in the same way that the opamps do.
|
|
if(CYHAL_POWER_LEVEL_OFF == power)
|
|
{
|
|
Cy_SAR_Disable(obj->base);
|
|
}
|
|
else
|
|
{
|
|
Cy_SAR_Enable(obj->base);
|
|
}
|
|
return CY_RSLT_SUCCESS;
|
|
}
|
|
|
|
static uint16_t _cyhal_adc_get_average_count(cyhal_adc_t* obj, int channel_idx)
|
|
{
|
|
uint32_t average_count = 1;
|
|
/* If averaging is in interleaved mode, it does not impact the sample time */
|
|
#if defined(CY_IP_MXS40PASS_SAR_INSTANCES)
|
|
bool is_interleaved = CY_SAR_AVG_MODE_INTERLEAVED == (SAR_SAMPLE_CTRL(obj->base) & SAR_SAMPLE_CTRL_AVG_MODE_Msk);
|
|
#else
|
|
bool is_interleaved = false;
|
|
#endif
|
|
|
|
if(false == is_interleaved)
|
|
{
|
|
average_count = (SAR_SAMPLE_CTRL(obj->base) & SAR_SAMPLE_CTRL_AVG_CNT_Msk) >> SAR_SAMPLE_CTRL_AVG_CNT_Pos;
|
|
average_count = (1uL << (average_count + 1uL));
|
|
}
|
|
|
|
return (obj->base->CHAN_CONFIG[channel_idx] & SAR_CHAN_CONFIG_AVG_EN_Msk) ? average_count : 1;
|
|
}
|
|
|
|
/* Gets acquisition times and conversion clocks for all enabled channels, factoring in averaging */
|
|
static void _cyhal_adc_get_sample_times(cyhal_adc_t* obj, uint32_t* min_acquisition_ns, uint32_t* conversion_clock_cycles)
|
|
{
|
|
*min_acquisition_ns = *conversion_clock_cycles = 0;
|
|
for(uint8_t i = 0; i < CY_SAR_SEQ_NUM_CHANNELS; ++i)
|
|
{
|
|
cyhal_adc_channel_t* chan_config = obj->channel_config[i];
|
|
if(NULL != chan_config && (0u != (obj->base->CHAN_EN & 1 << i)))
|
|
{
|
|
uint32_t min_time = chan_config->minimum_acquisition_ns;
|
|
uint8_t clock_cycles = _CYHAL_ADC_CONVERSION_CYCLES;
|
|
uint16_t average_count = _cyhal_adc_get_average_count(obj, i);
|
|
min_time *= average_count;
|
|
clock_cycles *= average_count;
|
|
|
|
*min_acquisition_ns += min_time;
|
|
*conversion_clock_cycles += clock_cycles;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t _cyhal_adc_calc_optimal_clock_rate(cyhal_adc_t* obj, uint32_t target_sample_hz)
|
|
{
|
|
/* From the architecture TRM */
|
|
const uint32_t ADC_CLOCK_MAX_HZ = 60000000;
|
|
const uint32_t ADC_CLOCK_MIN_HZ = 1000000;
|
|
|
|
uint32_t sample_period_ns = _CYHAL_UTILS_NS_PER_SECOND / target_sample_hz;
|
|
uint32_t total_acquisition_ns, conversion_clock_cycles;
|
|
_cyhal_adc_get_sample_times(obj, &total_acquisition_ns, &conversion_clock_cycles);
|
|
|
|
uint32_t conversion_budget_ns;
|
|
if(sample_period_ns < total_acquisition_ns)
|
|
{
|
|
// Requested sampling rate is impossible - go as fast as we can.
|
|
conversion_budget_ns = 1;
|
|
}
|
|
else
|
|
{
|
|
conversion_budget_ns = sample_period_ns - total_acquisition_ns;
|
|
}
|
|
|
|
uint32_t target_period_ns = conversion_budget_ns / conversion_clock_cycles;
|
|
uint32_t target_clock_hz = _CYHAL_UTILS_NS_PER_SECOND / target_period_ns;
|
|
if(target_clock_hz > ADC_CLOCK_MAX_HZ)
|
|
{
|
|
target_clock_hz = ADC_CLOCK_MAX_HZ;
|
|
}
|
|
else if(target_clock_hz < ADC_CLOCK_MIN_HZ)
|
|
{
|
|
target_clock_hz = ADC_CLOCK_MIN_HZ;
|
|
}
|
|
|
|
return target_clock_hz;
|
|
}
|
|
|
|
uint32_t _cyhal_adc_compute_actual_sample_rate(cyhal_adc_t* obj)
|
|
{
|
|
/* Assumes that the acquisition timers and clock frequency are already set */
|
|
uint32_t clock_frequency_hz = cyhal_clock_get_frequency(&obj->clock);
|
|
uint32_t clock_period_ns = (clock_frequency_hz > 0)
|
|
? _CYHAL_UTILS_NS_PER_SECOND / clock_frequency_hz
|
|
: 0;
|
|
uint16_t sample_timer[] =
|
|
{
|
|
(obj->base->SAMPLE_TIME01 & SAR_SAMPLE_TIME01_SAMPLE_TIME0_Msk) >> SAR_SAMPLE_TIME01_SAMPLE_TIME0_Pos,
|
|
(obj->base->SAMPLE_TIME01 & SAR_SAMPLE_TIME01_SAMPLE_TIME1_Msk) >> SAR_SAMPLE_TIME01_SAMPLE_TIME1_Pos,
|
|
(obj->base->SAMPLE_TIME23 & SAR_SAMPLE_TIME23_SAMPLE_TIME2_Msk) >> SAR_SAMPLE_TIME23_SAMPLE_TIME2_Pos,
|
|
(obj->base->SAMPLE_TIME23 & SAR_SAMPLE_TIME23_SAMPLE_TIME3_Msk) >> SAR_SAMPLE_TIME23_SAMPLE_TIME3_Pos,
|
|
};
|
|
|
|
uint32_t total_sample_time_ns = 0;
|
|
|
|
for(uint8_t i = 0; i < CY_SAR_SEQ_NUM_CHANNELS; ++i)
|
|
{
|
|
if(0u == (obj->base->CHAN_EN & 1u << i))
|
|
{
|
|
continue;
|
|
}
|
|
uint8_t sample_time_idx =
|
|
(obj->base->CHAN_CONFIG[i] & SAR_CHAN_CONFIG_SAMPLE_TIME_SEL_Msk) >> SAR_CHAN_CONFIG_SAMPLE_TIME_SEL_Pos;
|
|
/* Per the register map, the register value is one more than the actual cycle number. */
|
|
uint32_t sample_cycles = sample_timer[sample_time_idx] - 1;
|
|
uint32_t total_cycles = sample_cycles + _CYHAL_ADC_CONVERSION_CYCLES;
|
|
uint32_t sample_time_ns = total_cycles * clock_period_ns;
|
|
sample_time_ns *= _cyhal_adc_get_average_count(obj, i);
|
|
total_sample_time_ns += sample_time_ns;
|
|
}
|
|
|
|
uint32_t sample_frequency_hz = (total_sample_time_ns > 0)
|
|
? _CYHAL_UTILS_NS_PER_SECOND / total_sample_time_ns
|
|
: 0;
|
|
return sample_frequency_hz;
|
|
}
|
|
|
|
cy_rslt_t cyhal_adc_set_sample_rate(cyhal_adc_t* obj, uint32_t desired_sample_rate_hz, uint32_t* achieved_sample_rate_hz)
|
|
{
|
|
cy_rslt_t result = CY_RSLT_SUCCESS;
|
|
/* If we don't own the clock, the caller needs to adjust it and/or the acquisition times to achive the desired rate */
|
|
if(obj->dedicated_clock)
|
|
{
|
|
uint32_t desired_hz = _cyhal_adc_calc_optimal_clock_rate(obj, desired_sample_rate_hz);
|
|
result = cyhal_clock_set_frequency(&(obj->clock), desired_hz, NULL);
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
result = _cyhal_adc_populate_acquisition_timers(obj);
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
*achieved_sample_rate_hz = _cyhal_adc_compute_actual_sample_rate(obj);
|
|
}
|
|
else
|
|
{
|
|
*achieved_sample_rate_hz = 0u;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* ADC Channel HAL Functions
|
|
*******************************************************************************/
|
|
|
|
uint32_t _cyhal_adc_channel_convert_config(const cyhal_adc_channel_config_t* config, const cyhal_adc_t* adc,
|
|
cyhal_gpio_t vplus, cyhal_gpio_t vminus)
|
|
{
|
|
uint32_t result =
|
|
_BOOL2FLD(SAR_CHAN_CONFIG_AVG_EN, config->enable_averaging)
|
|
| _VAL2FLD(SAR_CHAN_CONFIG_SAMPLE_TIME_SEL, 0u); /* Placeholder, will be updated by populate_acquisition_timers */
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
CY_UNUSED_PARAMETER(vminus); // On M0S8, vminus is implied by vplus
|
|
result |= _cyhal_adc_get_pin_addr(vplus);
|
|
bool resolution_override = (adc->resolution != _CYHAL_ADC_RESOLUTION);
|
|
result |= _VAL2FLD(SAR_CHAN_CONFIG_RESOLUTION, (resolution_override ? CY_SAR_SUB_RES : CY_SAR_MAX_RES));
|
|
result |= _BOOL2FLD(SAR_CHAN_CONFIG_DIFFERENTIAL_EN, NC != vminus);
|
|
#else
|
|
CY_UNUSED_PARAMETER(adc); // MXS40 doesn't support resolution override, so ADC is not used
|
|
result |= _cyhal_adc_get_pin_addr(vplus, true);
|
|
if(vminus == NC)
|
|
{
|
|
/* Single-ended channel */
|
|
result |= (uint32_t)CY_SAR_CHAN_SINGLE_ENDED;
|
|
}
|
|
else
|
|
{
|
|
/* Differential */
|
|
result |= _cyhal_adc_get_pin_addr(vminus, false);
|
|
result |= (uint32_t)CY_SAR_CHAN_DIFFERENTIAL_UNPAIRED;
|
|
}
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
static void _cyhal_adc_update_chan_offset(cyhal_adc_channel_t* obj)
|
|
{
|
|
/* Normally, the PDL sets the offset in the ADC init. But we change the channel config after we initialize
|
|
* the ADC itself, so we need to set the offset appropriately here. Otherwise the _uv functions will not
|
|
* work correctly.
|
|
* The conditions in the PDL are: singleEnded && vrefNegSelect && singleEndedSigned. We always operate
|
|
* in signed mode so we only need to check the first two.
|
|
*/
|
|
bool single_ended = (CYHAL_ADC_VNEG == obj->vminus);
|
|
uint32_t neg_sel_vref_val = (uint32_t)CY_SAR_NEG_SEL_VREF;
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
// The NEG_SEL values in the M0S8 pdl are not pre-shifted
|
|
neg_sel_vref_val = neg_sel_vref_val << SAR_CTRL_NEG_SEL_Pos;
|
|
#endif
|
|
bool neg_sel_vref = (neg_sel_vref_val == (obj->adc->base->CTRL & SAR_CTRL_NEG_SEL_Msk));
|
|
int16_t offset = (single_ended && neg_sel_vref) ? (-1 * ((int16_t) (CY_SAR_WRK_MAX_12BIT / 2))) : 0;
|
|
|
|
#if defined(CY_IP_MXS40PASS_SAR_INSTANCES) && CY_IP_MXS40PASS_SAR_INSTANCES < 2
|
|
Cy_SAR_SetOffset(obj->channel_idx, offset);
|
|
#else
|
|
Cy_SAR_SetChannelOffset(obj->adc->base, obj->channel_idx, offset);
|
|
#endif
|
|
}
|
|
|
|
cy_rslt_t cyhal_adc_channel_init_diff(cyhal_adc_channel_t *obj, cyhal_adc_t* adc, cyhal_gpio_t vplus, cyhal_gpio_t vminus, const cyhal_adc_channel_config_t* cfg)
|
|
{
|
|
CY_ASSERT(obj != NULL);
|
|
CY_ASSERT(adc != NULL);
|
|
|
|
const uint32_t CYHAL_ADC_MIN_ACQUISITION_TIME_NS = 167;
|
|
|
|
cy_rslt_t result = CY_RSLT_SUCCESS;
|
|
|
|
memset(obj, 0, sizeof(cyhal_adc_channel_t));
|
|
obj->vplus = NC;
|
|
obj->vminus = NC;
|
|
|
|
// Check for invalid pin or pin belonging to a different SAR
|
|
const cyhal_resource_pin_mapping_t *vplus_map = _cyhal_adc_find_matching_resource(&(adc->resource), vplus,
|
|
cyhal_pin_map_pass_sarmux_pads, sizeof(cyhal_pin_map_pass_sarmux_pads)/sizeof(cyhal_pin_map_pass_sarmux_pads[0]));
|
|
const cyhal_resource_pin_mapping_t *vminus_map = NULL;
|
|
|
|
if(NULL == vplus_map)
|
|
{
|
|
result = CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS == result && CYHAL_ADC_VNEG != vminus)
|
|
{
|
|
vminus_map = _cyhal_adc_find_matching_resource(&(adc->resource), vminus, cyhal_pin_map_pass_sarmux_pads,
|
|
sizeof(cyhal_pin_map_pass_sarmux_pads)/sizeof(cyhal_pin_map_pass_sarmux_pads[0]));
|
|
if (NULL == vminus_map)
|
|
{
|
|
result = CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
result = _cyhal_utils_reserve_and_connect(vplus, vplus_map);
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
obj->vplus = vplus;
|
|
if(CYHAL_ADC_VNEG != vminus)
|
|
{
|
|
result = _cyhal_utils_reserve_and_connect(vminus, vminus_map);
|
|
}
|
|
}
|
|
|
|
uint8_t chosen_channel = 0;
|
|
|
|
if (CY_RSLT_SUCCESS == result)
|
|
{
|
|
obj->vminus = vminus;
|
|
|
|
// Find the first available channel
|
|
for(chosen_channel = 0; chosen_channel < CY_SAR_SEQ_NUM_CHANNELS; ++chosen_channel)
|
|
{
|
|
if(NULL == adc->channel_config[chosen_channel])
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (chosen_channel >= CY_SAR_SEQ_NUM_CHANNELS) // No channels available
|
|
result = CYHAL_ADC_RSLT_NO_CHANNELS;
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
// Don't set the ADC until here so that free knows whether we have allocated
|
|
// the channel on the parent ADC instance (and therefore doesn't try to free it if
|
|
// something fails further up)
|
|
obj->adc = adc;
|
|
obj->channel_idx = chosen_channel;
|
|
obj->adc->channel_config[chosen_channel] = obj;
|
|
obj->minimum_acquisition_ns = (cfg->min_acquisition_ns > CYHAL_ADC_MIN_ACQUISITION_TIME_NS)
|
|
? cfg->min_acquisition_ns : CYHAL_ADC_MIN_ACQUISITION_TIME_NS;
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
result = _cyhal_adc_populate_acquisition_timers(obj->adc);
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
uint32_t fw_ctrl_plus = _cyhal_adc_get_fw_switch_control(vplus, true);
|
|
uint32_t mux_ctrl_plus = _cyhal_adc_get_mux_switch_control(vplus);
|
|
|
|
_CYHAL_ADC_SET_SWITCH(obj->adc->base, fw_ctrl_plus, true);
|
|
Cy_SAR_SetSwitchSarSeqCtrl(obj->adc->base, mux_ctrl_plus, _CYHAL_ADC_SARSEQ_STATE(true));
|
|
|
|
if(CYHAL_ADC_VNEG != vminus)
|
|
{
|
|
uint32_t fw_ctrl_minus = _cyhal_adc_get_fw_switch_control(vminus, false);
|
|
uint32_t mux_ctrl_minus = _cyhal_adc_get_mux_switch_control(vminus);
|
|
|
|
_CYHAL_ADC_SET_SWITCH(obj->adc->base, fw_ctrl_minus, true);
|
|
Cy_SAR_SetSwitchSarSeqCtrl(obj->adc->base, mux_ctrl_minus, _CYHAL_ADC_SARSEQ_STATE(true));
|
|
}
|
|
|
|
result = cyhal_adc_channel_configure(obj, cfg);
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS != result)
|
|
{
|
|
cyhal_adc_channel_free(obj);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
cy_rslt_t cyhal_adc_channel_configure(cyhal_adc_channel_t *obj, const cyhal_adc_channel_config_t *config)
|
|
{
|
|
CY_ASSERT(NULL != obj);
|
|
|
|
obj->adc->base->CHAN_CONFIG[obj->channel_idx] = _cyhal_adc_channel_convert_config(config, obj->adc, obj->vplus, obj->vminus);
|
|
if(config->enabled)
|
|
{
|
|
obj->adc->base->CHAN_EN |= 1u << obj->channel_idx;
|
|
}
|
|
else
|
|
{
|
|
obj->adc->base->CHAN_EN &= ~(1u << obj->channel_idx);
|
|
}
|
|
_cyhal_adc_update_chan_offset(obj);
|
|
return _cyhal_adc_populate_acquisition_timers(obj->adc);
|
|
}
|
|
|
|
void cyhal_adc_channel_free(cyhal_adc_channel_t *obj)
|
|
{
|
|
if(obj->adc != NULL)
|
|
{
|
|
// Disable the channel, the unconfigure it
|
|
obj->adc->channel_config[obj->channel_idx] = NULL;
|
|
|
|
if(NC != obj->vplus)
|
|
{
|
|
uint32_t fw_ctrl_plus = _cyhal_adc_get_fw_switch_control(obj->vplus, true);
|
|
uint32_t mux_ctrl_plus = _cyhal_adc_get_mux_switch_control(obj->vplus);
|
|
|
|
_CYHAL_ADC_SET_SWITCH(obj->adc->base, fw_ctrl_plus, false);
|
|
Cy_SAR_SetSwitchSarSeqCtrl(obj->adc->base, mux_ctrl_plus, _CYHAL_ADC_SARSEQ_STATE(false));
|
|
}
|
|
|
|
if(NC != obj->vminus)
|
|
{
|
|
uint32_t mux_ctrl_minus = _cyhal_adc_get_mux_switch_control(obj->vminus);
|
|
uint32_t fw_ctrl_minus = _cyhal_adc_get_fw_switch_control(obj->vminus, false);
|
|
|
|
_CYHAL_ADC_SET_SWITCH(obj->adc->base, fw_ctrl_minus, false);
|
|
Cy_SAR_SetSwitchSarSeqCtrl(obj->adc->base, mux_ctrl_minus, _CYHAL_ADC_SARSEQ_STATE(false));
|
|
}
|
|
obj->adc->base->CHAN_CONFIG[obj->channel_idx] = 0;
|
|
obj->adc = NULL;
|
|
}
|
|
|
|
_cyhal_utils_release_if_used(&(obj->vplus));
|
|
_cyhal_utils_release_if_used(&(obj->vminus));
|
|
}
|
|
|
|
uint16_t cyhal_adc_read_u16(const cyhal_adc_channel_t *obj)
|
|
{
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
const uint8_t RESULT_SCALING_FACTOR = UINT16_MAX / ((1 << obj->adc->resolution) - 1); // 12-bit SAR resolution
|
|
#else
|
|
const uint8_t RESULT_SCALING_FACTOR = UINT16_MAX / 0xFFF; // constant 12-bit SAR resolution
|
|
#endif
|
|
int32_t signed_result = cyhal_adc_read(obj);
|
|
/* Legacy API for BWC. Convert from signed to unsigned by adding 0x800 to
|
|
* convert the lowest signed 12-bit number to 0x0.
|
|
*/
|
|
uint16_t unsigned_result = (uint16_t)(signed_result + 0x800);
|
|
/* The SAR provides a 12-bit result, but this API is defined to fill a full 16-bit range */
|
|
uint16_t scaled_result = unsigned_result * RESULT_SCALING_FACTOR;
|
|
return scaled_result;
|
|
}
|
|
|
|
int32_t cyhal_adc_read(const cyhal_adc_channel_t *obj)
|
|
{
|
|
uint32_t old_en_mask = 0u;
|
|
|
|
#if defined(CY_IP_MXS40PASS_SAR_INSTANCES)
|
|
bool isInterleaved = (CY_SAR_AVG_MODE_INTERLEAVED == (SAR_SAMPLE_CTRL(obj->adc->base) & SAR_SAMPLE_CTRL_AVG_MODE_Msk));
|
|
#else
|
|
bool isInterleaved = false;
|
|
#endif
|
|
bool isChannelAveraging = (obj->adc->base->CHAN_CONFIG[obj->channel_idx] & SAR_CHAN_CONFIG_AVG_EN_Msk);
|
|
if(!obj->adc->continuous_scanning)
|
|
{
|
|
/* Enable the selected channel only, then perform an on-demand conversion.
|
|
* Save the old enabled channel set to restore after we're done */
|
|
old_en_mask = SAR_CHAN_EN(obj->adc->base);
|
|
Cy_SAR_SetChanMask(obj->adc->base, 1U << obj->channel_idx);
|
|
obj->adc->conversion_complete = false;
|
|
|
|
// If interleaved averaging and average is enabled for this channel, set for
|
|
// continuous scanning and then stop the scan once we get a result. This is
|
|
// because the ADC hardware has a special case where it will not raise
|
|
// the EOC interrupt until AVG_COUNT scans have occurred when all enabled
|
|
// channels are using interleaved channels. This means that for the first AVG_COUNT - 1
|
|
// scans there will be no interrupt, therefore conversion_complete will never
|
|
// be set true, and therefore the loop below would be stuck waiting forever,
|
|
// never able to trigger a subsequent scan.
|
|
Cy_SAR_StartConvert(obj->adc->base, (isInterleaved && isChannelAveraging) ? CY_SAR_START_CONVERT_CONTINUOUS : CY_SAR_START_CONVERT_SINGLE_SHOT);
|
|
}
|
|
|
|
/* Cy_SAR_IsEndConversion relies on and clears the EOS interrupt status bit.
|
|
* We don't know how this read will be used in combination with interrupts,
|
|
* so implement our own interrupt-driven EOS flag
|
|
*/
|
|
while(!obj->adc->conversion_complete) { }
|
|
|
|
int32_t result = Cy_SAR_GetResult32(obj->adc->base, obj->channel_idx);
|
|
|
|
if(!obj->adc->continuous_scanning)
|
|
{
|
|
if(isInterleaved && isChannelAveraging)
|
|
{
|
|
Cy_SAR_StopConvert(obj->adc->base);
|
|
}
|
|
Cy_SAR_SetChanMask(obj->adc->base, old_en_mask);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int32_t cyhal_adc_read_uv(const cyhal_adc_channel_t *obj)
|
|
{
|
|
CY_ASSERT(NULL != obj);
|
|
|
|
int32_t counts = cyhal_adc_read(obj);
|
|
return Cy_SAR_CountsTo_uVolts(obj->adc->base, obj->channel_idx, counts);
|
|
}
|
|
|
|
void _cyhal_adc_start_async_read(cyhal_adc_t* obj, size_t num_scan, int32_t* result_list)
|
|
{
|
|
CY_ASSERT(NULL == obj->async_buff_next); /* Transfer already in progress */
|
|
uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
|
|
obj->async_scans_remaining = num_scan;
|
|
obj->async_buff_next = obj->async_buff_orig = result_list;
|
|
|
|
if(false == obj->continuous_scanning)
|
|
{
|
|
Cy_SAR_StartConvert(obj->base, CY_SAR_START_CONVERT_SINGLE_SHOT);
|
|
}
|
|
cyhal_system_critical_section_exit(savedIntrStatus);
|
|
}
|
|
|
|
cy_rslt_t cyhal_adc_read_async(cyhal_adc_t* obj, size_t num_scan, int32_t* result_list)
|
|
{
|
|
CY_ASSERT(NULL != obj);
|
|
obj->async_transfer_in_uv = false;
|
|
_cyhal_adc_start_async_read(obj, num_scan, result_list);
|
|
return CY_RSLT_SUCCESS;
|
|
}
|
|
|
|
cy_rslt_t cyhal_adc_read_async_uv(cyhal_adc_t* obj, size_t num_scan, int32_t* result_list)
|
|
{
|
|
CY_ASSERT(NULL != obj);
|
|
obj->async_transfer_in_uv = true;
|
|
_cyhal_adc_start_async_read(obj, num_scan, result_list);
|
|
return CY_RSLT_SUCCESS;
|
|
}
|
|
|
|
cy_rslt_t cyhal_adc_set_async_mode(cyhal_adc_t *obj, cyhal_async_mode_t mode, uint8_t dma_priority)
|
|
{
|
|
CY_ASSERT(NULL != obj);
|
|
CY_ASSERT(NULL == obj->async_buff_next); /* Can't swap mode while a transfer is running */
|
|
|
|
cy_rslt_t result = CY_RSLT_SUCCESS;
|
|
|
|
if(mode == CYHAL_ASYNC_DMA)
|
|
{
|
|
#if defined(CY_IP_M0S8CPUSSV3_DMAC) || defined(CY_IP_M4CPUSS_DMA) || defined(CY_IP_M4CPUSS_DMAC)
|
|
result = cyhal_dma_init(&(obj->dma), CYHAL_DMA_PRIORITY_DEFAULT, CYHAL_DMA_DIRECTION_PERIPH2MEM);
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
cyhal_dma_register_callback(&(obj->dma), &_cyhal_adc_dma_handler, obj);
|
|
cyhal_dma_enable_event(&(obj->dma), CYHAL_DMA_TRANSFER_COMPLETE, dma_priority, true);
|
|
}
|
|
#else
|
|
result = CYHAL_ADC_RSLT_BAD_ARGUMENT; // DMA not supported
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#if defined(CY_IP_M0S8CPUSSV3_DMAC) || defined(CY_IP_M4CPUSS_DMA) || defined(CY_IP_M4CPUSS_DMAC)
|
|
/* Free the DMA instances if we reserved them but don't need them anymore */
|
|
if(CYHAL_RSC_INVALID != obj->dma.resource.type)
|
|
{
|
|
cyhal_dma_free(&obj->dma);
|
|
obj->dma.resource.type = CYHAL_RSC_INVALID;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if(CY_RSLT_SUCCESS == result)
|
|
{
|
|
obj->async_mode = mode;
|
|
}
|
|
return CY_RSLT_SUCCESS;
|
|
}
|
|
|
|
void cyhal_adc_register_callback(cyhal_adc_t *obj, cyhal_adc_event_callback_t callback, void *callback_arg)
|
|
{
|
|
CY_ASSERT(NULL != obj);
|
|
|
|
uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
|
|
obj->callback_data.callback = (cy_israddress) callback;
|
|
obj->callback_data.callback_arg = callback_arg;
|
|
cyhal_system_critical_section_exit(savedIntrStatus);
|
|
}
|
|
|
|
void cyhal_adc_enable_event(cyhal_adc_t *obj, cyhal_adc_event_t event, uint8_t intr_priority, bool enable)
|
|
{
|
|
/* We always listen to EOS internally, so no need to update a hardware interrupt mask */
|
|
if(enable)
|
|
{
|
|
obj->user_enabled_events |= event;
|
|
}
|
|
else
|
|
{
|
|
obj->user_enabled_events &= ~event;
|
|
}
|
|
|
|
IRQn_Type irqn = _cyhal_adc_irq_n[obj->resource.block_num];
|
|
NVIC_SetPriority(irqn, intr_priority);
|
|
}
|
|
|
|
static cyhal_dest_t _cyhal_adc_calculate_dest(uint8_t block_num)
|
|
{
|
|
CY_ASSERT(block_num < _CYHAL_ADC_SAR_INSTANCES);
|
|
return _cyhal_adc_tr_in[block_num];
|
|
}
|
|
|
|
static cyhal_source_t _cyhal_adc_calculate_source(uint8_t block_num)
|
|
{
|
|
CY_ASSERT(block_num < _CYHAL_ADC_SAR_INSTANCES);
|
|
return _cyhal_adc_tr_out[block_num];
|
|
}
|
|
|
|
cy_rslt_t cyhal_adc_connect_digital(cyhal_adc_t *obj, cyhal_source_t source, cyhal_adc_input_t input)
|
|
{
|
|
if(input == CYHAL_ADC_INPUT_START_SCAN)
|
|
{
|
|
Cy_SAR_SetConvertMode(obj->base, CY_SAR_TRIGGER_MODE_FW_AND_HWEDGE);
|
|
cyhal_dest_t dest = _cyhal_adc_calculate_dest(obj->resource.block_num);
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
// On M0S8 the trigger type is not configurable so the type argument to connect_signal is ignored
|
|
// Therefore, we arbitrarily pick EDGE to satisfy the interface
|
|
return _cyhal_connect_signal(source, dest, CYHAL_SIGNAL_TYPE_EDGE);
|
|
#else
|
|
return _cyhal_connect_signal(source, dest, (cyhal_signal_type_t)TRIGGER_TYPE_PASS_TR_SAR_OUT);
|
|
#endif
|
|
}
|
|
|
|
return CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
|
|
cy_rslt_t cyhal_adc_enable_output(cyhal_adc_t *obj, cyhal_adc_output_t output, cyhal_source_t *source)
|
|
{
|
|
if(output == CYHAL_ADC_OUTPUT_SCAN_COMPLETE)
|
|
{
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
SAR_SAMPLE_CTRL(obj->base) |= SAR_SAMPLE_CTRL_EOS_DSI_OUT_EN_Msk;
|
|
#else
|
|
SAR_SAMPLE_CTRL(obj->base) |= SAR_SAMPLE_CTRL_TRIGGER_OUT_EN_Msk;
|
|
#endif
|
|
*source = _cyhal_adc_calculate_source(obj->resource.block_num);
|
|
return CY_RSLT_SUCCESS;
|
|
}
|
|
|
|
return CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
|
|
cy_rslt_t cyhal_adc_disconnect_digital(cyhal_adc_t *obj, cyhal_source_t source, cyhal_adc_input_t input)
|
|
{
|
|
if(input == CYHAL_ADC_INPUT_START_SCAN)
|
|
{
|
|
Cy_SAR_SetConvertMode(obj->base, CY_SAR_TRIGGER_MODE_FW_ONLY);
|
|
cyhal_dest_t dest = _cyhal_adc_calculate_dest(obj->resource.block_num);
|
|
return _cyhal_disconnect_signal(source, dest);
|
|
}
|
|
|
|
return CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
|
|
cy_rslt_t cyhal_adc_disable_output(cyhal_adc_t *obj, cyhal_adc_output_t output)
|
|
{
|
|
if(output != CYHAL_ADC_OUTPUT_SCAN_COMPLETE)
|
|
{
|
|
return CYHAL_ADC_RSLT_BAD_ARGUMENT;
|
|
}
|
|
|
|
#if defined(CY_IP_M0S8PASS4A_INSTANCES)
|
|
SAR_SAMPLE_CTRL(obj->base) &= ~SAR_SAMPLE_CTRL_EOS_DSI_OUT_EN_Msk;
|
|
#else
|
|
SAR_SAMPLE_CTRL(obj->base) &= ~SAR_SAMPLE_CTRL_TRIGGER_OUT_EN_Msk;
|
|
#endif
|
|
|
|
return CY_RSLT_SUCCESS;
|
|
}
|
|
|
|
#if defined(__cplusplus)
|
|
}
|
|
#endif
|
|
|
|
#endif /* defined(CY_IP_MXS40PASS_SAR_INSTANCES) */
|