add hdo telegram

This commit is contained in:
Kizarm 2024-03-05 14:52:55 +01:00
parent a9351ebff7
commit 7a8411ad33
5 changed files with 310 additions and 50 deletions

View file

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

View file

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

189
hdo/hdo.cpp Normal file
View file

@ -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<len; n++) {
// Vlastní Goertzelův algoritmus.
int q0 = coeff * q1;
// pokud byl coeff zvětšen, je třeba to tu zase zmenšit
q0 >>= 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;
}

63
hdo/hdo.h Normal file
View file

@ -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<int, 8> 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

View file

@ -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<int, 8> 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<len; n++) {
// Vlastní Goertzelův algoritmus.
int q0 = coeff * q1;
// pokud byl coeff zvětšen, je třeba to tu zase zmenšit
q0 >>= 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;
}