####################
Quasi ADC using GPIO
####################

************
Introduction
************

`xcore.ai` devices offer an inexpensive way to read the value of a variable resistor (rheostat) or potentiometer without requiring an external ADC. The performance is suitable for applications such as reading the position of an analog slider for gain control. Effective resolutions exceeding eight bits can be achieved, which is sufficient for many control applications.

The Quasi ADC (QADC) relies on the stable input threshold of the `xcore.ai` IO, around 1.15 V when Vddio is 3.3 V. By charging a capacitor to the supply rail and discharging it through a resistor, the RC time constant — and hence the resistance value — can be measured by timing the voltage transition.

The `xcore` provides precise IO timing through its port logic (10 ns resolution in this case), allowing a reasonably accurate ADC to be implemented with just a couple of passive components and supporting software.

Two schemes are available, each with advantages and disadvantages depending on the application. Unless tight component tolerances or manufacturing calibration are possible, the potentiometer scheme is recommended. Table 1 summarises the two approaches.

.. _fig_src_filters:
.. list-table:: QADC Comparison
     :widths: 30 40 40
     :header-rows: 1

     * - Item
       - Rheostat reader
       - Potential reader
     * - Minimum scale
       - 0% + small dead zone
       - 0% + small dead zone
     * - Maximum scale
       - Dependent on end to end track tolerance
       - 100% + small dead zone
     * - Possible point of inflection midway
       - No
       - Yes at 35% travel if tolerance of components poor
     * - Typical max counts
       - ~10000
       - ~10000
     * - Required VDDIO tolerance
       - 5%
       - 5%
     * - Typical minimum conversion time per channel
       - ~0.2 - 0.5 milliseconds
       - ~0.2 - 0.5 milliseconds
     * - Supported port widths
       - 1 bit ports only
       - Any port width. Arrays of ports must be of same type.
     * - Number of channels
       - Limited by port count only
       - Limited by port count only
     * - Typical ENOBs post filtering
       - 8+
       - 8+
     * - Requires 5 % capacitor (eg C0G)
       - Yes
       - Yes
     * - Requires calibration
       - Yes, if passive components worse than 10 % tolerance. Will improve full scale estimated position.
       - Normally OK up to around 20 % passive tolerance. 10% will improve linearity.
     * - Linearity
       - Good
       - Reasonable, depending on passive tolerance
     * - Monotonic
       - Yes
       - Yes
     * - Memory usage (8 bit, two channels)
       - 3 kB
       - 5 kB

Noise is always a concern in the analog domain, and the QADC is no exception. In particular, power supply stability and signal coupling (for example, when routing the QADC input close to digital IO signals) should be considered when designing the circuitry.

Since the QADC relies on continuously charging and discharging a capacitor, it is recommended that any analog supplies on the board be kept separate from the `xcore` digital supply. This helps prevent noise generated during the QADC conversion process from coupling into parts of the system where it would be unwelcome.

|newpage|

***************
Rheostat Reader
***************

The rheostat reader uses just two terminals of a potentiometer and treats it as variable resistor (rheostat). The scheme works as follows:

- Charge capacitor via the port by driving a `one` and waiting for at least 5 maximum RC charge periods.
- Make IO open circuit which initiates the discharge. Take the port timer at this point. Setup a ``pinseq(0)`` event on the port to capture the transition to zero.
- Wait for transition to a read `zero` and take the stop timestamp.
- Set the port to high impedance because there is no point in fully discharging the capacitor.
- Calculate difference the difference in time.
- Post process value to reduce noise and improve linearity.

The rheostat reader currently supports only arrays of 1 bit ports.


.. _fig_qadc_rheo_schem:
.. figure:: images/qadc_rheo_schem.drawio.png
   :width: 60%

   QADC Rheostat Circuit

The rheostat reader offers excellent linearity; however, it is sensitive to full-scale accuracy when the passive components have wide tolerances. For example, with 20% tolerances, the full scale may be reached at only 80% of the travel, or conversely, only 80% may be registered at the end of the travel. See :ref:`effect of passive components <effect_passives>` for more details.


