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)