Per Mårtensson 4 лет назад
Родитель
Сommit
0fc0644123

+ 7 - 0
CurrentRanger.code-workspace

@@ -0,0 +1,7 @@
+{
+	"folders": [
+		{
+			"path": "."
+		}
+	]
+}

+ 39 - 0
include/README

@@ -0,0 +1,39 @@
+
+This directory is intended for project header files.
+
+A header file is a file containing C declarations and macro definitions
+to be shared between several project source files. You request the use of a
+header file in your project source file (C, C++, etc) located in `src` folder
+by including it, with the C preprocessing directive `#include'.
+
+```src/main.c
+
+#include "header.h"
+
+int main (void)
+{
+ ...
+}
+```
+
+Including a header file produces the same results as copying the header file
+into each source file that needs it. Such copying would be time-consuming
+and error-prone. With a header file, the related declarations appear
+in only one place. If they need to be changed, they can be changed in one
+place, and programs that include the header file will automatically use the
+new version when next recompiled. The header file eliminates the labor of
+finding and changing all the copies as well as the risk that a failure to
+find one copy will result in inconsistencies within a program.
+
+In C, the usual convention is to give header files names that end with `.h'.
+It is most portable to use only letters, digits, dashes, and underscores in
+header file names, and at most one dot.
+
+Read more about using header files in official GCC documentation:
+
+* Include Syntax
+* Include Operation
+* Once-Only Headers
+* Computed Includes
+
+https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html

+ 46 - 0
lib/README

@@ -0,0 +1,46 @@
+
+This directory is intended for project specific (private) libraries.
+PlatformIO will compile them to static libraries and link into executable file.
+
+The source code of each library should be placed in a an own separate directory
+("lib/your_library_name/[here are source files]").
+
+For example, see a structure of the following two libraries `Foo` and `Bar`:
+
+|--lib
+|  |
+|  |--Bar
+|  |  |--docs
+|  |  |--examples
+|  |  |--src
+|  |     |- Bar.c
+|  |     |- Bar.h
+|  |  |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
+|  |
+|  |--Foo
+|  |  |- Foo.c
+|  |  |- Foo.h
+|  |
+|  |- README --> THIS FILE
+|
+|- platformio.ini
+|--src
+   |- main.c
+
+and a contents of `src/main.c`:
+```
+#include <Foo.h>
+#include <Bar.h>
+
+int main (void)
+{
+  ...
+}
+
+```
+
+PlatformIO Library Dependency Finder will find automatically dependent
+libraries scanning project source files.
+
+More information about PlatformIO Library Dependency Finder
+- https://docs.platformio.org/page/librarymanager/ldf.html

BIN
nextion/bat100.png


BIN
nextion/bat20.png


BIN
nextion/bat40.png


BIN
nextion/bat5.png


BIN
nextion/bat60.png


BIN
nextion/bat80.png


BIN
nextion/batcrg.png


BIN
nextion/bias.png


BIN
nextion/biasgrey.png


BIN
nextion/bluetooth.png


BIN
nextion/bluetoothgrey.png


BIN
nextion/currentranger.HMI


BIN
nextion/currentranger.png


BIN
nextion/lpf.jpg


BIN
nextion/lpf.png


BIN
nextion/lpfgrey.jpg


BIN
nextion/lpfgrey.png


BIN
nextion/next.png


BIN
nextion/prev.png


BIN
nextion/usb-symbol.jpg


BIN
nextion/usbgrey.jpg


+ 24 - 0
platformio.ini

@@ -0,0 +1,24 @@
+;PlatformIO Project Configuration File
+;
+;   Build options: build flags, source filter
+;   Upload options: custom upload port, speed and extra flags
+;   Library options: dependencies, extra library storages
+;   Advanced options: extra scripting
+;
+; Please visit documentation for the other options and examples
+; https://docs.platformio.org/page/projectconf.html
+
+[env:current_ranger_nextion]
+platform = atmelsam
+board = current_ranger
+framework = arduino
+upload_port = com10
+; change microcontroller
+board_build.mcu = samd21g18a
+
+; change MCU frequency
+board_build.f_cpu = 48000000L
+lib_deps=https://github.com/cmaglie/FlashStorage#master 
+        https://github.com/adafruit/Adafruit_FreeTouch#master 
+        seithan/Easy Nextion Library @ ^1.0.4
+lib_ignore=U8g2

+ 54 - 0
src/README.md

