Monday, January 16, 2017

A Simple Voltage Monitor for the BITX40 (from ND6T)


BITX Voltage Monitor
by Don Cantrell, ND6T

With eight pins available for reading analog signals, the little Nano® has some enticing capabilities. More useful than an “S” meter reading, a measurement of the supply voltage is another display item that you can easily include. This will allow you to keep track of your battery status if you are portable. It could also be adapted to check on any other voltage (or voltages) that you might be concerned with. Like your PA supply if you are going to feed it with a separate higher voltage.

Looking at the wiring diagram of my VFO build you will notice a couple of resistors that are in series and placed between the +12 Volt input and ground. There is a 1.8 Kohm from ground and a 5.6 Kohm on the power feed side. Power rating is unimportant since they are going to dissipate only about 25 milliwatts. I chose these values so that it could measure up to 20 Volts without subjecting the Nano® to more than 5 volts. A safe Nano® is a happy Nano®.

Whenever measuring any voltage with the Nano®, endeavor to keep the maximum voltage to the analog pin below 5 Volts but compromise to retain the best dynamic range. The measurement involves converting the range into 1024 steps and then dividing the result by whatever number will display a reading that will be closest to what the measured voltage actually is. If you have too much range then those steps will be larger and more inaccurate.

Software implementation is wonderfully simple. Here's how to read the voltage on pin A2:

    // Read supply voltage 
    int supply = analogRead(A1);

The first line is just a comment, reminding you what the next code is about. This line is optional. The second line establishes a local variable (that I named “supply”) and specifies it to be an integer type, saving a little storage space and keeping handling simple. The “=” sign assigns the result of the “analogRead” function on port A1. If the voltage applied to that pin was 2.5 Volts, for instance, the “supply” variable is now worth 512. That's because 2.5 volts is half the maximum 5 volts and, therefore, half of the maximum 1024. Make sense?

To display that as a voltage on our LCD screen we would use this line:


Why divide the supply variable by 47.7? Because that would make it correspond to the voltage that we were feeding the resistive scaling divider with. Find it by experiment. In this case you would see 10.73 on the display. Apply 10.73 volts to the top of the divider and you will see 2.5 volts on the tap where you have pin A1 attached and you will read 10.73 (or so) on the display.

Easy. Two resistors, two lines of code. You can use similar schemes to monitor any voltage or current. Rectify the voltage from a winding on a toroid that you run the antenna lead through and read output power (antenna current). Set up a VSWR bridge. Ahhh! The possibilities!

de ND6T       


  1. I use a similar method for measuring voltages greater than the maximum ADC voltage. I have a simple set of definitions and code I paste into my sketch as needed. I just define ADC values for the board I am using , and voltage divider values

    // define info about ADC and voltage divider values
    #define ADC_ref 500 // ADC reference voltage X 100 ( 500 for nano ,uno , 5 volt pro mini ( 330 for 3.3v pro mini ,teensy, stm32)
    #define ADC_res 1023 // ADC resolution ( 1023 for nano uno pro mini ) (4095 for teensy stm32 )
    #define R1 10 // from GND to Vin_bat, express in 100R (10 = 1000 Ohm)
    #define R2 47 // from + power supply to Vin_bat, express in 100R (47 = 4700 Ohm)
    #define V_multi (R1+R2)/R1 // compute multiplier based on voltage divider values

    // ADC pin to read battery vltage
    #define VIN_bat A6

    //battery voltage x 100 divide by 100 in display routine
    int bat_voltage;

    // measure and scale voltage
    bat_voltage = analogRead(VIN_bat);
    // compute battery voltage using ADC & divider values
    bat_voltage = map(bat_voltage , 0, ADC_res, 0, (ADC_ref * V_multi);

  2. This comment has been removed by the author.

  3. My own 'scheme' is to use Custom LCD characters:
    13.0 V down to the recommended 11.9 V Gel Battery discharge, I can show one of 7 battery icons.

    But anything above 13.0 V, I'll show a 'plugged in' icon, just like a laptop (since a gel battery is nearly always below 13V).

    Custom Chars can be used in a single LCD spot (if you don't want to display it all out in digits: '12.9' takes 4 LCD spots).

    I have a little tool which you can use to setup the character bytes:

    Here's my set of Custom LCD Icons, starting with 'Power Supply', followed by full battery...empty battery (11.9V for Gel Batteries) -

    int batterySense[8] = {647,632,622,612,602,597,592,587}; // Values based upon 560k/180k resistor divider
    byte battery[][8] = {
    { B01010, B01010, B11111, B10001, B10001, B10001, B01110, B00100 } // Plugged In > 13.0
    ,{ B01110, B11111, B11111, B11111, B11111, B11111, B11111, B11111 } // Full Battery > 12.7
    ,{ B01110, B11011, B11111, B11111, B11111, B11111, B11111, B11111 } // > 12.5
    ,{ B01110, B11011, B10001, B11111, B11111, B11111, B11111, B11111 } // > 12.3
    ,{ B01110, B11011, B10001, B10001, B11111, B11111, B11111, B11111 } // > 12.1
    ,{ B01110, B11011, B10001, B10001, B10001, B11111, B11111, B11111 } // > 12.0
    ,{ B01110, B11011, B10001, B10001, B10001, B10001, B11111, B11111 } // > 11.9
    ,{ B01110, B11011, B10001, B10001, B10001, B10001, B10001, B11111 } // > 11.8
    #define BatteryCheckSeconds 5 * 1000L // Check the battery every 'n' Seconds.
    unsigned long lastBatteryCheck = 0L;

  4. Hi!
    You got a small bug on your code\explanation :)

    Software implementation is wonderfully simple. Here's how to read the voltage on pin => A2 <=:

    // Read supply voltage
    int supply = analogRead(=> A1 <=);

  5. Vildor: Yes, indeed! Nothing better than the "send" key to find those errors! If only I could learn to read:-)
    -Don, ND6T