/*
 * David Muller; Germαn Alfaro
 * davehmuller@gmail.com; alfaro.germanevera@gmail.com
 *
 * Modified for MBARI MFET  Thom Maughan 2018
 * tm@mbari.org
 */


/*
 * ADS1248_iso.c
 * Driver library for isolated ADS1248.
 */

#include "main.h"
#include "system.h"
#include "ads1248_iso.h"
#include "uartstdio.h"	// User local version with larger RX buffer
#include "user_io.h"
#include "board_util.h"

extern uint32_t serDbgFlg;

typedef enum
{
    ADS1248_CMD_WAKEUP   = 0x00U,
    ADS1248_CMD_SLEEP    = 0x02U,
    ADS1248_CMD_SYNC     = 0x04U,
    ADS1248_CMD_RESET    = 0x06U,
    ADS1248_CMD_NOP      = 0xFFU,
    ADS1248_CMD_RDATA    = 0x12U,
    ADS1248_CMD_RDATAC   = 0x14U,
    ADS1248_CMD_SDATAC   = 0x16U,
    ADS1248_CMD_RREG     = 0x20U,
    ADS1248_CMD_WREG     = 0x40U,
    ADS1248_CMD_SYSOCAL  = 0x60U,
    ADS1248_CMD_SYSGCAL  = 0x61U,
    ADS1248_CMD_SELFOCAL = 0x62U
} ADS1248_CMD;

// ADS1248 Register macros
#define REG_MUX0    0x00U
#define REG_VBIAS   0x01U
#define REG_MUX1    0x02U
#define REG_SYS0    0x03U
#define REG_OFC0    0x04U
#define REG_OFC1    0x05U
#define REG_OFC2    0x06U
#define REG_FSC0    0x07U
#define REG_FSC1    0x08U
#define REG_FSC2    0x09U
#define REG_IDAC0   0x0AU
#define REG_IDAC1   0x0BU
#define REG_GPIOCFG 0x0CU
#define REG_GPIODIR 0x0DU
#define REG_GPIODAT 0x0EU

//9.5.3.9 WREG (0100 rrrr, 0000 nnnn)
//The WREG command writes to the registers, starting with the register specified as part of the instruction. The
//number of registers that are written is one plus the value of the second byte. The command structure for WREG
//is listed below.
// First Command Byte: 0100 rrrr, where rrrr is the address of the first register to be written.
// Second Command Byte: 0000 nnnn, where nnnn is the number of bytes to be written  1.
// Byte(s): data to be written to the registers

//9.5.3.8 RREG (0010 rrrr, 0000 nnnn)
//The RREG command outputs the data from up to 15 registers, starting with the register address specified as part
//of the instruction. The number of registers read is one plus the value of the second byte. If the count exceeds the
//remaining registers, the addresses wrap back to the beginning. The two byte command structure for RREG is
//listed below.
// First Command Byte: 0010 rrrr, where rrrr is the address of the first register to read.
// Second Command Byte: 0000 nnnn, where nnnn is the number of bytes to read 1.
// Byte(s): data read from the registers are clocked out with NOPs

#if BOARD_MFET >= 1 || BOARD_MPHOX == 1  || BOARD_MSC == 2
    // Function macros for Mini-Board (removed the invert on START and RESET)
    #define iso_DRDY()          MAP_GPIOPinRead(GPIO_PORTN_BASE, GPIO_PIN_3)
    #define iso_assert_CS()     MAP_GPIOPinWrite(GPIO_PORTH_BASE, GPIO_PIN_1, 0x00);    // Assert chip select (set low)
    #define iso_deassert_CS()   MAP_GPIOPinWrite(GPIO_PORTH_BASE, GPIO_PIN_1, 0xff);    // Deassert chip select (set high)

    #define iso_START_high()    MAP_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_6, 0xff);    // Not inverted on mini-board
    #define iso_START_low()     MAP_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_6, 0x00);    // Not inverted on mini-board
    #define iso_RESET_high()    MAP_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_5, 0xff);    // Not inverted on mini-board
    #define iso_RESET_low()     MAP_GPIOPinWrite(GPIO_PORTN_BASE, GPIO_PIN_5, 0x00);    // Not inverted on mini-board
#elif BOARD_NANOFET >= 1
    // Function macros for NanoFET
    #define iso_DRDY()          MAP_GPIOPinRead(GPIO_PORTD_BASE, GPIO_PIN_6)           // PD6
    #define iso_assert_CS()     MAP_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_1, 0x00);    // PD1 Assert chip select (set low)
    #define iso_deassert_CS()   MAP_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_1, 0xff);    // PD1 Deassert chip select (set high)

    #define iso_START_high()    MAP_GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_6, 0xff);
    #define iso_START_low()     MAP_GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_6, 0x00);
    #define iso_RESET_high()    MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_4, 0xff);
    #define iso_RESET_low()     MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_4, 0x00);
