From 661eeb368f303268d31bc659e37dccdb61a1ab88 Mon Sep 17 00:00:00 2001 From: Alex Voinea Date: Wed, 19 May 2021 21:18:30 +0300 Subject: [PATCH] USART hal prototype --- CMakeLists.txt | 5 +- src/hal/avr/usart.cpp | 72 ++++++++++++++++++++++++++++ src/hal/circle_buffer.hpp | 98 ++++++++++++++++++++++++++++++++++++++ src/hal/uart.h | 32 ------------- src/hal/usart.h | 99 +++++++++++++++++++++++++++++++++++++++ src/main.cpp | 86 +++++++++++++++++++++------------- 6 files changed, 326 insertions(+), 66 deletions(-) create mode 100644 src/hal/avr/usart.cpp create mode 100644 src/hal/circle_buffer.hpp delete mode 100644 src/hal/uart.h create mode 100644 src/hal/usart.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 21cb804..abe11df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -176,7 +176,10 @@ if(CMAKE_CROSSCOMPILING) # generate linker map file target_link_options(firmware PUBLIC -Wl,-Map=firmware.map) - target_sources(firmware PRIVATE src/main.cpp src/hal/avr/cpu.cpp src/modules/protocol.cpp) + target_sources( + firmware PRIVATE src/main.cpp src/hal/avr/cpu.cpp src/modules/protocol.cpp + src/hal/avr/usart.cpp + ) else() enable_testing() diff --git a/src/hal/avr/usart.cpp b/src/hal/avr/usart.cpp new file mode 100644 index 0000000..dc64b3e --- /dev/null +++ b/src/hal/avr/usart.cpp @@ -0,0 +1,72 @@ +#include "../usart.h" +#include + +uint8_t hal::USART::Read() { + uint8_t c = 0; + this->rx_buf.ConsumeFirst(c); + return c; +} + +void hal::USART::Write(uint8_t c) { + _written = true; + // If the buffer and the data register is empty, just write the byte + // to the data register and be done. This shortcut helps + // significantly improve the effective datarate at high (> + // 500kbit/s) bitrates, where interrupt overhead becomes a slowdown. + if (tx_buf.IsEmpty() && (husart->UCSRxA & (1 << 5))) { + husart->UDRx = c; + husart->UCSRxA |= (1 << 6); + return; + } + + // If the output buffer is full, there's nothing for it other than to + // wait for the interrupt handler to empty it a bit + while (!tx_buf.push_back_DontRewrite(c)) { + if (bit_is_clear(SREG, SREG_I)) { + // Interrupts are disabled, so we'll have to poll the data + // register empty flag ourselves. If it is set, pretend an + // interrupt has happened and call the handler to free up + // space for us. + if (husart->UCSRxA & (1 << 5)) + ISR_UDRE(); + } else { + // nop, the interrupt handler will free up space for us + } + } + + husart->UCSRxB |= (1 << 5); //enable UDRE interrupt +} + +void hal::USART::Flush() { + // If we have never written a byte, no need to flush. This special + // case is needed since there is no way to force the TXC (transmit + // complete) bit to 1 during initialization + if (!_written) + return; + + while ((husart->UCSRxB & (1 << 5)) || ~(husart->UCSRxA & (1 << 6))) { + if (bit_is_clear(SREG, SREG_I) && (husart->UCSRxB & (1 << 5))) + // Interrupts are globally disabled, but the DR empty + // interrupt should be enabled, so poll the DR empty flag to + // prevent deadlock + if (husart->UCSRxA & (1 << 5)) + ISR_UDRE(); + } + // If we get here, nothing is queued anymore (DRIE is disabled) and + // the hardware finished tranmission (TXC is set). +} + +void hal::USART::puts(const char *str) { + while (*str) + this->Write(*str++); +} + +hal::USART usart1(USART1); + +ISR(USART1_RX_vect) { + usart1.ISR_RX(); +} + +ISR(USART1_UDRE_vect) { + usart1.ISR_UDRE(); +} diff --git a/src/hal/circle_buffer.hpp b/src/hal/circle_buffer.hpp new file mode 100644 index 0000000..e6b77cf --- /dev/null +++ b/src/hal/circle_buffer.hpp @@ -0,0 +1,98 @@ +// circle_buffer.hpp +#pragma once + +#include +#include +/*****************************************************************************/ +// general circular buffer +// you can never use entire size +// because write position (end) cannot be equal to begin +// because begin == end == empty +template +class CircleBuffer { +public: + using Elem = T; + +protected: + T data[SIZE]; + volatile size_t begin; // position of first element + volatile size_t end; // position behind last element == write position + volatile size_t pushed; + static void incrementIndex(volatile size_t &index) { index = (index + 1) % SIZE; } + static void decrementIndex(volatile size_t &index) { index = (index + SIZE - 1) % SIZE; } + +public: + CircleBuffer() + : begin(0) + , end(0) + , pushed(0) {} + + void push_back(T elem); + bool push_back_DontRewrite(T elem); + size_t Count() const { return (end + SIZE - begin) % SIZE; } + bool IsEmpty() const { return begin == end; } + bool CanPush() const { + size_t index = begin; + incrementIndex(index); + return (index != end); + } + size_t PushedCount() const { return pushed; } + + constexpr size_t Size() const { return SIZE; } + + bool ConsumeFirst(T &elem); // data must be processed before next push_back + bool ConsumeLast(T &elem); // data must be processed before next push_back + const T &GetFirstIfAble() const; // data must be processed before next push_back, must not be empty + const T &GetLastIfAble() const; // data must be processed before next push_back, must not be empty +}; + +template +void CircleBuffer::push_back(T elem) { + data[end] = elem; + incrementIndex(end); + if (begin == end) { //begin just was erased, set new begin + incrementIndex(begin); + } + ++pushed; +} + +template +bool CircleBuffer::push_back_DontRewrite(T elem) { + size_t index = begin; + incrementIndex(index); + if (index != end) { + push_back(elem); + return true; + } + return false; +} + +template +bool CircleBuffer::ConsumeFirst(T &elem) { + if (IsEmpty()) + return false; + elem = GetFirstIfAble(); + incrementIndex(begin); + return true; +} + +template +bool CircleBuffer::ConsumeLast(T &elem) { + if (IsEmpty()) + return false; + elem = GetLastIfAble(); + decrementIndex(end); + return true; +} + +template +const T &CircleBuffer::GetFirstIfAble() const { + return data[begin]; +} + +template +const T &CircleBuffer::GetLastIfAble() const { + size_t index = end; + decrementIndex(index); + return data[index]; +} diff --git a/src/hal/uart.h b/src/hal/uart.h deleted file mode 100644 index 6663491..0000000 --- a/src/hal/uart.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/// UART interface -/// @@TODO decide, if this class will behave like a singleton, or there will be multiple classes -/// for >1 UART interfaces - -namespace hal { -class UART { - -public: - /// @returns current character from the UART without extracting it from the read buffer - uint8_t Peek() const; - /// @returns true if there are no bytes to be read - bool ReadEmpty() const; - /// @returns current character from the UART and extracts it from the read buffer - uint8_t Read(); - - /// @param c character to be pushed into the TX buffer (to be sent) - void Write(uint8_t c); - /// @returns true if there is at least one byte free in the TX buffer (i.e. some space to add a character to be sent) - bool CanWrite() const; - /// blocks until the TX buffer was successfully transmitted - void Flush(); - -private: - /// implementation of the receive ISR's body - void ISR_RX(); - /// implementation of the transmit ISR's body - void ISR_TX(); -}; - -} // namespace hal diff --git a/src/hal/usart.h b/src/hal/usart.h new file mode 100644 index 0000000..06b91ce --- /dev/null +++ b/src/hal/usart.h @@ -0,0 +1,99 @@ +#pragma once +#include +#include +#include "gpio.h" +#include "circle_buffer.hpp" +/// USART interface +/// @@TODO decide, if this class will behave like a singleton, or there will be multiple classes +/// for >1 USART interfaces + +namespace hal { +class USART { +public: + struct USART_TypeDef { + volatile uint8_t UCSRxA; + volatile uint8_t UCSRxB; + volatile uint8_t UCSRxC; + volatile uint8_t UCSRxD; + volatile uint16_t UBRRx; + volatile uint8_t UDRx; + }; + + struct USART_InitTypeDef { + hal::gpio::GPIO_pin rx_pin; + hal::gpio::GPIO_pin tx_pin; + uint32_t baudrate; + }; + + /// @returns current character from the UART without extracting it from the read buffer + uint8_t Peek() const { + return rx_buf.GetFirstIfAble(); + } + /// @returns true if there are no bytes to be read + bool ReadEmpty() const { + return rx_buf.IsEmpty(); + } + /// @returns current character from the UART and extracts it from the read buffer + uint8_t Read(); + + /// @param c character to be pushed into the TX buffer (to be sent) + void Write(uint8_t c); + /// @param str c string to be sent. NL is appended + void puts(const char *str); + /// @returns true if there is at least one byte free in the TX buffer (i.e. some space to add a character to be sent) + bool CanWrite() const { + return tx_buf.CanPush(); + } + /// blocks until the TX buffer was successfully transmitted + void Flush(); + + /// Initializes USART interface + __attribute__((always_inline)) inline void Init(USART_InitTypeDef *const conf) { + gpio::Init(conf->rx_pin, gpio::GPIO_InitTypeDef(gpio::Mode::input, gpio::Level::low)); + gpio::Init(conf->tx_pin, gpio::GPIO_InitTypeDef(gpio::Mode::output, gpio::Level::low)); + husart->UBRRx = (((double)(F_CPU)) / (((double)(conf->baudrate)) * 8.0) - 1.0 + 0.5); + husart->UCSRxA = (1 << 1); // Set double baudrate setting. Clear all other status bits/flags + // husart->UCSRxC |= (1 << 3); // 2 stop bits. Preserve data size setting + husart->UCSRxD = 0; //disable hardware flow control. This register is reserved on all AVR devides with USART. + husart->UCSRxB = (1 << 3) | (1 << 4) | (1 << 7); // Turn on the transmission and reception circuitry and enable the RX interrupt + } + + /// implementation of the receive ISR's body + __attribute__((always_inline)) inline void ISR_RX() { + if (husart->UCSRxA & (1 << 4)) { + (void)husart->UDRx; + } else { + rx_buf.push_back_DontRewrite(husart->UDRx); + } + } + /// implementation of the transmit ISR's body + __attribute__((always_inline)) inline void ISR_UDRE() { + uint8_t c = 0; + tx_buf.ConsumeFirst(c); + husart->UDRx = c; + + // clear the TXC bit -- "can be cleared by writing a one to its bit + // location". This makes sure flush() won't return until the bytes + // actually got written + husart->UCSRxA |= (1 << 6); + + if (tx_buf.IsEmpty()) + husart->UCSRxB &= ~(1 << 5); // disable UDRE interrupt + } + + USART(USART_TypeDef *husart) + : husart(husart) {}; + +private: + // IO base address + USART_TypeDef *husart; + bool _written; + + CircleBuffer tx_buf; + CircleBuffer rx_buf; +}; + +} // namespace hal + +#define USART1 ((hal::USART::USART_TypeDef *)&UCSR1A) +extern hal::USART usart1; diff --git a/src/main.cpp b/src/main.cpp index 679406f..9ccaf8a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,42 +1,65 @@ #include "logic/mm_control.h" #include "hal/gpio.h" #include "hal/spi.h" +#include "hal/usart.h" #include "pins.h" +#include /// One-time setup of HW and SW components /// Called before entering the loop() function void setup() { using namespace hal; - spi::SPI_InitTypeDef spi_conf = { - .miso_pin = gpio::GPIO_pin(TMC2130_SPI_MISO_PIN), - .mosi_pin = gpio::GPIO_pin(TMC2130_SPI_MOSI_PIN), - .sck_pin = gpio::GPIO_pin(TMC2130_SPI_SCK_PIN), - .ss_pin = gpio::GPIO_pin(TMC2130_SPI_SS_PIN), - .prescaler = 2, //4mhz - .cpha = 1, - .cpol = 1, - }; - spi::Init(SPI0, &spi_conf); + // spi::SPI_InitTypeDef spi_conf = { + // .miso_pin = gpio::GPIO_pin(TMC2130_SPI_MISO_PIN), + // .mosi_pin = gpio::GPIO_pin(TMC2130_SPI_MOSI_PIN), + // .sck_pin = gpio::GPIO_pin(TMC2130_SPI_SCK_PIN), + // .ss_pin = gpio::GPIO_pin(TMC2130_SPI_SS_PIN), + // .prescaler = 2, //4mhz + // .cpha = 1, + // .cpol = 1, + // }; + // spi::Init(SPI0, &spi_conf); - // SPI example - gpio::Init(gpio::GPIO_pin(GPIOC, 6), gpio::GPIO_InitTypeDef(gpio::Mode::output, gpio::Level::high)); - uint8_t dat[5]; - gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::low); - spi::TxRx(SPI0, 0x01); - spi::TxRx(SPI0, 0x00); - spi::TxRx(SPI0, 0x00); - spi::TxRx(SPI0, 0x00); - spi::TxRx(SPI0, 0x00); - gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::high); - gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::low); - dat[0] = spi::TxRx(SPI0, 0x00); - dat[1] = spi::TxRx(SPI0, 0x00); - dat[2] = spi::TxRx(SPI0, 0x00); - dat[3] = spi::TxRx(SPI0, 0x00); - dat[4] = spi::TxRx(SPI0, 0x00); - gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::high); - (void)dat; + // // SPI example + // gpio::Init(gpio::GPIO_pin(GPIOC, 6), gpio::GPIO_InitTypeDef(gpio::Mode::output, gpio::Level::high)); + // uint8_t dat[5]; + // gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::low); + // spi::TxRx(SPI0, 0x01); + // spi::TxRx(SPI0, 0x00); + // spi::TxRx(SPI0, 0x00); + // spi::TxRx(SPI0, 0x00); + // spi::TxRx(SPI0, 0x00); + // gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::high); + // gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::low); + // dat[0] = spi::TxRx(SPI0, 0x00); + // dat[1] = spi::TxRx(SPI0, 0x00); + // dat[2] = spi::TxRx(SPI0, 0x00); + // dat[3] = spi::TxRx(SPI0, 0x00); + // dat[4] = spi::TxRx(SPI0, 0x00); + // gpio::WritePin(gpio::GPIO_pin(GPIOC, 6), gpio::Level::high); + // (void)dat; + + USART::USART_InitTypeDef usart_conf = { + .rx_pin = gpio::GPIO_pin(GPIOD, 2), + .tx_pin = gpio::GPIO_pin(GPIOD, 3), + .baudrate = 115200, + }; + + usart1.Init(&usart_conf); + sei(); + usart1.puts("1234567890\n"); + usart1.puts("1234567890\n"); + usart1.puts("1234567890\n"); + usart1.puts("1234567890\n"); + usart1.puts("1234567890\n"); + usart1.puts("1234567890\n"); + usart1.puts("1234567890\n"); + usart1.puts("1234567890\n"); + usart1.puts("1234567890\n"); + usart1.puts("1234567890\n"); + usart1.puts("1234567890\n"); + // usart1.Flush(); } /// Main loop of the firmware @@ -59,11 +82,8 @@ void loop() { int main() { setup(); for (;;) { - using namespace hal::gpio; - WritePin(GPIO_pin(GPIOB, 5), Level::low); - TogglePin(GPIO_pin(GPIOB, 6)); - if (hal::gpio::ReadPin(GPIO_pin(GPIOB, 7)) == hal::gpio::Level::low) - break; + if (!usart1.ReadEmpty()) + usart1.Write(usart1.Read()); loop(); } return 0;