thermometer ready

This commit is contained in:
Kizarm 2025-01-31 18:59:46 +01:00
parent 51d6bea95e
commit 3783c92bd9
6 changed files with 425 additions and 12 deletions

View file

@ -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

View file

@ -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<size_t> (& ADC1.RDATAR_DR_ACT_DCG);
// Configure the memory address
DMA1.MADDR1.R = reinterpret_cast<size_t> (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);
}

View file

@ -0,0 +1,21 @@
#ifndef ADCDMA_H
#define ADCDMA_H
#include <stdint.h>
#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<uint16_t> * dst;
public:
explicit AdcDma () noexcept;
void attach (OneWay<uint16_t> & d) { dst = & d; }
void send (const bool b);
protected:
void DmaInit ();
};
#endif // ADCDMA_H

View file

@ -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 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<uint16_t> {
FIFO<uint32_t, FIFOLEN> & ring;
uint32_t y, w;
public:
explicit Average (FIFO<uint32_t, FIFOLEN> & 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<len; n++) {
suma += ptr [n];
}
y += suma - w; // ustálení teploty trvá dost dlouho, takže lze posílat klouzavý
w = y >> 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<uint32_t, FIFOLEN> fifo;
static TextPlayer player (fifo, led);
static GsmDecoder decoder(fifo);
static GpioClass button (GPIOA, 0u, (GPIO_Speed_In | GPIO_UPDI_MPPO));
static FIFO<uint32_t, FIFOLEN> avgring;
static AdcDma adc;
static Average avg (avgring);
static const SPLINE<array_size(measured)> 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<int32_t>(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;
}

View file

@ -0,0 +1,77 @@
#ifndef _REAL_FIX_H
#define _REAL_FIX_H
#include <stdint.h>
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

View file

@ -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.
template<class T, size_t N>constexpr 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<const int N> 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<N; i++) { x[i] = p[i].y; y[i] = p[i].x; }}
else {for (int i=0; i<N; i++) { x[i] = p[i].x; y[i] = p[i].y; }}
/* Tenhle příšerně složitý konstruktor je převzat ze stackoverflow.com (původní odkaz už zmizel)
* Není to zas taková sranda - koeficienty se počítají řešením tridiagonální matice řádu N.
* V těch indexech se snadno zabloudí, tohle kupodivu funguje (přirozené splajny) je to dost krátké.
* Je zajímavé, že tohle najdete spíš ve Fortranu než v C, matematici jsou zřejmě hodně konzervativní.
* */
const int n = N - 1;
double h [n];
for (int i = 0; i < n; ++i) { h[i] = x[i+1]-x[i]; }
double alpha [n]; alpha [0] = 0.0;
for (int i = 1; i < n; ++i) {
alpha[i] = 3.0 * (y[i+1] - y[i]) / h[i] - 3.0 * (y[i] - y[i-1]) / h[i-1];
}
double c [n+1], l [n+1], mu [n+1], z [n+1]; l [0] = 1.0; mu[0] = 0.0; z [0] = 0.0;
for (int i = 1; i < n; ++i) { // přímý chod
l [i] = 2.0 * (x[i+1] - x[i-1]) - h[i-1] * mu[i-1];
mu[i] = h[i] / l[i];
z [i] = (alpha[i] - h[i-1] * z[i-1]) / l[i];
}
l[n] = 1.0; z[n] = 0.0; c[n] = 0.0;
double b [n], d [n];
for (int j = n-1; j >= 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