#elif BOARD_MSC == 3
    // Function macros for MSC3
    #if defined(PART_TM4C1290NCPDT)

        #define iso_DRDY()          MAP_GPIOPinRead(GPIO_PORTL_BASE, GPIO_PIN_2)           // pH_DRDY PL2 Tiva 1290revA

        #define iso_assert_CS()     MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, 0x00);          // Assert chip select (set low)  SSI3Fss PF2
        #define iso_deassert_CS()   MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_2, GPIO_PIN_2);    // Deassert chip select (set high)

        #define iso_START_high()    MAP_GPIOPinWrite(GPIO_PORTL_BASE, GPIO_PIN_1, GPIO_PIN_1);    // pH_Start PL1
        #define iso_START_low()     MAP_GPIOPinWrite(GPIO_PORTL_BASE, GPIO_PIN_1, 0x00);
        #define iso_RESET_high()    MAP_GPIOPinWrite(GPIO_PORTL_BASE, GPIO_PIN_0, GPIO_PIN_0);    // pH_ADC_Reset PL0
        #define iso_RESET_low()     MAP_GPIOPinWrite(GPIO_PORTL_BASE, GPIO_PIN_0, 0x00);

        #define iso_POWER_high()    MAP_GPIOPinWrite(GPIO_PORTL_BASE, GPIO_PIN_3, GPIO_PIN_3);    // pH_pwrEnable PL3
        #define iso_POWER_low()     MAP_GPIOPinWrite(GPIO_PORTL_BASE, GPIO_PIN_3, 0x00);

    #elif defined(PART_TM4C123GH6PM)
        #define iso_DRDY()          MAP_GPIOPinRead(GPIO_PORTE_BASE, GPIO_PIN_1)           // PE1

        #define iso_assert_CS()     MAP_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_1, 0x00);    // PD1 Assert chip select (set low)  SSI3Fss
        #define iso_deassert_CS()   MAP_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_1, 0xff);    // PD1 Deassert chip select (set high)

        #define iso_START_high()    MAP_GPIOPinWrite(GPIO_PORTE_BASE, GPIO_PIN_2, 0xff);    // PE2
        #define iso_START_low()     MAP_GPIOPinWrite(GPIO_PORTE_BASE, GPIO_PIN_2, 0x00);
        #define iso_RESET_high()    MAP_GPIOPinWrite(GPIO_PORTE_BASE, GPIO_PIN_3, 0xff);    // PE3
        #define iso_RESET_low()     MAP_GPIOPinWrite(GPIO_PORTE_BASE, GPIO_PIN_3, 0x00);

        #define iso_POWER_high()    MAP_GPIOPinWrite(GPIO_PORTE_BASE, GPIO_PIN_0, 0xff);    // pH_pwrEnable
        #define iso_POWER_low()     MAP_GPIOPinWrite(GPIO_PORTE_BASE, GPIO_PIN_0, 0x00);

    #else
        #error "Unknown part"
    #endif
#else
    #error "Unknown board"
#endif


/*
 * ADS1248 Delays
 */
//205ms delay. At default 5SPS, digital filter reset takes about 200ms
#define SPS5DELAY (MILLISECOND * 205)

//55ms delay. At 20SPS, digital filter reset takes about 50ms
#define SPS20DELAY (MILLISECOND * 55)

//805ms delay. At 20SPS, calibration takes about 801ms
#define SPS20CALIBRATIONDELAY (MILLISECOND * 805)

#if BOARD_MFET >= 1 || BOARD_MPHOX >= 1  || BOARD_MSC == 2 || BOARD_NANOFET >= 1 || BOARD_MSC == 3
    #define ADS1248_POWERON_RESET_DELAY (MILLISECOND * 20)      // Datasheet says 16ms, we'll give it 20ms
#elif BOARD_SEAPHOX == 1
    #define ADS1248_POWERON_RESET_DELAY (MILLISECOND * 200)      // TPS3836 reset is strapped for 200msec
#else
    #error "Unknown board"
#endif

void ads1248_wait_spi_idle()
{
    while(MAP_SSIBusy(SSI3_BASE))
    {

    }
}

void ads1248_flush_spi_rx()
{
    uint32_t rdata;
    while(MAP_SSIDataGetNonBlocking(SSI3_BASE, &rdata))
    {

    }
}

void ads1248_spi_write(uint8_t const * const buf, const size_t len)
{
    // Required delay before first clk after nCS low, t_CSSC
    // 10ns
    // at 120MHz, 1 clock cycle is ~8.3ns, so just the idle check should be good for timing

    ads1248_wait_spi_idle();

    for(size_t i = 0; i < len; i++)
    {
        MAP_SSIDataPut(SSI3_BASE, buf[i]);
    }

    ads1248_wait_spi_idle();

    // Required delay after last clk before nCS high, t_SCCS
    // 7/(4.096MHz) = 1.7us
    // 7/(3.89MHz) = 1.8us
    MAP_SysCtlDelay(3 * MICROSECOND);
}

void ads1248_spi_write_and_read(uint8_t const * const wbuf, uint8_t* const rbuf, const size_t len)
{
    // Required delay before first clk after nCS low, t_CSSC
    // 10ns
    // at 120MHz, 1 clock cycle is ~8.3ns, so just the idle check should be good for timing

    ads1248_wait_spi_idle();

    ads1248_flush_spi_rx();

    for(size_t i = 0; i < len; i++)
    {
        MAP_SSIDataPut(SSI3_BASE, wbuf[i]);

        uint32_t rdata;
        MAP_SSIDataGet(SSI3_BASE, &rdata);
        rbuf[i] = rdata & 0x000000FFU;
    }

    ads1248_wait_spi_idle();

    // Required delay after last clk before nCS high, t_SCCS
    // 7/(4.096MHz) = 1.7us
    // 7/(3.89MHz) = 1.8us
    MAP_SysCtlDelay(3 * MICROSECOND);
}

