Source code for philander.stc311x

"""A module to provide base classes and data types for ST gas gauge driver implementations.
"""
__author__ = "Carl Bellgardt"
__version__ = "0.1"
__all__ = ["STC311x", "OperatingMode"]

import time
from philander.penum import Enum, unique, auto, idiotypic

from philander.battery import Status as BatStatus, Level as BatLevel
from philander.gasgauge import GasGauge, SOCChangeRate, StatusID
from philander.gpio import GPIO
from philander.interruptable import Interruptable, Event
from philander.primitives import Current, Voltage, Percentage, Temperature
from philander.serialbus import SerialBusDevice
from philander.stc311x_reg import STC311x_Reg
from philander.sysfactory import SysFactory
from philander.systypes import ErrorCode, RunLevel, Info


[docs] @unique @idiotypic class OperatingMode(Enum): opModeUnknown = auto() opModeStandby = auto() opModeVoltage = auto() opModeMixed = auto()
[docs] class STC311x(GasGauge, SerialBusDevice, Interruptable): """Base implementation for the stc311x gas gauge chip family. A gas gauge allows to keep track of the state of charge (SOC), remaining capacity, current voltage etc. of a battery. Info about the specific gas gauge ICs can be found at https://www.st.com/en/power-management/stc3115.html or https://www.st.com/en/power-management/stc3117.html """ ADDRESSES_ALLOWED = [0x70] MODEL_ID = None # to be defined in sub-classes RSENSE_DEFAULT = 10 # default Rsense, in mOhm BAT_CAPACITY_DEFAULT = 800 # default battery capacity in mAh BAT_IMPEDANCE_DEFAULT= 200 # default battery impedance in mOhm ALARM_SOC_DEFAULT = 1 # default SOC alarm threshold [%] ALARM_VOLTAGE_DEFAULT = 3000 # default voltage alarm threshold [mV] RELAX_CURRENT_DEFAULT = 5000 # default current monitoring threshold [uA] RELAX_TIMER_DEFAULT = 480 # default current monitoring timer [s] # Constants needed only for the implementation POR_TIMEOUT = 3 # POR timeout i seconds def __init__(self): SerialBusDevice.__init__(self) Interruptable.__init__(self) self.REGISTER = None # chip specific register information self.pinInt = None self.RSense = None # Sense resistor in milli Ohm self.batCapacity = None # Battery capacity in mAh self.batImpedance= None # Battery impedance in mOhm. self.alarmSOC = None # SOC alarm threshold [%] self.alarmVoltage = None # Voltage alarm threshold [mV] self.relaxCurrent = None # Current monitoring threshold [uA] self.relaxTimerCC2VM = None # Current monitor timing CC->VM [s] def _getOperatingMode(self): data, err = self.readByteRegister(self.REGISTER.REG_MODE) if err.isOk(): if data & self.REGISTER.MODE_GG_RUN: if data & self.REGISTER.MODE_VMODE: ret = OperatingMode.opModeVoltage else: ret = OperatingMode.opModeMixed else: ret = OperatingMode.opModeStandby else: ret = OperatingMode.opModeUnknown return ret # # Module API #
[docs] @classmethod def Params_init(cls, paramDict): """Initializes configuration parameters with defaults. The following settings are supported: ================================= ========================================================================================================== Key name Value type, meaning and default ================================= ========================================================================================================== SerialBusDevice.address ``int`` I2C serial device address; default is :attr:`ADDRESSES_ALLOWED` [0]. Gasgauge.SenseResistor ``int`` Current sense resistor Rs in mOhm [5...50]; default is ``RSENSE_DEFAULT`` Gasgauge.battery.capacity ``int`` Battery capacity in mAh; default is ``BAT_CAPACITY_DEFAULT`` Gasgauge.battery.impedance ``int`` Battery impedance in mOhm; default is ``BAT_IMPEDANCE_DEFAULT`` Gasgauge.alarm.soc ``int`` SOC alarm threshold [%]; default is ``ALARM_SOC_DEFAULT`` Gasgauge.alarm.voltage ``int`` Voltage alarm threshold [mV]; default is ``ALARM_VOLTAGE_DEFAULT`` Gasgauge.relax.current ``int`` Current monitoring threshold [uA]; default is ``RELAX_CURRENT_DEFAULT`` Gasgauge.relax.timer ``int`` Current monitoring timer count [s]; default is ``RELAX_TIMER_DEFAULT`` Gasgauge.int.gpio.* ALM pin configuration; See :meth:`.GPIO.Params_init`. =============================================================================================================================================== Also see: :meth:`.Gasgauge.Params_init`, :meth:`.SerialBusDevice.Params_init`, :meth:`.GPIO.Params_init`. """ def_dict = { "SerialBusDevice.address": cls.ADDRESSES_ALLOWED[0], "Gasgauge.SenseResistor": cls.RSENSE_DEFAULT, "Gasgauge.battery.capacity": cls.BAT_CAPACITY_DEFAULT, "Gasgauge.battery.impedance": cls.BAT_IMPEDANCE_DEFAULT, "Gasgauge.alarm.soc": cls.ALARM_SOC_DEFAULT, "Gasgauge.alarm.voltage": cls.ALARM_VOLTAGE_DEFAULT, "Gasgauge.relax.current": cls.RELAX_CURRENT_DEFAULT, "Gasgauge.relax.timer": cls.RELAX_TIMER_DEFAULT, "Gasgauge.int.gpio.direction": GPIO.DIRECTION_IN, "Gasgauge.int.gpio.trigger": GPIO.TRIGGER_EDGE_FALLING, "Gasgauge.int.gpio.bounce" : GPIO.BOUNCE_NONE, } def_dict.update(paramDict) paramDict.update(def_dict) # update again to apply changes to original reference 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. In this case the registers for the specific chip are defined and optionally the GPIO-Pin for interrupts is initialized. 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 paramDict(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 """ self.Params_init(paramDict) err = ErrorCode.errOk if err.isOk(): err = SerialBusDevice.open(self, paramDict) if err.isOk(): self.RSense = paramDict["Gasgauge.SenseResistor"] self.batCapacity = paramDict["Gasgauge.battery.capacity"] self.batImpedance = paramDict["Gasgauge.battery.impedance"] self.alarmSOC = paramDict["Gasgauge.alarm.soc"] self.alarmVoltage = paramDict["Gasgauge.alarm.voltage"] self.relaxCurrent = paramDict["Gasgauge.relax.current"] self.relaxTimerCC2VM = paramDict["Gasgauge.relax.timer"] err = self._setup() if err.isOk() and ("Gasgauge.int.gpio.pinDesignator" in paramDict): # Setup GPIO pin for interrupts prefix = "Gasgauge.int." gpioParams = dict( [(k.replace(prefix, ""),v) for k,v in paramDict.items() if k.startswith(prefix)] ) self.pinInt = SysFactory.getGPIO() # open GPIO pin err = self.pinInt.open(gpioParams) self.enableInterrupt() return err
[docs] def close(self): """Shut down the device after usage. This method should be called when the device is not used, anymore, e.g. as part of the application exit procedure. The following steps are executed: * close I2C-Bus connection * close GPIO pin for interrupts After return, the device can still be re-used, by calling :meth:`.open` again. Also see: :meth:`.GPIO.close`, :meth:`.Module.close`. """ self.setRunLevel(RunLevel.shutdown) err = SerialBusDevice.close(self) if self.pinInt is not None: err2 = self.pinInt.close() self.pinInt = None if err.isOk(): err = err2 return err
[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 """ ret = ErrorCode.errOk mode = self.REGISTER.MODE_OFF if level in [RunLevel.active, RunLevel.idle]: # Mixed mode: coulomb counter + voltage gas gauge -> Leave VMODE off mode = self.REGISTER.MODE_OFF | self.REGISTER.MODE_GG_RUN | self.REGISTER.MODE_FORCE_CC if self.pinInt: mode |= self.REGISTER.MODE_ALM_ENA elif level in [RunLevel.relax, RunLevel.snooze, RunLevel.nap, RunLevel.sleep, RunLevel.deepSleep]: # Power saving mode: voltage gas gauge, only. -> VMODE = 1 mode = self.REGISTER.MODE_OFF | self.REGISTER.MODE_VMODE | self.REGISTER.MODE_GG_RUN | self.REGISTER.MODE_FORCE_VM if self.pinInt: mode |= self.REGISTER.MODE_ALM_ENA elif level == RunLevel.shutdown: # ret = backupRam(self) mode = self.REGISTER.MODE_VMODE | self.REGISTER.MODE_FORCE_VM else: ret = ErrorCode.errNotSupported # set mode and return ErrorCode if ret.isOk(): ret = SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_MODE, mode) return ret
# # Gasgauge API #
[docs] def reset(self): """Soft resets the device. The device is in some default state, afterwards and must be re-configured according to the application's needs. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ # UNDOCUMENTED: At the end of the reset phase, the MODE_GG_RUN bit is cleared. # In order to detect this, we have to set it, first: mode_data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_MODE) if err.isOk() and not (mode_data & self.REGISTER.MODE_GG_RUN): mode_data |= self.REGISTER.MODE_GG_RUN err = SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_MODE, mode_data) # same applies for beneath # Do a soft reset by asserting CTRL_PORDET if err.isOk(): # TODO: consider adding a set_ctrl / get_ctrl / add_ctrl method for this purpose; same for mode ctrl_data = self.REGISTER.CTRL_IO0DATA | self.REGISTER.CTRL_GG_RST | self.REGISTER.CTRL_PORDET err = SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_CTRL, ctrl_data) # Delay: Loop until we see the MODE_GG_RUN bit cleared: if err.isOk(): t0 = time.time() done = False bootFinished = False while not done: mode_data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_MODE) tNow = time.time() bootFinished = err.isOk() and not (mode_data & self.REGISTER.MODE_GG_RUN) done = bootFinished or (tNow - t0 > STC311x.POR_TIMEOUT) if not bootFinished: err = ErrorCode.errMalfunction # Then, re-initialize the device if err.isOk(): self._setup() return err
[docs] def getInfo(self): """Retrieves an information block from the gas gauge device. Typically, this kind of information is rather static in that, it does not change over time. Usually, this information is somewhat unique for the charger origin, manufacturing date, hardware/firmware revision, product/model ID, chip series and alike. For that reason, this function can be used to see, if communication works reliably. For more dynamic meta-information see :meth:`getStatus`. The method returns both, an instance of :class:`Info`, carrying the information block as well as an error code, indicating success or failure. The info block shall be evaluated only, if the method returned successfully. Even then, the caller should still evaluate the ``validity`` attribute of the returned info block to find out, which of the information is actually valid. :return: The information object and an error code indicating either success or the reason of failure. :rtype: Info, ErrorCode """ info = Info() chip_id, err = self.readByteRegister(self.REGISTER.REG_ID) if err.isOk(): info.chipID = chip_id if chip_id == self.REGISTER.CHIP_ID: info.validity = Info.validChipID if not self.MODEL_ID is None: info.modelID = self.MODEL_ID info.validity = (info.validity | Info.validModelID) return info, err
[docs] def getStatus(self, statusID): """Retrieves status data from the device. Typically, this kind of information is more dynamic in that, it changes (much) over time. Usually, it further describes the IC's current shape and condition, such as the availability of new data, the cause of an interrupt or the status of certain hardware functions. Also, secondary measurements such as the die temperature could be subject to status data. For more static meta-information see :meth:`getInfo`. The given ``statusID`` parameter specifies, exactly which status information should be retrieved. Its type and interpretation depends on the implementation. The method returns both, resulting status data and an error code indicating success or failure. The status data should be considered valid only, if the error code indicates a successful execution of this method. The type and interpretation of the status data depends on the specific implementation. :param int statusID: Identifies the status information to be retrieved. :return: The status object and an error code indicating either success or the reason of failure. :rtype: Object, ErrorCode """ data = None if statusID == StatusID.dieTemp: data, err = self.readByteRegister(self.REGISTER.REG_TEMPERATURE) # LSB is 1 °C, so we don't need any scaling. else: data = None err = ErrorCode.errNotSupported return data, err
@staticmethod def _transferSOC(data): # LSB is 1/512 %, so shift by 9 bits. # Add 1/2 for correct rounding ret = data + 0x0100 >> 9 return Percentage(ret)
[docs] def getStateOfCharge( self ): """Retrieves the state of charge. That is the fraction of electric energy from the total capacity, that is still or already stored in the battery. This information is valid for both, the charging as well as the discharging process. :return: A percentage [0...100] value or :attr:`Percentage.invalid`\ to indicate that this information could not be retrieved. :rtype: Percentage """ # SOC is a 16bit value with LSB = 1/512 % # But reading just the high-byte results in an inconsistent response. # So, read the full word. data, err = SerialBusDevice.readWordRegister(self, self.REGISTER.REG_SOC) if err.isOk(): ret = self._transferSOC(data) # future RAM-functions could be implemented here # if ret != Percentage.invalid: # updateRamWord(self, self.REGISTER._IDX_RAM_SOC) else: ret = Percentage.invalid return ret
@staticmethod def _transferVoltage(data): # LSB is 2.2mV, so scale by factor 2.20 = 22/10 ret = (data * 22 + 5) // 10 return Voltage(ret)
[docs] def getBatteryVoltage(self): """Retrieves the battery voltage in milli Volt. :return: A on-negative integer value [mV] or :attr:`Voltage.invalid`\ to indicate that this information could not be retrieved. :rtype: Voltage """ data, err = self.readWordRegister(self.REGISTER.REG_VOLTAGE) if err.isOk(): ret = self._transferVoltage(data) else: ret = Voltage.invalid return ret
def _transferCurrent(self, data): # Actually, we read out the voltage drop over the sense resistor. # LSB is 5.88V, so first scaling factor is 5.88 = 294/50 # Value is signed! # R = U/I so we get I = U/R; Note that R is given in milliOhm! # So, finally we scale by 294 / 50 * 1000 / rs = 294 * 20 / rs = 5880 / rs. if data < 0x8000: ret = (data * 5880 + (self.RSense // 2)) // self.RSense else: data = 0x10000 - data ret = (data * 5880 - (self.RSense // 2)) // self.RSense return ret
[docs] def getBatteryCurrent(self): """Retrieves the battery current in micro Ampere at the time this\ function is executed. See also: :meth:`getBatteryCurrentAvg` :return: A non-negative integer value [micro A] or :attr:`Current.invalid`\ to indicate that this information could not be retrieved. :rtype: Current """ current, err = self.readWordRegister(self.REGISTER.REG_CURRENT) if err.isOk(): ret = self._transferCurrent(current) else: ret = Current.invalid return ret
# Local functions for internal use @staticmethod def _transferOCV(data): # LSB is 0.55 mV, so scale by factor 0.55 = 55/100 = 11/20. ret = (data * 11 + 10) // 20 return Voltage(ret) @staticmethod def _invTransferOCV(value): # LSB is 0.55 mV, so scale by factor 1/0.55 = 100/55 = 20/11. ret = (value * 20 + 5) // 11 return ret @staticmethod def _crc(data, length): ret = 0 for idx in range(length): ret ^= data[idx] return ret def _checkRamConsistency(self, data): # check if RAM test register value is correct if (len(data) < self.REGISTER.RAM_SIZE) or \ (data[self.REGISTER.IDX_RAM_TEST] != self.REGISTER.RAM_TEST): ret = ErrorCode.errCorruptData else: code = self._crc(data, self.REGISTER.RAM_SIZE - 1) if code != data[self.REGISTER.IDX_RAM_CRC]: ret = ErrorCode.errCorruptData else: ret = ErrorCode.errOk return ret # UNUSED static ErrorCode_t updateRamWord( const Device_Index_t devIdx, const unsigned int ramIndex, const uint16_t newData ) # { # ErrorCode_t ret = errOk; # uint8_t buffer[RAM_SIZE + 1]; # uint8_t *ramContent; # uint8_t data8; # # ramContent = &buffer[1]; # data8 = REG_RAM_FIRST; # ret = Serial_Bus_read_write_Buffer( &dContext[devIdx].sdev, ramContent, RAM_SIZE, &data8, 1); # # if( ret == errOk ) # { # ret = checkRamConsistency( ramContent, RAM_SIZE ); # } # if( ret == errOk ) # { # ramContent[ramIndex] = newData & 0xFF; # ramContent[ramIndex+1] = newData >> 8; # ramContent[IDX_RAM_CRC] = crc( ramContent, RAM_SIZE-1 ); # buffer[0] = REG_RAM_FIRST; # ret = Serial_Bus_write_Buffer( &dContext[devIdx].sdev, buffer, RAM_SIZE + 1 ); # } # return ret; # } # # UNUSED static ErrorCode_t backupRam( const Device_Index_t devIdx ) # { # ErrorCode_t ret = errOk; # uint8_t buffer[RAM_SIZE + 1]; # uint8_t *ramContent; # uint16_t data16; # # ramContent = &buffer[1]; # memset( ramContent, 0, RAM_SIZE ); # ramContent[IDX_RAM_TEST] = RAM_TEST; # ret = Serial_Bus_read_Reg_Word( &dContext[devIdx].sdev, REG_SOC, &data16 ); # ramContent[IDX_RAM_SOC_L]= data16 & 0xFF; # ramContent[IDX_RAM_SOC_H]= data16 >> 8; # ret = Serial_Bus_read_Reg_Word( &dContext[devIdx].sdev, REG_CC_CNF, &data16 ); # ramContent[IDX_RAM_CC_CNF_L]= data16 & 0xFF; # ramContent[IDX_RAM_CC_CNF_H]= data16 >> 8; # ret = Serial_Bus_read_Reg_Word( &dContext[devIdx].sdev, REG_VM_CNF, &data16 ); # ramContent[IDX_RAM_VM_CNF_L]= data16 & 0xFF; # ramContent[IDX_RAM_VM_CNF_H]= data16 >> 8; # ramContent[IDX_RAM_CRC] = crc( ramContent, RAM_SIZE - 1 ); # buffer[0] = REG_RAM_FIRST; # Serial_Bus_write_Buffer(&dContext[devIdx].sdev, buffer, RAM_SIZE + 1 ); # # return ret; # } def _setupAlarm(self): # REG_ALARM_SOC, LSB=0,5%, scaling = 2. data = self.alarmSOC * 2 err = self.writeByteRegister( self.REGISTER.REG_ALARM_SOC, data) if err.isOk(): # REG_ALARM_VOLTAGE, LSB=17,6mV, scaling = 10/176 = 5/88 data = (self.alarmVoltage * 5 + 44) // 88 err = self.writeByteRegister( self.REGISTER.REG_ALARM_VOLTAGE, data) return err def _setupCurrentMonitoring(self): # REG_CURRENT_THRES, LSB=47,04µV # scaling = 1/47040 as relax current is in micro Ampere! data = (self.relaxCurrent * self.batImpedance + 23520) // 47040 err = self.writeByteRegister( self.REGISTER.REG_CURRENT_THRES, data ) # Sub classes must handle their REG_RELAX_MAX / REG_CMONIT_MAX counters return err def _setup(self): # Check communication data, err = self.readByteRegister(self.REGISTER.REG_ID) if err.isOk(): err = ErrorCode.errOk if (data == self.REGISTER.CHIP_ID) else ErrorCode.errResourceConflict # Read RAM content if err.isOk(): ramContent, err = self.readBufferRegister(self.REGISTER.REG_RAM_FIRST, self.REGISTER.RAM_SIZE) # Check RAM consistency canRestore = False # Set True to enable RAM restoration if err.isOk(): err = self._checkRamConsistency(ramContent) if err.isOk(): # check CTRL_PORDET and CTRL_BATFAIL data, err = self.readByteRegister(self.REGISTER.REG_CTRL) if err.isOk(): if data & (self.REGISTER.CTRL_BATFAIL | self.REGISTER.CTRL_PORDET): # battery removed / voltage dropped below threshold # no restoration, start anew, instead! canRestore = False else: canRestore = False err = ErrorCode.errOk if err.isOk(): # common steps (pre-phase) if canRestore: # restore configuration from RAM # ensure that GG_RUN is cleared self.writeByteRegister(self.REGISTER.REG_MODE, self.REGISTER.MODE_OFF) # restore REG_CC_CNF data = (ramContent[self.REGISTER.IDX_RAM_CC_CNF_H] << 8) | ramContent[self.REGISTER.IDX_RAM_CC_CNF_L] self.writeWordRegister(self.REGISTER.REG_CC_CNF, data) # restore REG_VM_CNF data = (ramContent[self.REGISTER.IDX_RAM_VM_CNF_H] << 8) | ramContent[self.REGISTER.IDX_RAM_VM_CNF_L] self.writeWordRegister(self.REGISTER.REG_VM_CNF, data) # restore REG_SOC data = (ramContent[self.REGISTER.IDX_RAM_SOC_H] << 8) | ramContent[self.REGISTER.IDX_RAM_SOC_L] self.writeWordRegister(self.REGISTER.REG_SOC, data) else: # initialize configuration with defaults # run gas gauge to get first OCV and current measurement data = self.REGISTER.MODE_OFF | self.REGISTER.MODE_GG_RUN |self.REGISTER.MODE_FORCE_CC self.writeByteRegister(self.REGISTER.REG_MODE, data) # read OCV data, _ = self.readWordRegister(self.REGISTER.REG_OCV) ocv = self._transferOCV(data) # read current data, _ = self.readWordRegister(self.REGISTER.REG_CURRENT) current = self._transferCurrent(data) # ensure that GG_RUN is cleared self.writeByteRegister(self.REGISTER.REG_MODE, self.REGISTER.MODE_OFF) # Determine and write the content of REG_CC_CNF # Following the STC3115 data sheet, chapter 6.2.1. on coulomb counter, # this register "scales the charge integrated by the # sigma delta converter into a percentage value of the battery capacity" # It depends on the battery capacity (Cnom) and the current sense resistor (Rsense) as follows: # REG_CC_CNF = Rsense [mOhm] * Cnom [mAh] ⁄ 49,556 # Scaling factor: 1/49,556 = 1000/49556 = 250/12389 cnfCC = (self.RSense * self.batCapacity * 250 + 6194) // 12389 self.writeWordRegister(self.REGISTER.REG_CC_CNF, cnfCC) # Determine and write the content of REG_VM_CNF # Following to chapter 6.2.2, this register "configures the parameter used by the algorithm". # It is calculated from the battery's impedance (Ri) and apacity (Cnom) as follows: # REG_VM_CNF = Ri [mOhm] * Cnom [mAh] ⁄ 977,78 # Scaling factor: 1/977.78 = 100/97778 = 50/48889 cnfVM = (self.batImpedance * self.batCapacity * 50 + 24444) // 48889 self.writeWordRegister(self.REGISTER.REG_VM_CNF, cnfVM) # compensate OCV if current > 1000000: current //= 1000 ocv = ocv - current * self.batImpedance // 1000 else: ocv = ocv - current * self.batImpedance // (1000 * 1000) data = self._invTransferOCV(ocv) # write OCV back self.writeWordRegister(self.REGISTER.REG_OCV, data) # wait 100ms to get valid SOC if hasattr(time, "sleep_ms"): time.sleep_ms(100) else: time.sleep(0.1) data, _ = self.readWordRegister(self.REGISTER.REG_SOC) # store new backup to RAM ramContent = [0] * self.REGISTER.RAM_SIZE ramContent[self.REGISTER.IDX_RAM_TEST] = self.REGISTER.RAM_TEST ramContent[self.REGISTER.IDX_RAM_SOC_L] = data & 0xFF ramContent[self.REGISTER.IDX_RAM_SOC_H] = data >> 8 ramContent[self.REGISTER.IDX_RAM_CC_CNF_L] = cnfCC & 0xFF ramContent[self.REGISTER.IDX_RAM_CC_CNF_H] = cnfCC >> 8 ramContent[self.REGISTER.IDX_RAM_VM_CNF_L] = cnfVM & 0xFF ramContent[self.REGISTER.IDX_RAM_VM_CNF_H] = cnfVM >> 8 ramContent[self.REGISTER.IDX_RAM_CRC] = self._crc(ramContent, self.REGISTER.RAM_SIZE - 1) self.writeBufferRegister(self.REGISTER.REG_RAM_FIRST, ramContent) # Common steps (post-phase) self._setupAlarm() self._setupCurrentMonitoring() # Clear interrupts # IO0DATA = 1 to see alarm conditions on the ALM pin # GG_RST = 1 do reset the conversion counter # GG_VM = 0, cannot be written # BATFAIL = 0 to clear this flag # PORDET = 0 to clear the POR detection flag # ALM_SOC = 0 to clear low-SOC condition # ALM_VOLT= 0 to clear low voltage condition # UVLOD = 0 to clear UVLO event. data = (self.REGISTER.CTRL_IO0DATA | self.REGISTER.CTRL_GG_RST) err = self.writeByteRegister( self.REGISTER.REG_CTRL, data ) # Run the gas gauge err = self.setRunLevel( RunLevel.active ) return err # # Interruptable API #
[docs] def registerInterruptHandler(self, onEvent=Event.evtInt1, callerFeedBack=None, handler=None): if handler is not None: # Enable; from app (=sink) to hardware (=source) self.pinInt.registerInterruptHandler(onEvent, callerFeedBack, handler) err = self.pinInt.enableInterrupt() if err.isOk(): data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_MODE) if err.isOk(): data |= self.REGISTER.MODE_ALM_ENA err = SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_MODE, data) if err.isOk(): # check if there already is an interrupt present data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_CTRL) if data & self.REGISTER.CTRL_IO0DATA: handler(Event.evtInt1, callerFeedBack) else: self.disableInterrupt() else: err = ErrorCode.errInvalidParameter # TODO: is this the right error code? else: # Disable; from hardware to app. data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_MODE) if err.isOk(): data &= ~self.REGISTER.MODE_ALM_ENA # TODO: ModeValues class need to be adjusted to work with all binary operations as expected err = SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_MODE, data) self.disableInterrupt() return err
[docs] def enableInterrupt(self): if self.pinInt: err = self.pinInt.enableInterrupt() else: err = ErrorCode.errUnavailable return err
[docs] def disableInterrupt(self): if self.pinInt: err = self.pinInt.disableInterrupt() else: err = ErrorCode.errUnavailable return err
def _getEventContext(self, event): pass # TODO: this function, see original implementation
# ErrorCode_t stc311x_getEventContext( const Device_Index_t devIdx, # const gasgauge_Event_t event, # gasgauge_EventContext_t *context ) # { # ErrorCode_t ret = errOk; # uint8_t data8=0, clear8; # # if( (devIdx == DEVICE_INDEX_INVALID) || (devIdx >= CONFIG_GASGAUGE_COUNT) || (context == NULL) ) # { # ret = errInvalidParameter; # } else if( event == gasgauge_EvtNone ) { # ret = errFewData; # } else if( event != gasgauge_EvtInt1 ) { # ret = errCorruptData; # } else { # # ret = Serial_Bus_read_Reg_Byte( &dContext[devIdx].sdev, REG_CTRL, &data8 ); # if( ret == errOk ) # { # context->source = gasgauge_EvtSrcUnknown; # #if defined(CONFIG_GASGAUGE_IS_STC3117) # clear8 = CTRL_IO0DATA | CTRL_BATFAIL | CTRL_ALM_SOC | CTRL_ALM_VOLT | CTRL_UVLOD; # #else # clear8 = CTRL_IO0DATA | CTRL_BATFAIL | CTRL_ALM_SOC | CTRL_ALM_VOLT; # #endif # # // Highest priority, as by reading, PORDET was cleared, so we don't get it another time! # if( data8 & CTRL_PORDET ) # { # context->source = gasgauge_EvtSrcPOR; # } else if( data8 & CTRL_ALM_VOLT ) # { # context->source = gasgauge_EvtSrcLowVolt; # context->detail.voltage = stc311x_getBatteryVoltage( devIdx ); # clear8 &= ~CTRL_ALM_VOLT; # } else if( data8 & CTRL_ALM_SOC ) # { # context->source = gasgauge_EvtSrcLowSOC; # context->detail.soc = stc311x_getStateOfCharge( devIdx ); # clear8 &= ~CTRL_ALM_SOC; # } else if( data8 & CTRL_BATFAIL ) # { # context->source = gasgauge_EvtSrcBatFail; # clear8 &= ~CTRL_BATFAIL; # } # #if defined(CONFIG_GASGAUGE_IS_STC3117) # else if( data8 & CTRL_UVLOD ) # { # context->source = stc311x_EvtSrcUndervoltage; # clear8 &= ~CTRL_UVLOD; # } # #endif # # if( context->source != gasgauge_EvtSrcUnknown ) # { # ret = Serial_Bus_write_Reg_Byte( &dContext[devIdx].sdev, REG_CTRL, clear8 ); # } # } # } # return ret; # }