diff --git a/.gitignore b/.gitignore index 34f99fd..08d27c5 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ V203/usb/scope/software/ui_mainwindow.h V203/usb/spitest/* V203F6P6/programmer/software/programmer minichlink/minichlink +V203F6P6/disco/fftpre.cxx +V203F6P6/disco/precomp diff --git a/V203F6P6/disco/Makefile b/V203F6P6/disco/Makefile new file mode 100644 index 0000000..40fdc21 --- /dev/null +++ b/V203F6P6/disco/Makefile @@ -0,0 +1,60 @@ +TARGET?= ch32v203 + +TOOL ?= gcc +#TOOL ?= clang + +PRJ = example + +VPATH = . ./$(TARGET) +BLD = ./build/ +DFLAGS = -d +LFLAGS = -g +LDLIBS = +BFLAGS = --strip-unneeded + +CFLAGS = -MMD -Wall -Wno-parentheses -ggdb -fno-exceptions -ffunction-sections -fdata-sections +CFLAGS+= -I. -I./$(TARGET) -I./common +DEL = rm -f + +# zdrojaky +OBJS = main.o hack.o fft.o +OBJS += adc.o spiclass.o +OBJS += ws2812b.o spectrum.o + +include $(TARGET)/$(TOOL).mk +BOBJS = $(addprefix $(BLD),$(OBJS)) + +all: $(BLD) $(PRJ).elf +# ... atd. +-include $(BLD)*.d +# linker +$(PRJ).elf: $(BOBJS) + -@echo [LD $(TOOL),$(TARGET)] $@ + @$(LD) $(LFLAGS) -o $(PRJ).elf $(BOBJS) $(LDLIBS) + -@echo "size:" + @$(SIZE) $(PRJ).elf + -@echo "listing:" + $(DUMP) $(DFLAGS) $(PRJ).elf > $(PRJ).lst + -@echo "OK." + $(COPY) $(BFLAGS) -O binary $(PRJ).elf $(PRJ).bin +# preloz co je potreba +$(BLD)%.o: %.c + -@echo [CC $(TOOL),$(TARGET)] $@ + @$(CC) -std=gnu99 -c $(CFLAGS) $< -o $@ +$(BLD)%.o: %.cpp + -@echo [CX $(TOOL),$(TARGET)] $@ + @$(CXX) -std=c++17 -fno-rtti -c $(CFLAGS) $< -o $@ +$(BLD): + mkdir $(BLD) +flash: $(PRJ).elf + minichlink -w $(PRJ).bin flash -b + +$(BLD)fft.o: fftpre.cxx fft.cpp fft.h config.h +./precomp: fft.cpp config.h + g++ -std=c++17 -Wall -Os fft.cpp -o precomp +fftpre.cxx: ./precomp + ./precomp +# vycisti +clean: + $(DEL) $(BLD)* *.lst *.bin *.elf *.map *~ precomp fftpre.cxx +.PHONY: all clean flash diff --git a/V203F6P6/disco/adc.cpp b/V203F6P6/disco/adc.cpp new file mode 100644 index 0000000..6f0e538 --- /dev/null +++ b/V203F6P6/disco/adc.cpp @@ -0,0 +1,133 @@ +#include "system.h" +#include "oneway.h" +#include "adc.h" + +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 = 0u; // CH0 + //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 +} +typedef __SIZE_TYPE__ size_t; +inline void AdcDma::Dma1Ch1Init () noexcept { + // 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 = ADC_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; + }); +} +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; + }); +} +//////////////////////////////////////////////////////////////////////////////////// +AdcDma::AdcDma() noexcept : pL (buffer), pH (buffer + ADC_HALF_LEN), dst (nullptr) { + pInstance = this; + EnableClock (); + Timer3Init (125u); + NVIC.EnableIRQ (DMA1_Channel1_IRQn); + AdcCalibrate(); + Dma1Ch1Init (); + AdcPostInit (); + // start timer + TIM3.CTLR1.B.CEN = SET; +} +inline void AdcDma::send(const bool b) { + if (!dst) return; + if (b) dst->Send (pH, ADC_HALF_LEN); + else dst->Send (pL, ADC_HALF_LEN); +} diff --git a/V203F6P6/disco/adc.h b/V203F6P6/disco/adc.h new file mode 100644 index 0000000..355d7bb --- /dev/null +++ b/V203F6P6/disco/adc.h @@ -0,0 +1,23 @@ +#ifndef ADCDMA_H +#define ADCDMA_H +#include +#include "oneway.h" +#include "config.h" + +class AdcDma { + static constexpr unsigned ADC_HALF_LEN = 1u << FFTORDER; + static constexpr unsigned ADC_FULL_LEN = ADC_HALF_LEN * 2u; + + uint16_t * pL; + uint16_t * pH; + uint16_t buffer [ADC_FULL_LEN]; + OneWay * dst; + public: + explicit AdcDma () noexcept; + void attach (OneWay & d) { dst = & d; } + void send (const bool b); + protected: + void Dma1Ch1Init (); +}; + +#endif // ADCDMA_H diff --git a/V203F6P6/disco/ch32v203 b/V203F6P6/disco/ch32v203 new file mode 120000 index 0000000..7650c85 --- /dev/null +++ b/V203F6P6/disco/ch32v203 @@ -0,0 +1 @@ +../ch32v203/ \ No newline at end of file diff --git a/V203F6P6/disco/common b/V203F6P6/disco/common new file mode 120000 index 0000000..8332399 --- /dev/null +++ b/V203F6P6/disco/common @@ -0,0 +1 @@ +../common/ \ No newline at end of file diff --git a/V203F6P6/disco/complex.h b/V203F6P6/disco/complex.h new file mode 100644 index 0000000..1f88175 --- /dev/null +++ b/V203F6P6/disco/complex.h @@ -0,0 +1,101 @@ +// +// C++ Interface: complex +// +// Description: +// +// +// Author: Mrazik , (C) 2009 +// +// Copyright: See COPYING file that comes with this distribution +// +// +#ifndef COMPLEX_H +#define COMPLEX_H + +#include +typedef short real; +typedef int dreal; // 2. násobná šířka + +static constexpr unsigned HSHIFT = 8u * sizeof(real) - 1u; + +/** + \class Complex + + \author Mrazik + + \brief Komplexní číslo a práce s ním + + Zkrácená verze pracující s 16-bitovými integer. Pouze nutné metody pro FFT. + + Je to základní datová položka filtru. Všechny metody jsou jednoduché, takže je možné použít inline funkce. */ +class complex { + private: + real re; //!< reálná část + real im; //!< imaginární část + public: + /** + * Konstruktor + * @param x reálná část + * @param y imaginární část + */ + explicit constexpr complex (const real x, const real y) noexcept : re(x), im(y) {} + /** + * Default konstruktor (nastaví re=0.0,im=0.0) + */ + explicit constexpr complex () noexcept : re(0.0), im(0.0) {} + // constexpr complex (const complex & o) noexcept : re(o.re), im(o.im) {} + /** + * Sčítání + * @param a odkaz na sčítanec + * @param b odkaz na sčítanec + */ + void add (const complex & a, const complex & b) { + re = a.re + b.re; + im = a.im + b.im; + } + /** + * Odčítání + * @param a odkaz na číslo, od něhož se odčítá + * @param b odkaz na číslo, jež se odčítá + */ + void sub (const complex & a, const complex & b) { + re = a.re - b.re; + im = a.im - b.im; + } + /** + * Násobení - integer verze výsledek zmenší 2^16-krát. Čili 16.bit*16.bit=32.bit + * a vezme se jen horní 16.bitová část, zbytek se zahodí. + * @param a odkaz na násobenec + * @param b odkaz na násobitel + */ + void operator*= (const complex & a) { + real rt; + const dreal ar(a.re), br(re), ai(a.im), bi(im); + rt = (ar * br - ai * bi) >> HSHIFT; + im = (ar * bi + ai * br) >> HSHIFT; + re = rt; + } + void operator>>= (const real i) { + re >>= i; + im >>= i; + } + void setc (const real x, const real y) { + re = x; + im = y; + } + real getr (void) const { + return re; + } + real geti (void) const { + return im; + } + void conj (void) { + im = -im; + } + dreal norm () const { + const dreal a = re, b = im; + return a*a + b*b; + } +}; + +#endif diff --git a/V203F6P6/disco/config.h b/V203F6P6/disco/config.h new file mode 100644 index 0000000..f7a63fb --- /dev/null +++ b/V203F6P6/disco/config.h @@ -0,0 +1,7 @@ +#ifndef _CONFIG_H +#define _CONFIG_H + +static constexpr int FFTORDER = 8; +static constexpr unsigned FIFOLEN = 8u; + +#endif // _CONFIG_H diff --git a/V203F6P6/disco/fft.cpp b/V203F6P6/disco/fft.cpp new file mode 100644 index 0000000..8e2ad20 --- /dev/null +++ b/V203F6P6/disco/fft.cpp @@ -0,0 +1,197 @@ +// fft.cpp - impelementation of class +// of fast Fourier transform - FFT + +#include "fft.h" +#ifdef __linux__ +#include +#include +class ComplexDouble { + private: + double re; + double im; + public: + explicit constexpr ComplexDouble () noexcept : re(0.0), im(0.0) {} //!< konstruktor + explicit constexpr ComplexDouble (const double x, const double y) noexcept : re(x), im(y) {} //!< konstruktor + void setc (double x, double y) {re = x; im = y;}; //!< nastavení hodnoty + void operator&= (const ComplexDouble & x) { //!< přičti a normuj na jednotku + re += x.re; + im += x.im; + double a (re*re + im*im); + a = 1.0 / ::sqrt (a); + re *= a; + im *= a; + }; + void operator*= (const ComplexDouble & x) { //!< znásob komplexním číslem + const double a(x.re*re - x.im*im); + im = x.re*im + x.im*re; + re = a; + }; + double gre (void) const {return re;}; //!< návrat reálné části + double gim (void) const {return im;}; //!< návrat imaginární části +}; + + +static unsigned short Reverse [1 << FFTORDER]; +static complex Ones [(1 << FFTORDER) >> 1]; +// s tímhle nebude potřeba se zabývat - prosté přehození pořadí bitů +// Zde do pole pouze pro zrychlení (není pro to instrukce procesoru) +void IFFT::PrecomputeOrder (void) { + const unsigned int half = N / 2; + const unsigned int last = N - 1; + unsigned j = 0u; + for (unsigned i = 0; i < last; i++) { + Reverse [i] = j; + // printf ("i = %d, j = %d\n", i, j); + unsigned k = half; + while (k <= j) { + j -= k; + k /= 2; + } + j += k; + } + Reverse [last] = last; +} +void IFFT::PrecomputeOnes (void) { + static constexpr double coeff = double((1UL << (HSHIFT)) - 1); + const unsigned int half = N >> 1; + unsigned int i,j,k; + real reo, imo; + // Tady to spočteme trochu jinak, bez použití sin a cos (ale stejně je potřeba sqrt) + ComplexDouble reone (1.0, 0.0), xx; // reálná jednotka, pomocná proměnná + ComplexDouble * aa = new ComplexDouble [M]; // pole komplexních jednotek - základní rotace + k = M-1; + aa[0].setc (0.0, 1.0); // nultá rotace je o 90st. - komplexní jednotka + for (i=1; i> 1] = {"); + const unsigned H = N >> 1; + for (unsigned n=0u; n> 12, 0); // rozšířit z 12 na 16 bitů pro FFT + } +} +#endif + +IFFT::IFFT() noexcept : reverse(Reverse), ones(reinterpret_cast (Ones)) { +} + +// FORWARD FOURIER TRANSFORM, INPLACE VERSION +// Data - both input data and output +bool IFFT::Forward (complex * const Data) const { + if (!Data) return false; + Rearrange (Data); + Perform (Data, false); + return true; +} +// INVERSE FOURIER TRANSFORM, INPLACE VERSION +// Data - both input data and output +bool IFFT::Inverse (complex * const Data) const { + if (!Data) return false; + Rearrange (Data); + Perform (Data, true); + return true; +} +// Změna pořadí ve vstupním poli +void IFFT::Rearrange (complex * const Data) const { + unsigned int Target, Position; + complex Temp; + for (Position = 0; Position < N; Position++) { + Target = reverse [Position]; + if (Target > Position) { + Temp = Data [Target]; + Data [Target] = Data [Position]; + Data [Position] = Temp; + } + } +} + +// FFT implementation +void IFFT::Perform (complex * const Data, const bool Inverse) const { + unsigned int Dest, Step, Jump, Group, Pair, Incr, Roti; + complex Factor, Produc; + static constexpr real Shift = 1; // Úprava velikosti při forwardu, jinak to přeteče + + for (Step = 1; Step < N; Step <<= 1) { // 1,2,...N/2 + // printf ("1.cyklus Step=%4d\n", Step); + Jump = Step << 1; // 2,4,...N + Incr = N / Jump; // N/2....1 + Roti = 0; + for (Group = 0; Group < Step; Group++) { + // printf (" 2.cyklus Group=%4d, rotace=%4d\n", Group, Roti); + for (Pair = Group; Pair < N; Pair += Jump) { + Dest = Pair + Step; + Factor = ones [Roti]; + Produc = Data [Pair]; + if (!Inverse) { + Factor.conj(); + Factor >>= Shift; // shifty s integer doprava o 1 bit + Produc >>= Shift; // Zároveň normalizace (není potřeba při reverzi). + } + Factor *= Data [Dest]; + // printf (" 3.cyklus Pair=%4d, Dest =%4d\n", Pair, Dest); + Data [Pair].add (Produc, Factor); + Data [Dest].sub (Produc, Factor); + } + Roti += Incr; + } + } +} diff --git a/V203F6P6/disco/fft.h b/V203F6P6/disco/fft.h new file mode 100644 index 0000000..669bc62 --- /dev/null +++ b/V203F6P6/disco/fft.h @@ -0,0 +1,29 @@ +// fft.h - declaration of class +// of fast Fourier transform - FFT +// + +#ifndef _FFT_H_ +#define _FFT_H_ + +// Include Complex numbers header +#include "config.h" +#include "complex.h" + +class IFFT { + static constexpr unsigned int M = FFTORDER, N = 1u << FFTORDER; + public: + explicit IFFT () noexcept; + bool Forward (complex * const Data) const; + bool Inverse (complex * const Data) const; + void Precompute (); + protected: + void Rearrange (complex * const Data) const; + void Perform (complex * const Data, const bool Inverse = false) const; + void PrecomputeOrder (); + void PrecomputeOnes (); + private: + const unsigned short * const reverse; + const complex * const ones; +}; +extern void CopyToComplex (complex * dst, const unsigned short * src, const unsigned len); +#endif diff --git a/V203F6P6/disco/hack.c b/V203F6P6/disco/hack.c new file mode 100644 index 0000000..391b7bf --- /dev/null +++ b/V203F6P6/disco/hack.c @@ -0,0 +1,21 @@ +#include +#include +typedef __SIZE_TYPE__ size_t; +size_t strlen (const char *s) { + size_t l = 0; + while (*s++) l++; + return l; +} +void *memcpy (void *dest, const void *src, size_t n) { + const char *s = (const char *) src; + char *d = (char *) dest; + int i; + for (i=0; i ring; // výměna barvy led +static AdcDma adc; // zdroj signálu +static Spectrum spectrum (ring, led);// vlastní FFT + vyhodnocení +static ws2812b chain (ring); // řetězec led WS2812B +static SpiClass spi (chain); // ovládání tt. retězce +/////////////////////////////////////////////////////////////////////////// +int main () { + led << true; + adc.attach(spectrum); + spi.Init (); + for (;;) { + /* do nothing */ + } + return 0; +} diff --git a/V203F6P6/disco/spectrum.cpp b/V203F6P6/disco/spectrum.cpp new file mode 100644 index 0000000..1995e4f --- /dev/null +++ b/V203F6P6/disco/spectrum.cpp @@ -0,0 +1,46 @@ +#include "ws2812b.h" +#include "spectrum.h" + +static uint8_t saturate (const unsigned k, const unsigned ofs = 0x100u) { + if (k < ofs) return 0u; // začátek je třeba trochu posunout + const unsigned x = k - ofs; // jinak ledky trochu svítí - žárovka tuto vlastnost nemá + if (x >= (1u << 16)) return 0xffu; // zasarutuj + unsigned w, y = 0xffu; // aproximaci začneme od 255 + for (unsigned n=0u; n<6u; n++) { // končí cca za 3-4 iterace, max. 6 + w = (y + x/y) >> 1; // Newtonova metoda sqrt + if (y == w) break; // konec iterace - lepší už to nebude + else y = w; + } + return y; // výsledek +} + +unsigned int Spectrum::Send(uint16_t * const ptr, const unsigned int len) { + led << true; + // procesor je rychlý, zvládne FFT i s kosinovým oknem v přerušení + CopyToComplex (buffer, ptr, len); + ifft.Forward (buffer); + // vyhodnotí barvy pro basy, středy a výšky + unsigned qa = 0u; + for (unsigned n=1u; n> 0, 0x200u); + qa = 0u; + for (unsigned n=blow; n> 4); + qa = 0u; + for (unsigned n=bmiddle; n> 2); + // uloží barvy do ledek + Entry l0(0), l1(0), l2(0), l3(0); + l0.ws.order = 0; l0.ws.r = cred; + l1.ws.order = 1; l1.ws.g = cgreen; + l2.ws.order = 2; l2.ws.b = cblue; + l3.ws.order = 3; l3.ws.r = 0xffu - cred; l3.ws.g = 0xffu - cgreen; l3.ws.b = 0xffu - cblue; + // a frontou pošle na výstup + ring.Write (l0.number); + ring.Write (l1.number); + ring.Write (l2.number); + ring.Write (l3.number); + // změří čas výpočtu (pod 0.5ms, rámec je 32ms) + led << false; + return len; +} diff --git a/V203F6P6/disco/spectrum.h b/V203F6P6/disco/spectrum.h new file mode 100644 index 0000000..9a7be51 --- /dev/null +++ b/V203F6P6/disco/spectrum.h @@ -0,0 +1,24 @@ +#ifndef SPECTRUM_H +#define SPECTRUM_H +#include "oneway.h" +#include "gpio.h" +#include "fifo.h" +#include "fft.h" + +/** + */ +class Spectrum : public OneWay { + static constexpr unsigned FFTSIZE = 1 << FFTORDER; + GpioClass & led; + FIFO & ring; + const unsigned bhigh, bmiddle, blow; + uint8_t cred, cgreen, cblue; + const IFFT ifft; + complex buffer [FFTSIZE]; + public: + explicit Spectrum (FIFO & ff, GpioClass & io) noexcept : OneWay(), led(io), ring(ff), + bhigh (FFTSIZE >> 1), bmiddle(bhigh >> 1), blow (bmiddle >> 4), cred(0u), cgreen(0u), cblue(0u), ifft() {} + unsigned int Send(uint16_t * const ptr, const unsigned int len) override; +}; + +#endif // SPECTRUM_H diff --git a/V203F6P6/disco/spiclass.cpp b/V203F6P6/disco/spiclass.cpp new file mode 100644 index 0000000..30192e2 --- /dev/null +++ b/V203F6P6/disco/spiclass.cpp @@ -0,0 +1,118 @@ +#include "system.h" +#include "spiclass.h" +typedef __SIZE_TYPE__ size_t; + +enum SPICLK : uint32_t { + FPCLK_2 = 0u, // 72 MHz + FPCLK_4, // 36 MHz + FPCLK_8, // 18 MHz + FPCLK_16, // 9 MHz + FPCLK_32, // 4.5 MHz + FPCLK_64, // 2.25 MHz + FPCLK_128, // 1.125 MHz + FPCLK_256, // 0.5625 MHz +}; + +static SpiClass * pSpiInstance = nullptr; +extern "C" { +//[[gnu::interrupt]] extern void DMA1_Channel2_IRQHandler(); + [[gnu::interrupt]] extern void DMA1_Channel3_IRQHandler(); + extern void * memcpy (void * dest, const void * src, size_t n); +}; +void DMA1_Channel3_IRQHandler() { // transmit channel + if (pSpiInstance) pSpiInstance->drq(); +} +static constexpr unsigned FM = 3u; // 50 MHz +static void InitPins () noexcept { + // PA4 - NSS, PA5 - SCK, PA6 - MISO, PA7 - MOSI + GPIOA.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> uint32_t { + /*r.B.MODE4 = FM; + r.B.CNF4 = 2u; // alt push - pull + r.B.MODE6 = 0u; // input mode + r.B.CNF6 = 1u; // floating */ + r.B.MODE5 = FM; + r.B.CNF5 = 2u; // alt push - pull + r.B.MODE7 = FM; + r.B.CNF7 = 2u; // alt push - pull + return r.R; + }); + // AFIO - default +} +void SpiClass::drq() { + if (!driver) return; + DMA1_Type::INTFR_DEF state (DMA1.INTFR); + if (state.B.GIF3 != RESET) { // Zřejmě zbytečné, ale pokud používám víc DMA + DMA1.INTFCR.B.CGIF3 = SET; // kanálů, pak to tak má být. + } else return; // Událost nevznikla pro kanál 1. +/*if (state.B.HTIF3) { + DMA1.INTFCR.B.CHTIF3 = SET; // clear half + } */ + if (state.B.TCIF3) { + DMA1.INTFCR.B.CTCIF3 = SET; // clear complete + driver->Send (ptrh, LEDS_LEN); + } +} + +SpiClass::SpiClass(OneWay & base) noexcept : driver (& base), /*ptrl (buffer),*/ ptrh (buffer + PADDING) { + pSpiInstance = this; + for (unsigned n=0u; n(ptrh); + const OneColor oz(0x00); + for (unsigned n=0; n uint32_t { + r.B.SPI1EN = SET; + r.B.IOPAEN = SET; + r.B.AFIOEN = SET; + return r.R; + }); + RCC.AHBPCENR.B.DMA1EN = SET; + InitPins(); + // Configure the peripheral data register address + DMA1.PADDR3.R = reinterpret_cast (& SPI1.DATAR); + // Configure the memory address + DMA1.MADDR3.R = reinterpret_cast (buffer); + // Configure the number of DMA tranfer to be performs on DMA channel 3 + DMA1.CNTR3 .R = FULL_LEN; + // Configure increment, size, interrupts and circular mode + DMA1.CFGR3.modify([] (DMA1_Type::CFGR3_DEF & r) -> uint32_t { + r.B.PL = 3u; // highest priority + r.B.DIR = SET; // memory -> periferal + r.B.MINC = SET; // memory increment + r.B.MSIZE = 0u; // 8-bit + r.B.PSIZE = 0u; // 8-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; + }); + SPI1.CTLR1.modify([](SPI1_Type::CTLR1_DEF & r) -> uint32_t { + r.B.CPHA = RESET; + r.B.CPOL = RESET; + r.B.MSTR = SET; + r.B.DFF = RESET; // 8 bit + r.B.SSM = SET; + r.B.SSI = SET; + r.B.LSBFIRST = SET; + /* 2.25 MHz - 1bit = 444 ns + * 1 LED => 9 x 8 x 0.444 = 32 us DMA celkem (10 + 4) x 32 = 0.448 ms + * */ + r.B.BR = FPCLK_64; + return r.R; + }); + SPI1.CTLR2.modify([](SPI1_Type::CTLR2_DEF & r) -> uint32_t { + r.B.SSOE = SET; + //r.B.RXNEIE = SET; + //r.B.TXEIE = SET; + r.B.TXDMAEN = SET; + return r.R; + }); + NVIC.EnableIRQ(DMA1_Channel3_IRQn); + SPI1.CTLR1.B.SPE = SET; +} diff --git a/V203F6P6/disco/spiclass.h b/V203F6P6/disco/spiclass.h new file mode 100644 index 0000000..ec104ee --- /dev/null +++ b/V203F6P6/disco/spiclass.h @@ -0,0 +1,23 @@ +#ifndef SPICLASS_H +#define SPICLASS_H +#include +#include "ws2812b.h" +/** + */ +static constexpr unsigned PADDING = 10 * sizeof (Color); +static constexpr unsigned LEDS_LEN = NUMLEDS * sizeof (Color); +static constexpr unsigned FULL_LEN = PADDING + LEDS_LEN; + +class SpiClass { + OneWay * driver; +//uint8_t * const ptrl; + uint8_t * const ptrh; + uint8_t buffer [FULL_LEN]; + public: + explicit SpiClass (OneWay & base) noexcept; + void Init (); + void drq (); + protected: +}; + +#endif // SPICLASS_H diff --git a/V203F6P6/disco/ws2812b.cpp b/V203F6P6/disco/ws2812b.cpp new file mode 100644 index 0000000..2b41600 --- /dev/null +++ b/V203F6P6/disco/ws2812b.cpp @@ -0,0 +1,50 @@ +#include "ws2812b.h" + +enum WS2812B_SPI : uint32_t { + BIT_LOW = 1u, BIT_HIGH = 3u, +}; + +OneColor::OneColor(const uint8_t color) noexcept { + b0 = (color & 0x80u) ? BIT_HIGH : BIT_LOW; + b1 = (color & 0x40u) ? BIT_HIGH : BIT_LOW; + b2 = (color & 0x20u) ? BIT_HIGH : BIT_LOW; + b3 = (color & 0x10u) ? BIT_HIGH : BIT_LOW; + b4 = (color & 0x08u) ? BIT_HIGH : BIT_LOW; + b5 = (color & 0x04u) ? BIT_HIGH : BIT_LOW; + b6 = (color & 0x02u) ? BIT_HIGH : BIT_LOW; + b7 = (color & 0x01u) ? BIT_HIGH : BIT_LOW; +} +unsigned OneColor::to_string(char * ptr, const int m) { + auto f = [=](const uint32_t x, char * buf) -> unsigned { + unsigned i = 0; + for (int k=0; k(ptr); + Color & c = cptr [ne.ws.order]; + const OneColor cb (ne.ws.b), cg (ne.ws.g), cr (ne.ws.r); + c.b = cb; c.g = cg; c.r = cr; + } + } + return len; +} diff --git a/V203F6P6/disco/ws2812b.h b/V203F6P6/disco/ws2812b.h new file mode 100644 index 0000000..10b77ea --- /dev/null +++ b/V203F6P6/disco/ws2812b.h @@ -0,0 +1,55 @@ +#ifndef WS2812B_H +#define WS2812B_H +#include +#include "config.h" +#include "oneway.h" +#include "fifo.h" +static constexpr int BWW = 3; +struct OneColor { + uint32_t b0 : BWW; + uint32_t b1 : BWW; + uint32_t b2 : BWW; + uint32_t b3 : BWW; + uint32_t b4 : BWW; + uint32_t b5 : BWW; + uint32_t b6 : BWW; + uint32_t b7 : BWW; + explicit OneColor (const uint8_t c) noexcept; + unsigned to_string (char * ptr, const int m = BWW); +}__attribute__((packed)); +struct Color { + OneColor g,r,b; +}__attribute__((packed)); +union Entry { + struct _ws { + uint8_t g,r,b; + uint8_t order; // určuje pořadí LED + } ws; + uint32_t number; + explicit Entry (const uint32_t e) noexcept { number = e; } +}; +/*************************************************************************************/ +static constexpr unsigned NUMLEDS = 4u; +/*************************************************************************************/ +/** @class ws2812b + * @brief Driver pro WS2812B + * On ten driver je trochu divný. Běží nad SPI s použitím pinu MOSI, 1 bit barvy + * jsou 3 bity SPI. Funguje to tak, že SPI běží přes DMA v cirkulárním módu, tedy + * pořád, v 1. části dělá "reset", tedy časování rámce (vysílá nuly). + * V 2. části si vybere z fronty ring data a uloží je do buferu ve správném + * tvaru. Využívá se toho, že přerušení přijde až na konci a naplnění daty + * netrvá dlouho, takže se přepisuje jen část, která se právě nevysílá. + * Fronta byla použita (celkem zbytečně) protože zatím netuším jaká data posílat. + * NOTE + * Protože WS2812B je 5V záležitost a procesor je napájen jen 3.3V, funguje to + * na hraně a je dobré zapojit mezi MOSI a +5V rezistor 1k. Je to pak stabilnější. + * */ +class ws2812b : public OneWay { + FIFO & ring; + public: + explicit ws2812b (FIFO & r) noexcept : OneWay (), ring(r) {} + unsigned int Send (uint8_t * const ptr, const unsigned int len) override; + protected: +}; + +#endif // WS2812B_H