void ads1248_write_reg(const uint8_t reg, uint8_t const * const buf, const size_t len)
{
    // Required delay before first clk after nCS low, t_CSSC
    // 10ns
    // at 120MHz, 1 clock cycle is ~8.3ns, so just the idle check should be good for timing

    ads1248_wait_spi_idle();

    ads1248_flush_spi_rx();

    MAP_SSIDataPut(SSI3_BASE, ADS1248_CMD_WREG | reg);
    MAP_SSIDataPut(SSI3_BASE, len - 1);

    for(size_t i = 0; i < len; i++)
    {
        MAP_SSIDataPut(SSI3_BASE, buf[i]);
    }

    ads1248_wait_spi_idle();

    // Required delay after last clk before nCS high, t_SCCS
    // 7/(4.096MHz) = 1.7us
    // 7/(3.89MHz) = 1.8us
    MAP_SysCtlDelay(3 * MICROSECOND);
}

extern unsigned int iso_flag;

// Local function prototypes
unsigned char ADS1248_gain_sps(const int gain, const int sps);

/*
 * openADS1248_iso()
 * Performs initial set up of the isolated ADS1248.
 * Note that SSI3FSS is configured to toggle chip select manually.
 * This is needed as ADS1248 requires CS to stay low during complete
 * SSI register transactions.
 * Pseudocode from the ADS1248 datasheet:
 *
 * Power up;
 * Delay for a minimum of 16 ms to allow power supplies to settle and power-on reset to complete;
 * Enable the device by setting the START pin high;
 * Configure the serial interface of the microcontroller to SPI mode 1 (CPOL = 0, CPHA =1);
 * If the CS pin is not tied low permanently, configure the microcontroller GPIO connected to CS as an output;
 * Configure the microcontroller GPIO connected to the DRDY pin as a falling edge triggered interrupt input;
 * Set CS to the device low;
 * Delay for a minimum of tCSSC;
 * Send the RESET command (06h) to make sure the device is properly reset after power up;
 * Delay for a minimum of 0.6 ms;
 * Send SDATAC command (16h) to prevent the new data from interrupting data or register transactions;
 * Write the respective register configuration with the WREG command (40h, 03h, 01h, 00h, 03h and 42h);
 * As an optional sanity check, read back all configuration registers with the RREG command (four bytes from 20h, 03h);
 * Send the SYNC command (04h) to start the ADC conversion;
 * Delay for a minimum of tSCCS;
 * Clear CS to high (resets the serial interface);
 *
 * Loop
 * {
 *   Wait for DRDY to transition low;
 *   Take CS low;
 *   Delay for a minimum of tCSSC;
 *   Send the RDATA command (12h);
 *   Send 24 SCLKs to read out conversion data on DOUT/DRDY;
 *   Delay for a minimum of tSCCS;
 *   Clear CS to high;
 * }
 * Take CS low;
 * Delay for a minimum of tCSSC;  (10 nano-sec)
 * Send the SLEEP command (02h) to stop conversions and put the device in power-down mode;
 *
 *
 */
// Removed BOARD_SEAPHOX openADS1248



// 22msec Thom 29 May 2019
void openADS1248_iso(void)
{
#if BOARD_MFET >= 1 || BOARD_MPHOX >= 1  || BOARD_MSC == 2

    // Power up by enabling regulator
    MAP_GPIOPinWrite(GPIO_PORTK_BASE, GPIO_PIN_2, 0xff);    // Iso 3.3V power on for ADS1248 via ADM7160 regulator

    //Deassert GPIO control signals for ADS1248: ADC_Reset reset high (active lo), START high, CS high
    iso_RESET_high();    // ADC_Reset is PortN.5
    iso_START_high();    // START is PortN.6
    iso_deassert_CS();   // PortH.1

    // delay for ADM7160 3.3v stability
    MAP_SysCtlDelay(MILLISECOND*2);                        // ADM7160 is within 90% after 180 usec, 2msec should be way plenty

    // Pulse ADS1248 RESET
    iso_RESET_low();
    MAP_SysCtlDelay(MICROSECOND * 10);  // ADS1258 reset is 1 usec, 10 usec is conservative
    iso_RESET_high();

    // Setup SSI port 3
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI3);
    while( ! MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_SSI3) )
    {

    }

    MAP_GPIOPinConfigure(GPIO_PH2_SSI3RX);
    MAP_GPIOPinConfigure(GPIO_PH3_SSI3TX);
    MAP_GPIOPinConfigure(GPIO_PH0_SSI3CLK);
    MAP_GPIOPinTypeSSI(GPIO_PORTH_BASE, GPIO_PIN_0 | GPIO_PIN_2 | GPIO_PIN_3);


    // Configure and enable SSI for SPI master mode.
    MAP_SSIConfigSetExpClk(SSI3_BASE, GetSysClkHz(), SSI_FRF_MOTO_MODE_1, SSI_MODE_MASTER, 1000000, 8);   //MOTO_MODE_1
    MAP_SSIEnable(SSI3_BASE);

    // Wait for ADS1248 clock initialization and calibration to complete after reset (datasheet says 16msec), SPI communication can begin 0.6 msec after reset
    MAP_SysCtlDelay(MILLISECOND*20);

    // Note: the ADS1248evm and SEAPHOX boards have a reset chip TPS3836 strapped for 200msec (300msec max), the MFET has a GPIO iso reset.
