This is a shield to generate TTL PWM signals from 15 mHz up to 5 kHz. The duty-cycle can be adjusted in steps of 200 µs.
This is used to keep the atoms in the Lithium Lab "warm", i.e. inject fibers / set PID coefficients.
Designed on inspiration by the Lithium Lab.
FREQUENCY RANGE
15 mHz ... 5 kHz
OUTPUT POWER
TTL, positive and negative
POWER SUPPLY
7.5 V, 200 mA
✈ The building Blocks • Functional Description
The Arduino MEGA2560 is the workhorse of this generator. Two long variables, 'ISLow' and 'IsHigh' hold the timing information.
The loop has a delay of 100 µs. It consists of a switch constructor. If we are in the "low" season, a counter is
incremented each time, until it reaches the IsLow value. Then it is switched to the "high" season. Now the counter
is incremented, until it reaches the IsHigh value.
The periodically scanning of the rotary encoder is done with a timer-interrupt. The evaluation of the encoder is done by
the function UpdateEncoder(), initially written by Mr. Joël Steinemann, one of our smart apprentices at ETH zürich •
Quantumoptics.
If the frequency is high, the householding function of the Arduino/Genuino introduces some small jitter, but that is acceptable,
as we are interested in an average value.
The advantage (regarading commercial generators) is the capability of very long sequences. As the variables are of type long,
we can have a time of 2 * 214748.3647 s which corresponds to 2.32830 µHz.
That's exactly what our physicists wanted.
✈ Downloads
✈ Arduino Sketch - The Code
Double click on code to select ...
/* //////////////////////////////////////////////////////////////////////
ARDUINO/Genuino (MEGA2560) Board - "* KILOMOD * Synthesizer"
https://changpuak.ch/electronics/Arduino-Shield-* KILOMOD *.php
Software Version 2.0
14.02.2019 by ALEXANDER SSE FRANK
HELPFUL:
https://www.tutorialspoint.com/c_standard_library/c_function_sprintf.htm
////////////////////////////////////////////////////////////////////// */
// OLED 128x64 with SH1106 Controller
// E.G. DM-OLED13-625
#define OLED_MOSI 10
#define OLED_CLK 9
#define OLED_DC 12
#define OLED_CS 13
#define OLED_RESET 11
// STATE OF THE ROTARY ENCODER
const int RE2 = A3 ; // PRESSED
const int RE1 = A2 ;
const int RE0 = A1 ;
unsigned long StartMilli ;
unsigned long DurationMilli ;
boolean PressedLong = false ;
// OUTPUT
const int PolarityPin = 2 ;
const int OutputPin = 5 ;
boolean Polarity = false ;
// Polarity = false .:. POSITIVE (NORMAL)
// Polarity = true .:. NEGATIVE (INVERTED)
long MinHigh = 1 ; // TIMES 100 us
long MaxHigh = 655360 ; // TIMES 100 us
long MinLow = 1 ; // TIMES 100 us
long MaxLow = 655360 ; // TIMES 100 us
long IsHigh = 655360 ; // TIMES 100 us
long IsLow = 655360 ; // TIMES 100 us
long counter ;
long Timeused ;
long IsLowOld ;
long IsHighOld ;
float Frequency = 1/(0.000001*IsHigh + 0.000001*IsLow) ;
float TargetFrequency = Frequency ;
float OldNewRatio ;
unsigned int action = 0 ; // OUTPUT IS CURRENTLY HIGH
// CURSOR
unsigned int CursorX = 6 ;
unsigned int CursorY = 2 ;
unsigned int CursorVal = 0 ;
unsigned int Rest = 0 ;
unsigned int Ganz = 0 ;
unsigned int OffsetX = 0 ;
long IncDecMultiply = 0 ;
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH1106.h>
#include <stdlib.h>
#include "TimerOne.h"
// INTERRUPT VARIABLES
volatile unsigned int RotaryEncoderStatus = 0;
volatile unsigned int RotaryEncoderStatusOld = 0;
volatile unsigned int RotaryEncoderActivity = 1 ;
int EncoderValue = 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
void setup()
{
delay(1999);
// KNOB - ROTARY ENCODER
pinMode(RE2, INPUT_PULLUP);
pinMode(RE1, INPUT_PULLUP);
pinMode(RE0, INPUT_PULLUP);
// OUTPUT SIGNAL
pinMode(OutputPin, OUTPUT);
pinMode(PolarityPin, OUTPUT);
digitalWrite(OutputPin, LOW);
digitalWrite(PolarityPin, Polarity);
Serial.begin(115200);
// INIT OLED
display.begin(SH1106_SWITCHCAPVCC);
// SHOW STARTUP SCREEN
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("KILOMOD");
display.setTextSize(1);
display.setCursor(0,21);
display.println("A TTL SYNTHESISER");
display.setCursor(0,33);
display.println("BASED ON THE MEGA2560");
display.setCursor(0,45);
display.println("(C) ETH QUANTUMOPTICS");
display.setCursor(0,57);
display.println("BUILT 14.02.2019");
display.display();
delay(999);
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("KILOMOD");
display.setTextSize(1);
display.setCursor(0,21);
display.println("FIRMWARE WRITTEN BY");
display.setCursor(0,33);
display.println("MR. JOEL STEINEMANN");
display.setCursor(0,45);
display.println("MR. ALEXANDER FRANK");
display.setCursor(0,57);
display.println("MR. TILMAN ESSLINGER");
display.display();
delay(999);
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("KILOMOD");
display.setTextSize(1);
display.setCursor(0,21);
display.println("HARDWARE DESIGNED BY");
display.setCursor(0,33);
display.println("MR. ALEXANDER FRANK");
display.setCursor(0,45);
display.println("MS. LAURA CORMAN");
display.setCursor(0,57);
display.println("MR. TILMAN ESSLINGER");
display.display();
delay(999);
UpDateOLED();
// ENABLE INTERRUPT FOR PIN ...
Timer1.initialize(1000); // EVERY 1 ms
Timer1.attachInterrupt(CheckRotaryEncoder);
RotaryEncoderStatus = digitalRead(RE2) << 2 ;
RotaryEncoderStatus |= digitalRead(RE1) << 1 ;
RotaryEncoderStatus |= digitalRead(RE0) ;
RotaryEncoderStatusOld = RotaryEncoderStatus ;
RotaryEncoderActivity = 0x00;
}
void loop()
{
switch (action)
{
case 0:
// OUTPUT IS LOW
counter += 1 ;
if (counter > IsLow)
{
digitalWrite(OutputPin, LOW);
action = 1 ;
counter = 0 ;
}
break;
case 1:
// OUTPUTS HIGH
counter += 1 ;
if (counter > IsHigh)
{
digitalWrite(OutputPin, HIGH);
action = 0 ;
counter = 0 ;
}
break;
}
// /////////////////////////////////////////////////////////////////////
// CHECK ROTARY ENCODER
// /////////////////////////////////////////////////////////////////////
// if (RotaryEncoderActivity != 0x00) SerialHexOutput(RotaryEncoderActivity);
// KNOB PRESSED
if (RotaryEncoderActivity == 0x04)
{
// FALLING EDGE ONLY
if (( RotaryEncoderStatus & 0x04 ) == 0x00)
{
CursorVal += 1 ;
if (CursorVal > 16) CursorVal = 0 ;
Ganz = 0 ; Rest = 0 ;
if (CursorVal > 0)
{
Ganz = 1 ;
Rest = CursorVal ;
}
if (CursorVal > 6)
{
Ganz = 2 ;
Rest = CursorVal - 6;
}
if (CursorVal > 12)
{
Ganz = 3 ;
Rest = CursorVal - 12;
}
UpDateOLED();
RotaryEncoderActivity = 0x00 ;
}
}
// KNOB ROTATED
// CHECK FOR CHANGE OF ROTARY ENCODER
if ( RotaryEncoderActivity > 0x00 )
{
// EVALUATE KNOB NOT PRESSED
if (( RotaryEncoderStatus & 0x04 ) != 0x00)
{
UpdateEncoder();
}
}
// TIMEBASE :-)
delayMicroseconds(99);
}
void SerialHexOutput(byte value)
{
Serial.print("0x");
if (value < 0x10) Serial.print("0");
Serial.println(value,HEX);
}
// /////////////////////////////////////////////////////////////////////
// SUBROUTINES DISPLAY
// /////////////////////////////////////////////////////////////////////
void UpDateOLED()
{
char fstr[10];
String str ;
int len = 0 ;
// CURSOR FOR EVERYTHING BUT POLARITY
CursorX = Rest ;
CursorY = Ganz ;
OffsetX = 7 ;
display.clearDisplay();
display.drawLine(0, 12, 128, 12, WHITE);
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0,0);
display.println("** KILOMOD TTL GEN **");
// /////////////////////////////////////////
display.setCursor(0,17);
display.print("POLARITY : ");
if (!Polarity) display.print("POSITIVE ");
else display.print("NEGATIVE ");
if (Ganz == 0) display.print("<");
// /////////////////////////////////////////
display.setCursor(0,29);
display.print("HIGH : ");
str = String(IsHigh, DEC);
len = 42 - 6 * str.length() ;
display.setCursor(OffsetX*6+len,29);
for (int i=0; i<str.length(); i++)
{
display.print(str[i]);
}
display.print("00 us ");
if (Ganz == 1) display.print("<");
// /////////////////////////////////////////
display.setCursor(0,41);
display.print("LOW : ");
str = String(IsLow, DEC);
len = 42 - 6 * str.length() ;
display.setCursor(OffsetX*6+len,41);
for (int i=0; i<str.length(); i++)
{
display.print(str[i]);
}
display.print("00 us ");
if (Ganz == 2) display.print("<");
// /////////////////////////////////////////
display.setCursor(0,53);
display.print("FREQ : ");
Frequency = 1/(0.0001*IsHigh + 0.0001*IsLow) ;
display.print(dtostrf(Frequency,9,3,fstr));
display.print(" Hz ");
if (Ganz == 3) display.print("<");
if (Ganz > 0)
{
display.setCursor((OffsetX+CursorX)*6,19+CursorY*12);
display.print("_");
}
display.drawLine(0, 63, 128, 63, WHITE);
// DISPLAY THAT THING
display.display();
}
// /////////////////////////////////////////////////////////////////////
// SUBROUTINES ROTARY ENCODER
// /////////////////////////////////////////////////////////////////////
// CHECK ROTARY ENCODER (A1,A2,A3)
void CheckRotaryEncoder()
{
RotaryEncoderStatusOld = RotaryEncoderStatus ;
RotaryEncoderStatus = digitalRead(RE2) << 2 ;
RotaryEncoderStatus |= digitalRead(RE1) << 1 ;
RotaryEncoderStatus |= digitalRead(RE0) ;
RotaryEncoderActivity = RotaryEncoderActivity
| (RotaryEncoderStatus ^ RotaryEncoderStatusOld) ;
}
void UpdateEncoder()
{
/*
¦ A ¦ B ¦
-----------------
¦ 0 ¦ 0 ¦
-----------------
¦¦ ¦ 0 ¦ 1 ¦ -1
¦¦ ----------------- /\
\/ ¦ 1 ¦ 1 ¦ ¦¦
+1 ----------------- ¦¦
¦ 1 ¦ 0 ¦
-----------------
¦ 0 ¦ 0 ¦
-----------------
WHEN AB IS HIGH AND BY THE NEXT ROTATION B GOES HIGH,
THEN THE ENCODER TURNED LEFT.
IF B GOES LOW, THEN IT TURNED RIGTH.
EXACTLY THE OPPOSITE IS TRUE, WHEN AB IS LOW.
*/
EncoderValue = 0 ;
//DETECT THE ROTATION OF THE ENCODER AND SET SOME CONSTANTS
switch(RotaryEncoderStatusOld)
{
// AB WERE LOW
case 4:
if ((RotaryEncoderStatus & 0x02) == 0x00) EncoderValue = + 1 ;
else EncoderValue = - 1 ;
break;
// AB WERE HIGH
case 14:
if ((RotaryEncoderStatus & 0x02) == 0x00) EncoderValue = + 1;
else EncoderValue = - 1 ;
break;
}
// POLARITY
if (Ganz == 0)
{
Polarity = !Polarity ;
digitalWrite(PolarityPin, Polarity) ;
UpDateOLED();
RotaryEncoderActivity = 0x00 ;
}
// HIGH TIME
if (Ganz == 1)
{
if (Rest == 1) IncDecMultiply = 100000 ;
if (Rest == 2) IncDecMultiply = 10000 ;
if (Rest == 3) IncDecMultiply = 1000 ;
if (Rest == 4) IncDecMultiply = 100 ;
if (Rest == 5) IncDecMultiply = 10 ;
if (Rest == 6) IncDecMultiply = 1 ;
IsHigh = IsHigh + EncoderValue * IncDecMultiply ;
if (IsHigh < MinHigh) IsHigh = MinHigh ;
if (IsHigh > MaxHigh) IsHigh = MaxHigh ;
Frequency = 1/(0.000001*IsHigh + 0.000001*IsLow) ;
UpDateOLED();
RotaryEncoderActivity = 0x00 ;
}
// LOW TIME
if (Ganz == 2)
{
if (Rest == 1) IncDecMultiply = 100000 ;
if (Rest == 2) IncDecMultiply = 10000 ;
if (Rest == 3) IncDecMultiply = 1000 ;
if (Rest == 4) IncDecMultiply = 100 ;
if (Rest == 5) IncDecMultiply = 10 ;
if (Rest == 6) IncDecMultiply = 1 ;
IsLow = IsLow + EncoderValue * IncDecMultiply ;
if (IsLow < MinLow) IsLow = MinLow ;
if (IsLow > MaxLow) IsLow = MaxLow ;
Frequency = 1/(0.000001*IsHigh + 0.000001*IsLow) ;
UpDateOLED();
RotaryEncoderActivity = 0x00 ;
}
// FREQUENCY
if (Ganz == 3)
{
if (Rest == 1) IncDecMultiply = 1000 ;
if (Rest == 2) IncDecMultiply = 100 ;
if (Rest == 3) IncDecMultiply = 10 ;
if (Rest == 4) IncDecMultiply = 1 ;
IsLowOld = IsLow ;
IsHighOld = IsHigh ;
// TARGET FREQUENCY
TargetFrequency = Frequency + EncoderValue * IncDecMultiply ;
OldNewRatio = Frequency / TargetFrequency ;
IsHigh = IsHigh * OldNewRatio ;
IsLow = IsLow * OldNewRatio ;
// IF IsHigh AND IsLow == 1 THEN WE HAVE A PROBLEM
while ((IsLowOld == IsLow) && (IsHighOld = IsHigh) && (OldNewRatio != 1.0))
{
OldNewRatio = OldNewRatio * OldNewRatio ;
IsHigh = IsHigh * OldNewRatio ;
IsLow = IsLow * OldNewRatio ;
// Serial.println(OldNewRatio,5) ;
}
if (IsLow < MinLow) IsLow = MinLow ;
if (IsLow > MaxLow) IsLow = MaxLow ;
if (IsHigh < MinHigh) IsHigh = MinHigh ;
if (IsHigh > MaxHigh) IsHigh = MaxHigh ;
Frequency = 1/(0.000001*IsHigh + 0.000001*IsLow) ;
UpDateOLED();
RotaryEncoderActivity = 0x00 ;
}
}
// /////////////////////////////////////////////////////////////
// END OF FILE.
// /////////////////////////////////////////////////////////////
✈ 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 ?