add disco

This commit is contained in:
Kizarm 2025-02-11 14:22:40 +01:00
parent 0d77388545
commit f00b7884f7
18 changed files with 943 additions and 0 deletions

2
.gitignore vendored
View file

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

60
V203F6P6/disco/Makefile Normal file
View file

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

133
V203F6P6/disco/adc.cpp Normal file
View file

@ -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<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 = 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);
}

23
V203F6P6/disco/adc.h Normal file
View file

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

1
V203F6P6/disco/ch32v203 Symbolic link
View file

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

1
V203F6P6/disco/common Symbolic link
View file

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

101
V203F6P6/disco/complex.h Normal file
View file

@ -0,0 +1,101 @@
//
// C++ Interface: complex
//
// Description:
//
//
// Author: Mrazik <mraz@seznam.cz>, (C) 2009
//
// Copyright: See COPYING file that comes with this distribution
//
//
#ifndef COMPLEX_H
#define COMPLEX_H
#include <math.h>
typedef short real;
typedef int dreal; // 2. násobná šířka
static constexpr unsigned HSHIFT = 8u * sizeof(real) - 1u;
/**
\class Complex
\author Mrazik <mrazik@volny.cz>
\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

7
V203F6P6/disco/config.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef _CONFIG_H
#define _CONFIG_H
static constexpr int FFTORDER = 8;
static constexpr unsigned FIFOLEN = 8u;
#endif // _CONFIG_H

197
V203F6P6/disco/fft.cpp Normal file
View file

@ -0,0 +1,197 @@
// fft.cpp - impelementation of class
// of fast Fourier transform - FFT
#include "fft.h"
#ifdef __linux__
#include <cmath>
#include <cstdio>
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<k; i++) { // další budou vždy polovinou předchozího úhlu
aa[i] = aa[i-1]; // tedy nastavíme předchozí úhel
aa[i] &= reone; // sečteme ho s reálnou jednotkou a normujeme
}
for (i=0; i<half; i++) {
xx.setc (1.0, 0.0); // nastavit základ, kterýn budeme rotovat = reálná jednotka
for (j=0; j<k; j++) { // procházíme jednotlivé bity indexu i
// zleva - pokud je nastaven násobíme příslušně otočenou jednotkou
if (i & (1 << (k-j-1))) xx *= aa[j];
}
// a sinus i kosinus je hotov
reo = (real) (coeff * xx.gre()); // znásobíme koeficientem
imo = (real) (coeff * xx.gim()); // a je to
Ones [i].setc (reo, imo);
}
delete [] aa;
}
void IFFT::Precompute() {
PrecomputeOrder();
PrecomputeOnes();
FILE * out = fopen("fftpre.cxx","w");
fprintf(out, "/* GENERATED FILE - DO NOT EDIT. */\n");
fprintf(out, "static const unsigned short Reverse [1 << FFTORDER] = {");
for (unsigned n=0u; n<N; n++) {
if ((n%8) == 0) fprintf(out, "\n");
fprintf(out, " 0x%04Xu,", Reverse[n]);
}
fprintf(out, "\n};\n");
/* Tímto debilním způsobem to dostanu do flash, nenašel jsem jak to udělat s complex */
fprintf(out, "struct point {short x,y;};\n");
fprintf(out, "static const point Ones [(1 << FFTORDER) >> 1] = {");
const unsigned H = N >> 1;
for (unsigned n=0u; n<H; n++) {
if ((n%4) == 0) fprintf(out, "\n");
complex c = Ones[n];
fprintf(out, " {%+6d,%+6d},", c.getr(), c.geti());
}
fprintf(out, "\n};\n");
fprintf(out, "static const unsigned short CosWindow [1 << FFTORDER] = {");
static constexpr double alpha = 2.0 * M_PI / double (1 << FFTORDER), ampl = double ((1l << 15) - 1);
for (unsigned n=0u; n<N; n++) {
if ((n%8) == 0) fprintf(out, "\n");
const double w = ampl * (1.0 - ::cos (double(n) * alpha));
fprintf(out, " 0x%04lXu,", ::lround(w));
}
fprintf(out, "\n};\n");
fclose(out);
}
int main () {
IFFT ifft;
ifft.Precompute();
return 0;
}
#else
#include "fftpre.cxx"
static_assert (sizeof(complex) == sizeof(point), "size failed");
void IFFT::Precompute () {}
void IFFT::PrecomputeOnes () {}
void IFFT::PrecomputeOrder () {}
void CopyToComplex (complex * dst, const unsigned short * src, const unsigned len) {
for (unsigned n=0u; n<len; n++) {
int k = src [n] - 0x800, m = CosWindow[n];
dst [n].setc ((k * m) >> 12, 0); // rozšířit z 12 na 16 bitů pro FFT
}
}
#endif
IFFT::IFFT() noexcept : reverse(Reverse), ones(reinterpret_cast<const complex * const> (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;
}
}
}