#elif BOARD_MSC == 3

    // Start into reset
    iso_RESET_low();
    iso_START_low();
    iso_deassert_CS();
    _dsb();
    MAP_SysCtlDelay(MICROSECOND * 10);

    // Power up by enabling regulator
    iso_POWER_high();
    _dsb();

    // delay for ADM7160 3.3v stability
    MAP_SysCtlDelay(MILLISECOND*2);                        // ADM7160 is within 90% after 180 usec, 2msec should be way plenty

    // Release ADS1248 RESET
    MAP_SysCtlDelay(MICROSECOND * 10);  // ADS1248 reset is 1 usec, 10 usec is conservative
    iso_RESET_high();
    _dsb();
    MAP_SysCtlDelay(MICROSECOND * 600); // wait for digital reset to complete

    // DVDD Rising Power On Reset
    // Wait for ADS1248 clock initialization and calibration to complete after reset (datasheet says 16msec)
    // SPI comms must wait until POR is complete. For a nRST / nonPOR start, SPI can occur after ~600us
    MAP_SysCtlDelay(MILLISECOND*20);

    iso_START_high();
    _dsb();

    #if defined(PART_TM4C123GH6PM)
        // Setup SSI port 3
        MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI3);
        while( ! MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_SSI3) )
        {

        }

        MAP_GPIOPinConfigure(GPIO_PD2_SSI3RX);      //GPIO_PH2_SSI3RX
        MAP_GPIOPinConfigure(GPIO_PD3_SSI3TX);
        MAP_GPIOPinConfigure(GPIO_PD0_SSI3CLK);
        MAP_GPIOPinTypeSSI(GPIO_PORTD_BASE, GPIO_PIN_0 | GPIO_PIN_2 | GPIO_PIN_3);

        // Configure and enable SSI for SPI master mode.
        MAP_SSIConfigSetExpClk(SSI3_BASE, GetSysClkHz(), SSI_FRF_MOTO_MODE_1, SSI_MODE_MASTER, 1000000, 8);   //MOTO_MODE_1
        MAP_SSIEnable(SSI3_BASE);
    #elif defined(PART_TM4C1290NCPDT)
        // Setup SSI port 3
        MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI3);  //done in init, not needed here
        while( ! MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_SSI3) )
        {

        }

        MAP_GPIOPinConfigure(GPIO_PF1_SSI3XDAT0);   // MOSI  TX  Note: 123g has TX/RX opposite
        MAP_GPIOPinConfigure(GPIO_PF0_SSI3XDAT1);   // MISO  RX
        MAP_GPIOPinConfigure(GPIO_PF3_SSI3CLK);
        MAP_GPIOPinTypeSSI(GPIO_PORTF_BASE, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_3);

        // Configure and enable SSI for SPI master mode.
        //MAP_SSIConfigSetExpClk(SSI3_BASE, g_ui32SysClock, SSI_FRF_MOTO_MODE_1, SSI_MODE_MASTER, 1000000, 8);   //MOTO_MODE_1   T2 flaky on 1290
        //T2works MAP_SSIConfigSetExpClk(SSI3_BASE, g_ui32SysClock, SSI_FRF_MOTO_MODE_1, SSI_MODE_MASTER, 100000, 8);   //MOTO_MODE_1  1/10  DEBUG APFTODO, working T2
        //T2works MAP_SSIConfigSetExpClk(SSI3_BASE, g_ui32SysClock, SSI_FRF_MOTO_MODE_1, SSI_MODE_MASTER, 400000, 8);   //MOTO_MODE_1  1/10  DEBUG APFTODO, working T2
        //T2broken MAP_SSIConfigSetExpClk(SSI3_BASE, g_ui32SysClock, SSI_FRF_MOTO_MODE_1, SSI_MODE_MASTER, 800000, 8);   //MOTO_MODE_1  1/10  DEBUG APFTODO, working T2
        MAP_SSIConfigSetExpClk(SSI3_BASE, g_ui32SysClock, SSI_FRF_MOTO_MODE_1, SSI_MODE_MASTER, 1000000, 8);   //MOTO_MODE_1, back to 1MHz with SPI CS fix in init for 1290
        MAP_SSIEnable(SSI3_BASE);
    #else
        #error "Unknown part"
    #endif


