Sorry, no fancy display when nothing connected :-)
✈ Motivation
In our experiments, a lot of RF-Carriers are mixed up and down. Sometimes
it is necessary to monitor an intermediate Frequency to keep track of the lock status
of one of our lasers. This can now be done easily with this nifty little counter.
And of course, it can be locked to the internal available 10 MHz GPS Reference ...
✈ The Design
The Block Diagram of the "Countermod"
The Frequency at the input is clamped to a maximum value of +10 dBm by a BAV99
Diode. This shall enhance the chances to survive, in case it is connected to
an AOM-Controller.
The device can be equipped with a lot of different XCOs. In case a more precise external
Reference is available, the AD8307 measures it's amplitude. This value is available at an analog
input pin to the Arduino. It then can switch to the external 10 MHz.
The used Reference is then divided down to 100 Hz, 10 Hz, 1 Hz and 0.1 Hz. All available
settings for the Gate-time can be seen in the following table :
#
S0
GATE-TIME
REMARKS
0
0
400 ms
...
1
1
4 s
...
A 1 PPS signal is made available at a rear BNC output.
If the frequency is extremely low or high, an internal prescaler (MC12080) may be changed
from ten to eighty.
The Arduino has knowledge of the Gate to be open. In case it is open (= counting) it
waits and handles interrupt stuff. If the Gate is closed, the Arduino reads the value, resets the counter und updates
the display. The Gate works independant of the Arduino. It can only decide, which one to use.
On the fontpanel, the supply-voltage is available to power prescalers, preamplifiers e.a.
Assembled PCB, this one is from JLCPCB (Shenzhen)
✈ Downloads
✈ Test Sketch for Arduino/Genuino Nano Every
Double click on code to select ...
/* //////////////////////////////////////////////////////////////////
ARDUINO/Genuino Project "COUNTERMOD", a 3 GHz Frequency Counter
https://www.changpuak.ch/electronics/Arduino-Countermod.php
Software Version 1.0
03.07.2020 by ALEXANDER SSE FRANK
NOTE: FOR NANO EVERY, NEEDED TO UNCOMMENT IN Adafruit_SH1106.cpp
LINES 549 ...
// save I2C bitrate
#ifndef __SAM3X8E__
uint8_t twbrbackup = TWBR;
TWBR = 12; // upgrade to 400KHz!
#endif
NOTE: FOR NANO EVERY, NEEDED TO UNCOMMENT IN Adafruit_SH1106.cpp
LINES 574 ...
#ifndef __SAM3X8E__
TWBR = twbrbackup;
#endif
HELPFUL :
https://learn.adafruit.com/adafruit-gfx-graphics-library/
graphics-primitives
////////////////////////////////////////////////////////////////// */
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH1106.h>
// DISPLAY
#define OLED_MOSI 5
#define OLED_CLK 4
#define OLED_DC 7
#define OLED_CS 8
#define OLED_RESET 6
// ROTARY ENCODER
const int RotaryEncoder1 = A7 ; // PRESSED
const int RotaryEncoder2 = 2 ;
const int RotaryEncoder3 = 3 ;
volatile boolean LEFT = false ;
volatile boolean RIGHT = false ;
volatile boolean PRESS = false ;
// SERIAL COMMUNICATION
byte B[20] ; // holds User Input from Serial
int pointer = 0 ;
Adafruit_SH1106 display(OLED_MOSI,OLED_CLK,OLED_DC,OLED_RESET,OLED_CS);
#if (SH1106_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SH1106.h!");
#endif
unsigned long F_TRUE = 0 ;
unsigned long F_RAW = 0 ;
int SR_CLEAR = A0 ;
int Cursor = 2 ; // 0 = invisible
int CursorDelay = 399 ;
unsigned long NextCursorAction = 0 ;
int StatusGate = 0 ;
int StatusGateOld = 0 ;
int StatusToken = 0 ;
boolean GateOpen = false ;
boolean GateOpenOld = true ;
// /////////////////////////////////////////////////////////////////////
// INTERNAL OR EXTERNAL 10 MHz SOURCE
// /////////////////////////////////////////////////////////////////////
#define RSSI_Pin A6
#define TimeBaseSelectPin 9
int ReferenceSource = 0 ; // INTERN
const int ExtON = 345 ;
const int ExtOFF = 234 ;
int RSSI = 0 ;
void CheckReferenceSource()
{
RSSI = analogRead(RSSI_Pin) ;
if(RSSI > ExtON)
{
digitalWrite(TimeBaseSelectPin, HIGH) ;
ReferenceSource = 1 ; // EXTERN
}
if(RSSI < ExtOFF)
{
digitalWrite(TimeBaseSelectPin, LOW) ;
ReferenceSource = 0 ; // INTERN
}
}
// /////////////////////////////////////////////////////////////////////
// PRESCALER AND GATE TIME ROUTINES
// /////////////////////////////////////////////////////////////////////
#define GATE 13
#define PrescalerPin12 A2
#define PrescalerPin3 A3
int PrescalerValue = 0 ;
#define RangePin A1
int Range = 1 ; // 80 MHz ... 3 GHz
void SetRange()
{
if(Range == 0)
{
digitalWrite(RangePin, LOW) ;
PrescalerValue = 1 ;
}
if(Range == 1)
{
digitalWrite(RangePin, HIGH) ;
digitalWrite(PrescalerPin12, HIGH) ;
digitalWrite(PrescalerPin3, HIGH) ;
PrescalerValue = 10 ;
}
if(Range == 2)
{
digitalWrite(RangePin, HIGH) ;
digitalWrite(PrescalerPin12, HIGH) ;
digitalWrite(PrescalerPin3, LOW) ;
PrescalerValue = 20 ;
}
if(Range == 3)
{
digitalWrite(RangePin, HIGH) ;
digitalWrite(PrescalerPin12, LOW) ;
digitalWrite(PrescalerPin3, HIGH) ;
PrescalerValue = 40 ;
}
if(Range == 4)
{
digitalWrite(RangePin, HIGH) ;
digitalWrite(PrescalerPin12, LOW) ;
digitalWrite(PrescalerPin3, LOW) ;
PrescalerValue = 80 ;
}
}
int GateTime = 0 ; // 400 ms OPEN
#define GATE_SEL_0 10 // THIS IS S0
void SetGateTime(int val)
{
// X0, 400 ms
if(val == 0)
{
digitalWrite(GATE_SEL_0, HIGH) ;
}
// X1, 4000 ms
if(val == 1)
{
digitalWrite(GATE_SEL_0, LOW) ;
}
GateTime = val ;
}
// /////////////////////////////////////////////////////////////////////
// Serial Communication Routines
// /////////////////////////////////////////////////////////////////////
void ShowInputBuffer()
{
// FOR DEBUG REASON ONLY :-)
for (int i = 0; i < 10; i++)
{
Serial.print("B[") ;
Serial.print(i, DEC) ;
Serial.print("] = ") ;
Serial.println(B[i]) ;
}
}
void FlushInputBuffer()
{
while (Serial.available())
{
B[19] = Serial.read() ;
}
for (int i = 0; i < 20; i++) B[i] = 32 ;
}
void CheckForSerialInput()
{
if (Serial.available())
{
B[pointer] = Serial.read() ;
pointer += 1 ;
if (pointer > 19) pointer = 0 ; // EMERGENCY BREAK
}
}
void EvaluateSerialInput()
{
// *IDN?
if ((B[0]==42)&&(B[1]==73)&&(B[2]==68)&&(B[3]==78)&&(B[4]==63))
{
Serial.println("Countermod V1.9 by Changpuak.ch (C) 07/2020") ;
FlushInputBuffer() ;
pointer = 0 ;
}
// F? Ask for Frequency
if((B[0]==70)&&(B[1]==63))
{
Serial.print(F_TRUE, DEC) ;
Serial.println(" Hz") ;
FlushInputBuffer() ;
pointer = 0 ;
}
// G:0 Gate Time 0 = 400 ms
if((B[0]==71)&&(B[1]==58)&&(B[2]==48))
{
GateTime = 0 ;
SetGateTime(GateTime) ;
Serial.println("O.K.") ;
FlushInputBuffer() ;
pointer = 0 ;
}
// G:1 Gate Time 1 = 4000 ms
if((B[0]==71)&&(B[1]==58)&&(B[2]==49))
{
GateTime = 1 ;
SetGateTime(GateTime) ;
Serial.println("O.K.") ;
FlushInputBuffer() ;
pointer = 0 ;
}
// R:0 Set Range to LOW,
if((B[0]==82)&&(B[1]==58)&&(B[2]==48))
{
Range = 0 ;
SetRange() ;
Serial.println("O.K.") ;
FlushInputBuffer() ;
pointer = 0 ;
}
// R:1 Set Range to HIGH, Divider = 10
if((B[0]==82)&&(B[1]==58)&&(B[2]==49))
{
Range = 1 ;
SetRange() ;
Serial.println("O.K.") ;
FlushInputBuffer() ;
pointer = 0 ;
}
// R:2 Set Range to HIGH, Divider = 20
if((B[0]==82)&&(B[1]==58)&&(B[2]==50))
{
Range = 2 ;
SetRange() ;
Serial.println("O.K.") ;
FlushInputBuffer() ;
pointer = 0 ;
}
// R:3 Set Range to HIGH, Divider = 40
if((B[0]==82)&&(B[1]==58)&&(B[2]==51))
{
Range = 3 ;
SetRange() ;
Serial.println("O.K.") ;
FlushInputBuffer() ;
pointer = 0 ;
}
// R:4 Set Range to HIGH, Divider = 80
if((B[0]==82)&&(B[1]==58)&&(B[2]==52))
{
Range = 4 ;
SetRange() ;
Serial.println("O.K.") ;
FlushInputBuffer() ;
pointer = 0 ;
}
// REF? Ask for Reference Source
if((B[0]==82)&&(B[1]==69)&&(B[2]==70)&&(B[3]==63))
{
if(ReferenceSource == 1) Serial.println("EXTERNAL") ;
else Serial.println("INTERNAL") ;
FlushInputBuffer() ;
pointer = 0 ;
}
else
{
// THROW AWAY GARBAGE FROM SERIAL INPUT (FIRST CHAR ONLY :-(
// * F G R NIL
if((B[0]!=42)&&(B[0]!=70)&&(B[0]!=71)&&(B[0]!=82)&&(B[0]!=32))
{
Serial.println("SYNTAX ERROR. UNKNOWN COMMAND.") ;
ShowInputBuffer() ;
FlushInputBuffer() ;
pointer = 0 ;
}
}
}
// /////////////////////////////////////////////////////////////////////
// SUBROUTINES DISPLAY.
// /////////////////////////////////////////////////////////////////////
void UpDateDisplay()
{
int offset = 0 ;
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0); display.print("****");
display.setCursor(33,0); display.print("COUNTERMOD");
display.setCursor(104,0); display.print("****");
display.drawLine(0, 12, 128, 12, WHITE);
display.setTextSize(2) ;
// FREQUENCY
String FString = String(F_TRUE, DEC) ;
int LFString = 10 - FString.length() ;
for (int i=LFString; i<10; i++)
{
if (i>=0) offset = 0 ;
if (i>=1) offset = 5 ;
if (i>=4) offset = 10 ;
if (i>=7) offset = 15 ;
display.setTextColor(WHITE) ;
display.setTextSize(2) ;
display.setCursor(i*11+offset,19) ;
display.print(FString[i-LFString]) ;
}
display.drawLine(0, 40, 128, 40, WHITE);
// STATUS OVEN
display.setTextSize(0);
display.setCursor(0, 45) ;
if(ReferenceSource == 1) display.print("EXT") ;
else display.print("INT") ;
// STATUS GATE
// if(digitalRead(GATE) == 1) display.fillCircle(40, 48, 3, 1);
if(digitalRead(GATE) == 1) display.fillRoundRect(29, 45, 10, 7, 2, 1);
else display.drawRoundRect(29, 45, 10, 7, 2, 1);
display.setCursor(46, 45) ;
display.print("GATE ") ;
if(GateTime == 0) display.print("0.4 SEC") ;
if(GateTime == 1) display.print("4.0 SEC") ;
display.setCursor(0, 57) ;
// RANGE AND PRESCALER
// LOW RANGE
if(Range == 0) display.print("100 kHz - 45 MHz") ;
// HIGH RANGE
if(Range >= 1)
{
display.print("25 MHz - 1.0 GHz") ;
// STATUS PRESCALER
display.setCursor(100, 57) ;
display.print(PrescalerValue,DEC) ;
}
// Cursor
if(Cursor == 1) display.fillTriangle(122, 48, 127, 45, 127, 51, 1) ;
if(Cursor == 2) display.fillTriangle(122, 60, 127, 57, 127, 63, 1) ;
display.display() ;
}
// /////////////////////////////////////////////////////////////////////
// S E T U P
// /////////////////////////////////////////////////////////////////////
void setup()
{
Serial.begin(115200) ;
Wire.begin() ;
// INIT OLED
display.begin(SH1106_SWITCHCAPVCC);
// SHOW STARTUP SCREEN
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0); display.print("****");
display.setCursor(33,0); display.print("COUNTERMOD");
display.setCursor(104,0); display.print("****");
display.drawLine(0, 12, 128, 12, WHITE);
display.setTextSize(1);
display.setCursor(0, 21);
display.println("A 10 MHz TO 1.2 GHz ");
display.setCursor(0, 33);
display.println("UHF FREQUENCY COUNTER");
display.setCursor(0, 45);
display.println("(C) ETH QUANTUMOPTICS");
display.setCursor(0, 57);
display.println("BUILT 25.07.2020");
display.display();
delay(999) ;
FlushInputBuffer() ;
pinMode(RotaryEncoder1, INPUT_PULLUP);
pinMode(RotaryEncoder2, INPUT_PULLUP);
pinMode(RotaryEncoder3, INPUT_PULLUP);
// YELLOW
attachInterrupt(digitalPinToInterrupt(RotaryEncoder2),
RotaryEncoderISR2, FALLING);
// GREEN
attachInterrupt(digitalPinToInterrupt(RotaryEncoder3),
RotaryEncoderISR3, FALLING);
// PRESCALER
pinMode(PrescalerPin12, OUTPUT) ;
pinMode(PrescalerPin3, OUTPUT) ;
pinMode(RangePin, OUTPUT) ;
Serial.println("Countermod V1.9 by Changpuak.ch (C) 07/2020") ;
Serial.print("PRESCALER : "); Serial.println(PrescalerValue, DEC) ;
Serial.print("Range : ") ; Serial.println(Range, DEC) ;
Serial.print("GATE-TIME : ");
Serial.println(GateTime, DEC) ;
Serial.print("REFERENCE : ") ;
if(ReferenceSource == 1) Serial.println("EXTERNAL") ;
else Serial.println("INTERNAL XCO") ;
Serial.print("FREQUENCY : ") ;
Serial.print(F_TRUE, DEC) ;
Serial.println(" Hz") ;
Serial.println("DEVICE READY.\n") ;
pinMode(GATE, INPUT) ;
pinMode(GATE_SEL_0, OUTPUT) ;
SetGateTime(GateTime) ;
pinMode(RSSI_Pin, INPUT) ;
pinMode(TimeBaseSelectPin, OUTPUT) ;
pinMode(SR_CLEAR, OUTPUT) ;
SetRange() ;
digitalWrite(SR_CLEAR, HIGH) ;
delay(10);
digitalWrite(SR_CLEAR, LOW) ;
delay(3000);
}
// /////////////////////////////////////////////////////////////////////
// AUX FUNCTIONS
// /////////////////////////////////////////////////////////////////////
void EvaluateKeyBoard()
{
if (LEFT)
{
noInterrupts() ;
// GATE-TIME
if((Cursor == 1) && (millis() > NextCursorAction))
{
GateTime -= 1 ;
if(GateTime < 0) GateTime = 1 ;
NextCursorAction = millis() + CursorDelay ;
}
// INPUT RANGE
if((Cursor == 2) && (millis() > NextCursorAction))
{
Range -= 1 ;
if(Range < 0) Range = 4 ;
NextCursorAction = millis() + CursorDelay ;
}
SetGateTime(GateTime) ;
SetRange() ;
LEFT = false ;
RIGHT = false ;
interrupts() ;
}
if (RIGHT)
{
noInterrupts() ;
// GATE-TIME
if((Cursor == 1) && (millis() > NextCursorAction))
{
GateTime += 1 ;
if(GateTime > 1) GateTime = 0 ;
NextCursorAction = millis() + CursorDelay ;
}
// INPUT RANGE
if((Cursor == 2) && (millis() > NextCursorAction))
{
Range += 1 ;
if(Range > 4) Range = 0 ;
NextCursorAction = millis() + CursorDelay ;
}
SetGateTime(GateTime) ;
SetRange() ;
LEFT = false ;
RIGHT = false ;
interrupts() ;
}
// KEY PRESSED >> ADVANCE CURSOR
if(PRESS)
{
if(millis() > NextCursorAction)
{
Cursor += 1 ;
if(Cursor == 3) Cursor = 1 ;
NextCursorAction = millis() + CursorDelay ;
}
PRESS = false ;
}
}
byte RotByte(byte x)
{
byte y = 0x00 ;
if((x & 0x01) > 0) y |= 0x80 ;
if((x & 0x02) > 0) y |= 0x40 ;
if((x & 0x04) > 0) y |= 0x20 ;
if((x & 0x08) > 0) y |= 0x10 ;
if((x & 0x10) > 0) y |= 0x08 ;
if((x & 0x20) > 0) y |= 0x04 ;
if((x & 0x40) > 0) y |= 0x02 ;
if((x & 0x80) > 0) y |= 0x01 ;
return y ;
}
void ReadDividerChain()
{
// MSB
Wire.beginTransmission(0x3C) ;
Wire.write(0x00) ;
Wire.endTransmission() ;
byte MSB = 0x00 ;
Wire.requestFrom(0x3C, 1) ;
if(Wire.available()) MSB = Wire.read() ;
F_RAW = RotByte(MSB) ;
F_RAW = F_RAW << 8 ;
// ISB
Wire.beginTransmission(0x39) ;
Wire.write(0x00) ;
Wire.endTransmission() ;
byte ISB = 0x00 ;
Wire.requestFrom(0x39, 1) ;
if(Wire.available()) ISB = Wire.read() ;
F_RAW |= RotByte(ISB) ;
F_RAW = F_RAW << 8 ;
// LSB
Wire.beginTransmission(0x38) ;
Wire.write(0x00) ;
Wire.endTransmission() ;
byte LSB = 0x00 ;
Wire.requestFrom(0x38, 1) ;
if(Wire.available()) LSB = Wire.read() ;
F_RAW |= RotByte(LSB) ;
}
// /////////////////////////////////////////////////////////////////////
// M A I N L O O P
// /////////////////////////////////////////////////////////////////////
void loop()
{
StatusGateOld = StatusGate ;
StatusGate = digitalRead(GATE) ;
StatusToken = StatusGateOld + StatusGate ;
switch (StatusToken)
{
case 0:
// Gate is still closed
if(digitalRead(RotaryEncoder1) == 0) PRESS = true ;
EvaluateKeyBoard() ;
CheckForSerialInput() ;
EvaluateSerialInput() ;
CheckReferenceSource() ;
UpDateDisplay() ;
break;
case 1:
// Gate status has changed recently
// If Gate has closed
if(StatusGate == 0)
{
// READ NEW FREQUENCY
ReadDividerChain() ;
// RESET COUNTER
digitalWrite(SR_CLEAR, HIGH) ;
delay(5);
digitalWrite(SR_CLEAR, LOW) ;
// CALCULATE TRUE FREQUENCY
F_TRUE = F_RAW ;
// COMPENSATE FOR PRESCALER
if(Range == 1) F_TRUE = F_TRUE * 10 ;
if(Range == 2) F_TRUE = F_TRUE * 20 ;
if(Range == 3) F_TRUE = F_TRUE * 40 ;
if(Range == 4) F_TRUE = F_TRUE * 80 ;
// COMPENSATE FOR GATE-TIME
if(GateTime == 0) F_TRUE = F_TRUE * 2.5 ;
if(GateTime == 1) F_TRUE = F_TRUE * 0.25 ;
// DISPLAY NEW FREQUENCY
UpDateDisplay() ;
// OVERWRITE STATUS-TOKEN ... JUST IN CASE :-)
StatusToken = 2 ;
// Serial.println(F_TRUE,DEC) ;
}
break;
case 2:
// Gate is still open
if(digitalRead(RotaryEncoder1) == 0) PRESS = true ;
EvaluateKeyBoard() ;
CheckForSerialInput() ;
EvaluateSerialInput() ;
CheckReferenceSource() ;
UpDateDisplay() ;
break;
default:
// NOP
// NOP
break;
}
delay(1) ;
}
// /////////////////////////////////////////////////////////////////////
// INTERRUPT SERVICE ROUTINES
// /////////////////////////////////////////////////////////////////////
void RotaryEncoderISR2()
{
// YELLOW
LEFT = false ;
RIGHT = false ;
int autre = digitalRead(RotaryEncoder3) ;
if (autre < 1) LEFT = true ;
if (autre > 0) RIGHT = true ;
}
void RotaryEncoderISR3()
{
// GREEN
LEFT = false ;
RIGHT = false ;
int autre = digitalRead(RotaryEncoder2) ;
if (autre < 1) RIGHT = true ;
if (autre > 0) LEFT = true ;
}
// /////////////////////////////////////////////////////////////////////
// END OF FILE.
// /////////////////////////////////////////////////////////////////////
✈ Remote Control of the Countermod
COM SETTINGS :
Set up the COM port inside the PC according to the following list.
• Baud rate: 115200
• Parity bit: None
• Data bit: 8
• Stop bit: 1
• Data flow control: None
COMMAND SYNTAX : *IDN?
Description: Returns the Countermod identification.
Example *IDN?
Returns Countermod V2.0 by Changpuak.ch (C) 07/2020
G:0 OR G:1
Description: Uses Gatetime '0' = 400 ms or '1' = 4000 ms
Example G:1
Returns O.K.
R:0
Description: Uses Range '0' = 100 kHz up to 80 MHz
Example R:0
Returns O.K.
R:1
Description: Uses Range '1' = 100 MHz up to 1000 MHz, Prescaler = 10
Example R:1
Returns O.K.
R:2
Description: Uses Range '2' = 100 MHz up to 1000 MHz, Prescaler = 20
Example R:2
Returns O.K.
R:3
Description: Uses Range '3' = 100 MHz up to 1000 MHz, Prescaler = 40
Example R:3
Returns O.K.
R:4
Description: Uses Range '4' = 100 MHz up to 1000 MHz, Prescaler = 80
Example R:4
Returns O.K.
F?
Description: Returns the Frequency and unit.
Example F?
Returns 0 Hz
REF?
Description: Returns the used Reference Source.
Example REF?
Returns INTERNAL or EXTERNAL
Remote Control with e.g. HTerm 0.8.5 from Tobias Hammer
✈ Rearpanel Connectors
Among the (obvious) USB port from the Arduino Nano Every, there are two BNC connectors. The device may be synchronised
to an external 10 MHz Frequency Standard. Apply them to the white BNC. This one
is galvanically isolated. An AD8307 measures the level and the Arduino then switches
to external.
The second BNC Connector outputs a 1 PPS signal to be used to synchronise other devices.
And yes, we love Radio Basilisk :-)
✈ Share your thoughts
The webmaster does not read these comments regularely. Urgent questions should be send via email.
Ads or links to completely uncorrelated things will be removed.
Your Browser says that you allow tracking. Mayst we suggest that you check that DNT thing ?