aboutsummaryrefslogtreecommitdiffstats
path: root/sw/ltc2400.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sw/ltc2400.cpp')
-rw-r--r--sw/ltc2400.cpp154
1 files changed, 154 insertions, 0 deletions
diff --git a/sw/ltc2400.cpp b/sw/ltc2400.cpp
new file mode 100644
index 0000000..2a76d87
--- /dev/null
+++ b/sw/ltc2400.cpp
@@ -0,0 +1,154 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Matthias P. Braendli
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+*/
+
+/* Essential information from datasheet:
+ *
+ * SCK mode:
+ * The internal or external SCK mode is selected on power-up and then
+ * reselected every time a HIGH-to-LOW transition is detected at the CS pin. If
+ * SCK is HIGH or floating at power-up or during this transition, the
+ * converter enters the internal SCK mode. If SCK is LOW at power-up or
+ * during this transition, the converter enters the external SCK mode.
+ *
+ * CS:
+ * At any time during the conversion cycle, CS may be pulled LOW in order to
+ * monitor the state of the converter. While CS is pulled LOW, EOC is output
+ * to the SDO pin. EOC = 1 while a conversion is in progress and EOC = 0 if the
+ * device is in the sleep state. Independent of CS, the device automatically
+ * enters the low power sleep state once the conversion is complete.
+ *
+ * The device remains in the sleep state until the first rising edge of SCK is
+ * seen while CS is LOW.
+ *
+ * We must latch incoming data on the rising edge of SCK.
+ *
+ * On the 32nd falling edge of SCK, the device begins a new conversion.
+ *
+ * The sub LSBs are valid conversion results beyond the 24-bit level that may
+ * be included in averaging or discarded without loss of resolution.
+ *
+ * Output format, 32 bits in total:
+ * EOC, DMY, SIG, EXR, MSB..LSB (24 bits), SUB-LSB(4 bits)
+ *
+ * - EOC goes low when the conversion is complete
+ * - DMY is always low
+ * - SIG is 1 when Vin > 0
+ * - EXR is 0 when 0 <= Vin <= Vref
+ *
+ *
+ * We will use External SCK, Single Cycle Conversion (see table 4):
+ * - SCK must be low on falling edge of CSn
+ * - We will discard the sub LSB
+ * - We will check that DMY is always 0, SIG always 1 and EXR always 0
+ *
+*/
+
+#include <avr/io.h>
+#include <avr/interrupt.h>
+#include "ltc2400.h"
+
+static void cs_low()
+{
+ cli();
+ PORTB &= ~PINB_SPI_LTC_CSn;
+ sei();
+}
+
+static void cs_high()
+{
+ cli();
+ PORTB |= PINB_SPI_LTC_CSn;
+ sei();
+}
+
+void ltc2400_init()
+{
+ cli();
+
+ // Set bit order=MSB first (DORD=0)
+ // And SPI mode=0 (CPOL=0, CPHA=0)
+ SPCR &= ~(_BV(DORD) | _BV(CPOL) | _BV(CPHA));
+
+ // clock divider:
+ // SPR1 and SPR0 are the two LSB bits in SPCR
+ // SPR1 SPR0 ~SPI2X Freq
+ // 0 0 0 fosc/2
+ // 0 1 0 fosc/8
+ // 1 0 0 fosc/32
+ // 1 1 0 fosc/64
+ // double speed mode:
+ // 0 0 1 fosc/4
+ // 0 1 1 fosc/16
+ // 1 0 1 fosc/64
+ // 1 1 1 fosc/128
+
+ constexpr uint8_t SPI_CLOCK_MASK = 0x03; // SPR1 = bit 1, SPR0 = bit 0 on SPCR
+ constexpr uint8_t SPI_2XCLOCK_MASK = 0x01; // SPI2X = bit 0 on SPSR
+ SPCR = (SPCR & ~SPI_CLOCK_MASK) | (SPR1 & SPI_CLOCK_MASK);
+ SPSR = (SPSR & ~SPI_2XCLOCK_MASK); // clear SPI2X
+
+ sei();
+}
+
+int ltc2400_conversion_ready()
+{
+ cs_low();
+ // EOC == 0 means conversion is complete and device in sleep mode
+ const int eoc = (PORTB & PINB_SPI_MISO) ? 0 : 1;
+ cs_high();
+
+ return eoc;
+}
+
+float ltc2400_get_conversion_result(bool& dmy_fault, bool& exr_fault)
+{
+ cs_low();
+
+ uint8_t data[4] = {};
+
+ for (int i = 0; i < 4; i++) {
+ SPDR = 0x0; // always output 0
+
+ while (!(SPSR & _BV(SPIF))) { /* wait */ }
+ data[i] = SPDR;
+ }
+
+ cs_high();
+
+ const uint32_t result =
+ ((uint32_t)data[3] << 24) |
+ ((uint32_t)data[2] << 16) |
+ ((uint32_t)data[1] << 8) |
+ ((uint32_t)data[0]);
+
+ dmy_fault = result & _BV(30);
+ exr_fault = not (result & _BV(28));
+
+ // Mask 4 MSB status bits, and shift out 4 sub-LSB bits
+ const uint32_t adc_value = (result >> 4) & 0x00FFFFFF;
+
+ // Convert ADC value to voltage
+ return ((float)adc_value) / ((float)0x00FFFFFF) / 5.0f;
+}
+