.. _fig_qadc_rheo_ticks:
.. figure:: images/qadc_rheo_ticks.png
   :width: 80%

   QADC Rheostat Timer Ticks vs Position

.. note::
    If the QADC input pin is left disconnected you will see a full scale output.

|newpage|

****************
Potential Reader
****************

The potential reader uses all three terminals of a potentiometer where the track end terminals are connected between ground and Vddio. Depending on the initial reading of the IO pin, the QADC either charges the capacitor to Vddio or discharges it ground and then times the transition through the threshold point to the potential set by the potentiometer, via the equivalent resistance of the potentiometer. The equivalent resistance of the potentiometer is the parallel of the upper and lower sections between the wiper and the end terminals added to the series resistor.

Due to the reasonably complex calculation required to determine the estimated position from the transition time, which includes several precision multiplies, divides and a logarithm, a look up table (LUT) is pre-calculated at initialisation to make the conversion step more CPU efficient.


.. _fig_qadc_pot_ticks:
.. figure:: images/qadc_pot_ticks.png
   :width: 80%

   QADC Potentiometer Timer Ticks vs Position


.. _fig_qadc_pot_par_res:
.. figure:: images/qadc_pot_par_res.pdf
   :width: 80%

   QADC Potentiometer Equivalent Resistance vs Position for 25 kOhm Component


The scheme works as follows:

- Read the current port value to see if voltage of the potentiometer is above or below threshold.
- Set the inverse port value and wait to charge capacitor fully to the opposite supply rail.
- Set the port to high impedance and take a timestamp.
- Take a timestamp when voltage crosses threshold.
- Use the lookup table to calculate the start voltage.
- Post process value to reduce noise and improve linearity.

The rheostat reader currently supports arrays of any port width with the proviso that all ports are the same width.

The potential reader offers good performance and is less susceptible to component tolerances due to the mathematics of using a parallel resistor network and the logarithm used. It will always achieve zero and full scale however if tolerances are too large then it may show worse non-linearity than the rheostat reader and, in particular, around the 35% setting point which corresponds the threshold voltage of the IO. It does however always remain monotonic in operation. See the :ref:`effect of passive components <effect_passives>` section for more details.

A small amount of noise is present when taking readings close to the threshold point. A moving average filter is typically used and so these non-linearities are reduced in practice and more than eight bits of resolution can easily be achieved.


.. _fig_qadc_pot_schem:
.. figure:: images/qadc_pot_schem.drawio.png
   :width: 60%

   QADC Potentiometer Circuit



.. _fig_qadc_pot_equiv_schem:
.. figure:: images/qadc_pot_equiv_schem.drawio.png
   :width: 60%

   QADC Potentiometer Equivalent Circuit


.. note::
    If the QADC input pin is left disconnected you will likely see a value of 35 % of full scale for the 1b port version and a value of close to zero for the wide port version.

|newpage|

Post Processing
===============

Both QADC schemes benefit from post processing of the raw measured transition time to improve performance.


.. _fig_post_proc:
.. figure:: images/qadc_post_proc.drawio.png
   :width: 80%

   QADC Post Processing Steps

The included post processing steps are as follows:



Moving Average Filter
---------------------

The moving average filter (sometimes know as a Boxcar FIR) helps filter out noise from the raw signal. It uses the conversion of history and takes the average value of the conversion and effectively low-pass filters the signal. One filter is provided per channel and the depth of the filter is configurable. A typical depth of 32 has been found to provide a good performance. Due to the low pass effect very long filters will reduce the response time of the QADC.

Scaling
-------

Scaling typically means reducing the raw resolution of the ADC from 12 - 13 bits and quantising it to a typical bit resolution such at 8, 9 or 10 bits. This provides a signal which has a know range, for example, 0 - 511 for the 9 bit case. This step also offers the possibility of calibration where the tolerance of the passive components may affect the estimated position of the input.

