From 16e16bb03515827ea2e0d2ed514d0ab5cafc3eca Mon Sep 17 00:00:00 2001 From: Holger Wirtz Date: Fri, 13 Aug 2021 12:33:15 +0200 Subject: [PATCH 1/3] Added class for graphic eq. --- MicroDexed.ino | 4 + config.h | 32 ---- sgtl5000_graphic_eq.hpp | 375 +++++++++++++++++++++++++++------------- 3 files changed, 262 insertions(+), 149 deletions(-) diff --git a/MicroDexed.ino b/MicroDexed.ino index a140ddc..9f049e3 100644 --- a/MicroDexed.ino +++ b/MicroDexed.ino @@ -357,6 +357,10 @@ extern void sequencer(void); extern LCDMenuLib2 LCDML; #endif +#ifdef SGTL5000_AUDIO_ENHANCE +BiquadCoef graphic_eq(7); +#endif + extern void getNoteName(char* noteName, uint8_t noteNumber); /*********************************************************************** diff --git a/config.h b/config.h index 09d895d..24601f4 100644 --- a/config.h +++ b/config.h @@ -183,38 +183,6 @@ #define SGTL5000_LINEOUT_LEVEL 29 #endif -#ifdef SGTL5000_AUDIO_ENHANCE - -#define GRAPHIC_EQ_TYPE_0 bq_type_lowpass -#define GRAPHIC_EQ_CENTER_FRQ_0 115.0 -#define GRAPHIC_EQ_Q_0 2.0 - -#define GRAPHIC_EQ_TYPE_1 bq_type_bandpass -#define GRAPHIC_EQ_CENTER_FRQ_1 330.0 -#define GRAPHIC_EQ_Q_1 2.0 - -#define GRAPHIC_EQ_TYPE_2 bq_type_bandpass -#define GRAPHIC_EQ_CENTER_FRQ_2 990.0 -#define GRAPHIC_EQ_Q_2 2.0 - -#define GRAPHIC_EQ_TYPE_3 bq_type_bandpass -#define GRAPHIC_EQ_CENTER_FRQ_3 2000.0 -#define GRAPHIC_EQ_Q_3 2.0 - -#define GRAPHIC_EQ_TYPE_4 bq_type_bandpass -#define GRAPHIC_EQ_CENTER_FRQ_4 4000.0 -#define GRAPHIC_EQ_Q_4 2.0 - -#define GRAPHIC_EQ_TYPE_5 bq_type_bandpass -#define GRAPHIC_EQ_CENTER_FRQ_5 9900.0 -#define GRAPHIC_EQ_Q_5 2.0 - -#define GRAPHIC_EQ_TYPE_6 bq_type_highpass -#define GRAPHIC_EQ_CENTER_FRQ_6 11000.0 -#define GRAPHIC_EQ_Q_6 2.0 - -#endif - //************************************************************************************************* //* UI //************************************************************************************************* diff --git a/sgtl5000_graphic_eq.hpp b/sgtl5000_graphic_eq.hpp index 41b042c..f6e2642 100644 --- a/sgtl5000_graphic_eq.hpp +++ b/sgtl5000_graphic_eq.hpp @@ -3,69 +3,207 @@ #include #include +#define EQ_LOWPASS 0 +#define EQ_HIGHPASS 1 +#define EQ_BANDPASS 2 +#define EQ_NOTCH 3 +#define EQ_PEAK 4 +#define EQ_LOWSHELF 5 +#define EQ_HIGHSHELF 6 + +#define GRAPHIC_EQ_TYPE_0 EQ_HIGHPASS +#define GRAPHIC_EQ_CENTER_FRQ_0 115.0 +#define GRAPHIC_EQ_Q_0 2.0 + +#define GRAPHIC_EQ_TYPE_1 EQ_BANDPASS +#define GRAPHIC_EQ_CENTER_FRQ_1 330.0 +#define GRAPHIC_EQ_Q_1 2.0 + +#define GRAPHIC_EQ_TYPE_2 EQ_BANDPASS +#define GRAPHIC_EQ_CENTER_FRQ_2 990.0 +#define GRAPHIC_EQ_Q_2 2.0 + +#define GRAPHIC_EQ_TYPE_3 EQ_BANDPASS +#define GRAPHIC_EQ_CENTER_FRQ_3 2000.0 +#define GRAPHIC_EQ_Q_3 2.0 + +#define GRAPHIC_EQ_TYPE_4 EQ_BANDPASS +#define GRAPHIC_EQ_CENTER_FRQ_4 4000.0 +#define GRAPHIC_EQ_Q_4 2.0 + +#define GRAPHIC_EQ_TYPE_5 EQ_BANDPASS +#define GRAPHIC_EQ_CENTER_FRQ_5 9900.0 +#define GRAPHIC_EQ_Q_5 2.0 + +#define GRAPHIC_EQ_TYPE_6 EQ_LOWPASS +#define GRAPHIC_EQ_CENTER_FRQ_6 11000.0 +#define GRAPHIC_EQ_Q_6 2.0 + extern AudioControlSGTL5000 sgtl5000_1; -typedef struct biquad_coefficients_s { - float32_t a0; - float32_t a1; - float32_t a2; - float32_t b1; - float32_t b2; -} biquad_coefficients_t; - -typedef struct biquad_params_s { - uint8_t filter_type; - float32_t Fc; - float32_t Q; -} biquad_params_t; - -enum { - bq_type_lowpass = 0, - bq_type_highpass, - bq_type_bandpass, - bq_type_notch, - bq_type_peak, - bq_type_lowshelf, - bq_type_highshelf +class BiquadCoef +{ + public: + BiquadCoef(uint8_t num_bands); + ~BiquadCoef(); + + void set_eq_type(uint8_t band, uint8_t ft); + void set_eq_Fc(uint8_t band, float32_t frq); + void set_eq_Q(uint8_t band, float32_t q); + void set_gain(uint8_t band, float32_t gain); + void get_coef(uint8_t band, int* c); + + private: + void calcBiquadCoefficients(uint8_t band); + + uint8_t num_bands; + + uint8_t *filter_type; + float32_t *Fc; + float32_t *Q; + float32_t *peakGainDB; + + float32_t *a0; + float32_t *a1; + float32_t *a2; + float32_t *b1; + float32_t *b2; }; -#define NUM_GRAPHIC_EQ_BANDS 7 +BiquadCoef::BiquadCoef(uint8_t num_bands) +{ + num_bands = constrain(num_bands, 1, 7); + + sgtl5000_1.eqFilterCount(num_bands); + + filter_type = new uint8_t[num_bands]; + Fc = new float32_t[num_bands]; + Q = new float32_t[num_bands]; + peakGainDB = new float32_t[num_bands]; + a0 = new float32_t[num_bands]; + a1 = new float32_t[num_bands]; + a2 = new float32_t[num_bands]; + b1 = new float32_t[num_bands]; + b2 = new float32_t[num_bands]; -biquad_coefficients_t biquad_coefficients[NUM_GRAPHIC_EQ_BANDS]; -biquad_params_t biquad_params[NUM_GRAPHIC_EQ_BANDS]; + set_eq_type(0, GRAPHIC_EQ_TYPE_0); + set_eq_Fc(0, GRAPHIC_EQ_CENTER_FRQ_0); + set_eq_Q(0, GRAPHIC_EQ_Q_0); + set_gain(0, 0.0); -void set_sgtl5000_7band_eq(uint8_t band, uint8_t filter_type, float32_t Fc, float32_t Q); -void set_sgtl5000_7band_eq_gain(uint8_t band, float32_t peakGainDB); -void setup_sgtl5000_graphic_7band_eq(void); -void calcBiquadCoefficients(biquad_coefficients_t* biquad_coefficients, uint8_t filter_type, float32_t Fc, float32_t Q, float32_t peakGain); + if (num_bands > 1) + { + set_eq_type(1, GRAPHIC_EQ_TYPE_1); + set_eq_Fc(1, GRAPHIC_EQ_CENTER_FRQ_1); + set_eq_Q(1, GRAPHIC_EQ_Q_1); + set_gain(1, 0.0); + } -void set_sgtl5000_7band_eq(uint8_t band, uint8_t filter_type, float32_t Fc, float32_t Q) + if (num_bands > 2) + { + set_eq_type(2, GRAPHIC_EQ_TYPE_2); + set_eq_Fc(2, GRAPHIC_EQ_CENTER_FRQ_2); + set_eq_Q(2, GRAPHIC_EQ_Q_2); + set_gain(2, 0.0); + } + + if (num_bands > 3) + { + set_eq_type(3, GRAPHIC_EQ_TYPE_3); + set_eq_Fc(3, GRAPHIC_EQ_CENTER_FRQ_3); + set_eq_Q(3, GRAPHIC_EQ_Q_3); + set_gain(3, 0.0); + } + + if (num_bands > 4) + { + set_eq_type(4, GRAPHIC_EQ_TYPE_4); + set_eq_Fc(4, GRAPHIC_EQ_CENTER_FRQ_4); + set_eq_Q(4, GRAPHIC_EQ_Q_4); + set_gain(4, 0.0); + } + + if (num_bands > 5) + { + set_eq_type(5, GRAPHIC_EQ_TYPE_5); + set_eq_Fc(5, GRAPHIC_EQ_CENTER_FRQ_5); + set_eq_Q(5, GRAPHIC_EQ_Q_5); + set_gain(5, 0.0); + } + + if (num_bands > 6) + { + set_eq_type(6, GRAPHIC_EQ_TYPE_6); + set_eq_Fc(6, GRAPHIC_EQ_CENTER_FRQ_6); + set_eq_Q(6, GRAPHIC_EQ_Q_6); + set_gain(6, 0.0); + } + + for (uint8_t i = 0; i < num_bands; i++) + { + int tmp[num_bands]; + + calcBiquadCoefficients(i); + get_coef(i, tmp); + sgtl5000_1.eqFilter(i, tmp); + } +} + +BiquadCoef::~BiquadCoef() +{ + ; +} + +void BiquadCoef::set_eq_type(uint8_t band, uint8_t ft) { - biquad_params[band].filter_type = filter_type; - biquad_params[band].Fc = Fc; - biquad_params[band].Q = Q; + int tmp[num_bands]; + + filter_type[band] = ft; + calcBiquadCoefficients(band); + get_coef(band, tmp); + sgtl5000_1.eqFilter(band, tmp); +} - calcBiquadCoefficients(&biquad_coefficients[band], filter_type, Fc, Q, 0.0); +void BiquadCoef::set_eq_Fc(uint8_t band, float32_t frq) +{ + int tmp[num_bands]; - //sgtl5000_1.eqFilter(band, biquad_coefficients); + Fc[band] = frq; + calcBiquadCoefficients(band); + get_coef(band, tmp); + sgtl5000_1.eqFilter(band, tmp); } -void set_sgtl5000_7band_eq_gain(uint8_t band, float32_t peakGainDB) +void BiquadCoef::set_eq_Q(uint8_t band, float32_t q) { - calcBiquadCoefficients(&biquad_coefficients[band], biquad_params[band].filter_type, biquad_params[band].Fc, biquad_params[band].Q, peakGainDB); - //sgtl5000_1.eqFilter(band, biquad_coefficients); + int tmp[num_bands]; + + Q[band] = q; + calcBiquadCoefficients(band); + get_coef(band, tmp); + sgtl5000_1.eqFilter(band, tmp); } -void setup_sgtl5000_graphic_7band_eq(void) +void BiquadCoef::set_gain(uint8_t band, float32_t gain) { - //sgtl5000_1.eqFilterCount(7); // enable 7 bands - set_sgtl5000_7band_eq(0, GRAPHIC_EQ_TYPE_0, GRAPHIC_EQ_CENTER_FRQ_0, GRAPHIC_EQ_Q_0); - set_sgtl5000_7band_eq(1, GRAPHIC_EQ_TYPE_1, GRAPHIC_EQ_CENTER_FRQ_1, GRAPHIC_EQ_Q_1); - set_sgtl5000_7band_eq(2, GRAPHIC_EQ_TYPE_2, GRAPHIC_EQ_CENTER_FRQ_2, GRAPHIC_EQ_Q_2); - set_sgtl5000_7band_eq(3, GRAPHIC_EQ_TYPE_3, GRAPHIC_EQ_CENTER_FRQ_3, GRAPHIC_EQ_Q_3); - set_sgtl5000_7band_eq(4, GRAPHIC_EQ_TYPE_4, GRAPHIC_EQ_CENTER_FRQ_4, GRAPHIC_EQ_Q_4); - set_sgtl5000_7band_eq(5, GRAPHIC_EQ_TYPE_5, GRAPHIC_EQ_CENTER_FRQ_5, GRAPHIC_EQ_Q_5); - set_sgtl5000_7band_eq(6, GRAPHIC_EQ_TYPE_6, GRAPHIC_EQ_CENTER_FRQ_6, GRAPHIC_EQ_Q_6); + int tmp[num_bands]; + + peakGainDB[band] = gain; + calcBiquadCoefficients(band); + get_coef(band, tmp); + sgtl5000_1.eqFilter(band, tmp); +} + +void BiquadCoef::get_coef(uint8_t band, int* c) +{ + if (c != NULL) + { + c[0] = a0[band] * 0x8000; + c[1] = a1[band] * 0x8000; + c[2] = a2[band] * 0x8000; + c[3] = b1[band] * 0x8000; + c[4] = b2[band] * 0x8000; + } } // Taken from https://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ @@ -86,100 +224,103 @@ void setup_sgtl5000_graphic_7band_eq(void) // You may modify and use this source code to create binary code // for your own purposes, free or commercial. // -void calcBiquadCoefficients(biquad_coefficients_t* biquad_coefficients, uint8_t filter_type, float32_t Fc, float32_t Q, float32_t peakGain) +void BiquadCoef::calcBiquadCoefficients(uint8_t band) { + if (band > num_bands) + band = num_bands; + float32_t norm; - float32_t V = pow(10, fabs(peakGain) / 20.0); - float32_t K = tan(M_PI * Fc); - switch (filter_type) { - case bq_type_lowpass: - norm = 1 / (1 + K / Q + K * K); - biquad_coefficients->a0 = K * K * norm; - biquad_coefficients->a1 = 2 * biquad_coefficients->a0; - biquad_coefficients->a2 = biquad_coefficients->a0; - biquad_coefficients->b1 = 2 * (K * K - 1) * norm; - biquad_coefficients->b2 = (1 - K / Q + K * K) * norm; + float32_t V = pow(10, fabs(peakGainDB[band]) / 20.0); + float32_t K = tan(M_PI * Fc[band]); + switch (filter_type[band]) { + case EQ_LOWPASS: + norm = 1 / (1 + K / Q[band] + K * K); + a0[band] = K * K * norm; + a1[band] = 2 * a0[band]; + a2[band] = a0[band]; + b1[band] = 2 * (K * K - 1) * norm; + b2[band] = (1 - K / Q[band] + K * K) * norm; break; - case bq_type_highpass: - norm = 1 / (1 + K / Q + K * K); - biquad_coefficients->a0 = 1 * norm; - biquad_coefficients->a1 = -2 * biquad_coefficients->a0; - biquad_coefficients->a2 = biquad_coefficients->a0; - biquad_coefficients->b1 = 2 * (K * K - 1) * norm; - biquad_coefficients->b2 = (1 - K / Q + K * K) * norm; + case EQ_HIGHPASS: + norm = 1 / (1 + K / Q[band] + K * K); + a0[band] = 1 * norm; + a1[band] = -2 * a0[band]; + a2[band] = a0[band]; + b1[band] = 2 * (K * K - 1) * norm; + b2[band] = (1 - K / Q[band] + K * K) * norm; break; - case bq_type_bandpass: - norm = 1 / (1 + K / Q + K * K); - biquad_coefficients->a0 = K / Q * norm; - biquad_coefficients->a1 = 0; - biquad_coefficients->a2 = -biquad_coefficients->a0; - biquad_coefficients->b1 = 2 * (K * K - 1) * norm; - biquad_coefficients->b2 = (1 - K / Q + K * K) * norm; + case EQ_BANDPASS: + norm = 1 / (1 + K / Q[band] + K * K); + a0[band] = K / Q[band] * norm; + a1[band] = 0; + a2[band] = -a0[band]; + b1[band] = 2 * (K * K - 1) * norm; + b2[band] = (1 - K / Q[band] + K * K) * norm; break; - case bq_type_notch: - norm = 1 / (1 + K / Q + K * K); - biquad_coefficients->a0 = (1 + K * K) * norm; - biquad_coefficients->a1 = 2 * (K * K - 1) * norm; - biquad_coefficients->a2 = biquad_coefficients->a0; - biquad_coefficients->b1 = biquad_coefficients->a1; - biquad_coefficients->b2 = (1 - K / Q + K * K) * norm; + case EQ_NOTCH: + norm = 1 / (1 + K / Q[band] + K * K); + a0[band] = (1 + K * K) * norm; + a1[band] = 2 * (K * K - 1) * norm; + a2[band] = a0[band]; + b1[band] = a1[band]; + b2[band] = (1 - K / Q[band] + K * K) * norm; break; - case bq_type_peak: - if (peakGain >= 0) { // boost - norm = 1 / (1 + 1 / Q * K + K * K); - biquad_coefficients->a0 = (1 + V / Q * K + K * K) * norm; - biquad_coefficients->a1 = 2 * (K * K - 1) * norm; - biquad_coefficients->a2 = (1 - V / Q * K + K * K) * norm; - biquad_coefficients->b1 = biquad_coefficients->a1; - biquad_coefficients->b2 = (1 - 1 / Q * K + K * K) * norm; + case EQ_PEAK: + if (peakGainDB[band] >= 0) { // boost + norm = 1 / (1 + 1 / Q[band] * K + K * K); + a0[band] = (1 + V / Q[band] * K + K * K) * norm; + a1[band] = 2 * (K * K - 1) * norm; + a2[band] = (1 - V / Q[band] * K + K * K) * norm; + b1[band] = a1[band]; + b2[band] = (1 - 1 / Q[band] * K + K * K) * norm; } else { // cut - norm = 1 / (1 + V / Q * K + K * K); - biquad_coefficients->a0 = (1 + 1 / Q * K + K * K) * norm; - biquad_coefficients->a1 = 2 * (K * K - 1) * norm; - biquad_coefficients->a2 = (1 - 1 / Q * K + K * K) * norm; - biquad_coefficients->b1 = biquad_coefficients->a1; - biquad_coefficients->b2 = (1 - V / Q * K + K * K) * norm; + norm = 1 / (1 + V / Q[band] * K + K * K); + a0[band] = (1 + 1 / Q[band] * K + K * K) * norm; + a1[band] = 2 * (K * K - 1) * norm; + a2[band] = (1 - 1 / Q[band] * K + K * K) * norm; + b1[band] = a1[band]; + b2[band] = (1 - V / Q[band] * K + K * K) * norm; } break; - case bq_type_lowshelf: - if (peakGain >= 0) { // boost + case EQ_LOWSHELF: + if (peakGainDB[band] >= 0) { // boost norm = 1 / (1 + sqrt(2) * K + K * K); - biquad_coefficients->a0 = (1 + sqrt(2 * V) * K + V * K * K) * norm; - biquad_coefficients->a1 = 2 * (V * K * K - 1) * norm; - biquad_coefficients->a2 = (1 - sqrt(2 * V) * K + V * K * K) * norm; - biquad_coefficients->b1 = 2 * (K * K - 1) * norm; - biquad_coefficients->b2 = (1 - sqrt(2) * K + K * K) * norm; + a0[band] = (1 + sqrt(2 * V) * K + V * K * K) * norm; + a1[band] = 2 * (V * K * K - 1) * norm; + a2[band] = (1 - sqrt(2 * V) * K + V * K * K) * norm; + b1[band] = 2 * (K * K - 1) * norm; + b2[band] = (1 - sqrt(2) * K + K * K) * norm; } else { // cut norm = 1 / (1 + sqrt(2 * V) * K + V * K * K); - biquad_coefficients->a0 = (1 + sqrt(2) * K + K * K) * norm; - biquad_coefficients->a1 = 2 * (K * K - 1) * norm; - biquad_coefficients->a2 = (1 - sqrt(2) * K + K * K) * norm; - biquad_coefficients->b1 = 2 * (V * K * K - 1) * norm; - biquad_coefficients->b2 = (1 - sqrt(2 * V) * K + V * K * K) * norm; + a0[band] = (1 + sqrt(2) * K + K * K) * norm; + a1[band] = 2 * (K * K - 1) * norm; + a2[band] = (1 - sqrt(2) * K + K * K) * norm; + b1[band] = 2 * (V * K * K - 1) * norm; + b2[band] = (1 - sqrt(2 * V) * K + V * K * K) * norm; } break; - case bq_type_highshelf: - if (peakGain >= 0) { // boost + case EQ_HIGHSHELF: + if (peakGainDB[band] >= 0) { // boost norm = 1 / (1 + sqrt(2) * K + K * K); - biquad_coefficients->a0 = (V + sqrt(2 * V) * K + K * K) * norm; - biquad_coefficients->a1 = 2 * (K * K - V) * norm; - biquad_coefficients->a2 = (V - sqrt(2 * V) * K + K * K) * norm; - biquad_coefficients->b1 = 2 * (K * K - 1) * norm; - biquad_coefficients->b2 = (1 - sqrt(2) * K + K * K) * norm; + a0[band] = (V + sqrt(2 * V) * K + K * K) * norm; + a1[band] = 2 * (K * K - V) * norm; + a2[band] = (V - sqrt(2 * V) * K + K * K) * norm; + b1[band] = 2 * (K * K - 1) * norm; + b2[band] = (1 - sqrt(2) * K + K * K) * norm; } else { // cut norm = 1 / (V + sqrt(2 * V) * K + K * K); - biquad_coefficients->a0 = (1 + sqrt(2) * K + K * K) * norm; - biquad_coefficients->a1 = 2 * (K * K - 1) * norm; - biquad_coefficients->a2 = (1 - sqrt(2) * K + K * K) * norm; - biquad_coefficients->b1 = 2 * (K * K - V) * norm; - biquad_coefficients->b2 = (V - sqrt(2 * V) * K + K * K) * norm; + a0[band] = (1 + sqrt(2) * K + K * K) * norm; + a1[band] = 2 * (K * K - 1) * norm; + a2[band] = (1 - sqrt(2) * K + K * K) * norm; + b1[band] = 2 * (K * K - V) * norm; + b2[band] = (V - sqrt(2 * V) * K + K * K) * norm; } break; } From f2fcd10b2b8512455fe83585c0db3c960734c0a9 Mon Sep 17 00:00:00 2001 From: Holger Wirtz Date: Sun, 15 Aug 2021 12:27:04 +0200 Subject: [PATCH 2/3] Using version 0.3.3 as third-party for TeensyTimerTool library. --- config.h | 4 - third-party/TeensyTimerTool-master.zip | Bin 112547 -> 0 bytes third-party/TeensyTimerTool/LICENSE | 21 + third-party/TeensyTimerTool/README.md | 7 + .../TeensyTimerTool/assets/FTM0_8CH.jpg | Bin 0 -> 98047 bytes .../01_Basic/ArrayOfTimers/ArrayOfTimers.ino | 31 ++ .../examples/01_Basic/ArrayOfTimers/README.md | 3 + .../01_Basic/HelloOneShot/HelloOneShot.ino | 26 ++ .../01_Basic/HelloPeriodic/HelloPeriodic.ino | 29 ++ .../01_Basic/MoreTimers/MoreTimers.ino | 52 +++ .../UsingChronoDurations1.ino | 35 ++ .../UsingChronoDurations2.ino | 33 ++ .../CallbackWithParams/CallbackWithParams.ino | 29 ++ .../02_Advanced/UsingLambdas/UsingLambdas.ino | 21 + .../examples/02_Advanced/UsingLambdas/pins.h | 253 +++++++++++ .../DoubleExposure/DoubleExposure.ino | 38 ++ .../DoubleExposure/LaserController.h | 32 ++ .../DoubleExposure/PulseGenerator.h | 61 +++ .../03_Applications/DoubleExposure/README.md | 1 + .../DoubleExposure/SystemController.h | 63 +++ .../99_Misc/PinInformation/GPIO_Info.h | 39 ++ .../99_Misc/PinInformation/PWM_TimerInfo.c | 213 +++++++++ .../99_Misc/PinInformation/PWM_TimerInfo.h | 62 +++ .../examples/99_Misc/PinInformation/PinInfo.h | 17 + .../99_Misc/PinInformation/PinInformation.ino | 53 +++ third-party/TeensyTimerTool/library.json | 21 + .../TeensyTimerTool/library.properties | 10 + .../src/ESP32-dummy/TCK/TCK.cpp | 17 + .../TeensyTimerTool/src/ESP32-dummy/TCK/TCK.h | 79 ++++ .../src/ESP32-dummy/TCK/TckChannel.h | 89 ++++ .../src/ErrorHandling/error_codes.h | 40 ++ .../src/ErrorHandling/error_handler.cpp | 79 ++++ .../src/ErrorHandling/error_handler.h | 17 + .../TeensyTimerTool/src/ITimerChannel.h | 48 +++ .../TeensyTimerTool/src/Teensy/FTM/FTM.h | 91 ++++ .../src/Teensy/FTM/FTM_Channel.h | 120 ++++++ .../src/Teensy/FTM/FTM_ChannelInfo.h | 17 + .../TeensyTimerTool/src/Teensy/FTM/FTM_Info.h | 110 +++++ .../TeensyTimerTool/src/Teensy/GPT/GPT.h | 77 ++++ .../src/Teensy/GPT/GPTChannel.h | 116 +++++ .../TeensyTimerTool/src/Teensy/GPT/GPTmap.h | 32 ++ .../TeensyTimerTool/src/Teensy/PIT4/PIT.cpp | 13 + .../TeensyTimerTool/src/Teensy/PIT4/PIT.h | 70 +++ .../src/Teensy/PIT4/PITChannel.h | 152 +++++++ .../TeensyTimerTool/src/Teensy/PIT4/PITMap.h | 32 ++ .../TeensyTimerTool/src/Teensy/TCK/TCK.cpp | 41 ++ .../TeensyTimerTool/src/Teensy/TCK/TCK.h | 82 ++++ .../src/Teensy/TCK/TckChannel.h | 336 +++++++++++++++ .../src/Teensy/TCK/TckChannelBase.h | 15 + .../TeensyTimerTool/src/Teensy/TMR/TMR.h | 100 +++++ .../src/Teensy/TMR/TMRChannel.h | 178 ++++++++ .../TeensyTimerTool/src/TeensyTimerTool.h | 9 + third-party/TeensyTimerTool/src/Timer.cpp | 10 + .../TeensyTimerTool/src/Uno-dummy/TCK/TCK.cpp | 14 + .../TeensyTimerTool/src/Uno-dummy/TCK/TCK.h | 79 ++++ .../src/Uno-dummy/TCK/TckChannel.h | 89 ++++ third-party/TeensyTimerTool/src/baseTimer.cpp | 22 + third-party/TeensyTimerTool/src/baseTimer.h | 116 +++++ third-party/TeensyTimerTool/src/boardDef.h | 66 +++ third-party/TeensyTimerTool/src/config.cpp | 88 ++++ third-party/TeensyTimerTool/src/config.h | 7 + .../TeensyTimerTool/src/defaultConfig.h | 91 ++++ third-party/TeensyTimerTool/src/frequency.h | 406 ++++++++++++++++++ .../TeensyTimerTool/src/oneShotTimer.h | 66 +++ .../TeensyTimerTool/src/periodicTimer.h | 22 + third-party/TeensyTimerTool/src/timer.h | 34 ++ third-party/TeensyTimerTool/src/types.h | 30 ++ 67 files changed, 4250 insertions(+), 4 deletions(-) delete mode 100644 third-party/TeensyTimerTool-master.zip create mode 100644 third-party/TeensyTimerTool/LICENSE create mode 100644 third-party/TeensyTimerTool/README.md create mode 100644 third-party/TeensyTimerTool/assets/FTM0_8CH.jpg create mode 100644 third-party/TeensyTimerTool/examples/01_Basic/ArrayOfTimers/ArrayOfTimers.ino create mode 100644 third-party/TeensyTimerTool/examples/01_Basic/ArrayOfTimers/README.md create mode 100644 third-party/TeensyTimerTool/examples/01_Basic/HelloOneShot/HelloOneShot.ino create mode 100644 third-party/TeensyTimerTool/examples/01_Basic/HelloPeriodic/HelloPeriodic.ino create mode 100644 third-party/TeensyTimerTool/examples/01_Basic/MoreTimers/MoreTimers.ino create mode 100644 third-party/TeensyTimerTool/examples/01_Basic/UsingChronoDurations1/UsingChronoDurations1.ino create mode 100644 third-party/TeensyTimerTool/examples/01_Basic/UsingChronoDurations2/UsingChronoDurations2.ino create mode 100644 third-party/TeensyTimerTool/examples/02_Advanced/CallbackWithParams/CallbackWithParams.ino create mode 100644 third-party/TeensyTimerTool/examples/02_Advanced/UsingLambdas/UsingLambdas.ino create mode 100644 third-party/TeensyTimerTool/examples/02_Advanced/UsingLambdas/pins.h create mode 100644 third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/DoubleExposure.ino create mode 100644 third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/LaserController.h create mode 100644 third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/PulseGenerator.h create mode 100644 third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/README.md create mode 100644 third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/SystemController.h create mode 100644 third-party/TeensyTimerTool/examples/99_Misc/PinInformation/GPIO_Info.h create mode 100644 third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PWM_TimerInfo.c create mode 100644 third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PWM_TimerInfo.h create mode 100644 third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PinInfo.h create mode 100644 third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PinInformation.ino create mode 100644 third-party/TeensyTimerTool/library.json create mode 100644 third-party/TeensyTimerTool/library.properties create mode 100644 third-party/TeensyTimerTool/src/ESP32-dummy/TCK/TCK.cpp create mode 100644 third-party/TeensyTimerTool/src/ESP32-dummy/TCK/TCK.h create mode 100644 third-party/TeensyTimerTool/src/ESP32-dummy/TCK/TckChannel.h create mode 100644 third-party/TeensyTimerTool/src/ErrorHandling/error_codes.h create mode 100644 third-party/TeensyTimerTool/src/ErrorHandling/error_handler.cpp create mode 100644 third-party/TeensyTimerTool/src/ErrorHandling/error_handler.h create mode 100644 third-party/TeensyTimerTool/src/ITimerChannel.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/FTM/FTM.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/FTM/FTM_Channel.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/FTM/FTM_ChannelInfo.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/FTM/FTM_Info.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/GPT/GPT.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/GPT/GPTChannel.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/GPT/GPTmap.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/PIT4/PIT.cpp create mode 100644 third-party/TeensyTimerTool/src/Teensy/PIT4/PIT.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/PIT4/PITChannel.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/PIT4/PITMap.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/TCK/TCK.cpp create mode 100644 third-party/TeensyTimerTool/src/Teensy/TCK/TCK.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/TCK/TckChannel.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/TCK/TckChannelBase.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/TMR/TMR.h create mode 100644 third-party/TeensyTimerTool/src/Teensy/TMR/TMRChannel.h create mode 100644 third-party/TeensyTimerTool/src/TeensyTimerTool.h create mode 100644 third-party/TeensyTimerTool/src/Timer.cpp create mode 100644 third-party/TeensyTimerTool/src/Uno-dummy/TCK/TCK.cpp create mode 100644 third-party/TeensyTimerTool/src/Uno-dummy/TCK/TCK.h create mode 100644 third-party/TeensyTimerTool/src/Uno-dummy/TCK/TckChannel.h create mode 100644 third-party/TeensyTimerTool/src/baseTimer.cpp create mode 100644 third-party/TeensyTimerTool/src/baseTimer.h create mode 100644 third-party/TeensyTimerTool/src/boardDef.h create mode 100644 third-party/TeensyTimerTool/src/config.cpp create mode 100644 third-party/TeensyTimerTool/src/config.h create mode 100644 third-party/TeensyTimerTool/src/defaultConfig.h create mode 100644 third-party/TeensyTimerTool/src/frequency.h create mode 100644 third-party/TeensyTimerTool/src/oneShotTimer.h create mode 100644 third-party/TeensyTimerTool/src/periodicTimer.h create mode 100644 third-party/TeensyTimerTool/src/timer.h create mode 100644 third-party/TeensyTimerTool/src/types.h diff --git a/config.h b/config.h index 24601f4..2c92167 100644 --- a/config.h +++ b/config.h @@ -357,10 +357,6 @@ #define USBCON 1 #endif -// Audio -#ifdef TGA_AUDIO_BOARD -#endif - // Some optimizations #define USE_TEENSY_DSP 1 diff --git a/third-party/TeensyTimerTool-master.zip b/third-party/TeensyTimerTool-master.zip deleted file mode 100644 index 531b1824121cff523211c0ce3d1a8f4388c88da6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112547 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%0D2|KitNj1J6nI75g=AbiXAHU3Otf#C2|~D8Xe% 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 L4ViTf3>g0h4@~>Q diff --git a/third-party/TeensyTimerTool/LICENSE b/third-party/TeensyTimerTool/LICENSE new file mode 100644 index 0000000..00b7427 --- /dev/null +++ b/third-party/TeensyTimerTool/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Lutz Niggl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third-party/TeensyTimerTool/README.md b/third-party/TeensyTimerTool/README.md new file mode 100644 index 0000000..0202484 --- /dev/null +++ b/third-party/TeensyTimerTool/README.md @@ -0,0 +1,7 @@ +# TeensyTimerTool + +The TeensyTimerTool is a library that provides a generic, easy to use interface to the hardware timers of the PJRC Teensy boards. In addition, it provides up to 20 highly efficient software timers that use the same interface. All timers can be used in periodic and one-shot mode. Currently the library supports the ARM T3.X and T4.0 boards. + +- This way to the corresponding PJRC **[forum post](https://forum.pjrc.com/threads/59112-TeensyTimerTool)** +- This way to the documentation in the **[TeensyTimerTool WIKI](https://github.com/luni64/TeensyTimerTool/wiki)** + diff --git a/third-party/TeensyTimerTool/assets/FTM0_8CH.jpg b/third-party/TeensyTimerTool/assets/FTM0_8CH.jpg new file mode 100644 index 0000000000000000000000000000000000000000..077c65018bd8c18fcf0ef8f7cbb291a5c17b89e9 GIT binary patch literal 98047 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 LED(OUTPUT); +Timer timer; + +void setup() +{ + timer.beginOneShot([] { LED = LOW; }); +} + +void loop() +{ + LED = HIGH; + timer.trigger(25'000); + delay(1'000); +} diff --git a/third-party/TeensyTimerTool/examples/02_Advanced/UsingLambdas/pins.h b/third-party/TeensyTimerTool/examples/02_Advanced/UsingLambdas/pins.h new file mode 100644 index 0000000..99dcad7 --- /dev/null +++ b/third-party/TeensyTimerTool/examples/02_Advanced/UsingLambdas/pins.h @@ -0,0 +1,253 @@ +#pragma once + +#include +#include +#include + +namespace pins +{ + namespace // private + { + enum boards { + notDefined = -1, + T_LC, + T3_0_1_2, + T3_5_6, + }; + +#if defined(__MKL26Z64__) + constexpr boards board = boards::T_LC; +#elif defined(__MK20DX128__) || defined(__MK20DX256__) + constexpr boards board = boards::T3_0_1_2; +#elif defined(__MK64FX512__) || defined(__MK66FX1M0__) + constexpr boards board = boards::T3_5_6; +#else + constexpr boards board = boards::notDefined; +#endif + static_assert(board != boards::notDefined, "Error in Pin.h"); + + // Indices into GPIOx and PORT arrays below + enum portList { + na = -1, Port_A, Port_B, Port_C, Port_D, Port_E + }; + + //Base adresses of the GPIOx register blocks + constexpr uintptr_t GPIOx_PDOR[] = + { + (uintptr_t)&(GPIOA_PDOR), + (uintptr_t)&(GPIOB_PDOR), + (uintptr_t)&(GPIOC_PDOR), + (uintptr_t)&(GPIOD_PDOR), + (uintptr_t)&(GPIOE_PDOR), + }; + + // Base adresses of the Pin Control Registers + constexpr uintptr_t PORTx_PCR0[] = + { + (uintptr_t)&(PORTA_PCR0), + (uintptr_t)&(PORTB_PCR0), + (uintptr_t)&(PORTC_PCR0), + (uintptr_t)&(PORTD_PCR0), + (uintptr_t)&(PORTE_PCR0), + }; + + //---------------------------------------------------------------- + // Translate Teensy pin number to port and bitnr + // The map will be used by the complier only, no code will be generated + + struct pinInfo { + const portList port; + const int pin; + }; + + constexpr pinInfo pinMap[][3] = + { // T-LC T3.0/1/2 T3.5/6 + /* Pin 0 */{ { Port_B, 16 },{ Port_B, 16 },{ Port_B, 16 } }, + /* Pin 1 */{ { Port_B, 17 },{ Port_B, 17 },{ Port_B, 17 } }, + /* Pin 2 */{ { Port_D, 0 },{ Port_D, 0 },{ Port_D, 0 } }, + /* Pin 3 */{ { Port_A, 1 },{ Port_A, 12 },{ Port_A, 12 } }, + /* Pin 4 */{ { Port_A, 2 },{ Port_A, 13 },{ Port_A, 13 } }, + /* Pin 5 */{ { Port_D, 7 },{ Port_D, 7 },{ Port_D, 7 } }, + /* Pin 6 */{ { Port_D, 4 },{ Port_D, 4 },{ Port_D, 4 } }, + /* Pin 7 */{ { Port_D, 2 },{ Port_D, 2 },{ Port_D, 2 } }, + /* Pin 8 */{ { Port_D, 3 },{ Port_D, 3 },{ Port_D, 3 } }, + /* Pin 9 */{ { Port_C, 3 },{ Port_C, 3 },{ Port_C, 3 } }, + /* Pin 10*/{ { Port_C, 4 },{ Port_C, 4 },{ Port_C, 4 } }, + /* Pin 11*/{ { Port_C, 6 },{ Port_C, 6 },{ Port_C, 6 } }, + /* Pin 12*/{ { Port_C, 7 },{ Port_C, 7 },{ Port_C, 7 } }, + /* Pin 13*/{ { Port_C, 5 },{ Port_C, 5 },{ Port_C, 5 } }, + /* Pin 14*/{ { Port_D, 1 },{ Port_D, 1 },{ Port_D, 1 } }, + /* Pin 15*/{ { Port_C, 0 },{ Port_C, 0 },{ Port_C, 0 } }, + /* Pin 16*/{ { Port_B, 0 },{ Port_B, 0 },{ Port_B, 0 } }, + /* Pin 17*/{ { Port_B, 1 },{ Port_B, 1 },{ Port_B, 1 } }, + /* Pin 18*/{ { Port_B, 3 },{ Port_B, 3 },{ Port_B, 3 } }, + /* Pin 19*/{ { Port_B, 2 },{ Port_B, 2 },{ Port_B, 2 } }, + /* Pin 20*/{ { Port_D, 5 },{ Port_D, 5 },{ Port_D, 5 } }, + /* Pin 21*/{ { Port_D, 6 },{ Port_D, 6 },{ Port_D, 6 } }, + /* Pin 22*/{ { Port_C, 1 },{ Port_C, 1 },{ Port_C, 1 } }, + /* Pin 23*/{ { Port_C, 2 },{ Port_C, 2 },{ Port_C, 2 } }, + /* Pin 24*/{ { Port_E, 20 },{ Port_A, 5 },{ Port_E, 26 } }, + /* Pin 25*/{ { Port_E, 21 },{ Port_B, 19 },{ Port_A, 5 } }, + /* Pin 26*/{ { Port_E, 30 },{ Port_E, 1 },{ Port_A, 14 } }, + /* Pin 27*/{ { na, -1 },{ Port_C, 9 },{ Port_A, 15 } }, + /* Pin 28*/{ { na, -1 },{ Port_C, 8 },{ Port_A, 16 } }, + /* Pin 29*/{ { na, -1 },{ Port_C, 10 },{ Port_B, 18 } }, + /* Pin 30*/{ { na, -1 },{ Port_C, 5 },{ Port_B, 19 } }, + /* Pin 31*/{ { na, -1 },{ Port_E, 6 },{ Port_B, 10 } }, + /* Pin 32*/{ { na, -1 },{ Port_B, 1 },{ Port_B, 11 } }, + /* Pin 33*/{ { na, -1 },{ Port_A, 2 },{ Port_E, 24 } }, + /* Pin 34*/{ { na, -1 },{ na, -1 },{ Port_E, 25 } }, + /* Pin 35*/{ { na, -1 },{ na, -1 },{ Port_C, 8 } }, + /* Pin 36*/{ { na, -1 },{ na, -1 },{ Port_C, 9 } }, + /* Pin 37*/{ { na, -1 },{ na, -1 },{ Port_C, 10 } }, + /* Pin 38*/{ { na, -1 },{ na, -1 },{ Port_C, 11 } }, + /* Pin 39*/{ { na, -1 },{ na, -1 },{ Port_A, 17 } }, + /* Pin 40*/{ { na, -1 },{ na, -1 },{ Port_A, 28 } }, + /* Pin 41*/{ { na, -1 },{ na, -1 },{ Port_A, 29 } }, + /* Pin 42*/{ { na, -1 },{ na, -1 },{ Port_A, 26 } }, + /* Pin 43*/{ { na, -1 },{ na, -1 },{ Port_B, 20 } }, + /* Pin 44*/{ { na, -1 },{ na, -1 },{ Port_B, 22 } }, + /* Pin 45*/{ { na, -1 },{ na, -1 },{ Port_B, 23 } }, + /* Pin 46*/{ { na, -1 },{ na, -1 },{ Port_B, 21 } }, + /* Pin 47*/{ { na, -1 },{ na, -1 },{ Port_D, 8 } }, + /* Pin 48*/{ { na, -1 },{ na, -1 },{ Port_D, 9 } }, + /* Pin 49*/{ { na, -1 },{ na, -1 },{ Port_B, 4 } }, + /* Pin 50*/{ { na, -1 },{ na, -1 },{ Port_B, 5 } }, + /* Pin 51*/{ { na, -1 },{ na, -1 },{ Port_D, 14 } }, + /* Pin 52*/{ { na, -1 },{ na, -1 },{ Port_D, 13 } }, + /* Pin 53*/{ { na, -1 },{ na, -1 },{ Port_D, 12 } }, + /* Pin 54*/{ { na, -1 },{ na, -1 },{ Port_D, 15 } }, + /* Pin 55*/{ { na, -1 },{ na, -1 },{ Port_D, 11 } }, + /* Pin 56*/{ { na, -1 },{ na, -1 },{ Port_E, 10 } }, + /* Pin 57*/{ { na, -1 },{ na, -1 },{ Port_E, 11 } }, + /* Pin 58*/{ { na, -1 },{ na, -1 },{ Port_E, 0 } }, + /* Pin 59*/{ { na, -1 },{ na, -1 },{ Port_E, 1 } }, + /* Pin 60*/{ { na, -1 },{ na, -1 },{ Port_E, 2 } }, + /* Pin 61*/{ { na, -1 },{ na, -1 },{ Port_E, 3 } }, + /* Pin 62*/{ { na, -1 },{ na, -1 },{ Port_E, 4 } }, + /* Pin 63*/{ { na, -1 },{ na, -1 },{ Port_E, 5 } }, + }; + } + + // Pin Mux constants + enum pinMux + { + ALT0 = 0, + ALT1, ALT2, ALT3, ALT4, ALT5, ALT6, ALT7 + }; + + //----------------------------------------------------------------------------------------------------- + // Pin class + // all methods static, no instances will be generated in code + + template + class pin + { + private: + // Calculate the adress of the pin control register + static constexpr uintptr_t get_PORTx_PCRn_addr() + { + static_assert(pinMap[pinNr][board].port != na && pinMap[pinNr][board].pin != -1, "Pin not supported by board"); + return PORTx_PCR0[(pinMap[pinNr][board]).port] + 4 * pinMap[pinNr][board].pin; + } + + // Calculate the adress of the bit banded GPIO registers of the pin + static constexpr uintptr_t getPDORAdr() + { + return 0x42000000 + 32 * (GPIOx_PDOR[pinMap[pinNr][board].port] - 0x40000000) + 4 * pinMap[pinNr][board].pin; + } + + static constexpr uintptr_t pcr = get_PORTx_PCRn_addr(); // Pin Control Register + static constexpr uintptr_t pdorBB = getPDORAdr(); // Bitband adress of GPIOx_PDOR + static constexpr uintptr_t psorBB = pdorBB + 4 * 32; // GPIOx_PSOR = GPIOx_PDOR + 4 + static constexpr uintptr_t pcorBB = psorBB + 4 * 32; // GPIOx_PCOR = GPIOx_PDOR + 8 + static constexpr uintptr_t ptorBB = pcorBB + 4 * 32; // GPIOx_PTOR = GPIOx_PDOR + 12 + static constexpr uintptr_t pdirBB = ptorBB + 4 * 32; // GPIOx_PDIR = GPIOx_PDOR + 16 + static constexpr uintptr_t pddrBB = pdirBB + 4 * 32; // GPIOx_PDDR = GPIOx_PDOR + 20 + + public: + // Construction (class contains only static members) + pin() {} + pin(int i) { pinMode(i); } + + pin(const pin&) = delete; // disable copy constructor + + // Setting and getting the pin value + template::value>::type> + operator T() const { return *reinterpret_cast(pdirBB); } + operator bool() const { return *reinterpret_cast(pdirBB) & 1; } + inline void operator = (const bool v) const { *reinterpret_cast(pdorBB) = v; } // assignment --> somePin = HIGH + inline void operator = (const pin& v) const = delete; // disable copying + + // Pin operations + static inline void toggle() { *reinterpret_cast(ptorBB) = 1; } // toggles pin + //static inline int getValue() { return *reinterpret_cast(pdirBB); } // returns pin value (usefull for static calls) + //static inline void setValue(const int v) { *reinterpret_cast(pdorBB) = v; } // sets value (usefull for static calls) + //static inline void setHIGH() { *reinterpret_cast(psorBB) = 1; } // sets pin to HIGH (usefull for static calls) + //static inline void setLOW() { *reinterpret_cast(pcorBB) = 1; } // sets pin to LOW (usefull for static calls) + + // Pin configuration + static inline void pinMode(int mode) + { + // replace by a call to pinMode(pinNr,mode) ??? TBD + switch (mode) + { + case OUTPUT: + *reinterpret_cast(pddrBB) = 1; //Pin direction register + *reinterpret_cast(pcr) = PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(1); //Pin control register + break; + case OUTPUT_OPENDRAIN: + *reinterpret_cast(pddrBB) = 1; + *reinterpret_cast(pcr) = PORT_PCR_SRE | PORT_PCR_DSE | PORT_PCR_MUX(1) | PORT_PCR_ODE; + break; + case INPUT: + *reinterpret_cast(pddrBB) = 0; + *reinterpret_cast(pcr) = PORT_PCR_MUX(1); + break; + case INPUT_PULLUP: + *reinterpret_cast(pddrBB) = 0; + *reinterpret_cast(pcr) = PORT_PCR_MUX(1) | PORT_PCR_PE | PORT_PCR_PS; + break; + case INPUT_PULLDOWN: + *reinterpret_cast(pddrBB) = 0; + *reinterpret_cast(pcr) = PORT_PCR_MUX(1) | PORT_PCR_PE; + break; + case INPUT_DISABLE: + *reinterpret_cast(pcr) = 0; + break; + } + } + + static inline void driveStrengthEnable(bool enable) + { + //TBD static_assert: pin has DSE capability + if (enable) { + *reinterpret_cast(pcr) |= PORT_PCR_DSE; + } + else { + *reinterpret_cast(pcr) &= ~PORT_PCR_DSE; + } + } + + static inline void slowSlewRateEnable(bool enable) + { + //TBD static_assert: pin has SRE capability + if (enable) { + *reinterpret_cast(pcr) |= PORT_PCR_SRE; + } + else { + *reinterpret_cast(pcr) &= ~PORT_PCR_SRE; + } + } + + static inline void setMUX(pinMux altFunc) + { + *reinterpret_cast(pcr) = (*reinterpret_cast(pcr) & ~PORT_PCR_MUX_MASK) | PORT_PCR_MUX(altFunc); + } + + static inline void attachInterrupt(void(*function)(void), int mode) + { + ::attachInterrupt(pinNr, function, mode); + } + }; +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/DoubleExposure.ino b/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/DoubleExposure.ino new file mode 100644 index 0000000..f50866b --- /dev/null +++ b/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/DoubleExposure.ino @@ -0,0 +1,38 @@ +#include "ResponsiveAnalogRead.h" +#include "SystemController.h" + +constexpr unsigned potPin = A0; +ResponsiveAnalogRead pot(potPin, false); + +SystemController sysControl; + + +void setup() +{ + sysControl.begin(); + + sysControl.setExposureDelay(250); // set exposure delay (time between two exposures) to 250 µs + sysControl.shoot(); // do a manual exposure + delay(10); + sysControl.setExposureDelay(500); // same with 500µs delay between exposures + sysControl.shoot(); + + sysControl.continousMode(true); // start continously shooting + delay(1000); + sysControl.continousMode(false); // stop after one second + + delay(500); + sysControl.continousMode(true); // start again +} + +void loop() +{ + // --> Uncomment if you have a control voltage on the pot pin <-- + // pot.update(); + // if (pot.hasChanged()) + // { + // unsigned expDelay = map(pot.getValue(), 0, 1023, 100, 500); // 0-3.3V analog value, maps to 100-500 + // controller.setExposureDelay(expDelay); + // Serial.printf("Exposure Delay: %u µs\n", expDelay); + // } +} diff --git a/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/LaserController.h b/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/LaserController.h new file mode 100644 index 0000000..fcdb72a --- /dev/null +++ b/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/LaserController.h @@ -0,0 +1,32 @@ +#pragma once +#include "PulseGenerator.h" +#include "TeensyTimerTool.h" + +class LaserController +{ + public: + void begin(unsigned preTriggerPin, unsigned triggerPin, unsigned camPin); + void shoot(); + + protected: + PulseGenerator preTrigger, trigger, camera; +}; + +void LaserController::begin(unsigned preTriggerPin, unsigned triggerPin, unsigned camPin) +{ + preTrigger.begin(preTriggerPin); + trigger.begin(triggerPin); + camera.begin(camPin); +} + +void LaserController::shoot() +{ + constexpr float t_warmup = 140 - 5.5; + constexpr float t_p = 10 - 3; + constexpr float t_camDelay = 130 - 7.5; + constexpr float t_int = 30 - 3; + + preTrigger.schedulePulse(0, t_p); // immediately generate the pretrigger pulse + trigger.schedulePulse(t_warmup, t_p); // schedule the trigger pulse to start after the warmup time + camera.schedulePulse(t_camDelay, t_int); // schedule the cam pulse after the camDelay time +} diff --git a/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/PulseGenerator.h b/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/PulseGenerator.h new file mode 100644 index 0000000..db1f5ab --- /dev/null +++ b/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/PulseGenerator.h @@ -0,0 +1,61 @@ +#pragma once + +#include "Arduino.h" +#include "TeensyTimerTool.h" + +class PulseGenerator +{ + public: + PulseGenerator(); // constructor, initializes non hardware related stuff + void begin(unsigned pin); // intializes hardware related stuff + void schedulePulse(float delay, float width); // schedules a 'width µs' wide pulse after a waiting time of 'delay µs'. Non blocking. + + protected: + TeensyTimerTool::OneShotTimer pulseTimer; // used for generating the pulses + void callback(); + + uint32_t pin; + float width; +}; + + + +void PulseGenerator::begin(unsigned pin) +{ + this->pin = pin; + pinMode(pin, OUTPUT); + digitalWriteFast(pin, LOW); + + pulseTimer.begin([this] { this->callback(); }); +} + +void PulseGenerator::schedulePulse(float delay, float _width) +{ + width = _width; // the timer callback needs to know the pulse width to generate the pulse + + if (delay != 0) + { + pulseTimer.trigger(delay); // this triggers the oneShot timer to fire after 'delay µs' + } + else //delay == 0, directly generate the pulse + { + digitalWriteFast(pin, HIGH); + pulseTimer.trigger(width); // this triggers the oneShotTimer to fire after 'width µs' + } +} + +void PulseGenerator::callback() +{ + if (digitalReadFast(pin) == LOW) + { + digitalWriteFast(pin, HIGH); + pulseTimer.trigger(width); // retrigger + } else + { + digitalWriteFast(pin, LOW); + } +} + +PulseGenerator::PulseGenerator() + : pulseTimer(TeensyTimerTool::FTM0) // use one of the 8 FTM0 channels +{} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/README.md b/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/README.md new file mode 100644 index 0000000..bec8308 --- /dev/null +++ b/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/README.md @@ -0,0 +1 @@ +### See https://github.com/luni64/TeensyTimerTool/wiki/Double-Exposure-Laser-Illuminator for a description of this example. diff --git a/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/SystemController.h b/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/SystemController.h new file mode 100644 index 0000000..efa2c19 --- /dev/null +++ b/third-party/TeensyTimerTool/examples/03_Applications/DoubleExposure/SystemController.h @@ -0,0 +1,63 @@ +#pragma once +#include "LaserController.h" +#include "TeensyTimerTool.h" + +class SystemController +{ + public: + SystemController(); + inline void begin(); + inline void shoot(); + + inline void continousMode(bool on); + inline void setExposureDelay(unsigned delay); + + protected: + TeensyTimerTool::PeriodicTimer mainTimer; + LaserController lCtrl1, lCtrl2; + unsigned exposureDelay = 300; +}; + +SystemController::SystemController() + : mainTimer(TeensyTimerTool::TCK) {} // construct the main 15Hz timer, we use a TCK here + +void SystemController::begin() +{ + constexpr unsigned repetitionRate = 15; // Hz + + constexpr unsigned preTrig1_pin = 1; + constexpr unsigned trig1_pin = 2; + constexpr unsigned preTrig2_pin = 3; + constexpr unsigned trig2_pin = 4; + constexpr unsigned cam_pin = 5; + + lCtrl1.begin(preTrig1_pin, trig1_pin, cam_pin); + lCtrl2.begin(preTrig2_pin, trig2_pin, cam_pin); + + mainTimer.begin([this] { this->shoot(); }, 1E6 / repetitionRate, false); // attach callback but don't start yet +} + +void SystemController::shoot() +{ + elapsedMicros stopwatch = 0; + lCtrl1.shoot(); + while (stopwatch < exposureDelay) { yield(); } + lCtrl2.shoot(); +} + +void SystemController::continousMode(bool on) +{ + if (on) + { + mainTimer.start(); + } + else + { + mainTimer.stop(); + } +} + +void SystemController::setExposureDelay(unsigned delay) +{ + exposureDelay = max(100u, min(500u, delay)); // limit to 100-500 µs +} diff --git a/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/GPIO_Info.h b/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/GPIO_Info.h new file mode 100644 index 0000000..093ab32 --- /dev/null +++ b/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/GPIO_Info.h @@ -0,0 +1,39 @@ +#pragma once + +#include "core_pins.h" +#include + +struct GPIO_Info_t +{ + inline GPIO_Info_t(unsigned pin); + const uintptr_t pinCfg; + const unsigned gpioPortNr; + const unsigned gpioBitNr; + + char name[20]; +}; + +//====================================================================================== +#if defined(KINETISK) || defined(KINETISL) + +GPIO_Info_t::GPIO_Info_t(unsigned pin) + : pinCfg((uintptr_t)digital_pin_to_info_PGM[pin].config), // address of pin config register + gpioPortNr((pinCfg - (uintptr_t)&PORTA_PCR0) / 0x1000), // cfg base addresses are 4kB aligned staring with PORTA_PCR0 + gpioBitNr((pinCfg & 0xFFF) / 4) // each bit has to 4 consecutive 32bit cfg registers +{ + snprintf(name, 20, "GPIO%d_%02d", gpioPortNr, gpioBitNr); +} + +//====================================================================================== +#elif defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) + + +GPIO_Info_t::GPIO_Info_t(unsigned pin) + : pinCfg((uintptr_t)digital_pin_to_info_PGM[pin].reg), // address of pin config register + gpioPortNr((pinCfg - (uintptr_t)&IMXRT_GPIO6) / 0x4000 + 6), // cfg base addresses are 4kB aligned staring with PORTA_PCR0 + gpioBitNr(__builtin_ffs(digital_pin_to_info_PGM[pin].mask) - 1) // each bit has to 4 consecutive 32bit cfg registers +{ + snprintf(name, 20, "GPIO%d_%02d", gpioPortNr, gpioBitNr); +} + +#endif \ No newline at end of file diff --git a/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PWM_TimerInfo.c b/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PWM_TimerInfo.c new file mode 100644 index 0000000..9a01feb --- /dev/null +++ b/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PWM_TimerInfo.c @@ -0,0 +1,213 @@ +#include "core_pins.h" + +/***** + * Unlike Teensy 4.x there is no information array in the core for the T3.x series + * We thus build the corresponding array from copies of the pin definitions in core_pins.h + *****/ + + +#if defined(ARDUINO_TEENSYLC) + +const int PwmTimerModule[CORE_NUM_DIGITAL] = { + [0 ... CORE_NUM_DIGITAL - 1] = -1, + [CORE_TPM0_CH0_PIN] = 0, + [CORE_TPM0_CH1_PIN] = 0, + [CORE_TPM0_CH2_PIN] = 0, + [CORE_TPM0_CH3_PIN] = 0, + [CORE_TPM0_CH4_PIN] = 0, + [CORE_TPM0_CH5_PIN] = 0, + [CORE_TPM1_CH0_PIN] = 1, + [CORE_TPM1_CH1_PIN] = 1, + [CORE_TPM2_CH0_PIN] = 2, + [CORE_TPM2_CH1_PIN] = 2, +}; + +const int PwmTimerType[CORE_NUM_DIGITAL] = { + [0 ... CORE_NUM_DIGITAL - 1] = 0, + [CORE_TPM0_CH0_PIN] = 2, + [CORE_TPM0_CH1_PIN] = 2, + [CORE_TPM0_CH2_PIN] = 2, + [CORE_TPM0_CH3_PIN] = 2, + [CORE_TPM0_CH4_PIN] = 2, + [CORE_TPM0_CH5_PIN] = 2, + [CORE_TPM1_CH0_PIN] = 2, + [CORE_TPM1_CH1_PIN] = 2, + [CORE_TPM2_CH0_PIN] = 2, + [CORE_TPM2_CH1_PIN] = 2, +}; + +//=========================================================================================== +#elif defined(ARDUINO_TEENSY30) + +const int PwmTimerModule[CORE_NUM_DIGITAL] = { + [0 ... CORE_NUM_DIGITAL - 1] = -1, + [CORE_FTM0_CH0_PIN] = 0, + [CORE_FTM0_CH1_PIN] = 0, + [CORE_FTM0_CH2_PIN] = 0, + [CORE_FTM0_CH3_PIN] = 0, + [CORE_FTM0_CH4_PIN] = 0, + [CORE_FTM0_CH5_PIN] = 0, + [CORE_FTM0_CH6_PIN] = 0, + [CORE_FTM0_CH7_PIN] = 0, + [CORE_FTM1_CH0_PIN] = 1, + [CORE_FTM1_CH1_PIN] = 1, +}; + +const int PwmTimerType[CORE_NUM_DIGITAL] = { + [0 ... CORE_NUM_DIGITAL - 1] = 0, + [CORE_FTM0_CH0_PIN] = 1, + [CORE_FTM0_CH1_PIN] = 1, + [CORE_FTM0_CH2_PIN] = 1, + [CORE_FTM0_CH3_PIN] = 1, + [CORE_FTM0_CH4_PIN] = 1, + [CORE_FTM0_CH5_PIN] = 1, + [CORE_FTM0_CH6_PIN] = 1, + [CORE_FTM0_CH7_PIN] = 1, + [CORE_FTM1_CH0_PIN] = 1, + [CORE_FTM1_CH1_PIN] = 1, +}; + +//=========================================================================================== +#elif defined(__MK20DX256__) +const int PwmTimerModule[CORE_NUM_DIGITAL] = { + [0 ... CORE_NUM_DIGITAL - 1] = -1, + [CORE_FTM0_CH0_PIN] = 0, + [CORE_FTM0_CH1_PIN] = 0, + [CORE_FTM0_CH0_PIN] = 0, + [CORE_FTM0_CH1_PIN] = 0, + [CORE_FTM0_CH2_PIN] = 0, + [CORE_FTM0_CH3_PIN] = 0, + [CORE_FTM0_CH4_PIN] = 0, + [CORE_FTM0_CH5_PIN] = 0, + [CORE_FTM0_CH6_PIN] = 0, + [CORE_FTM0_CH7_PIN] = 0, + [CORE_FTM1_CH0_PIN] = 1, + [CORE_FTM1_CH1_PIN] = 1, + [CORE_FTM2_CH0_PIN] = 2, + [CORE_FTM2_CH1_PIN] = 2, +}; + +//=========================================================================================== +const int PwmTimerType[CORE_NUM_DIGITAL] = { + [0 ... CORE_NUM_DIGITAL - 1] = 0, + [CORE_FTM0_CH0_PIN] = 1, + [CORE_FTM0_CH1_PIN] = 1, + [CORE_FTM0_CH0_PIN] = 1, + [CORE_FTM0_CH1_PIN] = 1, + [CORE_FTM0_CH2_PIN] = 1, + [CORE_FTM0_CH3_PIN] = 1, + [CORE_FTM0_CH4_PIN] = 1, + [CORE_FTM0_CH5_PIN] = 1, + [CORE_FTM0_CH6_PIN] = 1, + [CORE_FTM0_CH7_PIN] = 1, + [CORE_FTM1_CH0_PIN] = 1, + [CORE_FTM1_CH1_PIN] = 1, + [CORE_FTM2_CH0_PIN] = 1, + [CORE_FTM2_CH1_PIN] = 1, +}; + +//=========================================================================================== +#elif defined(ARDUINO_TEENSY35) +const int PwmTimerModule[CORE_NUM_DIGITAL] = { + [0 ... CORE_NUM_DIGITAL - 1] = -1, + [CORE_FTM0_CH0_PIN] = 0, + [CORE_FTM0_CH1_PIN] = 0, + [CORE_FTM0_CH2_PIN] = 0, + [CORE_FTM0_CH3_PIN] = 0, + [CORE_FTM0_CH4_PIN] = 0, + [CORE_FTM0_CH5_PIN] = 0, + [CORE_FTM0_CH6_PIN] = 0, + [CORE_FTM0_CH7_PIN] = 0, + [CORE_FTM1_CH0_PIN] = 1, + [CORE_FTM1_CH1_PIN] = 1, + [CORE_FTM2_CH0_PIN] = 2, + [CORE_FTM2_CH1_PIN] = 2, + [CORE_FTM3_CH0_PIN] = 3, + [CORE_FTM3_CH1_PIN] = 3, + [CORE_FTM3_CH2_PIN] = 3, + [CORE_FTM3_CH3_PIN] = 3, + [CORE_FTM3_CH4_PIN] = 3, + [CORE_FTM3_CH5_PIN] = 3, + [CORE_FTM3_CH6_PIN] = 3, + [CORE_FTM3_CH7_PIN] = 3, +}; + +const int PwmTimerType[CORE_NUM_DIGITAL] = { + [0 ... CORE_NUM_DIGITAL - 1] = 0, + [CORE_FTM0_CH0_PIN] = 1, + [CORE_FTM0_CH1_PIN] = 1, + [CORE_FTM0_CH2_PIN] = 1, + [CORE_FTM0_CH3_PIN] = 1, + [CORE_FTM0_CH4_PIN] = 1, + [CORE_FTM0_CH5_PIN] = 1, + [CORE_FTM0_CH6_PIN] = 1, + [CORE_FTM0_CH7_PIN] = 1, + [CORE_FTM1_CH0_PIN] = 1, + [CORE_FTM1_CH1_PIN] = 1, + [CORE_FTM2_CH0_PIN] = 1, + [CORE_FTM2_CH1_PIN] = 1, + [CORE_FTM3_CH0_PIN] = 1, + [CORE_FTM3_CH1_PIN] = 1, + [CORE_FTM3_CH2_PIN] = 1, + [CORE_FTM3_CH3_PIN] = 1, + [CORE_FTM3_CH4_PIN] = 1, + [CORE_FTM3_CH5_PIN] = 1, + [CORE_FTM3_CH6_PIN] = 1, + [CORE_FTM3_CH7_PIN] = 1, +}; + +//=========================================================================================== +#elif defined(ARDUINO_TEENSY36) +const int PwmTimerModule[CORE_NUM_DIGITAL] = { + [0 ... CORE_NUM_DIGITAL - 1] = -1, + [CORE_FTM0_CH0_PIN] = 0, + [CORE_FTM0_CH1_PIN] = 0, + [CORE_FTM0_CH2_PIN] = 0, + [CORE_FTM0_CH3_PIN] = 0, + [CORE_FTM0_CH4_PIN] = 0, + [CORE_FTM0_CH5_PIN] = 0, + [CORE_FTM0_CH6_PIN] = 0, + [CORE_FTM0_CH7_PIN] = 0, + [CORE_FTM1_CH0_PIN] = 1, + [CORE_FTM1_CH1_PIN] = 1, + [CORE_FTM2_CH0_PIN] = 2, + [CORE_FTM2_CH1_PIN] = 2, + [CORE_FTM3_CH0_PIN] = 3, + [CORE_FTM3_CH1_PIN] = 3, + [CORE_FTM3_CH2_PIN] = 3, + [CORE_FTM3_CH3_PIN] = 3, + [CORE_FTM3_CH4_PIN] = 3, + [CORE_FTM3_CH5_PIN] = 3, + [CORE_FTM3_CH6_PIN] = 3, + [CORE_FTM3_CH7_PIN] = 3, + [CORE_TPM1_CH0_PIN] = 1, + [CORE_TPM1_CH1_PIN] = 1, +}; + +const int PwmTimerType[CORE_NUM_DIGITAL] = { + [0 ... CORE_NUM_DIGITAL - 1] = 0, + [CORE_FTM0_CH0_PIN] = 1, + [CORE_FTM0_CH1_PIN] = 1, + [CORE_FTM0_CH2_PIN] = 1, + [CORE_FTM0_CH3_PIN] = 1, + [CORE_FTM0_CH4_PIN] = 1, + [CORE_FTM0_CH5_PIN] = 1, + [CORE_FTM0_CH6_PIN] = 1, + [CORE_FTM0_CH7_PIN] = 1, + [CORE_FTM1_CH0_PIN] = 1, + [CORE_FTM1_CH1_PIN] = 1, + [CORE_FTM2_CH0_PIN] = 1, + [CORE_FTM2_CH1_PIN] = 1, + [CORE_FTM3_CH0_PIN] = 1, + [CORE_FTM3_CH1_PIN] = 1, + [CORE_FTM3_CH2_PIN] = 1, + [CORE_FTM3_CH3_PIN] = 1, + [CORE_FTM3_CH4_PIN] = 1, + [CORE_FTM3_CH5_PIN] = 1, + [CORE_FTM3_CH6_PIN] = 1, + [CORE_FTM3_CH7_PIN] = 1, + [CORE_TPM1_CH0_PIN] = 2, + [CORE_TPM1_CH1_PIN] = 2, +}; + +#endif diff --git a/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PWM_TimerInfo.h b/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PWM_TimerInfo.h new file mode 100644 index 0000000..910ef9e --- /dev/null +++ b/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PWM_TimerInfo.h @@ -0,0 +1,62 @@ +#pragma once + +#include "core_pins.h" +#include + +struct PWM_TimerInfo_t +{ + inline PWM_TimerInfo_t(unsigned _pin = 0); + unsigned type; + unsigned module; + char name[20]; +}; + +//=========================================================================================================== +#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) + +// holds core info about used pwm timers. Struct is defined in pwm.c. +// There is no header declaring it. So, we need to do this here: +struct pwm_pin_info_struct +{ + uint8_t type; // 0=no pwm, 1=flexpwm, 2=quad + uint8_t module; // 0-3, 0-3 + uint8_t channel; // 0=X, 1=A, 2=B + uint8_t muxval; // +}; +extern pwm_pin_info_struct pwm_pin_info[]; // This array holds the pwm timer info + +PWM_TimerInfo_t::PWM_TimerInfo_t(unsigned pin) +{ + constexpr char timerNames[][9] = {"FLEX_PWM", "QUAD"}; + + type = pwm_pin_info[pin].type; + module = (pwm_pin_info[pin].module >> 4) + 1; + + if (type != 0) + snprintf(name, 20, "%s%d", timerNames[type - 1], module); + else + snprintf(name, 20, "no pwm"); +} + +//=========================================================================================================== +#elif defined(ARDUINO_TEENSYLC) || \ + defined(ARDUINO_TEENSY30) || defined(ARDUINO_TEENSY32) || \ + defined(ARDUINO_TEENSY35) || defined(ARDUINO_TEENSY36) + +extern "C" const int PwmTimerModule[CORE_NUM_DIGITAL]; +extern "C" const int PwmTimerType[CORE_NUM_DIGITAL]; + +PWM_TimerInfo_t::PWM_TimerInfo_t(unsigned pin) +{ + constexpr char timerNames[][9] = {"FTM", "TPM"}; + + type = PwmTimerType[pin]; + module = PwmTimerModule[pin]; + + if (type != 0) + snprintf(name, 20, "%s%d", timerNames[type - 1], module); + else + snprintf(name, 20, "none"); +} + +#endif \ No newline at end of file diff --git a/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PinInfo.h b/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PinInfo.h new file mode 100644 index 0000000..0821167 --- /dev/null +++ b/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PinInfo.h @@ -0,0 +1,17 @@ +#pragma once + +#include "GPIO_Info.h" +#include "PWM_TimerInfo.h" + +struct PinInfo +{ + inline PinInfo(unsigned _pin) + : pin(_pin), + gpioInfo(pin), + pwmTimerInfo(pin) + {} + + const unsigned pin; + const GPIO_Info_t gpioInfo; + const PWM_TimerInfo_t pwmTimerInfo; +}; diff --git a/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PinInformation.ino b/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PinInformation.ino new file mode 100644 index 0000000..c48be6d --- /dev/null +++ b/third-party/TeensyTimerTool/examples/99_Misc/PinInformation/PinInformation.ino @@ -0,0 +1,53 @@ +#include "PinInfo.h" +#include + +void setup() +{ + while (!Serial) {} + + // setup an array containing info for all digital pins + PinInfo* pins[CORE_NUM_DIGITAL]; + for (unsigned i = 0; i < CORE_NUM_DIGITAL; i++) + { + pins[i] = new PinInfo(i); + } + + // Print out info sorted by pin numbers + Serial.println("-------------------------------"); + Serial.println(" Sorted by pin number"); + printPins(pins, CORE_NUM_DIGITAL); + + Serial.println("\n-------------------------------"); + Serial.println(" Sorted by PWM timer"); + std::sort(pins, pins + CORE_NUM_DIGITAL, [](PinInfo* a, PinInfo* b) { + if (a->pwmTimerInfo.type < b->pwmTimerInfo.type) return false; + if (a->pwmTimerInfo.type > b->pwmTimerInfo.type) return true; + if (a->pwmTimerInfo.module < b->pwmTimerInfo.module) return true; + return false; + }); + printPins(pins, CORE_NUM_DIGITAL); + + Serial.println("\n-------------------------------"); + Serial.println(" Sorted by GPIO register: "); + std::sort(pins, pins + CORE_NUM_DIGITAL, [](PinInfo* a, PinInfo* b) { + if (a->gpioInfo.gpioPortNr < b->gpioInfo.gpioPortNr) return true; + if (a->gpioInfo.gpioPortNr > b->gpioInfo.gpioPortNr) return false; + if (a->gpioInfo.gpioBitNr < b->gpioInfo.gpioBitNr) return true; + return false; + }); + printPins(pins, CORE_NUM_DIGITAL); +} + +void loop() {} + +// Helpers ------------------------------------------------------- + +void printPins(PinInfo* pins[], unsigned nrOfPins) +{ + Serial.println("Pin | GPIO Reg | PWM timer"); + Serial.println("----|------------|-------------"); + for (unsigned i = 0; i < nrOfPins; i++) + { + Serial.printf("%02d | %-9s | %-10s\n", pins[i]->pin, pins[i]->gpioInfo.name, pins[i]->pwmTimerInfo.name); + } +} diff --git a/third-party/TeensyTimerTool/library.json b/third-party/TeensyTimerTool/library.json new file mode 100644 index 0000000..ee274a5 --- /dev/null +++ b/third-party/TeensyTimerTool/library.json @@ -0,0 +1,21 @@ +{ + "name": "TeensyTimerTool", + "keywords": "teensy, pjrc, timer, quad, gpt, tmr, pit, imxrt1062", + "description": "TeensyTimerTool is a library that provides a generic, easy to use interface to the hardware timers of the PJRC Teensy boards. In addition, it provides up to 20 highly efficient software timers that use the same interface. All timers can be used in periodic and one-shot mode. Currently the library supports the ARM T3.X and T4.0 boards. You can either pick a free timer from a pool or specify exactly which of the available hardware or software timer modules you want to use.", + "repository": + { + "type": "git", + "url": "https://github.com/luni64/TeensyTimerTool" + }, + "authors": + { + "name": "Lutz Niggl", + "email": "lutz.niggl@lunoptics.com", + "url": "https://github.com/luni64", + "maintainer": true + }, + "homepage": "https://github.com/luni64/TeensyTimerTool", + "version": "0.3.3", + "frameworks": "arduino", + "platforms": "Teensy" +} diff --git a/third-party/TeensyTimerTool/library.properties b/third-party/TeensyTimerTool/library.properties new file mode 100644 index 0000000..9661166 --- /dev/null +++ b/third-party/TeensyTimerTool/library.properties @@ -0,0 +1,10 @@ +name=TeensyTimerTool +version=0.3.3 +author=Lutz Niggl +maintainer=Lutz Niggl +sentence=Generic Interface to Teensy Timers +paragraph= TeensyTimerTool is a library that provides a generic, easy to use interface to the hardware timers (FTM, GPT, QUAD, PIT) of the PJRC Teensy boards. In addition, it provides up to 20 highly efficient software timers that use the same interface. All timers can be used in periodic and one-shot mode. Currently the library supports the ARM T3.X and T4.0 boards. You can either pick a free timer from a pool or specify exactly which of the available hardware or software timer modules you want to use. +category=Timing +url=https://github.com/luni64/TeensyTimerTool +architectures=* +includes=TeensyTimerTool.h diff --git a/third-party/TeensyTimerTool/src/ESP32-dummy/TCK/TCK.cpp b/third-party/TeensyTimerTool/src/ESP32-dummy/TCK/TCK.cpp new file mode 100644 index 0000000..29bbc2d --- /dev/null +++ b/third-party/TeensyTimerTool/src/ESP32-dummy/TCK/TCK.cpp @@ -0,0 +1,17 @@ +#ifdef ESP32 + +#include "../../TeensyTimerTool.h" + +#if defined(HAS_TCK) and defined(ESP32) + +namespace TeensyTimerTool +{ + bool TCK_t::isInitialized = false; + TckChannel* TCK_t::channels[maxTckChannels]; +} + +void yield() { TeensyTimerTool::TCK_t::tick(); } + +#endif + +#endif \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/ESP32-dummy/TCK/TCK.h b/third-party/TeensyTimerTool/src/ESP32-dummy/TCK/TCK.h new file mode 100644 index 0000000..df9e86a --- /dev/null +++ b/third-party/TeensyTimerTool/src/ESP32-dummy/TCK/TCK.h @@ -0,0 +1,79 @@ +#pragma once + +#include "TckChannel.h" +#include +#include + +namespace TeensyTimerTool +{ + constexpr unsigned maxTckChannels = 20; + + class TCK_t + { + public: + static inline ITimerChannel* getTimer(); + static inline void removeTimer(TckChannel*); + static inline void tick(); + + protected: + static bool isInitialized; + static TckChannel* channels[maxTckChannels]; + }; + + // IMPLEMENTATION ================================================================== + + ITimerChannel* TCK_t::getTimer() + { + Serial.printf("TCK getTimer()\n"); + + if (!isInitialized) + { + for (unsigned chNr = 0; chNr < maxTckChannels; chNr++) + { + channels[chNr] = nullptr; + } + isInitialized = true; + } + + for (unsigned chNr = 0; chNr < maxTckChannels; chNr++) + { + if (channels[chNr] == nullptr) + { + channels[chNr] = new TckChannel(); + return channels[chNr]; + } + } + + return nullptr; + } + + void TCK_t::removeTimer(TckChannel* channel) + { + for (unsigned chNr = 0; chNr < maxTckChannels; chNr++) + { + if (channels[chNr] == channel) + { + channels[chNr] = nullptr; + delete channel; + break; + } + } + } + + void TCK_t::tick() + { + digitalWriteFast(12,HIGH); + for(unsigned i = 0; i < maxTckChannels; i++) + { + if (channels[i] != nullptr ) + { + channels[i]->tick(); + } + } + digitalWriteFast(12,LOW); + } + + + constexpr TimerGenerator* TCK = TCK_t::getTimer; + +} // namespace TeensyTimerTool diff --git a/third-party/TeensyTimerTool/src/ESP32-dummy/TCK/TckChannel.h b/third-party/TeensyTimerTool/src/ESP32-dummy/TCK/TckChannel.h new file mode 100644 index 0000000..bd3089b --- /dev/null +++ b/third-party/TeensyTimerTool/src/ESP32-dummy/TCK/TckChannel.h @@ -0,0 +1,89 @@ +#pragma once + +#include "../ITimerChannel.h" +#include "../types.h" +#include "Arduino.h" + +namespace TeensyTimerTool +{ + class TCK_t; + + class TckChannel : public ITimerChannel + { + public: + inline TckChannel() { triggered = false; } + inline virtual ~TckChannel(){}; + + inline void begin(callback_t cb, unsigned period, bool periodic) + { + Serial.println("begin"); + + triggered = false; + this->periodic = periodic; + this->period = period * (F_CPU / 1'000'000); + this->callback = cb; + + startCNT = ARM_DWT_CYCCNT; + } + + inline void start() + { + Serial.println("start"); + + this->startCNT = ARM_DWT_CYCCNT; + this->triggered = true; + } + + inline void stop() + { + this->triggered = false; + } + + // inline void setPeriod(uint32_t microSeconds); + inline uint32_t getPeriod(void); + + inline void trigger(uint32_t delay) // µs + { + this->startCNT = ARM_DWT_CYCCNT; + this->period = delay * (F_CPU / 1'000'000) - 68; + this->triggered = true; + } + + protected: + uint32_t startCNT, period; + callback_t callback; + bool triggered; + bool periodic; + + inline void tick(); + bool block = false; + + friend TCK_t; + }; + + // IMPLEMENTATION ============================================== + + void TckChannel::tick() + { + static bool lock = false; + + if (!lock && period != 0 && triggered && (ARM_DWT_CYCCNT - startCNT) >= period) + { + lock = true; + startCNT = ARM_DWT_CYCCNT; + triggered = periodic; // i.e., stays triggerd if periodic, stops if oneShot + callback(); + lock = false; + } + } + + // void TckChannel::setPeriod(uint32_t microSeconds) + // { + // period = microSeconds * (F_CPU / 1'000'000) ; + // } + uint32_t TckChannel::getPeriod() + { + return period * (1'000'000.0f / F_CPU); + } + +} // namespace TeensyTimerTool diff --git a/third-party/TeensyTimerTool/src/ErrorHandling/error_codes.h b/third-party/TeensyTimerTool/src/ErrorHandling/error_codes.h new file mode 100644 index 0000000..500bdcb --- /dev/null +++ b/third-party/TeensyTimerTool/src/ErrorHandling/error_codes.h @@ -0,0 +1,40 @@ +#pragma once + +namespace TeensyTimerTool +{ + enum class errorCode + { + OK = 0, + + // Warnings + periodOverflow= -100, + wrongType= -101, + triggeredLate= -102, //warn if new setted period is shorter than elapsed time + + //General errors + argument = 100, + callback= 101, + reload= 102, + noFreeModule = 103, + noFreeChannel = 104, // requested module has no free channel + notImplemented= 105, // timer does not support this feature + notInitialized= 106, + + + // GTP Errors + GTP_err = 200, + GTP_err2 = 201, + + //TMR Errors + TMR_err = 300, + TMR_err2 = 301, + + //FTM Errors + FTM_err = 400, + FTM_err2 = 401, + + //TCK Errors + TCK_err = 900, + TCK_err2 = 901, + }; +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/ErrorHandling/error_handler.cpp b/third-party/TeensyTimerTool/src/ErrorHandling/error_handler.cpp new file mode 100644 index 0000000..8eefa63 --- /dev/null +++ b/third-party/TeensyTimerTool/src/ErrorHandling/error_handler.cpp @@ -0,0 +1,79 @@ +#include "error_handler.h" +#include "core_pins.h" +#include "types.h" + +namespace TeensyTimerTool +{ + ErrorHandler::ErrorHandler(Stream& s) : stream(s) + { + pinMode(LED_BUILTIN, OUTPUT); + } + + void ErrorHandler::operator()(errorCode code) const + { + const char* txt; + + switch (code) + { + case errorCode::OK: + txt = "OK"; + break; + + // warnings + case errorCode::periodOverflow: + txt = "Period overflow, set to maximum"; + break; + case errorCode::wrongType: + txt = "Wrong parameter type"; + break; + + // general errors + case errorCode::reload: + txt = "Period must not be zero"; + break; + case errorCode::noFreeChannel: + txt = "Timer module has no free channel"; + break; + case errorCode::noFreeModule: + txt = "Timer pool contains no free timer"; + break; + case errorCode::notImplemented: + txt = "Function not implemented for this timer"; + break; + case errorCode::notInitialized: + txt = "Timer not initialized or available"; + break; + + default: + txt = "Unknown error"; + break; + } + + if ((int)code < 0) // in case of warnings we return after printing + { + stream.printf("W-%i: %s\n", -(int)code, txt); + return; + } + + stream.printf("E-%i: %s\n", (int)code, txt); // in case of errors we don't return + while (true) + { + digitalWriteFast(LED_BUILTIN, !digitalReadFast(LED_BUILTIN)); + delay(50); + } + } + + errorFunc_t errFunc; + + errorCode postError(errorCode e) + { + if (errFunc != nullptr && e != errorCode::OK) errFunc(e); + return e; + } + + void attachErrFunc(errorFunc_t _errFunc) + { + errFunc = _errFunc; + } + +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/ErrorHandling/error_handler.h b/third-party/TeensyTimerTool/src/ErrorHandling/error_handler.h new file mode 100644 index 0000000..da7eedb --- /dev/null +++ b/third-party/TeensyTimerTool/src/ErrorHandling/error_handler.h @@ -0,0 +1,17 @@ +#pragma once + +#include "error_codes.h" +#include "Stream.h" + +namespace TeensyTimerTool +{ + class ErrorHandler + { + public: + ErrorHandler(Stream& s); + void operator()(errorCode code) const; + + protected: + Stream& stream; + }; +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/ITimerChannel.h b/third-party/TeensyTimerTool/src/ITimerChannel.h new file mode 100644 index 0000000..1145c9c --- /dev/null +++ b/third-party/TeensyTimerTool/src/ITimerChannel.h @@ -0,0 +1,48 @@ +#pragma once + +#include "types.h" + +namespace TeensyTimerTool +{ + class ITimerChannel + { + public: + virtual errorCode begin(callback_t callback, float period, bool oneShot) = 0; + virtual errorCode trigger(float delay) = 0; + virtual errorCode triggerDirect(uint32_t reload){ return postError(errorCode::notImplemented); }; + virtual errorCode triggerDirect(uint64_t reload){ return postError(errorCode::notImplemented); }; + virtual errorCode getTriggerReload(float delay, uint32_t* reload) {return postError(errorCode::notImplemented);}; + virtual errorCode getTriggerReload(float delay, uint64_t* reload) {return postError(errorCode::notImplemented);}; + + virtual errorCode start() = 0; + virtual errorCode stop() = 0; + + virtual errorCode setPrescaler(int psc) { return postError(errorCode::notImplemented); } + + virtual float getMaxPeriod() const = 0; + //virtual errorCode setPeriod(uint32_t microSeconds) { return postError(errorCode::notImplemented); }; + virtual errorCode setPeriod(float microSeconds) { return postError(errorCode::notImplemented); }; + //virtual errorCode setNextPeriod(uint32_t microSeconds) { return postError(errorCode::notImplemented); }; + virtual errorCode setNextPeriod(float microSeconds) { return postError(errorCode::notImplemented); }; + virtual uint32_t getPeriod() { return 0; } + + inline void setCallback(callback_t); + + protected: + inline ITimerChannel(callback_t* cbStorage = nullptr); + callback_t* pCallback; + }; + + // IMPLEMENTATION ==================================================== + + ITimerChannel::ITimerChannel(callback_t* cbStorage) + { + this->pCallback = cbStorage; + } + + void ITimerChannel::setCallback(callback_t cb) + { + *pCallback = cb; + } + +} // namespace TeensyTimerTool \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/Teensy/FTM/FTM.h b/third-party/TeensyTimerTool/src/Teensy/FTM/FTM.h new file mode 100644 index 0000000..cba1870 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/FTM/FTM.h @@ -0,0 +1,91 @@ +#pragma once +#include "FTM_Channel.h" +#include "FTM_Info.h" + +namespace TeensyTimerTool +{ + template + class FTM_t + { + public: + inline static ITimerChannel* getTimer(); + FTM_t() = delete; + + private: + static bool isInitialized; + inline static void isr() FASTRUN; + + static constexpr FTM_r_t* r = (FTM_r_t*)FTM_Info::baseAdr; + static constexpr unsigned maxChannel = FTM_Info::nrOfChannels; + static FTM_ChannelInfo channelInfo[maxChannel]; + + static_assert(moduleNr < 4, "Module number < 4 required"); + }; + + // IMPLEMENTATION ================================================================== + + template + ITimerChannel* FTM_t::getTimer() + { + if (!isInitialized) + { + r->SC = FTM_SC_CLKS(0b00); // Disable clock + r->MOD = 0xFFFF; // Set full counter range + r->CNT = 0; + + for (unsigned chNr = 0; chNr < maxChannel; chNr++) // init channels + { + channelInfo[chNr].isReserved = false; + channelInfo[chNr].callback = nullptr; + channelInfo[chNr].chRegs = &r->CH[chNr]; + channelInfo[chNr].ticksPerMicrosecond = 1E-6f * F_BUS / (1 << FTM_Info::prescale); + + r->CH[chNr].SC &= ~FTM_CSC_CHF; // FTM requires to clear flag by setting bit to 0 + r->CH[chNr].SC &= ~FTM_CSC_CHIE; // Disable channel interupt + r->CH[chNr].SC = FTM_CSC_MSA; + } + r->SC = FTM_SC_CLKS(0b01) | FTM_SC_PS(FTM_Info::prescale); // Start clock + attachInterruptVector(FTM_Info::irqNumber, isr); // prepare isr and nvic, don't yet enable interrupts + NVIC_ENABLE_IRQ(FTM_Info::irqNumber); + isInitialized = true; + } + + for (unsigned chNr = 0; chNr < maxChannel; chNr++) + { + if (!channelInfo[chNr].isReserved) + { + channelInfo[chNr].isReserved = true; + return new FTM_Channel(r, &channelInfo[chNr]); + } + } + return nullptr; + } + + template + void FTM_t::isr() + { + for (unsigned i = 0; i < maxChannel; i++) + { + FTM_ChannelInfo* ci = &channelInfo[i]; // pre resolving the references turns out to be slightly faster + FTM_CH_t* cr = ci->chRegs; + if ((cr->SC & (FTM_CSC_CHIE | FTM_CSC_CHF)) == (FTM_CSC_CHIE | FTM_CSC_CHF)) // only handle if channel is active (CHIE set) and overflowed (CHF set) + { + if (ci->isPeriodic) + { + cr->SC &= ~FTM_CSC_CHF; // clear channel flag + cr->CV = r->CNT + ci->reload; // set compare value to 'reload' counts ahead of counter + } else + { + cr->SC &= ~FTM_CSC_CHIE; //disable interrupt in on shot mode + } + ci->callback(); + } + } + } + + template + FTM_ChannelInfo FTM_t::channelInfo[maxChannel]; + + template + bool FTM_t::isInitialized = false; +} diff --git a/third-party/TeensyTimerTool/src/Teensy/FTM/FTM_Channel.h b/third-party/TeensyTimerTool/src/Teensy/FTM/FTM_Channel.h new file mode 100644 index 0000000..bb0f257 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/FTM/FTM_Channel.h @@ -0,0 +1,120 @@ +#pragma once + +#include "../../ITimerChannel.h" +#include "Arduino.h" +#include "FTM_ChannelInfo.h" +#include "FTM_Info.h" + +namespace TeensyTimerTool +{ + class FTM_Channel : public ITimerChannel + { + public: + inline FTM_Channel(FTM_r_t* regs, FTM_ChannelInfo* ci); + inline virtual ~FTM_Channel(); + + inline float getMaxPeriod() const override; + inline errorCode begin(callback_t cb, float tcnt, bool periodic) + { + begin(cb, (uint32_t)tcnt, periodic); + return errorCode::OK; + }; + inline errorCode begin(callback_t cb, uint32_t tcnt, bool periodic); + + inline errorCode trigger(float tcnt) override FASTRUN; + inline errorCode triggerDirect(uint32_t reload) override FASTRUN; + inline errorCode getTriggerReload(float delay, uint32_t* reload) override; + + inline errorCode start() override; + inline errorCode stop() override; + + inline uint16_t ticksFromMicros(float micros); + + // inline void setPeriod(uint32_t) {} + + protected: + FTM_ChannelInfo* ci; + FTM_r_t* regs; + callback_t* pCallback = nullptr; + }; + + // IMPLEMENTATION ============================================== + + FTM_Channel::FTM_Channel(FTM_r_t* regs, FTM_ChannelInfo* channelInfo) + : ITimerChannel(nullptr) + { + this->regs = regs; + this->ci = channelInfo; + } + + errorCode FTM_Channel::begin(callback_t callback, uint32_t tcnt, bool periodic) + { + ci->isPeriodic = periodic; + ci->reload = ticksFromMicros(tcnt); + ci->callback = callback; + + return errorCode::OK; + } + + errorCode FTM_Channel::start() + { + ci->chRegs->CV = regs->CNT + ci->reload; // compare value (current counter + pReload) + ci->chRegs->SC &= ~FTM_CSC_CHF; // reset timer flag + ci->chRegs->SC = FTM_CSC_MSA | FTM_CSC_CHIE; // enable interrupts + return errorCode::OK; + } + + errorCode FTM_Channel::stop() + { + ci->chRegs->SC &= ~FTM_CSC_CHIE; // enable interrupts + ci->chRegs->SC &= ~FTM_CSC_CHF; // reset timer flag + + return errorCode::OK; + } + + errorCode FTM_Channel::getTriggerReload(float delay, uint32_t* reload) + { + *reload = ticksFromMicros(delay); + return errorCode::OK; + } + + errorCode FTM_Channel::trigger(float delay) + { + return triggerDirect(ticksFromMicros(delay)); + } + + errorCode FTM_Channel::triggerDirect(uint32_t reload) + { + uint32_t cv = regs->CNT + reload + 1; // calc early to minimize error + ci->chRegs->SC &= ~FTM_CSC_CHF; // Reset timer flag + + regs->SC &= ~FTM_SC_CLKS_MASK; // need to switch off clock to immediately set new CV + ci->chRegs->CV = cv; // compare value (current counter + pReload) + regs->SC |= FTM_SC_CLKS(0b01); // restart clock + + ci->chRegs->SC = FTM_CSC_MSA | FTM_CSC_CHIE; // enable interrupts + + return errorCode::OK; + } + + float FTM_Channel::getMaxPeriod() const + { + return (0xFFFF * 1E-6f) / ci->ticksPerMicrosecond; // max period in seconds + } + + uint16_t FTM_Channel::ticksFromMicros(float micros) + { + uint32_t rl = ci->ticksPerMicrosecond * micros; + if (rl > 0xFFFF) + { + postError(errorCode::periodOverflow); // warning only, continues with clipped value + rl = 0xFFFF; + } + return rl; + } + + FTM_Channel::~FTM_Channel() + { + } + +} // namespace TeensyTimerTool diff --git a/third-party/TeensyTimerTool/src/Teensy/FTM/FTM_ChannelInfo.h b/third-party/TeensyTimerTool/src/Teensy/FTM/FTM_ChannelInfo.h new file mode 100644 index 0000000..0470f77 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/FTM/FTM_ChannelInfo.h @@ -0,0 +1,17 @@ +#pragma once + +#include "FTM_Info.h" +#include "../../types.h" + +namespace TeensyTimerTool +{ + struct FTM_ChannelInfo + { + bool isReserved; + bool isPeriodic; + callback_t callback; + uint32_t reload; + FTM_CH_t* chRegs; + float ticksPerMicrosecond; + }; +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/Teensy/FTM/FTM_Info.h b/third-party/TeensyTimerTool/src/Teensy/FTM/FTM_Info.h new file mode 100644 index 0000000..76e1967 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/FTM/FTM_Info.h @@ -0,0 +1,110 @@ +#pragma once + +#include "boardDef.h" +#include + + // All information in this header is calculated at compile time. + // FTM_Info will not generate any code + +namespace TeensyTimerTool +{ + typedef struct // FTM & TPM Channels + { + volatile uint32_t SC; + volatile uint32_t CV; + } FTM_CH_t; + + typedef struct // FTM register block (this layout is compatible to a TPM register block) + { + volatile uint32_t SC; + volatile uint32_t CNT; + volatile uint32_t MOD; + FTM_CH_t CH[8]; + volatile uint32_t CNTIN; + volatile uint32_t STATUS; + volatile uint32_t MODE; + volatile uint32_t SYNC; + volatile uint32_t OUTINIT; + volatile uint32_t OUTMASK; + volatile uint32_t COMBINE; + volatile uint32_t DEADTIME; + volatile uint32_t EXTTRIG; + volatile uint32_t POL; + volatile uint32_t FMS; + volatile uint32_t FILTER; + volatile uint32_t FLTCTRL; + volatile uint32_t QDCTRL; + volatile uint32_t CONF; + volatile uint32_t FLTPOL; + volatile uint32_t SYNCONF; + volatile uint32_t INVCTRL; + volatile uint32_t SWOCTRL; + volatile uint32_t PWMLOAD; + } FTM_r_t; + + //======================================================================= + // using a static class instead of namespace here to reduce namespace pollution + // (anonymous namespace doesn't make sense for header only class) + + template + class FTM_Info + { + private: + #if defined(ARDUINO_TEENSYLC) + static constexpr unsigned boardNr = 0; + #elif defined(ARDUINO_TEENSY30) + static constexpr unsigned boardNr = 1; + #elif defined(ARDUINO_TEENSY31) || defined(ARDUINO_TEENSY32) + static constexpr unsigned boardNr = 2; + #elif defined(ARDUINO_TEENSY35) + static constexpr unsigned boardNr = 3; + #elif defined(ARDUINO_TEENSY36) + static constexpr unsigned boardNr = 4; + #else + #error Board not valid + #endif + + static constexpr int IRQ_Numbers[][4] + { //FTM0 FTM1 FTM2 FTM3 + { 0, 0, 0, 0 }, // Teensy LC + { 25, 26, 0, 0 }, // Teensy 3.0 + { 62, 63, 64, 0 }, // Teensy 3.1/3.2 + { 42, 43, 44, 71 }, // Teensy 3.5 + { 42, 43, 44, 71 }, // Teensy 3.6 + }; + + static constexpr int FTM_NrOfChannels[] + { + 8, // FTM0 + 2, // FTM1 + 2, // FTM2 + 8, // FTM3 + }; + + static constexpr uintptr_t FTM_BaseAdr[] // can't use defines from kinetis.h since, depending on board, not all are always defined. + { + 0x4003'8000, // FTM0 + 0x4003'9000, // FTM1 + 0x400B'8000, // FTM2 + 0x400B'9000, // FTM3 + }; + + static constexpr unsigned FTM_Prescale = + FTM_DEFAULT_PSC[module] < 0 || FTM_DEFAULT_PSC[module] > 7 ? // prescale value to roughly get 2 ticks per µs + ( + F_BUS > 120'000'000 ? 0b111 : + F_BUS > 60'000'000 ? 0b110 : + F_BUS > 30'000'000 ? 0b101 : + F_BUS > 15'000'000 ? 0b100 : + F_BUS > 8'000'000 ? 0b011 : + F_BUS > 4'000'000 ? 0b010 : + F_BUS > 2'000'000 ? 0b001 : 0b000 + ):FTM_DEFAULT_PSC[module]; + + public: + static constexpr uintptr_t baseAdr = FTM_BaseAdr[module]; + static constexpr IRQ_NUMBER_t irqNumber = (IRQ_NUMBER_t)IRQ_Numbers[boardNr][module]; + static constexpr unsigned nrOfChannels = FTM_NrOfChannels[module]; + static constexpr unsigned prescale = FTM_Prescale; + }; +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/Teensy/GPT/GPT.h b/third-party/TeensyTimerTool/src/Teensy/GPT/GPT.h new file mode 100644 index 0000000..9963f82 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/GPT/GPT.h @@ -0,0 +1,77 @@ +#pragma once + +#include "GPTChannel.h" + +namespace TeensyTimerTool +{ + template + class GPT_t + { + public: + static ITimerChannel* getTimer(); + + protected: + static bool isInitialized; + static void isr(); + static callback_t callback; + static GptChannel* channel; + + // the following is calculated at compile time + static constexpr IRQ_NUMBER_t irq = moduleNr == 0 ? IRQ_GPT1 : IRQ_GPT2; + static IMXRT_GPT_t* const pGPT; + static_assert(moduleNr < 2, "Wrong GPT Number"); + }; + + // IMPLEMENTATION =========================================================================== + + template + IMXRT_GPT_t* const GPT_t::pGPT = reinterpret_cast(moduleNr == 0 ? &IMXRT_GPT1 : &IMXRT_GPT2); + + template + ITimerChannel* GPT_t::getTimer() + { + if (!isInitialized) + { + isInitialized = true; + + if (moduleNr == 0) // GPT1 clock settings + CCM_CCGR1 |= CCM_CCGR1_GPT1_BUS(CCM_CCGR_ON) | CCM_CCGR1_GPT1_SERIAL(CCM_CCGR_ON); // + else // GPT2 + CCM_CCGR0 |= CCM_CCGR0_GPT2_BUS(CCM_CCGR_ON) | CCM_CCGR0_GPT2_SERIAL(CCM_CCGR_ON); // + // + if (USE_GPT_PIT_150MHz) // timer clock setting from config.h + CCM_CSCMR1 &= ~CCM_CSCMR1_PERCLK_CLK_SEL; // use F_BUS + else // + CCM_CSCMR1 |= CCM_CSCMR1_PERCLK_CLK_SEL; // use 24MHz + // + pGPT->CR = GPT_CR_CLKSRC(0x001) | GPT_CR_ENMOD; // stopped, restart mode and peripheral clock source + + attachInterruptVector(irq, isr); + NVIC_ENABLE_IRQ(irq); + + channel = new GptChannel(pGPT, &callback); + return channel; + } + return nullptr; + } + + template + void GPT_t::isr() + { + if (!channel->periodic) + pGPT->CR &= ~GPT_CR_EN; // stop timer in one shot mode + // + pGPT->SR = 0x3F; // reset all interrupt flags + callback(); // we only enabled the OF1 interrupt-> no need to find out which interrupt was actually called + asm volatile("dsb"); // wait until register changes propagated through the cache + } + + template + bool GPT_t::isInitialized = false; + + template + callback_t GPT_t::callback = nullptr; + + template + GptChannel* GPT_t::channel = nullptr; +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/Teensy/GPT/GPTChannel.h b/third-party/TeensyTimerTool/src/Teensy/GPT/GPTChannel.h new file mode 100644 index 0000000..6eabc3c --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/GPT/GPTChannel.h @@ -0,0 +1,116 @@ +#pragma once + +#include "../../ITimerChannel.h" +#include "GPTmap.h" +#include "core_pins.h" + +namespace TeensyTimerTool +{ + class GptChannel : public ITimerChannel + { + public: + inline GptChannel(IMXRT_GPT_t*, callback_t*); + inline virtual ~GptChannel(); + + inline errorCode begin(callback_t cb, float tcnt, bool periodic) override; + inline errorCode start() override; + inline errorCode stop() override; + + inline errorCode trigger(float delay) override; + inline errorCode triggerDirect(uint32_t delay) override; + inline errorCode getTriggerReload(float delay, uint32_t* reload) override; + + inline float getMaxPeriod() const override { return getMaxMicros() / 1E6; } + + + + bool periodic; + protected: + inline uint32_t microsecondToCycles(float micros) const; + inline float getMaxMicros() const; + + + IMXRT_GPT_t* regs; + uint32_t reload; + float clock; + }; + + // IMPLEMENTATION ============================================== + + GptChannel::GptChannel(IMXRT_GPT_t* registers, callback_t* cbStorage) + : ITimerChannel(cbStorage), regs(registers) + { + clock = (CCM_CSCMR1 & CCM_CSCMR1_PERCLK_CLK_SEL) ? 24 : (F_BUS_ACTUAL / 1000000); + } + + errorCode GptChannel::begin(callback_t cb, float period, bool periodic) + { + this->periodic = periodic; + if (periodic) + { + reload = microsecondToCycles(period); + regs->OCR1 = reload; + } + setCallback(cb); + + return errorCode::OK; + } + + errorCode GptChannel::start() + { + regs->SR = 0x3F; // clear all interupt flags + regs->IR = GPT_IR_OF1IE; // enable OF1 interrupt + regs->CR |= GPT_CR_EN; // enable timer + return errorCode::OK; + } + + errorCode GptChannel::stop() + { + regs->CR &= ~GPT_CR_EN; // disable timer + regs->IR = 0; + return errorCode::OK; + } + + GptChannel::~GptChannel() + { + stop(); + setCallback(nullptr); + } + + errorCode GptChannel::trigger(float delay) //should be optimized somehow + { + return triggerDirect(microsecondToCycles(delay)); + } + + errorCode GptChannel::triggerDirect(uint32_t reload) + { + regs->SR = 0x3F; // clear all interupt flags + regs->IR = GPT_IR_OF1IE; // enable OF1 interrupt + regs->OCR1 = reload; // set overflow value + regs->CR |= GPT_CR_EN; // enable timer + + return errorCode::OK; + } + + errorCode GptChannel::getTriggerReload(float delay, uint32_t* reload) + { + *reload = microsecondToCycles(delay); + return errorCode::OK; + } + + uint32_t GptChannel::microsecondToCycles(float micros) const + { + if (micros > getMaxMicros()) + { + micros = getMaxPeriod(); + postError(errorCode::periodOverflow); + } + return (uint32_t)(clock * micros) - 1; + } + + float GptChannel::getMaxMicros() const + { + return (float)0xFFFF'FFFE / clock; + } + +} // namespace TeensyTimerTool \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/Teensy/GPT/GPTmap.h b/third-party/TeensyTimerTool/src/Teensy/GPT/GPTmap.h new file mode 100644 index 0000000..63bdf94 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/GPT/GPTmap.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +namespace TeensyTimerTool +{ + struct IMXRT_GPT_t + { + //51.7.1 GPT Control Register + volatile uint32_t CR; + //51.7.2 GPT Prescaler Register(GPTx_PR) + volatile uint32_t PR; + //51.7.3 GPT Status Register(GPTx_SR) + volatile uint32_t SR; + //51.7.4 GPT Interrupt Register(GPTx_IR) + volatile uint32_t IR; + //51.7.5 GPT Output Compare Register (GPTx_OCR1) + volatile uint32_t OCR1; + //51.7.6 GPT Output Compare Register (GPTx_OCR2) + volatile uint32_t OCR2; + //51.7.7 GPT Output Compare Register (GPTx_OCR3) + volatile uint32_t OCR3; + //51.7.8 GPT Input Capture Register 1 (GPTx_ICR1) + volatile uint32_t ICR1; + //51.7.9 GPT Input Capture Register 1 (GPTx_ICR2) + volatile uint32_t ICR2; + //51.7.10 GPT Counter Register (GPTx_CNT) + volatile uint32_t CNT; + }; + +} // namespace TeensyTimerTool \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/Teensy/PIT4/PIT.cpp b/third-party/TeensyTimerTool/src/Teensy/PIT4/PIT.cpp new file mode 100644 index 0000000..92c76d2 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/PIT4/PIT.cpp @@ -0,0 +1,13 @@ +#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) || defined(ARDUINO_TEENSY_MICROMOD) + +#include "PIT.h" + +namespace TeensyTimerTool +{ + bool PIT_t::isInitialized = false; + PITChannel PIT_t::channel[4] = {{0}, {1}, {2}, {3}}; + + uint32_t PITChannel::clockFactor = 1; +} + +#endif \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/Teensy/PIT4/PIT.h b/third-party/TeensyTimerTool/src/Teensy/PIT4/PIT.h new file mode 100644 index 0000000..1cb8600 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/PIT4/PIT.h @@ -0,0 +1,70 @@ +#pragma once + +#include "PITChannel.h" + +namespace TeensyTimerTool +{ + class PIT_t + { + public: + inline static ITimerChannel* getTimer(); + + protected: + static bool isInitialized; + static void isr(); + static PITChannel channel[4]; + }; + + // IMPLEMENTATION =========================================================================== + + ITimerChannel* PIT_t::getTimer() + { + if (!isInitialized) + { + isInitialized = true; + + CCM_CCGR1 |= CCM_CCGR1_PIT(CCM_CCGR_ON); + PIT_MCR = 1; + + if (USE_GPT_PIT_150MHz) // timer clock setting from config.h + CCM_CSCMR1 &= ~CCM_CSCMR1_PERCLK_CLK_SEL; // FBus (usually 150MHz) + else + CCM_CSCMR1 |= CCM_CSCMR1_PERCLK_CLK_SEL; // 24MHz + + attachInterruptVector(IRQ_PIT, isr); + NVIC_ENABLE_IRQ(IRQ_PIT); + } + + for (unsigned i = 0; i < 4; i++) + { + if (channel[i].callback == nullptr) return &channel[i]; + } + return nullptr; + } + + inline void PIT_t::isr() + { + if (IMXRT_PIT_CHANNELS[0].TFLG) + { + IMXRT_PIT_CHANNELS[0].TFLG = 1; + channel[0].isr(); + } + if (IMXRT_PIT_CHANNELS[1].TFLG) + { + IMXRT_PIT_CHANNELS[1].TFLG = 1; + channel[1].isr(); + } + if (IMXRT_PIT_CHANNELS[2].TFLG) + { + IMXRT_PIT_CHANNELS[2].TFLG = 1; + channel[2].isr(); + } + if (IMXRT_PIT_CHANNELS[3].TFLG) + { + IMXRT_PIT_CHANNELS[3].TFLG = 1; + channel[3].isr(); + } + + asm volatile("dsb"); //wait until register changes propagated through the cache + } +} diff --git a/third-party/TeensyTimerTool/src/Teensy/PIT4/PITChannel.h b/third-party/TeensyTimerTool/src/Teensy/PIT4/PITChannel.h new file mode 100644 index 0000000..5d688d7 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/PIT4/PITChannel.h @@ -0,0 +1,152 @@ +#pragma once + +#include "../../ITimerChannel.h" +#include "Arduino.h" +#include "PITMap.h" + +namespace TeensyTimerTool +{ + class PIT_t; + + class PITChannel : public ITimerChannel + { + public: + inline PITChannel(unsigned nr); + inline virtual ~PITChannel(); + + inline errorCode begin(callback_t cb, float tcnt, bool periodic) override; + //inline errorCode begin(callback_t cb, uint32_t tcnt, bool periodic) override; + inline errorCode start() override; + inline errorCode stop() override; + + //inline errorCode trigger(uint32_t) override; + inline errorCode trigger(float) override; + inline errorCode setNextPeriod(float us) override; + inline errorCode setPeriod(float us) override; + + inline float getMaxPeriod() const override; + + bool isPeriodic; + + protected: + //IMXRT_GPT_t* regs; + //uint32_t reload; + + inline void isr(); + + PITChannel() = delete; + PITChannel(const PITChannel&) = delete; + + const unsigned chNr; + callback_t callback = nullptr; + + static uint32_t clockFactor; + + friend PIT_t; + }; + + // IMPLEMENTATION ============================================== + + PITChannel::PITChannel(unsigned nr) + : ITimerChannel(nullptr), chNr(nr) + { + callback = nullptr; + clockFactor = (CCM_CSCMR1 & CCM_CSCMR1_PERCLK_CLK_SEL) ? 24 : (F_BUS_ACTUAL / 1000000); + } + + errorCode PITChannel::begin(callback_t cb, float micros, bool periodic) + { + isPeriodic = periodic; + callback = cb; + + if (isPeriodic) + { + IMXRT_PIT_CHANNELS[chNr].TCTRL = 0; + IMXRT_PIT_CHANNELS[chNr].TFLG = 1; + + setNextPeriod(micros); + // float tmp = micros * clockFactor; + // if (tmp > 0xFFFF'FFFF) + // { + // postError(errorCode::periodOverflow); + // IMXRT_PIT_CHANNELS[chNr].LDVAL = 0xFFFF'FFFE; + // } else + // { + // IMXRT_PIT_CHANNELS[chNr].LDVAL = (uint32_t)tmp - 1; + // } + } + return errorCode::OK; + } + + errorCode PITChannel::start() + { + IMXRT_PIT_CHANNELS[chNr].TCTRL = PIT_TCTRL_TEN | PIT_TCTRL_TIE; + return errorCode::OK; + } + + errorCode PITChannel::stop() + { + IMXRT_PIT_CHANNELS[chNr].TCTRL = 0; + return errorCode::OK; + } + + errorCode PITChannel::setNextPeriod(float us) + { + float cts = us * clockFactor; + if (cts > 0xFFFF'FFFF) + { + postError(errorCode::periodOverflow); + IMXRT_PIT_CHANNELS[chNr].LDVAL = 0xFFFF'FFFE; + } else + { + IMXRT_PIT_CHANNELS[chNr].LDVAL = (uint32_t)cts - 1; + } + return errorCode::OK; + } + + errorCode PITChannel::setPeriod(float us) + { + stop(); // need to stop/start timer to change current period (see ch 53.9.5.4) + setNextPeriod(us); + start(); + return errorCode::OK; + } + + void PITChannel::isr() + { + if (callback != nullptr) + { + callback(); + if (!isPeriodic) IMXRT_PIT_CHANNELS[chNr].TCTRL = 0; // switch off timer + } + } + + PITChannel::~PITChannel() + { + callback = nullptr; + } + + errorCode PITChannel::trigger(float delay) //should be optimized somehow + { + IMXRT_PIT_CHANNELS[chNr].TCTRL = 0; + IMXRT_PIT_CHANNELS[chNr].TFLG = 1; + + float tmp = delay * clockFactor; + if (tmp > 0xFFFF'FFFF) + { + postError(errorCode::periodOverflow); + IMXRT_PIT_CHANNELS[chNr].LDVAL = 0xFFFF'FFFE; + } else + IMXRT_PIT_CHANNELS[chNr].LDVAL = (uint32_t)tmp - 1; + + start(); + + return errorCode::OK; + } + + float PITChannel::getMaxPeriod() const + { + return (float)0xFFFF'FFFE / clockFactor / 1'000'000; + } + +} // namespace TeensyTimerTool \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/Teensy/PIT4/PITMap.h b/third-party/TeensyTimerTool/src/Teensy/PIT4/PITMap.h new file mode 100644 index 0000000..2e0336c --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/PIT4/PITMap.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +namespace TeensyTimerTool +{ + // struct IMXRT_GPT_t + // { + // //51.7.1 GPT Control Register + // volatile uint32_t CR; + // //51.7.2 GPT Prescaler Register(GPTx_PR) + // volatile uint32_t PR; + // //51.7.3 GPT Status Register(GPTx_SR) + // volatile uint32_t SR; + // //51.7.4 GPT Interrupt Register(GPTx_IR) + // volatile uint32_t IR; + // //51.7.5 GPT Output Compare Register (GPTx_OCR1) + // volatile uint32_t OCR1; + // //51.7.6 GPT Output Compare Register (GPTx_OCR2) + // volatile uint32_t OCR2; + // //51.7.7 GPT Output Compare Register (GPTx_OCR3) + // volatile uint32_t OCR3; + // //51.7.8 GPT Input Capture Register 1 (GPTx_ICR1) + // volatile uint32_t ICR1; + // //51.7.9 GPT Input Capture Register 1 (GPTx_ICR2) + // volatile uint32_t ICR2; + // //51.7.10 GPT Counter Register (GPTx_CNT) + // volatile uint32_t CNT; + // }; + +} // namespace TeensyTimerTool \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/Teensy/TCK/TCK.cpp b/third-party/TeensyTimerTool/src/Teensy/TCK/TCK.cpp new file mode 100644 index 0000000..3c3fc5e --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/TCK/TCK.cpp @@ -0,0 +1,41 @@ +#include "../../config.h" + +#if defined(TEENSYDUINO) + + #include "TCK.h" + namespace TeensyTimerTool + { + bool TCK_t::isInitialized = false; + TckChannelBase* TCK_t::channels[NR_OF_TCK_TIMERS]; + } + +//---------------------------------------------------------------------- + #if YIELD_TYPE == YIELD_OPTIMIZED + + void yield() + { + TeensyTimerTool::TCK_t::tick(); + } + +//---------------------------------------------------------------------- + #elif YIELD_TYPE == YIELD_STANDARD + + #include "EventResponder.h" + + namespace TeensyTimerTool + { + static EventResponder er; + + void initYieldHook() + { + er.attach([](EventResponderRef r) + { + TeensyTimerTool::TCK_t::tick(); + r.triggerEvent(); + }); + er.triggerEvent(); + } + } + #endif +#endif +//#endif \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/Teensy/TCK/TCK.h b/third-party/TeensyTimerTool/src/Teensy/TCK/TCK.h new file mode 100644 index 0000000..c9eadc5 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/TCK/TCK.h @@ -0,0 +1,82 @@ +#pragma once + +//#include "TckChannelBase.h" +#include "TckChannel.h" +#include "core_pins.h" + +namespace TeensyTimerTool +{ + extern const unsigned NR_OF_TCK_TIMERS; + + class TCK_t + { + public: + template static inline ITimerChannel* getTimer(); + static inline void removeTimer(TckChannelBase*); + static inline void tick(); + + protected: + static bool isInitialized; + static TckChannelBase* channels[NR_OF_TCK_TIMERS]; + }; + + // IMPLEMENTATION ================================================================== + + template + ITimerChannel* TCK_t::getTimer() + { + if (!isInitialized) + { + for (unsigned chNr = 0; chNr < NR_OF_TCK_TIMERS; chNr++) + { + channels[chNr] = nullptr; + } + isInitialized = true; + + // enable the cycle counter + ARM_DEMCR |= ARM_DEMCR_TRCENA; + ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; + + // initialize the yield hook + #if defined(TEENSYDUINO) && YIELD_TYPE == YIELD_STANDARD + extern void initYieldHook(); + initYieldHook(); + #endif + } + + for (unsigned chNr = 0; chNr < NR_OF_TCK_TIMERS; chNr++) + { + if (channels[chNr] == nullptr) + { + channels[chNr] = new TckChannel(); + return channels[chNr]; + } + } + + return nullptr; + } + + void TCK_t::removeTimer(TckChannelBase* channel) + { + for (unsigned chNr = 0; chNr < NR_OF_TCK_TIMERS; chNr++) + { + if (channels[chNr] == channel) + { + channels[chNr] = nullptr; + delete channel; + break; + } + } + } + + void TCK_t::tick() + { + for (unsigned i = 0; i < NR_OF_TCK_TIMERS; i++) + { + if (channels[i] != nullptr) + { + channels[i]->tick(); + } + } + } +} diff --git a/third-party/TeensyTimerTool/src/Teensy/TCK/TckChannel.h b/third-party/TeensyTimerTool/src/Teensy/TCK/TckChannel.h new file mode 100644 index 0000000..784ba26 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/TCK/TckChannel.h @@ -0,0 +1,336 @@ +#pragma once + +//#include "Arduino.h" +#include "ErrorHandling/error_codes.h" +#include "TckChannelBase.h" +#include "core_pins.h" + +namespace TeensyTimerTool +{ + class TCK_t; + +#if !defined(ARDUINO_TEENSYLC) // T-LC doesn't have a cycle counter, nees special treatment + + template + class TckChannel : public TckChannelBase + { + public: + TckChannel(); + virtual ~TckChannel(){}; //TBD + + inline errorCode begin(callback_t cb, float period, bool periodic) override; + inline errorCode start() override; + inline errorCode stop() override; + + inline errorCode trigger(float delay_us) override; + inline errorCode triggerDirect(CounterType reload) override; + inline errorCode getTriggerReload(float delay, CounterType* reload) override + { + *reload = microsecondToCycles(delay); + return errorCode::OK; + } + + float getMaxPeriod() const override { return getMaxMicros() / 1E6; } // seconds + + CounterType getCycleCounter() { postError(errorCode::wrongType); } + + // inline errorCode setPeriod(uint32_t microSeconds) override; + // inline errorCode setPeriod(uint32_t microSeconds) override; + // inline errorCode setNextPeriod(uint32_t microSeconds) override; + + protected: + inline CounterType microsecondToCycles(const float microSecond) const; + float getMaxMicros() const; + + inline bool tick(); + callback_t callback; + + bool periodic; + bool triggered; + + // bool block = false; + + CounterType startCnt, currentPeriod, nextPeriod; + + uint32_t lastCyCnt; + uint32_t curHIGH; + + float clock; + + friend TCK_t; + + // inline uint32_t CPUCyclesToMicroseond(const uint32_t cpuCycles); + // inline errorCode _setCurrentPeriod(const uint32_t period); + // inline void _setNextPeriod(const uint32_t period); + }; + + // IMPLEMENTATION ============================================== + + template + TckChannel::TckChannel() + { + triggered = false; + clock = F_CPU / 1'000'000.0f; + } + + template + errorCode TckChannel::begin(callback_t cb, float period, bool periodic) + { + this->triggered = false; + + this->periodic = periodic; + if (periodic) + { + this->currentPeriod = microsecondToCycles(period); + this->nextPeriod = this->currentPeriod; + } + this->callback = cb; + + return errorCode::OK; + } + + template + errorCode TckChannel::start() + { + this->startCnt = getCycleCounter(); + this->triggered = true; + return errorCode::OK; + } + + template + errorCode TckChannel::stop() + { + this->triggered = false; + return errorCode::OK; + } + + template + errorCode TckChannel::triggerDirect(CounterType reload) + { + this->startCnt = getCycleCounter(); + this->nextPeriod = reload; + this->currentPeriod = this->nextPeriod; + this->triggered = true; + return errorCode::OK; + } + + template + errorCode TckChannel::trigger(float delay) // µs + { + return triggerDirect(microsecondToCycles(delay)); + } + + template + bool TckChannel::tick() + { + static bool lock = false; + counter_t now = getCycleCounter(); + if (!lock && this->currentPeriod != 0 && this->triggered && (now - this->startCnt) >= this->currentPeriod) + { + lock = true; + //this->startCnt = now; + this->startCnt += currentPeriod; + this->triggered = this->periodic; // i.e., stays triggerd if periodic, stops if oneShot + callback(); + lock = false; + return true; + } else + { + return false; + } + } + + template + CounterType TckChannel::microsecondToCycles(float microSecond) const + { + if (microSecond > getMaxMicros()) + { + microSecond = getMaxMicros(); + postError(errorCode::periodOverflow); + } + return (CounterType)(microSecond * clock); + } + + // SPECIALIZATIONS ================================================================================= + // 32bit Counter ------------------------------------------------------------------------- + + template <> + inline uint32_t TckChannel::getCycleCounter() + { + return ARM_DWT_CYCCNT; //directly use the cycle counter for uint32_t + } + + template <> + inline float TckChannel::getMaxMicros() const + { + return 0xF000'0000 / clock; // don't use full range otherwise tick might miss the turnover for large periods + } + + // 64bit Counter ------------------------------------------------------------------------- + + template <> + inline uint64_t TckChannel::getCycleCounter() + { + uint32_t now = ARM_DWT_CYCCNT; // (extend the cycle counter to 64 bit) + if (now < lastCyCnt) + { + curHIGH++; + } + lastCyCnt = now; + return (((uint64_t)curHIGH << 32) | now); + } + + template <> + inline float TckChannel::getMaxMicros() const + { + return 0xFFFF'FFFF; // currently limited to 2^32 µs (71.6h). Could be extended to 2^64 but would require change of interface + } + + // template + // uint32_t TckChannel::CPUCyclesToMicroseond(const uint32_t cpuCycles) + // { + // return (1'000'000.0f / F_CPU) * cpuCycles; + // } + + // template + // void TckChannel::_setNextPeriod(const uint32_t period) + // { + // this->nextPeriod = period; + // } + + // template + // errorCode TckChannel::_setCurrentPeriod(const uint32_t period) + // { + // this->currentPeriod = period; + // const bool hasTicked = this->tick(); + + // if (hasTicked) + // { + // return errorCode::triggeredLate; + // } else + // { + // return errorCode::OK; + // } + // } + + // template + // errorCode TckChannel::setPeriod(uint32_t microSeconds) + // { + // const uint32_t period = microsecondToCPUCycles(microSeconds); + + // this->_setNextPeriod(period); + // return this->_setCurrentPeriod(period); + // } + + // template + // errorCode TckChannel::setPeriod(uint32_t microSeconds) + // { + // const uint32_t period = microsecondToCPUCycles(microSeconds); + // return this->_setCurrentPeriod(period); + // } + + // template + // errorCode TckChannel::setNextPeriod(uint32_t microSeconds) + // { + // const uint32_t period = microsecondToCPUCycles(microSeconds); + // this->_setNextPeriod(period); + // return errorCode::OK; + // } + +#else // TeensyLC + + // Quick hack only, needs to be improved + + template + class TckChannel : public TckChannelBase + { + public: + TckChannel() { triggered = false; } + virtual ~TckChannel(){}; + + errorCode begin(callback_t cb, float period, bool periodic) override + { + triggered = false; + this->periodic = periodic; + this->period = (uint32_t)period; + this->callback = cb; + + return errorCode::OK; + } + + errorCode start() + { + this->startCNT = micros(); + this->triggered = true; + return errorCode::OK; + } + + errorCode stop() + { + this->triggered = false; + return errorCode::OK; + } + + inline errorCode setPeriod(uint32_t microSeconds) override; + inline uint32_t getPeriod(void); + + inline errorCode trigger(float delay) // µs + { + this->startCNT = micros(); + this->period = (uint32_t)delay; + this->triggered = true; + return errorCode::OK; + } + + float getMaxPeriod() const override { return getMaxMicros() / 1E6; } // seconds + + protected: + static constexpr float clock = F_CPU / 1'000'000.0f; + float getMaxMicros() const { return 0xF000'0000 / clock; } // don't use full range otherwise tick might miss the turnover for large periods + + uint32_t startCNT, period; + callback_t callback; + bool triggered; + bool periodic; + + inline bool tick(); + bool block = false; + + friend TCK_t; + }; + + // IMPLEMENTATION ============================================== + + template + bool TckChannel::tick() + { + static bool lock = false; + + if (!lock && period != 0 && triggered && (micros() - startCNT) >= period) + { + lock = true; + startCNT = micros(); + triggered = periodic; // i.e., stays triggerd if periodic, stops if oneShot + callback(); + lock = false; + return true; + } + return false; + } + + template + errorCode TckChannel::setPeriod(uint32_t microSeconds) + { + period = microSeconds; + return errorCode::OK; + } + + template + uint32_t TckChannel::getPeriod() + { + return period; + } + +#endif + +} // namespace TeensyTimerTool diff --git a/third-party/TeensyTimerTool/src/Teensy/TCK/TckChannelBase.h b/third-party/TeensyTimerTool/src/Teensy/TCK/TckChannelBase.h new file mode 100644 index 0000000..61990cd --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/TCK/TckChannelBase.h @@ -0,0 +1,15 @@ +#pragma once + +#include "../../ITimerChannel.h" + +namespace TeensyTimerTool +{ + class TckChannelBase : public ITimerChannel + { + public: + virtual bool tick() = 0; + virtual ~TckChannelBase() = 0; + }; + + inline TckChannelBase::~TckChannelBase() {} +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/Teensy/TMR/TMR.h b/third-party/TeensyTimerTool/src/Teensy/TMR/TMR.h new file mode 100644 index 0000000..57fac66 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/TMR/TMR.h @@ -0,0 +1,100 @@ +#pragma once + +#include "TMRChannel.h" +#include "imxrt.h" + +namespace TeensyTimerTool +{ + template + class TMR_t + { + public: + static ITimerChannel* getTimer(); + + protected: + static bool isInitialized; + static void isr(); + static callback_t callbacks[4]; + + // the following is calculated at compile time + static constexpr IRQ_NUMBER_t irq = moduleNr == 0 ? IRQ_QTIMER1 : moduleNr == 1 ? IRQ_QTIMER2 : moduleNr == 2 ? IRQ_QTIMER3 : IRQ_QTIMER4; + static IMXRT_TMR_t* const pTMR; + static IMXRT_TMR_CH_t* const pCH0; + static IMXRT_TMR_CH_t* const pCH1; + static IMXRT_TMR_CH_t* const pCH2; + static IMXRT_TMR_CH_t* const pCH3; + + static_assert(moduleNr < 4, "Module number < 4 required"); + }; + + // IMPLEMENTATION ================================================================== + + template IMXRT_TMR_t* const TMR_t::pTMR = moduleNr == 0 ? &IMXRT_TMR1 : moduleNr == 1 ? &IMXRT_TMR2 : moduleNr == 2 ? &IMXRT_TMR3 : &IMXRT_TMR4; + template IMXRT_TMR_CH_t* const TMR_t::pCH0 = &pTMR->CH[0]; + template IMXRT_TMR_CH_t* const TMR_t::pCH1 = &pTMR->CH[1]; + template IMXRT_TMR_CH_t* const TMR_t::pCH2 = &pTMR->CH[2]; + template IMXRT_TMR_CH_t* const TMR_t::pCH3 = &pTMR->CH[3]; + + template + ITimerChannel* TMR_t::getTimer() + { + if (!isInitialized) + { + for (unsigned chNr = 0; chNr < 4; chNr++) + { + pTMR->CH[chNr].CTRL = 0x0000; + callbacks[chNr] = nullptr; + } + attachInterruptVector(irq, isr); // start + NVIC_ENABLE_IRQ(irq); + isInitialized = true; + return new TMRChannel(pCH0, &callbacks[0]); + } + + for (unsigned chNr = 0; chNr < 4; chNr++) + { + IMXRT_TMR_CH_t* pCh = &pTMR->CH[chNr]; + if (pCh->CTRL == 0x0000) + { + return new TMRChannel(pCh, &callbacks[chNr]); + } + } + return nullptr; + } + + template + void TMR_t::isr() + { + // no loop to gain some time by avoiding indirections and pointer calculations + if (callbacks[0] != nullptr && pCH0->CSCTRL & TMR_CSCTRL_TCF1) + { + pCH0->CSCTRL &= ~TMR_CSCTRL_TCF1; + callbacks[0](); + } + + if (callbacks[1] != nullptr && pCH1->CSCTRL & TMR_CSCTRL_TCF1) + { + pCH1->CSCTRL &= ~TMR_CSCTRL_TCF1; + callbacks[1](); + } + + if (callbacks[2] != nullptr && pCH2->CSCTRL & TMR_CSCTRL_TCF1) + { + pCH2->CSCTRL &= ~TMR_CSCTRL_TCF1; + callbacks[2](); + } + + if (callbacks[3] != nullptr && pCH3->CSCTRL & TMR_CSCTRL_TCF1) + { + pCH3->CSCTRL &= ~TMR_CSCTRL_TCF1; + callbacks[3](); + } + asm volatile("dsb"); //wait until register changes propagated through the cache + } + + template + bool TMR_t::isInitialized = false; + + template + callback_t TMR_t::callbacks[4]; +} diff --git a/third-party/TeensyTimerTool/src/Teensy/TMR/TMRChannel.h b/third-party/TeensyTimerTool/src/Teensy/TMR/TMRChannel.h new file mode 100644 index 0000000..5ebbe06 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Teensy/TMR/TMRChannel.h @@ -0,0 +1,178 @@ +#pragma once +#include "../../ITimerChannel.h" +//#include "Arduino.h" +#include +#include "ErrorHandling/error_codes.h" +#include "config.h" +#include "imxrt.h" + +namespace TeensyTimerTool +{ + class TMRChannel : public ITimerChannel + { + public: + inline TMRChannel(IMXRT_TMR_CH_t* regs, callback_t* cbStorage); + inline virtual ~TMRChannel(); + + inline errorCode begin(callback_t cb, float tcnt, bool periodic) override; + inline errorCode start() override; + inline errorCode stop() override; + + inline errorCode trigger(float tcnt) override; + inline float getMaxPeriod() const override; + + inline errorCode setPeriod(float us) override; + inline void setPrescaler(uint32_t psc); // psc 0..7 -> prescaler: 1..128 + + protected: + IMXRT_TMR_CH_t* regs; + callback_t** pCallback = nullptr; + float pscValue; + uint32_t pscBits; + + inline float_t microsecondToCounter(const float_t us) const; + inline float_t counterToMicrosecond(const float_t cnt) const; + }; + + // IMPLEMENTATION ============================================== + + TMRChannel::TMRChannel(IMXRT_TMR_CH_t* regs, callback_t* cbStorage) + : ITimerChannel(cbStorage) + { + this->regs = regs; + setPrescaler(TMR_DEFAULT_PSC); + } + + TMRChannel::~TMRChannel() + { + } + + errorCode TMRChannel::start() + { + regs->CNTR = 0x0000; + + regs->CSCTRL &= ~TMR_CSCTRL_TCF1; + regs->CSCTRL |= TMR_CSCTRL_TCF1EN; + return errorCode::OK; + } + + errorCode TMRChannel::stop() + { + regs->CSCTRL &= ~TMR_CSCTRL_TCF1EN; + return errorCode::OK; + } + + + errorCode TMRChannel::begin(callback_t cb, float tcnt, bool periodic) + { + const float_t t = microsecondToCounter(tcnt); + uint16_t reload; + if (t > 0xFFFF) + { + postError(errorCode::periodOverflow); + reload = 0xFFFE; + } else + { + reload = (uint16_t)t - 1; + } + + regs->CTRL = 0x0000; + regs->LOAD = 0x0000; + regs->COMP1 = reload; + regs->CMPLD1 = reload; + regs->CNTR = 0x0000; + setCallback(cb); + + if (!periodic) + regs->CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(pscBits) | TMR_CTRL_ONCE | TMR_CTRL_LENGTH; + + else + regs->CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(pscBits) | TMR_CTRL_LENGTH; + + start(); + return t > 0xFFFF ? errorCode::periodOverflow : errorCode::OK; + } + + + errorCode TMRChannel::trigger(float tcnt) // quick and dirty, should be optimized + { + const float_t t = microsecondToCounter(tcnt); + uint16_t reload = t > 0xFFFF ? 0xFFFF : (uint16_t)t; + + regs->CTRL = 0x0000; + regs->LOAD = 0x0000; + regs->COMP1 = reload; + regs->CMPLD1 = reload; + regs->CNTR = 0x0000; + + regs->CSCTRL &= ~TMR_CSCTRL_TCF1; + regs->CSCTRL |= TMR_CSCTRL_TCF1EN; + + regs->CTRL = TMR_CTRL_CM(1) | TMR_CTRL_PCS(pscBits) | TMR_CTRL_ONCE | TMR_CTRL_LENGTH; + + return errorCode::OK; + } + + void TMRChannel::setPrescaler(uint32_t psc) // psc 0..7 -> prescaler: 1..128 + { + pscValue = 1 << (psc & 0b0111); + pscBits = 0b1000 | (psc & 0b0111); + } + + float TMRChannel::getMaxPeriod() const + { + return pscValue / 150'000'000.0f * 0xFFFE; + } + + // void TMRChannel::_setNextPeriod(const uint16_t cnt) + // { + // regs->CMPLD1 = cnt; + // } + + // errorCode TMRChannel::_setCurrentPeriod(const uint16_t cnt) + // { + + // regs->COMP1 = cnt; + + // //Do we need to wait some cycle for IP bus to update here / cache flush? + // //asm volatile("dsb"); + + // if (regs->CNTR > cnt) + // { + // //if counter alrready went over setted value force a triggering + // regs->CNTR = cnt; + // return errorCode::triggeredLate; + // } + + // else + // { + // return errorCode::OK; + // } + // } + + errorCode TMRChannel::setPeriod(float us) + { + //const float_t t = microsecondToCounter(us); + + // if (t <= 0xFFFF) + // { + // return _setCurrentPeriod(t); + // } else + // { + // return errorCode::periodOverflow; + // } + return errorCode::notImplemented; + } + + + float_t TMRChannel::microsecondToCounter(const float_t us) const + { + return us * 150.0f / pscValue; + } + + float_t TMRChannel::counterToMicrosecond(const float_t cnt) const + { + return cnt * pscValue / 150.0f; + } + +} // namespace TeensyTimerTool diff --git a/third-party/TeensyTimerTool/src/TeensyTimerTool.h b/third-party/TeensyTimerTool/src/TeensyTimerTool.h new file mode 100644 index 0000000..c4cd898 --- /dev/null +++ b/third-party/TeensyTimerTool/src/TeensyTimerTool.h @@ -0,0 +1,9 @@ +#pragma once + +#include "config.h" +#include "timer.h" +#include "periodicTimer.h" +#include "oneShotTimer.h" +#include "ErrorHandling/error_handler.h" + +static_assert(TEENSYDUINO >= 150, "This library requires Teensyduino > 1.5"); \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/Timer.cpp b/third-party/TeensyTimerTool/src/Timer.cpp new file mode 100644 index 0000000..9518a39 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Timer.cpp @@ -0,0 +1,10 @@ +#include "timer.h" +#include "config.h" + +namespace TeensyTimerTool +{ + Timer::Timer(TimerGenerator* generator) + :BaseTimer(generator, true) + { + } +} diff --git a/third-party/TeensyTimerTool/src/Uno-dummy/TCK/TCK.cpp b/third-party/TeensyTimerTool/src/Uno-dummy/TCK/TCK.cpp new file mode 100644 index 0000000..dbb4cb6 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Uno-dummy/TCK/TCK.cpp @@ -0,0 +1,14 @@ +//#include "../../TeensyTimerTool.h" +#include "../../config.h" + +#if defined(HAS_TCK) and defined(UNO) + +namespace TeensyTimerTool +{ + bool TCK_t::isInitialized = false; + TckChannel* TCK_t::channels[maxTckChannels]; +} + +void yield() { TeensyTimerTool::TCK_t::tick(); } + +#endif diff --git a/third-party/TeensyTimerTool/src/Uno-dummy/TCK/TCK.h b/third-party/TeensyTimerTool/src/Uno-dummy/TCK/TCK.h new file mode 100644 index 0000000..aa64b60 --- /dev/null +++ b/third-party/TeensyTimerTool/src/Uno-dummy/TCK/TCK.h @@ -0,0 +1,79 @@ +#pragma once + +#include "TckChannel.h" +#include +#include + +namespace TeensyTimerTool +{ + constexpr unsigned maxTckChannels = 20; + + class TCK_t + { + public: + static inline ITimerChannel* getTimer(); + static inline void removeTimer(TckChannel*); + static inline void tick(); + + protected: + static bool isInitialized; + static TckChannel* channels[maxTckChannels]; + }; + + // IMPLEMENTATION ================================================================== + + ITimerChannel* TCK_t::getTimer() + { + Serial.printf("TCK getTimer()\n"); + + if (!isInitialized) + { + for (unsigned chNr = 0; chNr < maxTckChannels; chNr++) + { + channels[chNr] = nullptr; + } + isInitialized = true; + } + + for (unsigned chNr = 0; chNr < maxTckChannels; chNr++) + { + if (channels[chNr] == nullptr) + { + channels[chNr] = new TckChannel(); + return channels[chNr]; + } + } + + return nullptr; + } + + void TCK_t::removeTimer(TckChannel* channel) + { + for (unsigned chNr = 0; chNr < maxTckChannels; chNr++) + { + if (channels[chNr] == channel) + { + channels[chNr] = nullptr; + delete channel; + break; + } + } + } + + void TCK_t::tick() + { + digitalWriteFast(12,HIGH); + for(unsigned i = 0; i < maxTckChannels; i++) + { + if (channels[i] != nullptr ) + { + channels[i]->tick(); + } + } + digitalWriteFast(12,LOW); + } + + + //constexpr TimerGenerator* TCK = TCK_t::getTimer; + +} // namespace TeensyTimerTool diff --git a/third-party/TeensyTimerTool/src/Uno-dummy/TCK/TckChannel.h b/third-party/TeensyTimerTool/src/Uno-dummy/TCK/TckChannel.h new file mode 100644 index 0000000..bd3089b --- /dev/null +++ b/third-party/TeensyTimerTool/src/Uno-dummy/TCK/TckChannel.h @@ -0,0 +1,89 @@ +#pragma once + +#include "../ITimerChannel.h" +#include "../types.h" +#include "Arduino.h" + +namespace TeensyTimerTool +{ + class TCK_t; + + class TckChannel : public ITimerChannel + { + public: + inline TckChannel() { triggered = false; } + inline virtual ~TckChannel(){}; + + inline void begin(callback_t cb, unsigned period, bool periodic) + { + Serial.println("begin"); + + triggered = false; + this->periodic = periodic; + this->period = period * (F_CPU / 1'000'000); + this->callback = cb; + + startCNT = ARM_DWT_CYCCNT; + } + + inline void start() + { + Serial.println("start"); + + this->startCNT = ARM_DWT_CYCCNT; + this->triggered = true; + } + + inline void stop() + { + this->triggered = false; + } + + // inline void setPeriod(uint32_t microSeconds); + inline uint32_t getPeriod(void); + + inline void trigger(uint32_t delay) // µs + { + this->startCNT = ARM_DWT_CYCCNT; + this->period = delay * (F_CPU / 1'000'000) - 68; + this->triggered = true; + } + + protected: + uint32_t startCNT, period; + callback_t callback; + bool triggered; + bool periodic; + + inline void tick(); + bool block = false; + + friend TCK_t; + }; + + // IMPLEMENTATION ============================================== + + void TckChannel::tick() + { + static bool lock = false; + + if (!lock && period != 0 && triggered && (ARM_DWT_CYCCNT - startCNT) >= period) + { + lock = true; + startCNT = ARM_DWT_CYCCNT; + triggered = periodic; // i.e., stays triggerd if periodic, stops if oneShot + callback(); + lock = false; + } + } + + // void TckChannel::setPeriod(uint32_t microSeconds) + // { + // period = microSeconds * (F_CPU / 1'000'000) ; + // } + uint32_t TckChannel::getPeriod() + { + return period * (1'000'000.0f / F_CPU); + } + +} // namespace TeensyTimerTool diff --git a/third-party/TeensyTimerTool/src/baseTimer.cpp b/third-party/TeensyTimerTool/src/baseTimer.cpp new file mode 100644 index 0000000..3a548dd --- /dev/null +++ b/third-party/TeensyTimerTool/src/baseTimer.cpp @@ -0,0 +1,22 @@ +#include "baseTimer.h" +#include "Arduino.h" +#include "types.h" + +namespace TeensyTimerTool +{ + + BaseTimer::BaseTimer(TimerGenerator* generator, bool periodic) + : timerGenerator(generator) + { + this->timerGenerator = generator; + this->timerChannel = nullptr; + this->isPeriodic = periodic; + } + + errorCode BaseTimer::setPrescaler(int psc) + { + this->prescaler = psc; + return errorCode::OK; + } + +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/baseTimer.h b/third-party/TeensyTimerTool/src/baseTimer.h new file mode 100644 index 0000000..de8c229 --- /dev/null +++ b/third-party/TeensyTimerTool/src/baseTimer.h @@ -0,0 +1,116 @@ +#pragma once + +//#include "Arduino.h" +#include "ErrorHandling/error_codes.h" +#include "ITimerChannel.h" +#include + +#if defined(USE_TIME_LITERALS) +# include "frequency.h" +# include +using namespace std::chrono_literals; +using namespace std::chrono; +#endif + +namespace TeensyTimerTool +{ + class BaseTimer + { + public: + template + inline errorCode begin(callback_t callback, T period, bool start = true); + inline errorCode setPrescaler(int psc); + inline errorCode end(); + inline errorCode start(); + inline errorCode stop(); + + inline float getMaxPeriod() const; + + protected: + + template ::value, int>* = nullptr> + T getPeriod(T v) { return v; } + + + BaseTimer(TimerGenerator* generator, bool periodic); + + TimerGenerator* timerGenerator; + ITimerChannel* timerChannel; + bool isPeriodic; + uint32_t prescaler = 0; + +#if defined(USE_TIME_LITERALS) + public: + template + inline float getMaxDuration() const { return getMaxPeriod() * dur::period::den / dur::period::num; } + + protected: + template ::value, int>* = nullptr> + float getPeriod(T v) { return (duration_cast>(v).count()); } + + template ::value, int>* = nullptr> + float getPeriod(T v) { return 1'000'000 / duration_cast(v).count(); } +#endif + }; + + // INLINE IMPLEMENTATION ================================================ + + template + errorCode BaseTimer::begin(callback_t callback, T p, bool start) + { + auto period = getPeriod(p); + + if (callback == nullptr) return postError(errorCode::callback); + if (isPeriodic && period == 0) return postError(errorCode::reload); + + if (timerChannel == nullptr) + { + if (timerGenerator != nullptr) // use timer passed in during construction + { + timerChannel = timerGenerator(); + if (timerChannel == nullptr) return postError(errorCode::noFreeChannel); + } else //find the next free timer + { + for (unsigned i = 0; timerChannel == nullptr && i < timerCnt; i++) + { + timerChannel = timerPool[i](); + } + } + if (timerChannel == nullptr) return postError(errorCode::noFreeModule); + } + + errorCode result = timerChannel->begin(callback, period, isPeriodic); + + if (result == errorCode::OK) + { + if (isPeriodic && start) timerChannel->start(); + } + return postError(result); + } + + errorCode BaseTimer::end() + { + return postError(errorCode::notImplemented); + } + + errorCode BaseTimer::start() + { + timerChannel->start(); + return errorCode::OK; // hack, implement return value in timer interface + } + + errorCode BaseTimer::stop() + { + return postError(timerChannel->stop()); + } + + float BaseTimer::getMaxPeriod() const + { + if (timerChannel != nullptr) + { + return timerChannel->getMaxPeriod(); + } + postError(errorCode::notInitialized); + return 0; + } +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/boardDef.h b/third-party/TeensyTimerTool/src/boardDef.h new file mode 100644 index 0000000..48fcb99 --- /dev/null +++ b/third-party/TeensyTimerTool/src/boardDef.h @@ -0,0 +1,66 @@ +#pragma once +#include + +namespace TeensyTimerTool +{ + class ITimerChannel; + using TimerGenerator = ITimerChannel*(); //returns a pointer to a free timer channel or nullptr + + // TEENSYDUINO ========================================================================== + #if defined(TEENSYDUINO) + + #if defined(ARDUINO_TEENSYLC) + extern TimerGenerator *const TCK; + + #elif defined(ARDUINO_TEENSY30) + extern TimerGenerator *const FTM0, * const FTM1; + extern TimerGenerator *const TCK, * const TCK32, * const TCK64; + + #elif defined(ARDUINO_TEENSY31) || defined(ARDUINO_TEENSY32) + extern TimerGenerator *const FTM0, * const FTM1, * const FTM2; + extern TimerGenerator *const TCK, * const TCK32, * const TCK64; + + + #elif defined(ARDUINO_TEENSY35) + extern TimerGenerator *const FTM0, * const FTM1, * const FTM2, * const FTM3, * const FTM4; + extern TimerGenerator *const TCK, * const TCK32, * const TCK64; + + #elif defined(ARDUINO_TEENSY36) + extern TimerGenerator *const FTM0, *const FTM1, *const FTM2, *const FTM3, *const FTM4; + extern TimerGenerator *const TCK, * const TCK32, * const TCK64; + + #elif defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) || defined(ARDUINO_TEENSY_MICROMOD) + extern TimerGenerator *const TMR1, *const TMR2, *const TMR3, *const TMR4; + extern TimerGenerator *const GPT1, *const GPT2; + extern TimerGenerator *const PIT; + extern TimerGenerator *const TCK, * const TCK32, * const TCK64; + + #else + #error BOARD NOT SUPPORTED + #endif + + #define YIELD_NONE 0 + #define YIELD_STANDARD 1 + #define YIELD_OPTIMIZED 2 + + constexpr int PSC_AUTO = -1; + constexpr int PSC_1 = 0; + constexpr int PSC_2 = 1; + constexpr int PSC_4 = 2; + constexpr int PSC_8 = 3; + constexpr int PSC_16 = 4; + constexpr int PSC_32 = 5; + constexpr int PSC_64 = 6; + constexpr int PSC_128 = 7; + + extern void(* const tick)(); + + + // ESP32 ========================================================================== + #elif defined(ESP32) + //... + + #else + # error "Board not supported" + #endif +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/config.cpp b/third-party/TeensyTimerTool/src/config.cpp new file mode 100644 index 0000000..91e0237 --- /dev/null +++ b/third-party/TeensyTimerTool/src/config.cpp @@ -0,0 +1,88 @@ +#include "config.h" +#include "boardDef.h" + +using tick_t = void (*) (); + +#if defined(ARDUINO_TEENSY40) || defined(ARDUINO_TEENSY41) || defined(ARDUINO_TEENSY_MICROMOD) + #include "Teensy/TMR/TMR.h" + #include "Teensy/GPT/GPT.h" + #include "Teensy/PIT4/PIT.h" + #include "Teensy/TCK/TCK.h" + + namespace TeensyTimerTool + { + TimerGenerator* const TMR1 = TMR_t<0>::getTimer; + TimerGenerator* const TMR2 = TMR_t<1>::getTimer; + TimerGenerator* const TMR3 = TMR_t<2>::getTimer; + TimerGenerator* const TMR4 = TMR_t<3>::getTimer; + + TimerGenerator* const GPT1 = GPT_t<0>::getTimer; + TimerGenerator* const GPT2 = GPT_t<1>::getTimer; + + TimerGenerator* const PIT = PIT_t::getTimer; + + TimerGenerator* const TCK = TCK_t::getTimer; + TimerGenerator* const TCK32 = TCK_t::getTimer; // same as TCK + TimerGenerator* const TCK64 = TCK_t::getTimer; + + constexpr tick_t tick = &TCK_t::tick; + } + +#elif defined (ARDUINO_TEENSY35) || defined (ARDUINO_TEENSY36) + #include "Teensy/FTM/FTM.h" + #include "Teensy/TCK/TCK.h" + + namespace TeensyTimerTool + { + TimerGenerator* const TCK = TCK_t::getTimer; + TimerGenerator* const TCK32 = TCK_t::getTimer; // same as TCK + TimerGenerator* const TCK64 = TCK_t::getTimer; + + TimerGenerator* const FTM0 = FTM_t<0>::getTimer; + TimerGenerator* const FTM1 = FTM_t<1>::getTimer; + TimerGenerator* const FTM2 = FTM_t<2>::getTimer; + TimerGenerator* const FTM3 = FTM_t<3>::getTimer; + TimerGenerator* const FTM4 = FTM_t<3>::getTimer; + + constexpr tick_t tick = &TCK_t::tick; + } + +#elif defined(ARDUINO_TEENSY31) || defined (ARDUINO_TEENSY32) + #include "Teensy/FTM/FTM.h" + #include "Teensy/TCK/TCK.h" + + namespace TeensyTimerTool + { + TimerGenerator* const TCK = TCK_t::getTimer; + TimerGenerator* const TCK32 = TCK_t::getTimer; // same as TCK + TimerGenerator* const TCK64 = TCK_t::getTimer; + + TimerGenerator* const FTM0 = FTM_t<0>::getTimer; + TimerGenerator* const FTM1 = FTM_t<1>::getTimer; + TimerGenerator* const FTM2 = FTM_t<2>::getTimer; + constexpr tick_t tick = &TCK_t::tick; + } + +#elif defined(ARDUINO_TEENSY30) + #include "Teensy/FTM/FTM.h" + #include "Teensy/TCK/TCK.h" + + namespace TeensyTimerTool + { + TimerGenerator* const TCK = TCK_t::getTimer; + + TimerGenerator* const FTM0 = FTM_t<0>::getTimer; + TimerGenerator* const FTM1 = FTM_t<1>::getTimer; + constexpr tick_t tick = &TCK_t::tick; + } + +#elif defined(ARDUINO_TEENSYLC) + #include "Teensy/TCK/TCK.h" + + namespace TeensyTimerTool + { + TimerGenerator* const TCK = TCK_t::getTimer; + constexpr tick_t tick = &TCK_t::tick; + } + +#endif diff --git a/third-party/TeensyTimerTool/src/config.h b/third-party/TeensyTimerTool/src/config.h new file mode 100644 index 0000000..6dd2eff --- /dev/null +++ b/third-party/TeensyTimerTool/src/config.h @@ -0,0 +1,7 @@ +#pragma once + +#if __has_include("userConfig.h") + #include "userConfig.h" +#else + #include "defaultConfig.h" +#endif diff --git a/third-party/TeensyTimerTool/src/defaultConfig.h b/third-party/TeensyTimerTool/src/defaultConfig.h new file mode 100644 index 0000000..a05bff5 --- /dev/null +++ b/third-party/TeensyTimerTool/src/defaultConfig.h @@ -0,0 +1,91 @@ +#pragma once + +#include "boardDef.h" +namespace TeensyTimerTool +{ +//-------------------------------------------------------------------------------------------- +// Timer pool defintion +// Add, and sort and remove to define the timer pool. Timers will be allocted from left to right + +#if defined(ARDUINO_TEENSY_MICROMOD) + TimerGenerator* const timerPool[] = {GPT1, GPT2, TMR1, TMR2, TMR3, TMR4, TCK}; + +#elif defined(ARDUINO_TEENSY40) + TimerGenerator* const timerPool[] = {GPT1, GPT2, TMR1, TMR2, TMR3, TMR4, TCK}; + +#elif defined(ARDUINO_TEENSY41) + TimerGenerator* const timerPool[] = {GPT1, GPT2, TMR1, TMR2, TMR3, TMR4, TCK}; + +#elif defined(ARDUINO_TEENSY36) + TimerGenerator* const timerPool[] = {FTM0, FTM1, FTM2, FTM3, FTM4, TCK}; + +#elif defined(ARDUINO_TEENSY35) + TimerGenerator* const timerPool[] = {FTM0, FTM1, FTM2, FTM3, TCK}; + +#elif defined(ARDUINO_TEENSY31) || defined(ARDUINO_TEENSY32) + TimerGenerator* const timerPool[] = {FTM0, FTM1, FTM2, TCK}; + +#elif defined(ARDUINO_TEENSY30) + TimerGenerator* const timerPool[] = {FTM0, FTM1, TCK}; + +#elif defined(ARDUINO_TEENSYLC) + TimerGenerator* const timerPool[] = {TCK}; + +#elif defined(ESP32) + TimerGenerator* const timerPool[] = {TCK}; + +#elif defined(UNO) + TimerGenerator* const timerPool[] = {TCK}; +#endif + constexpr unsigned timerCnt = sizeof(timerPool) / sizeof(timerPool[0]); + +//-------------------------------------------------------------------------------------------- +// Default settings for various timers + +// TMR (QUAD) + constexpr int TMR_DEFAULT_PSC = PSC_128; // Allowed prescaling values: PSC_1, PSC_2, PSC_4 ... PSC_128, clock = 150MHz + +// FTM + constexpr int FTM_DEFAULT_PSC[] = // Allowed prescaling values: PSC_AUTO, PSC_1, PSC_2, PSC_4 ... PSC_128, clock = FBUS + { // (PSC_AUTO adjusts prescaler to get roughly 2 timer ticks per µs) + /*FTM0*/ PSC_AUTO, + /*FTM1*/ PSC_AUTO, + /*FTM2*/ PSC_AUTO, + /*FTM3*/ PSC_AUTO + }; + + +// GPT & PID + constexpr bool USE_GPT_PIT_150MHz = false;// changes the clock source for GPT and PIT from 24MHz (standard) to 150MHz, might have side effects! + +// TCK + constexpr unsigned NR_OF_TCK_TIMERS = 20; // How many TCK timers shall be available + + #define YIELD_TYPE YIELD_STANDARD // Select the required yield strategy from the list below + // YIELD_NONE: lib doesn't touch yield. Make sure to call TeensyTimerTool::tick as often as possible + // YIELD_STANDARD: uses the standard yield function and adds a call to TeensyTimerTool::tick(). Lots of overhead in yield... + // YIELD_OPTIMIZED: generate an optimized yield which only calls TeensyTimerTool::Tick() (recommended if you don't use SerialEvents) + + + + +//-------------------------------------------------------------------------------------------- +// Callback type +// Uncomment if you prefer function pointer callbacks instead of std::function callbacks + +// #define PLAIN_VANILLA_CALLBACKS + + +//-------------------------------------------------------------------------------------------- +// Use additionally c++14 user literals (e.g. 3.4s, 50ms ...) for time inputs +// Comment the following line if you don't want this. + + #define USE_TIME_LITERALS + + +//-------------------------------------------------------------------------------------------- +// Advanced Features +// Uncomment if you need access to advanced features + +// #define ENABLE_ADVANCED_FEATURES +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/frequency.h b/third-party/TeensyTimerTool/src/frequency.h new file mode 100644 index 0000000..832056c --- /dev/null +++ b/third-party/TeensyTimerTool/src/frequency.h @@ -0,0 +1,406 @@ +// -*- C++ -*- + +// Copyright (C) 2008-2015 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// . + +#pragma once + +#include +#include +#include +#include + +using std::common_type; +using std::enable_if; +using std::is_convertible; +using std::ratio; +using std::ratio_divide; + +namespace TeensyTimerTool +{ + template > + struct frequency; + + template + struct __frequency_common_type_wrapper + { + private: + typedef std::__static_gcd<_Period1::num, _Period2::num> __gcd_num; + typedef std::__static_gcd<_Period1::den, _Period2::den> __gcd_den; + typedef typename _CT::type __cr; + typedef ratio<__gcd_num::value, + (_Period1::den / __gcd_den::value) * _Period2::den> + __r; + + public: + typedef std::__success_type> type; + }; + + template + struct __frequency_common_type_wrapper + { + typedef std::__failure_type type; + }; +} + +namespace std +{ + template + struct common_type, TeensyTimerTool::frequency<_Rep2, _Period2>> + : public TeensyTimerTool::__frequency_common_type_wrapper>::type, _Period1, _Period2>::type + { + }; +} + +namespace TeensyTimerTool +{ + using namespace std::chrono; + + // Primary template for frequency_cast impl. + template + struct __frequency_cast_impl + { + template + static constexpr _ToDur + __cast(const frequency<_Rep, _Period>& __d) + { + typedef typename _ToDur::rep __to_rep; + return _ToDur(static_cast<__to_rep>(static_cast<_CR>(__d.count()) * static_cast<_CR>(_CF::num) / static_cast<_CR>(_CF::den))); + } + }; + + template + struct __frequency_cast_impl<_ToDur, _CF, _CR, true, true> + { + template + static constexpr _ToDur + __cast(const frequency<_Rep, _Period>& __d) + { + typedef typename _ToDur::rep __to_rep; + return _ToDur(static_cast<__to_rep>(__d.count())); + } + }; + + template + struct __frequency_cast_impl<_ToDur, _CF, _CR, true, false> + { + template + static constexpr _ToDur + __cast(const frequency<_Rep, _Period>& __d) + { + typedef typename _ToDur::rep __to_rep; + return _ToDur(static_cast<__to_rep>( + static_cast<_CR>(__d.count()) / static_cast<_CR>(_CF::den))); + } + }; + + template + struct __frequency_cast_impl<_ToDur, _CF, _CR, false, true> + { + template + static constexpr _ToDur + __cast(const frequency<_Rep, _Period>& __d) + { + typedef typename _ToDur::rep __to_rep; + return _ToDur(static_cast<__to_rep>( + static_cast<_CR>(__d.count()) * static_cast<_CR>(_CF::num))); + } + }; + + template + struct __is_frequency + : std::false_type + { + }; + + template + struct __is_frequency> + : std::true_type + { + }; + + /// duration_cast + template + constexpr typename enable_if<__is_frequency<_ToDur>::value, _ToDur>::type duration_cast(const frequency<_Rep, _Period>& __d) + { + typedef typename _ToDur::period __to_period; + typedef typename _ToDur::rep __to_rep; + typedef ratio_divide<_Period, __to_period> __cf; + typedef typename common_type<__to_rep, _Rep, intmax_t>::type __cr; + typedef __frequency_cast_impl<_ToDur, __cf, __cr, __cf::num == 1, __cf::den == 1> __dc; + + return __dc::__cast(__d); + } + + /// frequency + template + struct frequency + { + typedef _Rep rep; + typedef _Period period; + + static_assert(!__is_frequency<_Rep>::value, "rep cannot be a frequency"); + static_assert(std::chrono::__is_ratio<_Period>::value, "period must be a specialization of ratio"); + static_assert(_Period::num > 0, "period must be positive"); + + // 20.11.5.1 construction / copy / destroy + constexpr frequency() = default; + + // NB: Make constexpr implicit. This cannot be explicitly + // constexpr, as any UDT that is not a literal type with a + // constexpr copy constructor will be ill-formed. + frequency(const frequency&) = default; + + template ::value && (std::chrono::treat_as_floating_point::value || !std::chrono::treat_as_floating_point<_Rep2>::value)>::type> + constexpr explicit frequency(const _Rep2& __rep) + : __r(static_cast(__rep)) + { + } + + template ::value || (ratio_divide<_Period2, period>::den == 1 && !treat_as_floating_point<_Rep2>::value)>::type> + constexpr frequency(const frequency<_Rep2, _Period2>& __d) + : __r(duration_cast(__d).count()) {} + + ~frequency() = default; + frequency& operator=(const frequency&) = default; + + // 20.11.5.2 observer + constexpr rep count() const { return __r; } + + constexpr frequency operator+() const { return *this; } + constexpr frequency operator-() const { return frequency(-__r); } + + frequency& operator++() + { + ++__r; + return *this; + } + + frequency operator++(int) + { + return frequency(__r++); + } + + frequency& operator--() + { + --__r; + return *this; + } + + frequency + operator--(int) + { + return frequency(__r--); + } + + frequency& + operator+=(const frequency& __d) + { + __r += __d.count(); + return *this; + } + + frequency& + operator-=(const frequency& __d) + { + __r -= __d.count(); + return *this; + } + + frequency& + operator*=(const rep& __rhs) + { + __r *= __rhs; + return *this; + } + + frequency& + operator/=(const rep& __rhs) + { + __r /= __rhs; + return *this; + } + + // DR 934. + template + typename enable_if::value, + frequency&>::type + operator%=(const rep& __rhs) + { + __r %= __rhs; + return *this; + } + + template + typename enable_if::value, + frequency&>::type + operator%=(const frequency& __d) + { + __r %= __d.count(); + return *this; + } + + // 20.11.5.4 special values + static constexpr frequency + zero() + { + return frequency(duration_values::zero()); + } + + static constexpr frequency + min() + { + return frequency(duration_values::min()); + } + + static constexpr frequency + max() + { + return frequency(duration_values::max()); + } + + private: + rep __r; + }; + + template + constexpr typename common_type, frequency<_Rep2, _Period2>>::type operator+(const frequency<_Rep1, _Period1>& __lhs, const frequency<_Rep2, _Period2>& __rhs) + { + typedef frequency<_Rep1, _Period1> __dur1; + typedef frequency<_Rep2, _Period2> __dur2; + typedef typename common_type<__dur1, __dur2>::type __cd; + return __cd(__cd(__lhs).count() + __cd(__rhs).count()); + } + + template + constexpr typename common_type, frequency<_Rep2, _Period2>>::type operator-(const frequency<_Rep1, _Period1>& __lhs, const frequency<_Rep2, _Period2>& __rhs) + { + typedef frequency<_Rep1, _Period1> __dur1; + typedef frequency<_Rep2, _Period2> __dur2; + typedef typename common_type<__dur1, __dur2>::type __cd; + return __cd(__cd(__lhs).count() - __cd(__rhs).count()); + } + + template + constexpr frequency::type, _Period> operator*(const frequency<_Rep1, _Period>& __d, const _Rep2& __s) + { + typedef frequency::type, _Period> __cd; + return __cd(__cd(__d).count() * __s); + } + + template + constexpr frequency::type, _Period> operator*(const _Rep1& __s, const frequency<_Rep2, _Period>& __d) + { + return __d * __s; + } + + template + constexpr frequency::value, _Rep2>::type>::type, _Period> operator/(const frequency<_Rep1, _Period>& __d, const _Rep2& __s) + { + typedef frequency::type, _Period> __cd; + return __cd(__cd(__d).count() / __s); + } + + template + constexpr typename common_type<_Rep1, _Rep2>::type operator/(const frequency<_Rep1, _Period1>& __lhs, const frequency<_Rep2, _Period2>& __rhs) + { + typedef frequency<_Rep1, _Period1> __dur1; + typedef frequency<_Rep2, _Period2> __dur2; + typedef typename common_type<__dur1, __dur2>::type __cd; + return __cd(__lhs).count() / __cd(__rhs).count(); + } + + // DR 934. + template + constexpr frequency::value, _Rep2>::type>::type, _Period> operator%(const frequency<_Rep1, _Period>& __d, const _Rep2& __s) + { + typedef frequency::type, _Period> __cd; + return __cd(__cd(__d).count() % __s); + } + + template + constexpr typename common_type, frequency<_Rep2, _Period2>>::type operator%(const frequency<_Rep1, _Period1>& __lhs, const frequency<_Rep2, _Period2>& __rhs) + { + typedef frequency<_Rep1, _Period1> __dur1; + typedef frequency<_Rep2, _Period2> __dur2; + typedef typename common_type<__dur1, __dur2>::type __cd; + return __cd(__cd(__lhs).count() % __cd(__rhs).count()); + } + + // comparisons + template + constexpr bool operator==(const frequency<_Rep1, _Period1>& __lhs, const frequency<_Rep2, _Period2>& __rhs) + { + typedef frequency<_Rep1, _Period1> __dur1; + typedef frequency<_Rep2, _Period2> __dur2; + typedef typename common_type<__dur1, __dur2>::type __ct; + return __ct(__lhs).count() == __ct(__rhs).count(); + } + + template + constexpr bool operator<(const frequency<_Rep1, _Period1>& __lhs, const frequency<_Rep2, _Period2>& __rhs) + { + typedef frequency<_Rep1, _Period1> __dur1; + typedef frequency<_Rep2, _Period2> __dur2; + typedef typename common_type<__dur1, __dur2>::type __ct; + return __ct(__lhs).count() < __ct(__rhs).count(); + } + + template + constexpr bool operator!=(const frequency<_Rep1, _Period1>& __lhs, const frequency<_Rep2, _Period2>& __rhs) + { + return !(__lhs == __rhs); + } + + template + constexpr bool operator<=(const frequency<_Rep1, _Period1>& __lhs, const frequency<_Rep2, _Period2>& __rhs) + { + return !(__rhs < __lhs); + } + + template + constexpr bool operator>(const frequency<_Rep1, _Period1>& __lhs, const frequency<_Rep2, _Period2>& __rhs) + { + return __rhs < __lhs; + } + + template + constexpr bool operator>=(const frequency<_Rep1, _Period1>& __lhs, const frequency<_Rep2, _Period2>& __rhs) + { + return !(__lhs < __rhs); + } + + using hertz = frequency; + using kilohertz = frequency; + using megahertz = frequency; + using gigahertz = frequency; + + constexpr hertz operator""_Hz(long double hz) { return hertz{hz}; } + constexpr hertz operator""_Hz(uint64_t hz) { return hertz{hz}; } + constexpr kilohertz operator""_kHz(long double kHz) { return kilohertz{kHz}; } + constexpr kilohertz operator""_kHz(uint64_t kHz) { return kilohertz{kHz}; } + constexpr megahertz operator""_MHz(long double MHz) { return megahertz{MHz}; } + constexpr megahertz operator""_MHz(uint64_t MHz) { return megahertz{MHz}; } + constexpr gigahertz operator""_GHz(long double GHz) { return gigahertz{GHz}; } + constexpr gigahertz operator""_GHz(uint64_t GHz) { return gigahertz{GHz}; } +} diff --git a/third-party/TeensyTimerTool/src/oneShotTimer.h b/third-party/TeensyTimerTool/src/oneShotTimer.h new file mode 100644 index 0000000..a218e93 --- /dev/null +++ b/third-party/TeensyTimerTool/src/oneShotTimer.h @@ -0,0 +1,66 @@ +#pragma once + +#include "ErrorHandling/error_codes.h" +#include "baseTimer.h" +#include "type_traits" + +namespace TeensyTimerTool +{ + class OneShotTimer : public BaseTimer + { + public: + inline OneShotTimer(TimerGenerator* generator = nullptr); + + inline errorCode begin(callback_t cb); + template errorCode trigger(T delay); + template errorCode triggerDirect(T reload); + template errorCode getTriggerReload(float delay, T* reload); + + #if defined(USE_TIME_LITERALS) + template + errorCode trigger(duration _delay) + { + T delay = duration_cast(_delay).count(); + return trigger(delay); + } + #endif + }; + + // Implementation ================================================ + + OneShotTimer::OneShotTimer(TimerGenerator* generator) + : BaseTimer(generator, false) + {} + + errorCode OneShotTimer::begin(callback_t callback) + { + return BaseTimer::begin(callback, 0, false); + } + + template + errorCode OneShotTimer::trigger(T delay) + { + static_assert(std::is_integral() || std::is_floating_point(), "Only floating point or integral types allowed"); + + errorCode result; + + if (std::is_floating_point()) + result = timerChannel->trigger((float)delay); + else + result = timerChannel->trigger((uint32_t)delay); + + return result; + } + + template + errorCode OneShotTimer::triggerDirect(T reload) + { + return timerChannel->triggerDirect(reload); + } + + template + errorCode OneShotTimer::getTriggerReload(float delay, T* reload) + { + return timerChannel->getTriggerReload(delay, reload); + } +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/periodicTimer.h b/third-party/TeensyTimerTool/src/periodicTimer.h new file mode 100644 index 0000000..021d781 --- /dev/null +++ b/third-party/TeensyTimerTool/src/periodicTimer.h @@ -0,0 +1,22 @@ +#pragma once + +#include "baseTimer.h" + +namespace TeensyTimerTool +{ + class PeriodicTimer : public BaseTimer + { + public: + PeriodicTimer(TimerGenerator* generator = nullptr) + : BaseTimer(generator, true) {} + + template ::value, int>* = nullptr> + errorCode setPeriod(T p) { return postError(timerChannel->setPeriod((float)p)); } + + template ::value, int>* = nullptr> + errorCode setNextPeriod(T p) { return postError(timerChannel->setNextPeriod((float)p)); } + }; + + // IMPLEMENTATION ===================================================================== + +} \ No newline at end of file diff --git a/third-party/TeensyTimerTool/src/timer.h b/third-party/TeensyTimerTool/src/timer.h new file mode 100644 index 0000000..a6684ab --- /dev/null +++ b/third-party/TeensyTimerTool/src/timer.h @@ -0,0 +1,34 @@ +#pragma once +#include "ErrorHandling/error_codes.h" +#include "ITimerChannel.h" +#include "baseTimer.h" +#include "types.h" + +namespace TeensyTimerTool +{ + //class [[deprecated("Use PeriodicTimer or OneShotTimer instead")]] Timer : public BaseTimer + class Timer : public BaseTimer + { + public: + Timer(TimerGenerator* gen = nullptr); + + inline errorCode beginPeriodic(callback_t cb, uint32_t period) + { + isPeriodic = true; + return BaseTimer::begin(cb, period, true); + } + inline errorCode beginOneShot(callback_t cb) + { + isPeriodic = false; + return BaseTimer::begin(cb, 0, false); + } + inline void trigger(uint32_t delay); + }; + + // IMPLEMENTATION ======================================================= + + void Timer::trigger(const uint32_t delay) + { + timerChannel->trigger(delay); + } +} diff --git a/third-party/TeensyTimerTool/src/types.h b/third-party/TeensyTimerTool/src/types.h new file mode 100644 index 0000000..7157a5b --- /dev/null +++ b/third-party/TeensyTimerTool/src/types.h @@ -0,0 +1,30 @@ +#pragma once +#include "ErrorHandling/error_codes.h" +#include "config.h" + +#if not defined(PLAIN_VANILLA_CALLBACKS) + + #include + inline void std::__throw_bad_function_call() + { + while (1) {} // do whatever you want to do instead of an exception + } + namespace TeensyTimerTool + { + using callback_t = std::function; + using errorFunc_t = std::function; + + extern void attachErrFunc(errorFunc_t); + extern errorCode postError(errorCode); + } +#else + namespace TeensyTimerTool + { + using callback_t = void (*)(); + using errorFunc_t = void (*)(errorCode); + + extern void attachErrFunc(errorFunc_t); + extern errorCode postError(errorCode); + } +#endif + From d98fdd9997a6e86d406b0a7110822a43f99cf04d Mon Sep 17 00:00:00 2001 From: Holger Wirtz Date: Tue, 17 Aug 2021 06:43:23 +0200 Subject: [PATCH 3/3] Additions for parametric graphic eq. --- MicroDexed.ino | 76 +++++---- UI.hpp | 327 ++++++++++++++++++++++++-------------- UI_FX.h | 170 ++++++++++---------- UI_FX_T4.h | 168 ++++++++++---------- config.h | 51 +++--- control_sgtl5000plus.cpp | 143 +++++++++++++++++ control_sgtl5000plus.h | 80 ++++++++++ dexed_sd.cpp | 38 +++-- sgtl5000_graphic_eq.hpp | 329 --------------------------------------- 9 files changed, 699 insertions(+), 683 deletions(-) create mode 100644 control_sgtl5000plus.cpp create mode 100644 control_sgtl5000plus.h delete mode 100644 sgtl5000_graphic_eq.hpp diff --git a/MicroDexed.ino b/MicroDexed.ino index 49a6d44..4d00888 100644 --- a/MicroDexed.ino +++ b/MicroDexed.ino @@ -52,7 +52,7 @@ using namespace TeensyTimerTool; #endif #endif #ifdef SGTL5000_AUDIO_ENHANCE -#include "sgtl5000_graphic_eq.hpp" +#include "control_sgtl5000plus.h" #endif // Audio engines @@ -118,7 +118,11 @@ AudioMixer8 drum_reverb_send_mixer_l; // Outputs #if defined(TEENSY_AUDIO_BOARD) AudioOutputI2S i2s1; -AudioControlSGTL5000 sgtl5000_1; +#ifdef SGTL5000_AUDIO_ENHANCE +AudioControlSGTL5000Plus sgtl5000(7); +#else +AudioControlSGTL5000 sgtl5000; +#endif #elif defined (I2S_AUDIO_ONLY) AudioOutputI2S i2s1; #elif defined(TGA_AUDIO_BOARD) @@ -361,10 +365,6 @@ extern void sequencer(void); extern LCDMenuLib2 LCDML; #endif -#ifdef SGTL5000_AUDIO_ENHANCE -BiquadCoef graphic_eq(7); -#endif - extern void getNoteName(char* noteName, uint8_t noteNumber); PeriodicTimer timer1; extern char seq_chord_names[7][4]; @@ -413,36 +413,34 @@ void setup() AudioMemory(AUDIO_MEM); #if defined(TEENSY_AUDIO_BOARD) - sgtl5000_1.enable(); - sgtl5000_1.lineOutLevel(SGTL5000_LINEOUT_LEVEL); - sgtl5000_1.dacVolumeRamp(); - sgtl5000_1.dacVolume(1.0); - //sgtl5000_1.dacVolumeRampLinear(); - //sgtl5000_1.dacVolumeRampDisable(); - sgtl5000_1.unmuteHeadphone(); - sgtl5000_1.unmuteLineout(); - sgtl5000_1.volume(SGTL5000_HEADPHONE_VOLUME, SGTL5000_HEADPHONE_VOLUME); // Headphone volume + sgtl5000.enable(); + sgtl5000.lineOutLevel(SGTL5000_LINEOUT_LEVEL); + sgtl5000.dacVolumeRamp(); + sgtl5000.dacVolume(1.0); + //sgtl5000.dacVolumeRampLinear(); + //sgtl5000.dacVolumeRampDisable(); + sgtl5000.unmuteHeadphone(); + sgtl5000.unmuteLineout(); + sgtl5000.volume(SGTL5000_HEADPHONE_VOLUME, SGTL5000_HEADPHONE_VOLUME); // Headphone volume #ifdef SGTL5000_AUDIO_THRU - //sgtl5000_1.audioPreProcessorEnable(); - sgtl5000_1.inputSelect(AUDIO_INPUT_LINEIN); - sgtl5000_1.lineInLevel(5); - //sgtl5000_1.adcHighPassFilterEnable(); + //sgtl5000.audioPreProcessorEnable(); + sgtl5000.inputSelect(AUDIO_INPUT_LINEIN); + sgtl5000.lineInLevel(5); + //sgtl5000.adcHighPassFilterEnable(); #endif #ifdef SGTL5000_AUDIO_ENHANCE - sgtl5000_1.audioPostProcessorEnable(); - sgtl5000_1.enhanceBassEnable(); - sgtl5000_1.enhanceBass(1.0, 1.5, 0, 5); // enhanceBass(1.0, 1.0, 1, 2); // Configures the bass enhancement by setting the levels of the original stereo signal and the bass-enhanced mono level which will be mixed together. The high-pass filter may be enabled (0) or bypassed (1). - sgtl5000_1.surroundSoundEnable(); - sgtl5000_1.surroundSound(7, 3); // Configures virtual surround width from 0 (mono) to 7 (widest). select may be set to 1 (disable), 2 (mono input) or 3 (stereo input). - sgtl5000_1.autoVolumeEnable(); - sgtl5000_1.autoVolumeControl(1, 1, 1, 0.9, 0.01, 0.05); - sgtl5000_1.eqSelect(GRAPHIC_EQUALIZER); - sgtl5000_1.eqBands(EQ_BASS_DEFAULT, EQ_MIDBASS_DEFAULT, EQ_MID_DEFAULT, EQ_MIDTREBLE_DEFAULT, EQ_TREBLE_DEFAULT); + sgtl5000.audioPostProcessorEnable(); + sgtl5000.enhanceBassEnable(); + sgtl5000.enhanceBass(1.0, 1.5, 0, 5); // enhanceBass(1.0, 1.0, 1, 2); // Configures the bass enhancement by setting the levels of the original stereo signal and the bass-enhanced mono level which will be mixed together. The high-pass filter may be enabled (0) or bypassed (1). + sgtl5000.surroundSoundEnable(); + sgtl5000.surroundSound(7, 3); // Configures virtual surround width from 0 (mono) to 7 (widest). select may be set to 1 (disable), 2 (mono input) or 3 (stereo input). + sgtl5000.autoVolumeEnable(); + sgtl5000.autoVolumeControl(1, 1, 1, 0.9, 0.01, 0.05); #else - sgtl5000_1.audioProcessorDisable(); - sgtl5000_1.autoVolumeDisable(); - sgtl5000_1.surroundSoundDisable(); - sgtl5000_1.enhanceBassDisable(); + sgtl5000.audioProcessorDisable(); + sgtl5000.autoVolumeDisable(); + sgtl5000.surroundSoundDisable(); + sgtl5000.enhanceBassDisable(); #endif #ifdef DEBUG Serial.println(F("Teensy-Audio-Board enabled.")); @@ -2205,13 +2203,13 @@ void set_fx_params(void) #endif #ifdef SGTL5000_AUDIO_ENHANCE - sgtl5000_1.eqBands( - mapfloat(configuration.fx.eq_bass, EQ_BASS_MIN, EQ_BASS_MAX, -1.0, 1.0), - mapfloat(configuration.fx.eq_midbass, EQ_MIDBASS_MIN, EQ_MIDBASS_MAX, -1.0, 1.0), - mapfloat(configuration.fx.eq_mid, EQ_MID_MIN, EQ_MID_MAX, -1.0, 1.0), - mapfloat(configuration.fx.eq_midtreble, EQ_MIDTREBLE_MIN, EQ_MIDTREBLE_MAX, -1.0, 1.0), - mapfloat(configuration.fx.eq_treble, EQ_TREBLE_MIN, EQ_TREBLE_MAX, -1.0, 1.0) - ); + sgtl5000.setEQFc(1, mapfloat(configuration.fx.eq_1, EQ_1_MIN, EQ_1_MAX, -1.0, 1.0)); + sgtl5000.setEQFc(2, mapfloat(configuration.fx.eq_2, EQ_2_MIN, EQ_2_MAX, -1.0, 1.0)); + sgtl5000.setEQFc(3, mapfloat(configuration.fx.eq_3, EQ_3_MIN, EQ_3_MAX, -1.0, 1.0)); + sgtl5000.setEQFc(4, mapfloat(configuration.fx.eq_4, EQ_4_MIN, EQ_4_MAX, -1.0, 1.0)); + sgtl5000.setEQFc(5, mapfloat(configuration.fx.eq_5, EQ_5_MIN, EQ_5_MAX, -1.0, 1.0)); + sgtl5000.setEQFc(6, mapfloat(configuration.fx.eq_6, EQ_6_MIN, EQ_6_MAX, -1.0, 1.0)); + sgtl5000.setEQFc(7, mapfloat(configuration.fx.eq_7, EQ_7_MIN, EQ_7_MAX, -1.0, 1.0)); #endif init_MIDI_send_CC(); diff --git a/UI.hpp b/UI.hpp index f0588a0..88e1b3e 100644 --- a/UI.hpp +++ b/UI.hpp @@ -26,9 +26,11 @@ #ifndef _UI_HPP_ #define _UI_HPP_ +#include +#include #include "config.h" #include "disp_plus.h" - +#include "synth_dexed.h" #include "effect_modulated_delay.h" #include "effect_stereo_mono.h" #ifdef USE_PLATEREVERB @@ -36,10 +38,6 @@ #else #include "effect_freeverbf.h" #endif -#include "synth_dexed.h" - -#include -#include #define _LCDML_DISP_cols LCD_cols #define _LCDML_DISP_rows LCD_rows @@ -107,8 +105,13 @@ extern char seq_chord_names[7][4]; extern void change_disp_sd(bool d); #endif -extern AudioControlSGTL5000 sgtl5000_1; -extern AudioSynthDexed* MicroDexed[NUM_DEXED]; +#ifdef SGTL5000_AUDIO_ENHANCE +#include "control_sgtl5000plus.h" +extern AudioControlSGTL5000Plus sgtl5000; +#else +extern AudioControlSGTL5000 sgtl5000; +#endif + #if defined(USE_FX) extern AudioSynthWaveform* chorus_modulator[NUM_DEXED]; extern AudioEffectModulatedDelay* modchorus[NUM_DEXED]; @@ -305,11 +308,13 @@ void UI_func_voice_select(uint8_t param); void UI_func_sysex_send_voice(uint8_t param); void UI_func_sysex_receive_bank(uint8_t param); void UI_func_sysex_send_bank(uint8_t param); -void UI_func_eq_bass(uint8_t param); -void UI_func_eq_midbass(uint8_t param); -void UI_func_eq_mid(uint8_t param); -void UI_func_eq_midtreble(uint8_t param); -void UI_func_eq_treble(uint8_t param); +void UI_func_eq_1(uint8_t param); +void UI_func_eq_2(uint8_t param); +void UI_func_eq_3(uint8_t param); +void UI_func_eq_4(uint8_t param); +void UI_func_eq_5(uint8_t param); +void UI_func_eq_6(uint8_t param); +void UI_func_eq_7(uint8_t param); void UI_function_not_enabled(void); void UI_function_not_implemented(uint8_t param); void UI_func_favorites(uint8_t param); @@ -4784,8 +4789,8 @@ void UI_func_arpeggio(uint8_t param) if (LCDML.FUNC_setup()) // ****** SETUP ********* { encoderDir[ENC_R].reset(); - seq_temp_select_menu=0; - seq_temp_active_menu=0; + seq_temp_select_menu = 0; + seq_temp_active_menu = 0; lcd.setCursor( 0, 0); lcd.print("Oct"); lcd.setCursor(7, 0); @@ -4881,79 +4886,79 @@ void UI_func_arpeggio(uint8_t param) } } - - lcd.setCursor( 4, 0); - lcd.print(arp_oct_usersetting); - lcd.setCursor( 6, 1); - lcd.print( arp_style_names[arp_style][0] ); - lcd.print( arp_style_names[arp_style][1] ); - lcd.print( arp_style_names[arp_style][2] ); - lcd.setCursor( 11, 1); - if (arp_speed == 0)lcd.print("1/16"); else if (arp_speed == 1)lcd.print("1/8 "); - if (seq_temp_select_menu == 0) { - lcd.setCursor( 3, 0); - lcd.print("["); - lcd.setCursor( 5, 0); - lcd.print("]"); - lcd.setCursor( 5, 1); - lcd.print(" "); - lcd.setCursor( 9, 1); - lcd.print(" "); - lcd.setCursor( 10, 1); - lcd.print(" "); - lcd.setCursor( 15, 1); - lcd.print(" "); + lcd.setCursor( 4, 0); + lcd.print(arp_oct_usersetting); + lcd.setCursor( 6, 1); + lcd.print( arp_style_names[arp_style][0] ); + lcd.print( arp_style_names[arp_style][1] ); + lcd.print( arp_style_names[arp_style][2] ); + lcd.setCursor( 11, 1); + if (arp_speed == 0)lcd.print("1/16"); else if (arp_speed == 1)lcd.print("1/8 "); - } - else if (seq_temp_select_menu == 1) - { + if (seq_temp_select_menu == 0) { + lcd.setCursor( 3, 0); + lcd.print("["); + lcd.setCursor( 5, 0); + lcd.print("]"); + lcd.setCursor( 5, 1); + lcd.print(" "); + lcd.setCursor( 9, 1); + lcd.print(" "); + lcd.setCursor( 10, 1); + lcd.print(" "); + lcd.setCursor( 15, 1); + lcd.print(" "); - lcd.setCursor( 5, 1); - lcd.print("["); - lcd.setCursor( 9, 1); - lcd.print("]"); - lcd.setCursor( 3, 0); - lcd.print(" "); - lcd.setCursor( 5, 0); - lcd.print(" "); - lcd.setCursor( 11, 0); - lcd.print(" "); - lcd.setCursor( 15, 0); - lcd.print(" "); - } - else if (seq_temp_select_menu == 2) - { - lcd.setCursor( 5, 1); - lcd.print(" "); - lcd.setCursor( 9, 1); - lcd.print(" "); - lcd.setCursor( 11, 0); - lcd.print("["); - lcd.setCursor( 15, 0); - lcd.print("]"); - lcd.setCursor( 10, 1); - lcd.print(" "); - lcd.setCursor( 15, 1); - lcd.print(" "); - } - else if (seq_temp_select_menu == 3) - { - lcd.setCursor( 11, 0); - lcd.print(" "); - lcd.setCursor( 15, 0); - lcd.print(" "); - lcd.setCursor( 10, 1); - lcd.print("["); - lcd.setCursor( 15, 1); - lcd.print("]"); + } + else if (seq_temp_select_menu == 1) + { + + lcd.setCursor( 5, 1); + lcd.print("["); + lcd.setCursor( 9, 1); + lcd.print("]"); lcd.setCursor( 3, 0); - lcd.print(" "); - lcd.setCursor( 5, 0); - lcd.print(" "); + lcd.print(" "); + lcd.setCursor( 5, 0); + lcd.print(" "); + lcd.setCursor( 11, 0); + lcd.print(" "); + lcd.setCursor( 15, 0); + lcd.print(" "); + } + else if (seq_temp_select_menu == 2) + { + lcd.setCursor( 5, 1); + lcd.print(" "); + lcd.setCursor( 9, 1); + lcd.print(" "); + lcd.setCursor( 11, 0); + lcd.print("["); + lcd.setCursor( 15, 0); + lcd.print("]"); + lcd.setCursor( 10, 1); + lcd.print(" "); + lcd.setCursor( 15, 1); + lcd.print(" "); + } + else if (seq_temp_select_menu == 3) + { + lcd.setCursor( 11, 0); + lcd.print(" "); + lcd.setCursor( 15, 0); + lcd.print(" "); + lcd.setCursor( 10, 1); + lcd.print("["); + lcd.setCursor( 15, 1); + lcd.print("]"); + lcd.setCursor( 3, 0); + lcd.print(" "); + lcd.setCursor( 5, 0); + lcd.print(" "); + } + } - - } if (LCDML.FUNC_close()) // ****** STABLE END ********* { encoderDir[ENC_R].reset(); @@ -7348,14 +7353,102 @@ void UI_func_sysex_send_voice(uint8_t param) } } -void UI_func_eq_bass(uint8_t param) +void UI_func_eq_1(uint8_t param) +{ +#ifndef SGTL5000_AUDIO_ENHANCE + if (LCDML.FUNC_setup()) // ****** SETUP ********* + { + encoderDir[ENC_R].reset(); + lcd.setCursor(0, 0); + lcd.print(F("EQ 50Hz")); + lcd.setCursor(0, 1); + lcd.print(F("Not implemented.")); + } +#else + if (LCDML.FUNC_setup()) // ****** SETUP ********* + { + encoderDir[ENC_R].reset(); + lcd_special_chars(METERBAR); + } + + if (LCDML.FUNC_loop()) // ****** LOOP ********* + { + if ((LCDML.BT_checkDown() && encoderDir[ENC_R].Down()) || (LCDML.BT_checkUp() && encoderDir[ENC_R].Up())) + { + if (LCDML.BT_checkDown()) + { + configuration.fx.eq_1 = constrain(configuration.fx.eq_1 + ENCODER[ENC_R].speed(), EQ_1_MIN, EQ_1_MAX); + } + else if (LCDML.BT_checkUp()) + { + configuration.fx.eq_1 = constrain(configuration.fx.eq_1 - ENCODER[ENC_R].speed(), EQ_1_MIN, EQ_1_MAX); + } + } + lcd_display_meter_float("EQ 50Hz", configuration.fx.eq_1, 0.1, 0.0, EQ_1_MIN, EQ_1_MAX, 1, 1, false, true, true); + sgtl5000.setEQFc(1, mapfloat(configuration.fx.eq_1, EQ_1_MIN, EQ_1_MAX, -1.0, 1.0)); + } + + if (LCDML.FUNC_close()) // ****** STABLE END ********* + { + lcd_special_chars(SCROLLBAR); + encoderDir[ENC_R].reset(); + EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx.eq_1), configuration.fx.eq_1); + } +#endif +} + +void UI_func_eq_2(uint8_t param) +{ +#ifndef SGTL5000_AUDIO_ENHANCE + if (LCDML.FUNC_setup()) // ****** SETUP ********* + { + encoderDir[ENC_R].reset(); + lcd.setCursor(0, 0); + lcd.print(F("EQ 120Hz")); + lcd.setCursor(0, 1); + lcd.print(F("Not implemented.")); + } +#else + if (LCDML.FUNC_setup()) // ****** SETUP ********* + { + encoderDir[ENC_R].reset(); + lcd_special_chars(METERBAR); + } + + if (LCDML.FUNC_loop()) // ****** LOOP ********* + { + if ((LCDML.BT_checkDown() && encoderDir[ENC_R].Down()) || (LCDML.BT_checkUp() && encoderDir[ENC_R].Up())) + { + if (LCDML.BT_checkDown()) + { + configuration.fx.eq_2 = constrain(configuration.fx.eq_2 + ENCODER[ENC_R].speed(), EQ_2_MIN, EQ_2_MAX); + } + else if (LCDML.BT_checkUp()) + { + configuration.fx.eq_2 = constrain(configuration.fx.eq_2 - ENCODER[ENC_R].speed(), EQ_2_MIN, EQ_2_MAX); + } + } + lcd_display_meter_float("EQ 120Hz", configuration.fx.eq_2, 0.1, 0.0, EQ_2_MIN, EQ_2_MAX, 1, 1, false, true, true); + sgtl5000.setEQFc(2, mapfloat(configuration.fx.eq_2, EQ_2_MIN, EQ_2_MAX, -1.0, 1.0)); + } + + if (LCDML.FUNC_close()) // ****** STABLE END ********* + { + lcd_special_chars(SCROLLBAR); + encoderDir[ENC_R].reset(); + EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx.eq_2), configuration.fx.eq_2); + } +#endif +} + +void UI_func_eq_3(uint8_t param) { #ifndef SGTL5000_AUDIO_ENHANCE if (LCDML.FUNC_setup()) // ****** SETUP ********* { encoderDir[ENC_R].reset(); lcd.setCursor(0, 0); - lcd.print(F("EQ Bass")); + lcd.print(F("EQ 220Hz")); lcd.setCursor(0, 1); lcd.print(F("Not implemented.")); } @@ -7372,34 +7465,34 @@ void UI_func_eq_bass(uint8_t param) { if (LCDML.BT_checkDown()) { - configuration.fx.eq_bass = constrain(configuration.fx.eq_bass + ENCODER[ENC_R].speed(), EQ_BASS_MIN, EQ_BASS_MAX); + configuration.fx.eq_3 = constrain(configuration.fx.eq_3 + ENCODER[ENC_R].speed(), EQ_3_MIN, EQ_3_MAX); } else if (LCDML.BT_checkUp()) { - configuration.fx.eq_bass = constrain(configuration.fx.eq_bass - ENCODER[ENC_R].speed(), EQ_BASS_MIN, EQ_BASS_MAX); + configuration.fx.eq_3 = constrain(configuration.fx.eq_3 - ENCODER[ENC_R].speed(), EQ_3_MIN, EQ_3_MAX); } } - lcd_display_meter_float("EQ Bass", configuration.fx.eq_bass, 0.1, 0.0, EQ_BASS_MIN, EQ_BASS_MAX, 1, 1, false, true, true); - sgtl5000_1.eqBand(0, mapfloat(configuration.fx.eq_bass, EQ_BASS_MIN, EQ_BASS_MAX, -1.0, 1.0)); + lcd_display_meter_float("EQ 220Hz", configuration.fx.eq_3, 0.1, 0.0, EQ_3_MIN, EQ_3_MAX, 1, 1, false, true, true); + sgtl5000.setEQFc(3, mapfloat(configuration.fx.eq_3, EQ_3_MIN, EQ_3_MAX, -1.0, 1.0)); } if (LCDML.FUNC_close()) // ****** STABLE END ********* { lcd_special_chars(SCROLLBAR); encoderDir[ENC_R].reset(); - EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx.eq_bass), configuration.fx.eq_bass); + EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx.eq_3), configuration.fx.eq_3); } #endif } -void UI_func_eq_midbass(uint8_t param) +void UI_func_eq_4(uint8_t param) { #ifndef SGTL5000_AUDIO_ENHANCE if (LCDML.FUNC_setup()) // ****** SETUP ********* { encoderDir[ENC_R].reset(); lcd.setCursor(0, 0); - lcd.print(F("EQ MidBass")); + lcd.print(F("EQ 1000Hz")); lcd.setCursor(0, 1); lcd.print(F("Not implemented.")); } @@ -7416,34 +7509,34 @@ void UI_func_eq_midbass(uint8_t param) { if (LCDML.BT_checkDown()) { - configuration.fx.eq_midbass = constrain(configuration.fx.eq_midbass + ENCODER[ENC_R].speed(), EQ_MIDBASS_MIN, EQ_MIDBASS_MAX); + configuration.fx.eq_4 = constrain(configuration.fx.eq_4 + ENCODER[ENC_R].speed(), EQ_4_MIN, EQ_4_MAX); } else if (LCDML.BT_checkUp()) { - configuration.fx.eq_midbass = constrain(configuration.fx.eq_midbass - ENCODER[ENC_R].speed(), EQ_MIDBASS_MIN, EQ_MIDBASS_MAX); + configuration.fx.eq_4 = constrain(configuration.fx.eq_4 - ENCODER[ENC_R].speed(), EQ_4_MIN, EQ_4_MAX); } } - lcd_display_meter_float("EQ MidBass", configuration.fx.eq_midbass, 0.1, 0.0, EQ_MIDBASS_MIN, EQ_MIDBASS_MAX, 1, 1, false, true, true); - sgtl5000_1.eqBand(1, mapfloat(configuration.fx.eq_midbass, EQ_MIDBASS_MIN, EQ_MIDBASS_MAX, -1.0, 1.0)); + lcd_display_meter_float("EQ 1000Hz", configuration.fx.eq_4, 0.1, 0.0, EQ_4_MIN, EQ_4_MAX, 1, 1, false, true, true); + sgtl5000.setEQFc(4, mapfloat(configuration.fx.eq_4, EQ_4_MIN, EQ_4_MAX, -1.0, 1.0)); } if (LCDML.FUNC_close()) // ****** STABLE END ********* { lcd_special_chars(SCROLLBAR); encoderDir[ENC_R].reset(); - EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx.eq_midbass), configuration.fx.eq_midbass); + EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx.eq_4), configuration.fx.eq_4); } #endif } -void UI_func_eq_mid(uint8_t param) +void UI_func_eq_5(uint8_t param) { #ifndef SGTL5000_AUDIO_ENHANCE if (LCDML.FUNC_setup()) // ****** SETUP ********* { encoderDir[ENC_R].reset(); lcd.setCursor(0, 0); - lcd.print(F("EQ Mid")); + lcd.print(F("EQ 2000Hz")); lcd.setCursor(0, 1); lcd.print(F("Not implemented.")); } @@ -7460,34 +7553,34 @@ void UI_func_eq_mid(uint8_t param) { if (LCDML.BT_checkDown()) { - configuration.fx.eq_mid = constrain(configuration.fx.eq_mid + ENCODER[ENC_R].speed(), EQ_MID_MIN, EQ_MID_MAX); + configuration.fx.eq_5 = constrain(configuration.fx.eq_5 + ENCODER[ENC_R].speed(), EQ_5_MIN, EQ_5_MAX); } else if (LCDML.BT_checkUp()) { - configuration.fx.eq_mid = constrain(configuration.fx.eq_mid - ENCODER[ENC_R].speed(), EQ_MID_MIN, EQ_MID_MAX); + configuration.fx.eq_5 = constrain(configuration.fx.eq_5 - ENCODER[ENC_R].speed(), EQ_5_MIN, EQ_5_MAX); } } - lcd_display_meter_float("EQ Mid", configuration.fx.eq_mid, 0.1, 0.0, EQ_MID_MIN, EQ_MID_MAX, 1, 1, false, true, true); - sgtl5000_1.eqBand(2, mapfloat(configuration.fx.eq_mid, EQ_MID_MIN, EQ_MID_MAX, -1.0, 1.0)); + lcd_display_meter_float("EQ 2000Hz", configuration.fx.eq_5, 0.1, 0.0, EQ_5_MIN, EQ_5_MAX, 1, 1, false, true, true); + sgtl5000.setEQFc(5, mapfloat(configuration.fx.eq_5, EQ_5_MIN, EQ_5_MAX, -1.0, 1.0)); } if (LCDML.FUNC_close()) // ****** STABLE END ********* { lcd_special_chars(SCROLLBAR); encoderDir[ENC_R].reset(); - EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx.eq_mid), configuration.fx.eq_mid); + EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx.eq_5), configuration.fx.eq_5); } #endif } -void UI_func_eq_midtreble(uint8_t param) +void UI_func_eq_6(uint8_t param) { #ifndef SGTL5000_AUDIO_ENHANCE if (LCDML.FUNC_setup()) // ****** SETUP ********* { encoderDir[ENC_R].reset(); lcd.setCursor(0, 0); - lcd.print(F("EQ MidTreble")); + lcd.print(F("EQ 7000Hz")); lcd.setCursor(0, 1); lcd.print(F("Not implemented.")); } @@ -7504,34 +7597,34 @@ void UI_func_eq_midtreble(uint8_t param) { if (LCDML.BT_checkDown()) { - configuration.fx.eq_midtreble = constrain(configuration.fx.eq_midtreble + ENCODER[ENC_R].speed(), EQ_MIDTREBLE_MIN, EQ_MIDTREBLE_MAX); + configuration.fx.eq_6 = constrain(configuration.fx.eq_6 + ENCODER[ENC_R].speed(), EQ_6_MIN, EQ_6_MAX); } else if (LCDML.BT_checkUp()) { - configuration.fx.eq_midtreble = constrain(configuration.fx.eq_midtreble - ENCODER[ENC_R].speed(), EQ_MIDTREBLE_MIN, EQ_MIDTREBLE_MAX); + configuration.fx.eq_6 = constrain(configuration.fx.eq_6 - ENCODER[ENC_R].speed(), EQ_6_MIN, EQ_6_MAX); } } - lcd_display_meter_float("EQ MidTreble", configuration.fx.eq_midtreble, 0.1, 0.0, EQ_MIDTREBLE_MIN, EQ_MIDTREBLE_MAX, 1, 1, false, true, true); - sgtl5000_1.eqBand(3, mapfloat(configuration.fx.eq_midtreble, EQ_MIDTREBLE_MIN, EQ_MIDTREBLE_MAX, -1.0, 1.0)); + lcd_display_meter_float("EQ 7000Hz", configuration.fx.eq_6, 0.1, 0.0, EQ_6_MIN, EQ_6_MAX, 1, 1, false, true, true); + sgtl5000.setEQFc(6, mapfloat(configuration.fx.eq_6, EQ_6_MIN, EQ_6_MAX, -1.0, 1.0)); } if (LCDML.FUNC_close()) // ****** STABLE END ********* { lcd_special_chars(SCROLLBAR); encoderDir[ENC_R].reset(); - EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx.eq_midtreble), configuration.fx.eq_midtreble); + EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx.eq_6), configuration.fx.eq_6); } #endif } -void UI_func_eq_treble(uint8_t param) +void UI_func_eq_7(uint8_t param) { #ifndef SGTL5000_AUDIO_ENHANCE if (LCDML.FUNC_setup()) // ****** SETUP ********* { encoderDir[ENC_R].reset(); lcd.setCursor(0, 0); - lcd.print(F("EQ Treble")); + lcd.print(F("EQ 10000Hz")); lcd.setCursor(0, 1); lcd.print(F("Not implemented.")); } @@ -7548,22 +7641,22 @@ void UI_func_eq_treble(uint8_t param) { if (LCDML.BT_checkDown()) { - configuration.fx.eq_treble = constrain(configuration.fx.eq_treble + ENCODER[ENC_R].speed(), EQ_TREBLE_MIN, EQ_TREBLE_MAX); + configuration.fx.eq_7 = constrain(configuration.fx.eq_7 + ENCODER[ENC_R].speed(), EQ_7_MIN, EQ_7_MAX); } else if (LCDML.BT_checkUp()) { - configuration.fx.eq_treble = constrain(configuration.fx.eq_treble - ENCODER[ENC_R].speed(), EQ_TREBLE_MIN, EQ_TREBLE_MAX); + configuration.fx.eq_7 = constrain(configuration.fx.eq_7 - ENCODER[ENC_R].speed(), EQ_7_MIN, EQ_7_MAX); } } - lcd_display_meter_float("EQ Treble", configuration.fx.eq_treble, 0.1, 0.0, EQ_TREBLE_MIN, EQ_TREBLE_MAX, 1, 1, false, true, true); - sgtl5000_1.eqBand(4, mapfloat(configuration.fx.eq_treble, EQ_TREBLE_MIN, EQ_TREBLE_MAX, -1.0, 1.0)); + lcd_display_meter_float("EQ 10000Hz", configuration.fx.eq_7, 0.1, 0.0, EQ_7_MIN, EQ_7_MAX, 1, 1, false, true, true); + sgtl5000.setEQFc(7, mapfloat(configuration.fx.eq_7, EQ_7_MIN, EQ_7_MAX, -1.0, 1.0)); } if (LCDML.FUNC_close()) // ****** STABLE END ********* { lcd_special_chars(SCROLLBAR); encoderDir[ENC_R].reset(); - EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx.eq_treble), configuration.fx.eq_treble); + EEPROM.update(EEPROM_START_ADDRESS + offsetof(configuration_s, fx.eq_7), configuration.fx.eq_7); } #endif } diff --git a/UI_FX.h b/UI_FX.h index 9be0d1f..7831762 100644 --- a/UI_FX.h +++ b/UI_FX.h @@ -54,88 +54,90 @@ LCDML_add(20, LCDML_0_1_2_3_4, 2, "Damping", UI_func_reverb_damping); LCDML_add(21, LCDML_0_1_2_3_4, 3, "Level", UI_func_reverb_level); LCDML_add(22, LCDML_0_1_2_3_4, 4, "Reverb Send", UI_func_reverb_send); LCDML_add(23, LCDML_0_1_2_3, 5, "EQ", NULL); -LCDML_add(24, LCDML_0_1_2_3_5, 1, "Bass", UI_func_eq_bass); -LCDML_add(25, LCDML_0_1_2_3_5, 2, "MidBass", UI_func_eq_midbass); -LCDML_add(26, LCDML_0_1_2_3_5, 3, "Mid", UI_func_eq_mid); -LCDML_add(27, LCDML_0_1_2_3_5, 4, "MidTreble", UI_func_eq_midtreble); -LCDML_add(28, LCDML_0_1_2_3_5, 5, "Treble", UI_func_eq_treble); -LCDML_add(29, LCDML_0_1, 3, "Controller", NULL); -LCDML_add(30, LCDML_0_1_3, 1, "Pitchbend", NULL); -LCDML_add(31, LCDML_0_1_3_1, 1, "PB Range", UI_func_pb_range); -LCDML_add(32, LCDML_0_1_3_1, 2, "PB Step", UI_func_pb_step); -LCDML_add(33, LCDML_0_1_3, 2, "Mod Wheel", NULL); -LCDML_add(34, LCDML_0_1_3_2, 1, "MW Range", UI_func_mw_range); -LCDML_add(35, LCDML_0_1_3_2, 2, "MW Assign", UI_func_mw_assign); -LCDML_add(36, LCDML_0_1_3_2, 3, "MW Mode", UI_func_mw_mode); -LCDML_add(37, LCDML_0_1_3, 3, "Aftertouch", NULL); -LCDML_add(38, LCDML_0_1_3_3, 1, "AT Range", UI_func_at_range); -LCDML_add(39, LCDML_0_1_3_3, 2, "AT Assign", UI_func_at_assign); -LCDML_add(40, LCDML_0_1_3_3, 3, "AT Mode", UI_func_at_mode); -LCDML_add(41, LCDML_0_1_3, 4, "Foot Ctrl", NULL); -LCDML_add(42, LCDML_0_1_3_4, 1, "FC Range", UI_func_fc_range); -LCDML_add(43, LCDML_0_1_3_4, 2, "FC Assign", UI_func_fc_assign); -LCDML_add(44, LCDML_0_1_3_4, 3, "FC Mode", UI_func_fc_mode); -LCDML_add(45, LCDML_0_1_3, 5, "Breath Ctrl", NULL); -LCDML_add(46, LCDML_0_1_3_5, 1, "BC Range", UI_func_bc_range); -LCDML_add(47, LCDML_0_1_3_5, 2, "BC Assign", UI_func_bc_assign); -LCDML_add(48, LCDML_0_1_3_5, 3, "BC Mode", UI_func_bc_mode); -LCDML_add(49, LCDML_0_1, 4, "MIDI", NULL); -LCDML_add(50, LCDML_0_1_4, 1, "MIDI Channel", UI_func_midi_channel); -LCDML_add(51, LCDML_0_1_4, 2, "Lowest Note", UI_func_lowest_note); -LCDML_add(52, LCDML_0_1_4, 3, "Highest Note", UI_func_highest_note); -LCDML_add(53, LCDML_0_1_4, 4, "MIDI Send Voice", UI_func_sysex_send_voice); -LCDML_add(54, LCDML_0_1, 5, "Setup", NULL); -LCDML_add(55, LCDML_0_1_5, 1, "Portamento", NULL); -LCDML_add(56, LCDML_0_1_5_1, 1, "Port. Mode", UI_func_portamento_mode); -LCDML_add(57, LCDML_0_1_5_1, 2, "Port. Gliss", UI_func_portamento_glissando); -LCDML_add(58, LCDML_0_1_5_1, 3, "Port. Time", UI_func_portamento_time); -LCDML_add(59, LCDML_0_1_5, 2, "Polyphony", UI_func_polyphony); -LCDML_add(60, LCDML_0_1_5, 3, "Transpose", UI_func_transpose); -LCDML_add(61, LCDML_0_1_5, 4, "Fine Tune", UI_func_tune); -LCDML_add(62, LCDML_0_1_5, 5, "Mono/Poly", UI_func_mono_poly); -LCDML_add(63, LCDML_0_1, 6, "Internal", NULL); -LCDML_add(64, LCDML_0_1_6, 1, "Note Refresh", UI_func_note_refresh); -LCDML_add(65, LCDML_0_1_6, 2, "Velocity Lvl", UI_func_velocity_level); -LCDML_add(66, LCDML_0_1, 7, "Operator", UI_handle_OP); -LCDML_add(67, LCDML_0_1, 8, "Save Voice", UI_func_save_voice); -LCDML_add(68, LCDML_0, 3, "Load/Save", NULL); -LCDML_add(69, LCDML_0_3, 1, "Performance", NULL); -LCDML_add(70, LCDML_0_3_1, 1, "Load Perf.", UI_func_load_performance); -LCDML_add(71, LCDML_0_3_1, 2, "Save Perf.", UI_func_save_performance); -LCDML_add(72, LCDML_0_3, 2, "Voice Config", NULL); -LCDML_add(73, LCDML_0_3_2, 1, "Load Voice Cfg", UI_func_load_voiceconfig); -LCDML_add(74, LCDML_0_3_2, 2, "Save Voice Cfg", UI_func_save_voiceconfig); -LCDML_add(75, LCDML_0_3, 3, "Effects", NULL); -LCDML_add(76, LCDML_0_3_3, 1, "Load Effects", UI_func_load_fx); -LCDML_add(77, LCDML_0_3_3, 2, "Save Effects", UI_func_save_fx); -LCDML_add(78, LCDML_0_3, 5, "MIDI", NULL); -LCDML_add(79, LCDML_0_3_5, 1, "MIDI Recv Bank", UI_func_sysex_receive_bank); -LCDML_add(80, LCDML_0_3_5, 2, "MIDI Snd Bank", UI_func_sysex_send_bank); -LCDML_add(81, LCDML_0_3_5, 3, "MIDI Snd Voice", UI_func_sysex_send_voice); -LCDML_add(82, LCDML_0, 4, "Drums", NULL); -LCDML_add(83, LCDML_0_4, 1, "Drums Main Vol", UI_func_drum_main_volume); -LCDML_add(84, LCDML_0_4, 2, "Drum Volumes", UI_func_drum_volume); -LCDML_add(85, LCDML_0_4, 3, "Drum Pan", UI_func_drum_pan); -LCDML_add(86, LCDML_0_4, 4, "Drum Rev.Send", UI_func_drum_reverb_send); -LCDML_add(87, LCDML_0, 5, "Sequencer", NULL); -LCDML_add(88, LCDML_0_5, 1, "Sequencer", UI_func_sequencer); -LCDML_add(89, LCDML_0_5, 2, "Vel./Chrd Edit", UI_func_seq_vel_editor); -LCDML_add(90, LCDML_0_5, 3, "Pattern Chain", UI_func_seq_pat_chain); -LCDML_add(91, LCDML_0_5, 4, "Arpeggio", UI_func_arpeggio); -LCDML_add(92, LCDML_0_5, 5, "Seq. Length", UI_func_seq_lenght); -LCDML_add(93, LCDML_0_5, 6, "Tempo", UI_func_seq_tempo); -LCDML_add(94, LCDML_0_5, 7, "L.Transp.Key", UI_func_seq_live_transpose_oct); -LCDML_add(95, LCDML_0_5, 8, "L.Trp.Offset", NULL); -LCDML_add(96, LCDML_0_5, 9, "Track Setup", UI_func_seq_track_setup); -LCDML_add(97, LCDML_0_5, 10, "Seq.Disp.Style", UI_func_seq_display_style); -LCDML_add(98, LCDML_0_5, 11, "LOAD Patterns", UI_func_seq_pattern_load); -LCDML_add(99, LCDML_0_5, 12, "SAVE Patterns", UI_func_seq_pattern_save); -LCDML_add(100, LCDML_0, 6, "System", NULL); -LCDML_add(101, LCDML_0_6, 1, "Stereo/Mono", UI_func_stereo_mono); -LCDML_add(102, LCDML_0_6, 2, "MIDI Soft THRU", UI_func_midi_soft_thru); -LCDML_add(103, LCDML_0_6, 3, "Favorites", UI_func_favorites); -LCDML_add(104, LCDML_0_6, 4, "EEPROM Reset", UI_func_eeprom_reset); -LCDML_add(105, LCDML_0, 7, "Info", UI_func_information); -LCDML_addAdvanced(106, LCDML_0, 8, COND_hide, "Volume", UI_func_volume, 0, _LCDML_TYPE_default); -#define _LCDML_DISP_cnt 106 +LCDML_add(24, LCDML_0_1_2_3_5, 1, "50Hz", UI_func_eq_1); +LCDML_add(25, LCDML_0_1_2_3_5, 2, "120Hz", UI_func_eq_2); +LCDML_add(26, LCDML_0_1_2_3_5, 3, "220Hz", UI_func_eq_3); +LCDML_add(27, LCDML_0_1_2_3_5, 4, "1000Hz", UI_func_eq_4); +LCDML_add(28, LCDML_0_1_2_3_5, 5, "2000Hz", UI_func_eq_5); +LCDML_add(29, LCDML_0_1_2_3_5, 6, "7000Hz", UI_func_eq_6); +LCDML_add(30, LCDML_0_1_2_3_5, 7, "10000Hz", UI_func_eq_7); +LCDML_add(31, LCDML_0_1, 3, "Controller", NULL); +LCDML_add(32, LCDML_0_1_3, 1, "Pitchbend", NULL); +LCDML_add(33, LCDML_0_1_3_1, 1, "PB Range", UI_func_pb_range); +LCDML_add(34, LCDML_0_1_3_1, 2, "PB Step", UI_func_pb_step); +LCDML_add(35, LCDML_0_1_3, 2, "Mod Wheel", NULL); +LCDML_add(36, LCDML_0_1_3_2, 1, "MW Range", UI_func_mw_range); +LCDML_add(37, LCDML_0_1_3_2, 2, "MW Assign", UI_func_mw_assign); +LCDML_add(38, LCDML_0_1_3_2, 3, "MW Mode", UI_func_mw_mode); +LCDML_add(39, LCDML_0_1_3, 3, "Aftertouch", NULL); +LCDML_add(40, LCDML_0_1_3_3, 1, "AT Range", UI_func_at_range); +LCDML_add(41, LCDML_0_1_3_3, 2, "AT Assign", UI_func_at_assign); +LCDML_add(42, LCDML_0_1_3_3, 3, "AT Mode", UI_func_at_mode); +LCDML_add(43, LCDML_0_1_3, 4, "Foot Ctrl", NULL); +LCDML_add(44, LCDML_0_1_3_4, 1, "FC Range", UI_func_fc_range); +LCDML_add(45, LCDML_0_1_3_4, 2, "FC Assign", UI_func_fc_assign); +LCDML_add(46, LCDML_0_1_3_4, 3, "FC Mode", UI_func_fc_mode); +LCDML_add(47, LCDML_0_1_3, 5, "Breath Ctrl", NULL); +LCDML_add(48, LCDML_0_1_3_5, 1, "BC Range", UI_func_bc_range); +LCDML_add(49, LCDML_0_1_3_5, 2, "BC Assign", UI_func_bc_assign); +LCDML_add(50, LCDML_0_1_3_5, 3, "BC Mode", UI_func_bc_mode); +LCDML_add(51, LCDML_0_1, 4, "MIDI", NULL); +LCDML_add(52, LCDML_0_1_4, 1, "MIDI Channel", UI_func_midi_channel); +LCDML_add(53, LCDML_0_1_4, 2, "Lowest Note", UI_func_lowest_note); +LCDML_add(54, LCDML_0_1_4, 3, "Highest Note", UI_func_highest_note); +LCDML_add(55, LCDML_0_1_4, 4, "MIDI Send Voice", UI_func_sysex_send_voice); +LCDML_add(56, LCDML_0_1, 5, "Setup", NULL); +LCDML_add(57, LCDML_0_1_5, 1, "Portamento", NULL); +LCDML_add(58, LCDML_0_1_5_1, 1, "Port. Mode", UI_func_portamento_mode); +LCDML_add(59, LCDML_0_1_5_1, 2, "Port. Gliss", UI_func_portamento_glissando); +LCDML_add(60, LCDML_0_1_5_1, 3, "Port. Time", UI_func_portamento_time); +LCDML_add(61, LCDML_0_1_5, 2, "Polyphony", UI_func_polyphony); +LCDML_add(62, LCDML_0_1_5, 3, "Transpose", UI_func_transpose); +LCDML_add(63, LCDML_0_1_5, 4, "Fine Tune", UI_func_tune); +LCDML_add(64, LCDML_0_1_5, 5, "Mono/Poly", UI_func_mono_poly); +LCDML_add(65, LCDML_0_1, 6, "Internal", NULL); +LCDML_add(66, LCDML_0_1_6, 1, "Note Refresh", UI_func_note_refresh); +LCDML_add(67, LCDML_0_1_6, 2, "Velocity Lvl", UI_func_velocity_level); +LCDML_add(68, LCDML_0_1, 7, "Operator", UI_handle_OP); +LCDML_add(69, LCDML_0_1, 8, "Save Voice", UI_func_save_voice); +LCDML_add(70, LCDML_0, 3, "Load/Save", NULL); +LCDML_add(71, LCDML_0_3, 1, "Performance", NULL); +LCDML_add(72, LCDML_0_3_1, 1, "Load Perf.", UI_func_load_performance); +LCDML_add(73, LCDML_0_3_1, 2, "Save Perf.", UI_func_save_performance); +LCDML_add(74, LCDML_0_3, 2, "Voice Config", NULL); +LCDML_add(75, LCDML_0_3_2, 1, "Load Voice Cfg", UI_func_load_voiceconfig); +LCDML_add(76, LCDML_0_3_2, 2, "Save Voice Cfg", UI_func_save_voiceconfig); +LCDML_add(77, LCDML_0_3, 3, "Effects", NULL); +LCDML_add(78, LCDML_0_3_3, 1, "Load Effects", UI_func_load_fx); +LCDML_add(79, LCDML_0_3_3, 2, "Save Effects", UI_func_save_fx); +LCDML_add(80, LCDML_0_3, 5, "MIDI", NULL); +LCDML_add(81, LCDML_0_3_5, 1, "MIDI Recv Bank", UI_func_sysex_receive_bank); +LCDML_add(82, LCDML_0_3_5, 2, "MIDI Snd Bank", UI_func_sysex_send_bank); +LCDML_add(83, LCDML_0_3_5, 3, "MIDI Snd Voice", UI_func_sysex_send_voice); +LCDML_add(84, LCDML_0, 4, "Drums", NULL); +LCDML_add(85, LCDML_0_4, 1, "Drums Main Vol", UI_func_drum_main_volume); +LCDML_add(86, LCDML_0_4, 2, "Drum Volumes", UI_func_drum_volume); +LCDML_add(87, LCDML_0_4, 3, "Drum Pan", UI_func_drum_pan); +LCDML_add(88, LCDML_0_4, 4, "Drum Rev.Send", UI_func_drum_reverb_send); +LCDML_add(89, LCDML_0, 5, "Sequencer", NULL); +LCDML_add(90, LCDML_0_5, 1, "Sequencer", UI_func_sequencer); +LCDML_add(91, LCDML_0_5, 2, "Vel./Chrd Edit", UI_func_seq_vel_editor); +LCDML_add(92, LCDML_0_5, 3, "Pattern Chain", UI_func_seq_pat_chain); +LCDML_add(93, LCDML_0_5, 4, "Arpeggio", UI_func_arpeggio); +LCDML_add(94, LCDML_0_5, 5, "Seq. Length", UI_func_seq_lenght); +LCDML_add(95, LCDML_0_5, 6, "Tempo", UI_func_seq_tempo); +LCDML_add(96, LCDML_0_5, 7, "L.Transp.Key", UI_func_seq_live_transpose_oct); +LCDML_add(97, LCDML_0_5, 8, "L.Trp.Offset", NULL); +LCDML_add(98, LCDML_0_5, 9, "Track Setup", UI_func_seq_track_setup); +LCDML_add(99, LCDML_0_5, 10, "Seq.Disp.Style", UI_func_seq_display_style); +LCDML_add(100, LCDML_0_5, 11, "LOAD Patterns", UI_func_seq_pattern_load); +LCDML_add(101, LCDML_0_5, 12, "SAVE Patterns", UI_func_seq_pattern_save); +LCDML_add(102, LCDML_0, 6, "System", NULL); +LCDML_add(103, LCDML_0_6, 1, "Stereo/Mono", UI_func_stereo_mono); +LCDML_add(104, LCDML_0_6, 2, "MIDI Soft THRU", UI_func_midi_soft_thru); +LCDML_add(105, LCDML_0_6, 3, "Favorites", UI_func_favorites); +LCDML_add(106, LCDML_0_6, 4, "EEPROM Reset", UI_func_eeprom_reset); +LCDML_add(107, LCDML_0, 7, "Info", UI_func_information); +LCDML_addAdvanced(108, LCDML_0, 8, COND_hide, "Volume", UI_func_volume, 0, _LCDML_TYPE_default); +#define _LCDML_DISP_cnt 108 #endif diff --git a/UI_FX_T4.h b/UI_FX_T4.h index 7519e5c..c89096f 100644 --- a/UI_FX_T4.h +++ b/UI_FX_T4.h @@ -57,87 +57,89 @@ LCDML_add(23, LCDML_0_1_2_3_4, 5, "Diffusion", UI_func_reverb_diffusion); LCDML_add(24, LCDML_0_1_2_3_4, 6, "Level", UI_func_reverb_level); LCDML_add(25, LCDML_0_1_2_3_4, 7, "Reverb Send", UI_func_reverb_send); LCDML_add(26, LCDML_0_1_2_3, 5, "EQ", NULL); -LCDML_add(27, LCDML_0_1_2_3_5, 1, "Bass", UI_func_eq_bass); -LCDML_add(28, LCDML_0_1_2_3_5, 2, "MidBass", UI_func_eq_midbass); -LCDML_add(29, LCDML_0_1_2_3_5, 3, "Mid", UI_func_eq_mid); -LCDML_add(30, LCDML_0_1_2_3_5, 4, "MidTreble", UI_func_eq_midtreble); -LCDML_add(31, LCDML_0_1_2_3_5, 5, "Treble", UI_func_eq_treble); -LCDML_add(32, LCDML_0_1, 3, "Controller", NULL); -LCDML_add(33, LCDML_0_1_3, 1, "Pitchbend", NULL); -LCDML_add(34, LCDML_0_1_3_1, 1, "PB Range", UI_func_pb_range); -LCDML_add(35, LCDML_0_1_3_1, 2, "PB Step", UI_func_pb_step); -LCDML_add(36, LCDML_0_1_3, 2, "Mod Wheel", NULL); -LCDML_add(37, LCDML_0_1_3_2, 1, "MW Range", UI_func_mw_range); -LCDML_add(38, LCDML_0_1_3_2, 2, "MW Assign", UI_func_mw_assign); -LCDML_add(39, LCDML_0_1_3_2, 3, "MW Mode", UI_func_mw_mode); -LCDML_add(40, LCDML_0_1_3, 3, "Aftertouch", NULL); -LCDML_add(41, LCDML_0_1_3_3, 1, "AT Range", UI_func_at_range); -LCDML_add(42, LCDML_0_1_3_3, 2, "AT Assign", UI_func_at_assign); -LCDML_add(43, LCDML_0_1_3_3, 3, "AT Mode", UI_func_at_mode); -LCDML_add(44, LCDML_0_1_3, 4, "Foot Ctrl", NULL); -LCDML_add(45, LCDML_0_1_3_4, 1, "FC Range", UI_func_fc_range); -LCDML_add(46, LCDML_0_1_3_4, 2, "FC Assign", UI_func_fc_assign); -LCDML_add(47, LCDML_0_1_3_4, 3, "FC Mode", UI_func_fc_mode); -LCDML_add(48, LCDML_0_1_3, 5, "Breath Ctrl", NULL); -LCDML_add(49, LCDML_0_1_3_5, 1, "BC Range", UI_func_bc_range); -LCDML_add(50, LCDML_0_1_3_5, 2, "BC Assign", UI_func_bc_assign); -LCDML_add(51, LCDML_0_1_3_5, 3, "BC Mode", UI_func_bc_mode); -LCDML_add(52, LCDML_0_1, 4, "MIDI", NULL); -LCDML_add(53, LCDML_0_1_4, 1, "MIDI Channel", UI_func_midi_channel); -LCDML_add(54, LCDML_0_1_4, 2, "Lowest Note", UI_func_lowest_note); -LCDML_add(55, LCDML_0_1_4, 3, "Highest Note", UI_func_highest_note); -LCDML_add(56, LCDML_0_1_4, 4, "MIDI Send Voice", UI_func_sysex_send_voice); -LCDML_add(57, LCDML_0_1, 5, "Setup", NULL); -LCDML_add(58, LCDML_0_1_5, 1, "Portamento", NULL); -LCDML_add(59, LCDML_0_1_5_1, 1, "Port. Mode", UI_func_portamento_mode); -LCDML_add(60, LCDML_0_1_5_1, 2, "Port. Gliss", UI_func_portamento_glissando); -LCDML_add(61, LCDML_0_1_5_1, 3, "Port. Time", UI_func_portamento_time); -LCDML_add(62, LCDML_0_1_5, 2, "Polyphony", UI_func_polyphony); -LCDML_add(63, LCDML_0_1_5, 3, "Transpose", UI_func_transpose); -LCDML_add(64, LCDML_0_1_5, 4, "Fine Tune", UI_func_tune); -LCDML_add(65, LCDML_0_1_5, 5, "Mono/Poly", UI_func_mono_poly); -LCDML_add(66, LCDML_0_1, 6, "Internal", NULL); -LCDML_add(67, LCDML_0_1_6, 1, "Note Refresh", UI_func_note_refresh); -LCDML_add(68, LCDML_0_1_6, 2, "Velocity Lvl", UI_func_velocity_level); -LCDML_add(69, LCDML_0_1, 7, "Operator", UI_handle_OP); -LCDML_add(70, LCDML_0_1, 8, "Save Voice", UI_func_save_voice); -LCDML_add(71, LCDML_0, 3, "Load/Save", NULL); -LCDML_add(72, LCDML_0_3, 1, "Performance", NULL); -LCDML_add(73, LCDML_0_3_1, 1, "Load Perf.", UI_func_load_performance); -LCDML_add(74, LCDML_0_3_1, 2, "Save Perf.", UI_func_save_performance); -LCDML_add(75, LCDML_0_3, 2, "Voice Config", NULL); -LCDML_add(76, LCDML_0_3_2, 1, "Load Voice Cfg", UI_func_load_voiceconfig); -LCDML_add(77, LCDML_0_3_2, 2, "Save Voice Cfg", UI_func_save_voiceconfig); -LCDML_add(78, LCDML_0_3, 3, "Effects", NULL); -LCDML_add(79, LCDML_0_3_3, 1, "Load Effects", UI_func_load_fx); -LCDML_add(80, LCDML_0_3_3, 2, "Save Effects", UI_func_save_fx); -LCDML_add(81, LCDML_0_3, 5, "MIDI", NULL); -LCDML_add(82, LCDML_0_3_5, 1, "MIDI Recv Bank", UI_func_sysex_receive_bank); -LCDML_add(83, LCDML_0_3_5, 2, "MIDI Snd Bank", UI_func_sysex_send_bank); -LCDML_add(84, LCDML_0_3_5, 3, "MIDI Snd Voice", UI_func_sysex_send_voice); -LCDML_add(85, LCDML_0, 4, "Drums", NULL); -LCDML_add(86, LCDML_0_4, 1, "Drums Main Vol", UI_func_drum_main_volume); -LCDML_add(87, LCDML_0_4, 2, "Drum Volumes", UI_func_drum_volume); -LCDML_add(88, LCDML_0_4, 3, "Drum Pan", UI_func_drum_pan); -LCDML_add(89, LCDML_0_4, 4, "Drum Rev.Send", UI_func_drum_reverb_send); -LCDML_add(90, LCDML_0, 5, "Sequencer", NULL); -LCDML_add(91, LCDML_0_5, 1, "Sequencer", UI_func_sequencer); -LCDML_add(92, LCDML_0_5, 2, "Vel./Chrd Edit", UI_func_seq_vel_editor); -LCDML_add(93, LCDML_0_5, 3, "Pattern Chain", UI_func_seq_pat_chain); -LCDML_add(94, LCDML_0_5, 4, "Arpeggio", UI_func_arpeggio); -LCDML_add(95, LCDML_0_5, 5, "Seq. Length", UI_func_seq_lenght); -LCDML_add(96, LCDML_0_5, 6, "Tempo", UI_func_seq_tempo); -LCDML_add(97, LCDML_0_5, 7, "L.Transp.Key", UI_func_seq_live_transpose_oct); -LCDML_add(98, LCDML_0_5, 8, "Track Setup", UI_func_seq_track_setup); -LCDML_add(99, LCDML_0_5, 9, "Seq.Disp.Style", UI_func_seq_display_style); -LCDML_add(100, LCDML_0_5, 10, "LOAD Patterns", UI_func_seq_pattern_load); -LCDML_add(101, LCDML_0_5, 11, "SAVE Patterns", UI_func_seq_pattern_save); -LCDML_add(102, LCDML_0, 6, "System", NULL); -LCDML_add(103, LCDML_0_6, 1, "Stereo/Mono", UI_func_stereo_mono); -LCDML_add(104, LCDML_0_6, 2, "MIDI Soft THRU", UI_func_midi_soft_thru); -LCDML_add(105, LCDML_0_6, 3, "Favorites", UI_func_favorites); -LCDML_add(106, LCDML_0_6, 4, "EEPROM Reset", UI_func_eeprom_reset); -LCDML_add(107, LCDML_0, 7, "Info", UI_func_information); -LCDML_addAdvanced(108, LCDML_0, 8, COND_hide, "Volume", UI_func_volume, 0, _LCDML_TYPE_default); -#define _LCDML_DISP_cnt 108 +LCDML_add(27, LCDML_0_1_2_3_5, 1, "50Hz", UI_func_eq_1); +LCDML_add(28, LCDML_0_1_2_3_5, 2, "110Hz", UI_func_eq_2); +LCDML_add(29, LCDML_0_1_2_3_5, 3, "220Hz", UI_func_eq_3); +LCDML_add(30, LCDML_0_1_2_3_5, 4, "1000Hz", UI_func_eq_4); +LCDML_add(31, LCDML_0_1_2_3_5, 5, "2000Hz", UI_func_eq_5); +LCDML_add(32, LCDML_0_1_2_3_5, 6, "7000Hz", UI_func_eq_6); +LCDML_add(33, LCDML_0_1_2_3_5, 7, "10000Hz", UI_func_eq_7); +LCDML_add(34, LCDML_0_1, 3, "Controller", NULL); +LCDML_add(35, LCDML_0_1_3, 1, "Pitchbend", NULL); +LCDML_add(36, LCDML_0_1_3_1, 1, "PB Range", UI_func_pb_range); +LCDML_add(37, LCDML_0_1_3_1, 2, "PB Step", UI_func_pb_step); +LCDML_add(38, LCDML_0_1_3, 2, "Mod Wheel", NULL); +LCDML_add(39, LCDML_0_1_3_2, 1, "MW Range", UI_func_mw_range); +LCDML_add(40, LCDML_0_1_3_2, 2, "MW Assign", UI_func_mw_assign); +LCDML_add(41, LCDML_0_1_3_2, 3, "MW Mode", UI_func_mw_mode); +LCDML_add(42, LCDML_0_1_3, 3, "Aftertouch", NULL); +LCDML_add(43, LCDML_0_1_3_3, 1, "AT Range", UI_func_at_range); +LCDML_add(44, LCDML_0_1_3_3, 2, "AT Assign", UI_func_at_assign); +LCDML_add(45, LCDML_0_1_3_3, 3, "AT Mode", UI_func_at_mode); +LCDML_add(46, LCDML_0_1_3, 4, "Foot Ctrl", NULL); +LCDML_add(47, LCDML_0_1_3_4, 1, "FC Range", UI_func_fc_range); +LCDML_add(48, LCDML_0_1_3_4, 2, "FC Assign", UI_func_fc_assign); +LCDML_add(49, LCDML_0_1_3_4, 3, "FC Mode", UI_func_fc_mode); +LCDML_add(50, LCDML_0_1_3, 5, "Breath Ctrl", NULL); +LCDML_add(51, LCDML_0_1_3_5, 1, "BC Range", UI_func_bc_range); +LCDML_add(52, LCDML_0_1_3_5, 2, "BC Assign", UI_func_bc_assign); +LCDML_add(53, LCDML_0_1_3_5, 3, "BC Mode", UI_func_bc_mode); +LCDML_add(54, LCDML_0_1, 4, "MIDI", NULL); +LCDML_add(55, LCDML_0_1_4, 1, "MIDI Channel", UI_func_midi_channel); +LCDML_add(56, LCDML_0_1_4, 2, "Lowest Note", UI_func_lowest_note); +LCDML_add(57, LCDML_0_1_4, 3, "Highest Note", UI_func_highest_note); +LCDML_add(58, LCDML_0_1_4, 4, "MIDI Send Voice", UI_func_sysex_send_voice); +LCDML_add(59, LCDML_0_1, 5, "Setup", NULL); +LCDML_add(60, LCDML_0_1_5, 1, "Portamento", NULL); +LCDML_add(61, LCDML_0_1_5_1, 1, "Port. Mode", UI_func_portamento_mode); +LCDML_add(62, LCDML_0_1_5_1, 2, "Port. Gliss", UI_func_portamento_glissando); +LCDML_add(63, LCDML_0_1_5_1, 3, "Port. Time", UI_func_portamento_time); +LCDML_add(64, LCDML_0_1_5, 2, "Polyphony", UI_func_polyphony); +LCDML_add(65, LCDML_0_1_5, 3, "Transpose", UI_func_transpose); +LCDML_add(66, LCDML_0_1_5, 4, "Fine Tune", UI_func_tune); +LCDML_add(67, LCDML_0_1_5, 5, "Mono/Poly", UI_func_mono_poly); +LCDML_add(68, LCDML_0_1, 6, "Internal", NULL); +LCDML_add(69, LCDML_0_1_6, 1, "Note Refresh", UI_func_note_refresh); +LCDML_add(70, LCDML_0_1_6, 2, "Velocity Lvl", UI_func_velocity_level); +LCDML_add(71, LCDML_0_1, 7, "Operator", UI_handle_OP); +LCDML_add(72, LCDML_0_1, 8, "Save Voice", UI_func_save_voice); +LCDML_add(73, LCDML_0, 3, "Load/Save", NULL); +LCDML_add(74, LCDML_0_3, 1, "Performance", NULL); +LCDML_add(75, LCDML_0_3_1, 1, "Load Perf.", UI_func_load_performance); +LCDML_add(76, LCDML_0_3_1, 2, "Save Perf.", UI_func_save_performance); +LCDML_add(77, LCDML_0_3, 2, "Voice Config", NULL); +LCDML_add(78, LCDML_0_3_2, 1, "Load Voice Cfg", UI_func_load_voiceconfig); +LCDML_add(79, LCDML_0_3_2, 2, "Save Voice Cfg", UI_func_save_voiceconfig); +LCDML_add(80, LCDML_0_3, 3, "Effects", NULL); +LCDML_add(81, LCDML_0_3_3, 1, "Load Effects", UI_func_load_fx); +LCDML_add(82, LCDML_0_3_3, 2, "Save Effects", UI_func_save_fx); +LCDML_add(83, LCDML_0_3, 5, "MIDI", NULL); +LCDML_add(84, LCDML_0_3_5, 1, "MIDI Recv Bank", UI_func_sysex_receive_bank); +LCDML_add(85, LCDML_0_3_5, 2, "MIDI Snd Bank", UI_func_sysex_send_bank); +LCDML_add(86, LCDML_0_3_5, 3, "MIDI Snd Voice", UI_func_sysex_send_voice); +LCDML_add(87, LCDML_0, 4, "Drums", NULL); +LCDML_add(88, LCDML_0_4, 1, "Drums Main Vol", UI_func_drum_main_volume); +LCDML_add(89, LCDML_0_4, 2, "Drum Volumes", UI_func_drum_volume); +LCDML_add(90, LCDML_0_4, 3, "Drum Pan", UI_func_drum_pan); +LCDML_add(91, LCDML_0_4, 4, "Drum Rev.Send", UI_func_drum_reverb_send); +LCDML_add(92, LCDML_0, 5, "Sequencer", NULL); +LCDML_add(93, LCDML_0_5, 1, "Sequencer", UI_func_sequencer); +LCDML_add(94, LCDML_0_5, 2, "Vel./Chrd Edit", UI_func_seq_vel_editor); +LCDML_add(95, LCDML_0_5, 3, "Pattern Chain", UI_func_seq_pat_chain); +LCDML_add(96, LCDML_0_5, 4, "Arpeggio", UI_func_arpeggio); +LCDML_add(97, LCDML_0_5, 5, "Seq. Length", UI_func_seq_lenght); +LCDML_add(98, LCDML_0_5, 6, "Tempo", UI_func_seq_tempo); +LCDML_add(99, LCDML_0_5, 7, "L.Transp.Key", UI_func_seq_live_transpose_oct); +LCDML_add(100, LCDML_0_5, 8, "Track Setup", UI_func_seq_track_setup); +LCDML_add(101, LCDML_0_5, 9, "Seq.Disp.Style", UI_func_seq_display_style); +LCDML_add(102, LCDML_0_5, 10, "LOAD Patterns", UI_func_seq_pattern_load); +LCDML_add(103, LCDML_0_5, 11, "SAVE Patterns", UI_func_seq_pattern_save); +LCDML_add(104, LCDML_0, 6, "System", NULL); +LCDML_add(105, LCDML_0_6, 1, "Stereo/Mono", UI_func_stereo_mono); +LCDML_add(106, LCDML_0_6, 2, "MIDI Soft THRU", UI_func_midi_soft_thru); +LCDML_add(107, LCDML_0_6, 3, "Favorites", UI_func_favorites); +LCDML_add(108, LCDML_0_6, 4, "EEPROM Reset", UI_func_eeprom_reset); +LCDML_add(109, LCDML_0, 7, "Info", UI_func_information); +LCDML_addAdvanced(110, LCDML_0, 8, COND_hide, "Volume", UI_func_volume, 0, _LCDML_TYPE_default); +#define _LCDML_DISP_cnt 110 #endif diff --git a/config.h b/config.h index 2c92167..05dc6eb 100644 --- a/config.h +++ b/config.h @@ -28,7 +28,6 @@ #include #include "midinotes.h" #include "teensy_board_detection.h" -#include "sgtl5000_graphic_eq.hpp" // If you want to test the system with Linux and without any keyboard and/or audio equipment, you can do the following: // 1. In Arduino-IDE enable "Tools->USB-Type->Serial + MIDI + Audio" @@ -605,25 +604,33 @@ #define VOICECONFIG_NUM_MAX MAX_VOICECONFIG #define VOICECONFIG_NUM_DEFAULT -1 -#define EQ_BASS_MIN -10 -#define EQ_BASS_MAX 10 -#define EQ_BASS_DEFAULT 0 +#define EQ_1_MIN -10 +#define EQ_1_MAX 10 +#define EQ_1_DEFAULT 0 -#define EQ_MIDBASS_MIN -10 -#define EQ_MIDBASS_MAX 10 -#define EQ_MIDBASS_DEFAULT 0 +#define EQ_2_MIN -10 +#define EQ_2_MAX 10 +#define EQ_2_DEFAULT 0 -#define EQ_MID_MIN -10 -#define EQ_MID_MAX 10 -#define EQ_MID_DEFAULT 0 +#define EQ_3_MIN -10 +#define EQ_3_MAX 10 +#define EQ_3_DEFAULT 0 -#define EQ_MIDTREBLE_MIN -10 -#define EQ_MIDTREBLE_MAX 10 -#define EQ_MIDTREBLE_DEFAULT 0 +#define EQ_4_MIN -10 +#define EQ_4_MAX 10 +#define EQ_4_DEFAULT 0 -#define EQ_TREBLE_MIN -10 -#define EQ_TREBLE_MAX 10 -#define EQ_TREBLE_DEFAULT 0 +#define EQ_5_MIN -10 +#define EQ_5_MAX 10 +#define EQ_5_DEFAULT 0 + +#define EQ_6_MIN -10 +#define EQ_6_MAX 10 +#define EQ65_DEFAULT 0 + +#define EQ_7_MIN -10 +#define EQ_7_MAX 10 +#define EQ_7_DEFAULT 0 // Buffer for load/save configuration as JSON @@ -681,11 +688,13 @@ typedef struct fx_s { uint8_t reverb_hidamp; uint8_t reverb_diffusion; uint8_t reverb_level; - int8_t eq_bass; - int8_t eq_midbass; - int8_t eq_mid; - int8_t eq_midtreble; - int8_t eq_treble; + int8_t eq_1; + int8_t eq_2; + int8_t eq_3; + int8_t eq_4; + int8_t eq_5; + int8_t eq_6; + int8_t eq_7; } fx_t; typedef struct performance_s { diff --git a/control_sgtl5000plus.cpp b/control_sgtl5000plus.cpp new file mode 100644 index 0000000..5a06484 --- /dev/null +++ b/control_sgtl5000plus.cpp @@ -0,0 +1,143 @@ +/* + MicroDexed + + MicroDexed is a port of the Dexed sound engine + (https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6/4.x with audio shield. + Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android + + (c)2018-2021 H. Wirtz + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include +#include +#include "control_sgtl5000plus.h" + +void AudioControlSGTL5000Plus::init_parametric_eq(void) +{ + eqSelect(PARAMETRIC_EQUALIZER); + eqFilterCount(num_bands); + + filter_type = new uint8_t[num_bands]; + Fc = new float[num_bands]; + Q = new float[num_bands]; + peakGainDB = new float[num_bands]; + + setEQType(1, GRAPHIC_EQ_TYPE_0); + setEQFc(1, GRAPHIC_EQ_CENTER_FRQ_0); + setEQQ(1, GRAPHIC_EQ_Q_0); + setEQGain(1, 0.0); + + if (num_bands > 1) + { + setEQType(2, GRAPHIC_EQ_TYPE_1); + setEQFc(2, GRAPHIC_EQ_CENTER_FRQ_1); + setEQQ(2, GRAPHIC_EQ_Q_1); + setEQGain(2, 0.0); + } + + if (num_bands > 2) + { + setEQType(3, GRAPHIC_EQ_TYPE_2); + setEQFc(3, GRAPHIC_EQ_CENTER_FRQ_2); + setEQQ(3, GRAPHIC_EQ_Q_2); + setEQGain(3, 0.0); + } + + if (num_bands > 3) + { + setEQType(4, GRAPHIC_EQ_TYPE_3); + setEQFc(4, GRAPHIC_EQ_CENTER_FRQ_3); + setEQQ(4, GRAPHIC_EQ_Q_3); + setEQGain(4, 0.0); + } + + if (num_bands > 4) + { + setEQType(5, GRAPHIC_EQ_TYPE_4); + setEQFc(5, GRAPHIC_EQ_CENTER_FRQ_4); + setEQQ(5, GRAPHIC_EQ_Q_4); + setEQGain(5, 0.0); + } + + if (num_bands > 5) + { + setEQType(6, GRAPHIC_EQ_TYPE_5); + setEQFc(6, GRAPHIC_EQ_CENTER_FRQ_5); + setEQQ(6, GRAPHIC_EQ_Q_5); + setEQGain(6, 0.0); + } + + if (num_bands > 6) + { + setEQType(7, GRAPHIC_EQ_TYPE_6); + setEQFc(7, GRAPHIC_EQ_CENTER_FRQ_6); + setEQQ(7, GRAPHIC_EQ_Q_6); + setEQGain(7, 0.0); + } +} + +void AudioControlSGTL5000Plus::setEQType(uint8_t band, uint8_t ft) +{ + if (filter_type) + { + int filter[5]; + + band = constrain(band, 1, num_bands); + filter_type[band - 1] = ft; + calcBiquad(filter_type[band - 1], Fc[band - 1], peakGainDB[band - 1], Q[band - 1], 524288, AUDIO_SAMPLE_RATE, filter); + //eqFilter(band, filter); + } +} + +void AudioControlSGTL5000Plus::setEQFc(uint8_t band, float frq) +{ + if (Fc) + { + int filter[5]; + + band = constrain(band, 1, num_bands); + Fc[band - 1] = frq; + calcBiquad(filter_type[band - 1], Fc[band - 1], peakGainDB[band - 1], Q[band - 1], 524288, AUDIO_SAMPLE_RATE, filter); + //eqFilter(band, filter); + } +} + +void AudioControlSGTL5000Plus::setEQQ(uint8_t band, float q) +{ + if (Q) + { + int filter[5]; + + band = constrain(band, 1, num_bands); + Q[band - 1] = q; + calcBiquad(filter_type[band - 1], Fc[band - 1], peakGainDB[band - 1], Q[band - 1], 524288, AUDIO_SAMPLE_RATE, filter); + //eqFilter(band, filter); + } +} + +void AudioControlSGTL5000Plus::setEQGain(uint8_t band, float gain) +{ + if (peakGainDB) + { + int filter[5]; + + band = constrain(band, 1, num_bands); + peakGainDB[band - 1] = gain; + calcBiquad(filter_type[band - 1], Fc[band - 1], peakGainDB[band - 1], Q[band - 1], 524288, AUDIO_SAMPLE_RATE, filter); + //eqFilter(band, filter); + } +} diff --git a/control_sgtl5000plus.h b/control_sgtl5000plus.h new file mode 100644 index 0000000..3e840f1 --- /dev/null +++ b/control_sgtl5000plus.h @@ -0,0 +1,80 @@ +/* + MicroDexed + + MicroDexed is a port of the Dexed sound engine + (https://github.com/asb2m10/dexed) for the Teensy-3.5/3.6/4.x with audio shield. + Dexed ist heavily based on https://github.com/google/music-synthesizer-for-android + + (c)2018-2021 H. Wirtz + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _SGTL5000PLUS_H_ +#define _SGTL5000PLUS_H_ + +#include +#include + +#define GRAPHIC_EQ_TYPE_0 FILTER_HIPASS +#define GRAPHIC_EQ_CENTER_FRQ_0 50.0 +#define GRAPHIC_EQ_Q_0 6.0 + +#define GRAPHIC_EQ_TYPE_1 FILTER_BANDPASS +#define GRAPHIC_EQ_CENTER_FRQ_1 120.0 +#define GRAPHIC_EQ_Q_1 6.0 + +#define GRAPHIC_EQ_TYPE_2 FILTER_BANDPASS +#define GRAPHIC_EQ_CENTER_FRQ_2 220.0 +#define GRAPHIC_EQ_Q_2 6.0 + +#define GRAPHIC_EQ_TYPE_3 FILTER_BANDPASS +#define GRAPHIC_EQ_CENTER_FRQ_3 1000.0 +#define GRAPHIC_EQ_Q_3 6.0 + +#define GRAPHIC_EQ_TYPE_4 FILTER_BANDPASS +#define GRAPHIC_EQ_CENTER_FRQ_4 2000.0 +#define GRAPHIC_EQ_Q_4 6.0 + +#define GRAPHIC_EQ_TYPE_5 FILTER_BANDPASS +#define GRAPHIC_EQ_CENTER_FRQ_5 7000.0 +#define GRAPHIC_EQ_Q_5 2.0 + +#define GRAPHIC_EQ_TYPE_6 FILTER_LOPASS +#define GRAPHIC_EQ_CENTER_FRQ_6 10000.0 +#define GRAPHIC_EQ_Q_6 6.0 + +class AudioControlSGTL5000Plus : public AudioControlSGTL5000 +{ + public: + AudioControlSGTL5000Plus(uint8_t n = 7) { + num_bands = constrain(n, 1, 7); + init_parametric_eq(); + }; + void setEQType(uint8_t band, uint8_t ft); + void setEQFc(uint8_t band, float frq); + void setEQQ(uint8_t band, float q); + void setEQGain(uint8_t band, float gain); + + private: + void init_parametric_eq(void); + uint8_t num_bands; + int* filter_coeff; + uint8_t* filter_type; + float* Fc; + float* Q; + float* peakGainDB; +}; +#endif diff --git a/dexed_sd.cpp b/dexed_sd.cpp index 95886dc..346e529 100644 --- a/dexed_sd.cpp +++ b/dexed_sd.cpp @@ -647,8 +647,13 @@ bool load_sd_fx_json(int8_t fx) configuration.fx.reverb_hidamp = data_json["reverb_hidamp"]; configuration.fx.reverb_diffusion = data_json["reverb_diffusion"]; configuration.fx.reverb_level = data_json["reverb_level"]; - configuration.fx.eq_bass = data_json["eq_bass"]; - configuration.fx.eq_treble = data_json["eq_treble"]; + configuration.fx.eq_1 = data_json["eq_1"]; + configuration.fx.eq_2 = data_json["eq_2"]; + configuration.fx.eq_3 = data_json["eq_3"]; + configuration.fx.eq_4 = data_json["eq_4"]; + configuration.fx.eq_5 = data_json["eq_5"]; + configuration.fx.eq_6 = data_json["eq_6"]; + configuration.fx.eq_7 = data_json["eq_7"]; set_fx_params(); @@ -719,9 +724,13 @@ bool save_sd_fx_json(uint8_t fx) data_json["reverb_hidamp"] = configuration.fx.reverb_hidamp; data_json["reverb_diffusion"] = configuration.fx.reverb_diffusion; data_json["reverb_level"] = configuration.fx.reverb_level; - data_json["eq_bass"] = configuration.fx.eq_bass; - data_json["eq_treble"] = configuration.fx.eq_treble; - + data_json["eq_1"] = configuration.fx.eq_1; + data_json["eq_2"] = configuration.fx.eq_2; + data_json["eq_3"] = configuration.fx.eq_3; + data_json["eq_4"] = configuration.fx.eq_4; + data_json["eq_5"] = configuration.fx.eq_5; + data_json["eq_6"] = configuration.fx.eq_6; + data_json["eq_7"] = configuration.fx.eq_7; #ifdef DEBUG Serial.println(F("Write JSON data:")); serializeJsonPretty(data_json, Serial); @@ -839,9 +848,13 @@ bool save_sd_seq_json(uint8_t seq_number) data_json["reverb_hidamp"] = configuration.fx.reverb_hidamp; data_json["reverb_diffusion"] = configuration.fx.reverb_diffusion; data_json["reverb_level"] = configuration.fx.reverb_level; - data_json["eq_bass"] = configuration.fx.eq_bass; - data_json["eq_treble"] = configuration.fx.eq_treble; - + data_json["eq_1"] = configuration.fx.eq_1; + data_json["eq_2"] = configuration.fx.eq_2; + data_json["eq_3"] = configuration.fx.eq_3; + data_json["eq_4"] = configuration.fx.eq_4; + data_json["eq_5"] = configuration.fx.eq_5; + data_json["eq_6"] = configuration.fx.eq_6; + data_json["eq_7"] = configuration.fx.eq_7; #ifdef DEBUG Serial.println(F("Write JSON data:")); serializeJsonPretty(data_json, Serial); @@ -961,8 +974,13 @@ bool load_sd_seq_json(uint8_t seq_number) configuration.fx.reverb_hidamp = data_json["reverb_hidamp"]; configuration.fx.reverb_diffusion = data_json["reverb_diffusion"]; configuration.fx.reverb_level = data_json["reverb_level"]; - configuration.fx.eq_bass = data_json["eq_bass"]; - configuration.fx.eq_treble = data_json["eq_treble"]; + configuration.fx.eq_1 = data_json["eq_1"]; + configuration.fx.eq_2 = data_json["eq_2"]; + configuration.fx.eq_3 = data_json["eq_3"]; + configuration.fx.eq_4 = data_json["eq_4"]; + configuration.fx.eq_5 = data_json["eq_5"]; + configuration.fx.eq_6 = data_json["eq_6"]; + configuration.fx.eq_7 = data_json["eq_7"]; set_fx_params(); diff --git a/sgtl5000_graphic_eq.hpp b/sgtl5000_graphic_eq.hpp deleted file mode 100644 index f6e2642..0000000 --- a/sgtl5000_graphic_eq.hpp +++ /dev/null @@ -1,329 +0,0 @@ -#ifdef SGTL5000_AUDIO_ENHANCE - -#include -#include - -#define EQ_LOWPASS 0 -#define EQ_HIGHPASS 1 -#define EQ_BANDPASS 2 -#define EQ_NOTCH 3 -#define EQ_PEAK 4 -#define EQ_LOWSHELF 5 -#define EQ_HIGHSHELF 6 - -#define GRAPHIC_EQ_TYPE_0 EQ_HIGHPASS -#define GRAPHIC_EQ_CENTER_FRQ_0 115.0 -#define GRAPHIC_EQ_Q_0 2.0 - -#define GRAPHIC_EQ_TYPE_1 EQ_BANDPASS -#define GRAPHIC_EQ_CENTER_FRQ_1 330.0 -#define GRAPHIC_EQ_Q_1 2.0 - -#define GRAPHIC_EQ_TYPE_2 EQ_BANDPASS -#define GRAPHIC_EQ_CENTER_FRQ_2 990.0 -#define GRAPHIC_EQ_Q_2 2.0 - -#define GRAPHIC_EQ_TYPE_3 EQ_BANDPASS -#define GRAPHIC_EQ_CENTER_FRQ_3 2000.0 -#define GRAPHIC_EQ_Q_3 2.0 - -#define GRAPHIC_EQ_TYPE_4 EQ_BANDPASS -#define GRAPHIC_EQ_CENTER_FRQ_4 4000.0 -#define GRAPHIC_EQ_Q_4 2.0 - -#define GRAPHIC_EQ_TYPE_5 EQ_BANDPASS -#define GRAPHIC_EQ_CENTER_FRQ_5 9900.0 -#define GRAPHIC_EQ_Q_5 2.0 - -#define GRAPHIC_EQ_TYPE_6 EQ_LOWPASS -#define GRAPHIC_EQ_CENTER_FRQ_6 11000.0 -#define GRAPHIC_EQ_Q_6 2.0 - -extern AudioControlSGTL5000 sgtl5000_1; - -class BiquadCoef -{ - public: - BiquadCoef(uint8_t num_bands); - ~BiquadCoef(); - - void set_eq_type(uint8_t band, uint8_t ft); - void set_eq_Fc(uint8_t band, float32_t frq); - void set_eq_Q(uint8_t band, float32_t q); - void set_gain(uint8_t band, float32_t gain); - void get_coef(uint8_t band, int* c); - - private: - void calcBiquadCoefficients(uint8_t band); - - uint8_t num_bands; - - uint8_t *filter_type; - float32_t *Fc; - float32_t *Q; - float32_t *peakGainDB; - - float32_t *a0; - float32_t *a1; - float32_t *a2; - float32_t *b1; - float32_t *b2; -}; - -BiquadCoef::BiquadCoef(uint8_t num_bands) -{ - num_bands = constrain(num_bands, 1, 7); - - sgtl5000_1.eqFilterCount(num_bands); - - filter_type = new uint8_t[num_bands]; - Fc = new float32_t[num_bands]; - Q = new float32_t[num_bands]; - peakGainDB = new float32_t[num_bands]; - a0 = new float32_t[num_bands]; - a1 = new float32_t[num_bands]; - a2 = new float32_t[num_bands]; - b1 = new float32_t[num_bands]; - b2 = new float32_t[num_bands]; - - set_eq_type(0, GRAPHIC_EQ_TYPE_0); - set_eq_Fc(0, GRAPHIC_EQ_CENTER_FRQ_0); - set_eq_Q(0, GRAPHIC_EQ_Q_0); - set_gain(0, 0.0); - - if (num_bands > 1) - { - set_eq_type(1, GRAPHIC_EQ_TYPE_1); - set_eq_Fc(1, GRAPHIC_EQ_CENTER_FRQ_1); - set_eq_Q(1, GRAPHIC_EQ_Q_1); - set_gain(1, 0.0); - } - - if (num_bands > 2) - { - set_eq_type(2, GRAPHIC_EQ_TYPE_2); - set_eq_Fc(2, GRAPHIC_EQ_CENTER_FRQ_2); - set_eq_Q(2, GRAPHIC_EQ_Q_2); - set_gain(2, 0.0); - } - - if (num_bands > 3) - { - set_eq_type(3, GRAPHIC_EQ_TYPE_3); - set_eq_Fc(3, GRAPHIC_EQ_CENTER_FRQ_3); - set_eq_Q(3, GRAPHIC_EQ_Q_3); - set_gain(3, 0.0); - } - - if (num_bands > 4) - { - set_eq_type(4, GRAPHIC_EQ_TYPE_4); - set_eq_Fc(4, GRAPHIC_EQ_CENTER_FRQ_4); - set_eq_Q(4, GRAPHIC_EQ_Q_4); - set_gain(4, 0.0); - } - - if (num_bands > 5) - { - set_eq_type(5, GRAPHIC_EQ_TYPE_5); - set_eq_Fc(5, GRAPHIC_EQ_CENTER_FRQ_5); - set_eq_Q(5, GRAPHIC_EQ_Q_5); - set_gain(5, 0.0); - } - - if (num_bands > 6) - { - set_eq_type(6, GRAPHIC_EQ_TYPE_6); - set_eq_Fc(6, GRAPHIC_EQ_CENTER_FRQ_6); - set_eq_Q(6, GRAPHIC_EQ_Q_6); - set_gain(6, 0.0); - } - - for (uint8_t i = 0; i < num_bands; i++) - { - int tmp[num_bands]; - - calcBiquadCoefficients(i); - get_coef(i, tmp); - sgtl5000_1.eqFilter(i, tmp); - } -} - -BiquadCoef::~BiquadCoef() -{ - ; -} - -void BiquadCoef::set_eq_type(uint8_t band, uint8_t ft) -{ - int tmp[num_bands]; - - filter_type[band] = ft; - calcBiquadCoefficients(band); - get_coef(band, tmp); - sgtl5000_1.eqFilter(band, tmp); -} - -void BiquadCoef::set_eq_Fc(uint8_t band, float32_t frq) -{ - int tmp[num_bands]; - - Fc[band] = frq; - calcBiquadCoefficients(band); - get_coef(band, tmp); - sgtl5000_1.eqFilter(band, tmp); -} - -void BiquadCoef::set_eq_Q(uint8_t band, float32_t q) -{ - int tmp[num_bands]; - - Q[band] = q; - calcBiquadCoefficients(band); - get_coef(band, tmp); - sgtl5000_1.eqFilter(band, tmp); -} - -void BiquadCoef::set_gain(uint8_t band, float32_t gain) -{ - int tmp[num_bands]; - - peakGainDB[band] = gain; - calcBiquadCoefficients(band); - get_coef(band, tmp); - sgtl5000_1.eqFilter(band, tmp); -} - -void BiquadCoef::get_coef(uint8_t band, int* c) -{ - if (c != NULL) - { - c[0] = a0[band] * 0x8000; - c[1] = a1[band] * 0x8000; - c[2] = a2[band] * 0x8000; - c[3] = b1[band] * 0x8000; - c[4] = b2[band] * 0x8000; - } -} - -// Taken from https://www.earlevel.com/main/2012/11/26/biquad-c-source-code/ -// -// Biquad.h -// -// Created by Nigel Redmon on 11/24/12 -// EarLevel Engineering: earlevel.com -// Copyright 2012 Nigel Redmon -// -// For a complete explanation of the Biquad code: -// http://www.earlevel.com/main/2012/11/25/biquad-c-source-code/ -// -// License: -// -// This source code is provided as is, without warranty. -// You may copy and distribute verbatim copies of this document. -// You may modify and use this source code to create binary code -// for your own purposes, free or commercial. -// -void BiquadCoef::calcBiquadCoefficients(uint8_t band) -{ - if (band > num_bands) - band = num_bands; - - float32_t norm; - float32_t V = pow(10, fabs(peakGainDB[band]) / 20.0); - float32_t K = tan(M_PI * Fc[band]); - switch (filter_type[band]) { - case EQ_LOWPASS: - norm = 1 / (1 + K / Q[band] + K * K); - a0[band] = K * K * norm; - a1[band] = 2 * a0[band]; - a2[band] = a0[band]; - b1[band] = 2 * (K * K - 1) * norm; - b2[band] = (1 - K / Q[band] + K * K) * norm; - break; - - case EQ_HIGHPASS: - norm = 1 / (1 + K / Q[band] + K * K); - a0[band] = 1 * norm; - a1[band] = -2 * a0[band]; - a2[band] = a0[band]; - b1[band] = 2 * (K * K - 1) * norm; - b2[band] = (1 - K / Q[band] + K * K) * norm; - break; - - case EQ_BANDPASS: - norm = 1 / (1 + K / Q[band] + K * K); - a0[band] = K / Q[band] * norm; - a1[band] = 0; - a2[band] = -a0[band]; - b1[band] = 2 * (K * K - 1) * norm; - b2[band] = (1 - K / Q[band] + K * K) * norm; - break; - - case EQ_NOTCH: - norm = 1 / (1 + K / Q[band] + K * K); - a0[band] = (1 + K * K) * norm; - a1[band] = 2 * (K * K - 1) * norm; - a2[band] = a0[band]; - b1[band] = a1[band]; - b2[band] = (1 - K / Q[band] + K * K) * norm; - break; - - case EQ_PEAK: - if (peakGainDB[band] >= 0) { // boost - norm = 1 / (1 + 1 / Q[band] * K + K * K); - a0[band] = (1 + V / Q[band] * K + K * K) * norm; - a1[band] = 2 * (K * K - 1) * norm; - a2[band] = (1 - V / Q[band] * K + K * K) * norm; - b1[band] = a1[band]; - b2[band] = (1 - 1 / Q[band] * K + K * K) * norm; - } - else { // cut - norm = 1 / (1 + V / Q[band] * K + K * K); - a0[band] = (1 + 1 / Q[band] * K + K * K) * norm; - a1[band] = 2 * (K * K - 1) * norm; - a2[band] = (1 - 1 / Q[band] * K + K * K) * norm; - b1[band] = a1[band]; - b2[band] = (1 - V / Q[band] * K + K * K) * norm; - } - break; - case EQ_LOWSHELF: - if (peakGainDB[band] >= 0) { // boost - norm = 1 / (1 + sqrt(2) * K + K * K); - a0[band] = (1 + sqrt(2 * V) * K + V * K * K) * norm; - a1[band] = 2 * (V * K * K - 1) * norm; - a2[band] = (1 - sqrt(2 * V) * K + V * K * K) * norm; - b1[band] = 2 * (K * K - 1) * norm; - b2[band] = (1 - sqrt(2) * K + K * K) * norm; - } - else { // cut - norm = 1 / (1 + sqrt(2 * V) * K + V * K * K); - a0[band] = (1 + sqrt(2) * K + K * K) * norm; - a1[band] = 2 * (K * K - 1) * norm; - a2[band] = (1 - sqrt(2) * K + K * K) * norm; - b1[band] = 2 * (V * K * K - 1) * norm; - b2[band] = (1 - sqrt(2 * V) * K + V * K * K) * norm; - } - break; - case EQ_HIGHSHELF: - if (peakGainDB[band] >= 0) { // boost - norm = 1 / (1 + sqrt(2) * K + K * K); - a0[band] = (V + sqrt(2 * V) * K + K * K) * norm; - a1[band] = 2 * (K * K - V) * norm; - a2[band] = (V - sqrt(2 * V) * K + K * K) * norm; - b1[band] = 2 * (K * K - 1) * norm; - b2[band] = (1 - sqrt(2) * K + K * K) * norm; - } - else { // cut - norm = 1 / (V + sqrt(2 * V) * K + K * K); - a0[band] = (1 + sqrt(2) * K + K * K) * norm; - a1[band] = 2 * (K * K - 1) * norm; - a2[band] = (1 - sqrt(2) * K + K * K) * norm; - b1[band] = 2 * (K * K - V) * norm; - b2[band] = (V - sqrt(2 * V) * K + K * K) * norm; - } - break; - } - return; -} -#endif