Compare commits
31 commits
masterproj
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
d770b1131a | ||
![]() |
388506939f | ||
![]() |
21b098f750 | ||
![]() |
4e8fdff53e | ||
![]() |
222c75effb | ||
![]() |
884efd0174 | ||
![]() |
cf5a01b759 | ||
![]() |
a7606fc7a8 | ||
![]() |
8b9d62d308 | ||
![]() |
54c1b8ee4f | ||
![]() |
c784162b2e | ||
![]() |
18ab4f0d34 | ||
![]() |
0eea23b0fe | ||
![]() |
126953f690 | ||
![]() |
3fa166ee7b | ||
![]() |
3fda6ff2fb | ||
![]() |
723c36fca8 | ||
![]() |
b11d01bf13 | ||
![]() |
359152620a | ||
![]() |
bd46b8ae9f | ||
![]() |
f2331ecd85 | ||
![]() |
ca09dbe1fa | ||
![]() |
b3b93551f2 | ||
![]() |
d62edee8a5 | ||
![]() |
1ae4a52fd1 | ||
![]() |
cd22f0c8ad | ||
![]() |
1ecf275b60 | ||
![]() |
155921a0e1 | ||
![]() |
1cb7a66f1d | ||
![]() |
ca497e8360 | ||
![]() |
80943d4e9d |
73
README.md
|
@ -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.
|
||||
|
|
BIN
docs/img/AutoTM_software.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
docs/img/S11_Full_mechcoil_82-87.png
Normal file
After Width: | Height: | Size: 591 KiB |
BIN
docs/img/S11_full.png
Normal file
After Width: | Height: | Size: 338 KiB |
BIN
docs/img/el_instr.png
Normal file
After Width: | Height: | Size: 404 KiB |
BIN
docs/img/mech_instr.png
Normal file
After Width: | Height: | Size: 372 KiB |
BIN
docs/img/refl_inst.png
Normal file
After Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 2.4 MiB |
|
@ -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).
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
|
|
56
src/ATM.ino
|
@ -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");
|
||||
}*/
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
122
src/Utilities.h
|
@ -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
|
@ -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");
|
||||
}
|
|
@ -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
|
62
src/commands/ControlSwitch.cpp
Normal 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");
|
||||
}
|
26
src/commands/ControlSwitch.h
Normal 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
|
|
@ -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");
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
53
src/commands/MoveStepper.cpp
Normal 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");
|
||||
}
|
22
src/commands/MoveStepper.h
Normal 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
|
181
src/commands/PositionSweep.cpp
Normal 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.");
|
||||
}
|
25
src/commands/PositionSweep.h
Normal 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
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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
|
171
src/commands/VoltageSweep.cpp
Normal 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.");
|
||||
}
|
24
src/commands/VoltageSweep.h
Normal 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
|
|
@ -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
|