blink -> morse

This commit is contained in:
Kizarm 2025-01-29 15:29:40 +01:00
parent 1b0a6412ea
commit fa73dbffbc
10 changed files with 401 additions and 28 deletions

View file

@ -1,11 +1,11 @@
TARGET?= ch32v203
TOOL ?= gcc
#TOOL ?= clang
#TOOL ?= gcc
TOOL ?= clang
PRJ = example
VPATH = . ./$(TARGET)
VPATH = . ./$(TARGET) ./common
BLD = ./build/
DFLAGS = -d
LFLAGS = -g
@ -13,11 +13,11 @@ LDLIBS =
BFLAGS = --strip-unneeded
CFLAGS = -MMD -Wall -Wno-parentheses -ggdb -fno-exceptions -ffunction-sections -fdata-sections
CFLAGS+= -I. -I./$(TARGET)
CFLAGS+= -I. -I./$(TARGET) -I./common
DEL = rm -f
# zdrojaky
OBJS = main.o
OBJS = main.o pwmclass.o morse.o generator.o
#OBJS +=
include $(TARGET)/$(TOOL).mk

1
V203F6P6/blink/common Symbolic link
View file

@ -0,0 +1 @@
../common/

View file

@ -0,0 +1,28 @@
#include "generator.h"
#include "pwmclass.h"
#include "utils.h"
static constexpr unsigned W_TB = 8u;
static constexpr double AMPL = MAXPWM >> 1;
static constexpr int ULEN = 1 << W_TB;
static constexpr uint16_t u16_sin (const int x) {
const double a = (double (x) * D_PI) / double (ULEN);
const double s = AMPL * (1.0 + 0.96 * sincos (a, true));
return i_round (s);
}
static const TABLE<uint16_t, ULEN> sin_tab (u16_sin);
Generator::Generator (const unsigned f) noexcept : OneWay<uint16_t>(),
freq (f), base(0u), incr (0u), ms_count (0u) {
}
uint16_t Generator::step() {
const uint16_t v = sin_tab [base >> 24];
base += incr;
return v;
}
unsigned int Generator::Send(uint16_t * const ptr, const unsigned int len) {
for (unsigned n=0u; n<len; n++) ptr [n] = step();
if (ms_count) ms_count -= 1u; // průchod zde je za 1 ms přesně
return len;
}

View file

@ -0,0 +1,23 @@
#ifndef GENERATOR_H
#define GENERATOR_H
#include "oneway.h"
/* Něco jako DDS, přesná frekvence není řešena (závisí na TIM1). */
class Generator : public OneWay<uint16_t> {
const unsigned freq;
unsigned base, incr;
volatile unsigned ms_count;
public:
explicit Generator (const unsigned f) noexcept;
unsigned Send (uint16_t * const ptr, const unsigned len) override;
void delay (const unsigned ms) {
ms_count = ms;
while (ms_count);
}
void on () volatile { incr = freq; }
void off () volatile { base = 0u; incr = 0u; }
protected:
uint16_t step ();
};
#endif // GENERATOR_H

View file

@ -1,28 +1,24 @@
#include "system.h"
#include "gpio.h"
////////////////////////////////////////////////////////////////////////
static constexpr unsigned ticks = SYSTEM_CORE_CLOCK / 1000u;
static volatile unsigned counter = 0u;
extern "C" [[gnu::interrupt]] void SysTick_Handler ();
////////////////////////////////////////////////////////////////////////
void SysTick_Handler () {
SysTick.SR = 0u;
if (counter) counter -= 1u;
}
static void delay (const unsigned dly = 200u) {
counter = dly;
while (counter);
}
/* SIMPLE EXAMPLE: LED blinking */
/* Když už mám PWM hotové, tak to může pípat na pinu PA2. Je Tx pin pro
* budič RS485, na tuto sběrnici je možné připojit "špunty do uší"
* 32Ohm v sérii a zvuk je dostatečně hlasitý, čip to utáhne.
* Frekvence je 1kHz - čistý sinus.
*
* Pro tento procesor je možné použít pro překlad clang. Pak je možné
* tabulky pro sinus i pro komprimovaný kód morse použít konstantní
* výrazy. Nezvětšuje to délku kódu a je z toho vidět, jak se tyto
* věci počítají, aniž by bylo nutné použít nějaký externí nástroj.
*
* Kód je fakticky recyklovaný z plného CH32V203, drobné úpravy tam jsou
* protože je to pinově trochu jinak, princip je stejný.
* */
#include "morse.h"
//////////////////////////////////////
static GpioClass led (GPIOB, 8);
static Morse morse (led, 100u);
int main () {
GpioClass led (GPIOB, 8);
SysTick.Config (ticks);
unsigned pass_cnt = 0u;
for (;;) {
delay();
const bool b = pass_cnt & 1u;
led << b;
pass_cnt += 1u;
morse << "hello world";
}
return 0;
}