29
V203F6P6/disco/fft.h Normal file
View file

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

21
V203F6P6/disco/hack.c Normal file
View file

@ -0,0 +1,21 @@
#include <stdint.h>
#include <stdarg.h>
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<n; i++) d[i] = s[i];
return dest;
}
void *memset (void *s, int c, size_t n) {
char *p = (char *) s;
int i;
for (i=0; i<n; i++) p[i] = c;
return s;
}

52
V203F6P6/disco/main.cpp Normal file
View file

@ -0,0 +1,52 @@
#include "config.h"
#include "system.h"
#include "gpio.h"
#include "fifo.h"
#include "spectrum.h"
#include "ws2812b.h"
#include "adc.h"
#include "spiclass.h"
//////////////////////////////////////////////////////////////////////////
/* V mládí jsme stavěli různé "barevné hudby" což bylo pár analogových
* pásmových propustí a na nich pověšený usměrňovač plus budič (barevné)
* žárovky.
* Tady jsem to zkusil celé udělat digitálně. ADC procesoru analogový
* signál zdigitalizuje se vzorkovací frekvencí 8kHz, prožene se to rychlou
* Fourierovou transformací a ze vzniklého spektra se vyberou ty správné
* části, posčítají se amplitudy a to se předá do digitální LED.
* Asi by se s tím daly dělat zajímavé efekty, zde je to jen napodobenina
* toho analogového zažízení z mládí - 3 pásma pro basy, středy a výšky
* a ledky svítí jednou barvou - červená pro basy, zelená středy a modrá
* výšky. Míchat barvy do jedné ledky nevypadá dobře - je to poslední
* komplementání ledka v řetězci - svítí většinou bíle.
* to několik malých ďáblíků. Ledky se chovají jinak než žárovky.
* Žárovka potřebuje určitý malý výkon aby vůbec jen trochu svítila.
* Ledka svítí i při nepatrném proudu a působí to rušivě. Lze to odstranit
* zavedením malého ofsetu. Ledka také nemá setrvačnost, takže to může
* blikat velice divoce. Setrvačnost by šlo zavést programově, času je dost,
* ale nepovažoval jsem to za nutné.
* Rozdělení na pásma je proti analogu dost striktní. Na výšky je rezervováno
* celé pásmo od 2 do 4kHz, ukazuje se však, že tam toho signálu stejně moc
* není. Celé to nastavit je poměrně dost práce, zde to vyžaduje přibližně
* úroveň linkového signálu ze zvukovky. Původně jsem předpokládal, že
* bude nutné do ledek předávat něco jako logaritmus amplitudy signálu,
* běžně se to tak FFT dělá a je to docela logické. Jenže takhle to nefunguje.
* Lidský sluch i zrak mají citlivost přibližně logaritmickou a svit ledky
* by pak byl vlastně komprimován dvakrát, z čehož je zrak pak zmaten.
*/////////////////////////////////////////////////////////////////////////
static GpioClass led (GPIOB, 8); // indikace běhu FFT
static FIFO<uint32_t, FIFOLEN> 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;
}

