#include <LiquidCrystal.h>
// LCD connections (RS, EN, D4, D5, D6, D7)
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
// Analog pins for AD8307
const byte analogPinF = A0; // Forward power
const byte analogPinR = A1; // Reflected power
// AD8307 calibration parameters
const float Vreference = 5000.0; // mV
const float slopeF = 0.02618; // Slope for forward power
const float interceptF = 45.2; // Intercept for forward power
const float slopeR = 0.02618; // Slope for reflected power
const float interceptR = 45.2; // Intercept for reflected power
// Power measurement variables
int adcValueF = 0;
float voltageF = 0;
int adcValueR = 0;
float voltageR = 0;
float pow_fwd = 0; // Forward power in Watts
float pow_ref = 0; // Reflected power in Watts
float peak_power = 0; // Peak forward power in Watts
float peak_power_ref = 0; // Peak reflected power
float swr = 1.0; // Standing Wave Ratio
unsigned long last_peak_time = 0;
unsigned long last_swr_peak_reset = 0;
const unsigned long peak_hold_time = 1000; // 2 seconds peak hold
const unsigned long swr_peak_reset_time = 500; // 0.5s SWR peak reset
// Enhanced bar graph variables
const int virtual_columns = 80; // 16 chars * 5 segments per char
float bar_level = 0; // Current level (0-1)
const float bar_decay = 0.95; // Decay rate (adjustable)
const float bar_threshold = 0.01; // Minimum bar level to display
float bar_graph = 60; // bar graph filling coefficient. Adjust for max Power
float bar_graph_mult = 1.0f / bar_graph;
// Custom character definitions for smooth bar graph
byte bar0[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // Empty
byte bar1[8] = {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10}; // 1/5 full
byte bar2[8] = {0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18}; // 2/5 full
byte bar3[8] = {0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C, 0x1C}; // 3/5 full
byte bar4[8] = {0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E}; // 4/5 full
byte bar5[8] = {0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F}; // Full block
// Fast 10^x approximation
inline float pow10_fast(float x) {
return expf(2.302585092994046f * x); // exp(x * ln(10))
}
// Power calculation function
float computePower(int adcValue, float slope, float intercept) {
float voltage = (adcValue / 1023.0f) * Vreference; // 'f' suffix for float optimization
float powerdBm = (slope * voltage) - intercept;
return pow10_fast((powerdBm - 30) * 0.1f); // Multiply by 0.1f is faster than /10
}
void setup() {
// Initialize LCD
lcd.begin(16, 2);
// Create custom characters
lcd.createChar(0, bar0);
lcd.createChar(1, bar1);
lcd.createChar(2, bar2);
lcd.createChar(3, bar3);
lcd.createChar(4, bar4);
lcd.createChar(5, bar5);
// Display initial information
lcd.setCursor(0, 0);
lcd.print("Po:0W");
lcd.setCursor(9, 0);
lcd.print("SW:1.00");
// Configure ADC for better performance
analogReference(DEFAULT);
ADCSRA = (ADCSRA & 0xF8) | 0x04; // Set ADC clock divider to 16 (1MHz/16 = 62.5kHz)
}
void loop() {
// Read forward and reflected power
readPower();
// Calculate SWR
calculateSWR();
// Update peak power
updatePeak();
// Update enhanced bar graph
updateSuperResBarGraph();
// Update display
updateDisplay();
// Small delay to prevent flickering
//delay(10); // Reduced from 50ms for faster response
/////////////
}
void readPower() {
// Read forward power
// Read analog values ONCE per channel
adcValueF = analogRead(analogPinF);
adcValueR = analogRead(analogPinR);
// Compute powers using the helper function
pow_fwd = computePower(adcValueF, slopeF, interceptF);
pow_ref = computePower(adcValueR, slopeR, interceptR);
}
void calculateSWR() {
// Track peak reflected power
if (pow_ref > peak_power_ref) {
peak_power_ref = pow_ref;
}
// Reset SWR peaks periodically
if (millis() - last_swr_peak_reset > swr_peak_reset_time) {
peak_power_ref = pow_ref;
last_swr_peak_reset = millis();
}
// Calculate SWR using PEAK values with protection
swr = 1.0; // Default value
if (peak_power > 0.1 && peak_power_ref >= 0) { // 0.1W minimum threshold
float gamma = sqrt(peak_power_ref / peak_power);
if (gamma < 0.98) { // Protection against near-1.0 values
swr = (1.0 + gamma) / (1.0 - gamma);
}
}
// Limit maximum display value
if (swr > 99.9) swr = 99.9;
}
void updatePeak() {
// Update peak power if current power is higher
if (pow_fwd > peak_power) {
peak_power = pow_fwd;
last_peak_time = millis();
last_swr_peak_reset = millis(); // Reset SWR tracking on new peak
}
// Reset peak after hold time if no new peak detected
else if (millis() - last_peak_time > peak_hold_time) {
peak_power = pow_fwd;
}
}
void updateDisplay() {
// Update peak power display (optimized)
lcd.setCursor(0, 0);
lcd.print("Po:");
int display_power = int(peak_power + 0.5); // Proper rounding
if (display_power < 1) {
lcd.print("0W ");
} else {
lcd.print(display_power);
lcd.print('W');
// Add padding spaces based on number length
if (display_power < 10) lcd.print(" ");
else if (display_power < 100) lcd.print(' ');
}
// Update SWR display (optimized)
lcd.setCursor(9, 0);
lcd.print("SW:");
if (swr < 9.995) {
lcd.print(int(swr * 100 + 0.5) * 0.01, 2); // More efficient 2 decimals
} else {
lcd.print(int(swr * 10 + 0.5) * 0.1, 1); // More efficient 1 decimal
}
}
void updateSuperResBarGraph() {
// Update bar level - fast attack, slow decay
float target_level = pow_fwd * bar_graph_mult;
if (target_level > bar_level) {
bar_level = target_level; // Fast attack
} else {
bar_level *= bar_decay; // Slow decay
}
// Handle bar display
lcd.setCursor(0, 1);
if (bar_level < bar_threshold) {
bar_level = 0;
lcd.print(" "); // Clear line
} else {
// Pre-calculate values
static int last_lit_segments = 0;
int lit_segments = round(bar_level * virtual_columns);
lit_segments = constrain(lit_segments, 0, virtual_columns);
// Only redraw if changed significantly
if (abs(lit_segments - last_lit_segments) > 2) {
last_lit_segments = lit_segments;
for (int i = 0; i < 16; i++) {
int segments = lit_segments - (i * 5);
byte char_num = 0;
if (segments > 0) {
char_num = (segments >= 5) ? 5 : segments;
}
lcd.write(char_num);
}
}
}
}
FWD
REF
Slow
Fast
FWD
REF