87
V203F6P6/blink/morse.cpp Normal file
View file

@ -0,0 +1,87 @@
#include "morse.h"
#include "utils.h"
// Spočteme číslo (pro 1kHz) jako (1000 << 32) / 24000 (24 kHz je samplerate).
static constexpr unsigned F0 = (1000ull << 32) / 24000u;
static constexpr const char * const morse_code [] = { /* nedefinované znaky nahrazeny mezerou */
" ", /* */ " ", /*!*/ ".-..-.", /*\"*/ " ", /*#*/ " ", /*$*/
" ", /*%*/ " ", /*&*/ ".----.", /*\'*/ "-.--.", /*(*/ "-.--.-", /*)*/
" ", /***/ ".-.-.", /*+*/ "--..--", /*,*/ "-....-", /*-*/ ".-.-.-", /*.*/ "-..-." , /*/*/
"-----", /*0*/ ".----", /*1*/ "..---", /*2*/ "...--", /*3*/ "....-", /*4*/
".....", /*5*/ "-....", /*6*/ "--...", /*7*/ "---..", /*8*/ "----.", /*9*/
"---...", /*:*/ "-.-.-.", /*;*/ " ", /*<*/ "-...-" , /*=*/ " ", /*>*/ "..--..", /*?*/
".--.-.", /*@*/
".-", /*A*/ "-...", /*B*/ "-.-.", /*C*/ "-..", /*D*/ ".", /*E*/ "..-.", /*F*/
"--.", /*G*/ "....", /*H*/ "..", /*I*/ ".---", /*J*/ "-.-", /*K*/ ".-..", /*L*/
"--", /*M*/ "-.", /*N*/ "---", /*O*/ ".--.", /*P*/ "--.-", /*Q*/ ".-.", /*R*/
"...", /*S*/ "-", /*T*/ "..-", /*U*/ "...-", /*V*/ ".--", /*W*/ "-..-", /*X*/
"-.--", /*Y*/ "--..", /*Z*/ " ", " ", " ", " ", "..--.-", /*_*/
};
static constexpr unsigned slen (const char * const str) {
unsigned n = 0;
while (str[n]) n++;
return n;
}
static const TABLE<unsigned char, array_size (morse_code)> compressed_table
([](const unsigned n) -> auto {
const char * const ptr = morse_code [n];
const unsigned len = slen (ptr);
unsigned char mb = 0u;
if (ptr [0] == ' ') return mb;
mb = (len & 7u) << 5;
for (unsigned n=0; n<len; n++) {
if (ptr [n] == '-') mb |= (1u << n);
}
return mb;
});
Morse::Morse(const GpioClass & pin, const unsigned int ms) noexcept : unit (ms), led (pin),
gen (F0), pwm () {
pwm.attach(gen);
}
const Morse & Morse::operator<< (const char * text) {
for (unsigned n=0; ; n++) {
const char c = text [n];
if (c == '\0') break;
morse_byte mb;
if (c < '\x20') {
} else if (c < '`') {
const int i = c - '\x20';
mb.byte = compressed_table [i];
} else if (c == '`') {
} else if (c <= 'z') {
const int i = c - '\x40';
mb.byte = compressed_table [i];
} else {
}
out (mb);
}
gen.delay (10 * unit);
return * this;
}
/* . => 1 x unit
* - => 3 x unit
* mezera mezi značkami => 1 x unit
* mezera mezi znaky => 3 x unit
* mezera mezi slovy => 7 x unit
* */
void Morse::out (const morse_byte mb) {
/* Finta je v tom, že i když se pole mlen a bits překrývají,
* nevadí to - maximální délka je 6, takže v nejnižším bitu
* mlen může být obsažen 1 bit 6.bitového znaku.
* Takhle napsáno to běhá jen na malém indiánu, přepisovat
* to do bitových posunů nebudu, i když by to bylo čistší.
* */
const unsigned len = mb.mlen > 6u ? 6u : mb.mlen;
if (!len) { gen.delay (4 * unit); return; }
for (unsigned n=0; n<len; n++) {
off ();
gen.delay (unit);
const unsigned v = (1u << n) & mb.bits;
const unsigned d = v ? 3u : 1u;
on ();
gen.delay (d * unit);
off ();
}
gen.delay (3 * unit);
}