#elif BOARD_NANOFET >= 1

    // nanoFET has ADS1284 always powered

    //Deassert GPIO control signals for ADS1248: ADC_Reset reset high (active lo), START high, CS high
    iso_RESET_high();    // ADC_Reset is PortN.5
    iso_START_high();    // START is PortN.6
    iso_deassert_CS();   // PortH.1


    // Pulse ADS1248 RESET
    iso_RESET_low();
    MAP_SysCtlDelay(MICROSECOND * 10);  // ADS1258 reset is 1 usec, 10 usec is conservative
    iso_RESET_high();

    // Setup SSI port 3 but on 64pin part it is mapped to PD0-3
    MAP_SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI3);
    while( ! MAP_SysCtlPeripheralReady(SYSCTL_PERIPH_SSI3) )
    {

    }

    MAP_GPIOPinConfigure(GPIO_PD2_SSI3RX);   // MISO
    MAP_GPIOPinConfigure(GPIO_PD3_SSI3TX);   // MOSI
    MAP_GPIOPinConfigure(GPIO_PD0_SSI3CLK);
    MAP_GPIOPinTypeSSI(GPIO_PORTD_BASE, GPIO_PIN_0 | GPIO_PIN_2 | GPIO_PIN_3);


    // Configure and enable SSI for SPI master mode.
    MAP_SSIConfigSetExpClk(SSI3_BASE, GetSysClkHz(), SSI_FRF_MOTO_MODE_1, SSI_MODE_MASTER, 1000000, 8);   //MOTO_MODE_1
    MAP_SSIEnable(SSI3_BASE);

    // Wait for ADS1248 clock initialization and calibration to complete after reset (datasheet says 16msec), SPI communication can begin 0.6 msec after reset
    MAP_SysCtlDelay(MILLISECOND*20);

#else
    #error "Unknown board"
#endif

    // set to non-auto-reg mode, this avoids a possible race condition with conversion completion and clocking out the ADC value
    {
        const uint8_t buf[1] = {ADS1248_CMD_SDATAC};

        iso_assert_CS();
        ads1248_spi_write(buf, 1);
        iso_deassert_CS();
        MAP_SysCtlDelay(2 * MICROSECOND); // ensure tCSPW is met, the min nCS high time between commands

        // Wait for conversion to complete, as SDATAC only applies to the conversion after DRDY pulses
        while( iso_DRDY() )
        {

        }
    }

    // Set some other settings to make sure they are correct
    {
        const uint8_t buf[2] = {
                                0x00, // DOUT/nDRDY is DOUT
                                0xCC  // excitation off
        };

        iso_assert_CS();
        ads1248_write_reg(REG_IDAC0, buf, 2);
        iso_deassert_CS();
        MAP_SysCtlDelay(2 * MICROSECOND); // ensure tCSPW is met, the min nCS high time between commands

        // Wait for conversion to complete to make sure settings commit
        while( iso_DRDY() )
        {

        }
    }

#if 0
    // TODO: should we run self cal? SYSOCAL and SYSGCAL need external help, SELFOCAL is internal
    if(false)
    {
        const uint8_t buf[1] = {ADS1248_CMD_SELFOCAL};

        iso_assert_CS();
        ads1248_spi_write(buf, 1);
        iso_deassert_CS();
        MAP_SysCtlDelay(2 * MICROSECOND); // ensure tCSPW is met, the min nCS high time between commands

        // Wait for conversion to complete, as SDATAC only applies to the conversion after DRDY pulses
        while( iso_DRDY() )
        {

        }
    }
#endif

}




/*
 * closeADS1248()
 * Clears uP output pins for low power.
 * Shuts of power to optoisolators and ADS1248 analog supply.
 */
void closeADS1248_iso(void)
{

#if BOARD_MFET >= 1 || BOARD_MPHOX >= 1  || BOARD_MSC == 2
	iso_START_high();			// Pin cleared
	iso_RESET_high();			//  "
	MAP_SSIDisable(SSI3_BASE);

	// Change the SSI pins to GPIO to reduce power consumption
	MAP_SysCtlPeripheralDisable(SYSCTL_PERIPH_SSI3);

	MAP_GPIOPinTypeGPIOOutput(GPIO_PORTH_BASE, GPIO_PIN_0);		// SSI3CLK
	MAP_GPIOPinWrite(GPIO_PORTH_BASE, GPIO_PIN_0, 0x00);

	MAP_GPIOPinTypeGPIOInput(GPIO_PORTH_BASE, GPIO_PIN_2);		// SSI3Rx

	MAP_GPIOPinTypeGPIOOutput(GPIO_PORTH_BASE, GPIO_PIN_3);		// SSI3Tx
	MAP_GPIOPinWrite(GPIO_PORTH_BASE, GPIO_PIN_3, 0x00);

	iso_assert_CS();	// ADS1248 CS asserted low

	// Power down
	MAP_GPIOPinWrite(GPIO_PORTK_BASE, GPIO_PIN_2, 0x00);  	// Iso 3.3V power off
#elif BOARD_MSC == 3

	// Put ADC to sleep
	iso_deassert_CS();
	iso_START_low();
	_dsb();

    // Disable SSI3  to reduce power consumption
    MAP_SSIDisable(SSI3_BASE);
    MAP_SysCtlPeripheralDisable(SYSCTL_PERIPH_SSI3);

    // Change the SSI pins to GPIO to reduce power consumption
    #if defined(PART_TM4C123GH6PM)
        MAP_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_0, 0x00);
        MAP_GPIOPinTypeGPIOOutput(GPIO_PORTD_BASE, GPIO_PIN_0);     // SSI3CLK = PD0

        MAP_GPIOPinTypeGPIOInput(GPIO_PORTD_BASE, GPIO_PIN_2);      // SSI3Rx = PD2 MISO

        MAP_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_3, 0x00);
        MAP_GPIOPinTypeGPIOOutput(GPIO_PORTD_BASE, GPIO_PIN_3);     // SSI3Tx = PD3 MOSI
    #elif defined(PART_TM4C1290NCPDT)
        MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_3, 0x00);
        MAP_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_3);     // SSI3CLK = PF3

        //TODO: WPD on SSI3 MISO to reduce power if it chatters
        MAP_GPIOPinTypeGPIOInput(GPIO_PORTF_BASE, GPIO_PIN_0);      // SSI3Rx = PF0 MISO   SSI3DAT1

        MAP_GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_1, 0x00);
        MAP_GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_1);     // SSI3Tx = PF1 MOSI   SSI3DAT0
    #else
        #error "Unknown part"
    #endif

        // Put part in reset since we need to assert nCS for power down
        iso_RESET_low();
        _dsb();
        MAP_SysCtlDelay(2 * MICROSECOND);

        // Idle last IO lines and power off iso
        iso_assert_CS();
        _dsb();

        //TODO: WPD on iso adc DRDY to reduce power if it chatters

        // Power down
        iso_POWER_low();
        _dsb();