Hysteresis
----------

Even after filtering it may still be possible to see some small noise signal depending on configuration. This may also be exaggerated due to the natural quantisation to a digital value by the QADC, particularly if the setting is close to a transition point. By adding a small hysteresis (say a value of one or two) additional stability can be achieved at the cost of a very small dead zone at the last position. This may desirable if the QADC output is controlling a parameter that may be noticeable if it hunts between one or more positions. The hysteresis is configurable and may be removed completely if needed by setting to 0.


|newpage|

.. _effect_passives:

Comparing the Effect of Passive Component Tolerance on Both Schemes
===================================================================

Both schemes offered will work very well when the overall passive component tolerances are good (e.g. 5%). However typical variable resistors/potentiometers are designed to produce good relative resistances rather than absolute resistances and often the end-to-end resistance tolerance can be as high as 20%. The QADC relies more on absolute resistances, particularly the rheostat approach.

When passive component tolerances are poor we see differing effects on the real-life transfer curves of ``actual position`` versus ``estimated position`` depending on the scheme used.

For the ``Rheostat`` approach we see the good linearity and zero scale performance is always retained, however, full scale is directly affected. For example, if the resistor tolerance is 20% too low then the time constant will be smaller than expected and the maximum setting that can be achieved is 80% even at full travel (orange curve). This can be seen in :ref:`the rheostat transfer curve <fig_qadc_rheo_tol>`. If the resistor tolerance is 20% too high then full scale will be achieved at 80% travel and the last 20% of travel will give the same reading of full scale (green curve).

The small step close to zero is caused by the QADC not being able to charge the capacitor past the threshold voltage at low setting due to the required series resistor.

If a manufacturing test is an option to calibrate the component values then this is likely the best approach to adopt.

.. _fig_qadc_rheo_tol:
.. figure:: images/qadc_rheo_tol.png
   :width: 80%

   QADC Rheostat Effect of 20% Tolerance on Transfer Curve


The `Potentiometer` approach is more tolerant to the overall end to end resistance since it's operation also relies on the starting potential as well as the equivalent series resistance at any given setting, which itself is a function of the end-to-end track resistance. Even when tolerance is 20% out the end positions will always achieve zero and full scale however linearity is slightly degraded and an inflection point may be seen at around 1/3 of the travel.

The curve will always remain monotonic increasing however the effect of noise (present in all ADCs) and the use of post processing (filtering and hysteresis) reduces the real life effect to a point where it may be unnoticeable.

Where the potentiometer end to end resistance is lower or higher than than set in the model, the inflection effect will be see due to the RC time constant being shorter or longer than expected. This can be seen in :ref:`the potentiometer transfer curve <fig_qadc_pot_tol>`.

The small steps in the transfer curve close to zero and full scale settings are caused by the QADC not being able to charge the capacitor past the threshold voltage due to the potential divider effect of the series resistor and the potentiometer equivalent series resistance. Increasing the potentiometer value and decreasing the series resistor will reduce this effect.

.. note::
    Overall, it is recommended to use the `Potentiometer` approach in cases where the potentiometer tolerance is between 10% and 20% and including a manufacturing calibration step is not practical.


.. _fig_qadc_pot_tol:

.. figure:: images/qadc_pot_tol.png
   :width: 80%

   QADC Potentiometer Effect of 20% Tolerance on Transfer Curve


This theoretical behavior has been verified as shown in the :ref:`Hardware Characterisation <characterise>` section of this document.

|newpage|

.. _passive_selection:

Passive Component Selection
===========================

There are three components to consider when building one channel of QADC.

The variable resistor should be typically in the order of 20 - 50 kOhms, say 47 kOhms as a starting point. A lower value such as 10 kOhms may be used but it will either reduce the accuracy of the QADC slightly due to the increasing effect of the (required) series resistor and a reduced count or require the inclusion a larger capacitor to compensate which will increase power consumption due to greater charge/discharge amounts. The practical effect of this will also to be to increase the step sizes seen at the end positions of the transfer curves.