View file

@ -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<blow; n++) qa += buffer[n].norm();
cred = saturate (qa >> 0, 0x200u);
qa = 0u;
for (unsigned n=blow; n<bmiddle; n++) qa += buffer[n].norm();
cgreen = saturate (qa >> 4);
qa = 0u;
for (unsigned n=bmiddle; n<bhigh; n++) qa += buffer[n].norm();
cblue = saturate (qa >> 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;
}

24
V203F6P6/disco/spectrum.h Normal file
View file

@ -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<uint16_t> {
static constexpr unsigned FFTSIZE = 1 << FFTORDER;
GpioClass & led;
FIFO<uint32_t, FIFOLEN> & ring;
const unsigned bhigh, bmiddle, blow;
uint8_t cred, cgreen, cblue;
const IFFT ifft;
complex buffer [FFTSIZE];
public:
explicit Spectrum (FIFO<uint32_t, FIFOLEN> & ff, GpioClass & io) noexcept : OneWay<uint16_t>(), 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

118
V203F6P6/disco/spiclass.cpp Normal file
View file

@ -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<uint8_t> & base) noexcept : driver (& base), /*ptrl (buffer),*/ ptrh (buffer + PADDING) {
pSpiInstance = this;
for (unsigned n=0u; n<FULL_LEN; n++) buffer[n] = 0u;
Color * ptr = reinterpret_cast<Color*>(ptrh);
const OneColor oz(0x00);
for (unsigned n=0; n<NUMLEDS; n++) {
Color & c = ptr [n];
c.b = oz; c.g = oz; c.r = oz;
}
}
void SpiClass::Init() {
RCC.APB2PCENR.modify([](RCC_Type::APB2PCENR_DEF & r) -> 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<size_t> (& SPI1.DATAR);
// Configure the memory address
DMA1.MADDR3.R = reinterpret_cast<size_t> (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;
}

23
V203F6P6/disco/spiclass.h Normal file
View file

@ -0,0 +1,23 @@
#ifndef SPICLASS_H
#define SPICLASS_H
#include <stdint.h>
#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<uint8_t> * driver;
//uint8_t * const ptrl;
uint8_t * const ptrh;
uint8_t buffer [FULL_LEN];
public:
explicit SpiClass (OneWay<uint8_t> & base) noexcept;
void Init ();
void drq ();
protected:
};
#endif // SPICLASS_H

View file

@ -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<m; k++) {
buf [i++] = x & (1u << k) ? '1' : '0';
}
return i;
};
unsigned n = 0;
n += f (b0, ptr + n);
n += f (b1, ptr + n);
n += f (b2, ptr + n);
n += f (b3, ptr + n);
n += f (b4, ptr + n);
n += f (b5, ptr + n);
n += f (b6, ptr + n);
n += f (b7, ptr + n);
ptr [n] = '\0';
return n;
}
unsigned int ws2812b::Send (uint8_t * const ptr, const unsigned int len) {
uint32_t cmd;
while (ring.Read(cmd)) {
const Entry ne (cmd);
if (ne.ws.order < NUMLEDS) {
Color * cptr = reinterpret_cast<Color*>(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;
}

55
V203F6P6/disco/ws2812b.h Normal file
View file

@ -0,0 +1,55 @@
#ifndef WS2812B_H
#define WS2812B_H
#include <stdint.h>
#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 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<uint8_t> {
FIFO<uint32_t,FIFOLEN> & ring;
public:
explicit ws2812b (FIFO<uint32_t,FIFOLEN> & r) noexcept : OneWay<uint8_t> (), ring(r) {}
unsigned int Send (uint8_t * const ptr, const unsigned int len) override;
protected:
};
#endif // WS2812B_H