diff --git a/V203/common/baselayer.h b/V203/common/baselayer.h new file mode 100644 index 0000000..9a90cc6 --- /dev/null +++ b/V203/common/baselayer.h @@ -0,0 +1,74 @@ +#ifndef BASELAYER_H +#define BASELAYER_H +#include + +#ifdef __arm__ +#define debug(...) +#else // ARCH_CM0 +#ifdef DEBUG +#define debug printf +#else // DEBUG +#define debug(...) +#endif // DEBUG +#endif // ARCH_CM0 +/** @brief Bázová třída pro stack trochu obecnějšího komunikačního protokolu. + * + * @class BaseLayer + * @brief Od této třídy budeme dále odvozovat ostatní. + * +*/ +class BaseLayer { + public: + /** Konstruktor + */ + explicit constexpr BaseLayer () noexcept : pUp(nullptr), pDown(nullptr) {}; + /** Virtuální metoda, přesouvající data směrem nahoru, pokud s nimi nechceme dělat něco jiného. + @param data ukazatel na pole dat + @param len delka dat v bytech + @return počet přenesených bytů + */ + virtual uint32_t Up (const char * data, const uint32_t len) { + if (pUp) return pUp->Up (data, len); + return 0; + }; + /** Virtuální metoda, přesouvající data směrem dolů, pokud s nimi nechceme dělat něco jiného. + @param data ukazatel na pole dat + @param len delka dat v bytech + @return počet přenesených bytů + */ + virtual uint32_t Down (const char * data, const uint32_t len) { + if (pDown) return pDown->Down (data, len); + return len; + }; + /** @brief Zřetězení stacku. + * Tohle je vlastně to nejdůležitější. V čistém C by se musely + * nastavovat ukazatele na callback funkce, tady je to čitší - pro uživatele neviditelné, + * ale je to to samé. + @param bl Třída, ležící pod, spodní + @return Odkaz na tuto třídu (aby se to dalo řetězit) + */ + virtual BaseLayer & operator += (BaseLayer & bl) { + bl.setUp (this); // ta spodní bude volat při Up tuto třídu + setDown (& bl); // a tato třída bude volat při Down tu spodní + return * this; + }; + /** Getter pro pDown + @return pDown + */ + BaseLayer * getDown (void) const { return pDown; }; + protected: + /** Lokální setter pro pUp + @param p Co budeme do pUp dávat + */ + void setUp (BaseLayer * p) { pUp = p; }; + /** Lokální setter pro pDown + @param p Co budeme do pDown dávat + */ + void setDown (BaseLayer * p) { pDown = p; }; + private: + // Ono to je vlastně oboustranně vázaný spojový seznam. + BaseLayer * pUp; //!< Ukazatel na třídu, která bude dále volat Up + BaseLayer * pDown; //!< Ukazatel na třídu, která bude dále volat Down +}; + +#endif // BASELAYER_H diff --git a/V203/common/fifo.h b/V203/common/fifo.h new file mode 100644 index 0000000..f3f4651 --- /dev/null +++ b/V203/common/fifo.h @@ -0,0 +1,73 @@ +#ifndef FIFO_H +#define FIFO_H +/** Typ dbus_w_t je podobně definován jako sig_atomic_t v hlavičce signal.h. + * Je to prostě největší typ, ke kterému je "atomický" přístup. V GCC je definováno + * __SIG_ATOMIC_TYPE__, šlo by použít, ale je znaménkový. + * */ +#ifdef __SIG_ATOMIC_TYPE__ +typedef unsigned __SIG_ATOMIC_TYPE__ dbus_w_t; +#else +typedef unsigned int dbus_w_t; // pro AVR by to měl být uint8_t (šířka datové sběrnice) +#endif //__SIG_ATOMIC_TYPE__ +/// Tahle podivná rekurzívní formule je použita pro validaci délky bufferu. +static constexpr bool isValidM (const int N, const dbus_w_t M) { + // constexpr má raději rekurzi než cyklus (c++11) + return (N > 12) ? false : (((1u << N) == M) ? true : isValidM (N+1, M)); +} +/** @class FIFO + * @brief Jednoduchá fronta (kruhový buffer). + * + * V tomto přikladu je vidět, že synchronizace mezi přerušením a hlavní smyčkou programu + * může být tak jednoduchá, že je v podstatě neviditelná. Využívá se toho, že pokud + * do kruhového buferu zapisujeme jen z jednoho bodu a čteme také jen z jednoho bodu + * (vlákna), zápis probíhá nezávisle pomocí indexu m_head a čtení pomocí m_tail. + * Délka dat je dána rozdílem tt. indexů, pokud v průběhu výpočtu délky dojde k přerušení, + * v zásadě se nic špatného neděje, maximálně je délka určena špatně a to tak, + * že zápis nebo čtení je nutné opakovat. Důležité je, že po výpočtu se nová délka zapíše + * do paměti "atomicky". Takže např. pro 8-bit procesor musí být indexy jen 8-bitové. + * To není moc velké omezení, protože tyto procesory obvykle mají dost malou RAM, takže + * velikost bufferu stejně nebývá být větší než nějakých 64 položek. + * Opět nijak nevadí že přijde přerušení při zápisu nebo čtení položky - to se provádí + * dříve než změna indexu, zápis a čtení je vždy na jiném místě RAM. Celé je to uděláno + * jako šablona, takže je možné řadit do fronty i složitější věci než je pouhý byte. + * Druhým parametrem šablony je délka bufferu (aby to šlo konstruovat jako statický objekt), + * musí to být mocnina dvou v rozsahu 8 až 4096, default je 64. Mocnina 2 je zvolena proto, + * aby se místo zbytku po dělení mohl použít jen bitový and, což je rychlejší. + * */ +template class FIFO { + T m_data [M]; + volatile dbus_w_t m_head; //!< index pro zápis (hlava) + volatile dbus_w_t m_tail; //!< index pro čtení (ocas) + /// vrací skutečnou délku dostupných dat + constexpr dbus_w_t lenght () const { return (M + m_head - m_tail) & (M - 1); }; + /// zvětší a saturuje index, takže se tento motá v kruhu @param n index + void sat_inc (volatile dbus_w_t & n) const { n = (n + 1) & (M - 1); }; + public: + /// Konstruktor + explicit constexpr FIFO () noexcept { + // pro 8-bit architekturu může být byte jako index poměrně malý + static_assert (1ul << (8 * sizeof(dbus_w_t) - 1) >= M, "atomic type too small"); + // a omezíme pro jistotu i delku buferu na nějakou rozumnou delku + static_assert (isValidM (3, M), "M must be power of two in range <8,4096> or <8,128> for 8-bit data bus (AVR)"); + m_head = 0; + m_tail = 0; + } + /// Čtení položky + /// @return true, pokud se úspěšně provede + const bool Read (T & c) { + if (lenght() == 0) return false; + c = m_data [m_tail]; + sat_inc (m_tail); + return true; + } + /// Zápis položky + /// @return true, pokud se úspěšně provede + const bool Write (const T & c) { + if (lenght() >= (M - 1)) return false; + m_data [m_head] = c; + sat_inc (m_head); + return true; + } +}; + +#endif // FIFO_H diff --git a/V203/common/oneway.h b/V203/common/oneway.h new file mode 100644 index 0000000..f06cd6b --- /dev/null +++ b/V203/common/oneway.h @@ -0,0 +1,10 @@ +#ifndef ONEWAY_H +#define ONEWAY_H +#include +/* C++ interface (jako callback v C) */ +class OneWay { + public: + virtual unsigned Send (uint16_t * const ptr, const unsigned len) = 0; +}; + +#endif // ONEWAY_H diff --git a/V203/pwm/Makefile b/V203/pwm/Makefile new file mode 100644 index 0000000..35e717d --- /dev/null +++ b/V203/pwm/Makefile @@ -0,0 +1,55 @@ +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 pwmclass.o generator.o sin.o +#OBJS += + +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) +sin.c: sin.py + ./sin.py +flash: $(PRJ).elf + minichlink -w $(PRJ).bin flash -b +# vycisti +clean: + $(DEL) $(BLD)* *.lst *.bin *.elf *.map sin.c *~ +.PHONY: all clean flash run diff --git a/V203/pwm/ch32v203 b/V203/pwm/ch32v203 new file mode 120000 index 0000000..7650c85 --- /dev/null +++ b/V203/pwm/ch32v203 @@ -0,0 +1 @@ +../ch32v203/ \ No newline at end of file diff --git a/V203/pwm/common b/V203/pwm/common new file mode 120000 index 0000000..8332399 --- /dev/null +++ b/V203/pwm/common @@ -0,0 +1 @@ +../common/ \ No newline at end of file diff --git a/V203/pwm/generator.cpp b/V203/pwm/generator.cpp new file mode 100644 index 0000000..3378837 --- /dev/null +++ b/V203/pwm/generator.cpp @@ -0,0 +1,13 @@ +#include "generator.h" + +extern "C" const int16_t sin_tab[0x100]; + +int16_t Generator::step() { + const int16_t v = sin_tab [base >> 24]; + base += freq; + return v; +} +unsigned int Generator::Send(uint16_t * const ptr, const unsigned int len) { + for (unsigned n=0u; nsend(false); + else if (state.B.TCIF5 != RESET) pInstance->send(true); + + pInstance->signalize(); // zbytečný efekt +} + +/* + * initialize TIM1 for PWM + */ +static inline void tim1pwm_init () noexcept { + // Enable GPIOA and TIM1 + RCC.APB2PCENR.modify([] (RCC_Type::APB2PCENR_DEF & r) -> auto { + r.B.IOPAEN = SET; + r.B.TIM1EN = SET; + r.B.AFIOEN = SET; + return r.R; + }); + AFIO.PCFR.modify([](AFIO_Type::PCFR_DEF & r) -> auto { + r.B.TIM1RM = 1u; + return r.R; + }); + // PA7 is T1CH1N, PA8 is T1CH1, 10MHz Output alt func, push-pull + GPIOA.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto { + r.B.CNF7 = 2u; + r.B.MODE7 = 1u; + return r.R; + }); + GPIOA.CFGHR.modify([](GPIOA_Type::CFGHR_DEF & r) -> auto { + r.B.CNF8 = 2u; + r.B.MODE8 = 1u; + return r.R; + }); + // Reset TIM1 to init all regs + RCC.APB2PRSTR.B.TIM1RST = SET; + RCC.APB2PRSTR.B.TIM1RST = RESET; + // CTLR1: default is up, events generated, edge align + // SMCFGR: default clk input is CK_INT + // Prescaler + TIM1.PSC.R = 0u; + // Auto Reload - sets period + TIM1.ATRLR.R = MAXPWM - 1; + + TIM1.CCER.modify([](TIM1_Type::CCER_DEF & r) -> auto { + // Enable CH1N, CH1 output, positive pol + r.B.CC1NE = SET; + r.B.CC1E = SET; + /* + r.B.CC1NP = SET; // active Low + r.B.CC1P = SET; + */ + return r.R; + }); + // CH1 Mode is output, PWM1 (CC1S = 00, OC1M = 110) + TIM1.CHCTLR1_Output.modify([](TIM1_Type::CHCTLR1_Output_DEF & r) -> auto { + r.B.OC1M = 0x6u; + return r.R; + }); + // Enable TIM1 outputs + TIM1.BDTR.modify([](TIM1_Type::BDTR_DEF & r) -> auto { + r.B.MOE = SET; + r.B.DTG = 48u; // Dead time 1us + return r.R; + }); + + // Reload immediately + Trigger DMA + TIM1.SWEVGR.B.UG = SET; + TIM1.DMAINTENR.B.UDE = SET; + // Enable TIM1 + TIM1.CTLR1.B.CEN = SET; +} +typedef __SIZE_TYPE__ size_t; +static inline void dma1ch5_init (void * ptr) noexcept { + // Enable DMA + RCC.AHBPCENR.modify([](RCC_Type::AHBPCENR_DEF & r) -> auto { + r.B.SRAMEN = SET; + r.B.DMA1EN = SET; + return r.R; + }); + // DMA5 can be configured to attach to T1UP + // The system can only DMA out at ~2.2MSPS. 2MHz is stable. + DMA1.CNTR5 .R = FULL_LEN; + DMA1.MADDR5.R = reinterpret_cast(ptr); + DMA1.PADDR5.R = reinterpret_cast(& TIM1.CH1CVR); + NVIC.EnableIRQ (DMA1_Channel5_IRQn); + DMA1.CFGR5.modify([](DMA1_Type::CFGR5_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 ch5 + r.B.EN = SET; + return r.R; + }); +} + +PwmClass::PwmClass() noexcept : led(GPIOA, 0), count(0u), pL(buffer), pH(buffer + HALF_LEN), src(nullptr) { + pInstance = this; + tim1pwm_init (); + dma1ch5_init (buffer); +} diff --git a/V203/pwm/pwmclass.h b/V203/pwm/pwmclass.h new file mode 100644 index 0000000..71722c3 --- /dev/null +++ b/V203/pwm/pwmclass.h @@ -0,0 +1,32 @@ +#ifndef PWMCLASS_H +#define PWMCLASS_H +#include "system.h" +#include "gpio.h" +#include "oneway.h" +static constexpr unsigned HALF_LEN = 160u; +static constexpr unsigned FULL_LEN = 2u * HALF_LEN; +static constexpr unsigned MAXPWM = 6000u; +/* Používá TIM1, PWM kanál 1, DMA1 kanál 5, přerušení DMA1_Channel5_IRQHandler */ +class PwmClass { + GpioClass led; + unsigned count; + uint16_t * const pL; + uint16_t * const pH; + uint16_t buffer [FULL_LEN]; + OneWay * src; + public: + explicit PwmClass () noexcept; + void attach (OneWay & s) { src = & s; } + void send (const bool b) { + if (!src) return; + if (b) src->Send (pH, HALF_LEN); + else src->Send (pL, HALF_LEN); + } + void signalize () { + const bool b = count & 8u; + led << b; + count += 1u; + } +}; + +#endif // PWMCLASS_H diff --git a/V203/pwm/sin.py b/V203/pwm/sin.py new file mode 100755 index 0000000..cab12a1 --- /dev/null +++ b/V203/pwm/sin.py @@ -0,0 +1,27 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import math + +header = '''/* Generated file */ +#include +const int16_t sin_tab[] = {{{0:s} +}}; +''' + +def generate(): + s = '' + for n in range(0,256): + if (n % 16) == 0: + s += '\n ' + a = float(n) * math.pi / 128.0 + v = int (round (2900.0 * (math.sin (a)))); + s += '{0:+6d},'.format(v) + return s + +if __name__ == '__main__': + s = generate() + f = open ('sin.c','w') + f.write(header.format(s)) + f.close() +