Compare commits

...
Sign in to create a new pull request.

31 commits

Author SHA1 Message Date
jupfi
d770b1131a Change to forgejo. 2025-05-01 16:56:04 +02:00
jupfi
388506939f Added note to hardware files. 2024-02-21 09:56:48 +01:00
jupfi
21b098f750 Added results to readme. 2024-02-21 09:54:55 +01:00
jupfi
4e8fdff53e Updated docs to reflect the current implementation. 2024-02-21 09:43:29 +01:00
jupfi
222c75effb Updated voltage sweep. 2023-12-20 09:23:04 +01:00
jupfi
884efd0174 Fixed mixing up of tuning and matching voltages ... 2023-12-16 18:13:10 +01:00
jupfi
cf5a01b759 Removed pritning of voltages text. 2023-12-15 17:17:34 +01:00
jupfi
a7606fc7a8 Changed format of commands. 2023-12-15 17:01:13 +01:00
jupfi
8b9d62d308 Added feedback on switch state. 2023-12-15 16:01:21 +01:00
jupfi
54c1b8ee4f Implemented preset voltage sweep and returning of set voltages for tuning and matching. 2023-12-15 15:23:56 +01:00
jupfi
c784162b2e Implemented Backlash Correction and Position Sweep. 2023-12-12 15:53:08 +01:00
jupfi
18ab4f0d34 Corrected identifier. 2023-12-11 09:23:51 +01:00
jupfi
0eea23b0fe Improved stepper stalling 2023-12-10 18:15:50 +01:00
jupfi
126953f690 Added position update to stepper movement. 2023-12-08 08:17:56 +01:00
jupfi
3fa166ee7b Updated homing. 2023-12-07 20:13:53 +01:00
jupfi
3fda6ff2fb Fixed bug which lead to negative voltages. 2023-12-07 18:45:53 +01:00
jupfi
723c36fca8 Update setFrequency method for all calls where the frequency has to be set. 2023-08-25 09:27:13 +02:00
jupfi
b11d01bf13 Implemented MoveStepper command. 2023-08-22 09:04:30 +02:00
jupfi
359152620a Improved speed of frequency sweep. 2023-08-21 11:26:23 +02:00
jupfi
bd46b8ae9f Added confirmation of commands. 2023-08-21 10:16:39 +02:00
jupfi
f2331ecd85 Speed optimization for frequency sweep. 2023-08-18 17:21:44 +02:00
jupfi
ca09dbe1fa Added frequency sweep method. 2023-08-18 16:53:01 +02:00
jupfi
b3b93551f2 Improved switching of filters. 2023-08-18 16:31:09 +02:00
jupfi
d62edee8a5 Added argument for printing of data. 2023-08-18 15:38:42 +02:00
jupfi
1ae4a52fd1 Changed range to new values. 2023-08-18 14:58:13 +02:00
jupfi
cd22f0c8ad Implemented ControlSwitch command. 2023-08-18 13:57:06 +02:00
jupfi
1ecf275b60 Updated RF switch PIN. 2023-08-18 12:17:54 +02:00
jupfi
155921a0e1 Improved voltage sweep. 2023-08-17 12:29:01 +02:00
jupfi
1cb7a66f1d Changed averages and search stop condition. 2023-08-16 17:32:41 +02:00
jupfi
ca497e8360 Implemented basic voltage sweep. 2023-08-16 15:00:56 +02:00
Julia Pfitzer
80943d4e9d
Merge pull request #2 from jupfi/masterproject
Masterproject
2023-08-16 14:42:55 +02:00
36 changed files with 1154 additions and 406 deletions

View file

