ATM/ATM.ino

458 lines
17 KiB
C++

#include <TMC2130Stepper.h>
#include <AccelStepper.h>
#include <MultiStepper.h>
#include <math.h>
#include "ADF4351.h"
//SPI Pins
#define SCLK_PIN 14
#define MOSI_PIN 13
#define MISO_PIN 12
//ADF Pins
#define LE_PIN 27
#define CE_PIN 25
// Pins M1
#define EN_PIN_M1 25
#define DIR_PIN_M1 33
#define STEP_PIN_M1 32
#define CS_PIN_M1 26
#define DIAG1_PIN_M1 2 // used for homing
// Pins M2
#define EN_PIN_M2 17
#define DIR_PIN_M2 19
#define STEP_PIN_M2 5
#define CS_PIN_M2 18
#define DIAG1_PIN_M2 0 // used for homing
// Stall Detection sensitivity
#define STALL_VALUE -64 // [-64..63]
//ADC Pin
#define REFLECTION_PIN 15
// Frequency Settings
#define FREQUENCY_STEP 100000U // 100kHz frequency steps for initial frequency sweep
#define START_FREQUENCY 80000000U // 80MHz
#define STOP_FREQUENCY 160000000 // 120MHz
// Stepper Settings
#define STEPS_PER_ROTATION 3200U // 200 * 16 -> Microstepping
ADF4351 adf4351(SCLK_PIN, MOSI_PIN, LE_PIN, CE_PIN); // declares object PLL of type ADF4351
TMC2130Stepper tuning_driver = TMC2130Stepper(EN_PIN_M1, DIR_PIN_M1, STEP_PIN_M1, CS_PIN_M1, MOSI_PIN, MISO_PIN, SCLK_PIN);
TMC2130Stepper matching_driver = TMC2130Stepper(EN_PIN_M2, DIR_PIN_M2, STEP_PIN_M2, CS_PIN_M2, MOSI_PIN, MISO_PIN, SCLK_PIN);
AccelStepper tuning_stepper = AccelStepper(tuning_stepper.DRIVER, STEP_PIN_M1, DIR_PIN_M1);
AccelStepper matching_stepper = AccelStepper(matching_stepper.DRIVER, STEP_PIN_M2, DIR_PIN_M2);
void IRAM_ATTR tuningStall() {
Serial.println("Stall");
}
void setup() {
Serial.begin(115200);
pinMode(MISO_PIN, INPUT_PULLUP); // Seems to be necessary for SPI to work
tuning_driver.begin(); // Initiate pins and registeries
tuning_driver.rms_current(400); // Set stepper current to 800mA. The command is the same as command TMC2130.setCurrent(600, 0.11, 0.5);
tuning_driver.microsteps(16);
tuning_driver.coolstep_min_speed(0xFFFFF); // 20bit max - needs to be set for stallguard
tuning_driver.diag1_stall(1);
tuning_driver.diag1_active_high(1);
tuning_driver.sg_stall_value(STALL_VALUE);
tuning_driver.shaft_dir(0);
pinMode(DIAG1_PIN_M1, INPUT);
attachInterrupt(DIAG1_PIN_M1, tuningStall, RISING);
Serial.print("DRV_STATUS=0b");
Serial.println(tuning_driver.DRV_STATUS(), BIN);
matching_driver.begin(); // Initiate pins and registeries
matching_driver.rms_current(600); // Set stepper current to 800mA. The command is the same as command TMC2130.setCurrent(600, 0.11, 0.5);
matching_driver.microsteps(16);
digitalWrite(EN_PIN_M1, LOW);
digitalWrite(EN_PIN_M2, LOW);
tuning_stepper.setMaxSpeed(12000);
tuning_stepper.setAcceleration(12000);
tuning_stepper.setEnablePin(EN_PIN_M1);
tuning_stepper.setPinsInverted(false, false, true);
tuning_stepper.enableOutputs();
matching_stepper.setMaxSpeed(12000);
matching_stepper.setAcceleration(12000);
matching_stepper.setEnablePin(EN_PIN_M2);
matching_stepper.setPinsInverted(false, false, true);
matching_stepper.enableOutputs();
tuning_stepper.setCurrentPosition(0);
matching_stepper.setCurrentPosition(0);
adf4351.begin();
adf4351.setrf(25000000U);
adf4351.pwrlevel = 0; // This equals -4dBm*/
adf4351.setf(START_FREQUENCY);
///////////////////// TESTING //////////////////////////// -> This works reliably
/*unsigned long stime = millis();
uint32_t target_frequency = 105000000U;
Serial.println("_______________________________________________");
Serial.println("Start - Target frequency is:");
Serial.println(target_frequency);
uint32_t resonance_frequency = findCurrentResonanceFrequency(START_FREQUENCY, STOP_FREQUENCY, FREQUENCY_STEP);
Serial.println("Resonance is at:");
Serial.println(resonance_frequency);
resonance_frequency = approximateResonance(target_frequency, resonance_frequency);
Serial.println("Resonance after approximation is at:");
Serial.println(resonance_frequency);
resonance_frequency = bruteforceResonance(target_frequency, resonance_frequency);
Serial.println("Resonance after bruteforce is at:");
Serial.println(resonance_frequency);
/*int reflection = optimizeMatching(resonance_frequency);
Serial.println("Minimum Reflection is:");
Serial.println(reflection);
resonance_frequency = findCurrentResonanceFrequency(START_FREQUENCY, STOP_FREQUENCY, FREQUENCY_STEP);
resonance_frequency = bruteforceResonance(target_frequency, resonance_frequency);
Serial.println("Resonance after bruteforce is at:");
Serial.println(resonance_frequency);*/
//Serial.println("Matched in s");
//Serial.println((millis()-stime)/ 1000);
}
// Implement Serial communication ...
// This could probably cleaned up by using structs for the inputs, pointing to the different functions- > would reduce copy-paste code and make adding functions more intuitive
void loop() {
if (Serial.available()) {
String input_line = Serial.readStringUntil('\n'); // read string until newline character
char command = input_line.charAt(0); // gets first character of input
// approximate call
// CAREFULL -> if the coil has no proper matching in the frequency range this will not work! Only use this for testing -> otherwise use the automated 'decide' call.
if (command == 'a') {
float target_frequency_MHz = input_line.substring(1).toFloat();
uint32_t target_frequency = validateInput(target_frequency_MHz);
if (target_frequency == 0) return;
Serial.println("Approximating matching to target frequency in MHz:");
Serial.println(target_frequency_MHz);
uint32_t resonance_frequency = findCurrentResonanceFrequency(START_FREQUENCY, STOP_FREQUENCY, FREQUENCY_STEP);
resonance_frequency = approximateResonance(target_frequency, resonance_frequency);
Serial.println("Resonance after approximation is at:");
Serial.println(resonance_frequency);
// bruteforce call
// CAREFULL -> if the current resonance frequency is not within +-5MHz of the target frequency this will not work. Only use this for testing -> otherwise use the automated 'decide' call.
} else if (command == 'b') {
float target_frequency_MHz = input_line.substring(1).toFloat();
uint32_t target_frequency = validateInput(target_frequency_MHz);
if (target_frequency == 0) return;
Serial.println("Bruteforce matching to target frequency in MHz:");
Serial.println(target_frequency_MHz);
uint32_t resonance_frequency = findCurrentResonanceFrequency(START_FREQUENCY, STOP_FREQUENCY, FREQUENCY_STEP);
resonance_frequency = bruteforceResonance(target_frequency, resonance_frequency);
Serial.println("Resonance after bruteforce is at:");
Serial.println(resonance_frequency);
// decide call
// this function decides what kind of t&m mode should be used based on the relationship between target frequency and current resonance
// it also makes sure that there a homing routine performed in case there is currently no proper resonance in the frequency range
} else if (command == 'd'){
float target_frequency_MHz = input_line.substring(1).toFloat();
uint32_t target_frequency = validateInput(target_frequency_MHz);
if (target_frequency == 0) return;
Serial.println("Tuning and Matching to target frequency in MHz (automatic mode):");
Serial.println(target_frequency_MHz);
uint32_t resonance_frequency = findCurrentResonanceFrequency(START_FREQUENCY, STOP_FREQUENCY, FREQUENCY_STEP);
//Serial.println(resonance_frequency); // debug line
int32_t delta_frequency = target_frequency - resonance_frequency; // needs to be int -> negative frequencies possible
if (abs(delta_frequency) > 5000000U) resonance_frequency = approximateResonance(target_frequency, resonance_frequency);
//Serial.println(resonance_frequency); // debug line
resonance_frequency = bruteforceResonance(target_frequency, resonance_frequency);
Serial.println("Resonance after tuning and matching is at:");
Serial.println(resonance_frequency);
// calibration call
// Perform the homing routine by looking for the limit of the capacitors
// it also places the steppers in a way so there is a resonance dip inside the frequency range
// CAREFULL -> The values are hardcoded, these need to be changed if there is a different coil in use
} else if (command == 'c'){
// frequency sweep call
// scans the frequency range for the current resonance frequency
} else if (command == 'f'){
uint32_t resonance_frequency = findCurrentResonanceFrequency(START_FREQUENCY, STOP_FREQUENCY, FREQUENCY_STEP);
Serial.println("Resonance is at:");
Serial.println(resonance_frequency);
// calculates Reflection loss for a given frequency
} else if (command == 'r'){
float frequency_MHz = input_line.substring(1).toFloat();
uint32_t frequency = validateInput(frequency_MHz);
if (frequency == 0) return;
float reflection_loss = calculateRL(frequency);
Serial.println("For frequency:");
Serial.println(frequency);
Serial.println("Reflection Loss in dB is:");
Serial.println(reflection_loss);
// Invalid Input
} else {
Serial.println("Invalid Input");
}
}
}
// This helper function checks if the input frequency is plausible, if so it returns the value in Hz
// otherwise it returns 0
uint32_t validateInput(float frequency_MHz){
uint32_t frequency_Hz = frequency_MHz * 1000000U;
if (frequency_Hz < START_FREQUENCY){
Serial.println("Invalid input: frequency too low");
return 0;
} else if (frequency_Hz > STOP_FREQUENCY) {
Serial.println("Invalid input: frequency too high");
return 0;
}else{
return frequency_Hz;
}
}
int readReflection(int averages){
int reflection = 0;
for (int i = 0; i < averages; i ++) reflection += analogReadMilliVolts(REFLECTION_PIN);
return reflection/averages;
}
void homeStepper(){
}
// calculates the Reflection Loss at a specified frequency
//24mV/dB slope
//0dBV defined as 1V Sin RMS
// Would expect 1.74V as output for unmatched coil -> but it's 1.65V => ~10mV at Logamp
// Measurments: with 40dB LNA @85MHz
// Open: 1.6V RMS output
// Coil matched to -30dB: 1.0V RmS Output
float calculateRL(uint32_t frequency){
adf4351.setf(frequency);
delay(10);
int reflection_mv = readReflection(64); // Output of the logamp
float RMS_ADF = 13; // at -4dBm the ADF4351 generates sin with an RMS value of 131.5mV but due to to -10dB attenuation of the transcoupler and some additional reflections about 13mV are effectivly at the Logamp
float LOGAMP_SLOPE = 24; // Slope in mV/dB
int intercept_positioning = -108; // in dB
float reflection_dBV = (reflection_mv/LOGAMP_SLOPE) + intercept_positioning;
float reflection_rms = pow(10, reflection_dBV / 20) * 1000; // this step could be shortened but I still like to calculate it explicitly since there are multiple logarithmic operations going on here - > this value is in mV
float reflection_loss = 20 * log10((reflection_rms)/ RMS_ADF);
Serial.println(reflection_rms);
return reflection_loss;
}
// Finds current Resonance Frequency of the coil. There should be a substential dip already present atm.
// Add plausibility check to make sure there is one peak at at least -12dB
// Following is for setup WITHOUT 20dB LNA:
// -30dB aprox. 1.15V Oscilloscope -> normally 1.6V -> 1300 Points
// -16dB aprox. 1.27V Oscilloscope - normally 1.6V
// -18dB aprox 1.295V Oscilloscope -> use 1489 Points as decision line for sufficient Matching
// Values for setup WITH 20dB LNA: -> i don't know what happened here.
// open 1.2V
//-16dB 827mV Oscilloscope
//-30dB 770mV Oscilloscope
int32_t findCurrentResonanceFrequency(uint32_t start_frequency, uint32_t stop_frequency, uint32_t frequency_step){
int minimum_reflection = 4096;
int current_reflection = 0;
uint32_t minimum_frequency = 0;
adf4351.setf(start_frequency); // A frequency value needs to be set once -> there seems to be a bug with the first SPI call
for(uint32_t frequency = start_frequency; frequency <= stop_frequency; frequency += frequency_step){
adf4351.setf(frequency);
delay(5); // This delay is essential! There is a glitch with ADC2 that leads to wrong readings if GPIO27 is set to high for multiple microseconds.
current_reflection = readReflection(4);
if (current_reflection < minimum_reflection){
minimum_frequency = frequency;
minimum_reflection = current_reflection;
}
}
return minimum_frequency;
}
// Approximates the target frequency to about 3 MHZ with the tuning capacitor .... works so far
int32_t approximateResonance(uint32_t target_frequency, uint32_t current_resonance_frequency){
int32_t delta_frequency = target_frequency - current_resonance_frequency;
int rotation = 0; // rotation == 1 -> clockwise, rotation == -1 -> counterclockwise
if (delta_frequency < 0) rotation = -1; // negative delta means currentresonance is to high, hence anticlockwise movement is necessary
else rotation = 1;
tuning_stepper.moveTo(STEPS_PER_ROTATION * rotation);
tuning_stepper.runToPosition();
//step_n(STEPS_PER_ROTATION);
// @ Optimization possibility: -> just scan plausible area, would reduce half the scan time
int32_t one_revolution_resonance = findCurrentResonanceFrequency(current_resonance_frequency - 30000000U, current_resonance_frequency + 30000000U, FREQUENCY_STEP);
//Serial.println(one_revolution_resonance);
int32_t delta_one_revolution_frequency = one_revolution_resonance - current_resonance_frequency;
//Serial.println(delta_one_revolution_frequency);
//Serial.println(float) delta_frequency / (float) delta_one_revolution_frequency);
int32_t steps_to_delta_frequency = ((float) delta_frequency / (float) delta_one_revolution_frequency) * STEPS_PER_ROTATION * rotation;
tuning_stepper.moveTo(steps_to_delta_frequency);
tuning_stepper.runToPosition();
//step_n(STEPS_PER_ROTATION - steps_to_delta_frequency);
current_resonance_frequency = findCurrentResonanceFrequency(target_frequency - 5000000U, target_frequency + 5000000U, FREQUENCY_STEP);
return(current_resonance_frequency);
}
// Tries out different capacitor position until iteration depth is reached OR current_resonancy frequency matches the target_frequency
int32_t bruteforceResonance(uint32_t target_frequency, uint32_t current_resonance_frequency){
// Change Tuning Stepper -> Clockwise => Freq goes up
// Dir = 0 => Anticlockwise movement
int rotation = 0; // rotation == 1 -> clockwise, rotation == -1 -> counterclockwise
int ITERATIONS = 25; // Iteration depth
int iteration_steps = 0;
int32_t delta_frequency = target_frequency - current_resonance_frequency;
if (delta_frequency < 0) rotation = -1; // negative delta means currentresonance is too high, hence anticlockwise movement is necessary
else rotation = 1;
iteration_steps = rotation * (STEPS_PER_ROTATION / 50);
//'bruteforce' the stepper position to match the target frequency
for (int i = 0; i < ITERATIONS; i ++){
tuning_stepper.move(iteration_steps);
tuning_stepper.runToPosition();
// @ Optimization possibility: Reduce frequency range when close to target_frequency
current_resonance_frequency = findCurrentResonanceFrequency(target_frequency - 5000000U, target_frequency + 5000000U, FREQUENCY_STEP / 2);
//Serial.println(current_resonance_frequency);
//Serial.println(rotation);
//Serial.println(iteration_steps);
// Stops the iteration if the minima matches the target frequency
if (current_resonance_frequency == target_frequency) break;
// This means the bruteforce resolution was too high and the resonance frequency overshoot
// therfore the turn direction gets inverted and the increment halfed
if ((current_resonance_frequency > target_frequency) && (rotation == 1)) {
rotation = -1;
iteration_steps /= 2;
iteration_steps *= rotation;
}
else if ((current_resonance_frequency < target_frequency) && (rotation == -1)){
rotation = 1;
iteration_steps /= 2;
iteration_steps *= -rotation;
}
}
return current_resonance_frequency;
}
//
int optimizeMatching(uint32_t current_frequency){
int minimum_reflection = 4096;
int current_reflection = 0;
int minimum_matching_position = 0;
int ITERATIONS = 25; // //100 equals one full rotation
int iteration_steps = 0;
iteration_steps = STEPS_PER_ROTATION / 100;
for (int i = 0; i < ITERATIONS; i ++){
matching_stepper.move(iteration_steps);
matching_stepper.runToPosition();
for (uint32_t frequency = current_frequency - 5000000U; frequency < current_frequency + 5000000U; frequency += FREQUENCY_STEP){
adf4351.setf(frequency);
delay(10);
current_reflection = analogRead(REFLECTION_PIN);
if (current_reflection < minimum_reflection){
minimum_matching_position = matching_stepper.currentPosition();
minimum_reflection = current_reflection;
}
}
}
matching_stepper.moveTo(minimum_matching_position);
matching_stepper.run();
return (minimum_reflection);
}