Arduino-Nuumod.php 19040 Bytes 04-06-2024 20:15:20
Arduino/Genuino "Nuumod"
A Microvoltmeter with the LTC2400 and an Envico Interface
The assembled prototype
✈ Motivation
The status of our vacuum chambers is monitored via a voltage output of the ion pumps.
This voltage is measured with a
Pingumod.
Unfortunately one device outputs a vey low voltage (approx. 6 mV). This results in a constant
line on the Graphana Screen, as the resolution of the INA260 is only 1 mV. In order
to get a deeper insight in the well-beeing of the ion-pump / vacuum chamber, a device with a higher
resolution was desired. And yes, of course a I2C interface was also needed ...
✈ The Design
At the input, a OP07 (Low offset voltage: 150 μV max. Input offset drift: 1.5 μV/°C max) serves as an
Amplifier (100x for ±25 mV, 10x for ± 250 mV and 1x for ±2500 mV) as well as a buffer.
A MAX4602 (2.5Ω Quad, SPST, CMOS Analog Switch) does the range switching. The bipolar amplified
voltage is then level shifted to 0 ... 5 V by an Adder. The A/D conversion is done with the well-known
LTC 2400 (24-Bit µPower No Latency ΔΣ™ ADC)
The Arduino is the mastermind of that design. It also does the averageing. We use a maximum
of 8 Samples to be averaged. This is limited by the memory of the Arduino UNO. The Arduino can output
the floating value via usb or act as an I2C slave, when used as an Envico Sensor. The Eeprom
holds Sensor information, written to and read by a Envico Basestation.
The devices has NO overload protection, as this could influence the measurement accuracy.
A power supply of 12 ... 15 V, 250 mA is recommended.
✈ Test Sketch for Arduino/Genuino Uno
Double click on code to select ...
/* //////////////////////////////////////////////////////////////////
ARDUINO/Genuino Project "NUUMOD", a Microvoltmeter with the LTC2400
https://www.changpuak.ch/electronics/Arduino-Nuumod.php
Software Version 1.0, Standalone Version
When using as a remote sensor, measuring must be synchronised !!!
11.09.2020 by ALEXANDER SSE FRANK
HELPFUL :
https://learn.adafruit.com/adafruit-gfx-graphics-library/
graphics-primitives
////////////////////////////////////////////////////////////////// */
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH1106.h>
// DISPLAY
#define OLED_MOSI 6
#define OLED_CLK 5
#define OLED_DC 8
#define OLED_CS 9
#define OLED_RESET 7
// ROTARY ENCODER
const int RotaryEncoder1 = 4 ; // PRESSED
const int RotaryEncoder2 = 2 ;
const int RotaryEncoder3 = 3 ;
volatile boolean LEFT = false ;
volatile boolean RIGHT = false ;
volatile boolean PRESS = false ;
Adafruit_SH1106 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
#if (SH1106_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SH1106.h!");
#endif
// MEASUREMENT VALUES
double V_TRUE = 0.0 ;
double V_ABS = 0.0 ;
long V_RAW = 0 ; // 32 Bit
long OFFSET_ZERO = 8388608 ; // 32 Bit
int Average = 8 ;
const int AverageMax = 8 ;
const int AverageMin = 1 ;
double RingBuffer[AverageMax+1] ;
int RingBufferPointer = 0 ;
double RingBufferSum = 0.0 ;
const int ON = 1 ;
const int OFF = 0 ;
int Cursor = 1 ; // 0 = invisible
int CursorDelay = 399 ;
unsigned long NextCursorAction = 0 ;
// /////////////////////////////////////////////////////////////
// Range Routines
// /////////////////////////////////////////////////////////////
const int RangePin0 = A0 ;
const int RangePin1 = A1 ;
const int RangePin2 = A2 ;
const int RangePin3 = A3 ;
int Range = 0 ; // +/- 25 mV
// MAX 4602
// LOGIC 0 = OFF
// LOGIC 1 = ON
// A2 -> GAIN = 1
// A3 -> GAIN = 10
// A0 -> GAIN = 100
// A1 -> CONNECT INPUT
void Thru(int OnOff)
{
if(OnOff) digitalWrite(RangePin1, HIGH) ;
if(!OnOff) digitalWrite(RangePin1, LOW) ;
}
void SetRange()
{
switch (Range)
{
case 0:
// +/-25mV, GAIN = 100
digitalWrite(RangePin0, HIGH) ;
digitalWrite(RangePin2, LOW) ;
digitalWrite(RangePin3, LOW) ;
break;
case 1:
// +/-250mV, GAIN = 10
digitalWrite(RangePin0, LOW) ;
digitalWrite(RangePin2, LOW) ;
digitalWrite(RangePin3, HIGH) ;
break;
case 2:
// +/-2500mV, GAIN = 1
digitalWrite(RangePin0, LOW) ;
digitalWrite(RangePin2, HIGH) ;
digitalWrite(RangePin3, LOW) ;
break;
default:
// NOP
// NOP
break;
}
}
// /////////////////////////////////////////////////////////////
// ADC Routines
// /////////////////////////////////////////////////////////////
const int ADC_CLK_PIN = 13 ;
const int ADC_DATA_PIN = 12 ;
const int ADC_CHIP_SELECT_PIN = 11 ;
const double ADC_REFERENCE = 4.999999 ;
const long FULL_SCALE = 16777216 ;
// ERROR
// 0 = IN RANGE, OK
// 1 = OVER RANGE
// 2 = UNDER RANGE
// 3 = SOMETHING WRONG HERE
int error = 0 ;
void UpdateADCValue()
{
// START CONVERSION
digitalWrite(ADC_CLK_PIN, LOW) ;
digitalWrite(ADC_CHIP_SELECT_PIN, LOW) ;
// WAIT FOR BIT 31 TO GO LOW (WE HAVE A PULLUP THERE)
while(digitalRead(ADC_DATA_PIN)) delayMicroseconds(1) ;
V_RAW = shiftIn(ADC_DATA_PIN, ADC_CLK_PIN, MSBFIRST) ;
// Serial.print(V_RAW,HEX); Serial.print("-");
error = 3 ; // SOMETHING WRONG HERE
if ((V_RAW & 0xF0) == 0x30 ) error = 1 ; // OVER RANGE
if ((V_RAW & 0xF0) <= 0x10 ) error = 2 ; // UNDER RANGE
if ((V_RAW & 0xF0) == 0x20 ) error = 0 ; // IN RANGE, OK
// Serial.println(error);
V_RAW = (V_RAW & 0x0F) << 8 ;
V_RAW |= shiftIn(ADC_DATA_PIN, ADC_CLK_PIN, MSBFIRST) ;
V_RAW = V_RAW << 8 ;
V_RAW |= shiftIn(ADC_DATA_PIN, ADC_CLK_PIN, MSBFIRST) ;
V_RAW = V_RAW << 8 ;
V_RAW |= shiftIn(ADC_DATA_PIN, ADC_CLK_PIN, MSBFIRST) ;
digitalWrite(ADC_CHIP_SELECT_PIN, HIGH) ;
V_RAW = V_RAW >> 4 ;
V_TRUE = ADC_REFERENCE * (V_RAW - OFFSET_ZERO) / FULL_SCALE ;
if(Range == 0) V_TRUE *= 10 ;
if(Range == 1) V_TRUE *= 100 ;
if(Range == 2) V_TRUE *= 1000 ;
}
void AutoZero()
{
// DISCONNECT INPUT
Thru(OFF) ;
delay(200) ;
UpdateADCValue() ;
if(error == 0) OFFSET_ZERO = V_RAW ;
// Serial.println(OFFSET_ZERO,DEC) ;
Thru(ON) ;
}
// /////////////////////////////////////////////////////////////////////
// SUBROUTINES DISPLAY.
// /////////////////////////////////////////////////////////////////////
void UpDateDisplay()
{
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0); display.print("****");
display.setCursor(45,0); display.print("NUUMOD");
display.setCursor(104,0); display.print("****");
display.drawLine(0, 12, 128, 12, WHITE);
display.setTextSize(2) ;
// VOLTAGE
display.setCursor(0,19) ;
if(error == 0)
{
if(V_TRUE > 0.0) display.print("+") ;
if(V_TRUE < 0.0) display.print("-") ;
V_ABS = abs(V_TRUE) ;
display.setCursor(19,19) ;
if(V_ABS < 10.0)
{
display.print(V_ABS,4) ;
}
if((V_ABS >= 10.0) && (V_ABS < 100.0))
{
display.print(V_ABS,3) ;
}
if((V_ABS >= 100.0) && (V_ABS < 1000.0))
{
display.print(V_ABS,2) ;
}
if(V_ABS >= 1000.0)
{
display.print(V_ABS,1) ;
}
if(V_ABS >= 10000.0)
{
display.print(" --.--") ;
}
display.print(" mV") ;
}
// ERROR !=0
if(error == 1) display.print("THRU CEIL") ;
if(error == 2) display.print("THRU FLOOR") ;
if(error > 2) display.print("ERROR ?") ;
display.drawLine(0, 40, 128, 40, WHITE);
// RANGE
display.setTextSize(0);
display.setCursor(0, 45) ;
if(Range == 0) display.print("RANGE : 25 mV") ;
if(Range == 1) display.print("RANGE : 250 mV") ;
if(Range == 2) display.print("RANGE : 2500 mV") ;
// AVERAGE
display.setCursor(0, 57) ;
display.print("AVERAGE : ") ;
display.print(Average, DEC) ;
// Cursor
if(Cursor == 1) display.fillTriangle(122, 48, 127, 45, 127, 51, 1) ;
if(Cursor == 2) display.fillTriangle(122, 60, 127, 57, 127, 63, 1) ;
display.display() ;
}
// /////////////////////////////////////////////////////////////////////
// S E T U P
// /////////////////////////////////////////////////////////////////////
void setup()
{
Serial.begin(115200) ;
// INIT OLED
display.begin(SH1106_SWITCHCAPVCC);
// SHOW STARTUP SCREEN
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0); display.print("****");
display.setCursor(45,0); display.print("NUUMOD");
display.setCursor(104,0); display.print("****");
display.drawLine(0, 12, 128, 12, WHITE);
display.setTextSize(1);
display.setCursor(0, 21);
display.println("A MICROVOLTMETER");
display.setCursor(0, 33);
display.println("FOR ENVICO SYSTEM.");
display.setCursor(0, 45);
display.println("(C) ETH QUANTUMOPTICS");
display.setCursor(0, 57);
display.println("BUILT 28.10.2020");
display.display();
delay(999) ;
pinMode(RotaryEncoder1, INPUT_PULLUP);
pinMode(RotaryEncoder2, INPUT_PULLUP);
pinMode(RotaryEncoder3, INPUT_PULLUP);
// YELLOW
attachInterrupt(digitalPinToInterrupt(RotaryEncoder2),
RotaryEncoderISR2, FALLING);
// GREEN
attachInterrupt(digitalPinToInterrupt(RotaryEncoder3),
RotaryEncoderISR3, FALLING);
// PRESCALER
pinMode(RangePin0, OUTPUT) ;
pinMode(RangePin1, OUTPUT) ;
pinMode(RangePin2, OUTPUT) ;
pinMode(RangePin3, OUTPUT) ;
Serial.println("Nuumod V1.9 by Changpuak.ch (C) 28.10.2020") ;
Serial.println("DEVICE READY.\n") ;
SetRange() ;
// ADC
pinMode(ADC_CLK_PIN, OUTPUT) ;
pinMode(ADC_DATA_PIN, INPUT_PULLUP) ;
pinMode(ADC_CHIP_SELECT_PIN, OUTPUT) ;
delay(3000);
UpdateADCValue() ;
UpDateDisplay() ;
// SOME CALIBRATION
AutoZero() ;
}
// /////////////////////////////////////////////////////////////////////
// AUX FUNCTIONS
// /////////////////////////////////////////////////////////////////////
void EvaluateKeyBoard()
{
if (LEFT)
{
noInterrupts() ;
// RANGE
if((Cursor == 1) && (millis() > NextCursorAction))
{
Range -= 1 ;
if(Range < 0) Range = 2 ;
NextCursorAction = millis() + CursorDelay ;
}
// AVERAGE
if((Cursor == 2) && (millis() > NextCursorAction))
{
Average -= 1 ;
if(Average < AverageMin) Average = AverageMin ;
NextCursorAction = millis() + CursorDelay ;
}
SetRange() ;
LEFT = false ;
RIGHT = false ;
interrupts() ;
}
if (RIGHT)
{
noInterrupts() ;
// RANGE
if((Cursor == 1) && (millis() > NextCursorAction))
{
Range += 1 ;
if(Range > 2) Range = 0 ;
NextCursorAction = millis() + CursorDelay ;
}
// AVERAGE
if((Cursor == 2) && (millis() > NextCursorAction))
{
Average += 1 ;
if(Average > AverageMax) Average = AverageMax ;
NextCursorAction = millis() + CursorDelay ;
}
SetRange() ;
LEFT = false ;
RIGHT = false ;
interrupts() ;
}
// KEY PRESSED >> ADVANCE CURSOR
if(PRESS)
{
if(millis() > NextCursorAction)
{
Cursor += 1 ;
if(Cursor == 3) Cursor = 1 ;
NextCursorAction = millis() + CursorDelay ;
}
PRESS = false ;
}
}
// /////////////////////////////////////////////////////////////////////
// M A I N L O O P
// /////////////////////////////////////////////////////////////////////
void loop()
{
Thru(ON) ;
EvaluateKeyBoard() ;
RingBufferSum = 0.0 ;
UpdateADCValue() ;
RingBuffer[RingBufferPointer] = V_TRUE ;
RingBufferPointer += 1 ;
if(RingBufferPointer >= Average) RingBufferPointer = 0 ;
for(int i=0; i < Average; i++) RingBufferSum += RingBuffer[i] ;
V_TRUE = RingBufferSum / Average ;
Serial.println(V_TRUE,3) ;
UpDateDisplay() ;
AutoZero() ;
delay(199) ;
if(!digitalRead(RotaryEncoder1)) PRESS = true ;
}
// /////////////////////////////////////////////////////////////////////
// INTERRUPT SERVICE ROUTINES
// /////////////////////////////////////////////////////////////////////
void RotaryEncoderISR2()
{
// YELLOW
LEFT = false ;
RIGHT = false ;
int autre = digitalRead(RotaryEncoder3) ;
if (autre < 1) LEFT = true ;
if (autre > 0) RIGHT = true ;
}
void RotaryEncoderISR3()
{
// GREEN
LEFT = false ;
RIGHT = false ;
int autre = digitalRead(RotaryEncoder2) ;
if (autre < 1) RIGHT = true ;
if (autre > 0) LEFT = true ;
}
// /////////////////////////////////////////////////////////////////////
// END OF FILE.
// /////////////////////////////////////////////////////////////////////
✈ Downloads
✈ Assembly
A look inside - not much to be seen :-)
This shield uses THT as well as SMD components. But they are large. We used 1206, as space
is generously available. The input circuit uses sockets, which shall allow quick repair. Just in case ...
✈ Share your thoughts
The webmaster does not read these comments regularely. Urgent questions should be send via email.
Ads or links to completely uncorrelated things will be removed.
Your Browser says that you allow tracking. Mayst we suggest that you check that DNT thing ?