/*
ammeter.ino self-contained ammeter 50 ua to 5A LCD display, 50 mv drop
Works with bipolar DC and AC. Version with added transmissions.
John Saunders 12/21/2023
Power consumption:
Li Battery normal running 18 MA. = 6 days
Li Battery En to ground 75 uA - 4 years
AA battery normal operation 6 mA - 10 days
AA battery, En to ground 1 MA - 2 months - use auto switch
Needs a capacitor to ground from En to start.
*/
#include <Wire.h>
#include <hd44780.h> // main hd44780 header
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header
hd44780_I2Cexp lcd(0x26 );
#define SW_PORT A5 //Wiper of 12-position switch and 1o resistor ladder
#define BATT_PORT A9 //Half battery voltage
#define AA_PORT A2
#define OUT_PORT A0
#define SIGN_PORT A1
#define loopMax 30
#define acRMS 1.11
The value of acRMS adjusts the measured value of AC currents from the measured mean to the customary RMS. It is the fixed vale for a sine wave,
const int LCD_COLS = 8;
const int LCD_ROWS = 2;
const int STEP = 102; //For measuring range switch position
const int INIT_COUNT = 52;
const float vref = 3.294;
const float gain = 55.5;
const float mult = 97.66 * vref / gain; // 5000/(.05 * 1024), 15000 is 3 x that
struct range_t {
float errVal; //Set to correct for shunt resistance errors
int multVal; //1 or 3
int divVal;
char units;
};
range_t range[10] = {
// 0 1 2 3 4
{.998, 1, 100, 'u'}, {1.011, 3, 100, 'u'}, {.964, 1, 10, 'u'}, {1.044, 3, 10000, 'M'}, {1.218, 1, 1000, 'M'},
// 5 6 7 8 9
{.993, 3, 1000, 'M'}, {1.206, 1, 100, 'M'}, {1.069, 3, 100, 'M'}, {.938, 1, 10, 'M'}, {.369, 3, 1000, 'M'},
};
const int overloadLimit[3] = {1000, 643, 1005};
The value of vref was measured. It is critical for all of the measurements. I may be checked by comparing the voltage of the lithium battery at its charging sockets to the value displayed.
the values of range[] resulted from DC measurements compared to a 4-digit ammeter to get agreement within 1%.
The overall gain calculated from resistor values is 59, but 55.5 was used to cluster the range values around 1.
5000 or 15000 are the nominal full-scale values, but the displayed values are divided by divVal to get engineering units.
void txBatt(void) {
float battVolt = (analogRead(BATT_PORT) * vref) / 512;
float AAVolt = vref * analogRead(AA_PORT) / 512 - vref;
Serial.print("B,");
Serial.print(battVolt, 2);
Serial.print("V,");
Serial.print(AAVolt, 2);
Serial.println('V');
}
void txCurr(int sw, int sign, int rdn) {
Serial.print(sw);
Serial.print(',');
Serial.print(signChar[sign]);
Serial.print(rdn / range[sw].divVal);
Serial.print('.');
Serial.print(rdn % range[sw].divVal);
Serial.print(range[sw].units);
Serial.print('A');
if (sign == 1) {
Serial.print(" AC");
}
Serial.println(" ");
}
const char signChar[3] = {'+', '~', '-'};
int getSwitchPos(void) {
int retVal = 0;
int threshold = INIT_COUNT;
while (threshold < analogRead(SW_PORT)) {
threshold += STEP;
retVal++;
}
return retVal;
}
int getSign(void) {
int retval = '1';
if (analogRead(SIGN_PORT) > 115) {
retval = 2;
}
if (analogRead(SIGN_PORT) < 34) {
retval = 0;
}
return retval;
}
void dispBatt(void) {
float battVolt = (analogRead(BATT_PORT) * vref) / 512;
float AAVolt = vref * analogRead(AA_PORT) / 512 - vref;
lcd.print(battVolt, 2);
lcd.print(" V ,");
lcd.setCursor(0, 1);
lcd.print(' ');
lcd.print(AAVolt, 2);
lcd.print(" V ");
}
void dispCurr(int sw, int sign, int rdn) {
lcd.print(signChar[sign]);
lcd.print(' ');
lcd.print(rdn / range[sw].divVal);
lcd.print('.');
lcd.print(rdn % range[sw].divVal);
lcd.setCursor((range[sw].multVal - 1), 1);
lcd.print(range[sw].units);
lcd.print('A');
if (sign == 1) {
lcd.print(" AC ");
}
else {
lcd.print(" ");
}
}
void setup() {
int status;
int i;
pinMode(11, OUTPUT);
digitalWrite(11, HIGH);
Serial.begin(9600);
/*
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
*/
status = lcd.begin(LCD_COLS, LCD_ROWS);
if (status) // non zero status means it was unsuccesful
{
// hd44780 has a fatalError() routine that blinks an led if possible
// begin() failed so blink error code using the onboard LED if possible
//Serial.println("LCD init failure");
hd44780::fatalError(status); // does not return
}
digitalWrite(11, LOW);
delay(40);
digitalWrite(11, HIGH);
lcd.home();
lcd.clear();
lcd.print("Made in ");
lcd.setCursor(0, 1);
lcd.print("2023 by ");
delay(2000);
lcd.clear();
lcd.print("John Sau");
lcd.setCursor(0, 1);
lcd.print("nders 89");
delay(2000);
Serial.println(mult);
}
void loop() {
int sign = getSign();
int switchPos = getSwitchPos();
static int prevSwPos = 0;
static int loopCount = 0;
static int count;
int outVal;
lcd.clear();
lcd.setCursor(0, 0);
count = analogRead(OUT_PORT);
if (sign == 1) { //AC
outVal = (int)(count * mult * range[switchPos].errVal * range[switchPos].multVal * acRMS);
}
else {
outVal = (int)(count * mult * range[switchPos].errVal * range[switchPos].multVal);
}
if (loopCount > 0) {
loopCount--;
}
if (switchPos != prevSwPos) {
prevSwPos = switchPos;
loopCount = loopMax;
}
if ((switchPos != 10) ) {
if (count >= overloadLimit[sign]) {
lcd.print("OVERLOAD");
lcd.setCursor(0, 1);
lcd.print('!');
}
else {
dispCurr(switchPos, sign, outVal);
if (loopCount > 2) {
lcd.setCursor(7, 1);
lcd.print('*');
}
if (loopCount == 3) {
txCurr(switchPos, sign, outVal);
}
}
}
if (switchPos == 10) {
dispBatt();
}
delay(100);
}