From 03e2f96768486dc8837d8683f63b50a1efe166a5 Mon Sep 17 00:00:00 2001 From: Kizarm Date: Sat, 2 Mar 2024 19:33:18 +0100 Subject: [PATCH] add usart --- ch32v003/CH32V00xxx.h | 1 + ch32v003/usartclass.cpp | 76 ++++++++++++++++++++++++++++++++++++ ch32v003/usartclass.h | 14 +++++++ common/baselayer.h | 74 +++++++++++++++++++++++++++++++++++ common/fifo.h | 73 +++++++++++++++++++++++++++++++++++ common/print.cpp | 85 +++++++++++++++++++++++++++++++++++++++++ common/print.h | 73 +++++++++++++++++++++++++++++++++++ serial/Makefile | 53 +++++++++++++++++++++++++ serial/ch32v003 | 1 + serial/common | 1 + serial/main.cpp | 21 ++++++++++ 11 files changed, 472 insertions(+) create mode 100644 ch32v003/usartclass.cpp create mode 100644 ch32v003/usartclass.h create mode 100644 common/baselayer.h create mode 100644 common/fifo.h create mode 100644 common/print.cpp create mode 100644 common/print.h create mode 100644 serial/Makefile create mode 120000 serial/ch32v003 create mode 120000 serial/common create mode 100644 serial/main.cpp diff --git a/ch32v003/CH32V00xxx.h b/ch32v003/CH32V00xxx.h index 7f6438d..f30aa5d 100644 --- a/ch32v003/CH32V00xxx.h +++ b/ch32v003/CH32V00xxx.h @@ -2701,6 +2701,7 @@ struct USART1_Type { /*!< Universal synchronous asynchronous receiver transmitte } B; __IO uint32_t R; explicit STATR_DEF () noexcept { R = 0x000000c0u; } + explicit STATR_DEF (volatile STATR_DEF & o) noexcept { R = o.R; }; template void setbit (F f) volatile { STATR_DEF r; R = f (r); diff --git a/ch32v003/usartclass.cpp b/ch32v003/usartclass.cpp new file mode 100644 index 0000000..13e58b5 --- /dev/null +++ b/ch32v003/usartclass.cpp @@ -0,0 +1,76 @@ +#include "system.h" +#include "usartclass.h" +static UsartClass * pInstance = nullptr; +static constexpr unsigned HCLK = 48'000'000u; +extern "C" void USART1_IRQHandler (void) __attribute__((interrupt)); +void USART1_IRQHandler (void) { + if (pInstance) pInstance->irq(); +}; + +UsartClass::UsartClass(const unsigned int _baud) noexcept : BaseLayer (), tx_ring () { + pInstance = this; + // 1. Clock Enable + RCC.APB2PCENR.modify([](RCC_Type::APB2PCENR_DEF & r) -> auto { + r.B.USART1EN = SET; + r.B.IOPDEN = SET; + return r.R; + }); + // 2. GPIO Alternate Config - default TX/PD5, RX/PD6 + GPIOD.CFGLR.modify([](GPIOA_Type::CFGLR_DEF & r) -> auto { + r.B.MODE5 = 1u; + r.B.CNF5 = 2u; // or 3u for open drain + r.B.MODE6 = 0u; + r.B.CNF6 = 1u; // floating input + return r.R; + }); + // 4. NVIC + NVIC.EnableIRQ (USART1_IRQn); + // 5. USART registry 8.bit bez parity + USART1.CTLR1.modify([] (USART1_Type::CTLR1_DEF & r) -> auto { + r.B.RE = SET; + r.B.TE = SET; + r.B.RXNEIE = SET; + return r.R; + }); + USART1.CTLR2.R = 0; + //USART1.CTLR3.B.OVRDIS = SET; + const uint32_t tmp = HCLK / _baud; + USART1.BRR.R = tmp; + USART1.CTLR1.B.UE = SET; // nakonec povolit globálně +} +void UsartClass::irq () { + volatile USART1_Type::STATR_DEF status (USART1.STATR); // načti status přerušení + char rdata, tdata; + + if (status.B.TXE) { // od vysílače + if (tx_ring.Read (tdata)) { // pokud máme data + USART1.DATAR.B.DR = (uint8_t) tdata; // zapíšeme do výstupu + } else { // pokud ne + // Předpoklad je half-duplex i.e. RS485, jinak jen zakázat TXEIE + rdata = (USART1.DATAR.B.DR); // dummy read + USART1.CTLR1.modify([](USART1_Type::CTLR1_DEF & r) -> auto { + r.B.RE = SET; // povol prijem + r.B.TXEIE = RESET; // je nutné zakázat přerušení od vysílače + return r.R; + }); + } + } + if (status.B.RXNE) { // od přijímače + rdata = (USART1.DATAR.B.DR); // načteme data + Up (&rdata, 1u); // a pošleme dál + } +} +uint32_t UsartClass::Down(const char * data, const uint32_t len) { + unsigned n = 0u; + for (n=0u; n auto { + r.B.RE = RESET; + r.B.TXEIE = SET; // po povolení přerušení okamžitě přeruší + return r.R; + }); + return n; +} + + diff --git a/ch32v003/usartclass.h b/ch32v003/usartclass.h new file mode 100644 index 0000000..dfd3970 --- /dev/null +++ b/ch32v003/usartclass.h @@ -0,0 +1,14 @@ +#ifndef USARTCLASS_H +#define USARTCLASS_H +#include "baselayer.h" +#include "fifo.h" + +class UsartClass : public BaseLayer { + FIFO tx_ring; + public: + explicit UsartClass (const unsigned _baud) noexcept; + uint32_t Down (const char * data, const uint32_t len) override; + void irq (); +}; + +#endif // USARTCLASS_H diff --git a/common/baselayer.h b/common/baselayer.h new file mode 100644 index 0000000..9a90cc6 --- /dev/null +++ b/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/common/fifo.h b/common/fifo.h new file mode 100644 index 0000000..f3f4651 --- /dev/null +++ b/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/common/print.cpp b/common/print.cpp new file mode 100644 index 0000000..aa785b1 --- /dev/null +++ b/common/print.cpp @@ -0,0 +1,85 @@ +#include "print.h" + +#define sleep() + +static const char * hexStr = "0123456789ABCDEF"; +static const uint16_t numLen[] = {1, 32, 1, 11, 8, 0}; + +Print::Print (PrintBases b) : BaseLayer () { + base = b; +} +// Výstup blokujeme podle toho, co se vrací ze spodní vrstvy +uint32_t Print::BlockDown (const char * buf, uint32_t len) { + uint32_t n, ofs = 0, req = len; + for (;;) { + // spodní vrstva může vrátit i nulu, pokud je FIFO plné + n = BaseLayer::Down (buf + ofs, req); + ofs += n; // Posuneme ukazatel + req -= n; // Zmenšíme další požadavek + if (!req) break; + sleep(); // A klidně můžeme spát + } + return ofs; +} + +Print& Print::operator<< (const char * str) { + uint32_t i = 0; + while (str[i++]); // strlen + BlockDown (str, --i); + return *this; +} + +Print& Print::operator<< (const int num) { + uint32_t i = BUFLEN; + + if (base == DEC) { + unsigned int u; + if (num < 0) u = -num; + else u = num; + do { + // Knihovní div() je nevhodné - dělí 2x. + // Přímočaré a funkční řešení + uint32_t rem; + rem = u % (unsigned) DEC; // 1.dělení + u = u / (unsigned) DEC; // 2.dělení + buf [--i] = hexStr [rem]; + } while (u); + if (num < 0) buf [--i] = '-'; + } else { + uint32_t m = (1U << (uint32_t) base) - 1U; + uint32_t l = (uint32_t) numLen [(int) base]; + uint32_t u = (uint32_t) num; + for (unsigned n=0; n>= (unsigned) base; + } + if (base == BIN) buf [--i] = 'b'; + if (base == HEX) buf [--i] = 'x'; + buf [--i] = '0'; + } + BlockDown (buf+i, BUFLEN-i); + return *this; +} + +Print& Print::operator<< (const PrintBases num) { + base = num; + return *this; +} +void Print::out (const void * p, uint32_t l) { + const unsigned char* q = (const unsigned char*) p; + unsigned char uc; + uint32_t k, n = 0; + for (uint32_t i=0; i> 4; + buf[n++] = hexStr [k]; + k = uc & 0x0f; + buf[n++] = hexStr [k]; + buf[n++] = '>'; + } + buf[n++] = '\r'; + buf[n++] = '\n'; + BlockDown (buf, n); +} + diff --git a/common/print.h b/common/print.h new file mode 100644 index 0000000..f4489eb --- /dev/null +++ b/common/print.h @@ -0,0 +1,73 @@ +#ifndef PRINT_H +#define PRINT_H + +#include "baselayer.h" + +#define EOL "\r\n" +#define BUFLEN 64 + +/** + * @file + * @brief Něco jako ostream. + * + */ + +/// Základy pro zobrazení čísla. +enum PrintBases { + BIN=1, OCT=3, DEC=10, HEX=4 +}; + +/** + * @class Print + * @brief Třída pro výpisy do Down(). + * + * + * V main pak přibude jen definice instance této třídy + * @code + static Print print; + * @endcode + * a ukázka, jak se s tím pracuje: + * @snippet main.cpp Main print example + * Nic na tom není - operátor << má přetížení pro string, číslo a volbu formátu čísla (enum PrintBases). + * Výstup je pak do bufferu a aby nám to "neutíkalo", tedy aby se vypsalo vše, + * zavedeme blokování, vycházející z toho, že spodní třída vrátí jen počet bytů, + * které skutečně odeslala. Při čekání spí, takže nepoužívat v přerušení. + * @snippet src/print.cpp Block example + * Toto blokování pak není použito ve vrchních třídách stacku, + * blokovaná metoda je BlockDown(). Pokud bychom použili přímo Down(), blokování by pak + * používaly všechny vrstvy nad tím. A protože mohou Down() používat v přerušení, byl by problém. + * + * Metody pro výpisy jsou sice dost zjednodušené, ale zase to nezabere + * moc místa - pro ladění se to použít dá. Délka vypisovaného stringu není omezena + * délkou použitého buferu. + * + */ + +class Print : public BaseLayer { + public: + /// Konstruktor @param b Default decimální výpisy. + Print (PrintBases b = DEC); + /// Blokování výstupu + /// @param buf Ukazatel na data + /// @param len Délka přenášených dat + /// @return Počet přenesených bytů (rovno len) + uint32_t BlockDown (const char * buf, uint32_t len); + /// Výstup řetězce bytů + /// @param str Ukazatel na řetězec + /// @return Odkaz na tuto třídu kvůli řetězení. + Print & operator << (const char * str); + /// Výstup celého čísla podle base + /// @param num Číslo + /// @return Odkaz na tuto třídu kvůli řetězení. + Print & operator << (const int num); + /// Změna základu pro výstup čísla + /// @param num enum PrintBases + /// @return Odkaz na tuto třídu kvůli řetězení. + Print & operator << (const PrintBases num); + void out (const void* p, uint32_t l); + private: + PrintBases base; //!< Základ pro výstup čísla. + char buf[BUFLEN]; //!< Buffer pro výstup čísla. +}; + +#endif // PRINT_H diff --git a/serial/Makefile b/serial/Makefile new file mode 100644 index 0000000..e7632c8 --- /dev/null +++ b/serial/Makefile @@ -0,0 +1,53 @@ +# ch32v003 +TARGET?= ch32v003 +TOOL ?= gcc + +PRJ = example + +VPATH = . ./$(TARGET) ./common +BLD = ./build/ +DFLAGS = -d +LFLAGS = -g +LDLIBS = +BFLAGS = --strip-unneeded + +CFLAGS = -MMD -Wall -ggdb -fno-exceptions -ffunction-sections -fdata-sections +CFLAGS+= -I. -I./common -I./$(TARGET) -I/usr/include/newlib +DEL = rm -f + +# zdrojaky +OBJS = main.o usartclass.o print.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) -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 diff --git a/serial/ch32v003 b/serial/ch32v003 new file mode 120000 index 0000000..0bcf9a1 --- /dev/null +++ b/serial/ch32v003 @@ -0,0 +1 @@ +../ch32v003/ \ No newline at end of file diff --git a/serial/common b/serial/common new file mode 120000 index 0000000..8332399 --- /dev/null +++ b/serial/common @@ -0,0 +1 @@ +../common/ \ No newline at end of file diff --git a/serial/main.cpp b/serial/main.cpp new file mode 100644 index 0000000..a3e9f16 --- /dev/null +++ b/serial/main.cpp @@ -0,0 +1,21 @@ +#include "gpio.h" +#include "usartclass.h" +#include "print.h" +////////////////////////////////////// +/* Usart demo + abstraktní třídy C++ + * */ +////////////////////////////////////// +static GpioClass led (GPIOD, 4u); +static UsartClass serial (9600u); +static Print cout (DEC); +int main () { + int n = 0; + cout += serial; + for (;;) { + cout << "Hello world " << n << EOL; + const bool b = (n & 4) == 0; + led << b; + n += 1; + } + return 0; +}