Select Git revision
cpplint.cmake
appendix.tex 32.97 KiB
\chapter*{Appendix}
\addcontentsline{toc}{chapter}{Appendix}
%\pagenumbering{roman}
%\sectionnumbering{roman}
\setcounter{section}{0}
\renewcommand{\thesection}{\Alph{section}}
\section{LockIn amplifier settings}\label{app:lock_in}
\begin{table}[H]
\centering
\begin{tabular}{|l|l|}
\hline
Setting & Value \\ \hline
Frequency & 2 mV \\ \hline
Phase Shift & 0 \\ \hline
Harmonic & 1 \\ \hline
Trigger & sine \\ \hline
Sensitivity & variable* \\ \hline
\multicolumn{1}{|l|}{Time constant} & 300 ms \\ \hline
\multicolumn{1}{|l|}{Input} & Current 10\textasciicircum{}6 \\ \hline
\multicolumn{1}{|l|}{Couple} & AC \\ \hline
\end{tabular}
\caption{Settings used for the Lock-in amplifier in the experiment. Sensitivity is increased a step at a time until the Lock-in is no longer in overload. Source is chosen as internal. Ground is set to grounded.}
\label{fig:app_lock_in}
\end{table}
\section{Evaporation parameters} \label{app:evaporation}
\begin{table}[H]
\begin{tabular}{|c|c|c|c|c|c|c|}
\hline
& Time {[}min{]} & FIL {[}A{]} & EMIS {[}mA{]} & FLUX {[}nA{]} & Press. {[}mbar{]} & T {[}°C{]} \\ \hline \hline
Evap. 1 & $40$ & $1.75$ & $9.1-6.9$ & $490-540$ &$5.37 \times 10^{-9}$& $24 $ \\ \hline
Evap. 2 & $40$ & $1.75$ & $9.7-5.4$ & $450-530$ &$3.86 \times 10^{-9}$& $24$ \\ \hline
Evap. 3 & $40$ & $1.75$ & $9.5-5.3$ & $470-520$ &$3.21 \times 10^{-9}$& $24$ \\ \hline
Evap. 4 & $40$ & $1.75$ & $10.0-4.9$ & $460-510$ &$2.99 \times 10^{-9}$& $24$ \\ \hline
Evap. 5 & $40$ & $1.75$ & $6.8-4.7$ & $450-500$ &$2.86 \times 10^{-9}$& $24$ \\ \hline
\end{tabular}
\caption{Table with all the evaporation parameters. FIL stands for the current applied to the heating Filament, EMIS stands for the emission current, FLUX is the measured molecular flux. Press is the maximum pressure in the chamber during the evaporation, and T is the maximal temperature the crucible reached during the evaporation. The voltage was changed to ensure FLUX was in the desired range between $450-520$}
\label{tab:evaporation_settings}
\end{table}
%
%\begin{figure}[H]
% \centering
% \includegraphics[width=0.8\linewidth]{img/Evaporation/MaskAlignerChamber_evap.pdf}
% \caption{The Mask Aligner chamber configuration during evaporation.}
% \label{fig:evaporation_chamber_status}
%\end{figure}
\section{Walker principle diagram}\label{app:walker_diagram}
\begin{figure}[H]
\centering
\includegraphics[width=\linewidth, angle=90]{img/ElectronicsDiagramm.pdf}
\caption{Overview of the entire signal generation and amplification process of the new Mask Aligner controller.}
\label{fig:electronics_diagramm}
\end{figure}
\section{Walker circuit diagrams}\label{app:circuit_electronics}
\includepdf[pages=-,pagecommand={},width=\textwidth,angle=90]{img/Plots/Walker/MaskAlign Walker Signalelektronik 1.0.pdf}
\includepdf[pages=-,pagecommand={},width=\textwidth,angle=90]{img/Plots/Walker/MaskAlign Walker Netzteil modifiziert 24-05-2024.pdf}
\section{Mask Aligner Walker Commands}\label{sec:appendix_walker}
The commands need to be sent to the new driver electronics using a serial interface with a baudrate of $115200$ and either new line or carriage return as line end characters. The easiest way to do this is via the Serial Monitor of the Arduino IDE.
The new driver electronics have the following commands (as of 13.08.24) that can set the driving parameters:
\paragraph{pulse?}
Queries if a pulse is currently running. Returns $0$ (false) or $1$ (true)
\paragraph{pol x}
Changes the polarization direction. Default state is $1$, but can be set to $-1$ to make approach be the positive and retract the negative polarity.
\paragraph{amp x}
Changes the amplitude of the internally generated pulse. This affects the output peak voltage. The maximum value is $100$, which corresponds to $120$ V peak to peak. The default value is $67$, which corresponds to $80.4$ V.
\paragraph{volt x}
Sets the output voltage of the signal generated. Internally, this sets the amp parameter, which has limited precision. Due to this, the voltage value input will only be output approximately. The possible range is $0$-$120$ V peak. The script will give back the actual voltage that will be output. Default is $80$ V.
\paragraph{channel x}
Sets which channels should be output. X is of the form $z_1z_2z_3x$ where each value can be either $0$ to not pass any signal through or $1$ to pass signal through. For example, activating only channels Z1 and X would be accomplished by "channel 1001". The command has an alias as "ch x", which can be used in exactly the same way.
\paragraph{maxmstep x}
Sets the maximum amount of steps that can be run at once. This is a safety measure to ensure no one accidentally types a value too large and crashes the mask into the sample. If the value of steps is exceeded, the command will be ignored. This command has an alias as "maxsteps x", which can be used in exactly the same way. Allowed values are any positive integers.
\paragraph{step x}
Drives a single step in the specified direction. Allowed values are $1$ or $-1$. Direction of movement is determined by the "pol" parameter, by default $-1$ approaches and $1$ retracts.
\paragraph{mstep x}
Drives x steps in a row in the specified direction. Allowed values are any positive or negative integer smaller than "maxmstep". Direction of movement is determined by the "pol" parameter, by default $-$ approaches and $+$ retracts. This command has an alias as "steps x", which can be used in exactly the same way.
\paragraph{cancel}
Cancels the current pulses running. This command has multiple aliases as "stop" and "abort", as well as "c", which can be used in exactly the same way.
\paragraph{help}
Displays a list of all commands along with short explanations on how to use them.
Any of the parameters that can set one of the values can be queried for their current value by using command? (for example pol?).
\section{Mask Aligner Walker Code}\label{sec:walker_code}
\begin{lstlisting}[basicstyle=\ttfamily\scriptsize, language=C++, xleftmargin=0cm, tabsize=2]
#include "DueTimer.h" // Library used for timing
#include "DueFlashStorage.h" // Probably not needed
#define BAUDRATE 115200
// 115200 baud is reliable
#define FS (84000000.0/52.0/2.0)
// sampling frequency of dac in dual channel mode
#define PERIODSINGLE 0.00049356
// fitparameters for signal 5500Hz 8th order bessel lowpass
#define TOFFSETSINGLE 0.000085362
// fitparameters for signal 5500Hz 8th order bessel lowpass
#define NSINGLE 382
// approx 0.5ms - 5\% * 0.5 ms of buffer length for dual channel mode
#define NWAVEFORMSINGLE 6
// 2 waveforms = 2 channels + 4 fast channel high/low combinations
// Pin for polarity switch
#define SWITCH_POL 30
// pins of the switches for the 4 different channels
#define SWITCH1 22 //channel Z1
#define SWITCH2 24 //channel Z2
#define SWITCH3 26 //channel Z3
#define SWITCH4 28 //channel X
// pins of the shutters for the 4 different channels
#define SHUTTER1 53 //channel Z1
#define SHUTTER2 51 //channel Z2
#define SHUTTER3 49 //channel Z3
#define SHUTTER4 47 //channel X
#define BUFFER_SIZE 256 // serial buffer character length
#define CR 13
#define LF 10
uint32_t waveform[NWAVEFORMSINGLE][NSINGLE];
uint32_t waveform_zero[NSINGLE];
uint32_t sin_func[NSINGLE];
int32_t pulse_buffer = 0;
struct Configuration {
int8_t output_polarity = 1; // output_polarity of the signal
// for buttons and serial interface
uint8_t amplitude = 100; // amplitude in percent, realized by DAC,
// so affecting output smoothness (int 0 to 100)
bool channelZ1 = true; bool channelZ2 = true;
bool channelZ3 = true; bool channelX = true;
int32_t max_steps = 10000;
uint32_t shutter_idle_time = 5000000;
};
Configuration configuration;
DueFlashStorage dueFlashStorage;
char serial_buffer[BUFFER_SIZE];
uint8_t save_state_flag = 0;
bool no_signal = false;
bool shutter_off = false;
void turn_on_signal(){
no_signal = false;
Timer4.detachInterrupt();
Timer4.stop();
if(configuration.channelZ1)
digitalWrite(SHUTTER1, HIGH);
if(configuration.channelZ2)
digitalWrite(SHUTTER2, HIGH);
if(configuration.channelZ3)
digitalWrite(SHUTTER3, HIGH);
if(configuration.channelX)
digitalWrite(SHUTTER4, HIGH);
digitalWrite(SWITCH_POL, HIGH);
shutter_off = true;
}
void turn_off_signal(){
no_signal = true;
next_dac_vector(NSINGLE, waveform_zero);
schedule_shutter_on();
}
void schedule_shutter_on(){
Timer4.attachInterrupt(turn_shutter_on).start(configuration.shutter_idle_time);
}
void turn_shutter_on(){
Timer4.detachInterrupt();
digitalWrite(SHUTTER1, LOW);
digitalWrite(SHUTTER2, LOW);
digitalWrite(SHUTTER3, LOW);
digitalWrite(SHUTTER4, LOW);
shutter_off = false;
}
void control_switches(){
turn_shutter_on();
if(configuration.channelZ1)
digitalWrite(SWITCH1, HIGH);
else
digitalWrite(SWITCH1, LOW);
if(configuration.channelZ2)
digitalWrite(SWITCH2, HIGH);
else
digitalWrite(SWITCH2, LOW);
if(configuration.channelZ3)
digitalWrite(SWITCH3, HIGH);
else
digitalWrite(SWITCH3, LOW);
if(configuration.channelX)
digitalWrite(SWITCH4, HIGH);
else
digitalWrite(SWITCH4, LOW);
}
void delayed_switch_pol_high(){
digitalWrite(SWITCH_POL, HIGH);
Timer2.stop();
}
void delayed_switch_pol_low(){
digitalWrite(SWITCH_POL, LOW);
Timer2.stop();
}
//Inelegant but functional and probably still very fast
int8_t polarity_table(uint8_t x, uint8_t y){
if(x == 0 && y == -1)
return 0;
else if(x == 0 && y == 1)
return 1;
else if(x == 1 && y == -1)
return 1;
else if(x == 1 && y == 1)
return 0;
}
uint8_t state = 0;
const uint8_t FIRST = 0; const uint8_t NORMALDOWN = 1; const uint8_t NORMALUP = 2;
const uint8_t LAST = 3; const uint8_t STOP = 4;
int8_t last_sig;
const uint8_t polarity_delay = 25;
//State machine driving the pulses
//Pulse is made up of 6 possible parts:
//Curve from -3.3 to 3.3 V
//Curve from 3.3 to -3.3 V
//Curve from 0 to 3.3 V and vice versa for start and end pulses
//Curve from 0 to -3.3 V and vice versa
void pulse_start(){
switch (state)
{
case FIRST: //First pulse
if(abs(pulse_buffer) > 0){
turn_on_signal();
if(shutter_off){
Timer3.stop();
delayMicroseconds(16383);
Timer3.start(500);
}
digitalWrite(SWITCH_POL, LOW);
next_dac_vector(NSINGLE, waveform[2 +
polarity_table(configuration.output_polarity, signum(pulse_buffer))]);
last_sig = signum(pulse_buffer);
pulse_buffer -= signum(pulse_buffer);
state = pulse_buffer == 0 ? LAST : NORMALDOWN;
}
break;
case NORMALDOWN: //Switch pulse ON
next_dac_vector(NSINGLE, waveform[1 -
polarity_table(configuration.output_polarity, signum(pulse_buffer))]);
if(polarity_delay == 0)
digitalWrite(SWITCH_POL, HIGH);
else
Timer2.attachInterrupt(delayed_switch_pol_high).start(polarity_delay);
state = NORMALUP;
break;
case NORMALUP: //Switch pulse OFF
next_dac_vector(NSINGLE, waveform[0 +
polarity_table(configuration.output_polarity, signum(pulse_buffer))]);
last_sig = signum(pulse_buffer);
if(pulse_buffer != 0)
pulse_buffer -= signum(pulse_buffer);
if(polarity_delay == 0)
digitalWrite(SWITCH_POL, LOW);
else
Timer2.attachInterrupt(delayed_switch_pol_low).start(polarity_delay);
state = pulse_buffer == 0 ? LAST : NORMALDOWN;
break;
case LAST: //Last pulse
next_dac_vector(NSINGLE, waveform[5 -
polarity_table(configuration.output_polarity, last_sig)]);
if(polarity_delay == 0)
digitalWrite(SWITCH_POL, HIGH);
else
Timer2.attachInterrupt(delayed_switch_pol_high).start(polarity_delay);
state = STOP;
break;
case STOP: //Switch pulse ON
//Timer2.attachInterrupt(delayed_switch_pol_low).start(polarity_delay);
state = FIRST;
turn_off_signal();
break;
}
}
void setup() {
//put your setup code here, to run once:
Serial.begin(BAUDRATE);
Serial.setTimeout(50);
switch_setup();
dac_timer_setup();
dac_setup();
turn_shutter_on();
//Run timer that times pulses for 1kHz
Timer3.attachInterrupt(pulse_start).start(500);
//generate_sin();
generate_waveforms();
control_switches();
}
void switch_setup (){
pinMode(SWITCH1, OUTPUT);
pinMode(SWITCH2, OUTPUT);
pinMode(SWITCH3, OUTPUT);
pinMode(SWITCH4, OUTPUT);
pinMode(SWITCH_POL, OUTPUT);
pinMode(SHUTTER1, OUTPUT);
pinMode(SHUTTER2, OUTPUT);
pinMode(SHUTTER3, OUTPUT);
pinMode(SHUTTER4, OUTPUT);
digitalWrite(SWITCH1, LOW);
digitalWrite(SWITCH2, LOW);
digitalWrite(SWITCH3, LOW);
digitalWrite(SWITCH4, LOW);
digitalWrite(SHUTTER1, LOW);
digitalWrite(SHUTTER2, LOW);
digitalWrite(SHUTTER3, LOW);
digitalWrite(SHUTTER4, LOW);
}
//////////////////////////////////////////////////////////
//
// DAC RELATED FUNCTIONS
//
//////////////////////////////////////////////////////////
void dac_timer_setup (){
//The following creates a 84/52MHz square wave on
//PWM pin 2 with 50% dutycycle. It drives the DAC conversions
pmc_enable_periph_clk(TC_INTERFACE_ID + 0 * 3 + 0); // clock the TC0 channel 0
TcChannel * t = &(TC0->TC_CHANNEL)[0];
// pointer to TC0 registers for its channel 0
t->TC_CCR = TC_CCR_CLKDIS;
// disable internal clocking while setup regs
t->TC_IDR = 0xFFFFFFFF;
// disable interrupts
t->TC_SR;
// read int status reg to clear pending
t->TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 |
// use TCLK1 (prescale by 2, = 42MHz)
TC_CMR_WAVE |
// waveform mode
TC_CMR_WAVSEL_UP_RC |
// count-up PWM using RC as threshold
TC_CMR_EEVT_XC0 |
// Set external events from XC0 (this setup TIOB as output)
TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_CLEAR |
TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_CLEAR;
t->TC_RC = 26; // counter resets on RC, so sets period in terms of 42MHz clock
t->TC_RA = 13; // square wave
t->TC_CMR = (t->TC_CMR & 0xFFF0FFFF) | TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET;
// set clear and set from RA and RC compares
t->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG;
// re-enable local clocking and switch to hardware trigger source.
}
void dac_setup (){
pmc_enable_periph_clk (DACC_INTERFACE_ID) ; // start clocking DAC
dacc_reset(DACC);
dacc_set_transfer_mode(DACC, 1);
// write to both channels in word mode
dacc_set_power_save(DACC, 0, 1); // sleep = 0, fastwkup = 1
dacc_set_analog_control(DACC, DACC_ACR_IBCTLCH0(0x02)
| DACC_ACR_IBCTLCH1(0x02) | DACC_ACR_IBCTLDACCORE(0x01));
//For DAC frequency > 500 KHz !?!?!?!?!
dacc_set_trigger(DACC, 1);
//TIO Output of the Timer Counter Channel 0
dacc_set_timing(DACC, 1, 0, 8);
//uint32_t ul_refresh (refresh minimum seems to be 1024*1/42=24mus
//, uint32_t ul_maxs, uint32_t ul_startup (startup is set to 512 dac clocks = 12mus)
dacc_enable_channel(DACC, 0);
dacc_enable_channel(DACC, 1);
dacc_enable_flexible_selection(DACC);
NVIC_DisableIRQ(DACC_IRQn);
NVIC_ClearPendingIRQ(DACC_IRQn);
NVIC_EnableIRQ(DACC_IRQn);
DACC->DACC_PTCR = DACC_PTCR_TXTEN;
// Enable PDC Transmit channel request
uint32_t zero_dual_dac = (0 << 12) | 0 | (0 << 16) | (1 << 28);
// bit 12 selects dac0, bits 0-11 give value,
// bit 28 selects dac1, bits 16-27 give value
next_dac_vector(1, &zero_dual_dac);
}
void next_dac_vector(int nwave, uint32_t *wave){
DACC->DACC_TNPR = (uint32_t) wave; // DMA buffer
DACC->DACC_TNCR = nwave;
}
void generate_sin(){
for (int i=0; i < NSINGLE; i++){
double t = i/FS+TOFFSETSINGLE;
const double off = 100/FS;
}
}
void generate_waveforms(){
// we generate waveform data for dual dac channels
const uint32_t chsel = (0<<12) | (1<<28); //mask for lower bits dac0, higher bits dac1
double ch1 = 0;
double ch2 = 0;
double ch1_fast = 0;
double ch2_fast = 0;
for (int i=0; i < NSINGLE; i++){
double t = i/FS+TOFFSETSINGLE;
const double off = 100/FS;
//rising slope. Equation calculated by Marcus Liebmann.
//Acceleration is continuous sine.
if(t <= off) ch1 = 0.00;
else if ((t - off) <= PERIODSINGLE) ch1 = 0.01 * configuration.amplitude *
4095.0 * (-1.0/(2.0*PI) * sin(2.0 * PI *
(t - off)/PERIODSINGLE) + (t - off)/PERIODSINGLE);
else ch1 = 0.01 * configuration.amplitude * 4095.0;
ch2 = 0.01 * configuration.amplitude * 4095.0 - ch1;
//Flat Wave fore zeroing
waveform_zero[i] = 0 | 0 << 16 | chsel;
}
}
//////////////////////////////////////////////////////////
//
// SERIAL COMMANDS (VISA STYLE)
//
//////////////////////////////////////////////////////////
void process_serial_byte(char c){
if (c == CR || c == LF) {
if (strlen(serial_buffer) > 0) {
parse_command(serial_buffer);
}
reset_buffer();
}
else if (c > 32 && c < 127) {
add_to_buffer(c);
}
}
void reset_buffer() {
memset(&serial_buffer[0], 0, sizeof(serial_buffer));
}
void add_to_buffer(char c) {
if (strlen(serial_buffer) >= BUFFER_SIZE - 1) {
reset_buffer();
append_char(serial_buffer, c);
}
else {
append_char(serial_buffer, c);
}
}
void append_char(char* s, const char c) {
const int len = strlen(s);
if (len < BUFFER_SIZE - 1) {
s[len] = c;
s[len + 1] = '\0';
}
}
void parse_command(char *command) {
// Split command at semicolons
char *ptr;
ptr = strtok(command, ";");
// The value is going to be used for sscanf
int32_t value;
// Parse commands
while (ptr != NULL) {
// Identification
if (strcasecmp(ptr, "*idn?") == 0)
Serial.println("MAW 0113");
//This is the old identification change when ID is known
// Query
else if (strcasecmp(ptr, "pulse?") == 0)
Serial.println(abs(pulse_buffer) > 0);
else if (strcasecmp(ptr, "pol?") == 0)
Serial.println(configuration.output_polarity);
else if (strcasecmp(ptr, "amp?") == 0)
Serial.println(configuration.amplitude);
else if (strcasecmp(ptr, "volt?") == 0)
Serial.println((double) configuration.amplitude * 1.2);
else if (strcasecmp(ptr, "ch?") == 0)
{Serial.print("[ "); Serial.print(configuration.channelZ1);
Serial.print(" "); Serial.print(configuration.channelZ2);
Serial.print(" "); Serial.print(configuration.channelZ3);
Serial.print(" "); Serial.print(configuration.channelX);
Serial.println(" ]");}
else if (strcasecmp(ptr, "channel?") == 0)
{Serial.print("[ "); Serial.print(configuration.channelZ1);
Serial.print(" "); Serial.print(configuration.channelZ2);
Serial.print(" "); Serial.print(configuration.channelZ3);
Serial.print(" "); Serial.print(configuration.channelX);
Serial.println(" ]");}
else if (strcasecmp(ptr, "maxsteps?") == 0)
Serial.println(configuration.max_steps);
else if (strcasecmp(ptr, "maxmstep?") == 0)
Serial.println(configuration.max_steps);
// Set
else if (sscanf (ptr,"pol%i%99[^\n]",&value) == 1)
configuration.output_polarity = validate_polarity(value);
else if (sscanf (ptr,"amp%i%99[^\n]",&value) == 1)
configuration.amplitude = validate_amplitude(value);
else if (sscanf (ptr,"volt%i%99[^\n]",&value) == 1)
configuration.amplitude = validate_voltage(value);
else if (sscanf (ptr,"step%i%99[^\n]",&value) == 1)
pulse_buffer = validate_single_step(value);
else if (sscanf (ptr,"mstep%i%99[^\n]",&value) == 1)
pulse_buffer = validate_multi_step(value);
else if (sscanf (ptr,"steps%i%99[^\n]",&value) == 1)
pulse_buffer = validate_multi_step(value);
else if (sscanf (ptr,"channel%x%99[^\n]", &value) == 1)
set_channel(value); else if (sscanf (ptr,"ch%x%99[^\n]", &value) == 1)
set_channel(value);
//Abort signal
else if (strcasecmp(ptr, "cancel") == 0)
{stop_current_pulse_sequence();} else if (strcasecmp(ptr, "stop") == 0)
{stop_current_pulse_sequence();} else if (strcasecmp(ptr, "abort") == 0)
{stop_current_pulse_sequence();}
else if (sscanf (ptr,"maxmstep%i%99[^\n]",&value) == 1)
configuration.max_steps = validate_multi_step_max(value);
else if (sscanf (ptr,"maxmsteps%i%99[^\n]",&value) == 1)
configuration.max_steps = validate_multi_step_max(value);
// Help
else if (strcasecmp(ptr, "help") == 0) {print_help();}
// Save the state
if (save_state_flag){
save_state_flag = 0;
save_state();
}
// Move command pointer
ptr = strtok(NULL, ";");
}
}
//////////////////////////////////////////////////////////
//
// Validate Parsed inputs
//
//////////////////////////////////////////////////////////
void stop_current_pulse_sequence(){
Serial.println("Aborting pulse sequence!");
pulse_buffer = 0;
turn_off_signal();
}
void print_help(){
Serial.println("\"pulse?\" queries if there is currently a
pulse running. Returns 0 (false) or 1 (true)");
Serial.println("\"pol?\" queries current polarization direction.");
Serial.println("\"amp?\" queries current pulse amplitude.");
Serial.println("\"volt?\" queries current pulse voltage.");
Serial.println("\"channel?\" queries current channels status
order Z1 Z2 Z3 X. 0 is off 1 is on.");
Serial.println("\"maxmstep?\" queries current maximum allowed
steps for the mstep function. Aliases: \"maxsteps?");
Serial.println("\"polx\" changes the current polarization direction.
Accepted values: 1,-1");
Serial.println("\"amp x\" changes the current amplitude of
the signal (100 = 120 V).
Accepted values: 0 to 100");
Serial.println("\"volt x\" changes the current amplitude as a
voltage value.
This gives only approximate Voltages due to integer conversion.
Maximal conversion error is +- 0.5 V Accepted values: 0 to 120.");
Serial.println("\"step x\" drives single step. Accepted values: 1,-1");
Serial.println("\"mstep x\" drives multiple steps consecutively.
Aliases: \"steps x\", Default accepted values: -10000 to 10000");
Serial.println("\"maxmstep x\" changes the maximum allowed
steps for the mstepfunction.
Aliases: \"maxsteps. Default: 10000");
Serial.println("\"channel x\" changes channels to state x.
x is a bitmask Z1|Z2|Z3|X.
0 is off 1 is on. So turning on channels 1 and 3 would be
1010. Aliases: \"ch x\"
Accepted values: x:0000 to 1111 in binary");
Serial.println("\"cancel\" stops all current pulses.
Will finish the currently running pulse.
Aliases: \"stop\", \"abort\"");
Serial.println("\"help\" shows this information.");
}
//Value is read as a hexadecimal input value in the form 0000
//then it checks every forth digit which responds to doing binary
//comparison digit by digit for value and assigns motors
void set_channel(int value){
if (pulse_buffer != 0) {
Serial.println("Error: Wait for current steps to finish!");
return;
}
if(value > 4369 || value < 0){
Serial.println("Error: Invalid value for x in \"channel\" command!");
return;
}
configuration.channelX = (value & (1 << 0)) == 1;
configuration.channelZ3 = (value & (1 << 4)) == 16;
configuration.channelZ2 = (value & (1 << 8)) == 256;
configuration.channelZ1 = (value & (1 << 12)) == 4096;
control_switches();
Serial.print("Setting channel to: ");
Serial.print("[ "); Serial.print(configuration.channelZ1);
Serial.print(" "); Serial.print(configuration.channelZ2);
Serial.print(" "); Serial.print(configuration.channelZ3);
Serial.print(" ");
Serial.print(configuration.channelX); Serial.println(" ]");
Serial.print("[ "); Serial.print(configuration.channelZ1);
Serial.print(" "); Serial.print(configuration.channelZ2);
Serial.print(" "); Serial.print(configuration.channelZ3);
Serial.print(" "); Serial.print(configuration.channelX);
Serial.println(" ]");
}
uint8_t validate_polarity(int value){
if (abs(value) != 1) {
Serial.println("Error: Invalid polarity!");
return configuration.output_polarity;
}
save_state_flag = 1;
Serial.print("Polarity changed to: ");
Serial.println(value == 1);
return (uint8_t) value == 1;
}
uint8_t validate_amplitude(int value){
if (value < 0 || value > 100) {
Serial.println("Error: Invalid amplitude!");
return configuration.amplitude;
}
configuration.amplitude = (uint8_t) value;
generate_waveforms();
save_state_flag = 1;
Serial.print("Amplitude changed to: ");
Serial.println(value);
return (uint8_t) value;
}
uint8_t validate_voltage(int value){
if (value < 0 || value > 120) {
Serial.println("Error: Invalid voltage!");
return configuration.amplitude;
}
uint8_t amp = round((double) value * 0.83333);
if(amp > 120)
amp = 120;
configuration.amplitude = amp;
generate_waveforms();
save_state_flag = 1;
Serial.print("Amplitude changed to: ");
Serial.println(amp);
Serial.print("Voltage changed to: ");
Serial.println((double) amp * 1.2);
return (uint8_t) amp;
}
int32_t validate_multi_step(int value){
if (pulse_buffer != 0) {
Serial.println("Error: Wait for current steps to finish!");
return pulse_buffer;
}
if (abs(value) > configuration.max_steps) {
Serial.println("Error: Invalid value for
\"mstep\" command! Larger than allowed maximum!");
return pulse_buffer;}
Serial.print("Stepping: ");
Serial.print(value);
Serial.println(" steps");
return (int32_t) value;
}
int32_t validate_multi_step_max(int32_t value){
if (value < 0) {
Serial.println("Error: Invalid value for
\"maxmstep\" command!");
return pulse_buffer;}
Serial.print("Setting new max_step: ");
Serial.print(value);
Serial.println(" steps");
return (int32_t) value;
}
int16_t validate_single_step(int value){
if (pulse_buffer != 0) {
Serial.print("Error: Wait for current steps to finish!");
return pulse_buffer;
}
if (value != -1 && value != 1) {
Serial.print("Error: Invalid value for
\"step\" command!");
return pulse_buffer;}
//return (int16_t) (value * configuration.pulses_per_trigger);
Serial.print("Stepping: ");
Serial.println("single step");
return (int16_t) (value);
}
int32_t validate_idle_time(int value){
if (value < 0) {
Serial.println("Error: Invalid value for
\"idletime\" command! Only positive numbers!");
return pulse_buffer;
}
if (value < 100000) {
Serial.println("Error: Invalid value for
\"idletime\" command! Value chosen too small!");
return pulse_buffer;
}
Serial.print("Setting idle timer to: ");
Serial.println(value);
return (int32_t) value;
}
//////////////////////////////////////////////////////////
//
// SAVE AND RECALL STATE
//
//////////////////////////////////////////////////////////
//unused
void save_state(){
byte config_bytes[sizeof(Configuration)];
// create byte array to store the struct
memcpy(config_bytes, &configuration, sizeof(Configuration));
// copy the struct to the byte array
dueFlashStorage.write(4, config_bytes, sizeof(Configuration));
// write byte array to flash at address 4
}
//unused
void recall_state(){
if (dueFlashStorage.read(0)){ //codeRunningForTheFirstTime
dueFlashStorage.write(0, 0);
save_state();
return;
}
byte* config_bytes = dueFlashStorage.readAddress(4);
// byte array which is read from flash at adress 4
Configuration cff; // create a temporary struct
memcpy(&cff, config_bytes, sizeof(Configuration));
// copy byte array to temporary struct
// validate
if (cff.output_polarity == 1 || cff.output_polarity == -1)
configuration.output_polarity = cff.output_polarity;
if (cff.amplitude >= 0 && cff.amplitude <= 100)
configuration.amplitude = cff.amplitude;
}
void loop() {
// Read serial port commands
if (Serial.available()) {
process_serial_byte(Serial.read());
}
// Read serial port commands
//pulse_buffer = Serial.parseInt();
}
//////////////////////////////////////////////////////////
//
// Auxillary and helper functions
//
//////////////////////////////////////////////////////////
int signum(int x) {
return (x > 0) - (x < 0);
}
\end{lstlisting}
\section{Raycast Simulation}\label{sec:appendix_raycast}
The ray casting simulation takes the following parameters:
\paragraph{radius\_1}
The radius of the circle representing the crucible in Godot spacial units.
\paragraph{angle}
The angle of the cone in which rays are emitted from the crucible.
\paragraph{radius\_mask}
The radius of the cylinder collider, representing the hole in the mask.
\paragraph{distance\_circle\_mask}
The distance between crucible and mask in Godot spacial units.
\paragraph{distance\_sample}
The distance between the sample and the mask in Godot spacial units.
\paragraph{rays\_per\_frame}
The amount of rays cast per time step of computation. Higher values mean more "material" gets deposited.
\paragraph{running\_time}
The amount of time steps the simulation is run for.
\paragraph{deposition\_gain}
A multiplier to the amount of "material" that gets deposited for every ray that hits the sample. High values lead to grainier image, but allow for less computation time.
\paragraph{penalize\_deposition}
A boolean value that determines if a ray hitting a spot on the sample where previously nothing was deposited is less likely to deposit, than one that already has a deposit on the pixel for that spot.
\paragraph{first\_layer\_deposition\_prob}
The probability that material gets deposited on a pixel when previously no material had been deposited there. Does nothing if penalize\_deposition is false.
\paragraph{oscillation\_period}
The period in time steps for the noise oscillation.
\paragraph{delay\_oscill\_time}
The time delay in time steps before the noise oscillation starts.
\paragraph{save\_in\_progress\_images}
Boolean value, determining if images are stored before the full simulation time has elapsed. If false, only one image is saved at the end.
\paragraph{save\_intervall}
The interval at which images are stored in time steps. Does nothing if save\_in\_progress\_images is false.
\paragraph{oscillation\_dir}
The direction of translational displacement enacted by the oscillation. After half a period, the hole collider will be displaced by this amount. Oscillation always starts at the origin in x and z.
\paragraph{oscillation\_rot\_s}
The starting rotation of the hole in degrees. The hole collider oscillates between oscillation\_rot\_s and oscillation\_rot\_e. For constant tilt, set them both the same.
\paragraph{oscillation\_rot\_e}
The ending rotation of the hole in degrees. The hole collider oscillates between oscillation\_rot\_s and oscillation\_rot\_e. For constant tilt, set them both the same.
\paragraph{random\_seed}
The random seed for the pseudo random number generator used to generate rays. Can be set to get consistent results.
\paragraph{x\_min, x\_max, y\_min, y\_max}
The outer edges of the 2D image of the sample collider in Godot spacial coordinates.
\paragraph{resolution}
The resolution of the image in pixels.
\paragraph{path}
The path the images are saved to. In progress, images are saved with an additional step number appended to the string before the file format. File format must be .csv otherwise script will fail.