36
V203F6P6/blink/morse.h Normal file
View file

@ -0,0 +1,36 @@
#ifndef MORSE_H
#define MORSE_H
#include "gpio.h"
#include "generator.h"
#include "pwmclass.h"
union morse_byte {
struct {
unsigned char bits : 5;
unsigned char mlen : 3;
};
unsigned char byte;
explicit constexpr morse_byte () noexcept : byte (0u) {}
};
class Morse {
const unsigned unit;
const GpioClass & led;
Generator gen;
PwmClass pwm;
public:
explicit Morse (const GpioClass & pin, const unsigned ms = 100u) noexcept;
const Morse & operator<< (const char * text);
protected:
void out (const morse_byte mb);
void on () {
led << true; // LED je připojena proti GND, zde se tedy rozsvítí
gen.on ();
}
void off () {
led << false;
gen.off();
}
};
#endif // MORSE_H

107
V203F6P6/blink/pwmclass.cpp Normal file
View file

@ -0,0 +1,107 @@
#include "pwmclass.h"
#include "gpio.h"
typedef __SIZE_TYPE__ size_t;
extern "C" {
[[gnu::interrupt]] extern void DMA1_Channel2_IRQHandler( void );
};
static PwmClass * pInstance = nullptr;
void DMA1_Channel2_IRQHandler( void ) {
DMA1_Type::INTFR_DEF state (DMA1.INTFR);
if (state.B.GIF2 != RESET) {
DMA1.INTFCR.B.CGIF2 = SET;
} else return;
if (state.B.HTIF2 != RESET) {
DMA1.INTFCR.B.CHTIF2 = SET;
if (pInstance) pInstance->send(false);
}
if (state.B.TCIF2 != RESET) {
DMA1.INTFCR.B.CTCIF2 = SET;
if (pInstance) pInstance->send(true);
}
}
/*
* initialize TIM2 for PWM
*/
inline void PwmClass::TimInit() noexcept {
// Enable GPIOA and TIM1
RCC.APB2PCENR.modify([] (RCC_Type::APB2PCENR_DEF & r) -> auto {
r.B.IOPAEN = SET;
r.B.IOPBEN = SET;
//r.B.AFIOEN = SET;
return r.R;
});
RCC.APB1PCENR.B.TIM2EN = SET;
// PA2 is TIM2_CH3, 10MHz Output alt func, push-pull
GPIOA.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto {
r.B.CNF2 = 2u;
r.B.MODE2 = 1u;
return r.R;
});
// PB1 is DEN, active H Output 10 MHz, push-pull
GPIOB.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto {
r.B.CNF1 = 0u;
r.B.MODE1 = 1u;
return r.R;
});
GPIOB.BSHR.B.BS1 = SET; // set to H
// Reset TIM2 to init all regs
RCC.APB1PRSTR.B.TIM2RST = SET;
RCC.APB1PRSTR.B.TIM2RST = RESET;
// CTLR1: default is up, events generated, edge align
// SMCFGR: default clk input is CK_INT
// Prescaler
TIM2.PSC.R = 0u; // 144 MHz
// Auto Reload - sets period
TIM2.ATRLR.R = MAXPWM - 1; // 24 kHz
// CH3 Mode is output, PWM1 (CC3S = 00, OC3M = 110)
TIM2.CHCTLR2_Output.modify([](TIM2_Type::CHCTLR2_Output_DEF & r) -> auto {
r.B.OC3M = 0x6u;
return r.R;
});
// Enable TIM1 outputs
TIM2.CCER.modify([](TIM2_Type::CCER_DEF & r) -> auto {
// Enable CH3, CH3 output, positive pol
r.B.CC3E = SET;
//r.B.CC3P = SET; // negative
return r.R;
});
// Reload immediately + Trigger DMA
TIM2.SWEVGR.B.UG = SET;
TIM2.DMAINTENR.B.UDE = SET;
}
inline void PwmClass::DmaInit() noexcept {
// Enable DMA
RCC.AHBPCENR.modify([](RCC_Type::AHBPCENR_DEF & r) -> auto {
r.B.SRAMEN = SET;
r.B.DMA1EN = SET;
return r.R;
});
// DMA can be configured to attach to T2UP
// The system can only DMA out at ~2.2MSPS. 2MHz is stable.
DMA1.CNTR2 .R = FULL_LEN;
DMA1.MADDR2.R = reinterpret_cast<size_t>(buffer);
DMA1.PADDR2.R = reinterpret_cast<size_t>(& TIM2.CH3CVR);
NVIC.EnableIRQ (DMA1_Channel2_IRQn);
DMA1.CFGR2.modify([](DMA1_Type::CFGR2_DEF & r) -> auto {
r.B.DIR = SET; // MEM2PERIPHERAL
r.B.PL = 2u; // High priority.
r.B.PSIZE = 1u; // 16-bit peripheral
r.B.MSIZE = 1u; // 16-bit memory
r.B.MINC = SET; // Increase memory.
r.B.CIRC = SET; // Circular mode.
r.B.HTIE = SET; // Half-trigger
r.B.TCIE = SET; // Whole-trigger
// Enable DMA1 CH2
r.B.EN = SET;
return r.R;
});
}
PwmClass::PwmClass() noexcept : count(0u), pL(buffer), pH(buffer + HALF_LEN), src(nullptr) {
pInstance = this;
TimInit ();
DmaInit ();
// Enable TIM2
TIM2.CTLR1.B.CEN = SET;
}

