Source code for philander.adc

"""Abstraction module for analogue-to-digital conversion.

Provide a convergence layer API to abstract from several different
ADC implementing driver modules possibly installed on the target
system.
"""
__author__ = "Oliver Maye"
__version__ = "0.1"
__all__ = ["ADC"]

import logging

from philander.module import Module
from philander.sysfactory import SysProvider, SysFactory
from philander.systypes import ErrorCode


[docs] class ADC( Module ): """Analogue-to-digital converter abstraction class. Provide access to and control over the underlying ADC hardware. For that, an implementing driver module is used. As a convergence layer, this class is to hide specifics and to level syntactic requirements of the implementing package. """ # Static constants, independent from the implementation CHANNEL_DIE_TEMP = -1 # Internal ADC channel for die/core temp. DEFAULT_SAMPLING_TIME = 100 # default sampling time in microseconds DEFAULT_VREF_LOWER = 0 # default lower reference Voltage VRef- DEFAULT_VREF_UPPER = 3000 # default upper reference Voltage VRef+ # Constants, that may depend on the implementing class DIGITAL_MAX = 0xFFFF # Maximum digital value
[docs] @staticmethod def getADC( provider=SysProvider.AUTO ): """Generates an ADC implementation according to the requested provider. :param SysProvider provider: The low-level lib to rely on, or AUTO\ for automatic detection. :return: An ADC implementation object, or None in case of an error. :rtype: ADC """ deps = [(SysProvider.MICROPYTHON, "machine", "ADC"), (SysProvider.COMPOSITE, "philander.stadc1283", "STADC1283"), ] impls = { SysProvider.MICROPYTHON: ("philander.adc_micropython", "_ADC_Micropython"), SysProvider.COMPOSITE: ("philander.stadc1283", "STADC1283"), SysProvider.SIM: ("philander.adc_sim", "_ADC_Sim"), } if provider == SysProvider.AUTO: provider = SysFactory.autoDetectProvider( deps, SysProvider.SIM ) ret = SysFactory.createInstance( provider, impls ) return ret
def __init__(self): """Initialize the instance with defaults. Note that just after construction, the instance is not operable, yet. Call :meth:`open` to configure it and set it into a functional state. """ self._adc = None self.channel = None self.isOpen = False self.provider = SysProvider.NONE self.samplingTime = 0 # show it's not set self.vref_lower = ADC.DEFAULT_VREF_LOWER self.vref_upper = ADC.DEFAULT_VREF_UPPER # # Module API #
[docs] @classmethod def Params_init(cls, paramDict): """Initialize parameters with their defaults. The given dictionary should not be None, on entry. Options not present in the dictionary will be added and set to their defaults on return. The following options are supported. ================== ============================================== ========================= Key Range Default ================== ============================================== ========================= adc.channel pin name or channel number (e.g. 2 or "2") None adc.samplingTime Sampling time in microseconds; integer>=0 None adc.vref.lower lower reference voltage in mV; integer :attr:`DEFAULT_VREF_LOWER` adc.vref.upper upper reference voltage in mV; integer :attr:`DEFAULT_VREF_UPPER` ================== ============================================== ========================= :param dict(str, object) paramDict: Configuration parameters as obtained from :meth:`Params_init`, possibly. :return: none :rtype: None """ defaults = { "adc.vref.lower": ADC.DEFAULT_VREF_LOWER, "adc.vref.upper": ADC.DEFAULT_VREF_UPPER, } for key, value in defaults.items(): if not key in paramDict: paramDict[key] = value return None
[docs] def open(self, paramDict): """Opens the instance and sets it in a usable state. Allocate necessary hardware resources and configure user-adjustable parameters to meaningful defaults. This function must be called prior to any further usage of the instance. Involving it in the system ramp-up procedure could be a good choice. After usage of this instance is finished, the application should call :meth:`close`. :param dict(str, object) paramDict: Configuration parameters as obtained from :meth:`Params_init`, possibly. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ ret = ErrorCode.errOk # Retrieve defaults defaults = {} self.Params_init(defaults) # Scan parameters self.channel = paramDict.get("adc.channel", None) if self.channel is None: ret = ErrorCode.errFewData else: if not isinstance( self.channel, int ): try: num = int(self.channel) self.channel = num except ValueError: pass self.samplingTime = paramDict.get("adc.samplingTime", 0) self.vref_lower = paramDict.get("adc.vref.lower", defaults["adc.vref.lower"]) self.vref_upper = paramDict.get("adc.vref.upper", defaults["adc.vref.upper"]) if self.vref_lower < self.vref_upper: self.isOpen = True else: ret = ErrorCode.errInvalidParameter logging.debug("ADC base> open <%s> returns %s.", self.channel, ret) return ret
[docs] def close(self): """Closes this instance and releases associated hardware resources. This is the counterpart of :meth:`open`. Upon return, further usage of this instance is prohibited and may lead to unexpected results. The instance can be re-activated by calling :meth:`open`, again. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ ret = ErrorCode.errOk if self.isOpen: self.isOpen = False self._adc = None else: ret = ErrorCode.errResourceConflict logging.debug("ADC base> close <%s> returns %s.", self.channel, ret) return ret
[docs] def setRunLevel(self, level): """Select the power-saving operation mode. Switches the instance to one of the power-saving modes or recovers from these modes. Situation-aware deployment of these modes can greatly reduce the system's total power consumption. :param RunLevel level: The level to switch to. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ del level return ErrorCode.errNotImplemented
# # ADC specific API #
[docs] def getDigital(self): """Retrieve a sample and return its digital value. Gives the most recent sample as a digital value between zero and the maximum digital value [0...DIGITAL_MAX]. :return: A value in the range [0, DIGITAL_MAX] and an error code\ indicating either success or the reason of failure. :rtype: int, ErrorCode """ err = ErrorCode.errNotImplemented val = 0 return val, err
[docs] def getVoltage(self): """Retrieve a sample and return it as a voltage in mV. Gives the most recent sample as a voltage value between the lower and upper reference voltage, expressed as milli Volts. Calibration and temperature correction is at the discretion of the implementation and hence, cannot be assured. :return: A mV-value in the range [vref_lower, vref_upper] and\ an error code indicating either success or the reason of failure. :rtype: int, ErrorCode """ val = self.vref_lower dval, err = self.getDigital() if err.isOk(): val, err = self.toVoltage( dval ) return val, err
[docs] def toVoltage(self, digital): """Convert a digital value to its corresponding voltage in mV. Map the digital sample value to the range of [vref_lower, vref_upper] and return the corresponding voltage expressed in millivolts [mV]. :param int digital: The digital value to convert. Must be in [0, 0xFFFF]. :return: A mV-value in the range [vref_lower, vref_upper] and\ an error code indicating either success or the reason of failure. :rtype: int, ErrorCode """ err = ErrorCode.errOk val = self.vref_lower if not self.isOpen: err = ErrorCode.errResourceConflict elif not isinstance(digital, int): err = ErrorCode.errInvalidParameter elif (digital < 0) or (digital > self.DIGITAL_MAX): err = ErrorCode.errSpecRange else: val = digital * (self.vref_upper-self.vref_lower) val = (val + (self.DIGITAL_MAX // 2)) // self.DIGITAL_MAX val = val + self.vref_lower return val, err