@ -1,59 +1,70 @@
# ATM
## Stepper-based Automated Tuning and Matching
# ATM (Automatic Tuning and Matching) System
## Automated Tuning and Matching
Tuning and Matching of the resonator coil is an essential part of Nuclear Quadrupole Resonance (NQR) spectroscopy. Because unknown samples are often scanned over large frequency ranges, Tuning and Matching has to be performed frequently. For high Q probe coils using mechanically trimmable capacitors, this is a long and tedious task.
To overcome this issue, an automatic Tuning and Matching system was developed using widely available low-cost components. Stepper drivers control mechanically trimmable capacitors and a microcontroller measures the Return Loss at resonance frequency. The system is capable of Tuning and Matching of a resonator coil in a frequency range from 75 to 125 MHz. It can also measure the Return Loss of the probe coil at its resonance frequency. Therefore, a foundation has been laid for future implementation of broadband NQR scans using mechanical probe coils.
To overcome this issue, an automatic Tuning and Matching system was developed using widely available low-cost components. Stepper drivers control mechanically trimmable capacitors and a microcontroller measures the Return Loss at resonance frequency. The system is capable of Tuning and Matching of a resonator coil in a frequency range from 75 to 125 MHz. It can also measure the Return Loss of the probe coil at its resonance frequency.
## What works
- Homing of the probe coil using the sensorless load detection of TMC2130 stepper drivers.
Additionally the system is capable of Tuning and Matching of NQR probe coils using varactor diodes. In this case the system can output a Tuning and Matching voltage in a range from 0 to 5V.
This system is best used in combination with the [NQRduck](https://git.private.coffee/nqrduck) project which provides a graphical user interface for the ATM-system. Specifically, the [nqrduck-autotm](https://git.private.coffee/nqrduck/nqrduck-autotm) module is used to control the ATM-system.
The C++ program is then running on the ESP32 microcontroller. The NQRduck autotm module communicates with the ESP32 microcontroller using the USB interface.
<img src="docs/img/AutoTM_software.png" alt="drawing" width="150"/>
## Features
- Homing of the probe coil steppers using the sensorless load detection of TMC2130 stepper drivers.
- A mechanically tunable probe coil can be tuned and matched in the frequency range of 75-125MHz.
- The Return Loss at single frequencies can be measured.
- The current resonance frequency of the probe coil can be measured.
- $S_{11}$ measurements can be performed at a frequency with a fixed position for the tuning and matching capacitors.
- Electrically tunable probe coils can be tuned and matched in the frequency range of 75-125MHz.
## System overview
The ATM-system consists of multiple separate hardware components and multiple signal paths. One signal path is used to measure the reflection (characterized by the Return Loss) at a frequency with a fixed position for the tuning and matching capacitors. It is structured as following:
<img src="docs/img/system_overview.png" alt="drawing" width="500"/>
1. A frequency synthesizer outputs an RF signal to a filterbank.
2. The filterbank filters out harmonic components of the frequency \break synthesizer's output.
1. A frequency synthesizer (ADF4351) outputs an RF signal to a filterbank.
2. The filterbank filters out harmonic components of the frequency synthesizer's output.
3. The filtered signal is guided to a directional coupler setup. The directional coupler is connected to the resonator circuit and decouples the forward signal and the signal reflected at the resonator circuit.
4. The forward and reflected signals are converted to a DC signal using a logarithmic amplifier.
5. An instrumentation amplifier takes the DC signals of the forwarded and reflected signal as differential inputs and outputs an amplified signal.
6. An ADC reads in the output of the instrumentation amplifier, which is a representation of the amplitude of the reflected wave.
4. The forward and reflected signals are converted to a DC signal us an AD8302.
5. An ADC reads in the output of the AD8302 and calculates the Return Loss.
<img src="docs/img/refl_inst.png" alt="drawing" width="500"/>
Since the system also needs to be able to adjust the mechanically trimmable capacitors, stepper motors have to be controlled.
1. A microcontroller sends commands to two stepper drivers using a Serial Peripheral Interface (SPI).
2. Those stepper drivers control the steppers which are mechanically coupled to the the tuning and matching capacitors of the probe coil.
## Hardware
Schematics for the electrical circuit and mechanical housing can be found in the project directory.
<img src="docs/img/mech_instr.png" alt="drawing" width="500"/>
<img src="docs/img/system_picture.png" alt="drawing" width="500"/>
For electrically tunable probe coils the ESP32 uses the ADAC click to output a voltage to the varactor diodes.
<img src="docs/img/el_instr.png" alt="drawing" width="500"/>
## Hardware
Schematics for the electrical circuit and mechanical housing will be added soon.
## Install instructions
1. Download the project as a zip file and import it as a platformio project.
2. Connect your ESP32 microcontroller to your PC and flash the firmware onto it.
# Commands
The user can input different commands to the ATM-system using the serial interface of the ESP32. The general structure of such a command is a single character followed by a float value. The float value is only needed for certain commands as specified in the following table.
For the installation of the NQRduck autoTM module please refer to the [NQRduck](https://git.private.coffee/nqrduck) documentation.
| Command Name | Character | Float Value | Float Range|
| :--- | :----: | ---: | ---: |
| Homing Routine | h | No | - |
| Frequency Sweep | f | No | - |
| Iterative Resonance Tuning | b | Yes | 35.0-260.0 |
| Measure Return Loss | r | Yes | 35.0-260.0 |
| Matching | m | Yes | 10.0-100.0 |
| Calibrate | c | No | - |
| Automatic Tuning and Matching| d | Yes | 35.0-260.0 |
# Commands
The user can input different commands to the ATM-system using the serial interface of the ESP32. The general structure of such a command is a single character followed by a float value. The float value is only needed for certain commands.
# Results
A mechanically tunable probe coil was tuned and from 83MHz to 87MHz with a step size of 0.1MHz. The resulting reflection coefficient was then measured using the VNA (ZVL3, Rohde & Schwarz, Munich, Germany). The measured values were plotted using Matplotlib.
<img src="docs/img/S11_Full_mechcoil_82-87.png" alt="drawing" width="500">
For the electrically tunable probe coil the probe coil was tuned and matched from 83 to 84MHz with a step size of 0.1MHz. The resulting reflection coefficient was then measured using the VNA (ZVL3, Rohde & Schwarz, Munich, Germany). The measured values were plotted using Matplotlib.
<img src="docs/img/S11_full.png" alt="drawing" width="500">
## Future work
- Optimization for broadband NQR measurments.
- Implementation of electrical Tuning and Matching using varactor diodes.
- Automatic calibration to new probe coils.
- Optimization for $S_{11}$ measurements. Right now the maximum achievable matching is about -25dB.
- Improve the calibration for $S_{11}$ measurements. Right now the setup is very impractical.
## References
- [adf4351](https://github.com/dfannin/adf4351) by David Fannin was modified to be used with an ESP32 microcontroller.

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB

BIN
docs/img/S11_full.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

BIN
docs/img/el_instr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

BIN
docs/img/mech_instr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

BIN
docs/img/refl_inst.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

View file

@ -1 +1,2 @@
# Notes
These files are out of date and are not used in the current version of the ATM. I will update this documentation soon (hopefully).

View file

@ -27,12 +27,9 @@
#define DIAG1_PIN_M2 2 // used for homing
//ADC Pin - obslete
// #define REFLECTION_PIN 15
// Filter Bank
#define FILTER_SWITCH_A 23
#define FILTER_SWITCH_B 22
// RF Switch
#define RF_SWITCH_PIN 34
#define RF_SWITCH_PIN 15

View file

@ -19,14 +19,14 @@ struct FrequencyRange
const Filter FG_71MHZ = {71000000U, LOW, LOW};
const Filter FG_120MHZ = {120000000U, LOW, HIGH};
const Filter FG_180MHZ = {180000000U, HIGH, LOW};
const Filter FG_260MHZ = {260000000U, HIGH, LOW};
const Filter FG_260MHZ = {260000000U, HIGH, HIGH};
// All fitlers
const Filter FILTERS[] = {FG_71MHZ, FG_120MHZ, FG_180MHZ, FG_260MHZ};
// Settings for 100MHz -18dB
//#define TUNING_STEPPER_HOME 34250U
//#define MATCHING_STEPPER_HOME 45000U
// #define TUNING_STEPPER_HOME 34250U
// #define MATCHING_STEPPER_HOME 45000U
const FrequencyRange RANGE_35_70MHZ =
{
35000000U,
@ -44,7 +44,7 @@ const FrequencyRange RANGE_70_125MHZ =
100000000U,
FG_120MHZ,
34400U,
48500U,
60500U,
};
const FrequencyRange RANGE_125_180MHZ =
@ -60,5 +60,5 @@ const FrequencyRange RANGE_125_180MHZ =
const FrequencyRange HOME_RANGE = RANGE_70_125MHZ;
// Settings for 125MHz -30dB
//#define TUNING_STEPPER_HOME 37550U
//#define MATCHING_STEPPER_HOME 29500U
// #define TUNING_STEPPER_HOME 37550U
// #define MATCHING_STEPPER_HOME 29500U

View file

@ -1,89 +1,90 @@
#include "AD5593R.h"
#include <Wire.h>
//Definitions
#define _ADAC_NULL B00000000
#define _ADAC_ADC_SEQUENCE B00000010 // ADC sequence register - Selects ADCs for conversion
#define _ADAC_GP_CONTROL B00000011 // General-purpose control register - DAC and ADC control register
#define _ADAC_ADC_CONFIG B00000100 // ADC pin configuration - Selects which pins are ADC inputs
#define _ADAC_DAC_CONFIG B00000101 // DAC pin configuration - Selects which pins are DAC outputs
#define _ADAC_PULL_DOWN B00000110 // Pull-down configuration - Selects which pins have an 85 kO pull-down resistor to GND
#define _ADAC_LDAC_MODE B00000111 // LDAC mode - Selects the operation of the load DAC
// Definitions
#define _ADAC_NULL B00000000
#define _ADAC_ADC_SEQUENCE B00000010 // ADC sequence register - Selects ADCs for conversion
#define _ADAC_GP_CONTROL B00000011 // General-purpose control register - DAC and ADC control register
#define _ADAC_ADC_CONFIG B00000100 // ADC pin configuration - Selects which pins are ADC inputs
#define _ADAC_DAC_CONFIG B00000101 // DAC pin configuration - Selects which pins are DAC outputs
#define _ADAC_PULL_DOWN B00000110 // Pull-down configuration - Selects which pins have an 85 kO pull-down resistor to GND
#define _ADAC_LDAC_MODE B00000111 // LDAC mode - Selects the operation of the load DAC
#define _ADAC_GPIO_WR_CONFIG B00001000 // GPIO write configuration - Selects which pins are general-purpose outputs
#define _ADAC_GPIO_WR_DATA B00001001 // GPIO write data - Writes data to general-purpose outputs
#define _ADAC_GPIO_WR_DATA B00001001 // GPIO write data - Writes data to general-purpose outputs
#define _ADAC_GPIO_RD_CONFIG B00001010 // GPIO read configuration - Selects which pins are general-purpose inputs
#define _ADAC_POWER_REF_CTRL B00001011 // Power-down/reference control - Powers down the DACs and enables/disables the reference
#define _ADAC_OPEN_DRAIN_CFG B00001100 // Open-drain configuration - Selects open-drain or push-pull for general-purpose outputs
#define _ADAC_THREE_STATE B00001101 // Three-state pins - Selects which pins are three-stated
#define _ADAC_RESERVED B00001110 // Reserved
#define _ADAC_SOFT_RESET B00001111 // Software reset - Resets the AD5593R
#define _ADAC_THREE_STATE B00001101 // Three-state pins - Selects which pins are three-stated
#define _ADAC_RESERVED B00001110 // Reserved
#define _ADAC_SOFT_RESET B00001111 // Software reset - Resets the AD5593R
/**
* @name ADAC Configuration Data Bytes
******************************************************************************/
///@{
//write into MSB after _ADAC_POWER_REF_CTRL command to enable VREF
#define _ADAC_VREF_ON B00000010
///@{
// write into MSB after _ADAC_POWER_REF_CTRL command to enable VREF
#define _ADAC_VREF_ON B00000010
#define _ADAC_SEQUENCE_ON B00000010
/**
* @name ADAC Write / Read Pointer Bytes
******************************************************************************/
******************************************************************************/
///@{
#define _ADAC_DAC_WRITE B00010000
#define _ADAC_ADC_READ B01000000
#define _ADAC_DAC_READ B01010000
#define _ADAC_GPIO_READ B01110000
#define _ADAC_REG_READ B01100000
#define _ADAC_DAC_WRITE B00010000
#define _ADAC_ADC_READ B01000000
#define _ADAC_DAC_READ B01010000
#define _ADAC_GPIO_READ B01110000
#define _ADAC_REG_READ B01100000
//Class constructor
AD5593R::AD5593R(int a0, int I2C_SDA, int I2C_SCL) {
// Class constructor
AD5593R::AD5593R(int a0, int I2C_SDA, int I2C_SCL)
{
_a0 = a0;
_GPRC_msbs = 0x00;
_GPRC_lsbs = 0x00;
_PCR_msbs = 0x00;
_PCR_lsbs = 0x00;
//intializing the configuration struct.
for (int i = 0; i < _num_of_channels; i++) {
// intializing the configuration struct.
for (int i = 0; i < _num_of_channels; i++)
{
config.ADCs[i] = 0;
config.DACs[i] = 0;
}
for (int i = 0; i < _num_of_channels; i++) {
for (int i = 0; i < _num_of_channels; i++)
{
values.ADCs[i] = -1;
values.DACs[i] = -1;
}
//this allows for multiple devices on the same bus, see header.
if (_a0 > -1) {
// this allows for multiple devices on the same bus, see header.
if (_a0 > -1)
{
pinMode(_a0, OUTPUT);
digitalWrite(_a0, HIGH);
}
Wire.begin(I2C_SDA, I2C_SCL);
// This makes the ADC perform worse :-( (but it is faster)
// Wire.setClock(400000U);
}
//int AD5593R::configure_pins(*configuration config){
// int AD5593R::configure_pins(*configuration config){
//}
void AD5593R::enable_internal_Vref() {
//Enable selected device for writing
void AD5593R::enable_internal_Vref()
{
// Enable selected device for writing
_Vref = 2.5;
_ADC_max = _Vref;
_DAC_max = _Vref;
if (_a0 > -1) digitalWrite(_a0, LOW);
if (_a0 > -1)
digitalWrite(_a0, LOW);
//check if the on bit is already fliped on
if ((_PCR_msbs & 0x02) != 0x02) {
// check if the on bit is already fliped on
if ((_PCR_msbs & 0x02) != 0x02)
{
_PCR_msbs = _PCR_msbs ^ 0x02;
}
Wire.beginTransmission(_i2c_address);
@ -92,19 +93,23 @@ void AD5593R::enable_internal_Vref() {
Wire.write(_PCR_lsbs);
Wire.endTransmission();
//Disable selected device for writing
if (_a0 > -1) digitalWrite(_a0, HIGH);
// Disable selected device for writing
if (_a0 > -1)
digitalWrite(_a0, HIGH);
AD5593R_PRINTLN("Internal Reference on.");
}
void AD5593R::disable_internal_Vref() {
//Enable selected device for writing
void AD5593R::disable_internal_Vref()
{
// Enable selected device for writing
_Vref = -1;
_ADC_max = _Vref;
_DAC_max = _Vref;
if (_a0 > -1) digitalWrite(_a0, LOW);
//check if the on bit is already fliped off
if ((_PCR_msbs & 0x02) == 0x02) {
if (_a0 > -1)
digitalWrite(_a0, LOW);
// check if the on bit is already fliped off
if ((_PCR_msbs & 0x02) == 0x02)
{
_PCR_msbs = _PCR_msbs ^ 0x02;
}
Wire.beginTransmission(_i2c_address);
@ -113,17 +118,21 @@ void AD5593R::disable_internal_Vref() {
Wire.write(_PCR_lsbs);
Wire.endTransmission();
//Disable selected device for writing
if (_a0 > -1) digitalWrite(_a0, HIGH);
// Disable selected device for writing
if (_a0 > -1)
digitalWrite(_a0, HIGH);
AD5593R_PRINTLN("Internal Reference off.");
}
void AD5593R::set_ADC_max_2x_Vref() {
//Enable selected device for writing
void AD5593R::set_ADC_max_2x_Vref()
{
// Enable selected device for writing
_ADC_max = 2 * _Vref;
if (_a0 > -1) digitalWrite(_a0, LOW);
//check if 2x bit is on in the general purpose register
if ((_GPRC_lsbs & 0x20) != 0x20) {
if (_a0 > -1)
digitalWrite(_a0, LOW);
// check if 2x bit is on in the general purpose register
if ((_GPRC_lsbs & 0x20) != 0x20)
{
_GPRC_lsbs = _GPRC_lsbs ^ 0x20;
}
Wire.beginTransmission(_i2c_address);
@ -132,18 +141,22 @@ void AD5593R::set_ADC_max_2x_Vref() {
Wire.write(_GPRC_lsbs);
Wire.endTransmission();
//Disable selected device for writing
if (_a0 > -1) digitalWrite(_a0, HIGH);
// Disable selected device for writing
if (_a0 > -1)
digitalWrite(_a0, HIGH);
AD5593R_PRINTLN("ADC max voltage = 2xVref");
_ADC_2x_mode = 1;
}
void AD5593R::set_ADC_max_1x_Vref() {
//Enable selected device for writing
void AD5593R::set_ADC_max_1x_Vref()
{
// Enable selected device for writing
_ADC_max = _Vref;
if (_a0 > -1) digitalWrite(_a0, LOW);
if (_a0 > -1)
digitalWrite(_a0, LOW);
if ((_GPRC_lsbs & 0x20) == 0x20) {
if ((_GPRC_lsbs & 0x20) == 0x20)
{
_GPRC_lsbs = _GPRC_lsbs ^ 0x20;
}
Wire.beginTransmission(_i2c_address);
@ -152,18 +165,22 @@ void AD5593R::set_ADC_max_1x_Vref() {
Wire.write(_GPRC_lsbs);
Wire.endTransmission();
//Disable selected device for writing
if (_a0 > -1) digitalWrite(_a0, HIGH);
// Disable selected device for writing
if (_a0 > -1)
digitalWrite(_a0, HIGH);
AD5593R_PRINTLN("ADC max voltage = 1xVref");
_ADC_2x_mode = 0;
}
void AD5593R::set_DAC_max_2x_Vref() {
//Enable selected device for writing
void AD5593R::set_DAC_max_2x_Vref()
{
// Enable selected device for writing
_DAC_max = 2 * _Vref;
if (_a0 > -1) digitalWrite(_a0, LOW);
if (_a0 > -1)
digitalWrite(_a0, LOW);
if ((_GPRC_lsbs & 0x10) != 0x10) {
if ((_GPRC_lsbs & 0x10) != 0x10)
{
_GPRC_lsbs = _GPRC_lsbs ^ 0x10;
}
Wire.beginTransmission(_i2c_address);
@ -172,18 +189,22 @@ void AD5593R::set_DAC_max_2x_Vref() {
Wire.write(_GPRC_lsbs);
Wire.endTransmission();
//Disable selected device for writing
if (_a0 > -1) digitalWrite(_a0, HIGH);
// Disable selected device for writing
if (_a0 > -1)
digitalWrite(_a0, HIGH);
AD5593R_PRINTLN("DAC max voltage = 2xVref");
_DAC_2x_mode = 1;
}
void AD5593R::set_DAC_max_1x_Vref() {
//Enable selected device for writing
void AD5593R::set_DAC_max_1x_Vref()
{
// Enable selected device for writing
_DAC_max = _Vref;
if (_a0 > -1) digitalWrite(_a0, LOW);
if (_a0 > -1)
digitalWrite(_a0, LOW);
if ((_GPRC_lsbs & 0x10) == 0x10) {
if ((_GPRC_lsbs & 0x10) == 0x10)
{
_GPRC_lsbs = _GPRC_lsbs ^ 0x10;
}
Wire.beginTransmission(_i2c_address);
@ -192,36 +213,44 @@ void AD5593R::set_DAC_max_1x_Vref() {
Wire.write(_GPRC_lsbs);
Wire.endTransmission();
//Disable selected device for writing
if (_a0 > -1) digitalWrite(_a0, HIGH);
// Disable selected device for writing
if (_a0 > -1)
digitalWrite(_a0, HIGH);
AD5593R_PRINTLN("ADC max voltage = 1xVref");
_DAC_2x_mode = 0;
}
void AD5593R::set_Vref(float Vref) {
void AD5593R::set_Vref(float Vref)
{
_Vref = Vref;
if (_ADC_2x_mode == 0) {
if (_ADC_2x_mode == 0)
{
_ADC_max = Vref;
}
else {
else
{
_ADC_max = 2 * Vref;
}
if (_DAC_2x_mode == 0) {
if (_DAC_2x_mode == 0)
{
_DAC_max = Vref;
}
else {
else
{
_DAC_max = 2 * Vref;
}
}
void AD5593R::configure_DAC(byte channel) {
if (_a0 > -1) digitalWrite(_a0, LOW);
void AD5593R::configure_DAC(byte channel)
{
if (_a0 > -1)
digitalWrite(_a0, LOW);
config.DACs[channel] = 1;
byte channel_byte = 1 << channel;
//check to see if the channel is a DAC already
if ((_DAC_config & channel_byte) != channel_byte) {
// check to see if the channel is a DAC already
if ((_DAC_config & channel_byte) != channel_byte)
{
_DAC_config = _DAC_config ^ channel_byte;
}
Wire.beginTransmission(_i2c_address);
@ -230,48 +259,55 @@ void AD5593R::configure_DAC(byte channel) {
Wire.write(_DAC_config);
Wire.endTransmission();
if (_a0 > -1) digitalWrite(_a0, HIGH);
if (_a0 > -1)
digitalWrite(_a0, HIGH);
AD5593R_PRINT("Channel ");
AD5593R_PRINT(channel);
AD5593R_PRINTLN(" is configured as a DAC");
}
void AD5593R::configure_DACs(bool* channels) {
for (size_t i = 0; i < _num_of_channels; i++) {
if (channels[i] == 1) {
void AD5593R::configure_DACs(bool *channels)
{
for (size_t i = 0; i < _num_of_channels; i++)
{
if (channels[i] == 1)
{
configure_DAC(i);
}
}
}
int AD5593R::write_DAC(byte channel, float voltage) {
//error checking
if (config.DACs[channel] == 0) {
int AD5593R::write_DAC(byte channel, float voltage)
{
// error checking
if (config.DACs[channel] == 0)
{
AD5593R_PRINT("ERROR! Channel ");
AD5593R_PRINT(channel);
AD5593R_PRINTLN(" is not a DAC");
return -1;
}
if (_DAC_max == -1) {
if (_DAC_max == -1)
{
AD5593R_PRINTLN("Vref, or DAC_max is not defined");
return -2;
}
if (voltage > _DAC_max) {
if (voltage > _DAC_max)
{
AD5593R_PRINTLN("Vref, or DAC_max is lower than set voltage");
return -3;
}
if (_a0 > -1) digitalWrite(_a0, LOW);
if (_a0 > -1)
digitalWrite(_a0, LOW);
//find the binary representation of the
// find the binary representation of the
unsigned int data_bits = (voltage / _DAC_max) * 4095;
//extract the 4 most signifigant bits, and move them down to the bottom
// extract the 4 most signifigant bits, and move them down to the bottom
byte data_msbs = (data_bits & 0xf00) >> 8;
byte lsbs = (data_bits & 0x0ff);
//place the channel data in the most signifigant bits
// place the channel data in the most signifigant bits
byte msbs = (B10000000 | (channel << 4)) | data_msbs;
Wire.beginTransmission(_i2c_address);
@ -285,17 +321,21 @@ int AD5593R::write_DAC(byte channel, float voltage) {
AD5593R_PRINT(" is set to ");
AD5593R_PRINT(voltage);
AD5593R_PRINTLN(" Volts");
if (_a0 > -1) digitalWrite(_a0, HIGH);
if (_a0 > -1)
digitalWrite(_a0, HIGH);
values.DACs[channel] = voltage;
return 1;
}
void AD5593R::configure_ADC(byte channel) {
if (_a0 > -1) digitalWrite(_a0, LOW);
void AD5593R::configure_ADC(byte channel)
{
if (_a0 > -1)
digitalWrite(_a0, LOW);
config.ADCs[channel] = 1;
byte channel_byte = 1 << channel;
//check to see if the channel is a ADC already
if ((_ADC_config & channel_byte) != channel_byte) {
// check to see if the channel is a ADC already
if ((_ADC_config & channel_byte) != channel_byte)
{
_ADC_config = _ADC_config ^ channel_byte;
}
Wire.beginTransmission(_i2c_address);
@ -304,33 +344,38 @@ void AD5593R::configure_ADC(byte channel) {
Wire.write(_ADC_config);
Wire.endTransmission();
if (_a0 > -1) digitalWrite(_a0, HIGH);
if (_a0 > -1)
digitalWrite(_a0, HIGH);
AD5593R_PRINT("Channel ");
AD5593R_PRINT(channel);
AD5593R_PRINTLN(" is configured as a ADC");
}
void AD5593R::configure_ADCs(bool* channels) {
for (size_t i = 0; i < _num_of_channels; i++) {
if (channels[i] == 1) {
void AD5593R::configure_ADCs(bool *channels)
{
for (size_t i = 0; i < _num_of_channels; i++)
{
if (channels[i] == 1)
{
configure_ADC(i);
}
}
}
float AD5593R::read_ADC(byte channel) {
if (config.ADCs[channel] == 0) {
float AD5593R::read_ADC(byte channel, int averages)
{
if (config.ADCs[channel] == 0)
{
AD5593R_PRINT("ERROR! Channel ");
AD5593R_PRINT(channel);
AD5593R_PRINTLN(" is not an ADC");
return -1;
}
if (_ADC_max == -1) {
if (_ADC_max == -1)
{
AD5593R_PRINTLN("Vref, or ADC_max is not defined");
return -2;
}
if (_a0 > -1) digitalWrite(_a0, LOW);
Wire.beginTransmission(_i2c_address);
Wire.write(_ADAC_ADC_SEQUENCE);
@ -338,18 +383,31 @@ float AD5593R::read_ADC(byte channel) {
Wire.write(byte(1 << channel));
Wire.endTransmission();
int sum = 0;
for (int i = 0; i < averages; i++)
{
if (_a0 > -1)
gpio_set_level((gpio_num_t)_a0, LOW);
Wire.beginTransmission(_i2c_address);
Wire.write(_ADAC_ADC_READ);
Wire.endTransmission();
Wire.beginTransmission(_i2c_address);
Wire.write(_ADAC_ADC_READ);
Wire.endTransmission();
unsigned int data_bits = 0;
delayMicroseconds(10);
Wire.requestFrom(int(_i2c_address), int(2), int(1));
if (Wire.available()) data_bits = (Wire.read() & 0x0f) << 8;
if (Wire.available()) data_bits = data_bits | Wire.read();
if (_a0 > -1) digitalWrite(_a0, HIGH);
float data = _ADC_max * (data_bits) / 4095;
Wire.requestFrom(int(_i2c_address), int(2), int(1));
unsigned int data_bits = 0;
if (Wire.available())
data_bits = (Wire.read() & 0x0f) << 8;
if (Wire.available())
data_bits = data_bits | Wire.read();
sum += (data_bits);
if (_a0 > -1)
gpio_set_level((gpio_num_t)_a0, HIGH);
}
float data = _ADC_max * sum / 4095 / averages;
AD5593R_PRINT("Channel ");
AD5593R_PRINT(channel);
@ -359,22 +417,27 @@ float AD5593R::read_ADC(byte channel) {
return data;
}
float* AD5593R::read_ADCs() {
for (size_t i = 0; i < _num_of_channels; i++) {
if (config.ADCs[i] == 1) {
float *AD5593R::read_ADCs()
{
for (size_t i = 0; i < _num_of_channels; i++)
{
if (config.ADCs[i] == 1)
{
read_ADC(i);
}
}
return values.ADCs;
}
void AD5593R::configure_GPI(byte channel) {
if (_a0 > -1) digitalWrite(_a0, LOW);
void AD5593R::configure_GPI(byte channel)
{
if (_a0 > -1)
digitalWrite(_a0, LOW);
config.DACs[channel] = 1;
byte channel_byte = 1 << channel;
//check to see if the channel is a gpi already
if ((_GPI_config & channel_byte) != channel_byte) {
// check to see if the channel is a gpi already
if ((_GPI_config & channel_byte) != channel_byte)
{
_GPI_config = _GPI_config ^ _GPI_config;
}
Wire.beginTransmission(_i2c_address);
@ -384,27 +447,33 @@ void AD5593R::configure_GPI(byte channel) {
Wire.write(_GPI_config);
Wire.endTransmission();
if (_a0 > -1) digitalWrite(_a0, HIGH);
if (_a0 > -1)
digitalWrite(_a0, HIGH);
AD5593R_PRINT("Channel ");
AD5593R_PRINT(channel);
AD5593R_PRINTLN(" is configured as a GPI");
}
void AD5593R::configure_GPIs(bool* channels) {
for (size_t i = 0; i < _num_of_channels; i++) {
if (channels[i] == 1) {
void AD5593R::configure_GPIs(bool *channels)
{
for (size_t i = 0; i < _num_of_channels; i++)
{
if (channels[i] == 1)
{
configure_GPI(i);
}
}
}
void AD5593R::configure_GPO(byte channel) {
if (_a0 > -1) digitalWrite(_a0, LOW);
void AD5593R::configure_GPO(byte channel)
{
if (_a0 > -1)
digitalWrite(_a0, LOW);
config.DACs[channel] = 1;
byte channel_byte = 1 << channel;
//check to see if the channel is a gpo already
if ((_GPO_config & channel_byte) != channel_byte) {
// check to see if the channel is a gpo already
if ((_GPO_config & channel_byte) != channel_byte)
{
_GPO_config = _GPO_config ^ _GPO_config;
}
Wire.beginTransmission(_i2c_address);
@ -414,24 +483,26 @@ void AD5593R::configure_GPO(byte channel) {
Wire.write(_GPI_config);
Wire.endTransmission();
if (_a0 > -1) digitalWrite(_a0, HIGH);
if (_a0 > -1)
digitalWrite(_a0, HIGH);
AD5593R_PRINT("Channel ");
AD5593R_PRINT(channel);
AD5593R_PRINTLN(" is configured as a GPO");
}
void AD5593R::configure_GPOs(bool* channels) {
for (size_t i = 0; i < _num_of_channels; i++) {
if (channels[i] == 1) {
void AD5593R::configure_GPOs(bool *channels)
{
for (size_t i = 0; i < _num_of_channels; i++)
{
if (channels[i] == 1)
{
configure_GPO(i);
}
}
}
// bool AD5593R::read_GPI(byte channel) {
// AD5593R_PRINT("Channel ");
// AD5593R_PRINT(channel);
// AD5593R_PRINT(" reads ");
@ -439,9 +510,11 @@ void AD5593R::configure_GPOs(bool* channels) {
// return data;
// }
bool* AD5593R::read_GPIs() {
bool *AD5593R::read_GPIs()
{
if (_a0 > -1) digitalWrite(_a0, LOW);
if (_a0 > -1)
digitalWrite(_a0, LOW);
// request the data
Wire.beginTransmission(_i2c_address);
Wire.write(_ADAC_GPIO_READ);
@ -450,12 +523,17 @@ bool* AD5593R::read_GPIs() {
uint16_t data_bits = 0;
Wire.requestFrom(int(_i2c_address), int(2), int(1));
// mask bits, build the word
if (Wire.available()) data_bits = (Wire.read() & 0x0f) << 8;
if (Wire.available()) data_bits = data_bits | Wire.read();
if (_a0 > -1) digitalWrite(_a0, HIGH);
if (Wire.available())
data_bits = (Wire.read() & 0x0f) << 8;
if (Wire.available())
data_bits = data_bits | Wire.read();
if (_a0 > -1)
digitalWrite(_a0, HIGH);
for (size_t i = 0; i < _num_of_channels; i++) {
if (config.GPIs[i] == 1) {
for (size_t i = 0; i < _num_of_channels; i++)
{
if (config.GPIs[i] == 1)
{
values.GPI_reads[i] = bool(data_bits & 0x01);
}
data_bits >> 1;
@ -463,20 +541,25 @@ bool* AD5593R::read_GPIs() {
return values.GPI_reads;
}
void AD5593R::write_GPOs(bool* pin_states) {
void AD5593R::write_GPOs(bool *pin_states)
{
byte data_bits = 0;
for (size_t i = 0; i < _num_of_channels; i++) {
if (config.GPOs[i] == 1) {
for (size_t i = 0; i < _num_of_channels; i++)
{
if (config.GPOs[i] == 1)
{
values.GPO_writes[i] = pin_states[i];
data_bits = data_bits & pin_states[i];
}
data_bits << 1;
}
if (_a0 > -1) digitalWrite(_a0, LOW);
if (_a0 > -1)
digitalWrite(_a0, LOW);
Wire.beginTransmission(_i2c_address);
Wire.write(_ADAC_GPIO_WR_DATA);
Wire.write(0x00);
Wire.write(data_bits);
Wire.endTransmission();
if (_a0 > -1) digitalWrite(_a0, HIGH);
if (_a0 > -1)
digitalWrite(_a0, HIGH);
}

View file

@ -27,12 +27,12 @@ https://github.com/LukasJanavicius
// and be sure to use Serial.begin() in the setup.
// AD5593R_DEBUG
#pragma once
//comment this line to disable debugging
//#define AD5593R_DEBUG
// comment this line to disable debugging
// #define AD5593R_DEBUG
#ifdef AD5593R_DEBUG
#define AD5593R_PRINT(...) Serial.print(__VA_ARGS__)
#define AD5593R_PRINTLN(...) Serial.println(__VA_ARGS__)
#define AD5593R_PRINT(...) Serial.print(__VA_ARGS__)
#define AD5593R_PRINTLN(...) Serial.println(__VA_ARGS__)
#else
#define AD5593R_PRINT(...)
#define AD5593R_PRINTLN(...)
@ -43,27 +43,28 @@ https://github.com/LukasJanavicius
#endif
#include <Arduino.h>
//////Classes//////
class AD5593R {
class AD5593R
{
public:
// This configuration structure contains arrays of booleans,
// each array follows the form [channel0,...,channel7]
// where a 1 indicates the channel should be configured as the name implies
// for example an array ADCs[8] = {1,1,0,0,0,0,0,0} will configure channels 0 and 1 as ADCs.
// a declaration of this structure should be defined in your code, and passed into configure().
// You should not double assign pins, as only the first declaration will be assigned.
struct configuration {
bool ADCs[8]; //ADC pins
bool DACs[8]; //DAC pins
bool GPIs[8]; //input pins
bool GPOs[8]; //output pins
struct configuration
{
bool ADCs[8]; // ADC pins
bool DACs[8]; // DAC pins
bool GPIs[8]; // input pins
bool GPOs[8]; // output pins
};
configuration config;
// This structure contains arrays of
struct Read_write_values {
struct Read_write_values
{
float ADCs[8];
float DACs[8];
bool GPI_reads[8];
@ -96,42 +97,38 @@ public:
// or enable the internal reference will mean that any DAC/ADC function call will result in an error!
void set_Vref(float Vref);
//configures the selected channel as a DAC
// configures the selected channel as a DAC
void configure_DAC(byte channel);
void configure_DACs(bool* channels);
void configure_DACs(bool *channels);
// Sets the output voltage value of a given channel, returns 1 if the write is completed
// if the function returns -1 if the specified channel is not an DAC,
// if no reference voltage is specified a -2 will be returned,
// and if the voltage exceeds the maximum allowable voltage a -3 will be returned.
int write_DAC(byte channel, float voltage);
void write_DACs(float* voltages);
void write_DACs(float *voltages);
//configures the selected channel as a ADC
// configures the selected channel as a ADC
void configure_ADC(byte channel);
void configure_ADCs(bool* channels);
void configure_ADCs(bool *channels);
// Reads the voltage value of a given ADC channel, returns the Voltage if the write is completed
// if the function returns -1 if the specified channel is not an ADC,
// and if no reference voltage is specified a -2 will be returned.
float read_ADC(byte channel);
float* read_ADCs();
float read_ADC(byte channel, int averages = 1);
float *read_ADCs();
void configure_GPI(byte channel);
void configure_GPIs(bool* channels);
void configure_GPIs(bool *channels);
void configure_GPO(byte channel);
void configure_GPOs(bool* channels);
bool* read_GPIs();
void write_GPOs(bool* pin_states);
void configure_GPOs(bool *channels);
bool *read_GPIs();
void write_GPOs(bool *pin_states);
/*
@ -172,12 +169,11 @@ private:
// These structures are adapted from
// https://github.com/MikroElektronika/HEXIWEAR/blob/master/SW/Click%20Examples%20mikroC/examples/ADAC/library/__ADAC_Driver.h
int _num_of_channels = 8;
int _a0;
//general purpose control register data Bytes
// general purpose control register data Bytes
byte _GPRC_msbs;
byte _GPRC_lsbs;
@ -190,18 +186,18 @@ private:
byte _GPI_config;
byte _GPO_config;
//default address of the AD5593R, multiple devices are handled by setting the desired device's a0 to LOW
//by default the a0 pin will be pulled high, effectively changing its address. For more information on the addressing please
//refer to the data sheet in the introduction
// default address of the AD5593R, multiple devices are handled by setting the desired device's a0 to LOW
// by default the a0 pin will be pulled high, effectively changing its address. For more information on the addressing please
// refer to the data sheet in the introduction
byte _i2c_address = 0x10;
//Value of the reference voltage, if none is specified then all ADC/DAC functions will throw errors
// Value of the reference voltage, if none is specified then all ADC/DAC functions will throw errors
float _Vref = -1;
//flag for 2xVref mode
// flag for 2xVref mode
bool _ADC_2x_mode = 0;
//flag for 2xVref mode
// flag for 2xVref mode
bool _DAC_2x_mode = 0;
float _ADC_max = -1;

View file

@ -6,7 +6,10 @@
#include "commands/Homing.h"
#include "commands/SetVoltages.h"
#include "commands/MeasureReflection.h"
#include "commands/VoltageSweep.h"
#include "commands/ControlSwitch.h"
#include "commands/MoveStepper.h"
#include "commands/PositionSweep.h"
#define DEBUG
@ -20,13 +23,16 @@ TuneMatch tuneMatch;
Homing homing;
SetVoltages setVoltages;
MeasureReflection measureReflection;
VoltageSweep voltageSweep;
ControlSwitch controlSwitch;
MoveStepper moveStepper;
PositionSweep positionSweep;
// Frequency Settings
#define FREQUENCY_STEP 100000U // 100kHz frequency steps for initial frequency sweep
#define START_FREQUENCY 50000000U // 50MHz
#define STOP_FREQUENCY 110000000 // 110MHz
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);
@ -45,6 +51,9 @@ AD5593R adac = AD5593R(23, I2C_SDA, I2C_SCL);
bool DACs[8] = {0, 0, 1, 1, 0, 0, 0, 0};
bool ADCs[8] = {1, 1, 0, 0, 0, 0, 0, 0};
// This sets the active filter to the first one in the list
Filter active_filter = FILTERS[0];
boolean homed = false;
void setup()
@ -56,7 +65,11 @@ void setup()
commandManager.registerCommand('d', &tuneMatch);
commandManager.registerCommand('h', &homing);
commandManager.registerCommand('v', &setVoltages);
commandManager.registerCommand('m', &measureReflection);
commandManager.registerCommand('r', &measureReflection);
commandManager.registerCommand('s', &voltageSweep);
commandManager.registerCommand('c', &controlSwitch);
commandManager.registerCommand('m', &moveStepper);
commandManager.registerCommand('p', &positionSweep);
pinMode(MISO_PIN, INPUT_PULLUP); // Seems to be necessary for SPI to work
@ -80,7 +93,7 @@ void setup()
matcher.DRIVER.coolstep_min_speed(0xFFFFF); // 20bit max - needs to be set for stallguard
matcher.DRIVER.diag1_stall(1);
matcher.DRIVER.diag1_active_high(1);
matcher.DRIVER.sg_stall_value(STALL_VALUE - 2);
matcher.DRIVER.sg_stall_value(STALL_VALUE-2);
matcher.DRIVER.shaft_dir(0);
matcher.DRIVER.stealthChop(1); // Enable extremely quiet stepping
@ -107,7 +120,7 @@ void setup()
adf4351.begin();
adf4351.setrf(25000000U);
adf4351.pwrlevel = 2; // This equals -4dBm*/ For the electrical probe coils one should use at least -20dbm so an attenuator is necessary
adf4351.pwrlevel = 2; // This equals 2dBm*/ For the electrical probe coils one should use at least -20dbm so an attenuator is necessary
adf4351.setf(START_FREQUENCY);
// Setup for the RF Switch for the filterbank
@ -126,32 +139,32 @@ void setup()
adac.write_DAC(VT, 0.0);
adac.configure_ADCs(ADCs);
// RF Switch for switching between preamp and tuning and matching module
pinMode(RF_SWITCH_PIN, OUTPUT);
digitalWrite(RF_SWITCH_PIN, HIGH);
}
// Serial communication via USB.
// Commands:
// f<start frequency>f<stop frequency>f<frequency step> - Frequency Sweep
// d<target frequency in MHz>f<start frequency>f<stop frequency>f<frequency step> - Tune and Match
// h - Homing
void loop()
{
// Serial communication via USB.
// Commands:
// f<start frequency>f<stop frequency>f<frequency step> - Frequency Sweep
// d<target frequency in MHz>f<start frequency>f<stop frequency>f<frequency step> - Tune and Match
// h - Homing
// v<VM voltage in V>v<VT voltage in V> - Set Voltages
// r<frequency in MHz> - Measure Reflection
// s<start voltage in V>s<stop voltage in V>s<voltage step in V> - Voltage Sweep
// c<filter identifier> - Control Switch for the filterbank 'p' stands for preamplifier and 'a' for automatic tuning and matching.
// m<stepper identifier><steps> - Move stepper motor. 't' for tuner and 'm' for matcher. Positive steps move the stepper away from the motor and negative steps move the stepper towards the motor.
// p<tuning range in steps>t<tuning step in steps>t<tuning backlash in steps>m<matching range in steps>m<matching step in steps>m<matching backlash in steps> - Position Sweep
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
commandManager.executeCommand(command, input_line);
commandManager.printCommandResult(command);
// 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')
/*
Optimize matching call
else if (command == 'm')
{
printInfo("Optimize Matching around frequency:");
@ -183,10 +196,7 @@ void loop()
{
printInfo("Calibrating ...");
getCalibrationValues();
}
else
{
printInfo("Invalid Input");
}*/
} */
}
}

View file

@ -1,25 +1,34 @@
#include "CommandManager.h"
void CommandManager::registerCommand(char identifier, Command* command) {
void CommandManager::registerCommand(char identifier, Command *command)
{
commandMap[identifier] = command;
}
void CommandManager::executeCommand(char identifier, String input_line) {
void CommandManager::executeCommand(char identifier, String input_line)
{
auto it = commandMap.find(identifier);
if (it != commandMap.end()) {
Command* command = it->second;
command->execute(input_line);
} else {
if (it != commandMap.end())
{
Command *command = it->second;
command->confirmAndExecute(input_line);
}
else
{
Serial.println("Unknown command.");
}
}
void CommandManager::printCommandResult(char identifier) {
void CommandManager::printCommandResult(char identifier)
{
auto it = commandMap.find(identifier);
if (it != commandMap.end()) {
Command* command = it->second;
if (it != commandMap.end())
{
Command *command = it->second;
command->printResult();
} else {
}
else
{
Serial.println("Unknown command.");
}
}

View file

@ -2,7 +2,6 @@
#include <AccelStepper.h>
#include <MultiStepper.h>
#include "Utilities.h"
// Frequency Settings
@ -10,7 +9,7 @@
#define START_FREQUENCY 50000000U // 50MHz
#define STOP_FREQUENCY 110000000 // 110MHz
int32_t findCurrentResonanceFrequency(uint32_t start_frequency, uint32_t stop_frequency, uint32_t frequency_step)
int32_t findCurrentResonanceFrequency(uint32_t start_frequency, uint32_t stop_frequency, uint32_t frequency_step, boolean print_data)
{
int maximum_reflection = 0;
int current_reflection = 0;
@ -18,21 +17,20 @@ int32_t findCurrentResonanceFrequency(uint32_t start_frequency, uint32_t stop_fr
uint32_t minimum_frequency = 0;
float reflection = 0;
adf4351.setf(start_frequency); // A frequency value needs to be set once -> there seems to be a bug with the first SPI call
setFrequency(start_frequency); // A frequency value needs to be set once -> there seems to be a bug with the first SPI call
delay(50);
for (uint32_t frequency = start_frequency; frequency <= stop_frequency; frequency += frequency_step)
{
//adf4351.setf(frequency);
// setFrequency(frequency);
setFrequency(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);
current_phase = readPhase(4);
current_reflection = readReflection(8);
current_phase = readPhase(1);
// Send out the frequency identifier f with the frequency value
Serial.println(String("f") + frequency + "r" + current_reflection + "p" + current_phase);
if (print_data)
Serial.println(String("f") + frequency + "r" + current_reflection + "p" + current_phase);
if (current_reflection > maximum_reflection)
{
@ -41,7 +39,7 @@ int32_t findCurrentResonanceFrequency(uint32_t start_frequency, uint32_t stop_fr
}
}
adf4351.setf(minimum_frequency);
setFrequency(minimum_frequency);
delay(50);
reflection = readReflection(16);
if (reflection < 130)
@ -55,8 +53,7 @@ int32_t findCurrentResonanceFrequency(uint32_t start_frequency, uint32_t stop_fr
maximum_reflection = 0;
for (uint32_t frequency = minimum_frequency - 300000U; frequency <= minimum_frequency + 300000U; frequency += frequency_step)
{
adf4351.setf(frequency);
delay(100); // Higher delay so the capacitor has time to charge
setFrequency(frequency);
current_reflection = readReflection(64);
@ -70,54 +67,64 @@ int32_t findCurrentResonanceFrequency(uint32_t start_frequency, uint32_t stop_fr
return minimum_frequency;
}
void frequencySweep(uint32_t start_frequency, uint32_t stop_frequency, uint32_t frequency_step, boolean print_data, int averages)
{
int current_reflection = 0;
int current_phase = 0;
setFrequency(start_frequency); // A frequency value needs to be set once -> there seems to be a bug with the first SPI call
delay(50);
for (uint32_t frequency = start_frequency; frequency <= stop_frequency; frequency += frequency_step)
{
setFrequency(frequency);
current_reflection = readReflection(averages);
current_phase = readPhase(1);
// Send out the frequency identifier f with the frequency value
if (print_data)
Serial.println(String("f") + frequency + "r" + current_reflection + "p" + current_phase);
}
}
void setFrequency(uint32_t frequency)
{
// First we check what filter has to be used from the FILTERS array
// Then we set the filterbank accordingly
for (int i = 0; i < sizeof(FILTERS) / sizeof(FILTERS[0]); i++)
int i;
for (i = 0; i < sizeof(FILTERS) / sizeof(FILTERS[0]); i++)
{
// For the first filter we just check if the frequency is below the fg
if ((i == 0) && (frequency < FILTERS[i].fg))
{
digitalWrite(FILTER_SWITCH_A, FILTERS[i].control_input_a);
digitalWrite(FILTER_SWITCH_B, FILTERS[i].control_input_b);
break;
}
// For the last filter we just check if the frequency is above the fg
else if ((i == sizeof(FILTERS) / sizeof(FILTERS[0]) - 1) && (frequency > FILTERS[i].fg))
{
digitalWrite(FILTER_SWITCH_A, FILTERS[i].control_input_a);
digitalWrite(FILTER_SWITCH_B, FILTERS[i].control_input_b);
break;
}
// For the filters in between we check if the frequency is between the fg and the fg of the previous filter
else if ((frequency < FILTERS[i].fg) && (frequency > FILTERS[i - 1].fg))
{
digitalWrite(FILTER_SWITCH_A, FILTERS[i].control_input_a);
digitalWrite(FILTER_SWITCH_B, FILTERS[i].control_input_b);
else if ((frequency < FILTERS[i].fg) && (frequency > FILTERS[i - 1].fg))
break;
}
}
if (active_filter.fg != FILTERS[i].fg)
{
printInfo("Switching filter to: " + String(FILTERS[i].fg) + "Hz");
active_filter = FILTERS[i];
digitalWrite(FILTER_SWITCH_A, FILTERS[i].control_input_a);
digitalWrite(FILTER_SWITCH_B, FILTERS[i].control_input_b);
}
// Finally we set the frequency
adf4351.setf(frequency);
}
int readReflection(int averages)
{
int reflection = 0;
for (int i = 0; i < averages; i++)
// We multiply by 1000 to get the result in millivolts
reflection += (adac.read_ADC(0) * 1000);
return reflection / averages;
return (adac.read_ADC(MAGNITUDE, averages) * 1000);
}
int readPhase(int averages)
{
int phase = 0;
for (int i = 0; i < averages; i++)
phase += (adac.read_ADC(1) * 1000);
return phase / averages;
return (adac.read_ADC(PHASE, averages) * 1000);
}
int sumReflectionAroundFrequency(uint32_t center_frequency)
@ -127,8 +134,7 @@ int sumReflectionAroundFrequency(uint32_t center_frequency)
// sum approach -> cummulates reflection around resonance -> reduce influence of wrong minimum and noise
for (uint32_t frequency = center_frequency - 500000U; frequency < center_frequency + 500000U; frequency += FREQUENCY_STEP / 10)
{
adf4351.setf(frequency);
delay(10);
setFrequency(frequency);
sum_reflection += readReflection(16);
}
@ -182,7 +188,7 @@ int32_t bruteforceResonance(uint32_t target_frequency, uint32_t current_resonanc
if (current_resonance_frequency == target_frequency)
break;
adf4351.setf(current_resonance_frequency);
setFrequency(current_resonance_frequency);
delay(100);
resonance_reflection = readReflection(16);
DEBUG_PRINT(resonance_reflection);
@ -232,7 +238,7 @@ int optimizeMatching(uint32_t current_resonance_frequency)
DEBUG_PRINT(iteration_steps);
adf4351.setf(current_resonance_frequency);
setFrequency(current_resonance_frequency);
for (int i = 0; i < ITERATIONS; i++)
{
DEBUG_PRINT(i);
@ -252,7 +258,7 @@ int optimizeMatching(uint32_t current_resonance_frequency)
continue;
}
adf4351.setf(current_resonance_frequency);
setFrequency(current_resonance_frequency);
delay(100);
current_reflection = readReflection(16);
@ -295,7 +301,7 @@ int getMatchRotation(uint32_t current_resonance_frequency)
current_resonance_frequency = findCurrentResonanceFrequency(current_resonance_frequency - 1000000U, current_resonance_frequency + 1000000U, FREQUENCY_STEP / 10);
// int clockwise_match = sumReflectionAroundFrequency(current_resonance_frequency);
if (current_resonance_frequency != 0)
adf4351.setf(current_resonance_frequency);
setFrequency(current_resonance_frequency);
delay(100);
int clockwise_match = readReflection(64);
@ -304,7 +310,7 @@ int getMatchRotation(uint32_t current_resonance_frequency)
current_resonance_frequency = findCurrentResonanceFrequency(current_resonance_frequency - 1000000U, current_resonance_frequency + 1000000U, FREQUENCY_STEP / 10);
// int anticlockwise_match = sumReflectionAroundFrequency(current_resonance_frequency);
adf4351.setf(current_resonance_frequency);
setFrequency(current_resonance_frequency);
delay(100);
int anticlockwise_match = readReflection(64);
@ -333,6 +339,8 @@ int stallStepper(Stepper stepper)
stepper.STEPPER.stop();
delay(250);
return stepper.STEPPER.currentPosition(); // returns value until limit is reached
}
@ -384,14 +392,17 @@ uint32_t validateInput(float frequency_MHz)
}
}
void printInfo(String text) {
void printInfo(String text)
{
Serial.println("i" + text);
}
void printInfo(uint32_t number) {
void printInfo(uint32_t number)
{
Serial.println("i" + String(number)); // convert the number to a string before concatenating
}
void printError(String text) {
void printError(String text)
{
Serial.println("e" + text);
}

View file

@ -6,86 +6,98 @@
/**
* @brief This function finds the current resonance frequency of the coil. There should be a resonance already present or the algorithm might return nonsense.
* It also returns the data of the frequency scan which can then be sent to the PC for plotting.
*
*
* @param start_frequency The frequency at which the search should start
* @param stop_frequency The frequency at which the search should stop
* @param frequency_step The frequency step size
* @param print_data If true, the data will be printed to the serial monitor -> defaults to false
* @return int32_t The current resonance frequency
*
*
* @example findCurrentResonanceFrequency(START_FREQUENCY, STOP_FREQUENCY, FREQUENCY_STEP); // finds the current resonance frequency
*/
int32_t findCurrentResonanceFrequency(uint32_t start_frequency, uint32_t stop_frequency, uint32_t frequency_step);
*/
int32_t findCurrentResonanceFrequency(uint32_t start_frequency, uint32_t stop_frequency, uint32_t frequency_step, boolean print_data = false);
/**
* @brief This function sweeps the frequency from start_frequency to stop_frequency with a step size of frequency_step.
* It can be used for visualization of the reflection and phase data.
*
* @param start_frequency
* @param stop_frequency
* @param frequency_step
* @param print_data
*/
void frequencySweep(uint32_t start_frequency, uint32_t stop_frequency, uint32_t frequency_step, boolean print_data = false, int averages = 4);
/**
* @brief This function sets the frequency of the frequency synthesizer and switches the filterbank accordingly.
*
*
* @param frequency The frequency that should be set
* @return void
*
*
* @example setFrequency(100000000U); // sets the frequency to 100MHz
*/
*/
void setFrequency(uint32_t frequency);
/**
* @brief This function reads the reflection at the current frequency. It does not set the frequency.
*
*
* @param averages The number of readings that should be averaged
* @return int The average reflection in millivolts
*
*
* @example readReflection(64); // reads the reflection at the current frequency and averages over 64 readings
*/
*/
int readReflection(int averages);
/**
* @brief This function reads the phase at the current frequency. It does not set the frequency.
*
*
* @param averages The number of readings that should be averaged
* @return int The average phase in millivolts
*
*
* @example readPhase(64); // reads the phase at the current frequency and averages over 64 readings
*/
*/
int readPhase(int averages);
/**
* @brief This function sums up the reflection around a given frequency.
*
*
* @param center_frequency The frequency around which the reflection should be summed up
* @return int The sum of the reflection around the given frequency
*
*
* @example sumReflectionAroundFrequency(100000000U);
*/
*/
int sumReflectionAroundFrequency(uint32_t center_frequency);
/**
* @brief This function tries out different capacitor positions until iteration depth is reached OR current_resonancy frequency matches the target_frequency.
*
*
* @param target_frequency The frequency that should be matched
* @param current_resonance_frequency The current resonance frequency
* @return int32_t The current resonance frequency
*
*
* @example bruteforceResonance(100000000U, 90000000U); // tries to match 100MHz with a current resonance frequency of 90MHz
*/
*/
int32_t bruteforceResonance(uint32_t target_frequency, uint32_t current_resonance_frequency);
/**
* @brief This function tries to find a matching capacitor position that will decrease the reflection at the current resonance frequency to a minimum.
* @brief This function tries to find a matching capacitor position that will decrease the reflection at the current resonance frequency to a minimum.
* It will then move the stepper to this position.
*
*
* @param current_resonance_frequency The current resonance frequency
* @return int The reflection at the minimum matching position
*
*
* @example optimizeMatching(100000000); // tries to find the minimum reflection at 100 MHz and moves the stepper to this position
*/
*/
int optimizeMatching(uint32_t current_resonance_frequency);
/**
* @brief This function finds the direction which the matching capacitor should be turned to decrease the reflection.
*
*
* @param current_resonance_frequency The current resonance frequency
* @return int The direction in which the matching capacitor should be turned. 1 for clockwise, -1 for anticlockwise
*
*
* @example getMatchRotation(100000000); // returns the direction in which the matching capacitor should be turned at 100MHz to decrease the reflection
*/
*/
int getMatchRotation(uint32_t current_resonance_frequency);
/**
@ -93,56 +105,56 @@ int getMatchRotation(uint32_t current_resonance_frequency);
*
* @param stepper The stepper that should be stalled
* @return int The current position of the stepper
*
*
* @example stallStepper(tuner); // stalls the tuner stepper and returns the current position
*/
*/
int stallStepper(Stepper stepper);
/**
* @brief This function performs a homing of the steppers. It returns the current position of the stepper.
*
*
* @param stepper The stepper that should be homed
* @return long The current position of the stepper
*
*
* @example homeStepper(tuner); // homes the tuner stepper and returns the current position
*/
*/
long homeStepper(Stepper stepper);
/**
* @brief This function checks if the input is valid. It checks if the frequency is within the allowed range.
*
*
* @param frequency_MHz The frequency that should be checked in MHz.
* @return uint32_t The frequency in Hz if the input is valid, 0 otherwise.
*
*
* @example validateInput(100); // returns 100000000U
*/
*/
uint32_t validateInput(float frequency_MHz);
/**
* * @brief This method should be called when one wants to print information to the serial port.
*
* @param text The text that should be printed to the serial monitor. It should be a string
* @return void
*
* @example printInfo("This is a test"); // prints "iThis is a test"
*/
*
* @param text The text that should be printed to the serial monitor. It should be a string
* @return void
*
* @example printInfo("This is a test"); // prints "iThis is a test"
*/
void printInfo(String text);
/**
* @brief This method should be called when one wants to print information to the serial port.
*
* @param number The number that should be printed to the serial monitor. It should be a number
* @return void
*
* @example printInfo(123U); // prints "i123"
*/
* @brief This method should be called when one wants to print information to the serial port.
*
* @param number The number that should be printed to the serial monitor. It should be a number
* @return void
*
* @example printInfo(123U); // prints "i123"
*/
void printInfo(uint32_t number);
/**
* @brief This method should be called when one wants to an error to the serial port.
*
* @param text The text that should be printed to the serial monitor. It should be a string
* @return void
*
* @example printError("Duck not found"); // prints "eDuck not found"
*/
/**
* @brief This method should be called when one wants to an error to the serial port.
*
* @param text The text that should be printed to the serial monitor. It should be a string
* @return void
*
* @example printError("Duck not found"); // prints "eDuck not found"
*/
void printError(String text);

13
src/commands/Command.cpp Normal file
View file

@ -0,0 +1,13 @@
#include "Utilities.h"
#include "Command.h"
void Command::confirmAndExecute(String input_line)
{
confirmCommand();
execute(input_line);
}
void Command::confirmCommand()
{
Serial.println("c");
}

View file

@ -6,6 +6,11 @@
class Command {
public:
/**
* @brief Confirms the command then executes it.
*/
void confirmAndExecute(String input_line);
/**
* @brief Executes the command.
* Pure virtual function that must be implemented by derived classes.
@ -24,7 +29,10 @@ public:
*/
virtual void printHelp() = 0;
/**
* @brief Confirms the command by sending the 'c' character as confirmation.
*/
void confirmCommand();
};
#endif

View file

@ -0,0 +1,62 @@
#include "Utilities.h"
#include "ControlSwitch.h"
// Constructor
ControlSwitch::ControlSwitch()
{
pinMode(RF_SWITCH_PIN, OUTPUT);
switch_state = ATM_SYSTEM;
digitalWrite(RF_SWITCH_PIN, LOW);
}
void ControlSwitch::execute(String input_line)
{
char switch_to = input_line[1];
if ((switch_to == PRE_AMP) && (switch_state != PRE_AMP))
{
switch_state = PRE_AMP;
digitalWrite(RF_SWITCH_PIN, HIGH);
result = "Switched to preamp";
}
else if ((switch_to == ATM_SYSTEM) && (switch_state != ATM_SYSTEM))
{
switch_state = ATM_SYSTEM;
digitalWrite(RF_SWITCH_PIN, LOW);
result = "Switched to atm system";
}
else if (switch_to == switch_state)
{
String active_pathway = (switch_state == PRE_AMP) ? "preamp" : "atm system";
result = "Already switched to " + String(active_pathway);
}
else
{
result = "Invalid switch state";
}
}
void ControlSwitch::printResult()
{
printInfo(result);
if (switch_state == PRE_AMP)
{
Serial.println("cp");
}
else if (switch_state == ATM_SYSTEM)
{
Serial.println("ca");
}
}
void ControlSwitch::printHelp()
{
Serial.println("Control switch command");
Serial.println("Syntax: s<switch state>");
Serial.println("Example: sa");
Serial.println("This will switch to the atm system");
Serial.println("Possible switch states:");
Serial.println("p: preamp");
Serial.println("a: atm system");
}

View file

@ -0,0 +1,26 @@
#ifndef CONTROLSWITCH_H
#define CONTROLSWITCH_H
#define PRE_AMP 'p'
#define ATM_SYSTEM 'a'
#include "Command.h"
/**
* @brief This class is used to control the switch which is used to switch between the preamplifier and the atm system.
* It can take the command <switch_state> where switch_state is either p or a for preamp or atm system respectively.
*/
class ControlSwitch : public Command
{
public:
ControlSwitch();
void execute(String input_line) override;
void printResult() override;
void printHelp() override;
private:
char switch_state;
String result;
};
#endif

View file

@ -1,7 +1,8 @@
#include "Utilities.h"
#include "FrequencySweep.h"
void FrequencySweep::execute(String input_line) {
void FrequencySweep::execute(String input_line)
{
printInfo("Started frequency sweep");
// Get the start frequency which is the value until the next f character
char delimiter = 'f';
@ -11,21 +12,21 @@ void FrequencySweep::execute(String input_line) {
int freqStepIndex = input_line.indexOf(delimiter, stopFreqIndex) + 1;
// Extract each variable from the string
uint32_t startFreq = input_line.substring(startFreqIndex, stopFreqIndex - 1).toInt(); // Subtract 1 to not include the delimiter
uint32_t startFreq = input_line.substring(startFreqIndex, stopFreqIndex - 1).toInt(); // Subtract 1 to not include the delimiter
uint32_t stopFreq = input_line.substring(stopFreqIndex, freqStepIndex - 1).toInt();
uint32_t freqStep = input_line.substring(freqStepIndex).toInt(); // If no second parameter is provided, substring() goes to the end of the string
uint32_t freqStep = input_line.substring(freqStepIndex).toInt(); // If no second parameter is provided, substring() goes to the end of the string
// The find current resonance frequency also prints prints the S11 data to the serial monitor
resonance_frequency = findCurrentResonanceFrequency(startFreq, stopFreq, freqStep);
frequencySweep(startFreq, stopFreq, freqStep, true, 8);
}
void FrequencySweep::printResult() {
void FrequencySweep::printResult()
{
// This tells the PC that the frequency sweep is finished
Serial.print("r");
printInfo(resonance_frequency);
Serial.println("r");
}
void FrequencySweep::printHelp() {
void FrequencySweep::printHelp()
{
Serial.println("Frequency sweep command");
Serial.println("Syntax: f<start frequency>f<stop frequency>f<frequency step>");
Serial.println("Example: f100000000f200000000f50000");

View file

@ -12,7 +12,7 @@ public:
* @brief This function performs a frequency sweep
* @param input_line The input line from the serial monitor. The syntax is f<start frequency>f<stop frequency>f<frequency step>.
*/
void execute(String input_lne) override;
void execute(String input_line) override;
void printResult() override;
void printHelp() override;
private:

View file

@ -1,27 +1,37 @@
#include "Utilities.h"
#include "Homing.h"
void Homing::execute(String input_line) {
void Homing::execute(String input_line)
{
printInfo("Homing...");
// Move the steppers to their home position
tuner.STEPPER.setCurrentPosition(homeStepper(tuner));
matcher.STEPPER.setCurrentPosition(homeStepper(matcher));
// Now move the stepper a little bit away from the home position
uint32_t REST_POSITION = 10000;
tuner.STEPPER.moveTo(REST_POSITION);
tuner.STEPPER.runToPosition();
matcher.STEPPER.moveTo(REST_POSITION);
matcher.STEPPER.runToPosition();
}
void Homing::printResult() {
printInfo("Resonance frequency after homing:");
uint32_t startf = 35000000U;
uint32_t stopf = 110000000U;
uint32_t stepf = 100000U;
uint32_t resonance_frequency = findCurrentResonanceFrequency(startf, stopf, stepf / 2);
printInfo(resonance_frequency);
Serial.print("r");
void Homing::printResult()
{
printInfo("Homing finished");
// Format is p<tuning_position>m<matching_position>
uint32_t tuning_position = tuner.STEPPER.currentPosition();
uint32_t matching_position = matcher.STEPPER.currentPosition();
String position = "p" + String(tuning_position) + "m" + String(matching_position);
Serial.println(position);
}
void Homing::printHelp() {
void Homing::printHelp()
{
Serial.println("Homing command");
Serial.println("Syntax: h");
Serial.println("Example: h");
Serial.println("This will home the tuner and matcher");
}

View file

@ -7,7 +7,7 @@ void MeasureReflection::execute(String input_line)
return_loss = 0;
phase = 0;
printInfo("Measure Reflection");
// printInfo("Measure Reflection");
// Get the float after the r character which is the frequency value where the reflection measurment should be performed
float frequency_MHz = input_line.substring(1).toFloat();
uint32_t frequency = validateInput(frequency_MHz);
@ -28,7 +28,7 @@ void MeasureReflection::execute(String input_line)
void MeasureReflection::printResult()
{
// Print the results which are then read by the autotm module
char identifier = 'r';
char identifier = 'm';
char delimiter = 'p';
String text = String(identifier) + String(return_loss) + String(delimiter) + String(phase);

View file

@ -4,7 +4,7 @@
#include "Command.h"
/**
* @brief This class is used to perform a frequency sweep
* @brief This class is used to measure the reflection at a given frequency
*/
class MeasureReflection : public Command
{

View file

@ -0,0 +1,53 @@
#include "Utilities.h"
#include "MoveStepper.h"
void MoveStepper::execute(String input_line)
{
#define MATCHING_STEPPER 'm'
#define TUNING_STEPPER 't'
// Input format is m<stepper motor><steps>,<backlash>
char stepper = input_line[1];
input_line = input_line.substring(2);
uint32_t steps = input_line.substring(0, input_line.indexOf(',')).toInt();
input_line = input_line.substring(input_line.indexOf(',') + 1);
uint32_t backlash = input_line.toInt();
if (stepper == MATCHING_STEPPER)
{
uint32_t matching_position = matcher.STEPPER.currentPosition();
matcher.STEPPER.move(steps + backlash);
matcher.STEPPER.runToPosition();
matcher.STEPPER.setCurrentPosition(matching_position + steps);
}
else if (stepper == TUNING_STEPPER)
{
uint32_t tuning_position = tuner.STEPPER.currentPosition();
tuner.STEPPER.move(steps + backlash);
tuner.STEPPER.runToPosition();
tuner.STEPPER.setCurrentPosition(tuning_position + steps);
}
else
{
printInfo("Invalid stepper motor");
}
}
void MoveStepper::printResult()
{
// Print Info confirmation
printInfo("Fnished moving stepper");
uint32_t tuning_position = tuner.STEPPER.currentPosition();
uint32_t matching_position = matcher.STEPPER.currentPosition();
String position = "p" + String(tuning_position) + "m" + String(matching_position);
Serial.println(position);
}
void MoveStepper::printHelp()
{
Serial.println("Move stepper command");
Serial.println("Syntax: m<stepper motor><steps>");
Serial.println("Example: mt100");
Serial.println("This will move the tuning stepper motor 100 steps");
}

View file

@ -0,0 +1,22 @@
#ifndef MOVESTEPPER_H
#define MOVESTEPPER_H
#include "Command.h"
/**
* @brief This class is used to move either the tuning or matching stepper motor
*/
class MoveStepper : public Command {
public:
/**
* @brief This function moves the stepper motor
* @param input_line The input line from the serial monitor.
*/
void execute(String input_line) override;
void printResult() override;
void printHelp() override;
private:
uint32_t resonance_frequency;
};
#endif

View file

@ -0,0 +1,181 @@
#include "Utilities.h"
#include "PositionSweep.h"
void PositionSweep::execute(String input_line)
{
// Command structure: p<frequency in MHz>t<range>,<step size>,<backlash>,<last_direction>m<range>,<step size>,<backlash>,<last_direction>"
// First we get the frequency where the voltage sweep should be performed
float frequency_MHz = input_line.substring(1, input_line.indexOf('t')).toFloat();
uint32_t frequency = validateInput(frequency_MHz);
if (frequency == 0)
{
printInfo("Invalid frequency input for voltage sweep.");
return;
}
// Set the frequency
setFrequency(frequency);
// Then we get the tuning parameters
String tuning_parameters = input_line.substring(input_line.indexOf('t') + 1, input_line.indexOf('m'));
uint32_t tuning_range = tuning_parameters.substring(0, tuning_parameters.indexOf(',')).toInt();
tuning_parameters = tuning_parameters.substring(tuning_parameters.indexOf(',') + 1);
uint32_t tuning_step = tuning_parameters.substring(0, tuning_parameters.indexOf(',')).toInt();
tuning_parameters = tuning_parameters.substring(tuning_parameters.indexOf(',') + 1);
uint32_t tuning_backlash = tuning_parameters.substring(0, tuning_parameters.indexOf(',')).toInt();
tuning_parameters = tuning_parameters.substring(tuning_parameters.indexOf(',') + 1);
tuning_last_direction = tuning_parameters.toInt();
// Then we get the matching parameters
String matching_parameters = input_line.substring(input_line.indexOf('m') + 1);
uint32_t matching_range = matching_parameters.substring(0, matching_parameters.indexOf(',')).toInt();
matching_parameters = matching_parameters.substring(matching_parameters.indexOf(',') + 1);
uint32_t matching_step = matching_parameters.substring(0, matching_parameters.indexOf(',')).toInt();
matching_parameters = matching_parameters.substring(matching_parameters.indexOf(',') + 1);
uint32_t matching_backlash = matching_parameters.substring(0, matching_parameters.indexOf(',')).toInt();
matching_parameters = matching_parameters.substring(matching_parameters.indexOf(',') + 1);
matching_last_direction = matching_parameters.toInt();
printInfo("Tuning and Matching to target frequency in MHz (automatic mode):");
// Perform the position sweep
sweepPositions(tuning_range, tuning_step, tuning_backlash, matching_range, matching_step, matching_backlash);
// Finally we set the found positions
absolute_move_backlashcorrected(tuner, tuning_position, tuning_backlash);
absolute_move_backlashcorrected(matcher, matching_position, matching_backlash);
}
void PositionSweep::sweepPositions(uint32_t tuning_range, uint32_t tuning_step, uint32_t tuning_backlash, uint32_t matching_range, uint32_t matching_step, uint32_t matching_backlash)
{
int AVERAGES = 4;
// We want to maximize the reflection value, so we start with zero
float_t minimum_reflection = 0.0;
// First we get the current tuning position
uint32_t start_tuning_position = tuner.STEPPER.currentPosition();
// Then we get the current matching position
uint32_t start_matching_position = matcher.STEPPER.currentPosition();
uint32_t minimum_tuning_position = start_tuning_position - tuning_range;
// Maximum tuning position
uint32_t maximum_tuning_position = start_tuning_position + tuning_range;
// Minimum matching position
uint32_t minimum_matching_position = start_matching_position - matching_range;
// Maximum matching position
uint32_t maximum_matching_position = start_matching_position + matching_range;
// The variables here are absolute positions
for (uint32_t c_tuning_position = minimum_tuning_position; c_tuning_position <= maximum_tuning_position; c_tuning_position += tuning_step)
{
int backlash_compensation = absolute_move_backlashcorrected(tuner, c_tuning_position, tuning_backlash);
for (uint32_t c_matching_position = minimum_matching_position; c_matching_position <= maximum_matching_position; c_matching_position += matching_step)
{
// Set the tuning and matching voltage
int backlash_compensation = absolute_move_backlashcorrected(matcher, c_matching_position, matching_backlash);
// Measure the reflection at the given frequency
int reflection = readReflection(AVERAGES);
// If the reflection is lower than the current minimum, we have found a new minimum
if (reflection > minimum_reflection)
{
minimum_reflection = reflection;
tuning_position = c_tuning_position;
matching_position = c_matching_position;
}
}
}
}
int PositionSweep::absolute_move_backlashcorrected(Stepper stepper, uint32_t position, int32_t backlash)
{
uint32_t current_position;
// First we calculate the direction of the movement
int direction_to_move = 0;
int32_t backlash_compensation = 0;
// Get the stepper type
String stepper_type = stepper.TYPE;
int stepper_last_direction;
if (stepper_type == "Tuner")
{
current_position = tuner.STEPPER.currentPosition();
stepper_last_direction = tuning_last_direction;
}
else if (stepper_type == "Matcher")
{
current_position = matcher.STEPPER.currentPosition();
stepper_last_direction = matching_last_direction;
}
int difference = position - (int32_t)current_position;
// If the difference is negative the direction is negative
if (difference < 0)
direction_to_move = -1;
// If the difference is positive the direction is positive
else if (difference > 0)
direction_to_move = 1;
// Now we check if the backlash compensation is necessary
// If the direction of the movement is the same as the last direction, we do not need to compensate for backlash
if (direction_to_move == stepper_last_direction)
{
backlash_compensation = 0;
}
// If the direction of the movement is different from the last direction, we need to compensate for backlash
else
{
backlash_compensation = backlash * direction_to_move;
}
// Now we move the stepper motor
// And we set the last direction
// This done here like that because I don't want to use pointers
if (stepper_type == "Tuner")
{
tuning_last_direction = direction_to_move;
tuner.STEPPER.moveTo(position + backlash_compensation);
tuner.STEPPER.runToPosition();
tuner.STEPPER.setCurrentPosition(position);
}
else if (stepper_type == "Matcher")
{
matching_last_direction = direction_to_move;
matcher.STEPPER.moveTo(position + backlash_compensation);
matcher.STEPPER.runToPosition();
}
return backlash_compensation;
}
void PositionSweep::printResult()
{
// Print the results which are then read by the autotm module
String identifier = "z";
char delimiter = 'm';
String text = String(identifier) + String(tuning_position) + "," + String(tuning_last_direction) + String(delimiter) + String(matching_position) + "," + String(matching_last_direction);
Serial.println(text);
}
void PositionSweep::printHelp()
{
Serial.println("Position sweep command");
Serial.println("Syntax: p<frequency in MHz>t<range>,<step size>,<backlash>m<range>,<step size>,<backlash>");
Serial.println("Example: p100t100,20,1m100,20,1");
Serial.println("This will perform a position sweep around 100MHz with a tuning range of 100 steps, a tuning step size of 20 step and a tuning backlash of 1 step. The same parameters are used for the matching stepper motor.");
}

View file

@ -0,0 +1,25 @@
#ifndef POSITIONSWEEP_H
#define POSITIONSWEEP_H
#include "Command.h"
/**
* @brief This class is used to perform a position sweep. This means the return loss at a given frequency will be measured and the tuning and matching positions with the lowest return loss will be found.
*/
class PositionSweep : public Command
{
public:
void execute(String input_line) override;
void printResult() override;
void printHelp() override;
private:
void sweepPositions(uint32_t tuning_range, uint32_t tuning_step, uint32_t tuning_backlash, uint32_t matching_range, uint32_t matching_step, uint32_t matching_backlash);
int absolute_move_backlashcorrected(Stepper stepper, uint32_t position, int32_t backlash);
uint32_t tuning_position;
uint32_t matching_position;
int matching_last_direction = 0;
int tuning_last_direction = 0;
};
#endif

View file

@ -2,24 +2,41 @@
#include "SetVoltages.h"
void SetVoltages::execute(String input_line){
// Format is v<VT voltage>v<VM voltage>
// Example: v0.5v0.5
// This will set the VM and VT voltages to 0.5 V
char delimiter = 'v';
int VMIndex = input_line.indexOf(delimiter) + 1;
int VTIndex = input_line.indexOf(delimiter, VMIndex) + 1;
// remove first character
input_line = input_line.substring(1);
int delimiter_index = input_line.indexOf(delimiter);
if (delimiter_index == -1){
printInfo("Invalid input for set voltages command.");
return;
}
float VMVoltage = input_line.substring(VMIndex, VTIndex - 1).toFloat();
float VTVoltage = input_line.substring(VTIndex).toFloat();
String tuning_voltage_str = input_line.substring(0, delimiter_index);
String matching_voltage_str = input_line.substring(delimiter_index + 1);
adac.write_DAC(VM, VMVoltage);
adac.write_DAC(VT, VTVoltage);
tuning_voltage = tuning_voltage_str.toFloat();
matching_voltage = matching_voltage_str.toFloat();
adac.write_DAC(VT, tuning_voltage);
adac.write_DAC(VM, matching_voltage);
}
void SetVoltages::printResult(){
printInfo("Voltages set successfully");
// Print the results which are then read by the autotm module
char identifier = 'v';
char delimiter = 't';
String text = String(identifier) + String(tuning_voltage) + String(delimiter) + String(matching_voltage);
Serial.println(text);
}
void SetVoltages::printHelp(){
Serial.println("Set voltages command");
Serial.println("Syntax: v<VM voltage>v<VT voltage>");
Serial.println("Syntax: v<VT voltage>v<VM voltage>");
Serial.println("Example: v0.5v0.5");
Serial.println("This will set the VM and VT voltages to 0.5 V");
}

View file

@ -15,6 +15,9 @@ public:
void execute(String input_line) override;
void printResult() override;
void printHelp() override;
float_t matching_voltage;
float_t tuning_voltage;
};
#endif

View file

@ -0,0 +1,171 @@
#include "Utilities.h"
#include "VoltageSweep.h"
void VoltageSweep::execute(String input_line)
{
// Command format is s<frequency in MHz>o<optional tuning voltage>o<optional matching voltage>
// First we get the frequency where the voltage sweep should be performed
char identifier = 's';
char delimiter = 'o';
int frequencyIndex = input_line.indexOf(identifier) + 1;
// Check if optional voltages are given
if (input_line.indexOf(delimiter, frequencyIndex) == -1)
{
// If no optional voltages are given, we perform an automatic voltage sweep
String frequency_MHz = input_line.substring(frequencyIndex);
uint32_t frequency = validateInput(frequency_MHz.toFloat());
if (frequency == 0)
{
printInfo("Invalid frequency input for voltage sweep.");
return;
}
printInfo("Starting automatic voltage sweep at " + String(frequency) + " MHz");
automaticSweep(frequency);
return;
}
else
{
// If optional voltages are given, we perform a voltage sweep with the given voltages
int tuningIndex = input_line.indexOf(delimiter, frequencyIndex) + 1;
int matchingIndex = input_line.indexOf(delimiter, tuningIndex) + 1;
float frequency_MHz = input_line.substring(frequencyIndex, tuningIndex - 1).toFloat();
float tuning_voltage_predefined = input_line.substring(tuningIndex, matchingIndex - 1).toFloat();
float matching_voltage_predefined = input_line.substring(matchingIndex).toFloat();
uint32_t frequency = validateInput(frequency_MHz);
if (frequency == 0)
{
printInfo("Invalid frequency input for voltage sweep.");
return;
}
printInfo("Starting preset voltage sweep at " + String(frequency) + " MHz" + " with tuning voltage " + String(tuning_voltage_predefined) + " V and matching voltage " + String(matching_voltage_predefined) + " V");
presetVoltages(frequency, tuning_voltage_predefined, matching_voltage_predefined);
}
}
void VoltageSweep::presetVoltages(uint32_t frequency, float_t tuning_voltage_predefined, float_t matching_voltage_predefined)
{
// For preset voltages we only scan the tuning and matching voltages in a reduced range. This is because the voltages are already close to the optimum values.
float_t TUNING_RANGE = 0.2;
float_t MATCHING_RANGE = 0.2;
float_t voltage_step = 0.01;
setFrequency(frequency);
float_t tuning_voltage_start = tuning_voltage_predefined - TUNING_RANGE;
if (tuning_voltage_start < 0.0)
tuning_voltage_start = 0.0;
float_t tuning_voltage_stop = tuning_voltage_predefined + TUNING_RANGE;
if (tuning_voltage_stop > 5.0)
tuning_voltage_stop = 5.0;
printInfo("Tuning voltage start: " + String(tuning_voltage_start));
float_t matching_voltage_start = matching_voltage_predefined - MATCHING_RANGE;
if (matching_voltage_start < 0.0)
matching_voltage_start = 0.0;
float_t matching_voltage_stop = matching_voltage_predefined + MATCHING_RANGE;
if (matching_voltage_stop > 5.0)
matching_voltage_stop = 5.0;
// We use the sweepVoltages function to find the optimum tuning and matching voltages
sweepVoltages(voltage_step, tuning_voltage_start, tuning_voltage_stop, matching_voltage_start, matching_voltage_stop);
adac.write_DAC(VM, matching_voltage);
adac.write_DAC(VT, tuning_voltage);
}
void VoltageSweep::automaticSweep(uint32_t frequency)
{
float MAX_VOLTAGE = 5.0;
float MIN_VOLTAGE = 0.0;
// First set the frequency 2 MHz below the target frequency
uint32_t distance = 2000000;
setFrequency(frequency - distance);
// Then we perform a rough voltage sweep with a step size of 1V
sweepVoltages(1, MIN_VOLTAGE, MAX_VOLTAGE, MIN_VOLTAGE, MAX_VOLTAGE);
// Now we set the target frequency
setFrequency(frequency);
// Then we perform a more precise voltage sweep with a step size of 0.1V but only in the region of the found voltages
float_t tuning_range = 0.5;
sweepVoltages(0.05, tuning_voltage - tuning_range, tuning_voltage + tuning_range, MIN_VOLTAGE, MAX_VOLTAGE);
// Then we perform a last very precise voltage sweep with a step size of 0.01V but only in the region of the found voltages
tuning_range = 0.1;
float_t matching_range = 0.2;
sweepVoltages(0.01, tuning_voltage - tuning_range, tuning_voltage + tuning_range, matching_voltage - matching_range, matching_voltage + matching_range);
if (tuning_voltage < 0.0 || matching_voltage < 0.0)
{
tuning_voltage = 0.0;
matching_voltage = 0.0;
printError("No valid voltages found for automatic voltage sweep.");
}
// Finally we set the found voltages
adac.write_DAC(VM, matching_voltage);
adac.write_DAC(VT, tuning_voltage);
}
void VoltageSweep::sweepVoltages(float_t voltage_step, float_t tuning_start, float_t tuning_stop, float_t matching_start, float_t matching_stop)
{
int AVERAGES = 4;
// We want to maximize the reflection value, so we start with zero
float_t minimum_reflection = 0.0;
// This bruteforces the optimum voltage for tuning and matching.
for (float_t c_tuning_voltage = tuning_start; c_tuning_voltage <= tuning_stop; c_tuning_voltage += voltage_step)
{
for (float_t c_matching_voltage = matching_start; c_matching_voltage <= matching_stop; c_matching_voltage += voltage_step)
{
// Set the tuning and matching voltage
adac.write_DAC(VT, c_tuning_voltage);
adac.write_DAC(VM, c_matching_voltage);
// Measure the reflection at the given frequency
int reflection = readReflection(AVERAGES);
// If the reflection is lower than the current minimum, we have found a new minimum
if (reflection > minimum_reflection)
{
minimum_reflection = reflection;
tuning_voltage = c_tuning_voltage;
matching_voltage = c_matching_voltage;
// If the returnloss is better than 14dB, we can stop the voltage sweep
// float_t reflection_db = (reflection - 900) / 30.0;
// if (reflection_db > 14)
// return;
}
}
}
}
void VoltageSweep::printResult()
{
// Print the results which are then read by the autotm module
char identifier = 'v';
char delimiter = 't';
String text = String(identifier) + String(tuning_voltage) + String(delimiter) + String(matching_voltage);
Serial.println(text);
}
void VoltageSweep::printHelp()
{
Serial.println("Voltage sweep command");
Serial.println("Syntax: v<frequency in MHz>");
Serial.println("Example: v100");
Serial.println("This will perform a voltage sweep at 100 MHz and will return the optimum tuning and matching voltages for this frequency.");
}

View file

@ -0,0 +1,24 @@
#ifndef VOLTAGESWEEP_H
#define VOLTAGESWEEP_H
#include "Command.h"
/**
* @brief This class is used to perform a voltage sweep. This means the return loss at a given frequency will be measured and the tuning and matching voltage with the lowest return loss will be found.
*/
class VoltageSweep : public Command
{
public:
void execute(String input_line) override;
void printResult() override;
void printHelp() override;
private:
void sweepVoltages(float_t voltage_step, float_t tuning_start, float_t tuning_stop, float_t matching_start, float_t matching_stop);
void automaticSweep(uint32_t frequency);
void presetVoltages(uint32_t frequency, float_t tuning_voltage, float_t matching_voltage);
float_t matching_voltage;
float_t tuning_voltage;
};
#endif

View file

@ -5,10 +5,10 @@
#include <AccelStepper.h>
#include <MultiStepper.h>
#include <math.h>
#include "ADF4351.h"
#include "ADF4351.h"
#include "AD5593R.h"
#include "Pins.h" // Pins are defined here
#include "Pins.h" // Pins are defined here
#include "Stepper.h"
#include "Positions.h" // Calibrated frequency positions are defined her
@ -24,4 +24,6 @@ extern Stepper tuner;
extern Stepper matcher;
extern AD5593R adac;
extern Filter active_filter;
#endif