• [ON7IR] Digital SWR and power meter - part 2

    From ON7IR via rec.radio.amateur.moderat@21:1/5 to All on Sat May 22 05:29:18 2021
    XPost: rec.radio.amateur.moderated

    Ham radio ON7IR

    ///////////////////////////////////////////
    Digital SWR and power meter - part 2

    Posted: 21 May 2021 06:55 AM PDT https://on7ir.blogspot.com/2021/05/digital-swr-and-power-meter-part-2.html






    Part 2, about the hardware and the software for the digital SWR and
    powermeter. Previous (go to part 1)
    The schematic is quite simple. The analog ports A0 and A1 of the Arduino
    Nano are connected to the SWR bridge as well as the ground lead. Each of
    the digital ports D2, D7-D13 are connected to the TFT screen via a voltage divider consisting of 2 resistors (2200 and 5100 ohm) in series.
    In theory the voltage across the 5K1 resistor is 3.49V when 5 Volt is
    applied to the divider. But in real life the output is a bit lower, around
    4.7V and that gives us 3.28V, ideal for the TFT.
    The TFT board has an ILI9341 chip on board to handle the graphics and an XPT2046 for detecting screen touch events.Pinout of the TFT board is
    available on the silkscreen.



    Pins SCK, MISO, MOSI, DC, RESET, CS are used to talk to the ILI9341. The
    pins marked T_CLK, T_CS,T_DIN, T_DO and T_IRQ are used by the XPT2046. I
    wired these as well because the intention is to use the touch possibility
    in the near future.
    The pin marked LED is used for the backlight of the TFT display and needs
    +5 Volt. If you want to save power (no backlight after a certain amount of inactivity) or want to change the brightness of the backlight then this pin could be controlled by another digital port of the Arduino, preferable via
    a transistor. But I hardwired it to +5V.
    Before developing the sketch it is useful to know what values we have to convert from analog to digital. We do not only need to measure the voltage
    on the forward and reflected pins of the SWR bridge but also the real
    according power that is generated by the transmitter at that time has to be measured. Here an oscilloscope is very handy. With the transceiver
    connected to the 50 ohm dummyload and a simple tool (see link) to connect
    the oscilloscope probe in between, the peak-to-peak voltage can be read. A digital scope has the advantage that it can display the Vp-p value in
    figures immediately.
    On the Xiegu G90 and in one Watt steps from 1 up to 20 Watt all these
    measured values were put in an Excel sheet. The same for each 10% step on
    the Icom IC-7300. But be aware that your oscilloscope and/or its probe can handle the high voltage. With harm and disgrace I learned that the probes I
    had couldn't cope with these high voltages although they were rated for
    150V RMS, which is 212 Vp-p (at 50 ohm this is when 112 Watt is generated).
    The solution is to use an attenuator, note the values and then convert them
    to the unattenuated voltages. Or buy higher rated probes ofcourse ;-)
    The peak-to-peak voltages were then converted to Watts using the formula P
    = (Vpp)² / 400
    After examing the values it was clear that at low RF output or at low
    reflected power, the bridge is not linear. That is not a surprise because otherwise the analog SWR meter would have a linear scale.

    About the software.
    At the bottom you find the sketch for the SWR and power meter. I think it
    has enough comments to understand what it does.
    The non linearity of the bridge is handled by the multiMap library.
    Instead of making use of the map() function which maps a start and an end variable to another start and end variable, multiMap can map several input variables to corresponding output variables and interpolates everything in between. The input values must have increasing values and multiMap needs to know how many variables it has to use.
    If we look at the fwd2watt() function in the sketch we see these lines:
    fwdInVoltage = sensorFWD * (5.0 / 1023.0);
    This converts the voltage from the Vfwd connection of the bridge being read
    by analog port A0 into milliVolts
    float outFWD[] = { 0, 1, 1.5376, 2.6896, 4, 5.0176, 6.1504, 7.3984, 8.2944, 9.2416, 15.6816, 24.6016, 33.64, 45.56, 53.29, 64, 72.25, 81,
    92.16, 100, 120 };
    outFWD[] holds an array of the RF power values that were measured to a
    50ohm load as mentioned in the text above.
    float inFWD[] = { 350, 370, 424, 581, 695, 790, 877, 959, 996, 1107,
    1332, 1697, 2039, 2398, 2601, 2838, 3055, 3287, 3502, 3800, 4000 };
    inFWD[] holds an array of the once measured voltages at that Vfwd
    connection, measured at same time as the RF power values.
    outputValueFWD = multiMap(fwdInVoltage * 1000, inFWD, outFWD, 21);
    This does the trick, multiMap maps the 21 variables and interpolates the
    values in between.
    As an example, if the bridge delivers a forward voltage of 700 mV then it
    is mapped / translated to about 4 Watt. If the bridge delivers 2800 mV then
    it is translated to almost 64 Watt.
    The more values provided to the arrays the more accurate measuring. But on
    the other hand we must keep in mind that the Arduino only does a 10 bit
    analog to digital conversion = 1024 steps. How precise would you like it to
    be? In practise these 21 values are sufficient for the forward power. And
    for the reflected power 15 is enough.
    In this version 1.0 a scale at the SWR bargraph has been added.Running
    version 1.0

    As mentioned in part 1 this sketch is the basis, ready to enhance and
    expand functionalities.

    Finally here is the sketch,

    //*
    ** Digital PWR & SWR meter v1.0 by ON7IR **
    ** Published at on7ir.blogspot.com May 2021 **
    ** Copyright (C) 2021 Rudi Imbrechts (ON7IR) **

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program. If not, see <https://www.gnu.org/licenses/>.


    ** This sketch makes use of the SWR bridge from a Daiwa CN-410M analog
    SWR meter
    Forward and reflected values from this SWR bridge are the input for
    the Arduino.
    Output is presented on a 2.8" touchscreen. Touch function is not used here but will be in future version.
    More information on https://on7ir.blogspot.com/2021/05/digital-swr-and-power-meter-part-1.html
    and https://on7ir.blogspot.com/2021/05/digital-swr-and-power-meter-part-2.html

    ** This sketch uses several libraries.
    - Adafruit_GFX by Adafruit Industries
    - ILI9341_Fast library and Numeric display - RREFont are (c) 2020
    Pawel A. Hernik,
    see https://github.com/cbm80amiga/ILI9341_Fast and https://github.com/cbm80amiga/RREFont
    - multiMap library, see https://github.com/RobTillaart/MultiMap

    ** ILI9341 240x320 2.8" TFT pinout:
    1 = +5V
    2 = GND
    3 = CS
    4 = RST
    5 = DC
    6 = MOSI
    7 = SCK
    8 = Backlight
    9 = MISO
    10 = T_CLK
    11 = T_CS
    12 = T_DIN
    13 = T_DO
    14 = T_IRQ

    ** Warning ** TFT screen needs 5V power supply but the signal levels are 3.3V.
    Use voltage converters between Arduino and TFT pins!
    */

    #include <SPI.h>
    #include <Adafruit_GFX.h>
    #include <ILI9341_Fast.h>
    #include "MultiMap.h"

    #define TFT_CS 8
    #define TFT_DC 10
    #define TFT_RST 9

    ILI9341 tft = ILI9341(TFT_DC, TFT_RST, TFT_CS); // initialise the TFT

    //----------------------------------------------------------------------------- // define RRE fonts
    #include "RREFont.h"
    #include "rre_6x8.h"
    #include "rre_arialn_16.h"
    #include "rre_bold13x20.h"
    #include "rre_bold13x20v.h"
    #include "rre_bold13x20no.h"

    RREFont font; // needed for RREFont library initialization, define your fillRect
    void customRect(int x, int y, int w, int h, int c) {
    return tft.fillRect(x, y, w, h, c);
    } //-----------------------------------------------------------------------------

    const int SCR_WD = 320;
    const int SCR_HT = 240;
    const int fwdInPin = A0; // input forward power from SWR bridge
    const int refInPin = A1; // input reflected power from SWR bridge
    unsigned int sensorFWD = 0; // raw forward value
    unsigned int sensorREF = 0; // raw reflected value
    float fwdInVoltage; // calculated forward voltage from SWR bridge float refInVoltage; // calculated reflected voltage from SWR bridge float outputValueFWD; // calculated forward power
    float outputValueREF; // calculated reflected power
    float cswr; // used to calculate swr
    float swr; // contains SWR value
    const int swrTreshold = 5; // SWR treshold value. Not interested in SWR higher than 5
    int xbar = 110; // horizontal position of first graphic bar
    int nextbar = 50; // horizontal position of next bar
    int barHeight = 25; // height of the graphical bar
    int i = 0;
    int mode = 0, lastMode = -1;

    // define color scheme
    const unsigned short defaultColor = RGBto565(255, 255, 255); // rgb color areas (general) is white
    const unsigned short labelColor = RGBto565(250, 250, 250); // rgb color label text
    const unsigned short FwdColor = RGBto565(50, 250, 50); // green
    const unsigned short RefColor = LGREY; // light grey const unsigned short Swr1Color = RGBto565(0, 255, 140); // light blue when SWR is less than 2
    const unsigned short Swr2Color = YELLOW; // yellow
    when SWR is between 2 and 2.5
    const unsigned short Swr25Color = RGBto565(255, 100, 0); // orange
    when SWR is between 2.5 and 3
    const unsigned short Swr3Color = RED; // red when
    SWR is 3 or higher
    const unsigned short barBgColor = RGBto565(50, 50, 50); // grey background of the bars
    const unsigned short on7irColor = RGBto565(50, 100, 50); // dark
    greenish

    void setup() {
    Serial.begin(9600);
    pinMode(TFT_CS, OUTPUT); // avoid chip select contention. This is...
    digitalWrite(TFT_CS, HIGH); // ...needed later when
    touchscreen is activated
    tft.init(); // initialize TFT screen
    tft.setRotation(3); // rotate to landscape, pins at
    left side
    font.init(customRect, SCR_WD, SCR_HT); // custom fillRect function and screen width and height values
    }

    void setBigNumFont() {
    font.setFont(&rre_Bold13x20v);
    font.setSpacing(1);
    font.setScale(1, 2);
    font.setDigitMinWd(16);
    }

    void setInfoFont() {
    font.setFont(&rre_arialn_16);
    }

    void drawField(int x, int y, int w, int h, char *label, unsigned short col
    = defaultColor) {
    tft.drawRect(x, y + 7, w, h - 7, col);
    setInfoFont();
    font.setScale(1);
    font.setColor(labelColor, BLACK); // black is the background color of label text
    int wl = font.strWidth(label);
    font.printStr(x + (w - wl) / 2, y, label); // center the label text
    }

    void showVal(float v, int x, int y, int w, int p, unsigned short col) {
    setBigNumFont();
    font.setColor(col, BLACK); // black is the background color of the computed values
    char txt[10];
    dtostrf(v, w, p, txt);
    font.printStr(x, y, txt);
    }

    void constData() {
    drawField(0, 0, 95, 78, " FWD ", FwdColor); // print the frames
    and label text.
    drawField(112, 0, 95, 78, " REF ", RefColor);
    font.setScale(1);
    font.setColor(labelColor, BLACK);
    font.printStr(0, xbar - 18, "forward"); // print the text
    above each graphic bar
    font.printStr(0, xbar + nextbar - 18, "reflected");
    font.printStr(0, xbar + nextbar * 2 - 18, "SWR");
    font.setFont(&rre_6x8);
    font.setColor(on7irColor, BLACK);
    font.printStr(140, 90, "SWR&POWER METER by ON7IR"); // :-)
    font.setColor(WHITE, BLACK); // print SWR scale above graphic SWR bar in a white color
    font.printStr(60, xbar + nextbar * 2 - 10, "1");
    font.printStr(75, xbar + nextbar * 2 - 10, ".");
    font.printStr(91, xbar + nextbar * 2 - 10, ":");
    font.printStr(106, xbar + nextbar * 2 - 10, ".");
    font.printStr(122, xbar + nextbar * 2 - 10, "2");
    font.printStr(140, xbar + nextbar * 2 - 10, ".");
    font.printStr(158, xbar + nextbar * 2 - 10, ":");
    font.printStr(180, xbar + nextbar * 2 - 10, "3");
    font.printStr(215, xbar + nextbar * 2 - 10, ".");
    font.printStr(250, xbar + nextbar * 2 - 10, "4");
    font.printStr(310, xbar + nextbar * 2 - 10, "5");
    }

    void varData() {
    showVal(outputValueFWD, 4, 24, 4, 0, FwdColor); // show forward
    power in figures
    if (outputValueREF >= 100) { // show reflected
    power in figures
    showVal(outputValueREF, 117, 24, 4, 0, RefColor); // but don't show digits after decimal point when reflected power is above 100 watts
    } else {
    showVal(outputValueREF, 132, 24, 4, 1, RefColor);
    }
    if (outputValueFWD == 0) { // show SWR in
    figures
    swr = 0; // SWR is zero if nothing is transmitted
    }
    if (swr > 2.9) { // use various
    colors for SWR values
    showVal(swr, 254, 24, 3, 1, Swr3Color); // if SWR is dangerously high
    drawField(225, 0, 95, 78, " SWR ", Swr3Color);
    } else if (swr > 2.5) {
    showVal(swr, 254, 24, 3, 1, Swr25Color); // if SWR is between 2.5 and 3
    drawField(225, 0, 95, 78, " SWR ", Swr25Color);
    } else if (swr > 2) {
    showVal(swr, 254, 24, 3, 1, Swr2Color); // if SWR is between
    to and 2.5
    drawField(225, 0, 95, 78, " SWR ", Swr2Color);
    } else {
    showVal(swr, 254, 24, 3, 1, Swr1Color); // if SWR is below 2
    drawField(225, 0, 95, 78, " SWR ", Swr1Color);
    }
    }

    void drawBarFWD(int level) {
    level = map(level, 0, 120, 0, 100); // map to
    scale. Although the SWR bridge can handle up to 150W, for my purpose 120W
    full scale is sufficient
    int i = level * SCR_WD / 100;
    tft.fillRect(0, xbar, i, barHeight, FwdColor); // draw the
    bar with the forward power
    tft.fillRect(i, xbar, SCR_WD - i, barHeight, barBgColor); // bar
    background
    }

    void drawBarREF(int level) {
    level = map(level, 0, 120, 0, 100);
    int i = level * SCR_WD / 100;
    tft.fillRect(0, xbar + nextbar, i, barHeight, RefColor); // draw the
    bar with the reflected power
    tft.fillRect(i, xbar + nextbar, SCR_WD - i, barHeight, barBgColor);
    }

    void drawBarSWR(float level) {
    level = level * 10;
    level = map(level, 0, 50, 0, 100); // map to scale
    int i = (level * SCR_WD / 100);
    if (swr < 2) { // change
    color of the bar according to SWR value
    tft.fillRect(0, xbar + nextbar * 2, i, barHeight, Swr1Color);
    } else {
    if (swr < 2.5) {
    tft.fillRect(0, xbar + nextbar * 2, i, barHeight, Swr2Color);
    } else {
    if (swr < 3) {
    tft.fillRect(0, xbar + nextbar * 2, i, barHeight, Swr25Color);
    } else {
    tft.fillRect(0, xbar + nextbar * 2, i, barHeight, Swr3Color);
    }
    }
    }
    tft.fillRect(i, xbar + nextbar * 2, SCR_WD - i, barHeight, barBgColor);
    }

    // Converting forward and reflected power from the SWR bridge.
    // inREF, outREF, inFWD and outFWD values are obtained from measurements
    done on _my_ SWR bridge and transceiver at the same time.
    // They cannot be used with another SWR bridge, you have to measure them yourself and replace them in here.

    void ref2watt() {
    refInVoltage = sensorREF * (5.0 /
    1023.0); //
    convert the reflected input value to milliVolts
    float outREF[] = { 0, 0.2, 0.4, 0.6, 0.8, 1, 1.5, 2, 4, 13, 16, 20, 42,
    92, 110 }; // measured reflected power in Watts
    float inREF[] = { 18, 118, 185, 230, 270, 310, 400, 465, 773, 1488, 1664, 1926, 2700, 3900, 4300 }; // measured voltage in milliVolts
    outputValueREF = multiMap(refInVoltage * 1000, inREF, outREF,
    15); // map these 15 values
    outputValueREF = constrain(outputValueREF, 0,
    outputValueFWD); // reflected power cannot be higher than forwarded power
    }

    void fwd2watt() {
    fwdInVoltage = sensorFWD * (5.0 / 1023.0); // same comments as in the
    void ref2watt() except that these are the forward values
    float outFWD[] = { 0, 1, 1.5376, 2.6896, 4, 5.0176, 6.1504, 7.3984,
    8.2944, 9.2416, 15.6816, 24.6016, 33.64, 45.56, 53.29, 64, 72.25, 81,
    92.16, 100, 120 };
    float inFWD[] = { 350, 370, 424, 581, 695, 790, 877, 959, 996, 1107,
    1332, 1697, 2039, 2398, 2601, 2838, 3055, 3287, 3502, 3800, 4000 };
    outputValueFWD = multiMap(fwdInVoltage * 1000, inFWD, outFWD,
    21); // map these 21 values
    }

    void calcSWR() {
    cswr = sqrt((outputValueREF) / (outputValueFWD)); // use formulas to calculate SWR
    swr = ((1 + cswr) / (1 - cswr));
    swr = constrain(swr, 1, swrTreshold);
    }


    void loop() {
    sensorFWD = analogRead(fwdInPin); // read analog value of forwarded signal
    sensorFWD = constrain(sensorFWD, 70, 1023); // constrain to prevent a negative output value
    sensorREF = analogRead(refInPin); // read analog value of reflected signal
    sensorREF = constrain(sensorREF, 0, 1023);
    fwd2watt(); // get forward power
    ref2watt(); // get reflected power
    calcSWR(); // get calculated SWR
    drawBarFWD(outputValueFWD); // and draw them graphically
    drawBarREF(outputValueREF);
    drawBarSWR(swr);
    if (mode != lastMode) { // screen refresh
    lastMode = mode;
    tft.fillScreen(BLACK);
    constData(); // display constant screen
    data
    }
    varData(); // display variable screen
    data
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)