Choosing a value of 100 kOhms or above may also decrease performance due to PCB parasitics or IO input leakage affecting the accuracy and increasing the actual RC realtive to the model.

The capacitor value should by typically around 2 - 5 nF with the same trade-offs being seen as that of the variable resistor. A larger value is acceptable but it will increase the conversion time. A smaller value will increase noise and the effects of PCB stray capacitance may start to emerge. A 5 % tolerance C0G or similar capacitor is recommended although any type with good voltage vs. capacitance characteristics and 5 % tolerance is acceptable. Typical X7R decoupling capacitors are not ideal due to their negative voltage coefficient of capacitance which means the capacitance varies based on voltage. X7R are also much less stable over normal temperature ranges.

The series resistor value is a compromise. Ideally it would set to a low value to reduce the small step effects in the transfer curve however this increases the current draw on the IO pin when the slider is close to end settings, which is undesirable and may cause noise. Typically a value of 1 % of the variable resistor value maximum is applicable with a minimum being around 330 ohms to limit the inrush current to the capacitor from the charging phase. Smaller values may cause unwanted EMI when the IO pin charges the capacitor although the IO drive settings are set to the minimum of 2 mA in QADC to minimise this effect.

Typical values recommended are:

.. list-table:: Recommended passive values for QADC
   :widths: 20 25 25 25
   :header-rows: 1

   * - Capacitor value
     - Potentiometer value
     - Series resistor value
     - Conversion cycle time
   * - 2200 pF 5%
     - 47 - 100 kOhms, 20 % or better (10 % ideally)
     - 330 - 470 Ohms, 5 % or better
     - 1 millisecond (= 100,000 10 MHz timer ticks)


.. _tuning:

|newpage|

QADC Tuning
===========

Once the :ref:`passive components <passive_selection>`  have been selected then you can configure your QADC. Both schemes share a common configuration of type ``qadc_config_t`` as shown in the :ref:`API section <api>`.

.. note::
    It is highly recommended to prototype the values and test the configuration before deployment. There are natural sources of error in any analog scheme and these should be evaluated by the user.

The ``qadc_config_t`` configuration can be initialised (using C in this example rather than XC) as follows:


.. literalinclude:: ../../tests/qadc_c_interface/src/main.c
   :start-at: const qadc_config_t adc_config
   :end-at: .convert_interval_ticks


The passive component selection should be directly inputted into the structure and nominal values of ``3.3`` and ``1.15`` used for the IO voltage and threshold voltage.

The final three settings require some thought and are described below.

How to set auto_scale
---------------------

Autoscale works by measuring the time taken to reach the conversion result. If it takes longer than expected (full scale for rheostat or 35% setting for potentiometer) then it trims the max value so that the reading can be made more accurate during the following runtime of the QADC (until reset).

It can help cases where the RC constant is larger than expected however it is not possible to detect where the RC constant is smaller than expected because this is indistiguishable from a normal lower setting.

It may be helpful to use this setting if you choose to set the RC settings in the code a little lower than nominal to achieve better range. However it must be noted that the first full transition of QADC must complete before the new max is learnt and so will behave slightly differently to subsequent transitions.

Set this value to 0 by default.


How to set convert_interval_ticks
---------------------------------

This parameter is only relevant to ``continuous`` mode where a task cycles through the QADCs. It sets the total period per conversion which includes charging the capacitor, measuring the discharge periods and an idle time at the end to allow the capacitor to reach it's natural voltage governed by the external passives.

The QADC will check the set values and automatically assert if the following condition is not met::

    convert_interval_ticks > (max_charge_period_ticks + max_disch_ticks * 2)

The ``max_charge_period_ticks`` is nominally 5 times the RC constant and ``max_disch_ticks`` is calculated by the code as the maximum time to reach the threshold voltage when the IO goes high impedance. This is doubled to allow for some idle time and provides a safe setting. A typical setting will be in the region of one millisecond, depending on passive value selection.

