Categories
Statistics
Flag Counter
Since 08.08.2014
Counts only, if "DNT = disabled".

Your IP is 44.213.60.33
ec2-44-213-60-33.compute-1.a
Info
Valid HTML 4.01 Transitional Creative Commons Lizenzvertrag
rss
เราจะทำแบบวิศวกรผู้ยิ่งใหญ่
We love the King
23. July 2024
Your valuable opinion :
3 stars

Avg. 3.18 from 11 votes.



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




Block Diagram Nuumod


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




Inside the Nuumod

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 ?

 
t1 = 6585 d

t2 = 272 ms

★ ★ ★  Copyright © 2006 - 2024 by changpuak.ch  ★ ★ ★

Impressum