#elif BOARD_NANOFET >= 1

    iso_START_high();           // Pin cleared
    iso_RESET_high();           //  "
    MAP_SSIDisable(SSI3_BASE);

    // Change the SSI pins to GPIO to reduce power consumption
    MAP_SysCtlPeripheralDisable(SYSCTL_PERIPH_SSI3);

    MAP_GPIOPinTypeGPIOOutput(GPIO_PORTD_BASE, GPIO_PIN_0);     // SSI3CLK PD0
    MAP_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_0, 0x00);

    MAP_GPIOPinTypeGPIOInput(GPIO_PORTD_BASE, GPIO_PIN_2);      // SSI3Rx PD2

    MAP_GPIOPinTypeGPIOOutput(GPIO_PORTD_BASE, GPIO_PIN_3);     // SSI3Tx PD3
    MAP_GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_3, 0x00);

    iso_assert_CS();    // ADS1248 CS asserted low
#else
    #error "Unknown board"
#endif


}

/*
 * ADS1248_gpio_init()
 * Initialize the ADS1248 GPIO0,1,2,3 as outputs that are used for analog mux address control
 *
 */
void ADS1248_gpio_init(void)
{

#if BOARD_MFET >= 1 || BOARD_MPHOX >= 1  || BOARD_MSC == 2 || BOARD_MSC == 3

    // Write configuration data to GPIOCFG, GPIODIR, GPIODAT in one transaction
    const uint8_t buf[3] = {
                      0x0F, // cfg, enable GPIO0 and GPIO1 and GPIO2 and GPIO3
                      0x00, // dir, gpio as output
                      0x00  // dat, gpio data
    };

    iso_assert_CS();
    ads1248_write_reg(REG_GPIOCFG, buf, 3);
    iso_deassert_CS();
    MAP_SysCtlDelay(2 * MICROSECOND); // ensure tCSPW is met, the min nCS high time between commands

#elif BOARD_NANOFET >= 1

    // Write configuration data to GPIOCFG, GPIODIR, GPIODAT in one transaction
    iso_assert_CS();                                    // Start SSI register write transaction

    gpiocfg = 0x00000001;                               // enable gpio0
    gpiodir = 0x000000FE;                               // write all bit.0-7 gpio as input (1) except gpio0
    gpiodat = 0x00000000;                               // not needed

    while(MAP_SSIDataGetNonBlocking(SSI3_BASE, &data[0]));     // Flush receive FIFO

    MAP_SSIDataPutNonBlocking(SSI3_BASE, WREG + REG_GPIOCFG);   // Start writing at REG_GPIOCFG
    MAP_SSIDataPutNonBlocking(SSI3_BASE, 2);                    // Write 3 bytes (n-1)
    MAP_SSIDataPutNonBlocking(SSI3_BASE, gpiocfg);
    MAP_SSIDataPutNonBlocking(SSI3_BASE, gpiodir);
    MAP_SSIDataPutNonBlocking(SSI3_BASE, gpiodat);

    while(MAP_SSIBusy(SSI3_BASE))
        ;                      // Let SSI finish sending bytes
    iso_deassert_CS();                                  // Finish SSI register write transaction
#else
    #error "Unknown board"
#endif
}

/*
 * ADS1248_gpio_ADG1609_mux()
 * Write the ADS1248 GPIO used as ADG1609 mux control
  */
void ADS1248_gpio_ADG1609_mux(uint32_t chan)
{
#if BOARD_MFET >= 1  || BOARD_MPHOX == 1  || BOARD_MSC == 2 || BOARD_MSC == 3
    // if chan==0 or chan==3
    // then turn on ADS1248_gpio_num=2 (pin 17)
    // GPIO=0,1 are used for MUX address

    if(chan > 3)
    {
        uprintf("Mux channels are 0 to 3 in ADS1248_iso.c, error in arg for ADS1248_gpio_ADG1609_mux");  prn_nl();
        chan = 3;
    }

    // invert A0,A1 to the mux (chan selector) to compensate for hardware voltage level translation
    uint8_t gpiodat = (~chan) & 0x03U;                 // gpiodat has the inverted mux address (to compensate for the hardware level translator inversion)
    //uprintf("ADG1609 chan %lu invert for level shift, %lu", chan, gpiodat);  prn_nl();
    if((chan==0) || (chan==3))      // THOM support for new MFET/MCAP (not connected on first MFET)
    {
        gpiodat |= 0x04U;         // Set ADS1248 GPIO2 if chan0 or chan3
    }
    else
    {
        gpiodat &= ~0x04U;         // Clear ADS1248 GPIO2 if not chan0 or chan3
    }

    iso_assert_CS();
    ads1248_write_reg(REG_GPIODAT, &gpiodat, 1);
    iso_deassert_CS();
    MAP_SysCtlDelay(2 * MICROSECOND); // ensure tCSPW is met, the min nCS high time between commands

#else
    #error "Unknown board"
#endif

}





