NanOMeter 2020

(Pronounced Nan OM eter – Like speedometer of course)

Having historically kludged together a breadboard, or dead bug wired something to simply test an SR04, measure a resistance, or test a servo or stepper driver, I finally decided I’d do something a little more permanent. What trigger this was seeing VolsR’s (https://www.instructables.com/member/VolosR/) little Nano based “semi” multi-meter solution on Instructables.com.

His circuit info wasn’t completely accurate and he has since developed a more advanced printed circuit board version, but there was enough to get me started on putting a little multi function meter / tester together. I followed his basic concept and used his code as the core as it worked well, and then added a few more functions I needed or wanted. I even used his basic layout of his original board as it just worked well.

At this point the little box can:

  1. Measure DC voltage (with reverse voltage diode protection)
  2. Read an analog input and show the value, as well as the min, max and average values
  3. Measure the resistance of a resistor
  4. Measure the voltage drop of a diode or LED (and test the LED there too)
  5. Act as a beeping / LED continuity tester
  6. Generate a PWM output
  7. Test a servo
  8. Test a ping / SR04 sensor
  9. Drive a stepper motor driver\
  10. Monitor the external battery supply voltage on the Vin pin
  11. Allow adjustments to the data dump timer (more later)
  12. Do an I2C bus scan and show attached device addresses.
Sample view of the Volt Meter mode

Additionally the device is outputting data for the current mode to the serial port (USB or TTL) that can be used to log sensor data over time. It can also be minimally controlled over the serial port with mode toggle or selection, PWM width change, servo pulse changes, and logging time period changes. The data is dumped in a CSV format with the mode name, millis(), data1, data2, data3, data4 format. Using the millis() value one could time chart the data if desired. You could also use a wireless serial bridge such as a 2.4G or Bluetooth version and log that data back remotely as well I guess.

The I2C bus is exposed next to the display but at this point the Nano is at 99% of memory used on my compiler (30624 or 30720 bytes), so there is not much room for growth. Your compiler mileage may vary. I’m compiling under Arduino 1.8.7 on Ubuntu 18.04 with the Adafruit 1.0.0 SoftServo library and the 1.1.3 Stepper library. You can comment the “useFonts” #ifdef to remove the font usage and use the normal text scaling instead. It does save quite a bit of space.

Sample of the analog read view

Unfortunately the libraries, along with the fonts, eat up a lot of memory on the device. The images do not make any difference so removing them doesn’t help a lot. One could remove the fonts and have more programming space if desired I think. Only 69% of the dynamic memory is being used so I do not believe there will be any stability issues. Obviously the code could be optimized but I’m pretty done with it at this point. In reality only the A3 and A6 pins are left to be used so there isn’t much left to work with there either. My code is not pretty nor optimized but it works so I’ll leave it at that.

Current pin uses are:

(P) = PWM Pin

// 0 – Rx – On header pin lower right – Used to receive commands from serial console, etc
// 1 – Tx – On header pin lower right – Dumps CSV data to here
// 2 – Left Button
// 3(P)- Middle Button (on interrupt)
// 4 – Right Button
// 5(P)- Software Servo Pin
// 6(P)- PWM output on three pin header
// 7 – Ping Trigger
// 8 – Ping Echo
// 9(P)- Stepper IN1
// 10(P)- Stepper IN3
// 11(P)- Stepper IN2
// 12 – Stepper IN4
// 13 – Used for speaker output and LED display
// A0 – Main Analog Input
// A1 – External Battery Power Supply Voltage 100k/10k divider
// A2 – Input for Diode and Resistor testing
// A3 – SPARE
// A4 – I2C bus
// A5 – I2C bus
// A6 – SPARE
// A7 – Voltage Meter input – on 100k/10k divider

I used Adafruit’s SoftServo library as the normal servo library kills some PWM pins and I wasn’t sure if they would be needed. It works well for this use and I’ve used it on other robots as well. The AdaFruit OLED library is used for the SSD1306 device which is the 128 x 32 pixel version. VolsR’s updated version uses the larger 128×64 version but personally I would rather have the board space than more display space for this project.

To add the images I “temporarily borrowed” some icon art from online (I’ll give them back when I’m done) and converted them to the HEX format needed using the handy image2cpp online at at https://diyusthad.com/image2cpp. Works great and good enough results for me.

The Circuit

I haven’t drawn a full schematic up but each test point is pretty simply. Using the above pin outs one should be able to replicate it. The voltage dividers on the volt meter and external battery are simple 100K / 10K voltage dividers. The buttons simply go from the pins to ground and we use the internal pull up resistors on the Nano. I did not use any pull up resistors on the I2C display and it works fine. The tiny speaker connects directly to pin13 and to ground. It does show a positive terminal so I connected it like that. The dropping resistor for the diode / led is a 2.2K in mine, just tweak the code as needed.

The Buttons

With only three input buttons there are limits BUT in reality you can get six (6) options out of them at least. The middle button is hooked to an interrupt that toggles the “mode” for the box. The left (down) and right (up) buttons selection options within the modes if there are any, i.e. in PWM they change the pulse widths, etc. Simultaneously pressing the Up and Down toggle the “Pot Mode” allowing a pot to be connected to the analog input to control the PWM, Servo and data dump timer modes. Just makes it easier to test things. Pressing the down and select toggles the beeping sounds on and off, including the continuity mode. In analog reading mode the down button resets the averages.

The Down Sides

There are obviously some down sides to the little box. For one the voltage measurements aren’t the most accurate. I’m not sure if it’s the voltage drop on little 1N914 diode I’m using for reverse protection or something else. I do have an adjustment in the code to offset the diode and it’s accurate at lower voltages but off at higher voltages. I know voltage drop can vary by voltage but wasn’t aware it was that much.

The Conclusion

So far the little NanOMeter has been pretty handy. I’ve built a couple probe wires out of header jumper wires to more easily use the volt meter and continuity tester piece. Also built a header adapter for the analog input and resister/diode/continuity connectors from male headers that can be inserted to flip the gender of the connectors without surgery. I also added an additional header that provides power and ground directly from the battery (7.4v old Canon camera battery) that I can use to power bigger voltage / current requirements without leaning on the Nano’s regulator too much. Great for driving stepper motor controllers and could be used to drive H bridge motor drivers as well.

So if you need a simple voltmeter/analog tester/ohm meter/diode drop/continuity tester/PWM tester/Sevro tester/SR04 tester/stepper tester/data logger/I2C bus scanner, this could be useful.

The NanOMeter now has a prominent place on my bench and is quite handy when I need to check an address on an I2C device, validate a resistor value, test an analog based sensor etc.

Cheers – ProtoWrxs

//const String curVer = "2020-01-01";  // Final code clean up and documentation -back to 99% used memory so done
//                                     // Added #ifdef useFonts option to save memory and insure better compiling options
//                                     // Still no guarantee but works now on multiple Ubuntu boxes / library versions for me
//const String curVer = "2019-12-30";  // Added sound mute with SEL / UP combo and added code for DOWN / SEL if desired
//                                      Added I2C scanner option to display devices
//const String curVer = "2019-12-29";   // Documented as much as I could, changed PWM pulse control to single increments +/-
//                                      Changed analog stats to be in Min / Avg / Max on the screen, moved PWM from pin 9 to 6
//                                      Allows 9,10,11,12 for Stepper outputs
//const String curVer = "2019-12-28"; // Added Servo, Ping, Logging, and Serial controls
//const String curver = "2019-12-27"; // Initial version copied from Instructable site, cleaned up tabs and documented code

// NanOMeter - Multi Function Nano Based Test base
// Stephen W Nolen / ProtoWrxs
// Core code based on VolosR work on Instructables (https://www.instructables.com/member/VolosR/)
// I used the basic lay of VolosR's board as well as it just works
// He has an updated version but I personally like the 128x32 size screen better allowing for more component room
//
// Features Includes:
//   0 Volt meter
//   1 Analog Sensor
//   2 Resistor / Ohm Tester
//   3 Diode Voltage Drop / LED Tester
//   4 Continuity Tester with Beep
//   5 PWM generator
//   6 Servo Tester
//   7 Ping  / Sonar Tester (SR04 at least)
//   8 Stepper Motor Tester
//   9 External Battery Voltage Display
//  10 Data Logging time adjustment
//  11 I2C bus scanner
// Modes 0 - 9 dump data to the serial port (USB or TTL level pins lower right)
// Mode 10 Allows adjustment from 500 (1/2 second) to a minute interval adjustments
// Data dump format is:
// Mode, millis(), data1, data2, data3, data4
// All except analog dump a single data item
// Analog dumps the read, min, average, and max data items
// Ohms dumps "inf" if nothing attached, resistance otherwise
// Continutity dumps voltage drop with 0.00 being continuity
// NOTHING is dumped during mode 10 time adjustment
// I2C dumps addresses CSV but does NOT use the Data Logging timer

// Device has some limited remote serial controls
// M will dump the mode just like the middle select button
// 0-9 will select the mode listed above
// A will select the dump timer mode
// For Mode 5, 6, and 10, the "-" emulates pressed the down button and a "+" emulates the UP button
// The "=" command resets pulses or settings to their default for the mode you are in.


//***************************************************************************************//
// Pin Uses
//  0   - Rx - On header pin lower right - Used to receive commands from serial console or other devices
//  1   - Tx - On header pin lower right - Dumps CSV data to here - runs at 115200, change in code if  needed
//  2   - Right Button
//  3(P)- Middle Button (on interrupt)
//  4   - Left Button
//  5(P)- Software Servo Pin
//  6(P)- PWM output on three pin header
//  7   - Ping Trigger
//  8   - Ping Echo
//  9(P)- Stepper IN1
// 10(P)- Stepper IN3
// 11(P)- Stepper IN2
// 12   - Stepper IN4
// 13   - Used for speaker output and LED display
// A0   - Main Analog Input
// A1   - External Battery Power Suppy Voltage 100k/10k divider
// A2   - Input for Diode and Resistor testing
// A3   -
// A4   - I2C bus
// A5   - I2C bus
// A6   -
// A7   - Voltage Meter input - on 100k/10k divider
// 


// Load the libraries
//#include <SPI.h>          // Not used for now
#include <Wire.h>
//#include <Adafruit_GFX.h>
#include <Stepper.h>
#include <Adafruit_SSD1306.h>

// 2020-01-01 - Comment out to use basic scaled fonts
// Not as pretty but saves memory and may be needed for some combos
#define useFonts
#ifdef useFonts
  #include <Fonts/FreeSans9pt7b.h>
  #include <Fonts/FreeSans12pt7b.h>
#endif

#include <Adafruit_SoftServo.h>
// Note: To get full 180 degree movement on the servo I have modified the Adafruit_Softservo.ccp file as follows:
// micros = map(a, 0, 180, 500, 2500)
// The old 1000, 2000 mapping provides 90 degree movement only

// setup OLED - 128 x 32 is what we have here
#define OLED_RESET 4
Adafruit_SSD1306 display(OLED_RESET);

// Setup SoftServo for servo work
Adafruit_SoftServo myServo;  // 2019-12-28 - Using this as Servo kills PWM on 9 and 10 on Arduino

//#if (SSD1306_LCDHEIGHT != 32)
//#error("Height incorrect, please fix Adafruit_SSD1306.h!");
//#endif

// Setup Images - Most were converted via https://diyusthad.com/image2cpp
const unsigned char PROGMEM nanoImg [] { 
0x00, 0x00, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00,
0x30, 0x0e, 0x00, 0x00, 0x30, 0x0e, 0x00, 0x00, 0x30, 0x0e, 0x00, 0x00, 0x30, 0x0e, 0x00, 0x00,
0x3f, 0xfe, 0x38, 0x0e, 0x3f, 0xfe, 0x38, 0x0e, 0x3f, 0xfe, 0x38, 0x0e, 0x3f, 0xfe, 0x38, 0x0e,
0x3f, 0xfe, 0x38, 0x0e, 0x3f, 0xfe, 0x10, 0x04, 0x38, 0x0e, 0x10, 0x04, 0x3b, 0xee, 0x10, 0x04,
0x3b, 0xee, 0x10, 0x04, 0x33, 0xe6, 0x18, 0x0c, 0x03, 0xe0, 0x1f, 0xfc, 0x03, 0xe0, 0x01, 0xc0,
0x00, 0x80, 0x01, 0xc0, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80,
0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0xff, 0xff, 0x80, 0x00, 0x7f, 0xff, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

// 'datalog', 32x32px
const unsigned char PROGMEM dataImg [] {
0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x0f, 0x03, 0xc0, 0x00,
0x1c, 0x00, 0xe0, 0x00, 0x30, 0xfc, 0x30, 0x00, 0x63, 0xff, 0x18, 0x00, 0x07, 0x03, 0x80, 0x00,
0x0c, 0x00, 0xc3, 0x00, 0x00, 0xfc, 0x47, 0x80, 0x01, 0xce, 0x0f, 0xc0, 0x01, 0x82, 0x1e, 0xe0,
0x00, 0x30, 0x7c, 0x70, 0x00, 0x78, 0xfc, 0xf8, 0x00, 0x79, 0xff, 0xfc, 0x00, 0x33, 0xff, 0xce,
0x00, 0x07, 0xff, 0x8e, 0x00, 0x0f, 0xff, 0xfc, 0x00, 0x1f, 0xff, 0xf8, 0x00, 0x3f, 0xff, 0xf0,
0x00, 0x3f, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xe0, 0x00, 0x3f, 0xff, 0xe0, 0x00, 0x1f, 0xff, 0xc0,
0x00, 0x3f, 0xff, 0x80, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x1f, 0xfe, 0x00, 0x00, 0x3f, 0xfc, 0x00,
0x00, 0x77, 0xf8, 0x00, 0x00, 0xe2, 0x70, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};


const unsigned char PROGMEM pwmImg [] {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00, 0x0f, 0xfe, 0x00, 0x00,
0x0e, 0x0e, 0x00, 0x00, 0x0e, 0x0e, 0x00, 0x00, 0x0e, 0x0e, 0x00, 0x00, 0x0e, 0x0e, 0x00, 0x00,
0x0e, 0x0e, 0x00, 0x00, 0x0e, 0x0e, 0x00, 0x00, 0x0e, 0x0e, 0x00, 0x00, 0x0e, 0x0e, 0x00, 0x00,
0x00, 0x0e, 0x00, 0x70, 0x00, 0x0e, 0x00, 0x70, 0x00, 0x0e, 0x00, 0x70, 0x00, 0x0e, 0x00, 0x70,
0x00, 0x0e, 0x00, 0x70, 0x00, 0x0e, 0x00, 0x70, 0x00, 0x0e, 0x00, 0x70, 0x00, 0x0e, 0x00, 0x70,
0x00, 0x0f, 0xff, 0xf0, 0x00, 0x0f, 0xff, 0xf0, 0x00, 0x0f, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

// Clock image - not used for now
const unsigned char PROGMEM clockImg [] = {
0x0F, 0x80, 0x01, 0xF0, 0x39, 0xC0, 0x03, 0x9C, 0x61, 0x80, 0x01, 0x86, 0x43, 0x1F, 0xF8, 0xC2,
0xC6, 0x70, 0x0E, 0x63, 0x8D, 0xC0, 0x03, 0xB1, 0x9B, 0x00, 0x00, 0xD9, 0xF6, 0x01, 0x80, 0x6F,
0xEC, 0x01, 0x80, 0x37, 0x58, 0x01, 0x80, 0x1A, 0x10, 0x01, 0x80, 0x08, 0x30, 0x01, 0x80, 0x0C,
0x20, 0x01, 0x80, 0x04, 0x20, 0x01, 0x80, 0x04, 0x20, 0x01, 0x80, 0x04, 0x60, 0x01, 0x80, 0x06,
0x60, 0x01, 0x80, 0x06, 0x60, 0x7F, 0x80, 0x06, 0x60, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x06,
0x20, 0x00, 0x00, 0x04, 0x20, 0x00, 0x00, 0x04, 0x30, 0x00, 0x00, 0x0C, 0x10, 0x00, 0x00, 0x08,
0x18, 0x00, 0x00, 0x18, 0x08, 0x00, 0x00, 0x10, 0x0C, 0x00, 0x00, 0x30, 0x06, 0x00, 0x00, 0x60,
0x0F, 0x80, 0x01, 0xF0, 0x18, 0xE0, 0x07, 0x18, 0x30, 0x7C, 0x3E, 0x0C, 0x20, 0x0F, 0xF0, 0x04
};

const unsigned char PROGMEM batteryImg [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xF0, 0x40, 0x00, 0x00, 0x10, 0x47, 0x3C, 0xE0, 0x18,
0x47, 0xBD, 0xE0, 0x18, 0x45, 0xA5, 0xA0, 0x1E, 0x45, 0xA5, 0xA0, 0x1A, 0x45, 0xA5, 0xA0, 0x1A,
0x45, 0xA5, 0xA0, 0x1A, 0x45, 0xA5, 0xA0, 0x1A, 0x45, 0xA5, 0xA0, 0x1E, 0x47, 0xBD, 0xE0, 0x18,
0x47, 0x3C, 0xE0, 0x18, 0x40, 0x00, 0x00, 0x10, 0x3F, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

//const unsigned char PROGMEM battery2Img [] {
//0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x07, 0xff, 0xff, 0xf3, 0x81, 0xff, 0x80, 0x0c, 0x80, 0x3f,
//0x80, 0x0c, 0xe2, 0x1f, 0x80, 0x0c, 0x7d, 0x0f, 0x80, 0x1e, 0x39, 0x0f, 0x80, 0x1f, 0x81, 0x8f,
//0x80, 0x1f, 0xff, 0x0f, 0x80, 0x3f, 0xff, 0x0f, 0x80, 0x1f, 0xff, 0x0f, 0x80, 0x4f, 0xff, 0x0f,
//0x80, 0x07, 0xfe, 0x0f, 0x80, 0x81, 0xfe, 0x0f, 0x80, 0x80, 0x00, 0x0f, 0x80, 0x00, 0x04, 0x0f,
//0x81, 0x08, 0x04, 0x0f, 0x81, 0x03, 0x08, 0x0f, 0x80, 0x00, 0x08, 0x0f, 0x83, 0x00, 0x00, 0x0f,
//0x82, 0x00, 0x10, 0x0f, 0x80, 0x40, 0x10, 0x0f, 0x84, 0x10, 0x00, 0x0f, 0x84, 0x07, 0xe0, 0x0f,
//0x84, 0x00, 0x20, 0x0f, 0x84, 0x00, 0x00, 0x0f, 0xc6, 0x00, 0x40, 0x0f, 0xc3, 0x00, 0x40, 0x0f,
//0xf1, 0xe0, 0x80, 0x0f, 0xfe, 0x3f, 0x7f, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
//};

// Not used for now
//const unsigned char PROGMEM temperatureImg [] = {
//0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x04, 0x20, 0x00,
//0x00, 0x04, 0x20, 0x00, 0x00, 0x04, 0x20, 0x00, 0x00, 0x04, 0x20, 0x00, 0x00, 0x04, 0x20, 0x00,
//0x00, 0x04, 0x20, 0x00, 0x00, 0x04, 0x20, 0x00, 0x00, 0x04, 0x20, 0x00, 0x00, 0x04, 0x20, 0x00,
//0x00, 0x04, 0x20, 0x00, 0x00, 0x05, 0xA0, 0x00, 0x00, 0x05, 0xA0, 0x00, 0x00, 0x05, 0xA0, 0x00,
//0x00, 0x05, 0xA0, 0x00, 0x00, 0x05, 0xA0, 0x00, 0x00, 0x05, 0xA0, 0x00, 0x00, 0x0D, 0xB0, 0x00,
//0x00, 0x19, 0x98, 0x00, 0x00, 0x33, 0xCC, 0x00, 0x00, 0x26, 0x64, 0x00, 0x00, 0x6C, 0x36, 0x00,
//0x00, 0x68, 0x16, 0x00, 0x00, 0x68, 0x16, 0x00, 0x00, 0x2C, 0x34, 0x00, 0x00, 0x27, 0xE4, 0x00,
//0x00, 0x33, 0xCC, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x0F, 0xF0, 0x00, 0x00, 0x01, 0x80, 0x00
//};

const unsigned char PROGMEM continuityImg [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1F, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0xC0, 0x0F, 0xFF, 0xFF, 0xF0,
0x1F, 0xC0, 0x03, 0xF8, 0x7F, 0x00, 0x00, 0xFE, 0xFC, 0x00, 0x00, 0x3F, 0xF8, 0x07, 0xE0, 0x1F,
0xF0, 0x3F, 0xFC, 0x0F, 0x00, 0xFF, 0xFF, 0x00, 0x01, 0xFF, 0xFF, 0x80, 0x03, 0xF0, 0x0F, 0xC0,
0x07, 0xC0, 0x03, 0xE0, 0x03, 0x80, 0x01, 0xC0, 0x01, 0x0F, 0xF0, 0x80, 0x00, 0x1F, 0xF8, 0x00,
0x00, 0x3F, 0xFC, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x38, 0x1C, 0x00, 0x00, 0x01, 0x80, 0x00,
0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

const unsigned char PROGMEM resistorImg [] = {
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x04, 0x00, 0x00, 0x0F, 0x88,
0x00, 0x00, 0x18, 0xD0, 0x00, 0x00, 0x38, 0x60, 0x00, 0x00, 0x6C, 0x30, 0x00, 0x00, 0xC6, 0x18,
0x00, 0x00, 0x83, 0x08, 0x00, 0x01, 0x81, 0x8C, 0x00, 0x03, 0x00, 0xCC, 0x00, 0x07, 0x80, 0x78,
0x00, 0x0C, 0xC0, 0x30, 0x00, 0x18, 0x60, 0x60, 0x00, 0x38, 0x30, 0xC0, 0x00, 0x6C, 0x1B, 0x80,
0x01, 0xC6, 0x0E, 0x00, 0x03, 0x03, 0x0C, 0x00, 0x07, 0x01, 0x98, 0x00, 0x0D, 0x80, 0xF0, 0x00,
0x18, 0xC0, 0x60, 0x00, 0x30, 0x60, 0xC0, 0x00, 0x30, 0x31, 0x80, 0x00, 0x10, 0x19, 0x00, 0x00,
0x18, 0x0F, 0x00, 0x00, 0x0C, 0x06, 0x00, 0x00, 0x06, 0x0C, 0x00, 0x00, 0x0B, 0x18, 0x00, 0x00,
0x11, 0xF0, 0x00, 0x00, 0x20, 0x60, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00
};

const unsigned char PROGMEM diodeImg [] = {
0x00, 0x07, 0xE0, 0x00, 0x00, 0x1F, 0xF8, 0x00, 0x00, 0x3F, 0xFC, 0x00, 0x00, 0x78, 0x1E, 0x00,
0x00, 0xE0, 0x07, 0x00, 0x00, 0xE0, 0x07, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x01, 0xC0, 0x03, 0x80,
0x01, 0xC0, 0x03, 0x80, 0x01, 0xC0, 0x03, 0x80, 0x01, 0xC0, 0x03, 0x80, 0x01, 0xC0, 0x03, 0x80,
0x01, 0xC0, 0x03, 0x80, 0x01, 0xC0, 0x03, 0x80, 0x07, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xE0,
0x07, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x1C, 0x38, 0x00, 0x00, 0x1C, 0x38, 0x00, 0x00, 0x1C, 0x38, 0x00, 0x00, 0x1C, 0x38, 0x00,
0x00, 0x1C, 0x38, 0x00, 0x00, 0x1C, 0x38, 0x00, 0x00, 0x1C, 0x38, 0x00, 0x00, 0x1C, 0x38, 0x00,
0x00, 0x1C, 0x38, 0x00, 0x00, 0x1C, 0x38, 0x00, 0x00, 0x1C, 0x38, 0x00, 0x00, 0x1C, 0x38, 0x00
};

// 'electric-rc-model-servo-image_csp34733171', 32x25px
const unsigned char PROGMEM servoImg [] = {
0x00, 0x00, 0x7c, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x38, 0x00,
0x00, 0x07, 0xff, 0x80, 0x00, 0x0f, 0xff, 0x80, 0x00, 0x0f, 0xff, 0x80, 0x0f, 0xff, 0xff, 0x80,
0x0f, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xfc,
0x07, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0x80,
0x0f, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0x80,
0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0x80, 0x0f, 0xff, 0xff, 0x80,
0x07, 0xff, 0xff, 0x80
};

const unsigned char PROGMEM voltImg [] = {
0x1f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xf8, 0x18, 0x00, 0x00, 0x18,
0x18, 0x00, 0x00, 0x18, 0x18, 0x0f, 0xf0, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x60, 0x06, 0x18,
0x18, 0x40, 0x02, 0x18, 0x18, 0xc1, 0x81, 0x18, 0x18, 0x81, 0x81, 0x18, 0x19, 0x81, 0x81, 0x98,
0x19, 0x01, 0x80, 0x98, 0x19, 0x03, 0xc0, 0x98, 0x18, 0x07, 0xe0, 0x18, 0x18, 0x0f, 0xf0, 0x18,
0x18, 0x00, 0x00, 0x18, 0x1f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xf8,
0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf0, 0x0f, 0xf8, 0x1f, 0xe0, 0x07, 0xf8, 0x1f, 0xe0, 0x07, 0xf8,
0x1f, 0xe0, 0x07, 0xf8, 0x1f, 0xe0, 0x07, 0xf8, 0x1f, 0xe0, 0x07, 0xf8, 0x1f, 0xf0, 0x0f, 0xf8,
0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xfe, 0x7f, 0xf8, 0x1f, 0xff, 0xff, 0xf8, 0x1f, 0xff, 0xff, 0xf8,
};

const unsigned char PROGMEM radarImg [] {
0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x70, 0x00, 0x00, 0x7f, 0x7e, 0x00, 0x00, 0xff, 0x7f, 0x00,
0x01, 0xfe, 0x7f, 0xc0, 0x03, 0xe7, 0x67, 0xe0, 0x07, 0x9b, 0x79, 0xf0, 0x0f, 0x35, 0x7e, 0xb0,
0x0f, 0x75, 0x7e, 0x78, 0x1e, 0xfe, 0x7d, 0xb8, 0x1d, 0xf8, 0x5b, 0xbc, 0x3d, 0xf7, 0x67, 0xdc,
0x3b, 0xef, 0x67, 0xdc, 0x3b, 0xff, 0xdb, 0xde, 0x3b, 0xdf, 0x3b, 0xde, 0x00, 0x00, 0x00, 0x00,
0x3b, 0xdf, 0x7b, 0xde, 0x3b, 0xff, 0x7b, 0xde, 0x3b, 0xef, 0x7e, 0x5c, 0x3d, 0xf7, 0x75, 0x1c,
0x3d, 0xfb, 0x4e, 0xbc, 0x1e, 0xe4, 0x3f, 0xb8, 0x1e, 0xd7, 0xff, 0x78, 0x0f, 0x6f, 0x7e, 0xf0,
0x07, 0x9f, 0x7d, 0xf0, 0x07, 0xef, 0x73, 0xe0, 0x03, 0xf8, 0x1f, 0xc0, 0x00, 0xff, 0x7f, 0x80,
0x00, 0x7f, 0x7e, 0x00, 0x00, 0x1f, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

// 'stepper', 32x32px
const unsigned char PROGMEM stepperImg [] {
0x00, 0x03, 0xc0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xc0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x1f, 0xf7, 0xef, 0xf8, 0x1f, 0xe7, 0xe7, 0xf8,
0x3f, 0x87, 0xe1, 0xfc, 0x3f, 0x07, 0xe0, 0xfc, 0x3f, 0x07, 0xe0, 0xfc, 0x3f, 0x01, 0x80, 0xfc,
0x3f, 0x80, 0x01, 0xfc, 0x7f, 0xc0, 0x03, 0xfe, 0x7f, 0xf8, 0x1f, 0xfe, 0x7f, 0xff, 0xff, 0xfe,
0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00,
0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe,
0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xfe,
0x7f, 0xff, 0xff, 0xfe, 0x3f, 0xff, 0xff, 0xfe, 0x01, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0x80,
};

// 'speaker', 32x32px
const unsigned char PROGMEM speakerImg [] {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x80, 0x40, 0x00, 0x03, 0x80, 0xe0, 0x00, 0x07, 0x80, 0x70, 0x00, 0x1f, 0x80, 0x70,
0x00, 0x3f, 0x87, 0x38, 0x00, 0x7f, 0x87, 0x18, 0x00, 0xff, 0x83, 0x9c, 0x01, 0xff, 0x91, 0x8c,
0x3f, 0xff, 0xb9, 0xcc, 0x3f, 0xff, 0x98, 0xcc, 0x3f, 0xff, 0x9c, 0xce, 0x3f, 0xff, 0x8c, 0xce,
0x3f, 0xff, 0x8c, 0xce, 0x3f, 0xff, 0x8c, 0xee, 0x3f, 0xff, 0x8c, 0xce, 0x3f, 0xff, 0x9c, 0xce,
0x3f, 0xff, 0x98, 0xcc, 0x3f, 0xff, 0xb9, 0xcc, 0x01, 0xff, 0xb1, 0x8c, 0x00, 0x7f, 0x83, 0x9c,
0x00, 0x3f, 0x87, 0x18, 0x00, 0x1f, 0x87, 0x38, 0x00, 0x0f, 0x80, 0x70, 0x00, 0x07, 0x80, 0x70,
0x00, 0x03, 0x80, 0xe0, 0x00, 0x01, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

// 'nospeaker', 33x32px
const unsigned char PROGMEM nospeakerImg [] {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x0c,
0x00, 0x80, 0x00, 0x00, 0x06, 0x01, 0x80, 0x60, 0x00, 0x03, 0x03, 0x80, 0x70, 0x00, 0x01, 0x87,
0x80, 0x38, 0x00, 0x00, 0xcf, 0x83, 0x18, 0x00, 0x00, 0x6f, 0x83, 0x9c, 0x00, 0x00, 0x37, 0x81,
0x8c, 0x00, 0x00, 0xdb, 0x89, 0xcc, 0x00, 0x1f, 0xed, 0x9c, 0xce, 0x00, 0x1f, 0xf6, 0x9c, 0xe6,
0x00, 0x1f, 0xfb, 0x0e, 0x66, 0x00, 0x1f, 0xfd, 0x8e, 0x66, 0x00, 0x1f, 0xfe, 0xc6, 0x66, 0x00,
0x1f, 0xff, 0x66, 0x66, 0x00, 0x1f, 0xff, 0xb6, 0x66, 0x00, 0x1f, 0xff, 0x98, 0x66, 0x00, 0x1f,
0xff, 0x8c, 0xe6, 0x00, 0x1f, 0xff, 0x96, 0xce, 0x00, 0x00, 0x7f, 0x83, 0x4c, 0x00, 0x00, 0x3f,
0x81, 0x8c, 0x00, 0x00, 0x1f, 0x82, 0xdc, 0x00, 0x00, 0x0f, 0x83, 0x68, 0x00, 0x00, 0x07, 0x80,
0x30, 0x00, 0x00, 0x03, 0x80, 0x58, 0x00, 0x00, 0x00, 0x80, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

// 'satImg', 32x32px
const unsigned char PROGMEM satImg [] {
0x00, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x07, 0x00, 0x33, 0x00, 0x3f, 0x80,
0x6f, 0x00, 0x79, 0xc0, 0x6c, 0x01, 0xe0, 0xc0, 0x0d, 0x83, 0x81, 0xe0, 0x01, 0xce, 0x01, 0xe0,
0x00, 0xfc, 0x03, 0xe0, 0x00, 0x78, 0x03, 0x20, 0x00, 0x78, 0x06, 0x30, 0x00, 0xfc, 0x0e, 0x30,
0x01, 0xce, 0x1c, 0x30, 0x03, 0x84, 0x38, 0x60, 0x03, 0x00, 0x70, 0x60, 0x06, 0x00, 0xe0, 0x60,
0x0c, 0x01, 0xc0, 0x60, 0x0c, 0x03, 0x80, 0xc0, 0x18, 0x07, 0x01, 0xc0, 0x18, 0x1e, 0x01, 0x80,
0x18, 0x38, 0x03, 0xc0, 0x19, 0xf0, 0x0e, 0xc0, 0x1f, 0xc0, 0x1c, 0x60, 0x0f, 0x80, 0xf8, 0x60,
0x03, 0xff, 0xe0, 0x30, 0x00, 0xff, 0xe0, 0x30, 0x00, 0x00, 0xc0, 0x18, 0x00, 0x00, 0xc0, 0x1c,
0x00, 0x01, 0x80, 0x0c, 0x00, 0x01, 0xc0, 0x0e, 0x00, 0x03, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00
};

// Init vars
//int   Pause =300;    // Pause after display - change as needed
byte  Pause =300;    // Hard pause delay in some modes
int   Raw   = 0;     // Used to read Raw values
float Vin   = 0;     // Internal Calc
float Vout  = 0;     // External calc
float R1    = 2200;  // Divider for top side of resistor and diode tester - May want to tweak based on your actual reading
float R2    = 0;     // Default for lower side of resistor and diode, gets calc


float Vin2  = 0.00;     // Used for voltage meter calcs
float Vout2 = 0.00;     
                        //float Res1= 100000.00;// resistance of R1 (100K) 
//float Res1  = 105789.00;// Tweaked to actual readin
float Res1 = 100000.00;
                        //float Res2= 10000.00; // resistance of R2 (10K) 
float Res2  = 10000.00;  // Tweaked to actual readin

int   Val = 0;        // Working vars
float buffer= 0;

int maximum=1;        // Min and Max for analog reads defaults
int minimum=1024;

int Mode=0;           // Mode - default to 0 
int maxModes = 11;    // Maximum modes for rollover

unsigned long numberOfTimes=0;
unsigned long sum=0;
int avg=0;

int Pulse=125;         // Default PWM puluse width
int servoPulse = 90;   // Used for Servo testing

byte TRIGGER_PIN = 7;  // SR04 / Sonar trigger pin
byte ECHO_PIN    = 8 ; // SRO4 / Sonar echo return pin
//long mydistance   = 0;
int mydistance = 0;

long dataTimer;           // used for logging data to the serial port, set trigger in mode
long dataTrigger = 1000;  // default to every second

long i2cTimer;    // used for I2C bus scan refreshes - could use the dataTimer I suppose but if set high then slow refresh

byte inCommand;   // serial.read input command for remote control

boolean potMode   = false; // Where to read for PWM and Servo - true = pot, false = buttons, UP/DOWN together to toggle
boolean soundOn   = true;  // Can toggle sound on / off - DOWN then SEL buttons

// Setup stepper motor option on 9,11,10,12
// NOTE!: You will need a separate supply to drive the motor or you may blow the Nano regulator
const int stepsPerRevolution = 2048;  // change this to fit the number of steps per revolution
Stepper myStepper(stepsPerRevolution, 9, 11, 10, 12);
int stepCount = 0;

void setup()   
{  
  // Start up the display
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)

  // Show opening screen
  display.clearDisplay();
      display.drawBitmap(98, 0,  nanoImg, 32, 30, 1);
      display.setTextColor(WHITE);
      display.setCursor(0,0);
//      display.print("Ver:" + curVer); // Saves some bytes for memory like this
      display.print("Ver:2020-01-01");
      #ifdef useFonts
        display.setCursor(0,24);
        display.setFont(&FreeSans9pt7b);
      #else
        display.setCursor(0,14);
        display.setTextSize(2);
      #endif
      display.print("NanOMeter");
  display.display();

  Serial.begin(115200);   // For data logging and remote control
  
  // Input buttons
  pinMode(2,INPUT_PULLUP);
  pinMode(3,INPUT_PULLUP);
  pinMode(4,INPUT_PULLUP);
  
  // speaker and built in LED working together
  pinMode(13,OUTPUT);

  // Attach servo port
  myServo.attach(5);

  // Setup stepper speed
  myStepper.setSpeed(6);

  // Interrrupt routine to read the middle button
  attachInterrupt(1, buttonPress, FALLING);

  delay(3000);
  display.clearDisplay();
  display.display();
  dataTimer = millis();
  i2cTimer = millis();
}

void loop()
{
    // Up AND Select - 
    if ((digitalRead(2)==0) && (digitalRead(3)==0))
    {
      // room to toggle one more item here but no memory
      
      delay(250);
    }
    
    // Select AND Down - Toggle sound on/off - draw speaker icon to show result
    if ((digitalRead(3)==0) && (digitalRead(4)==0))
    {
      soundOn = !soundOn;
      toner(13,800,50);
      display.clearDisplay(); 
        if (soundOn)
        {display.drawBitmap(96, 0,  speakerImg, 32, 32, 1);}
        else
        {display.drawBitmap(96, 0,  nospeakerImg, 33, 32, 1);}        
      display.display();
      delay(1000);
    }
    
    // UP and DOWN - Check / toggle potMode here - not needed in each mode
    if ((digitalRead(2)==0) && (digitalRead(4)==0))
    {
      potMode = !potMode;   // toggle where we are getting information
      toner(13,800,50);
      delay(250);
    }

  // If we have a serial command then read it in
  if (Serial.available() > 0) 
  {
    inCommand = Serial.read(); // read the incoming byte:
    // M just bumps the mode - not overlly useful but how I started
    if (inCommand == 'M')
    {
      Mode++;
      toner(13,2250,50);
      if(Mode > maxModes)
      {
        Mode=0;
      }
    }
    // 0 - 9 goes directly into the requested mode, A jumps to the Adjust Data Log Timer mode
    // Obviously it would be nice to have direct commands such as 5:200 to set PWM to 200 BUT running out of memory for code
    // And I don't want to burn a lot of time optimizing things
    if (inCommand == '0'){toner(13,2250,50);Mode = 0;} // Volt meter
    if (inCommand == '1'){toner(13,2250,50);Mode = 1;} // Analog read mode
    if (inCommand == '2'){toner(13,2250,50);Mode = 2;} // Resistor read mode
    if (inCommand == '3'){toner(13,2250,50);Mode = 3;} // Diode drop mode
    if (inCommand == '4'){toner(13,2250,50);Mode = 4;} // Continuity mode
    if (inCommand == '5'){toner(13,2250,50);Mode = 5;} // PWM mode
    if (inCommand == '6'){toner(13,2250,50);Mode = 6;} // Servo tester mode
    if (inCommand == '7'){toner(13,2250,50);Mode = 7;} // Ping-SR04 mode
    if (inCommand == '8'){toner(13,2250,50);Mode = 8;} // Stepper test mode
    if (inCommand == '9'){toner(13,2250,50);Mode = 9;} // Battery test mode
    if (inCommand == 'A'){toner(13,2250,50);Mode = 10;}// Adjust data logging
    if (inCommand == 'I'){toner(13,2250,50);Mode = 11;}// I2C scan mode
  }

  // Process the Modes - These should really call subs for this but works for me for now
   
  //********************************************//
  // Volt Meter - Uses A7 as input and 100K / 10K voltage divider
  //********************************************//
  //
  if(Mode==0)
  {
    float volt3=readVcc()/1000.0;
    Val = analogRead(A7);
    Vout2 = (Val * volt3) / 1024.0; // see text
    Vin2  = Vout2 / (Res2/(Res1+Res2)); 
    // Trying to compensate for the 1N914 reverse voltage protection diode used on probes
    // This seems to need to compensate for the actual voltage more as higher votages are slightly off
    // Not a big deal for my needs but be aware of that issue
    // You can use your diode voltage drop feature to see what the diode says at the 5v point at least
    if (Vin2 > 0.0)
    {
      //Vin2 += 0.33;
      Vin2 += 0.0;
    }
    display.clearDisplay(); 
      display.drawBitmap(96, 0,  voltImg, 32, 32, 1);
      display.setFont();
      display.setTextSize(1);
      display.setTextColor(WHITE);
      display.setCursor(0,0);
      display.print("Volts:");
      display.setCursor(50,0);
      display.print(volt3);
      display.print("v");
      #ifdef useFonts
        display.setCursor(0,24);
        display.setFont(&FreeSans12pt7b);
      #else
        display.setCursor(0,10);
        display.setTextSize(2);
      #endif      
      display.print(Vin2);
      display.print("v");
    display.display();

    // Send logging data to serial port
    if (getET(dataTimer) > dataTrigger)
    {
      Serial.print("Volts,");
      Serial.print(millis());
      Serial.print(",");
      Serial.println(Vin2);
      dataTimer = millis();
    }
    delay(Pause);
  }

  //********************************************//
  // Analog input - Reads A0 and displays the analog read information along with min,max, and avg
  //********************************************//
  if(Mode==1)
  {
    if((digitalRead(4)==0) || (inCommand == '=')) // button to reset stats or inCommand of =1
    { 
      delay(100); minimum=1024; maximum=0; numberOfTimes=0; sum=0; 
    }
    display.clearDisplay();
      //display.drawBitmap(43, 0,  analogImg, 32, 28, 1);
      display.setFont();
      display.setTextColor(WHITE);
      display.setCursor(0,0);
      display.setTextSize(1);
      display.print("Analog:");
      int value=analogRead(A0);
      numberOfTimes=numberOfTimes+1;
      sum=sum+value;
      avg=sum/numberOfTimes;
      if(value>maximum)
        maximum=value;
      if(value<minimum)
        minimum=value;
      display.setFont();
      int lineWide=map(value,0,1024,0,128);
      display.setCursor(70,0);
        display.print("MIN:");
      display.setCursor(95,0);
        display.print(minimum);
      display.setCursor(70,10);
        display.print("AVG:");
      display.setCursor(95,10);
        display.print(avg);
      display.setCursor(70,20);
        display.print("MAX:");
      display.setCursor(95,20);
        display.print(maximum);
      #ifdef useFonts
        display.setCursor(0,24);
        display.setFont(&FreeSans9pt7b);
      #else
        display.setCursor(0,10);
        display.setTextSize(2);
      #endif
        display.print(value);
//      display.drawLine(0,31,lineWide,31,1);
      lineWide = map(value,0,1024,0,30);
      for (int myLine = 50; myLine < 65; myLine++)
      {
        display.drawLine(myLine,31,myLine,30-lineWide,1);
      }
   display.display();

    // Send logging data to serial port
    if (getET(dataTimer) > dataTrigger)
    {
      Serial.print("Analog,");
      Serial.print(millis());
      Serial.print(",");
      Serial.print(value);
      Serial.print(",");
      Serial.print(minimum);
      Serial.print(",");
      Serial.print(avg);
      Serial.print(",");
      Serial.println(maximum);
      dataTimer = millis();
    }

   // Auto reset average every so often
   if(numberOfTimes>100000)
    {
      numberOfTimes=0;
      sum=0;
    }
  
  }

  //********************************************//
  // Ohm Meter - Reads A2 value and calclates the resistor value
  //********************************************//
  if(Mode==2)
  {
    display.clearDisplay(); 
      display.setFont();
      display.setCursor(0,0);
      display.setTextSize(1);
      display.print("Ohms:");
      display.drawBitmap(96, 0,  resistorImg, 32, 32, 1);
      Raw= analogRead(A2);
      Vin=readVcc()/1000.0;
      buffer= Raw * Vin;
      Vout= (buffer)/1024.0;
      buffer= (Vin/Vout) -1;
      R2= R1 * buffer;
      #ifdef useFonts
        display.setCursor(0,24);
        display.setFont(&FreeSans12pt7b);
      #else
        display.setCursor(0,10);
        display.setTextSize(2);
      #endif
      if(R2<700000)
        display.print(R2);
      if(R2>700000)
        display.print("Empty");
    display.display();
    delay(Pause);

    // Send logging data to serial port
    if (getET(dataTimer) > dataTrigger)
    {
      Serial.print("Ohms,");
      Serial.print(millis());
      Serial.print(",");
      Serial.println(R2);
      dataTimer = millis();
    }
    
  }

  //********************************************//
  // Diode Voltage Drop - Read the A2 input and calcs the voltage drop across an diode or LED
  //********************************************//
  if(Mode==3)
  {
    display.clearDisplay();
      display.drawBitmap(96, 0,  diodeImg, 32, 32, 1);
      display.setFont();
      display.setCursor(0,0);
      display.setTextSize(1);
      display.print("Voltage Drop:");
      Raw= analogRead(A2);
      Vin=readVcc()/1000.0;
      buffer= Raw * Vin;
      Vout= (buffer)/1024.0;
      #ifdef useFonts
        display.setCursor(0,24);
        display.setFont(&FreeSans12pt7b);
      #else
        display.setCursor(0,10);
        display.setTextSize(2);
      #endif

      if(Vout==0)
      {
          display.print("Empty/0");
      }
      else
      {
         display.print(Vin-Vout);
      }
    display.display();

    // Send logging data to serial port
    if (getET(dataTimer) > dataTrigger)
    {
      Serial.print("VoltDrop,");
      Serial.print(millis());
      Serial.print(",");
      Serial.println(Vout);
      dataTimer = millis();
    }

    delay(Pause);
  }

  //********************************************//
  // Continuity Testor - Reads A2 and uses resistance < 1 to determine continuity
  //********************************************//
  if(Mode==4)
  {
    display.clearDisplay();
      Raw= analogRead(A2);
      Vin=readVcc()/1000.0;
      buffer= Raw * Vin;
      Vout= (buffer)/1024.0;
      float continuity=Vin-Vout;
      display.drawBitmap(96, 0,  continuityImg, 32, 32, 1);
      display.setFont();
      display.setCursor(0,0);
      display.setTextSize(1);
      display.print("Continuity: ");
      display.print(Vout);
      #ifdef useFonts
        display.setCursor(0,24);
        display.setFont(&FreeSans12pt7b);
      #else
        display.setCursor(0,10);
        display.setTextSize(2);
      #endif

      if(continuity<1)
      {
        toner(13,2250,65000);
        display.print("Yes");
      }
     if(continuity>1)
     {
        noTone(13);
        display.print("None ");        
     }
   display.display();

   // Send logging data to serial port
   if (getET(dataTimer) > dataTrigger)
    {
      Serial.print("Continuity,");
      Serial.print(millis());
      Serial.print(",");
      Serial.println(continuity);
      dataTimer = millis();
    }

 }

  //********************************************//
  // PWM output - PWM output for driving things - this is NOT servo BUT will kind of work with servo
  //********************************************//
  if(Mode==5)
  {
    if ((digitalRead(2)==0) || (inCommand == '+'))
    { 
      if(Pulse<255)
      {
        toner(13,1800,10);
        Pulse += 1; 
      } 
    }

    if((digitalRead(4)==0) || (inCommand == '-'))
    { 
      if(Pulse>1)
      {
        toner(13,1400,10);
        Pulse -= 1; 
      } 
    }
    // Recenter on = command
    if(inCommand == '=')
    {
      Pulse=127;
    }
    // If we have a pot on the analog input, use it to control the servo
    if (potMode)
    {
      Val = analogRead(A0);
      Pulse = map(Val,0,1023,0,255);
    }
 
    int lineWide2=0;
    analogWrite(6,Pulse);
    display.clearDisplay();
      //display.drawBitmap(72, 1,  pwmImg, 58, 30, 1);
      display.drawBitmap(96, 1,  pwmImg, 32, 32, 1);
      display.setFont();
      display.setCursor(0,0);
      display.setTextSize(1);
      display.print("Pulse Width:");
      if (potMode)
      {
        display.print("(Pot)");
      }
      #ifdef useFonts
        display.setCursor(0,24);
        display.setFont(&FreeSans12pt7b);
      #else
        display.setCursor(0,10);
        display.setTextSize(2);
      #endif

      display.print(Pulse);
      lineWide2= map(Pulse,0,255,0,128);
      display.drawLine(0,31,lineWide2,31,1);
   display.display();

   // Send logging data to serial port
   if (getET(dataTimer) > dataTrigger)
    {
      Serial.print("PWM,");      
      Serial.print(millis());
      Serial.print(",");
      Serial.println(Pulse);
      dataTimer = millis();
    }

  }   

  //********************************************//
  // Servo Tester - Software Servo output on pin 5 - drives servo header
  //********************************************//
  if(Mode==6)
  {
    if((digitalRead(2)==0) || (inCommand == '+'))
    { 
      if(servoPulse<180)
      {
        //toner(13,1800,10);
        servoPulse += 1; 
      } 
    }

    if((digitalRead(4)==0) || (inCommand == '-'))
    { 
      if(servoPulse>1)
      {
        //toner(13,1400,10);
        servoPulse -= 1; 
      } 
    }
    // Recenter on = command
    if(inCommand == '=')
    {
      servoPulse=90;
    }

    // If we have a pot on the analog input, use it to control the servo
    if (potMode)
    {
      Val = analogRead(A0);
      servoPulse = map(Val,0,1023,0,180);
    }

    int lineWide2=0;
    myServo.write(servoPulse);
    
    display.clearDisplay();
      display.drawBitmap(96, 1,  servoImg, 32, 25, 1);
      //display.drawBitmap(72, 1,  pwmImg, 58, 30, 1);
      display.setFont();
      display.setCursor(0,0);
      display.setTextSize(1);
      display.print("Servo:");
      if (potMode)
      {
        display.print("(Pot)");
      }
      #ifdef useFonts
        display.setCursor(0,24);
        display.setFont(&FreeSans12pt7b);
      #else
        display.setCursor(0,10);
        display.setTextSize(2);
      #endif
      
      display.print(servoPulse);
      lineWide2= map(servoPulse,0,180,0,128);
      display.drawLine(0,31,lineWide2,31,1);
   display.display();
   myServo.refresh();
   
   // Send logging data to serial port

   if (getET(dataTimer) > dataTrigger)
   {
      Serial.print("Servo,");
      Serial.print(millis());
      Serial.print(",");
      Serial.println(servoPulse);
      dataTimer = millis();
   }
 }   

  //********************************************//
  // SR 04 ping tester
  //********************************************//
  if(Mode==7)
  {
    display.clearDisplay();
      display.drawBitmap(96, 0,  radarImg, 32, 32, 1);
      display.setFont();
      display.setCursor(0,0);
      display.setTextSize(1);
      display.print("Ping Distance:");
      #ifdef useFonts
        display.setCursor(0,24);
        display.setFont(&FreeSans12pt7b);
      #else
        display.setCursor(0,10);
        display.setTextSize(2);
      #endif

      mydistance = ReadSonar();
      display.print(mydistance);
      display.print(" cm");
    display.display();
    delay(Pause);

    // Send logging data to serial port
    if (getET(dataTimer) > dataTrigger)
    {
      Serial.print("Sonar,");
      Serial.print(millis());
      Serial.print(",");
      Serial.println(mydistance);
      dataTimer = millis();
    }

  }

  //********************************************//
  // Stepper - Stepper on pins 9,10,11,12 - Upper right side of Nano
  //********************************************//
  if(Mode==8)
  {
    if((digitalRead(2)==0) || (inCommand == '+'))
    { 
      myStepper.step(1);
      stepCount += 1;
    }

    if((digitalRead(4)==0) || (inCommand == '-'))
    { 
      myStepper.step(-1);
      stepCount -= 1;
    }

    int lineWide2=0;

    display.clearDisplay();
      display.drawBitmap(96, 1,  stepperImg, 32, 32, 1);
      display.setFont();
      display.setCursor(0,0);
      display.setTextSize(1);
      display.print("Stepper:");
      #ifdef useFonts
        display.setCursor(0,24);
        display.setFont(&FreeSans12pt7b);
      #else
        display.setCursor(0,10);
        display.setTextSize(2);
      #endif

      display.print(stepCount);
      lineWide2= map(stepCount,-2048,2048,0,128);
      display.drawLine(0,31,lineWide2,31,1);
   display.display();
   
   // Send logging data to serial port
   if (getET(dataTimer) > dataTrigger)
   {
      Serial.print("Stepper,");
      Serial.print(millis());
      Serial.print(",");
      Serial.println(stepCount);
      dataTimer = millis();
   }
 }   


  //********************************************//
  // Internal Battery Vcc - Reads internal voltage somehow and displays
  // This cannot be the actual BATTERY voltage - must be internal based on other input?
  //********************************************//
//  if(Mode==8)
//  {
//    
//    display.clearDisplay();
//      display.drawBitmap(96, 0,  batteryImg, 32, 32, 1);
//      display.setFont();
//      display.setCursor(0,0);
//      display.setTextSize(1);
//      display.print("Battery:");
//      #ifdef useFonts
//      display.setCursor(0,24);
//        display.setFont(&FreeSans12pt7b);
//      #else
//        display.setCursor(0,10);
//        display.setTextSize(2);
//      #endif
//
//      float volt2=readVcc()/1000.0;
//      display.print(volt2);
//      display.setCursor(55,28);
//      display.print("V");
//    display.display();
//    delay(Pause);

//    // Send logging data to serial port
//    if (getET(dataTimer) > dataTrigger)
//    {
//      Serial.print("IntBatt");
//      Serial.print(",");
//      Serial.print(millis());
//      Serial.print(",");
//      Serial.println(volt2);
//      dataTimer = millis();
//    }

//  }

  //********************************************//
  // External Battery Vcc - Reads internal voltage somehow and displays
  //********************************************//
  if(Mode==9)
  {
    float volt3=readVcc()/1000.0;
    Val = analogRead(A1);
    Vout2 = (Val * volt3) / 1024.0; // see text
    Vin2  = Vout2 / (Res2/(Res1+Res2)); 
    //Vin2 += 0.64;            // Drop for protection diode
    //Vin2 -= 0.10;             // resting value no connection
    if (Vin2 > 0.9)
    {
      Vin2 += 0.33;
    }
    display.clearDisplay(); 
      display.drawBitmap(96, 0,  batteryImg, 32, 32, 1);
      display.setFont();
      display.setTextColor(WHITE);
      display.setCursor(0,0);
      display.setTextSize(1);
      display.print("Ext Battery:");
      #ifdef useFonts
        display.setCursor(0,24);
        display.setFont(&FreeSans12pt7b);
      #else
        display.setCursor(0,10);
        display.setTextSize(2);
      #endif
      display.print(Vin2);
      display.print("v");
    display.display();
    delay(Pause);

    // Send logging data to serial port
    if (getET(dataTimer) > dataTrigger)
    {
      Serial.print("ExtBatt,");
      Serial.print(millis());
      Serial.print(",");
      Serial.println(Vin2);
      dataTimer = millis();
    }
  }

  //********************************************//
  // Adjust data dump timer
  //********************************************//
  if(Mode==10)
  {
    // If we have a pot on the analog input, use it to control the servo
    if (potMode)
    {
      Val = analogRead(A0);
      Pulse = map(Val,0,1023,0,255);
    }

    if((digitalRead(2)==0) || (inCommand == '+'))
    { 
      if(dataTrigger<60000)   // Max one minute
      {
        toner(13,1300,10);
        dataTrigger += 10; 
      } 
    }

    if((digitalRead(4)==0) || (inCommand == '-'))
    { 
      if(dataTrigger>500)   // Min 1/2 second
      {
        toner(13,1400,10);        
        dataTrigger -= 10;
      } 
    }

    // If we have a pot on the analog input, use it to control the servo
    if (potMode)
    {
      Val = analogRead(A0);
      dataTrigger = map(Val,0,1000,500,60000);
      dataTrigger = constrain(dataTrigger,500,60000);
    }

    if (inCommand == '='){dataTrigger = 1000;}
    
    int lineWide2=0;
    display.clearDisplay();
      display.drawBitmap(96, 1,  dataImg, 32, 32, 1);
      display.setFont();
      display.setCursor(0,0);
      display.setTextSize(1);
      display.print("Data Log:");
      if (potMode)
      {
        display.print("(Pot)");
      }
      #ifdef useFonts
        display.setCursor(0,24);
        display.setFont(&FreeSans12pt7b);
      #else
        display.setCursor(0,10);
        display.setTextSize(2);
      #endif
      display.print(dataTrigger);
      display.print(" mS");
      lineWide2= map(dataTrigger,500,5000,0,128);
      display.drawLine(0,31,lineWide2,31,1);
   display.display();
  }

  //********************************************//
  // I2C Bus Scan
  //********************************************//
  if(Mode==11)
  {
    if (getET(i2cTimer) > 2000) // Just scan every two seconds, not every loop
    {
      i2cScan();
      i2cTimer = millis();
    }
  }
 
}
//********************************************//
// END OF MAIN LOOP
//********************************************//

// Interrupt routine to read the middle button and change Modes
void buttonPress()
{
  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();
  // If interrupts come faster than 200ms, assume it's a bounce and ignore
  if (interrupt_time - last_interrupt_time > 220) 
  {
    Mode++;
    toner(13,2250,50);
    if(Mode > maxModes)
    Mode=0;
  }
  last_interrupt_time = interrupt_time;
}


long readVcc() 
{
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
  #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = _BV(MUX3) | _BV(MUX2);
  #else
    ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #endif  

  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both

  long result = (high<<8) | low;

  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts
}

long ReadSonar()
{
  long duration, distance;
  pinMode(TRIGGER_PIN, OUTPUT);
  digitalWrite(TRIGGER_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIGGER_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIGGER_PIN, LOW);
  pinMode(ECHO_PIN, INPUT);
  duration = pulseIn(ECHO_PIN, HIGH);
  distance = duration / 58.2;
 
  return distance;

}

// ** returns the ET in millis for timer variable passed
long getET(long mytimer)
{
  long result;
  result = (millis() - mytimer);
  return result;
}

// Regular old I2C scanning routine and display
void i2cScan()
{
  byte error;
  int address;
  int nDevices;

 display.clearDisplay();
   display.drawBitmap(96, 1,  satImg, 32, 32, 1);
   display.setFont();
   display.setCursor(0,0);
   display.setTextSize(1);
   display.print("I2C Scan:");
   display.setCursor(0,12);

   Serial.print("I2C,");
   Serial.print(millis());

    nDevices = 0;
    for(address = 1; address < 127; address++ )
    {
      Wire.beginTransmission(address);
      error = Wire.endTransmission();

      if (error == 0)
      {
        if (address<16)
        {
          Serial.print(",0");
          display.print("0");
        }
        else
        {
          Serial.print(",");
        }
        Serial.print(address,HEX);
        display.print(address,HEX);
        display.print(" ");
        nDevices++;
        if (nDevices == 5)      // 
        {
          display.println();
        }
      }
  }
  display.display();
  Serial.println();
}

// tone() replacement to allow muting sound via soundOn
void toner(byte mypin, int mysound, int mylen)
{
  if (soundOn)
  {
    tone(mypin, mysound, mylen);
  }
}

//****************************************************************************//
// End of the world as we know it..†„ÿ°

Deployed Luftdaten / OpenSense Map Sensor

On 08/11/2019 I watched the famous (infamous?) Frenck (http://www.youtube.com/frenck) live stream his work on a SDS011 Particulate Matter Sensor. I had already noticed the sensor in the ESPHome support list but Frenck was taking a different approach. He was live streaming getting the device working for the Luftdaten.info project of tracking air quality.
Read More Here

Welcome to Protowrxs . com

Jack of All Trades – Master of None

Pretty much sums up my life and I have no apologies about it. I’ve enjoyed many different hobbies, have become good enough at most, and hope to keep on keeping on for some more years or decades before I’m done.

Here you’ll find information from 1/24 scale plastic models to full scale cars being built. From old bicycles to old bicycles with 1 kilo watt of electric power. From simple electronics to a smart home that has been “Online since 1999” – long before many were thinking of ‘connected’ homes.

I have built many little robots, big RC mowers, have a couple Trail 70’s being restored or rebuilt. I have a smart home that knows when I’m here, can listen in different apps and handle things when where are not here.

I have way too many 3D printers that have all been built, designed, or modified by myself, and even some old time stop motion animated “brickfilms” for you to enjoy.

Poke around – you’ll find many different directions, unfinished projects, and a mess of other ideas floating around here.