diff --git a/V203F6P6/thermometer/Makefile b/V203F6P6/thermometer/Makefile index c6a4a07..061b189 100644 --- a/V203F6P6/thermometer/Makefile +++ b/V203F6P6/thermometer/Makefile @@ -17,7 +17,7 @@ CFLAGS+= -I. -I./$(TARGET) -I./common -I./lib/gsm/inc -DEXTROM=1 DEL = rm -f # zdrojaky -OBJS = main.o pwmclass.o +OBJS = main.o pwmclass.o adc.o OBJS += GsmDecoder.o wrap.o OBJS += spiblocked.o norflash.o OBJS += extdata.o player.o diff --git a/V203F6P6/thermometer/adc.cpp b/V203F6P6/thermometer/adc.cpp new file mode 100644 index 0000000..f0884ec --- /dev/null +++ b/V203F6P6/thermometer/adc.cpp @@ -0,0 +1,133 @@ +#include "system.h" +#include "oneway.h" +#include "adc.h" +typedef __SIZE_TYPE__ size_t; + +static AdcDma * pInstance = nullptr; + +extern "C" { + [[gnu::interrupt]] extern void DMA1_Channel1_IRQHandler(); +} +void DMA1_Channel1_IRQHandler( void ) { + DMA1_Type::INTFR_DEF state (DMA1.INTFR); + if (state.B.GIF1 != RESET) { // Zřejmě zbytečné, ale pokud používám víc DMA + DMA1.INTFCR.B.CGIF1 = SET; // kanálů, pak to tak má být. + } else return; // Událost nevznikla pro kanál 1. + if (state.B.HTIF1 != RESET) { + DMA1.INTFCR.B.CHTIF1 = SET; + if (pInstance) pInstance->send (false); + } + if (state.B.TCIF1 != RESET) { + DMA1.INTFCR.B.CTCIF1 = SET; + if (pInstance) pInstance->send (true); + } +} + +static inline void EnableClock (void) noexcept { + // Enable DMA + RCC.AHBPCENR.modify([](RCC_Type::AHBPCENR_DEF & r) -> auto { + r.B.SRAMEN = SET; + r.B.DMA1EN = SET; + return r.R; + }); + // Enable ADC + GPIOA + RCC.APB2PCENR.modify([](RCC_Type::APB2PCENR_DEF & r) -> auto { + r.B.ADC1EN = SET; + r.B.IOPAEN = SET; + return r.R; + }); + RCC.APB1PCENR.B.TIM3EN = SET; // Enable TIM3 + RCC.CFGR0.B.ADCPRE = 3u; // PCLK2 divided by 8 as ADC clock (18 MHz, ! pretaktovano 14 MHz max). + // PIN PA0 / A0, PA1 / A1 + GPIOA.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto { + /*r.B.MODE0 = 0u; + r.B.CNF0 = 0u;*/ + r.B.MODE1 = 0u; + r.B.CNF1 = 0u; + return r.R; + }); +} +static inline void Timer3Init (uint32_t us) noexcept { + TIM3.PSC.R = 143u; // 1 MHz Fs + TIM3.ATRLR.R = us - 1u; + // TRGO update for ADC + TIM3.CTLR2.B.MMS = 2u; +} +static inline void AdcCalibrate (void) noexcept { + // RESET + RCC.APB2PRSTR.B.ADC1RST = SET; + RCC.APB2PRSTR.B.ADC1RST = RESET; + // set channels + ADC1.RSQR3__CHANNEL.modify([](ADC1_Type::RSQR3__CHANNEL_DEF & r) -> auto { + r.B.SQ1__CHSEL = 1u; // CH1 + //r.B.SQ2 = 1u; // CH1 + return r.R; + }); + ADC1.RSQR1.B.L = 0u; // 1 regular conversion + ADC1.SAMPTR2_CHARGE2.modify([](ADC1_Type::SAMPTR2_CHARGE2_DEF & r) -> auto { + r.B.SMP0_TKCG0 = 7u; + r.B.SMP1_TKCG1 = 7u; + return r.R; + }); + ADC1.CTLR1.modify([](ADC1_Type::CTLR1_DEF & r) -> auto { + r.B.SCAN = SET; + //r.B.BUFEN = SET; + //r.B.PGA = 3u; // x64 + return r.R; + }); + + ADC1.CTLR2.B.ADON = SET; + ADC1.CTLR2.B.RSTCAL = SET; // Launch the calibration by setting RSTCAL + while (ADC1.CTLR2.B.RSTCAL != RESET); // Wait until RSTCAL=0 + ADC1.CTLR2.B.CAL = SET; // Launch the calibration by setting CAL + while (ADC1.CTLR2.B.CAL != RESET); // Wait until CAL=0 +} +static inline void AdcPostInit (void) noexcept { + ADC1.CTLR2.modify([](ADC1_Type::CTLR2_DEF & r) -> auto { + r.B.DMA = SET; + r.B.EXTTRIG = SET; + r.B.EXTSEL = 4u; // TRGO event of timer 3 + r.B.SWSTART = SET; + return r.R; + }); +} +inline void AdcDma::DmaInit () { + // Configure the peripheral data register address + DMA1.PADDR1.R = reinterpret_cast (& ADC1.RDATAR_DR_ACT_DCG); + // Configure the memory address + DMA1.MADDR1.R = reinterpret_cast (buffer); + // Configure the number of DMA tranfer to be performs on DMA channel 1 + DMA1.CNTR1 .R = FULL_LEN; + // Configure increment, size, interrupts and circular mode + DMA1.CFGR1.modify([] (DMA1_Type::CFGR1_DEF & r) -> auto { + r.B.PL = 3u; // highest priority + r.B.MEM2MEM = RESET; // periferal -> memory + r.B.DIR = RESET; + r.B.MINC = SET; // memory increment + r.B.MSIZE = 1u; // 16-bit + r.B.PSIZE = 1u; // 16-bit + r.B.HTIE = SET; // INT Enable HALF + r.B.TCIE = SET; // INT Enable FULL + r.B.CIRC = SET; // Circular MODE + // Enable DMA Channel 1 + r.B.EN = SET; + return r.R; + }); +} +//////////////////////////////////////////////////////////////////////////////////// +AdcDma::AdcDma() noexcept : pL (buffer), pH (buffer + HALF_LEN), dst (nullptr) { + pInstance = this; + EnableClock (); + Timer3Init (1000u); + NVIC.EnableIRQ (DMA1_Channel1_IRQn); + AdcCalibrate(); + DmaInit (); + AdcPostInit (); + // start timer + TIM3.CTLR1.B.CEN = SET; +} +inline void AdcDma::send(const bool b) { + if (!dst) return; + if (b) dst->Send (pH, HALF_LEN); + else dst->Send (pL, HALF_LEN); +} diff --git a/V203F6P6/thermometer/adc.h b/V203F6P6/thermometer/adc.h new file mode 100644 index 0000000..a8f714a --- /dev/null +++ b/V203F6P6/thermometer/adc.h @@ -0,0 +1,21 @@ +#ifndef ADCDMA_H +#define ADCDMA_H +#include +#include "oneway.h" + +class AdcDma { + static constexpr unsigned HALF_LEN = 0x80u; + static constexpr unsigned FULL_LEN = HALF_LEN * 2u; + uint16_t * pL; + uint16_t * pH; + uint16_t buffer [FULL_LEN]; + OneWay * dst; + public: + explicit AdcDma () noexcept; + void attach (OneWay & d) { dst = & d; } + void send (const bool b); + protected: + void DmaInit (); +}; + +#endif // ADCDMA_H diff --git a/V203F6P6/thermometer/main.cpp b/V203F6P6/thermometer/main.cpp index f1a78a5..4a06677 100644 --- a/V203F6P6/thermometer/main.cpp +++ b/V203F6P6/thermometer/main.cpp @@ -2,29 +2,91 @@ #include "fifo.h" #include "player.h" #include "GsmDecoder.h" -// #include "adcdma.h" #include "oneway.h" #include "gpio.h" +#include "adc.h" +#include "spline.h" //////////////////////////////////////////////////////// -/* Demo, které jen počítá od 0 do 1000000. Výstup je PWM +/* Skutečně měří teplotu NTC termistorem. Výstup je PWM * na pinu PA2 24kHz, enable PB1. Odvozeno z teploměru na * https://github.com/Kizarm/TTSCP_Client/tree/main/kecal/stm - * */ //////////////////////////////////////////////////////// -static GpioClass led (GPIOB, 8); -static PwmClass pwm; -static FIFO fifo; -static TextPlayer player (fifo, led); -static GsmDecoder decoder(fifo); +static constexpr Pair measured [] = { // pár hodnota ADC, teplota ve °C + { +133.9, 125.000 }, + { +152.2, 120.000 }, + { +198.3, 110.000 }, + { +260.0, 100.000 }, + { +343.6, 90.000 }, + { +456.9, 80.000 }, + { +609.9, 70.000 }, + { +814.5, 60.000 }, + { +1082.6, 50.000 }, + { +1421.2, 40.000 }, + { +2047.5, 25.000 }, + { +2729.0, 10.000 }, + { +3139.8, 0.000 }, + { +3472.7, -10.000 }, + { +3715.6, -20.000 }, + { +3876.9, -30.000 }, + { +3960.2, -38.000 }, + { +3976.0, -40.000 }, +}; +class Average : public OneWay { + FIFO & ring; + uint32_t y, w; + public: + explicit Average (FIFO & r) : OneWay(), ring(r), y(0u), w(0u) {} + unsigned int Send(uint16_t * const ptr, const unsigned int len) override { + unsigned suma = 0u; + for (unsigned n=0u; n> 4; // průměr s postupným zapomínáním. Hodnota je násobena délkou + // bufferu, t.j. 128 (posun doleva o 7) + if (w < (134 * 128)) return len; // skip limits - nezobrazuj mimo rozsah -40 až 120 + if (w > (3976 * 128)) return len; + ring.Write (w); + return len; + } +}; +static unsigned abs_diff (const int a, const int b) { + const int d = a - b; + return d > 0 ? +d : -d; +} +//////////////////////////////////////////////////////// +static GpioClass led (GPIOB, 8u); +static PwmClass pwm; +static FIFO fifo; +static TextPlayer player (fifo, led); +static GsmDecoder decoder(fifo); + +static GpioClass button (GPIOA, 0u, (GPIO_Speed_In | GPIO_UPDI_MPPO)); +static FIFO avgring; +static AdcDma adc; +static Average avg (avgring); +static const SPLINE spline (measured, false); int main () { led << true; - unsigned counter = 0u; pwm.attach (decoder); + adc.attach (avg); + int old_value = 0; + uint32_t average; for (;;) { - player.say(counter++); - pwm.delay (); + if (avgring.Read(average)) { + // average je de facto posunuta doleva o 7, potřebujeme o 16 - rozdíl je tedy 9 + const int32_t ivalue = static_cast(average << 9); + const real rt = spline.interpolate(real(ivalue)); + const int temperature = 100 * rt.x >> 16; + const unsigned delta = abs_diff (temperature, old_value); + if (delta >= 50u or !button) { + old_value = temperature; + player.say(old_value, 2); // s rozlišením na setiny + player.say(sayed_texts.units); + } + } } return 0; } diff --git a/V203F6P6/thermometer/real_fix.h b/V203F6P6/thermometer/real_fix.h new file mode 100644 index 0000000..44d4bc5 --- /dev/null +++ b/V203F6P6/thermometer/real_fix.h @@ -0,0 +1,77 @@ +#ifndef _REAL_FIX_H +#define _REAL_FIX_H +#include +namespace FIX { + static constexpr int iround (const double d) { return d > 0.0 ? int (d + 0.5) : int (d - 0.5); } + /** + * @class real + * @brief Aritmetika v pevné řádové čárce. + * + * Není to moc přesné, ale bez float koprocesoru to postačí. + * A není to kompletní, jen pro výpočet polynomu, zde to stačí. + */ + struct real { + int32_t x; // necháme veřejné + /** + * @brief Konstruktor z double + * @param _x double + */ + explicit constexpr real (const double _x) : x (iround (_x)) {} + /** + * @brief Konstruktor z celého čísla, přímo nepoužívat - explicitní převod from_int() + * Ono se to trochu pere s tím double. + * @param _x integer + */ + explicit constexpr real (const int32_t _x) : x (_x) {} + /** + * @brief Kopírovací konstruktor + */ + explicit constexpr real () : x (0) {} + // stačí přetížit jen některé operátory, výpočet je dělán poměrně úsporně + + real operator+ (const real & r) const { + return real (x + r.x); + } + real operator- (const real & r) const { + return real (x - r.x); + } + + bool operator< (const real & r) const { + return x < r.x; + } + bool operator>= (const real & r) const { + return x >= r.x; + } + real & operator+= (const real & r) { + x += r.x; + return * this; + } + // Pouze pro výpisy - zachová formátovou specifikaci pro FLT + double operator+ () const { + return double (x); + } + }; + // Násobení s posunem zde používá "plovoucí" posun, jinak to nejde. + static void mull (real & res, const real & r, const int sl = 0) { + const int64_t result = (int64_t)(res.x) * (int64_t)(r.x); +#ifdef __linux__ + // Check overflow + const uint64_t u = result < 0 ? -result : +result; + const uint32_t o = u >> (sl + 32); + if (o) {fprintf (stderr, "Overflow 0x%X\n", o); // Není to dokonalé, ale alespoň něco + // Šla by vyhodit výjimka a backtrace zjistit proč a hlavně kde. + throw ("overflow exception"); } +#endif + res.x = result >> sl; + } + //! Vytvoří double z real + static constexpr double from_real (const real & r) { + const double a = 1.0 / double (1ull << 16); + return a * double(r.x); + } + static constexpr real to_real (const double x, const int sh) { + const double a = double (1ull << sh); + return real (a * x); + } +}; // end of namespace FIX +#endif // _REAL_FIX_H diff --git a/V203F6P6/thermometer/spline.h b/V203F6P6/thermometer/spline.h new file mode 100644 index 0000000..ae90eb9 --- /dev/null +++ b/V203F6P6/thermometer/spline.h @@ -0,0 +1,120 @@ +#ifndef SPLINE_H +#define SPLINE_H +/** @file + * @class SPLINE + * @brief Kubické splajny. + */ +#include "real_fix.h" +using namespace FIX; +typedef __SIZE_TYPE__ size_t; + +// Zjištění počtu prvků statického pole. +templateconstexpr size_t array_size (T (&) [N]) { return N; } +/*! + * @class Pair + * @brief Prvek vstupní tabulky pro výpočet kubických splajnů. + * */ +struct Pair { + double x, y; +}; +/*! @class SplineSet + @brief Výsledné koeficienty pro výpočet iterace pomocí polynomu 3. řádu. +*/ +struct SplineSet { + real x; //!< začátky intervalů x + real a; //!< záčátky intervalů y, koeficienty a + real b; //!< koeficienty b (x^1) + real c; //!< koeficienty c (x^2) + real d; //!< koeficienty d (x^3) +}; +//! V podstatě statický vektor pro SplineSet (bez nutnosti dymanické alokace) +template class SPLINE { + SplineSet data [N - 1]; // zde jsou schovány koeficienty (privátně) + public: + //! Konstruktor + explicit constexpr SPLINE (const Pair * const p, const bool reverse = false) noexcept { + double x [N], y [N]; // přeskupení dat - možnost inverzní funkce při reverse = true + if (reverse) {for (int i=0; i= 0; --j) { // zpětný chod + c[j] = z [j] - mu[j] * c[j+1]; + b[j] = (y[j+1] - y[j]) / h[j] - h[j] * (c[j+1] + 2*c[j]) / 3.0; + d[j] = (c[j+1] - c[j]) / (3.0 * h[j]); + } + for (int i = 0; i < n; ++i) { // závěrečný zápis koeficientů + // Pro FIX je potřeba nastavit různé posuny pro koeficienty, rozsah je příliš velký a pak by to bylo nepřesné. + data[i].x = to_real(x[i], 16); data[i].a = to_real(y[i], 16); + data[i].b = to_real(b[i], 20); data[i].c = to_real(c[i], 28); data[i].d = to_real(d[i], 36); + } + } + const SplineSet & operator[] (const int index) const { + return data [index]; + } + const size_t size () const { + return N - 1; + } + const real interpolate (const real x) const { + const unsigned n = range (x); // určíme interval ve kterém budeme počítat + const SplineSet & s = data [n]; // koeficienty polynomu v tomto intervalu + const real r = x - s.x; // souřadnice x v tomto intervalu + return cubic_poly (s, r); // vlastní výpočet + } + /***************************************************************/ + /** @class iterator + * @brief pro range-based for () */ + class iterator { + const SplineSet * ptr; + public: + iterator(const SplineSet * _ptr) : ptr (_ptr) {} + iterator operator++ () { ++ptr; return * this; } + bool operator!= (const iterator & other) const { return ptr != other.ptr; } + const SplineSet & operator* () const { return * ptr; } + }; + iterator begin () const { return iterator (data ); } + iterator end () const { return iterator (data + N - 1); } + protected: + const unsigned range (const real x) const { + int l=0, r=size() - 1u; + while (l <= r) { + const int s = (l + r) >> 1; + const real x0 = data[s + 0].x; + const real x1 = data[s + 1].x; + if (x < x0) r = s - 1; + else if (x >= x1) l = s + 1; + else return s; + } + return size() - 1u; + } + const real cubic_poly (const SplineSet & s, const real x) const { + real r(s.d); // d => 36 v konstruktoru + mull (r, x, 24); // 36+16-24 = 28 == c + r += s.c; // c => 28 + mull (r, x, 24); // 28+16-24 = 20 == b + r += s.b; // b => 20 + mull (r, x, 20); // 20+16-20 = 16 == a + r += s.a; // a=x => 16 + return r; // r=y => 16 + } +}; + +#endif // SPLINE_H