# -*- coding: utf-8 -*-

"""
author  Benoit Dubois
licence GPL v3+
brief   STM32 driver for MCP3551 ADC device
"""
import pyb


class Mcp3551(object):
    """Handle communication with ADC MCP3551 through SPI bus on STM32F4.
    Note: baudrate_max =  MHz
    """

    NUM_BITS = 22
    MAX_VALUE = (1 << (NUM_BITS-1)) - 1
    MIN_VALUE = -(1 << (NUM_BITS-1))

    def __init__(self, bus, baudrate, rdy_b_pin, cs_pin, v_ref):
        """
        :param bus: SPI bus number (int)
        :param baudrate: SPI bus (data) baudrate (int)
        :param rdy_b_pin: Pin in use for rdy_b (SDO/MISO) signal (str)
        :param cs_pin: Pin in use for cs signal (str)
        :param v_ref: Reference voltage value of ADC (float)
        """
        self.baudrate = baudrate
        self.v_ref = v_ref
        self.rdy_b = pyb.Pin(rdy_b_pin, pyb.Pin.IN)
        self.cs_b = pyb.Pin(cs_pin, pyb.Pin.OUT_PP)
        self.spi = pyb.SPI(bus)
        self.cs_b.high()
        self.cs_b.low()

    def single_read(self):
        """Read one value then stop acquisition.
        """
        self.cs_b.low()
        data = self.read()
        self.cs_b.high()
        return data

     def _bin2volt(self, value):
        """Note: does not check sign of data, so negative value are
        affected of a small error due to normalisation with MAX_VALUE
        instead of MIN_VALUE.
        """
        return value * self.v_ref / self.MAX_VALUE

    def volt(self):
        """Note: does not check sign of data, so negative value are
        affected of a small error due to normalisation with MAX_VALUE
        instead of MIN_VALUE.
        """
        return self._bin2volt(self.single_read(), self.v_ref, self.MAX_VALUE)

    def continous_mode(self, value=True):
        """Set/Deset continous acquisition mode.
        """
        if value is False:
            self.cs_b.high()
        else:
            self.cs_b.low()

    def read(self):
        """Note1: The ADC allows operation over [-Vref; +Vref] range.
        This capability is not implemented.
        Note2: If ADC does not respond (i.e. /RDY does not falling), return 0.
        """
        wd = 0
        buf = bytearray(3)
        while self.rdy_b.value() == 1:
            if wd > 100: # Watchdog on MCP response
                return 0
            else:
                wd += 1
                pyb.delay(1)
        self.spi.init(pyb.SPI.MASTER, baudrate=self.baudrate,
                      polarity=1, phase=1)
        self.spi.recv(buf, timeout=500)
        self.spi.deinit()
        if buf[0] & 256 == 1: # Overflow high
            return self.MAX_VALUE
        if buf[0] & 128 == 1: # Overflow low
            return self.MIN_VALUE
        data = (buf[0] << 16) + (buf[1] << 8) + buf[2]
        return self.twos_comp(data, self.NUM_BITS)

    @staticmethod
    def twos_comp(val, bits):
        """compute the 2's complement of int value val of lenght bits.
        """
        if (val & (1 << (bits - 1))) != 0: # if sign bit is set
            val = val - (1 << bits)        # compute negative value
        return val                         # return positive value as is


class Mcp3551Temp(Mcp3551):
    """Handle "slow_adc_shield" of Cyrus Rocher ie a board including
    conditionning of a RTD and the A to D conversion with a MCP3551 device.
    """

    def __init__(self, bus, baudrate, rdy_b_pin, cs_pin, v_ref=2.5,
                 r_0=100, alpha=0.00385, r_ref=2.5e3):
        """
        :param r_0: resistance of RTD @ 0°C (int)
        :param alpha: 1st order sensitivity of RTD (ohm/ohm/°C) (float)
        :param r_ref: resistance defining current through RTD (int)
        """
        super().__init__(bus, baudrate, rdy_b_pin, cs_pin, v_ref)
        self.r_0 = r_0
        self.r_ref = r_ref
        self.k = 1 / alpha / r_0

    @staticmethod
    def bin2resistance(bin_value, r_ref, bin_max):
        """Convert binary value to resistance value.
        """
        return bin_value * r_ref / bin_max

    @staticmethod
    def bin2temperature(bin_value, k, r_0, r_ref, bin_max):
        """Convert binary value to temperature value.
        """
        return k * (bin_value * r_ref / bin_max - r_0)

    def resistance(self):
        """Convert binary value to resistance value.
        """
        return self.bin2resistance(self.single_read(),
                                   self.r_ref,
                                   self.MAX_VALUE)

    def temperature(self):
        """Convert binary value to temperature value.
        """
        return self.bin2temperature(self.single_read(),
                                    self.k, self.r_0,
                                    self.r_ref, self.MAX_VALUE)


if __name__ == '__main__':
    ADC_SPI_CH = 1
    ADC_V_REF = 2.5
    ADC_SPI_BAUDRATE = 1000000
    ADC_PIN_CS = 'PA4'
    ADC_PIN_RDY_B = 'PA6'

    ADC = Mcp3551(bus=ADC_SPI_CH, baudrate=ADC_SPI_BAUDRATE,
                  rdy_b_pin=ADC_PIN_RDY_B, cs_pin=ADC_PIN_CS,
                  v_ref=ADC_V_REF)
    ADC.continous_mode(True)

    ACQ_NB = 0

    while True:
        pyb.delay(10) # in ms
        print(ACQ_NB, ADC.read())
        ACQ_NB += 1