@@ -0,0 +1,54 @@
+# CurrentRanger
+Precision auto-ranging current meter (ammeter)
+
+## [Please see the full guide and latest updates posted here.](https://lowpowerlab.com/guide/currentranger/)
+
+![CurrentRanger](https://lowpowerlab.com/wp-content/uploads/2018/09/DSC_2631-768x579.jpg)
+
+## Quick Highlights
+Here are some of the features of this instrument which sets it apart:
+
+* Low noise zero-offset with 3-ranges (1mV output per nA/µA/mA)
+* Low input burden voltage, high precision & bandwidth analog outputs
+* Increased flexibility and usability with several input and output terminal options
+* Auto-ranging capable
+* Use standalone with a small OLED display or with a multimeter/oscilloscope
+* Ultra fast range switching between any ranges (even nA to mA) without any glitching/bouncing of a mechanical switch
+* Low Pass Filter mode – very useful to capture low noise  signals on oscilloscopes
+* Unidirectional mode – most used mode in measuring DC currents ranging from [0, 3.3A]
+* Bidirectional mode – split supply biasing allows AC currents measurement ranging from [-1.65A, 1.65A]
+* LiPo battery powered – long life and extended measurement range
+* Auto-power-off
+* Full digital control for power & range switching via touch pads
+* OLED display option to read output with usable precision
+* Datalogging possible via Bluetooth serial module
+* SAMD21 Cortex M0+ powered, change firmware to your needs
+* Optional buzzer for audible feedback
+
+* Current ranges output:
+  - 0-3300 nA/µA/mA (Unidirectional mode)
+  - +/- 0-1650 nA/µA/mA (Bidirectional mode)
+  - Burden voltage:
+  - 17µV/mA
+  - 10µV/µA
+  - 10µV/nA
+* Output offset voltage¹: typically <10µV, max 50µV
+* Maximum input voltage differential (see Safety): 33mV
+* Accuracy:
+  - +/-0.05% (µA, nA ranges)
+  - +/-0.1% (mA range)
+* Highest resolution (nA range):
+  - 100pA (3.5digit meter)
+  - 10pA (4.5 digit meter)
+  - 1pA (5.5 digit meter)
+* Cascaded MAX4239 amplifiers with 100x output gain
+  - Bandwidth: >300KHz (-3dB)
+
+![CurrentRanger](https://lowpowerlab.com/wp-content/uploads/2018/09/DSC_2642.jpg)
+
+Sample no-load mA range comparison to µCurrent GOLD:
+![mA compare µCurrent](https://lowpowerlab.com/wp-content/uploads/2018/09/DS1Z_QuickPrint11_2.png)
+
+Sample measurement of a [Moteino](https://lowpowerlab.com/guide/moteino/) waking up and transmitting a  RFM69 transceiver packet:
+
+![CurrentRanger](https://lowpowerlab.com/wp-content/uploads/2018/09/CurrentRanger_LPF.gif)

+ 1165 - 0
src/main.cpp

@@ -0,0 +1,1165 @@
+#define NEXTION_SUPPORT
+#define PLATFORM_IO
+// CurrentRanger(TM) stock firmware
+// https://lowpowerlab.com/CurrentRanger
+// CurrentRanger is a *high-side* precision current meter featuring:
+//   - fast autoranging
+//   - uni/bi-directional modes (ie. DC/AC measurements)
+//   - ultra low burden voltage
+//   - 1mV per nA/uA/mA measurements with DMM/scope
+//   - OLED standalone readings
+//   - serial data logging option via 3.3v/RX/TX header or USB (must use isolation, read guide!)
+//   - full digital control for power/switching
+//   - LiPo powered with auto power-off feature (0.6uA quiescent current)
+// *************************************************************************************************************
+#ifndef CURRENT_RANGER_NEXTION
+  //#error CurrentRanger target board required, see guide on how to add it to the IDE: lowpowerlab.com/currentranger
+#endif
+//***********************************************************************************************************
+#include <FlashStorage.h>          //for emulated EEPROM - https://github.com/cmaglie/FlashStorage
+#include <Adafruit_FreeTouch.h>    //https://github.com/adafruit/Adafruit_FreeTouch
+#ifdef OLED_SUPPORT
+#include <U8g2lib.h>               //https://github.com/olikraus/u8g2/wiki/u8g2reference fonts:https://github.com/olikraus/u8g2/wiki/fntlistall
+#endif
+//#include <ATSAMD21_ADC.h>
+#ifdef NEXTION_SUPPORT
+#include "EasyNextionLibrary.h"
+#endif
+// CurrentRanger Firmware Version
+#define FW_VERSION "1.1.0"
+
+//***********************************************************************************************************
+#define BIAS_LED       11
+#define LPFPIN         4
+#define LPFLED         LED_BUILTIN
+#define AUTOFF         PIN_AUTO_OFF
+//***********************************************************************************************************
+#define MA_PIN PIN_PA13  //#define MA  38
+#define UA_PIN PIN_PA14  //#define UA  2
+#define NA_PIN PIN_PA15  //#define NA  5
+#define MA_GPIO_PIN PIN_PB11
+#define UA_GPIO_PIN PIN_PA12
+#define NA_GPIO_PIN PIN_PB10
+#define PINOP(pin, OP) (PORT->Group[(pin) / 32].OP.reg = (1 << ((pin) % 32)))
+#define PIN_OFF(THE_PIN) PINOP(THE_PIN, OUTCLR)
+#define PIN_ON(THE_PIN) PINOP(THE_PIN, OUTSET)
+#define PIN_TGL(THE_PIN) PINOP(THE_PIN, OUTTGL)
+//***********************************************************************************************************
+#define SENSE_OUTPUT           A3
+#define SENSE_GNDISO           A2
+#define SENSE_VIN              A5
+#define ADC_PRESCALER          ADC_CTRLB_PRESCALER_DIV16
+//#define ADC_AVGCTRL            ADC_AVGCTRL_SAMPLENUM_128 | ADC_AVGCTRL_ADJRES(0x4ul)
+                               //ADC_AVGCTRL_SAMPLENUM_1 | ADC_AVGCTRL_ADJRES(0x00ul);  // take 1 sample, adjusting result by 0
+                               //ADC_AVGCTRL_SAMPLENUM_16 | ADC_AVGCTRL_ADJRES(0x4ul); //take 16 samples adjust by 4
+                               //ADC_AVGCTRL_SAMPLENUM_256 | ADC_AVGCTRL_ADJRES(0x4ul); //take 256 samples adjust by 4
+                               //ADC_AVGCTRL_SAMPLENUM_512 | ADC_AVGCTRL_ADJRES(0x4ul); //take 512 samples adjust by 4
+                               //ADC_AVGCTRL_SAMPLENUM_1024 | ADC_AVGCTRL_ADJRES(0x4ul); //take 1024 samples adjust by 4
+#define ADC_SAMPCTRL           0b111 //sample timing [fast 0..0b111 slow]
+#define ADCFULLRANGE           4095.0
+#define VBAT_REFRESH_INTERVAL  5000 //ms
+#define LOBAT_THRESHOLD        3.40 //volts
+#define DAC_GND_ISO_OFFSET     10
+#define DAC_HALF_SUPPLY_OFFSET 512
+#define OUTPUT_CALIB_FACTOR    1.00  //calibrate final VOUT value
+#define ADC_OVERLOAD           3900  //assuming GNDISO DAC output is very close to 0, this is max value less ground offset (varies from unit to unit, 3900 is a safe value)
+//***********************************************************************************************************
+//#define ADC_CALIBRATE_FORCED
+#define ADC_CALIBRATE_FORCED_OFFSET 0
+#define ADC_CALIBRATE_FORCED_GAIN   2048
+#define LDO_DEFAULT                 3.300 //volts, change to actual LDO output (measure GND-3V on OLED header)
+//***********************************************************************************************************
+#define BUZZER    1   // BUZZER pin
+#define NOTE_C5   523
+#define NOTE_D5   587
+#define NOTE_E5   659
+#define NOTE_F5   698
+#define NOTE_G5   784
+#define NOTE_B5   988
+#define NOTE_C6   1047
+#define TONE_BEEP 4200
+//***********************************************************************************************************
+#define MODE_MANUAL                 0
+#define MODE_AUTORANGE              1
+#define STARTUP_MODE                MODE_MANUAL //or: MODE_AUTORANGE
+#define SWITCHDELAY_UP              8 //ms
+#define SWITCHDELAY_DOWN            8 //ms
+#define RANGE_SWITCH_THRESHOLD_HIGH ADC_OVERLOAD //ADC's 12bit value
+#define RANGE_SWITCH_THRESHOLD_LOW  6 //6*0.4xA ~ 2.4xA - range down below this value
+
+//***********************************************************************************************************
+#ifdef OLED_SUPPORT
+#include <Wire.h>                   //i2c scanner: https://playground.arduino.cc/Main/I2cScanner
+#define OLED_BAUD                   1600000 //fast i2c clock
+#define OLED_ADDRESS                0x3C    //i2c address on most small OLEDs
+#define OLED_REFRESH_INTERVAL       180     //ms
+U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
+#endif
+//***********************************************************************************************************
+#define TOUCH_N        8
+#define TOUCH_U        9
+#define TOUCH_M        A4
+Adafruit_FreeTouch qt[3] = {
+  Adafruit_FreeTouch( TOUCH_N, OVERSAMPLE_1, RESISTOR_50K, FREQ_MODE_NONE ),
+  Adafruit_FreeTouch( TOUCH_U, OVERSAMPLE_1, RESISTOR_50K, FREQ_MODE_NONE ),
+  Adafruit_FreeTouch( TOUCH_M, OVERSAMPLE_1, RESISTOR_50K, FREQ_MODE_NONE ),
+};
+#define TOUCH_HIGH_THRESHOLD  400 //range is 0..1023
+#define TOUCH_SAMPLE_INTERVAL 50 //ms
+//***********************************************************************************************************
+#define SERIAL_UART_BAUD        230400      //Serial baud for HC-06/bluetooth output
+#define BT_SERIAL_EN
+//#define LOGGER_FORMAT_EXPONENT  //ex: 123E-3 = 123mA
+//#define LOGGER_FORMAT_NANOS     //ex: 123456 = 123456nA = 123.456uA
+//#define LOGGER_FORMAT_ADC       //raw ADC output - note: automatic ADC_REF change
+#define BT_REFRESH_INTERVAL     200 //ms
+//***********************************************************************************************************
+#define AUTOOFF_BUZZ_DELAY     500 //ms
+#define AUTOOFF_DEFAULT        600 //seconds, turn unit off after 10min of inactivity
+#define AUTOOFF_DISABLED       0xFFFF  // do not turn off
+#define AUTOOFF_SMART          0xFFFE  // turn off only if there is no BT or USB data logging
+//***********************************************************************************************************
+#define LOGGING_FORMAT_EXPONENT 0 //ex: 123E-3 = 123mA
+#define LOGGING_FORMAT_NANOS    1 //ex: 1234 = 1.234uA = 0.001234mA
+#define LOGGING_FORMAT_MICROS   2 //ex: 1234 = 1.234mA = 1234000nA
+#define LOGGING_FORMAT_MILLIS   3 //ex: 1234 = 1.234A = 1234000uA = 1234000000nA
+#define LOGGING_FORMAT_ADC      4 //raw output for each range (0..4095)
+//***********************************************************************************************************
+#define ADC_SAMPLING_SPEED_AVG   0
+#define ADC_SAMPLING_SPEED_FAST  1
+#define ADC_SAMPLING_SPEED_SLOW  2
+//***********************************************************************************************************
+#ifdef NEXTION_SUPPORT
+#define NEX_LIGHT_GREY 50712
+#define NEX_BLUE 1055
+#define NEXTION_REFRESH_INTERVAL       300     //ms
+EasyNex myNex(SerialNextion);
+uint8_t nex_page=0;
+#endif
+int offsetCorrectionValue = 0;
+uint16_t gainCorrectionValue = 0;
+float ldoValue = 0, ldoOptimized=0;
+uint16_t autooff_interval = 0;
+uint8_t USB_LOGGING_ENABLED = false;
+uint8_t TOUCH_DEBUG_ENABLED = false;
+uint8_t GPIO_HEADER_RANGING = false;
+uint8_t BT_LOGGING_ENABLED = true;
+uint8_t LOGGING_FORMAT = LOGGING_FORMAT_EXPONENT;
+uint16_t ADC_SAMPLING_SPEED = ADC_SAMPLING_SPEED_AVG;
+uint32_t ADC_AVGCTRL;
+uint8_t calibrationPerformed=false;
+uint8_t analog_ref_half=true;
+char rangeUnit = 'm';
+uint8_t OLED_found=false;
+uint8_t autoffWarning=false;
+uint8_t autoffBuzz=0;
+#ifdef BT_SERIAL_EN
+  uint8_t BT_found=false;
+#endif
+FlashStorage(eeprom_ADCoffset, int);
+FlashStorage(eeprom_ADCgain, uint16_t);
+FlashStorage(eeprom_LDO, float);
+FlashStorage(eeprom_AUTOFF, uint16_t);
+FlashStorage(eeprom_LOGGINGFORMAT, uint8_t);
+FlashStorage(eeprom_ADCSAMPLINGSPEED, uint8_t);
+//***********************************************************************************************************
+#ifdef PLATFORM_IO
+void WDTclear();
+void analogReadCorrection(int offset, uint16_t gain);
+void analogReferenceHalf(uint8_t half);
+void toggleAutoranging();
+void Beep(byte theDelay, boolean twoSounds);
+void toggleOffset();
+void rangeMA();
+void rangeUA();
+void rangeNA();
+void toggleLPF();
+void rangeBeep(uint16_t switch_delay);
+void handleTouchPads();
+void handleAutoOff();
+int adcRead(byte ADCpin);
+void handleVbatRead();
+void readVOUT();
+void printSerialMenu();
+void refreshADCSamplingSpeed();
+void rebootIntoBootloader();
+void saveLDO(float newLdoValue);
+void analogReadCorrectionForced(int offset, uint16_t gain);
+void WDTset();
+void ldoOptimizeRefresh();
+float vbat=0, VOUT=0;
+#endif
+//***********************************************************************************************************
+void setup() {
+/*
+  //some buzz
+  tone(BUZZER, NOTE_C5); delay(100);
+  tone(BUZZER, NOTE_E5); delay(100);
+  tone(BUZZER, NOTE_G5); delay(100);
+  tone(BUZZER, NOTE_C6); delay(200);
+  noTone(BUZZER);        delay(50);
+  tone(BUZZER, NOTE_G5); delay(100);
+  tone(BUZZER, NOTE_C6); delay(400);
+  noTone(BUZZER);
+*/
+#ifdef NEXTION_SUPPORT
+  myNex.begin(115200); 
+#endif 
+  delay(50); //Wire apparently needs this
+#ifdef OLED_SUPPORT
+  Wire.begin();
+  Wire.beginTransmission(OLED_ADDRESS);
+  byte error = Wire.endTransmission();
+  if (error == 0)
+  {
+    Serial.print("OLED FOUND at 0x"); Serial.println(OLED_ADDRESS);
+    u8g2.begin();
+    //u8g2.setDisplayRotation(U8G2_R2); //if required (inside/custom mount?)
+    u8g2.setBusClock(OLED_BAUD);
+    OLED_found = true;
+  }
+  else Serial.println("NO OLED found...");
+#endif
+  pinMode(A0, OUTPUT); //DAC/GNDISO
+  //DAC->CTRLB.bit.EOEN = 0x00; //enable high drive strength - already done in wiring.c
+
+  pinMode(SENSE_OUTPUT, INPUT);
+  pinMode(SENSE_GNDISO, INPUT); //GND-ISO
+  pinMode(SENSE_VIN, INPUT); //VIN > 1MEG > SENSE_VIN > 2MEG > GND
+  pinMode(AUTOFF, INPUT_PULLUP);
+  pinMode(BIAS_LED, OUTPUT);
+  pinMode(LPFLED, OUTPUT); //STATUS/LPF-LED
+  pinMode(LPFPIN, OUTPUT); //LPF control pin
+  pinMode(BUZZER, OUTPUT);
+  PINOP(MA_PIN, DIRSET);
+  PINOP(UA_PIN, DIRSET);
+  PINOP(NA_PIN, DIRSET);
+  PINOP(MA_GPIO_PIN, DIRSET);
+  PINOP(UA_GPIO_PIN, DIRSET);
+  PINOP(NA_GPIO_PIN, DIRSET);
+
+  qt[0].begin(); qt[1].begin(); qt[2].begin(); //touch pads
+  analogWriteResolution(10);  //DAC resolution
+  analogReferenceHalf(true);
+
+  //DAC->CTRLA.bit.RUNSTDBY = 0x01;delay(1);
+  //DAC->CTRLB.bit.REFSEL=0;//pick internal reference, skip SYNCDAC (done by analogWrite)
+  analogWrite(A0, DAC_GND_ISO_OFFSET);  // Initialize Dac to OFFSET
+
+  autooff_interval = eeprom_AUTOFF.read();
+  if (autooff_interval==0) {
+    autooff_interval = AUTOOFF_DEFAULT;
+    eeprom_AUTOFF.write(autooff_interval);
+  }
+
+  LOGGING_FORMAT = eeprom_LOGGINGFORMAT.read();
+
+  offsetCorrectionValue = eeprom_ADCoffset.read();
+  gainCorrectionValue = eeprom_ADCgain.read();
+  ldoValue = eeprom_LDO.read();
+
+  if(ldoValue==0)
+    saveLDO(LDO_DEFAULT);
+  else ldoOptimizeRefresh();
+
+  ADC_SAMPLING_SPEED = eeprom_ADCSAMPLINGSPEED.read();
+  refreshADCSamplingSpeed(); //load correct value into ADC_AVGCTRL
+
+  if (gainCorrectionValue!=0) //check if anything saved in EEPROM (gain changed via SerialUSB +/-)
+    analogReadCorrectionForced(offsetCorrectionValue, gainCorrectionValue);
+  else {
+    analogReadCorrectionForced(ADC_CALIBRATE_FORCED_OFFSET, ADC_CALIBRATE_FORCED_GAIN);
+    eeprom_ADCoffset.write(offsetCorrectionValue);
+    eeprom_ADCgain.write(gainCorrectionValue);
+    //(offset, gain) - gain is 12 bit number (1 bit integer + 11bit fractional, see DS p895)
+    //               - offset is 12bit 2s complement format (DS p896)
+  }
+#ifdef OLED_SUPPORT
+  if (OLED_found)
+  {
+    u8g2.clearBuffer();
+    u8g2.setFont(u8g2_font_8x13B_tf);
+    u8g2.setCursor(15,10); u8g2.print("CurrentRanger");  
+    u8g2.setFont(u8g2_font_6x12_tf);
+    u8g2.setCursor(0,20); u8g2.print("Offset:");
+    u8g2.setCursor(64,20); u8g2.print(offsetCorrectionValue);
+    u8g2.setCursor(0,32); u8g2.print("Gain  :");
+    u8g2.setCursor(64,32); u8g2.print(gainCorrectionValue);
+    u8g2.setCursor(0,44); u8g2.print("LDO   :");
+    u8g2.setCursor(64,44); u8g2.print(ldoValue,3);
+    u8g2.setCursor(0, 56); u8g2.print("Firmware:");
+    u8g2.setCursor(64,56); u8g2.print(FW_VERSION);
+    u8g2.sendBuffer();
+    delay(2000);
+  }
+#endif
+#ifdef NEXTION_SUPPORT
+  if (analog_ref_half)
+  {
+    analogReferenceHalf(false);
+    vbat=adcRead(SENSE_VIN);
+    analogReferenceHalf(true);
+  }
+    else vbat=adcRead(SENSE_VIN);
+  vbat=((vbat/ADCFULLRANGE) * ldoValue) * 1.5; //1.5 given by vbat->A5 resistor ratio (1 / (2M * 1/(1M+2M)))
+  myNex.writeStr("page page4","cmd");
+  delay(2000);
+  nex_page=2;
+  myNex.writeStr("page page2","cmd");
+  myNex.writeStr("offset.txt",(String)offsetCorrectionValue);
+  myNex.writeStr("gain.txt",(String)gainCorrectionValue);
+  myNex.writeStr("ldo.txt",String(ldoValue,3));
+  myNex.writeStr("fw.txt",(String)FW_VERSION);
+  myNex.writeStr("bat.txt",(String)vbat);
+  delay(2000);
+  myNex.writeStr("page page0","cmd");
+  myNex.writeNum("b2.bco",NEX_BLUE);
+  nex_page=0;
+#endif
+#ifdef BT_SERIAL_EN
+  //BT check
+  Serial.print("Bluetooth AT check @");Serial.print(SERIAL_UART_BAUD);Serial.print("baud...");
+  delay(600);
+  SerialBT.begin(SERIAL_UART_BAUD);
+  SerialBT.print("AT"); //assuming HC-06, no line ending required
+  uint32_t timer=millis();
+  while(millis()-timer<1000) //about 1s to respond
+  {
+    if (SerialBT.available()==2 && SerialBT.read()=='O' && SerialBT.read()=='K')
+    {
+      BT_found=true;
+      break;
+    }
+  }
+
+  Serial.print(BT_found?"OK!":"No HC-06 response.\r\nChecking for BT v3.0...");
+
+  if (!BT_found)
+  {
+    SerialBT.print("\r\n"); //assuming HC-06 version 3.0 that requires line ending
+    uint32_t timer=millis();
+    while(millis()-timer<50) //about 50ms to respond
+    {
+      if (SerialBT.available()==4 && SerialBT.read()=='O' && SerialBT.read()=='K' && SerialBT.read()=='\r' && SerialBT.read() == '\n')
+      {
+        BT_found=true;
+        break;
+      }
+    }
+  
+    Serial.println(BT_found?"OK!":"No response.");
+  }
+
+  BT_LOGGING_ENABLED = BT_found;
+#endif
+
+  printSerialMenu();
+  WDTset();
+  if (STARTUP_MODE == MODE_AUTORANGE) toggleAutoranging();
+}
+
+uint32_t oledInterval=0, lpfInterval=0, offsetInterval=0, autorangeInterval=0, btInterval=0,
+         autoOffBuzzInterval=0, touchSampleInterval=0, lastKeepAlive=0, vbatInterval = VBAT_REFRESH_INTERVAL;
+#ifdef NEXTION_SUPPORT
+uint32_t nextionInterval=0;
+uint32_t nextionIntervalwrite=0;
+#endif
+byte LPF=0, BIAS=0, AUTORANGE=0;
+#ifndef PLATFORMIO
+float vbat=0, VOUT=0;
+#endif
+float read1=0,read2=0,readDiff=0;
+bool rangeSwitched=false;
+#define RANGE_MA rangeUnit=='m'
+#define RANGE_UA rangeUnit=='u'
+#define RANGE_NA rangeUnit=='n'
+
+void loop() {
+  //uint32_t timestamp=micros();
+#ifdef NEXTION_SUPPORT
+  if ( millis() - nextionInterval > NEXTION_REFRESH_INTERVAL) //refresh rate (ms)
+  {
+    myNex.NextionListen();
+    nextionInterval = millis(); 
+  }
+#endif
+  while (Serial.available()>0) {
+    char inByte = Serial.read();
+
+    // tickle the AUTOOFF function so it doesn't shut down when there are commands coming over serial
+    lastKeepAlive = millis();
+
+    switch (inByte) {
+      case '*':
+        eeprom_ADCgain.write(++gainCorrectionValue);
+        analogReadCorrection(offsetCorrectionValue,gainCorrectionValue);
+        Serial.print("new gainCorrectionValue = ");
+        Serial.println(gainCorrectionValue);
+        break;
+      case '/':
+        eeprom_ADCgain.write(--gainCorrectionValue);
+        analogReadCorrection(offsetCorrectionValue,gainCorrectionValue);
+        Serial.print("new gainCorrectionValue = ");
+        Serial.println(gainCorrectionValue);
+        break;
+      case '+':
+        eeprom_ADCoffset.write(++offsetCorrectionValue);
+        analogReadCorrection(offsetCorrectionValue,gainCorrectionValue);
+        Serial.print("new offsetCorrectionValue = ");
+        Serial.println(offsetCorrectionValue);
+        break;
+      case '-':
+        eeprom_ADCoffset.write(--offsetCorrectionValue);
+        analogReadCorrection(offsetCorrectionValue,gainCorrectionValue);
+        Serial.print("new offsetCorrectionValue = ");
+        Serial.println(offsetCorrectionValue);
+        break;
+      case '<':
+        saveLDO(ldoValue-0.001);
+        Serial.print("new LDO_Value = ");
+        Serial.println(ldoValue, 3);
+        break;
+      case '>':
+        saveLDO(ldoValue+0.001);
+        Serial.print("new LDO_Value = ");
+        Serial.println(ldoValue, 3);
+        break;
+      case 'r': //reboot to bootloader
+        Serial.print("\nRebooting to bootloader.");
+        for (byte i=0;i++<30;) { delay(10); Serial.print('.'); }
+        rebootIntoBootloader();
+        break;
+      case 'u': //toggle USB logging
+        USB_LOGGING_ENABLED =! USB_LOGGING_ENABLED;
+        Serial.println(USB_LOGGING_ENABLED ? "USB_LOGGING_ENABLED" : "USB_LOGGING_DISABLED");
+#ifdef NEXTION_SUPPORT
+        if (USB_LOGGING_ENABLED)  myNex.writeNum("usb.pic",10);
+        else myNex.writeNum("usb.pic",16);
+#endif
+        break;
+      case 't': //toggle touchpad serial output debug info
+        TOUCH_DEBUG_ENABLED =! TOUCH_DEBUG_ENABLED;
+        Serial.println(TOUCH_DEBUG_ENABLED ? "TOUCH_DEBUG_ENABLED" : "TOUCH_DEBUG_DISABLED");
+        break;
+      case 'g': //toggle GPIOs indicating ranging
+        GPIO_HEADER_RANGING =! GPIO_HEADER_RANGING;
+        if (GPIO_HEADER_RANGING) {
+          if (rangeUnit=='m') PIN_ON(MA_GPIO_PIN); else PIN_OFF(MA_GPIO_PIN);
+          if (rangeUnit=='u') PIN_ON(UA_GPIO_PIN); else PIN_OFF(UA_GPIO_PIN);
+          if (rangeUnit=='n') PIN_ON(NA_GPIO_PIN); else PIN_OFF(NA_GPIO_PIN);
+        }
+        Serial.println(GPIO_HEADER_RANGING ? "GPIO_HEADER_RANGING_ENABLED" : "GPIO_HEADER_RANGING_DISABLED");
+        break;
+      case 'b': //toggle BT/serial logging
+#ifdef BT_SERIAL_EN      
+        if (BT_found) {
+          BT_LOGGING_ENABLED =! BT_LOGGING_ENABLED;
+          Serial.println(BT_LOGGING_ENABLED ? "BT_LOGGING_ENABLED" : "BT_LOGGING_DISABLED");
+        } else {
+          BT_LOGGING_ENABLED = false;
+          Serial.println("BT Module not found: cannot enable logging");
+        }
+#else
+        Serial.println("BT_LOGGING Not Enabled");
+#endif
+        break;
+      case 'f': //cycle through output logging formats
+        if (++LOGGING_FORMAT>LOGGING_FORMAT_ADC) LOGGING_FORMAT=LOGGING_FORMAT_EXPONENT;
+        eeprom_LOGGINGFORMAT.write(LOGGING_FORMAT);
+        if (LOGGING_FORMAT==LOGGING_FORMAT_EXPONENT) Serial.println("LOGGING_FORMAT_EXPONENT"); else
+        if (LOGGING_FORMAT==LOGGING_FORMAT_NANOS) Serial.println("LOGGING_FORMAT_NANOS"); else
+        if (LOGGING_FORMAT==LOGGING_FORMAT_MICROS) Serial.println("LOGGING_FORMAT_MICROS"); else
+        if (LOGGING_FORMAT==LOGGING_FORMAT_MILLIS) Serial.println("LOGGING_FORMAT_MILLIS"); else
+        if (LOGGING_FORMAT==LOGGING_FORMAT_ADC) Serial.println("LOGGING_FORMAT_ADC");
+        break;
+      case 's':
+        if (++ADC_SAMPLING_SPEED>ADC_SAMPLING_SPEED_SLOW) ADC_SAMPLING_SPEED=ADC_SAMPLING_SPEED_AVG;
+        if (ADC_SAMPLING_SPEED==ADC_SAMPLING_SPEED_AVG) Serial.println("ADC_SAMPLING_SPEED_AVG"); else
+        if (ADC_SAMPLING_SPEED==ADC_SAMPLING_SPEED_FAST) Serial.println("ADC_SAMPLING_SPEED_FAST"); else
+        if (ADC_SAMPLING_SPEED==ADC_SAMPLING_SPEED_SLOW) Serial.println("ADC_SAMPLING_SPEED_SLOW");
+        eeprom_ADCSAMPLINGSPEED.write(ADC_SAMPLING_SPEED);
+        refreshADCSamplingSpeed();
+        break;
+      case 'a': //toggle autoOff function
+        if (autooff_interval == AUTOOFF_DEFAULT)
+        {
+          Serial.println("AUTOOFF_DISABLED");
+          autooff_interval = AUTOOFF_DISABLED;
+        }
+        else if (autooff_interval == AUTOOFF_SMART) {
+          Serial.println("AUTOOFF_DEFAULT");
+          autooff_interval = AUTOOFF_DEFAULT;
+          lastKeepAlive = millis();          
+        } else {
+          // turn off only when there is no serial or BT data logging
+          Serial.println("AUTOOFF_SMART");
+          autooff_interval = AUTOOFF_SMART;
+        }
+        eeprom_AUTOFF.write(autooff_interval);
+        break;
+      case '?':
+        printSerialMenu();
+        break;
+      default: break;
+    }
+  }
+
+  if (AUTORANGE) {
+    readVOUT();
+    //assumes we only auto-range in DC mode (no bias)
+    if (readDiff <= RANGE_SWITCH_THRESHOLD_LOW)
+    {
+      if      (RANGE_MA) { rangeUA(); rangeSwitched=true; rangeBeep(SWITCHDELAY_DOWN); }
+      else if (RANGE_UA) { rangeNA(); rangeSwitched=true; rangeBeep(SWITCHDELAY_DOWN); }
+    }
+    else if (readDiff >= RANGE_SWITCH_THRESHOLD_HIGH)
+    {
+      if      (RANGE_NA) { rangeUA(); rangeSwitched=true; rangeBeep(SWITCHDELAY_UP); }
+      else if (RANGE_UA) { rangeMA(); rangeSwitched=true; rangeBeep(SWITCHDELAY_UP); }
+    }
+    if (rangeSwitched) {
+      lastKeepAlive=millis();
+      rangeSwitched=false;
+      return; //!!!
+    }
+  }
+
+  uint8_t VOUTCalculated=false;
+  if (USB_LOGGING_ENABLED)
+  {//TODO: refactor
+    if (!AUTORANGE) readVOUT();
+    VOUT = readDiff*ldoOptimized*(BIAS?1:OUTPUT_CALIB_FACTOR);
+    VOUTCalculated=true;
+    if(LOGGING_FORMAT == LOGGING_FORMAT_EXPONENT) { Serial.print(VOUT); Serial.print("e"); Serial.println(RANGE_NA ? -9 : RANGE_UA ? -6 : -3); } else
+    if(LOGGING_FORMAT == LOGGING_FORMAT_NANOS) Serial.println(VOUT * (RANGE_NA ? 1 : RANGE_UA ? 1000 : 1000000)); else
+    if(LOGGING_FORMAT == LOGGING_FORMAT_MICROS) Serial.println(VOUT * (RANGE_NA ? 0.001 : RANGE_UA ? 1 : 1000)); else
+    if(LOGGING_FORMAT == LOGGING_FORMAT_MILLIS) Serial.println(VOUT * (RANGE_NA ? 0.000001 : RANGE_UA ? 0.001 : 1)); else
+    if(LOGGING_FORMAT == LOGGING_FORMAT_ADC) Serial.println(readDiff,0);
+  }
+
+#ifdef BT_SERIAL_EN
+  if (BT_LOGGING_ENABLED) {
+#ifdef OLED_SUPPORT
+    if (OLED_found) {
+      u8g2.setFont(u8g2_font_siji_t_6x10); //https://github.com/olikraus/u8g2/wiki/fntgrpsiji
+      u8g2.drawGlyph(104, 10, 0xE00B); //BT icon
+    }
+#endif
+    btInterval = millis();
+    if (!AUTORANGE) readVOUT();
+    if (!VOUTCalculated) {
+      VOUT = readDiff*ldoOptimized*(BIAS?1:OUTPUT_CALIB_FACTOR);
+      VOUTCalculated=true;
+    }
+    if(LOGGING_FORMAT == LOGGING_FORMAT_EXPONENT) { SerialBT.print(VOUT); SerialBT.print("e"); SerialBT.println(RANGE_NA ? -9 : RANGE_UA ? -6 : -3); } else
+    if(LOGGING_FORMAT == LOGGING_FORMAT_NANOS) SerialBT.println(VOUT * (RANGE_NA ? 1 : RANGE_UA ? 1000 : 1000000)); else
+    if(LOGGING_FORMAT == LOGGING_FORMAT_MICROS) SerialBT.println(VOUT * (RANGE_NA ? 0.001 : RANGE_UA ? 1 : 1000)); else
+    if(LOGGING_FORMAT == LOGGING_FORMAT_MILLIS) SerialBT.println(VOUT * (RANGE_NA ? 0.000001 : RANGE_UA ? 0.001 : 1)); else
+    if(LOGGING_FORMAT == LOGGING_FORMAT_ADC) SerialBT.println(readDiff,0);
+  }
+#endif
+
+  //OLED refresh: ~22ms (SCK:1.6mhz, ADC:64samples/DIV16/b111)
+#ifdef OLED_SUPPORT
+  if (OLED_found && millis() - oledInterval > OLED_REFRESH_INTERVAL) //refresh rate (ms)
+  {
+    oledInterval = millis();
+    if (!AUTORANGE) readVOUT();
+    if (!VOUTCalculated) VOUT = readDiff*ldoOptimized*(BIAS?1:OUTPUT_CALIB_FACTOR);
+    u8g2.clearBuffer(); //175us
+    u8g2.setFont(u8g2_font_6x12_tf); //7us
+
+    handleVbatRead();
+
+    u8g2.setFont(u8g2_font_siji_t_6x10);
+    if (vbat>4.3)
+      u8g2.drawGlyph(115, 10, 0xE23A); //charging!
+    else if(vbat>4.1)
+      u8g2.drawGlyph(115, 10, 0xE24B); //100%
+    else if(vbat>3.95)
+      u8g2.drawGlyph(115, 10, 0xE249); //80%
+    else if(vbat>3.85)
+      u8g2.drawGlyph(115, 10, 0xE247); //60%
+    else if(vbat>3.75)
+      u8g2.drawGlyph(115, 10, 0xE245); //40%
+    else if(vbat>3.65)
+      u8g2.drawGlyph(115, 10, 0xE244); //20%
+    else if(vbat>LOBAT_THRESHOLD)
+      u8g2.drawGlyph(115, 10, 0xE243); //5%!
+    else u8g2.drawGlyph(115, 10, 0xE242); //u8g2.drawStr(88,12,"LoBat!");
+
+    u8g2.setFont(u8g2_font_6x12_tf); //7us
+    if (AUTORANGE) {
+      u8g2.drawStr(0,12, analog_ref_half ? "AUTO\xb7\xbd" : "AUTO");
+      u8g2.setCursor(42,12); u8g2.print(readDiff,0);
+    } else {
+      if (analog_ref_half) u8g2.drawStr(0,12,"\xbd");
+      u8g2.setCursor(12,12); u8g2.print(readDiff,0);
+    }
+
+    if (autoffBuzz) u8g2.drawStr(5,26,"* AUTO OFF! *"); //autoffWarning
+    u8g2.setFont(u8g2_font_helvB24_te);
+    u8g2.setCursor(RANGE_MA ? 102 : 106, RANGE_UA ? 55:60); u8g2.print(RANGE_UA ? char('µ') : rangeUnit);
+    u8g2.setFont(u8g2_font_logisoso32_tr);
+    u8g2.setCursor(0,64); u8g2.print((BIAS&&abs(VOUT)>=0.4||!BIAS&&VOUT>=0.4)?VOUT:0, abs(VOUT)>=1000?0:1);
+    if (!BIAS && readDiff>ADC_OVERLOAD || BIAS && abs(readDiff)>ADC_OVERLOAD/2)
+    {
+      u8g2.setFont(u8g2_font_9x15B_tf);
+      u8g2.drawStr(0,28, "OVERLOAD!");
+    }
+    u8g2.sendBuffer();
+  }
+#endif
+#ifdef NEXTION_SUPPORT
+  if ( millis() - nextionIntervalwrite > NEXTION_REFRESH_INTERVAL) //refresh rate (ms)
+  {
+    nextionIntervalwrite =millis() ;   
+    handleVbatRead();
+    if (!AUTORANGE) readVOUT();
+    if (!VOUTCalculated) VOUT = readDiff*ldoOptimized*(BIAS?1:OUTPUT_CALIB_FACTOR);
+    if (nex_page==2) myNex.writeStr("bat.txt",(String)vbat);
+    if (nex_page==0){
+        if (vbat>4.3)
+        myNex.writeNum("p0.pic",6); //charging!
+        else if(vbat>4.1)
+        myNex.writeNum("p0.pic",5); ///100%
+        else if(vbat>3.95)
+        myNex.writeNum("p0.pic",4);  //80%
+        else if(vbat>3.85)
+        myNex.writeNum("p0.pic",3);  //60%
+        else if(vbat>3.75)
+        myNex.writeNum("p0.pic",2); //40%
+        else if(vbat>3.65)
+        myNex.writeNum("p0.pic",1); //20%
+        else 
+        myNex.writeNum("p0.pic",0);  //u8g2.drawStr(88,12,"LoBat!");
+    }
+    char buff[9];
+    float currentout=(((BIAS&&abs(VOUT)>=0.4)||(!BIAS&&VOUT>=0.4))?VOUT:0);
+    snprintf (buff, sizeof(buff), "%f", ((BIAS&&abs(VOUT)>=0.4)||(!BIAS&&VOUT>=0.4))?VOUT:0, (abs(VOUT)>=1000?0:1));
+    if ((!BIAS && readDiff>ADC_OVERLOAD) || (BIAS && abs(readDiff)>ADC_OVERLOAD/2))
+    {
+        if (nex_page == 0 || nex_page==1) 
+        myNex.writeStr("current.txt","OVERLOAD!");
+    }else{
+        if (nex_page == 0 || nex_page==1) 
+        myNex.writeStr("current.txt",String(buff)+rangeUnit+'A');
+        if (nex_page==1){
+        int Value = (uint8_t) map(currentout,0,3300,0,255);  //Read the pot value ann map it to 0.255 (max value of waveform=255)
+        String Tosend = "add ";                                       //We send the string "add "
+        Tosend += 1;                                                  //send the id of the block you want to add the value to
+        Tosend += ",";  
+        Tosend += 0;                                                  //Channel of taht id, in this case channel 0 of the waveform
+        Tosend += ",";
+        Tosend += Value ;                                              //Send the value and 3 full bytes
+        myNex.writeStr(Tosend,"cmd");
+        }
+    }  
+  }
+#endif
+  WDTclear();
+  handleTouchPads(); //~112uS
+  handleAutoOff();
+  //Serial.println(micros()-timestamp);
+} //loop()
+
+void handleVbatRead() {
+  //limit how often we read the battery since it's not expected to change a lot
+  if (millis() - vbatInterval < VBAT_REFRESH_INTERVAL) return;
+  else vbatInterval = millis();
+  uint8_t half = analog_ref_half;
+  if (half) analogReferenceHalf(false);
+  vbat=adcRead(SENSE_VIN);
+  if (half) analogReferenceHalf(true);
+  vbat=((vbat/ADCFULLRANGE) * ldoValue) * 1.5; //1.5 given by vbat->A5 resistor ratio (1 / (2M * 1/(1M+2M))) 
+
+/*
+  syncADC();
+  ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[SENSE_VIN].ulADCChannelNumber;
+  ADC->INPUTCTRL.bit.MUXNEG = 0x19;//ioGND
+  adcRead(); //discard first reading
+  vbat = adcRead();
+  syncADC();
+  ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[SENSE_OUTPUT].ulADCChannelNumber;
+  ADC->INPUTCTRL.bit.MUXNEG = g_APinDescription[SENSE_GNDISO].ulADCChannelNumber;
+  syncADC();
+*/
+}
+
+uint16_t valM=0, valU=0, valN=0;
+void handleTouchPads() {
+  if (millis() - touchSampleInterval < TOUCH_SAMPLE_INTERVAL) return;
+
+  if (TOUCH_DEBUG_ENABLED) {
+    Serial.print(qt[2].measure());Serial.print('\t');
+    Serial.print(qt[1].measure());Serial.print('\t');
+    Serial.println(qt[0].measure());
+  }
+
+  bool MA_PRESSED = qt[2].measure()>TOUCH_HIGH_THRESHOLD;
+  bool UA_PRESSED = qt[1].measure()>TOUCH_HIGH_THRESHOLD;
+  bool NA_PRESSED = qt[0].measure()>TOUCH_HIGH_THRESHOLD;
+
+  touchSampleInterval = millis();
+  if (MA_PRESSED || UA_PRESSED || NA_PRESSED) lastKeepAlive=millis();
+
+  //range switching
+  if (!AUTORANGE) {
+    if (MA_PRESSED && !UA_PRESSED && !NA_PRESSED && rangeUnit!='m') { rangeMA(); rangeBeep(20); }
+    if (UA_PRESSED && !MA_PRESSED && !NA_PRESSED && rangeUnit!='u') { rangeUA(); rangeBeep(20); }
+    if (NA_PRESSED && !UA_PRESSED && !MA_PRESSED && rangeUnit!='n') { rangeNA(); rangeBeep(20); }
+  }
+
+  //LPF activation --- [NA+UA]
+  if (UA_PRESSED && NA_PRESSED && !MA_PRESSED && millis()-lpfInterval>1000) { toggleLPF(); Beep(3, false); }
+
+  //offset toggling (GNDISO to half supply) --- [MA+UA]
+  if (MA_PRESSED && UA_PRESSED && !NA_PRESSED && millis()-offsetInterval>1000) { toggleOffset(); Beep(3, false); }
+
+  //AUTORANGE toggling
+  if (MA_PRESSED && NA_PRESSED && !UA_PRESSED && millis()-autorangeInterval>1000) { toggleAutoranging(); Beep(20, false); delay(50); Beep(20, false); }
+}
+
+void rangeMA() {
+  rangeUnit='m';
+  PIN_ON(MA_PIN);
+  PIN_OFF(UA_PIN);
+  PIN_OFF(NA_PIN);
+  if (GPIO_HEADER_RANGING) {
+    PIN_ON(MA_GPIO_PIN);
+    PIN_OFF(UA_GPIO_PIN);
+    PIN_OFF(NA_GPIO_PIN);
+  }
+  analogReferenceHalf(true);
+#ifdef BT_OUTPUT_ADC
+  if (BT_found) SerialBT.println("RANGE: MA");
+#endif
+#ifdef NEXTION_SUPPORT
+  myNex.writeNum("b0.bco",NEX_LIGHT_GREY);
+  myNex.writeNum("b1.bco",NEX_LIGHT_GREY);
+  myNex.writeNum("b2.bco",NEX_BLUE);
+#endif
+}
+
+void rangeUA() {
+  rangeUnit='u';
+  PIN_OFF(MA_PIN);
+  PIN_ON(UA_PIN);
+  PIN_OFF(NA_PIN);
+  if (GPIO_HEADER_RANGING) {
+    PIN_OFF(MA_GPIO_PIN);
+    PIN_ON(UA_GPIO_PIN);
+    PIN_OFF(NA_GPIO_PIN);
+  }
+  analogReferenceHalf(true);
+#ifdef BT_OUTPUT_ADC
+  if (BT_found) SerialBT.println("RANGE: UA");
+#endif
+#ifdef NEXTION_SUPPORT
+  myNex.writeNum("b0.bco",NEX_LIGHT_GREY);
+  myNex.writeNum("b1.bco",NEX_BLUE);
+  myNex.writeNum("b2.bco",NEX_LIGHT_GREY);
+#endif
+}
+
+void rangeNA() {
+  rangeUnit='n';
+  PIN_OFF(MA_PIN);
+  PIN_OFF(UA_PIN);
+  PIN_ON(NA_PIN);
+  if (GPIO_HEADER_RANGING) {
+    PIN_OFF(MA_GPIO_PIN);
+    PIN_OFF(UA_GPIO_PIN);
+    PIN_ON(NA_GPIO_PIN);
+  }
+  analogReferenceHalf(true);
+#ifdef BT_OUTPUT_ADC
+  if (BT_found) SerialBT.println("RANGE: NA");
+#endif
+#ifdef NEXTION_SUPPORT
+  myNex.writeNum("b0.bco",NEX_BLUE);
+  myNex.writeNum("b1.bco",NEX_LIGHT_GREY);
+  myNex.writeNum("b2.bco",NEX_LIGHT_GREY);
+#endif
+}
+
+void handleAutoOff() {
+  uint32_t autooff_deadline = uint32_t((autooff_interval == AUTOOFF_SMART && !(USB_LOGGING_ENABLED || BT_LOGGING_ENABLED))?AUTOOFF_DEFAULT:autooff_interval)*1000;
+  
+  if (millis() - lastKeepAlive > autooff_deadline - 5*1000) {
+    autoffWarning = true;
+
+    if (millis()-autoOffBuzzInterval> AUTOOFF_BUZZ_DELAY)
+    {
+      autoOffBuzzInterval = millis();
+      autoffBuzz=!autoffBuzz;
+
+      if (autoffBuzz)
+        tone(BUZZER, NOTE_B5);
+      else
+        noTone(BUZZER);
+    }
+
+    if (millis() - lastKeepAlive > autooff_deadline) {
+      pinMode(AUTOFF, OUTPUT);
+      digitalWrite(AUTOFF, LOW);
+    }
+  }
+  else if (autoffWarning) { autoffWarning=autoffBuzz=false; digitalWrite(AUTOFF, HIGH); noTone(BUZZER); }
+}
+
+void toggleLPF() {
+  LPF=!LPF;
+  lpfInterval = millis();
+  digitalWrite(LPFPIN, LPF);
+  digitalWrite(LPFLED, LPF);
+  if (AUTORANGE && !LPF) toggleAutoranging(); //turn off AUTORANGE
+#ifdef NEXTION_SUPPORT
+  if (LPF)  myNex.writeNum("lpf.pic",11);
+  else myNex.writeNum("lpf.pic",15);
+#endif 
+}
+
+void toggleOffset() {
+  BIAS=!BIAS;
+  offsetInterval = millis();
+  analogWrite(A0, (BIAS ? DAC_HALF_SUPPLY_OFFSET : DAC_GND_ISO_OFFSET));
+  digitalWrite(BIAS_LED, BIAS);
+  if (AUTORANGE && BIAS) toggleAutoranging(); //turn off AUTORANGE
+#ifdef NEXTION_SUPPORT
+  if (BIAS)  myNex.writeNum("bias.pic",12);
+  else myNex.writeNum("bias.pic",14);
+#endif
+}
+
+void toggleAutoranging() {
+  autorangeInterval = millis();
+  AUTORANGE=!AUTORANGE;
+  if (AUTORANGE && BIAS) toggleOffset(); //turn off BIAS
+  if (AUTORANGE && !LPF) toggleLPF(); //turn on BIAS
+#ifdef NEXTION_SUPPORT
+  if (AUTORANGE) myNex.writeNum("b3.bco",NEX_BLUE);
+  else myNex.writeNum("b3.bco",NEX_LIGHT_GREY);
+#endif
+}
+
+void Beep(byte theDelay, boolean twoSounds) {
+  tone(BUZZER, TONE_BEEP, theDelay);
+  if (twoSounds)
+  {
+    delay(10);
+    tone(BUZZER, 4500, theDelay);
+  }
+}
+
+static __inline__ void syncADC() __attribute__((always_inline, unused));
+static void syncADC() {
+  while(ADC->STATUS.bit.SYNCBUSY == 1);
+}
+
+void setupADC() {
+  ADC->CTRLA.bit.ENABLE = 0;              // disable ADC
+  syncADC();
+  ADC->REFCTRL.bit.REFCOMP = 1;
+  ADC->CTRLB.reg = ADC_PRESCALER | ADC_CTRLB_RESSEL_12BIT;
+  ADC->AVGCTRL.reg = ADC_AVGCTRL;
+  ADC->SAMPCTRL.reg = ADC_SAMPCTRL;
+  ADC->CTRLA.bit.ENABLE = 1;  // enable ADC
+  syncADC();
+//  // ADC Linearity/Bias Calibration from NVM (should already be done done in core)
+//  uint32_t bias = (*((uint32_t *) ADC_FUSES_BIASCAL_ADDR) & ADC_FUSES_BIASCAL_Msk) >> ADC_FUSES_BIASCAL_Pos;
+//  uint32_t linearity = (*((uint32_t *) ADC_FUSES_LINEARITY_0_ADDR) & ADC_FUSES_LINEARITY_0_Msk) >> ADC_FUSES_LINEARITY_0_Pos;
+//  linearity |= ((*((uint32_t *) ADC_FUSES_LINEARITY_1_ADDR) & ADC_FUSES_LINEARITY_1_Msk) >> ADC_FUSES_LINEARITY_1_Pos) << 5;
+//  ADC->CALIB.reg = ADC_CALIB_BIAS_CAL(bias) | ADC_CALIB_LINEARITY_CAL(linearity);
+}
+
+int adcRead(byte ADCpin) {
+  ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[ADCpin].ulADCChannelNumber;
+  syncADC();
+  ADC->SWTRIG.bit.START = 1;
+  while (ADC->INTFLAG.bit.RESRDY == 0);
+  ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY;
+  syncADC();
+  return ADC->RESULT.reg;
+}
+
+void readVOUT() {
+  readDiff = adcRead(SENSE_OUTPUT) - adcRead(SENSE_GNDISO) + offsetCorrectionValue;
+  if (!analog_ref_half && readDiff > RANGE_SWITCH_THRESHOLD_LOW && readDiff < RANGE_SWITCH_THRESHOLD_HIGH/3)
+  {
+    analogReferenceHalf(true);
+    readVOUT();
+  }
+  else if (analog_ref_half && readDiff >= RANGE_SWITCH_THRESHOLD_HIGH)
+  {
+    analogReferenceHalf(false);
+    readVOUT();
+  }
+}
+
+void analogReadCorrectionForced(int offset, uint16_t gain) {
+  offsetCorrectionValue=offset;
+  gainCorrectionValue=gain;
+  analogReadCorrection(offset,gain);
+}
+
+void WDTset() {
+  // Generic clock generator 2, divisor = 32 (2^(DIV+1))
+  GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(4);
+  // Enable clock generator 2 using low-power 32KHz oscillator. With /32 divisor above, this yields 1024Hz(ish) clock.
+  GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(2) | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_DIVSEL;
+  while(GCLK->STATUS.bit.SYNCBUSY);
+  // WDT clock = clock gen 2
+  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_WDT | GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2;
+
+  WDT->CTRL.reg = 0; //disable WDT
+  while(WDT->STATUS.bit.SYNCBUSY);
+  WDT->INTENCLR.bit.EW   = 1;      //disable early warning
+  WDT->CONFIG.bit.PER    = 0xA;    //period ~8s
+  WDT->CTRL.bit.WEN      = 0;      //disable window mode
+  while(WDT->STATUS.bit.SYNCBUSY);
+  WDTclear();
+  WDT->CTRL.bit.ENABLE = 1;        //enable WDT
+  while(WDT->STATUS.bit.SYNCBUSY);
+}
+
+uint32_t WDTInterval=0;
+void WDTclear() {
+  if (millis() - WDTInterval > 6999) //pet the dog every 7s
+  {
+    WDT->CLEAR.reg = WDT_CLEAR_CLEAR_KEY;
+    //while(WDT->STATUS.bit.SYNCBUSY);
+    WDTInterval=millis();
+  }
+}
+
+void ldoOptimizeRefresh() {
+  if (analog_ref_half)
+    ldoOptimized = (ldoValue*500)/ADCFULLRANGE;
+  else
+    ldoOptimized = (ldoValue*1000)/ADCFULLRANGE;
+}
+
+void saveLDO(float newLdoValue) {
+  ldoValue = newLdoValue;
+  eeprom_LDO.write(newLdoValue);
+  ldoOptimizeRefresh();
+}
+
+void refreshADCSamplingSpeed() {
+  if (ADC_SAMPLING_SPEED==ADC_SAMPLING_SPEED_AVG)
+    ADC_AVGCTRL = ADC_AVGCTRL_SAMPLENUM_64 | ADC_AVGCTRL_ADJRES(0x4ul);
+  else if (ADC_SAMPLING_SPEED==ADC_SAMPLING_SPEED_FAST)
+    ADC_AVGCTRL = ADC_AVGCTRL_SAMPLENUM_16 | ADC_AVGCTRL_ADJRES(0x4ul); //take 16 samples adjust by 4
+  else if (ADC_SAMPLING_SPEED==ADC_SAMPLING_SPEED_SLOW)
+    ADC_AVGCTRL = ADC_AVGCTRL_SAMPLENUM_256 | ADC_AVGCTRL_ADJRES(0x4ul); //take 512 samples adjust by 4
+  setupADC();
+  //other combinations:
+  //ADC_AVGCTRL_SAMPLENUM_128 | ADC_AVGCTRL_ADJRES(0x4ul)
+  //ADC_AVGCTRL_SAMPLENUM_1 | ADC_AVGCTRL_ADJRES(0x00ul);  // take 1 sample, adjusting result by 0
+  //ADC_AVGCTRL_SAMPLENUM_16 | ADC_AVGCTRL_ADJRES(0x4ul); //take 16 samples adjust by 4
+  //ADC_AVGCTRL_SAMPLENUM_256 | ADC_AVGCTRL_ADJRES(0x4ul); //take 256 samples adjust by 4
+  //ADC_AVGCTRL_SAMPLENUM_512 | ADC_AVGCTRL_ADJRES(0x4ul); //take 512 samples adjust by 4
+  //ADC_AVGCTRL_SAMPLENUM_1024 | ADC_AVGCTRL_ADJRES(0x4ul); //take 1024 samples adjust by 4
+}
+
+void printCalibInfo() {
+  Serial.println("\r\nADC calibration values:");
+  Serial.print("Offset="); Serial.println(offsetCorrectionValue);
+  Serial.print("Gain="); Serial.println(gainCorrectionValue);
+  Serial.print("LDO="); Serial.println(ldoValue,3);
+
+  
+  Serial.println("\r\nEEPROM Settings:");
+  Serial.print("LoggingFormat="); Serial.println(LOGGING_FORMAT); 
+  Serial.print("ADCSamplingSpeed="); Serial.println(ADC_SAMPLING_SPEED); 
+  Serial.print("AutoOff="); 
+    if (autooff_interval == AUTOOFF_DISABLED) {
+      Serial.println("DISABLED");
+    } else if (autooff_interval == AUTOOFF_SMART) {
+      Serial.println("SMART");
+    } else {
+      Serial.println(autooff_interval);
+    }
+
+  Serial.println("");
+}
+
+void printSerialMenu() {
+  // Print device name, firmware version and state for interop on PC side
+  Serial.println("\r\nCurrentRanger R3");  
+  Serial.print("Firmware version: "); Serial.println(FW_VERSION);  
+  Serial.print("BT Logging: "); Serial.println(BT_LOGGING_ENABLED);
+  Serial.print("USB Logging: "); Serial.println(USB_LOGGING_ENABLED); 
+
+  printCalibInfo();
+
+  Serial.println("a = cycle Auto-Off function");
+  Serial.print  ("b = toggle BT/serial logging (");Serial.print(SERIAL_UART_BAUD);Serial.println("baud)");
+  Serial.println("f = cycle serial logging formats (exponent,nA,uA,mA/raw-ADC)");
+  Serial.println("g = toggle GPIO range indication (SCK=mA,MISO=uA,MOSI=nA)");
+  Serial.println("r = reboot into bootloader");
+  Serial.println("s = cycle ADC sampling speeds (0=average,faster,slower)");
+  Serial.println("t = toggle touchpad serial output debug info");
+  Serial.println("u = toggle USB/serial logging");
+  Serial.println("< = Calibrate LDO value (-1mV)");
+  Serial.println("> = Calibrate LDO value (+1mV)");
+  Serial.println("* = Calibrate GAIN value (+1)");
+  Serial.println("/ = Calibrate GAIN value (-1)");
+  Serial.println("+ = Calibrate OFFSET value (+1)");
+  Serial.println("- = Calibrate OFFSET value (-1)");
+  Serial.println("? = Print this menu and calib info");
+  Serial.println();
+}
+
+void analogReferenceHalf(uint8_t half) {
+  analog_ref_half = half;
+  analogReference(half ? AR_INTERNAL1V65 : AR_DEFAULT);
+  ldoOptimizeRefresh();
+}
+
+void analogReadCorrection(int offset, uint16_t gain) {
+  ADC->OFFSETCORR.reg = ADC_OFFSETCORR_OFFSETCORR(offset);
+  ADC->GAINCORR.reg = ADC_GAINCORR_GAINCORR(gain);
+  ADC->CTRLB.bit.CORREN = 1;
+  while(ADC->STATUS.bit.SYNCBUSY);
+}
+
+void rangeBeep(uint16_t switch_delay) {
+  uint16_t freq = NOTE_C5;
+  if (RANGE_UA) freq = NOTE_D5;
+  if (RANGE_MA) freq = NOTE_E5;
+  tone(BUZZER, freq, switch_delay?switch_delay:20);
+}
+
+#define REBOOT_TOKEN 0xf01669ef //special token in RAM, picked up by the bootloader
+void rebootIntoBootloader() {
+  *((volatile uint32_t *)(HMCRAMC0_ADDR + HMCRAMC0_SIZE - 4)) = REBOOT_TOKEN; //Entering bootloader from application: https://github.com/microsoft/uf2-samdx1/issues/41
+  NVIC_SystemReset();
+}
+void trigger1(){
+  lastKeepAlive=millis();
+  if (!AUTORANGE){
+    rangeNA(); rangeBeep(0);
+  }
+}
+void trigger2(){
+  lastKeepAlive=millis();
+  if (!AUTORANGE){
+    rangeUA(); rangeBeep(0);
+  }
+}
+void trigger3(){
+  lastKeepAlive=millis();
+  if (!AUTORANGE){
+    rangeMA(); rangeBeep(0);
+  }
+}
+void trigger4(){
+  lastKeepAlive=millis();
+ toggleAutoranging(); Beep(20, false); delay(50); Beep(20, false); 
+}
+void trigger16(){
+  lastKeepAlive=millis();
+  Beep(20, false);
+  myNex.writeStr("page page0","cmd");
+  if (LPF)  myNex.writeNum("lpf.pic",11);
+  else
+  {
+    myNex.writeNum("lpf.pic",15);
+  }
+  if (BIAS)  myNex.writeNum("bias.pic",12);
+  else
+  {
+    myNex.writeNum("bias.pic",14);
+  }
+  if (AUTORANGE){
+    myNex.writeNum("b3.bco",NEX_BLUE);
+  }else{
+    myNex.writeNum("b3.bco",NEX_LIGHT_GREY);
+  }
+  if( rangeUnit=='n') myNex.writeNum("b0.bco",NEX_BLUE);
+  if( rangeUnit=='u') myNex.writeNum("b1.bco",NEX_BLUE);
+  if( rangeUnit=='m') myNex.writeNum("b2.bco",NEX_BLUE);
+
+  nex_page=0;
+}
+void trigger17(){
+  lastKeepAlive=millis(); 
+  Beep(20, false);
+ myNex.writeStr("page page1","cmd");
+ nex_page=1;
+}
+void trigger18(){
+  lastKeepAlive=millis(); 
+  Beep(20, false);
+  myNex.writeStr("page page2","cmd");
+  if (analog_ref_half)
+  {
+    analogReferenceHalf(false);
+    vbat=adcRead(SENSE_VIN);
+    analogReferenceHalf(true);
+  }
+    else vbat=adcRead(SENSE_VIN);
+    vbat=((vbat/ADCFULLRANGE) * ldoValue) * 1.5; //1.5 given by vbat->A5 resistor ratio (1 / (2M * 1/(1M+2M)))
+    nex_page=2;
+    myNex.writeStr("page page2","cmd");
+    myNex.writeStr("offset.txt",(String)offsetCorrectionValue);
+    myNex.writeStr("gain.txt",(String)gainCorrectionValue);
+    myNex.writeStr("ldo.txt",String(ldoValue,3));
+    myNex.writeStr("fw.txt",(String)FW_VERSION);
+    myNex.writeStr("bat.txt",(String)vbat);
+  nex_page=2;
+}
+void trigger19(){
+  lastKeepAlive=millis();
+  Beep(20, false);
+  myNex.writeStr("page page3","cmd");
+  nex_page=3;
+}
+void trigger32(){
+  lastKeepAlive=millis();
+  Beep(20, false);
+  toggleLPF();
+}
+void trigger33(){
+  lastKeepAlive=millis();
+  Beep(20, false);
+  toggleOffset();
+}
+void trigger34(){
+  lastKeepAlive=millis();
+  Beep(20, false);
+  USB_LOGGING_ENABLED=!USB_LOGGING_ENABLED;
+  if (USB_LOGGING_ENABLED)  myNex.writeNum("usb.pic",10);
+  else
+  {
+    myNex.writeNum("usb.pic",16);
+  }
+}
+void trigger35()
+{ //bluetooth
+  lastKeepAlive=millis();
+  Beep(20, false);  
+  if (BT_found){
+    BT_found=false;
+    myNex.writeNum("bluetooth.pic",13);
+  }
+  else
+  {
+    uint32_t timer=millis();
+    while(millis()-timer<1000) //about 1s to respond
+    {
+      if (SerialBT.available()==2 && SerialBT.read()=='O' && SerialBT.read()=='K')
+      {
+        BT_found=true;
+        break;
+      }
+    }
+    if (BT_found){
+      myNex.writeNum("bluetooth.pic",9);
+    }else{
+      myNex.writeNum("bluetooth.pic",13);
+    }
+  }
+}

+ 147 - 0
variant/variant.cpp

@@ -0,0 +1,147 @@
+/*
+  Copyright (c) 2014-2015 Arduino LLC.  All right reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the GNU Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+
+#include "variant.h"
+
+/*
+ * Pins descriptions
+ */
+const PinDescription g_APinDescription[]=
+{
+  // 0..13 - Digital pins
+  // ----------------------
+  // 0/1 - SERCOM/UART (Serial1)
+  { PORTA, 11, PIO_SERCOM, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM1_CH1, TCC1_CH1, EXTERNAL_INT_11 }, // RX: SERCOM0/PAD[3]
+  { PORTA, 10, PIO_SERCOM, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM1_CH0, TCC1_CH0, EXTERNAL_INT_10 }, // TX: SERCOM0/PAD[2]
+
+  // 2..12
+  // Digital Low
+  { PORTA, 14, PIO_DIGITAL, (PIN_ATTR_DIGITAL), No_ADC_Channel, PWM0_CH4, TCC0_CH4, EXTERNAL_INT_14 },
+  { PORTA,  9, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM0_CH1, TCC0_CH1, EXTERNAL_INT_9 }, // TCC0/WO[1]
+  { PORTA,  8, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM0_CH0, TCC0_CH0, EXTERNAL_INT_NMI },  // TCC0/WO[0]
+  { PORTA, 15, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM3_CH1, TC3_CH1, EXTERNAL_INT_15 }, // TC3/WO[1]
+  { PORTA, 20, PIO_TIMER_ALT, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER_ALT), No_ADC_Channel, PWM0_CH6, TCC0_CH6, EXTERNAL_INT_4 }, // TCC0/WO[6]
+  { PORTA, 21, PIO_DIGITAL, (PIN_ATTR_DIGITAL), No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_5 },
+
+  // Digital High
+  { PORTA,  6, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), ADC_Channel6, PWM1_CH0, TCC1_CH0, EXTERNAL_INT_6 }, // TCC1/WO[0]
+  { PORTA,  7, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), ADC_Channel7, PWM1_CH1, TCC1_CH1, EXTERNAL_INT_7 }, // TCC1/WO[1]
+  { PORTA, 18, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM3_CH0, TC3_CH0, EXTERNAL_INT_2 }, // TC3/WO[0]
+  { PORTA, 16, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM2_CH0, TCC2_CH0, EXTERNAL_INT_0 }, // TCC2/WO[0]
+  { PORTA, 19, PIO_TIMER_ALT, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER_ALT), No_ADC_Channel, PWM0_CH3, TCC0_CH3, EXTERNAL_INT_3 }, // TCC0/WO[3]
+
+  // 13 (LED)
+  { PORTA, 17, PIO_PWM, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM2_CH1, TCC2_CH1, EXTERNAL_INT_1 }, // TCC2/WO[1]
+
+  // 14..19 - Analog pins
+  // --------------------
+  { PORTA,  2, PIO_ANALOG, PIN_ATTR_ANALOG, ADC_Channel0, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_2 }, // ADC/AIN[0]
+  { PORTB,  8, PIO_ANALOG, (PIN_ATTR_PWM|PIN_ATTR_TIMER), ADC_Channel2, PWM4_CH0, TC4_CH0, EXTERNAL_INT_8 }, // ADC/AIN[2]
+  { PORTB,  9, PIO_ANALOG, (PIN_ATTR_PWM|PIN_ATTR_TIMER), ADC_Channel3, PWM4_CH1, TC4_CH1, EXTERNAL_INT_9 }, // ADC/AIN[3]
+  { PORTA,  4, PIO_ANALOG, (PIN_ATTR_PWM|PIN_ATTR_TIMER), ADC_Channel4, PWM0_CH0, TCC0_CH0, EXTERNAL_INT_4 }, // ADC/AIN[4]
+  { PORTA,  5, PIO_ANALOG, (PIN_ATTR_PWM|PIN_ATTR_TIMER), ADC_Channel5, PWM0_CH1, TCC0_CH1, EXTERNAL_INT_5 }, // ADC/AIN[5]
+  { PORTB,  2, PIO_ANALOG, 0, ADC_Channel10, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_2 }, // ADC/AIN[10]
+
+  // 20..21 I2C pins (SDA/SCL and also EDBG:SDA/SCL)
+  // ----------------------
+  { PORTA, 22, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_6 }, // SDA: SERCOM3/PAD[0]
+  { PORTA, 23, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_7 }, // SCL: SERCOM3/PAD[1]
+
+  // 22..24 - SPI pins (ICSP:MISO,SCK,MOSI)
+  // ----------------------
+  { PORTA, 12, PIO_SERCOM_ALT, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_12 }, // MISO: SERCOM4/PAD[0]
+  { PORTB, 10, PIO_SERCOM_ALT, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_10 }, // MOSI: SERCOM4/PAD[2]
+  { PORTB, 11, PIO_SERCOM_ALT, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_11 }, // SCK: SERCOM4/PAD[3]
+
+  // 25..26 - RX/TX LEDS (PB03/PA27)
+  // --------------------
+//{ PORTB,  3, PIO_OUTPUT, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // used as output only
+  { PORTB,  3, PIO_TIMER, PIN_ATTR_DIGITAL, ADC_Channel11, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE },
+  { PORTA, 27, PIO_OUTPUT, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // used as output only
+
+  // 27..29 - USB
+  // --------------------
+  { PORTA, 28, PIO_COM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // USB Host enable
+  { PORTA, 24, PIO_COM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // USB/DM
+  { PORTA, 25, PIO_COM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // USB/DP
+
+  // 30..41 - EDBG
+  // ----------------------
+  // 30/31 - EDBG/UART
+  { PORTB, 22, PIO_SERCOM_ALT, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // TX: SERCOM5/PAD[2]
+  { PORTB, 23, PIO_SERCOM_ALT, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // RX: SERCOM5/PAD[3]
+
+  // 32/33 I2C (SDA/SCL and also EDBG:SDA/SCL)
+  { PORTA, 99, PIO_SERCOM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // SDA: SERCOM3/PAD[0]
+  { PORTA, 99, PIO_SERCOM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // SCL: SERCOM3/PAD[1]
+
+  // 34..37 - EDBG/SPI
+  { PORTA, 19, PIO_SERCOM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // MISO: SERCOM1/PAD[3]
+  { PORTA, 16, PIO_SERCOM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // MOSI: SERCOM1/PAD[0]
+  { PORTA, 18, PIO_SERCOM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // SS: SERCOM1/PAD[2]
+  { PORTA, 17, PIO_SERCOM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // SCK: SERCOM1/PAD[1]
+
+  // 38..41 - EDBG/Digital
+  { PORTA, 13, PIO_PWM, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM), No_ADC_Channel, PWM0_CH5, NOT_ON_TIMER, EXTERNAL_INT_13 }, // EIC/EXTINT[13] *TCC2/WO[1] TCC0/WO[7]
+  { PORTA, 21, PIO_PWM_ALT, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM), No_ADC_Channel, PWM0_CH7, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // Pin 7
+  { PORTA,  6, PIO_PWM, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM), No_ADC_Channel, PWM1_CH0, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // Pin 8
+  { PORTA,  7, PIO_PWM, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM), No_ADC_Channel, PWM1_CH1, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // Pin 9
+
+  // 42 (AREF)
+  { PORTA, 3, PIO_ANALOG, PIN_ATTR_ANALOG, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // DAC/VREFP
+
+  // ----------------------
+  // 43..45 - Alternate use of A0 (DAC output), 44 SWCLK, 45, SWDIO
+  { PORTA,  2, PIO_ANALOG, PIN_ATTR_ANALOG, DAC_Channel0, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_2 }, // DAC/VOUT
+  { PORTA, 30, PIO_PWM, PIN_ATTR_DIGITAL|PIO_SERCOM, No_ADC_Channel, NOT_ON_PWM, TCC1_CH0, EXTERNAL_INT_10 }, // SWCLK
+  { PORTA, 31, PIO_PWM, PIN_ATTR_DIGITAL|PIO_SERCOM, No_ADC_Channel, NOT_ON_PWM, TCC1_CH1, EXTERNAL_INT_11 }, // SWDIO
+} ;
+
+extern "C" {
+    unsigned int PINCOUNT_fn() {
+        return (sizeof(g_APinDescription) / sizeof(g_APinDescription[0]));
+    }
+}
+
+const void* g_apTCInstances[TCC_INST_NUM+TC_INST_NUM]={ TCC0, TCC1, TCC2, TC3, TC4, TC5 } ;
+
+// Multi-serial objects instantiation
+SERCOM sercom0( SERCOM0 ) ;
+SERCOM sercom1( SERCOM1 ) ;
+SERCOM sercom2( SERCOM2 ) ;
+SERCOM sercom3( SERCOM3 ) ;
+SERCOM sercom4( SERCOM4 ) ;
+SERCOM sercom5( SERCOM5 ) ;
+
+Uart Serial1( &sercom0, PIN_SERIAL1_RX, PIN_SERIAL1_TX, PAD_SERIAL1_RX, PAD_SERIAL1_TX ) ;
+Uart SerialBT( &sercom5, PIN_SERIAL0_RX, PIN_SERIAL0_TX, PAD_SERIAL0_RX, PAD_SERIAL0_TX ) ;
+Uart SerialNextion( &sercom3, PIN_SERIAL2_RX, PIN_SERIAL2_TX, PAD_SERIAL2_RX, PAD_SERIAL2_TX ) ;
+void SERCOM0_Handler()
+{
+  Serial1.IrqHandler();
+}
+void SERCOM3_Handler()
+{
+  SerialNextion.IrqHandler();
+}
+void SERCOM5_Handler()
+{
+  SerialBT.IrqHandler();
+}
+

+ 247 - 0
variant/variant.h

@@ -0,0 +1,247 @@
+/*
+  Copyright (c) 2014-2015 Arduino LLC.  All right reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  See the GNU Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef _VARIANT_CURRENT_RANGER_NEXTION_
+#define _VARIANT_CURRENT_RANGER_NEXTION_
+
+// The definitions here needs a SAMD core >=1.6.10
+#define ARDUINO_SAMD_VARIANT_COMPLIANCE 10610
+
+/*----------------------------------------------------------------------------
+ *        Definitions
+ *----------------------------------------------------------------------------*/
+
+/** Frequency of the board main oscillator */
+#define VARIANT_MAINOSC		(32768ul)
+
+/** Master clock frequency */
+#define VARIANT_MCK	(F_CPU)
+
+/*----------------------------------------------------------------------------
+ *        Headers
+ *----------------------------------------------------------------------------*/
+
+#include "WVariant.h"
+
+#ifdef __cplusplus
+#include "SERCOM.h"
+#include "Uart.h"
+#endif // __cplusplus
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif // __cplusplus
+
+/*----------------------------------------------------------------------------
+ *        Pins
+ *----------------------------------------------------------------------------*/
+
+// Number of pins defined in PinDescription array
+#ifdef __cplusplus
+extern "C" unsigned int PINCOUNT_fn();
+#endif
+#define PINS_COUNT           (PINCOUNT_fn())
+#define NUM_DIGITAL_PINS     (20u)
+#define NUM_ANALOG_INPUTS    (7u)
+#define NUM_ANALOG_OUTPUTS   (1u)
+#define analogInputToDigitalPin(p)  ((p < 6u) ? (p) + 14u : -1)
+
+#define digitalPinToPort(P)        ( &(PORT->Group[g_APinDescription[P].ulPort]) )
+#define digitalPinToBitMask(P)     ( 1 << g_APinDescription[P].ulPin )
+//#define analogInPinToBit(P)        ( )
+#define portOutputRegister(port)   ( &(port->OUT.reg) )
+#define portInputRegister(port)    ( &(port->IN.reg) )
+#define portModeRegister(port)     ( &(port->DIR.reg) )
+#define digitalPinHasPWM(P)        ( g_APinDescription[P].ulPWMChannel != NOT_ON_PWM || g_APinDescription[P].ulTCChannel != NOT_ON_TIMER )
+
+/*
+ * digitalPinToTimer(..) is AVR-specific and is not defined for SAMD
+ * architecture. If you need to check if a pin supports PWM you must
+ * use digitalPinHasPWM(..).
+ *
+ * https://github.com/arduino/Arduino/issues/1833
+ */
+// #define digitalPinToTimer(P)
+
+// LEDs
+#define PIN_LED_13           (13u)
+//#define PIN_LED_RXL          (25u)
+#define PIN_LED_TXL          (26u)
+#define PIN_LED              PIN_LED_13
+//#define PIN_LED2             PIN_LED_RXL
+#define PIN_LED3             PIN_LED_TXL
+#define LED_BUILTIN          PIN_LED_13
+
+#define SS_FLASHMEM          (8u)
+
+/*
+ * Analog pins
+ */
+#define PIN_A0               (14ul)
+#define PIN_A1               (15ul)
+#define PIN_A2               (16ul)
+#define PIN_A3               (17ul)
+#define PIN_A4               (18ul)
+#define PIN_A5               (19ul)
+#define PIN_A6               (25ul)
+#define PIN_DAC0             (14ul)
+
+static const uint8_t A0  = PIN_A0;
+static const uint8_t A1  = PIN_A1;
+static const uint8_t A2  = PIN_A2;
+static const uint8_t A3  = PIN_A3;
+static const uint8_t A4  = PIN_A4;
+static const uint8_t A5  = PIN_A5;
+static const uint8_t A6  = PIN_A6;
+static const uint8_t DAC0 = PIN_DAC0;
+#define ADC_RESOLUTION		12
+
+// Other pins
+#define PIN_ATN              (38ul)
+static const uint8_t ATN = PIN_ATN;
+
+/*
+ * Serial interfaces
+ */
+// SerialBT
+#define PIN_SERIAL0_RX       (31ul)
+#define PIN_SERIAL0_TX       (30ul)
+#define PAD_SERIAL0_TX       (UART_TX_PAD_2)
+#define PAD_SERIAL0_RX       (SERCOM_RX_PAD_3)
+
+// Serial1
+#define PIN_SERIAL1_RX       (0ul)
+#define PIN_SERIAL1_TX       (1ul)
+#define PAD_SERIAL1_TX       (UART_TX_PAD_2)
+#define PAD_SERIAL1_RX       (SERCOM_RX_PAD_3)
+//Serial2
+#define PIN_SERIAL2_RX       (21ul)
+#define PIN_SERIAL2_TX       (20ul)
+#define PAD_SERIAL2_TX       (UART_TX_PAD_0)
+#define PAD_SERIAL2_RX       (SERCOM_RX_PAD_1)
+/*
+ * SPI Interfaces
+ */
+#define SPI_INTERFACES_COUNT 1
+
+#define PIN_SPI_MISO         (22u)
+#define PIN_SPI_MOSI         (23u)
+#define PIN_SPI_SCK          (24u)
+#define PERIPH_SPI           sercom4
+#define PAD_SPI_TX           SPI_PAD_2_SCK_3
+#define PAD_SPI_RX           SERCOM_RX_PAD_0
+
+static const uint8_t SS	  = PIN_A2 ;	// SERCOM4 last PAD is present on A2 but HW SS isn't used. Set here only for reference.
+static const uint8_t MOSI = PIN_SPI_MOSI ;
+static const uint8_t MISO = PIN_SPI_MISO ;
+static const uint8_t SCK  = PIN_SPI_SCK ;
+
+/*
+ * Wire Interfaces
+ */
+#define WIRE_INTERFACES_COUNT 0
+/*
+#define PIN_WIRE_SDA         (20u)
+#define PIN_WIRE_SCL         (21u)
+#define PERIPH_WIRE          sercom3
+#define WIRE_IT_HANDLER      SERCOM3_Handler
+
+static const uint8_t SDA = PIN_WIRE_SDA;
+static const uint8_t SCL = PIN_WIRE_SCL;
+*/
+/*
+ * USB
+ */
+#define PIN_USB_HOST_ENABLE (27ul)
+#define PIN_USB_DM          (28ul)
+#define PIN_USB_DP          (29ul)
+#define USB_HOST_EN         PIN_USB_HOST_ENABLE
+#define USB_HOST_ENABLE     PIN_USB_HOST_ENABLE
+#define PIN_AUTO_OFF        PIN_USB_HOST_ENABLE
+
+/*
+ * I2S Interfaces
+ */
+#define I2S_INTERFACES_COUNT 1
+
+#define I2S_DEVICE          0
+#define I2S_CLOCK_GENERATOR 3
+#define PIN_I2S_SD          (9u)
+#define PIN_I2S_SCK         (1u)
+#define PIN_I2S_FS          (0u)
+
+#ifdef __cplusplus
+}
+#endif
+
+/*----------------------------------------------------------------------------
+ *        Arduino objects - C++ only
+ *----------------------------------------------------------------------------*/
+
+#ifdef __cplusplus
+
+/*	=========================
+ *	===== SERCOM DEFINITION
+ *	=========================
+*/
+extern SERCOM sercom0;
+extern SERCOM sercom1;
+extern SERCOM sercom2;
+extern SERCOM sercom3;
+extern SERCOM sercom4;
+extern SERCOM sercom5;
+
+extern Uart SerialBT;
+extern Uart SerialNextion;
+//extern Uart Serial1;
+
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+unsigned int PINCOUNT_fn();
+#ifdef __cplusplus
+}
+#endif
+
+// These serial port names are intended to allow libraries and architecture-neutral
+// sketches to automatically default to the correct port name for a particular type
+// of use.  For example, a GPS module would normally connect to SERIAL_PORT_HARDWARE_OPEN,
+// the first hardware serial port whose RX/TX pins are not dedicated to another use.
+//
+// SERIAL_PORT_MONITOR        Port which normally prints to the Arduino Serial Monitor
+//
+// SERIAL_PORT_USBVIRTUAL     Port which is USB virtual serial
+//
+// SERIAL_PORT_LINUXBRIDGE    Port which connects to a Linux system via Bridge library
+//
+// SERIAL_PORT_HARDWARE       Hardware serial port, physical RX & TX pins.
+//
+// SERIAL_PORT_HARDWARE_OPEN  Hardware serial ports which are open for use.  Their RX & TX
+//                            pins are NOT connected to anything by default.
+#define SERIAL_PORT_USBVIRTUAL      Serial
+#define SerialUSB                   Serial //for compatibility with v1.4.0 and prior
+#define SERIAL_PORT_MONITOR         Serial
+// Serial has no physical pins broken out, so it's not listed as HARDWARE port
+#define SERIAL_PORT_HARDWARE        SerialBT
+#define SERIAL_PORT_HARDWARE_OPEN   SerialBT
+
+#endif
+