/*
 * pollADS1248_iso()
 * Returns the voltage between to two specified differential pins.
 * Sample averaging indicated by value of "trials".
 * Gain and data rate can be set for each channel.
 */
float pollADS1248_iso(unsigned char pos_chan, unsigned char neg_chan, int trials, int gain, int sps)  // was int trials 28 Apr 2021
{
	long sample = 0;
    long result = 0;

    #define MAX_TRIALS  120
	float voltages[MAX_TRIALS];

    wdog_pet();

	if(trials > MAX_TRIALS)     // THOM 3/19/2018, fixing a crash (app exit) that ends in loader_exit()
	{
	    uprintf("MAX TRIALS is %u in ADS1248_iso.c, more trials (avg) than %u was requested, edit pollADS1248_iso() and recompile", MAX_TRIALS, MAX_TRIALS);  prn_nl();
	    trials = MAX_TRIALS;
	}

	// Write configuration data to MUX0, VBIAS, MUX1, and SYS0 in one transaction
	// This also resets the digital filter and restarts conversion similar to a SYNC command
	{
	    const uint8_t cfg_buf[4]  = {
	                           (pos_chan << 3) | neg_chan, // MUX0 channel register,
	                           0,                          // VBIAS register
	                           0x30,                       // 2.048 V internal reference selected
	                           ADS1248_gain_sps(gain, sps) // SYS0 register. Sets gain and data rate
	    };

	    iso_assert_CS();
	    ads1248_write_reg(REG_MUX0, cfg_buf, 4);
	    iso_deassert_CS();
	    MAP_SysCtlDelay(2 * MICROSECOND); // ensure tCSPW is met, the min nCS high time between commands
	}

    const uint8_t wbuf[4] = {ADS1248_CMD_RDATA, ADS1248_CMD_NOP, ADS1248_CMD_NOP, ADS1248_CMD_NOP};
    uint8_t rbuf[4];

	// A/D is sampling continuously, so get as many samples as we need
	for(int i = 0; i < trials; i++)
	{
        wdog_pet();

	    // Wait for conversion to complete
		while( iso_DRDY() )
		{

		}

		iso_assert_CS();
		ads1248_spi_write_and_read(wbuf, rbuf, 4);
		iso_deassert_CS();
		MAP_SysCtlDelay(2 * MICROSECOND); // ensure tCSPW is met, the min nCS high time between commands

		// Concatenate the 3 bytes of the 24 bit count
		result = (((uint32_t)rbuf[1]) << 16) | (((uint32_t)rbuf[2]) << 8 ) | ((uint32_t)rbuf[3]);

		// Mask off MSB of 32 bit word
		result = 0x00FFFFFF & result;

		// Check to see if MSB is set (if MSB == 1, then we have a negative #, must convert appropriately)
		if( (result & 0x00800000) == 0x00800000 )
		{
			// Do the 2's complement conversion (we have a negative number)
			result = ~result;
			result = 0x007FFFFF & result;
			++result;
			result *= -1;
		}

		// TODO: should the inner divisions be floating point?
        //voltages[i] = ((float) sample/gain) * COUNTSTOVOLTS;
        // voltages[i] = ((float) result/gain) * COUNTSTOVOLTS;
		voltages[i]  = result;
		voltages[i] /= gain;
		voltages[i] *= COUNTSTOVOLTS;
		sample += result;
	}

	// TODO: should the inner divisions be floating point?
	// mean = ((float) (sample/trials/gain)) * COUNTSTOVOLTS;
	float mean = sample;
	mean /= trials;
	mean /= gain;
	mean *= COUNTSTOVOLTS;

	if(trials > 1)
	{
	    float sum_deviation = 0.0f;
		for(int i = 0; i < trials; i++)
		{
		    const float deviation = voltages[i]-mean;
			sum_deviation += deviation*deviation;
		}
		//sys_data.AD24_std = sqrtf(sum_deviation/(trials-1));   //Bessel correction is not good at low number of trials
		sys_samp.AD24_std = sqrtf(sum_deviation/trials);       // THOM 7 Jun 2018 - should check for number of trials and if low num use standard
	}
	else
	{
		sys_samp.AD24_std = 0.0f;
	}

	// TODO: should the inner divisions be floating point?
	// return ((float) (sample/trials/gain)) * COUNTSTOVOLTS;
	return mean;
}

/*
 * ADS1248_set_gain_sps()
 * Sets the PGA gain and data rate (samples per second).
 * Valid values for gain are: 1,2,4,8,16,32,64 and 128.
 * Valid values for data rate are: 5,10,20,40,80,160,320,640,1000 and 2000 sps.
 */