In ``single shot`` mode this setting is ignored because the API takes the correct amount of time to account for all required steps and the function returns when the result is ready.

|newpage|

QADC Usage
==========

There are three main modes of operation for the QADC.

Continuous Modes
----------------

If many channels are needed and ``continuous`` updates are required then it is convenient to run a task which performs background continuous conversion and associated filtering. This requires a dedicated hardware thread.

The values may then be read by the application either:

   - Over a channel (application on same or different tile from the QADC) or
   - By shared memory (same tile only) using the adc_xxx_state.results member which is a pointer to an array of ``unit16_t`` result values.

The examples included in ``/examples`` show both continuous modes in use.

Single Shot Mode
----------------

A ``single shot`` API is also available which allows a single conversion to be performed by calling a function. Note that the function call is blocking and will return only when the conversion is complete. This will typically take a few hundred microseconds for the recommended passive component selection. Larger RC values result in a longer conversion time.

When infrequent conversions are made using ``single shot`` mode it is recommended to reduce the depth of the moving average filter down (it can be set to 1 and above) to the actual number conversions performed for each desired QADC value.

The examples included in ``/examples`` show the single shot mode in use.

**************
Resource Usage
**************

The Rheostat reader requires 3 kB and either one thread (continuous operation) or ~300 microseconds of CPU time per conversion.

For a two channel, 8 bit (256 output levels) with a 16 entry moving average filter, the Potentiometer reader requires 5 kB and either one thread (continuous operation) or around ~300 microseconds of processing time per conversion.

********************
Example applications
********************

Building the examples
=====================

This section assumes that the `XMOS XTC Tools <https://www.xmos.com/software-tools/>`_ have been
downloaded and installed. The required version is specified in the accompanying ``README``.

Installation instructions can be found `here <https://xmos.com/xtc-install-guide>`_.

Special attention should be paid to the section on
`Installation of Required Third-Party Tools <https://www.xmos.com/documentation/XM-014363-PC/html/installation/install-configure/install-tools/install_prerequisites.html>`_.

The application is built using the `xcommon-cmake <https://www.xmos.com/file/xcommon-cmake-documentation/?version=latest>`_
build system, which is provided with the XTC tools and is based on `CMake <https://cmake.org/>`_.

The ``lib_qadc`` software ZIP package should be downloaded and extracted to a chosen working
directory.

Ensure that the relevant parameters have been adjusted to match the design:

.. literalinclude:: ../../examples/app_pot_reader/src/main.xc
   :start-at: #define NUM_ADC
   :end-at: on tile[1]: port p_adc[]

.. literalinclude:: ../../examples/app_pot_reader/src/main.xc
   :start-at: const unsigned capacitor_pf
   :end-at: const unsigned convert_interval_ticks

To configure the build, the following commands should be run from an XTC command prompt::

    cd lib_qadc/examples/
    cmake -G "Unix Makefiles" -B build

If any dependencies are missing they will be retrieved automatically during this step.

The application binaries should then be built using ``xmake``::

    xmake -j -C build

Binary artifacts (.xe files) will be generated under the appropriate subdirectories of the
``app_pot_reader/bin`` and ``app_rheo_reader/bin`` directories — one for each supported build
configuration.

For subsequent builds, the ``cmake`` step may be omitted.
If ``CMakeLists.txt`` or other build files are modified, ``cmake`` will be re-run automatically
by ``xmake`` as needed.


Running the examples
====================

The examples are designed to run on the XK-EVK-XU316 kit, although any `xcore.ai` based hardware
will also work, provided the QADC ports are adjusted accordingly. The required passive circuitry
should be connected to the QADC input pins.

