From 7a8411ad339aae758c698690f707d1a46d641a9e Mon Sep 17 00:00:00 2001 From: Kizarm Date: Tue, 5 Mar 2024 14:52:55 +0100 Subject: [PATCH] add hdo telegram --- README.md | 26 +++++++ hdo/Makefile | 2 +- hdo/hdo.cpp | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++ hdo/hdo.h | 63 +++++++++++++++++ hdo/main.cpp | 80 +++++++++------------- 5 files changed, 310 insertions(+), 50 deletions(-) create mode 100644 hdo/hdo.cpp create mode 100644 hdo/hdo.h diff --git a/README.md b/README.md index bc87500..ccf46f4 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,29 @@ minichlink, který umí flashnout firmware a GDB server. ## hello Základní program, používá GPIO a SysTick v režimu přerušení pro blikání LEDkou. +## pwm, adc, serial +DEMO pro základní seznámení s periferiemi. + +## hdo +Tohle je trochu komplexnější příklad. + +Na vstup PC4 přivádíme ze síťového transformátoru napětí +cca 0.7-1 V efektivního napětí. Vstup musí být tedy připojen +na odporový dělič 1:1, zapojený mezi VCC a GND a trafo k tomuto +bodu připojíme přes vhodný kondenzátor, tak, aby střídavé napětí +nebylo limitováno. + +Firmware z toho Goertzelovým algoritmem vytáhne signál HDO (zde 216.6 Hz) +a vyhodnotí jednotlivé pulsy. Velikost pulsu je vypisována na sériový +port (115200 Bd) a pokud překročí hodnotu trigger, rozsvítí LED na PD2 (aktivní v L). +Dále je pak z pulsů sestaven telegram, opět vypsán sériový port, +a pokud se shoduje s povelem uvedeným v konstruktoru Hdo, sepne / rozepne +relé na portu PD4 (aktivní v H) podle vysílaného signálu. +Celé se to vejde do 3.5 KiB flash a 1 KiB RAM, i v takto malém procesoru +tedy zbývá poměrně dost místa na jiné kraviny. + +Tvar výpisů je tento: + A1---B---4---- DP: VVZZ ZZZV ZZZZ VVZV + A--3-B--34---- DP: --VV Z-V- -V-V ---- + A1---B-------8 DP: VVVZ VZVV ZZZV -VZV + A1---B---45678 DP: ---Z -Z-V Z-Z- -VZV diff --git a/hdo/Makefile b/hdo/Makefile index ca6778c..d38e4a2 100644 --- a/hdo/Makefile +++ b/hdo/Makefile @@ -16,7 +16,7 @@ CFLAGS+= -I. -I./common -I./$(TARGET) -I/usr/include/newlib -DUSE_HSE=1 DEL = rm -f # zdrojaky -OBJS = main.o adcclass.o +OBJS = main.o adcclass.o hdo.o OBJS += usartclass.o print.o include $(TARGET)/$(TOOL).mk diff --git a/hdo/hdo.cpp b/hdo/hdo.cpp new file mode 100644 index 0000000..6b820a7 --- /dev/null +++ b/hdo/hdo.cpp @@ -0,0 +1,189 @@ +#include "hdo.h" +static constexpr int factor = int (double(1U << 20) * (1.0 / 1330.0) + 0.5); +static constexpr int period = 120; // perioda vyhodnocení Goertzelovým algoritmem +// pro výpočet dělení použijeme tento union +union divu { + int32_t val; + struct { + uint32_t unu : 16; + uint32_t res : 4; + uint32_t div : 12; + }; +}; +static constexpr const int index_table [] = {19,20,21,22, 24,25,26,27, 29,30,31,32, 34,35,36,37}; + +unsigned Hdo::Send (uint16_t *const ptr, const unsigned len) { + int q2 = 0, q1 = 0; + for (unsigned n=0; n>= ISHIFT; // zmenšení pro int + q0 += ((int) ptr [n]) - q2; // vlastní výpočet + q2 = q1; // posuv o vzorek + q1 = q0; // (rekurze) + } + int rv = q1 * q2; + rv *= -coeff; + rv >>= ISHIFT; // tady nutno zmenšit tak, jak bylo zvětšeno v calc_coeff() + rv += q1 * q1 + q2 * q2; // výkon by byl sqrt (rv), není nutné počítat, napětí stačí + data.Write (rv); // dáme do FIFO, vybíráme v main() + return len; +} +void Hdo::pass () { + int value; + if (!data.Read (value)) return; + // DEBUG value + cout << value << " \r"; + value -= trigger; + + if (value > 0) led << false; // LED je zapojená proti VCC + else led << true; + // Konečné vyhodnocení. + if (Decode (value, buf1)) { // Telegram OK. + HumanRead (buf1, buf2); // Převeď ho do čitelné podoby + + cout << buf2 << EOL; // Vypíšeme telegram + + int i = Action (buf2, cmd); // Nakonec proveď akci + if (i == +1) relay << true; + if (i == -1) relay << false; + } +} +/////////////////////////////////////////////////////////////// +int Hdo::Decode(int num, char * str) { + int rv = 0; + uint32_t cv = 0, bi = 0; + + switch (status) { + case WAIT_FOR_BEGIN: + counter = 0; + // start telegramu + if (num > 0) status = SYNC_PULSE; + break; + case SYNC_PULSE: + counter++; + if (num < 0) { // pokles + if (counter > SYNC_HI) { // pokud je včas, určuje další časování + counter = 0; + status = SYNC_SPACE; + } + else // chyba + status = WAIT_FOR_BEGIN; + } + break; + case SYNC_SPACE: + counter++; + if (num > 0) // vzestup během mezery = chyba + status = WAIT_FOR_BEGIN; + if (counter > SYNC_LO) { // celá synchronizační mezera + counter = -1; // jeden prázdný cyklus + suma = 0; + bits = 0; + status = CORELATE; + } + break; + // Budeme to dělat jako korelaci. Perioda pak nemusí být pevná, + // nakonec je to jednodušší a pochopitejnější. + case CORELATE: + counter++; + if (counter <= 0) break; // ten prázdný cyklus (synchronizace, určeno měřením) + divu dv; + // Tohle je fakticky dělení periodou 1330 ms + dv.val = counter * period * factor; + bi = dv.div; // index bitu (kolikátá perioda, podíl) + cv = dv.res; // 0..15 v periodě (zbytek po dělení) + + if (bi != bits) { // napřed vyhodnoceni předchozího + bits = bi; + if (suma > 0) suma = 1; + else suma = 0; + // output '0' or '1' + str [bits - 1] = (char) suma + 0x30; + suma = 0; + if (bits >= 44) { // TELEGRAM END + status = WAIT_FOR_BEGIN; + rv = 1; + str [bits] = 0; + } + } // pak korelace + if (cv < 14) suma += num; // 0..13 kladná korelace (puls) + else suma -= num; // jinak záporná korelace (mezera) + break; + default : + break; + } + return rv; +} +void Hdo::HumanRead(const char * src, char * dst) { + int tindex, windex = 0; + char c, avg; + + for (tindex = 0; tindex < 44; tindex++) { + avg = src [tindex] - 0x30; + // Doplním písmenka + if (tindex == 0) dst [windex++] = 'A'; + if (tindex == 4) dst [windex++] = 'B'; + if (tindex == 12) { + dst [windex++] = ' '; + dst [windex++] = 'D'; + dst [windex++] = 'P'; + dst [windex++] = ':'; + } + // Skupina A + if (tindex < 4) { + if (avg) c = '1' + tindex; + else c = '-'; + dst [windex++] = c; + } + // Skupina B + else if (tindex < 12) { + if (avg) c = '1' + tindex - 4; + else c = '-'; + dst [windex++] = c; + } + // Dvojpovel + else { + // skupiny po 4 oddělit mezerami pro lepší čitelnost + if (!((tindex+4) % 8)) dst [windex++] = ' '; + if (tindex % 2) { // bit "vypnuto" na tindex - to až následně + if (dst [windex] == '-') { // zapnuto nebylo + if (avg) dst [windex] = 'V'; // tedy je vypnuto + } + else { // bylo zapnuto + if (avg) dst [windex] = 'E'; // tedy je chyba - nemůže být obojí + } + windex++; + } + else { // bit "zapnuto" na tindex - to je první !!! + if (avg) dst [windex] = 'Z'; // je zapnuto + else dst [windex] = '-'; // ještě se uvidí + } + } + } + dst [windex] = 0; +} +int Hdo::Action(char * tlg, const char * command) { + int i, j; + char c; + i = command [1] - '1'; // An + if ((i < 0) || (i > 3)) return 0; // chyba + if (tlg [i+1] == '-') return 0; // není pro mne + i = command [3] - '1'; // Bn + if ((i < 0) || (i > 7)) return 0; // chyba + if (tlg [i+6] == '-') return 0; // není pro mne + i = command [6] - '0'; // DPn + j = command [7]; // DPn+1 + if (j) { + j -= '0'; + i *= 10; + i += j; + } // v i je číslo za DP + if ((i < 1) || (i > 16)) return 0; // chyba + i--; // index bude o 1 menší + j = index_table [i]; // v telegramu na pozici j + c = tlg [j]; // je písmeno + if (c == 'Z') return +1; // Z - potom zapni + if (c == 'V') return -1; // V - vypni + return 0; +} diff --git a/hdo/hdo.h b/hdo/hdo.h new file mode 100644 index 0000000..cf886a0 --- /dev/null +++ b/hdo/hdo.h @@ -0,0 +1,63 @@ +#ifndef HDO_H +#define HDO_H +#include "gpio.h" +#include "usartclass.h" +#include "print.h" +#include "oneway.h" + +static constexpr int ISHIFT = 12; +/* Tady je ten výpočet proveden externě. +static constexpr int calc_coeff (const double nfreq) { + return lround (double(2UL << ISHIFT) * cos (2.0 * 3.14159265358979323846 * nfreq)); +} + coeff = calc_coeff (216.6 / 1000.0) = 1706 +*/ +/** +\enum stat +Stavy konečného automatu vyhodnocení telegramu +*/ +enum stat { + WAIT_FOR_BEGIN = 0, //!< čekání na začátek telegramu + SYNC_PULSE, //!< startovací puls probíhá + SYNC_SPACE, //!< synchronizační mezera probíhá + CORELATE, //!< datový puls probíhá +}; +static constexpr int SYNC_HI = 17; //!< 2.33 sec délka startovacího pulsu +static constexpr int SYNC_LO = 22; //!< 2.99 sec délka synchronizační mezery +static constexpr int TBUFLEN = 64; + +class Hdo : public OneWay { + GpioClass led, relay; + UsartClass serial; + Print cout; + FIFO data; + const int coeff; + const int trigger; //!< rozhodovací úroveň (napevno) + const char * cmd; //!< rozhodovací, řídící string + + char buf1[TBUFLEN]; + char buf2[TBUFLEN]; + int suma; + uint32_t bits; + int counter; //!< čítač period + stat status; //!< stav konečného automatu detekce + + public: + explicit Hdo (const char * command) noexcept : OneWay (), + led (GPIOD, 2), relay (GPIOD, 4), serial (115200u), cout (DEC), data(), coeff (1706), trigger (0x4000), + cmd (command), suma (0), bits (0), counter (0), status (WAIT_FOR_BEGIN) { + /* trigger musí být nastaven tak do 1/3 až do 1/2 maximální vyhodnocené hodnoty (viz výpis) + * Je nutné použít HSE, tj. krystal 24 HHz. Bez toho to fakt nechodí a to i na procesorech i.e. STM. + * */ + cout += serial; + led << true; + } + unsigned Send (uint16_t * const ptr, const unsigned len) override; + void pass (); + protected: + int Decode (int num, char * str); + void HumanRead (const char * src, char * dst); + int Action (char * tlg, const char * command); +}; + +#endif // HDO_H diff --git a/hdo/main.cpp b/hdo/main.cpp index faca227..d3e1445 100644 --- a/hdo/main.cpp +++ b/hdo/main.cpp @@ -1,56 +1,38 @@ -#include "gpio.h" -#include "usartclass.h" -#include "print.h" #include "adcclass.h" -#include "oneway.h" -static constexpr int ISHIFT = 12; -////////////////////////////////////// -class Process : public OneWay { - GpioClass led; - UsartClass serial; - Print cout; - FIFO data; - const int coeff, trigger; - public: - explicit Process () noexcept : OneWay (), - led (GPIOD, 4), serial (115200u), cout (DEC), data(), coeff (1706), trigger (0x1000) { - - cout += serial; - } - unsigned Send (uint16_t * const ptr, const unsigned len) override { - int q2 = 0, q1 = 0; - for (unsigned n=0; n>= ISHIFT; // zmenšení pro int - q0 += ((int) ptr [n]) - q2; // vlastní výpočet - q2 = q1; // posuv o vzorek - q1 = q0; // (rekurze) - } - int rv = q1 * q2; - rv *= -coeff; - rv >>= ISHIFT; // tady nutno zmenšit tak, jak bylo zvětšeno v calc_coeff() - rv += q1 * q1 + q2 * q2; // výkon by byl sqrt (rv), není nutné počítat, napětí stačí - data.Write (rv); // dáme do FIFO, vybíráme v main() - q1 = 0; q2 = 0; - return len; - } - void pass () { - int avg; - if (!data.Read (avg)) return; - cout << avg << EOL; - if (avg > trigger) led << true; - else led << false; - } -}; -////////////////////////////////////// +#include "hdo.h" +/////////////////////////////////////////////////////////////// +/* Tohle je trochu komplexnější příklad. + * + * Na vstup PC4 přivádíme ze síťového transformátoru napětí + * cca 0.7-1 V efektivního napětí. Vstup musí být tedy připojen + * na odporový dělič 1:1, zapojený mezi VCC a GND a trafo k tomuto + * bodu připojíme přes vhodný kondenzátor, tak, aby střídavé napětí + * nebylo limitováno. + * Firmware z toho Goertzelovým algoritmem vytáhne signál HDO (zde 216.6 Hz) + * a vyhodnotí jednotlivé pulsy. Velikost pulsu je vypisována na sériový + * port (115200 Bd) a pokud překročí hodnotu trigger, rozsvítí LED na PD2 (aktivní v L). + * Dále je pak z pulsů sestaven telegram, opět vypsán sériový port, + * a pokud se shoduje s povelem uvedeným v konstruktoru Hdo, sepne / rozepne + * relé na portu PD4 (aktivní v H) podle vysílaného signálu. + * Celé se to vejde do 3.5 KiB flash a 1 KiB RAM, i v takto malém procesoru + * tedy zbývá poměrně dost místa na jiné kraviny. + * + * Tvar výpisů je tento: + * A1---B---4---- DP: VVZZ ZZZV ZZZZ VVZV + * A--3-B--34---- DP: --VV Z-V- -V-V ---- + * A1---B-------8 DP: VVVZ VZVV ZZZV -VZV + * A1---B---45678 DP: ---Z -Z-V Z-Z- -VZV + * + * + * !!! Krystal 24 MHz nutný !!! + * */ +/////////////////////////////////////////////////////////////// static AdcClass adc; -static Process out; +static Hdo hdo ("A3B1DP7"); int main () { - adc.attach(out); + adc.attach(hdo); for (;;) { - out.pass(); + hdo.pass(); } return 0; }