unsigned char ADS1248_gain_sps(const int gain, const int sps)
{
    uint8_t gain_code;
    uint8_t sps_code;
	uint8_t gain_sps;

	switch(gain)
	{
		case 1:
		    gain_code = PGA_1;
			break;

		case 2:
		    gain_code = PGA_2;
			break;

		case 4:
		    gain_code = PGA_4;
			break;

		case 8:
		    gain_code = PGA_8;
			break;

		case 16:
		    gain_code = PGA_16;
			break;

		case 32:
		    gain_code = PGA_32;
			break;

		case 64:
		    gain_code = PGA_64;
			break;

		case 128:
		    gain_code = PGA_128;
			break;

		default:
			prn_nl(); prn_nl();
			if(serDbgFlg == 1) { uprintf("Unrecognized PGA gain. Setting to 1."); }
			gain_code = PGA_1;
	}

	switch(sps)
	{
		case 5:
		    sps_code = SPS_5;
			break;

		case 10:
		    sps_code = SPS_10;
			break;

		case 20:
		    sps_code = SPS_20;
			break;

		case 40:
		    sps_code = SPS_40;
			break;

		case 80:
		    sps_code = SPS_80;
			break;

		case 160:
		    sps_code = SPS_160;
			break;

		case 320:
		    sps_code = SPS_320;
			break;

		case 640:
		    sps_code = SPS_640;
			break;

		case 1000:
		    sps_code = SPS_1000;
			break;

		case 2000:
		    sps_code = SPS_2000;
			break;

		default:
			prn_nl(); prn_nl();
			uprintf("Unrecognized data rate. Setting to 5 sps.");
			sps_code = SPS_5;
	}

    // Combine the two nibbles
	gain_sps = gain_code | sps_code;

	return gain_sps;
}

/*
 * ADS1248_gpio0()
 * Write ADS1248 GPIO0 used to measure bias bat on NanoFET
  */
void ADS1248_gpio0(uint32_t state)
{
#if BOARD_NANOFET >= 1
    uint32_t gpiodat, data[4];

    // Write configuration data to GPIOCFG, GPIODIR, GPIODAT in one transaction
    iso_assert_CS();                                    // Start SSI register write transaction

    gpiodat = 0x00000000;
    if(state == 1)
    {
        gpiodat = 0x00000001;
    }

    while(MAP_SSIDataGetNonBlocking(SSI3_BASE, &data[0]));     // Flush receive FIFO

    MAP_SSIDataPutNonBlocking(SSI3_BASE, WREG + REG_GPIODAT);   // Start writing at REG_GPIODAT  (WREG=0x40, see ADS1248 datasheet)
    MAP_SSIDataPutNonBlocking(SSI3_BASE, 0);                    // Write 1 bytes (n-1)
    MAP_SSIDataPutNonBlocking(SSI3_BASE, gpiodat);              // write 00, 01, 10, 11 for channel select

    while(MAP_SSIBusy(SSI3_BASE));                      // Let SSI finish sending bytes
    iso_deassert_CS();                                  // Finish SSI register write transaction
#endif
}

/*
 * ADS1248_gpio2()
 * Write ADS1248 GPIO2 used to control 'offset voltage measurement'
  */
void ADS1248_gpio2(uint32_t state)
{
#if BOARD_MFET >= 1  || BOARD_MPHOX >= 1  || BOARD_MSC == 2  || BOARD_MSC == 3

    uint8_t gpiodat = 0x00;
    if(state == 1)
    {
        gpiodat = 0x04;
    }

    iso_assert_CS();
    ads1248_write_reg(REG_GPIODAT, &gpiodat, 1);
    iso_deassert_CS();
    MAP_SysCtlDelay(2 * MICROSECOND); // ensure tCSPW is met, the min nCS high time between commands
#else
    #error "Unknown board"
#endif
}


void ADS1248_gpio_test(void)
{
#if BOARD_MFET >= 1  || BOARD_MPHOX >= 1  || BOARD_MSC == 2 || BOARD_MSC == 3

    openADS1248_iso();
    MAP_SysCtlDelay(MILLISECOND * 100);
    ADS1248_gpio_init();
    while(1)
    {
        ADS1248_gpio2(0);
        MAP_SysCtlDelay(MILLISECOND*1);
        ADS1248_gpio3(0);
        MAP_SysCtlDelay(MILLISECOND*1);

        ADS1248_gpio2(1);
        MAP_SysCtlDelay(MILLISECOND*1);
        ADS1248_gpio3(1);
        MAP_SysCtlDelay(MILLISECOND*1);
    }
#ifdef WARNING_ABATEMENT_UNREACHABLE
    closeADS1248_iso();
#endif
#else
    #error "Unknown board"
#endif
}

/*
 * ADS1248_gpio3()
 * Write ADS1248 GPIO3 used to control iso battery bias voltage  (float measures gnd, 0 measures bias-, 1 measures bias+)
  */
void ADS1248_gpio3(uint32_t state)
{
#if BOARD_MFET >= 1  || BOARD_MPHOX >= 1  || BOARD_MSC == 2 || BOARD_MSC == 3

    uint8_t gpiodat = 0x00;
    if(state == 1)
    {
        gpiodat = 0x00000008;
    }

    iso_assert_CS();
    ads1248_write_reg(REG_GPIODAT, &gpiodat, 1);
    iso_deassert_CS();
    MAP_SysCtlDelay(2 * MICROSECOND); // ensure tCSPW is met, the min nCS high time between commands

#else
    #error "Unknown board"
#endif
}