35
V203F6P6/blink/pwmclass.h Normal file
View file

@ -0,0 +1,35 @@
#ifndef PWMCLASS_H
#define PWMCLASS_H
#include "system.h"
#include "oneway.h"
static constexpr unsigned MAXPWM = 6000u;
/* Používá TIM2, PWM kanál 3, DMA1 kanál 2, přerušení DMA1_Channel2_IRQHandler */
class PwmClass {
static constexpr unsigned HALF_LEN = 24u;
static constexpr unsigned FULL_LEN = 2u * HALF_LEN;
volatile unsigned count;
uint16_t * const pL;
uint16_t * const pH;
uint16_t buffer [FULL_LEN];
OneWay<uint16_t> * src;
public:
explicit PwmClass () noexcept;
void attach (OneWay<uint16_t> & s) { src = & s; }
void send (const bool b) {
if (!src) return;
if (b) src->Send (pH, HALF_LEN);
else src->Send (pL, HALF_LEN);
if (count) count -= 1u;
}
void delay (const unsigned frames = 50u) {
count = frames;
while (count) {
asm volatile ("nop");
}
}
protected:
void DmaInit () noexcept;
void TimInit () noexcept;
};
#endif // PWMCLASS_H

60
V203F6P6/blink/utils.h Normal file
View file

@ -0,0 +1,60 @@
#ifndef UTILS_H
#define UTILS_H
typedef __SIZE_TYPE__ size_t;
template<class T, size_t N>constexpr size_t array_size (T (&) [N]) { return N; }
template<class T, const int N> class TABLE {
T data [N];
public:
/** @brief Konstruktor.
* @param f Ukazatel na constexpr funkci, která pak vytvoří tabulku.
* */
template<typename F> explicit constexpr TABLE (F f) noexcept {
for (int n=0; n<N; n++) data [n] = f (n);
}
/** operator[] vrátí konstantní odkaz na prvek pole, protože se předpokládá,
* že instance této třídy bude jako taková též konstantní
*/
const T & operator[] (const int index) const {
return data [index];
}
/** @class iterator
* @brief range-based for () */
class iterator {
const T * ptr;
public:
iterator(const T * _ptr) : ptr (_ptr) {}
iterator operator++ () { ++ptr; return * this; }
bool operator!= (const iterator & other) const { return ptr != other.ptr; }
const T & operator* () const { return * ptr; }
};
iterator begin () const { return iterator (data ); }
iterator end () const { return iterator (data + N); }
protected:
};
static constexpr double X_PI = 3.14159265358979323846;
static constexpr double D_PI = 2.0 * X_PI;
/* cmath nejde použít, tak si to mírně zjednodušíme, ale musí to fungovat */
static constexpr double dabs (const double a) { return a < 0.0 ? -a : +a; }
static constexpr int i_round (const double a) { return a < 0.0 ? int (a - 0.5) : int (a + 0.5); }
/* tahle divná funkce počítá sinus, pokud even=true i kosinus, pokud even=false */
static constexpr double sincos (const double x, const bool even) {
double result (0.0), element(1.0), divider(0.0);
if (even) { element *= x; divider += 1.0; }
constexpr double eps = 1.0e-9; // maximální chyba výpočtu
const double aa = - (x * x);
for (;;) {
result += element;
if (dabs (element) < eps) break;
divider += 1.0;
double fact = divider;
divider += 1.0;
fact *= divider;
element *= aa / fact;
}
return result;
}
#endif // UTILS_H