From an XTC command prompt, one of the following commands should be run from the ``examples``
directory::

    xrun --xscope app_pot_reader/bin/SINGLE/qadc_pot_example_SINGLE.xe
    xrun --xscope app_pot_reader/bin/CONTINUOUS_CHAN/qadc_pot_example_CONTINUOUS_CHAN.xe
    xrun --xscope app_pot_reader/bin/CONTINUOUS_MEM/qadc_pot_example_CONTINUOUS_MEM.xe
    xrun --xscope app_rheo_reader/SINGLE/bin/qadc_rheo_example_SINGLE.xe
    xrun --xscope app_rheo_reader/CONTINUOUS_CHAN/bin/qadc_rheo_example_CONTINUOUS_CHAN.xe
    xrun --xscope app_rheo_reader/CONTINUOUS_MEM/bin/qadc_rheo_example_CONTINUOUS_MEM.xe

In each case the converted QADC values will be periodically printed to the console, for example::

    Running QADC in continuous mode using dedicated task!
    Read channel ch 0: 558, ch 1: 328,
    Read channel ch 0: 558, ch 1: 330,
    Read channel ch 0: 558, ch 1: 342,
    Read channel ch 0: 536, ch 1: 364,
    Read channel ch 0: 488, ch 1: 428,
    Read channel ch 0: 420, ch 1: 490,
    Read channel ch 0: 373, ch 1: 516,
    Read channel ch 0: 337, ch 1: 544,
    Read channel ch 0: 305, ch 1: 570,
    Read channel ch 0: 277, ch 1: 592,
    Read channel ch 0: 251, ch 1: 610,
    Read channel ch 0: 219, ch 1: 634,
    ...

Alternatively, the applications can be programmed into flash memory for standalone execution::

    xflash app_pot_reader/bin/SINGLE/qadc_pot_example_SINGLE.xe

|newpage|

.. _characterise:

Hardware Characterisation of QADC Potentiometer Transfer Curve
==============================================================

A bench characterisation of the QADC potentiometer was conducted to verify the model. A 10 bit ADC was connected to the potentiometer and the voltage reading (reference voltage was logged). The QADC was configured to 10 bits then enabled and the estimated potentiometer setting was logged against the reference.

In this case, less than ideal settings of 10 k for the potentiometer was used (causing a noticeable step at the end of the transfer curve) however the effect of 0 %, 20 % and -20 % deviation in the specified RC constant against the values used can be seen.

.. figure:: images/calib_table_0.png
   :width: 80%

   QADC Potentiometer Transfer curve with 0 % Tolerance RC settings


.. figure:: images/calib_table_-20.png
   :width: 80%

   QADC Potentiometer Transfer curve with -20 % Tolerance RC settings


.. figure:: images/calib_table_20.png
   :width: 80%

   QADC Potentiometer Transfer curve with +20 % Tolerance RC settings


It can be seen that, although the linearity is affected by +- 20% tolerance, the transfer curve remains monotonic and the end positions can always be reached. It also confirms that the model closely matches the practical operation of the QADC.

The python model of the QADC may be found in the ``/design`` directory and is used in the regression test to ensure the embedded version matches.

|newpage|

.. _api:

*************
API Reference
*************

Common API
==========

Common items for both types of QADC are shown here.

.. note::
    Depending on whether QADC is called from an XC program with a par{} or from C with PAR_JOBS()
    extra hardware setup may be needed. If using PAR_JOBS() call qadc_pre_init_c() before
    QADC initialisation.

See the :ref:`QADC Tuning <tuning>` section for more details on setting these values.

.. doxygenstruct:: qadc_config_t
    :members:

.. doxygengroup:: lib_qadc_common
   :content-only:


QADC Rheostat API
=================

Specific items for the Rheostat QADC are shown here.


.. doxygenstruct:: qadc_rheo_state_t

.. doxygengroup:: lib_qadc_rheo_reader
   :content-only:

QADC Potentiometer API
======================

Specific items for the Potentiometer QADC are shown here.

.. doxygenstruct:: qadc_pot_state_t

.. doxygengroup:: lib_qadc_pot_reader
   :content-only:

