diff --git a/examples/uart_tx.cpp b/examples/uart_tx.cpp new file mode 100644 index 0000000000000000000000000000000000000000..333adb021ee639cd9a45ab2051472404c9cd39ff --- /dev/null +++ b/examples/uart_tx.cpp @@ -0,0 +1,16 @@ +#include "libnux/vx/time.h" +#include "libnux/vx/uart.h" + +using namespace libnux::vx; + +auto uart = SoftUartTx(115'200, UART_8N1); + +int start() +{ + sleep_cycles(100'000 * default_ppu_cycles_per_us); + + uart.printf("hello world"); + + sleep_cycles(100'000 * default_ppu_cycles_per_us); + return 0; +} diff --git a/include/libnux/vx/uart.h b/include/libnux/vx/uart.h new file mode 100644 index 0000000000000000000000000000000000000000..4e8267d179dc248691054846b860df6dde1b73be --- /dev/null +++ b/include/libnux/vx/uart.h @@ -0,0 +1,103 @@ +#pragma once + +#include "libnux/vx/mailbox.h" +#include "libnux/vx/spr.h" + +#include <cstddef> +#include <cstdint> +#include <memory> + +namespace libnux::vx { + +using uart_data_rate_t = unsigned long; +using uart_word_t = uint16_t; + + +enum class Parity : uint8_t +{ + NONE, + EVEN, + ODD, +}; + +/** + * Configuration of a UART interface. + */ +struct UartConfiguration +{ + /** + * Creates the configuration for a UART interface. + * + * @param width Width of the interface. Must not exceed the width of `uart_word_t`. + * @param parity Parity setting for the communication. + * @param stop_bits Amount of stop bits to send/expect. + */ + constexpr UartConfiguration(size_t const width, Parity const parity, size_t const stop_bits) : + width(width), parity(parity), stop_bits(stop_bits) + { + if (width > std::numeric_limits<uart_word_t>::digits) { + mailbox_write_string("ERROR: UART width exceeds capacity of the underlying data type!"); + exit(1); + } + }; + + size_t const width; + Parity const parity; + size_t const stop_bits; +}; + +constexpr UartConfiguration UART_8N1{8, Parity::NONE, 1}; + +/** + * Software defined UART for transmitting serial data via the PPU's GPIO pin. + */ +class SoftUartTx +{ +public: + /** + * Creates a SoftUartTx instance. + * + * We eagerly allocate a buffer for printf-formatting to ensure that printing does not require + * additional excessive memory allocation. + * + * @param data_rate Data rate (in baud) to be used. + * @param config UART configuration, defaults to 8N1: 8bit, no parity, one stop bit. + * @param invert_physical Invert the signal on the line (idle 'low' if true). + * @param buffer_size Size of the string buffer (in byte) for printf formatting. + */ + explicit SoftUartTx( + uart_data_rate_t data_rate, + UartConfiguration const& config = UART_8N1, + bool invert_physical = false, + size_t buffer_size = 128); + + ~SoftUartTx(); + + /** + * Transmit a single word through the UART. Blocks until the transmission is done. + * + * @param data Data to be written. + */ + void write(uart_word_t data) const; + + /** + * Write a printf-formatted string via this UART. Blocks until the transmission is done. + * + * @param format A printf-style format string + * @return Number of bytes that resulted from formatting. In case of truncation, this number can + * be larger than the `buffer_size` specified on construction. Can be negative in case + * of formatting errors. + */ + int printf(char const* format, ...) const; + +private: + uart_data_rate_t const data_rate; + time_base_t const cycles_per_bit; + UartConfiguration const& config; + bool const invert_physical; + size_t const buffer_size; + std::unique_ptr<char[]> string_buffer; + [[nodiscard]] constexpr bool logic2physical(bool value) const; +}; + +} // namespace libnux::vx diff --git a/src/libnux/vx/uart.cpp b/src/libnux/vx/uart.cpp new file mode 100644 index 0000000000000000000000000000000000000000..320011111853efc1fcac8589c980d062bbdfd902 --- /dev/null +++ b/src/libnux/vx/uart.cpp @@ -0,0 +1,107 @@ +#include "libnux/vx/uart.h" +#include "libnux/vx/time.h" + +#include <cstdarg> +#include <cstdio> +#include <vector> + +namespace libnux::vx { + +SoftUartTx::SoftUartTx( + uart_data_rate_t const data_rate, + UartConfiguration const& config, + bool const invert_physical, + size_t const buffer_size) : + data_rate(data_rate), + cycles_per_bit(default_ppu_cycles_per_us * 1'000'000 / data_rate), + config(config), + invert_physical(invert_physical), + buffer_size(buffer_size), + string_buffer(std::make_unique<char[]>(buffer_size)) +{ + // configure output mode for GPIO + set_goe(false); // inverted + + // default logic 'high' + set_gout(logic2physical(true)); +} + +SoftUartTx::~SoftUartTx() +{ + // should not be necessary, cf. issue #4051 + string_buffer.reset(); +} + +constexpr bool SoftUartTx::logic2physical(bool const value) const +{ + if (invert_physical) { + return (!value); + } + return (value); +} + +void SoftUartTx::write(uart_word_t const data) const +{ + std::vector<bool> bits_to_write; + + // start bit + bits_to_write.push_back(logic2physical(false)); + + // data frame + for (size_t bit_idx = 0; bit_idx < config.width; ++bit_idx) { + // Check if the i-th bit is set + const bool bit_value = (data & (1 << bit_idx)) != 0; + bits_to_write.push_back(logic2physical(bit_value)); + } + + // (optional) parity bit + switch (config.parity) { + case Parity::NONE: + break; + case Parity::EVEN: + bits_to_write.push_back(logic2physical(__builtin_parity(data))); + break; + case Parity::ODD: + bits_to_write.push_back(logic2physical(!__builtin_parity(data))); + break; + } + + // stop bits + for (size_t stop_bit = 0; stop_bit < config.stop_bits; ++stop_bit) { + bits_to_write.push_back(logic2physical(true)); + } + + // warm cache + sleep_cycles(10); + set_gout(logic2physical(true)); + + // write bits + for (auto const value : bits_to_write) { + set_gout(value); + sleep_cycles(cycles_per_bit); + } +} + +int SoftUartTx::printf(char const* const format, ...) const +{ + va_list args; + + va_start(args, format); + const int retval = vsnprintf(string_buffer.get(), buffer_size, format, args); + va_end(args); + + if (retval < 0) { + // formatting failed, don't output anything + return retval; + } + + char const* encoded = string_buffer.get(); + while (*encoded != '\0') { + write(*encoded); + ++encoded; + } + + return retval; +} + +} // namespace libnux::vx diff --git a/tests/hw/libnux/vx/test_uart.cpp b/tests/hw/libnux/vx/test_uart.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c2b9a25665bc93b95f1378aa899faccc3e967c7f --- /dev/null +++ b/tests/hw/libnux/vx/test_uart.cpp @@ -0,0 +1,38 @@ +#include "libnux/vx/uart.h" +#include "libnux/vx/unittest.h" + +using namespace libnux::vx; + +auto uart = SoftUartTx(115'200, UART_8N1); + +void test_uart_send_byte() +{ + testcase_begin("uart_send_byte"); + uart.write(0xAA); + testcase_end(); +} + +void test_uart_send_string() +{ + testcase_begin("uart_send_string"); + size_t bytes_sent; + + bytes_sent = uart.printf("hello"); + test_equal(bytes_sent, 5u); + + bytes_sent = uart.printf("abc%d", 123); + test_equal(bytes_sent, 6u); + + bytes_sent = uart.printf("%.2f", 3.1416); + test_equal(bytes_sent, 4u); + testcase_end(); +} + +void start() +{ + test_init(); + test_uart_send_byte(); + test_uart_send_string(); + test_summary(); + test_shutdown(); +} diff --git a/wscript b/wscript index 19838c4bc73e1f1dd78bd8bfb1a9acb32c965a2a..dff4aaf92d5f3a4b1f8356d47497823df487b2b7 100644 --- a/wscript +++ b/wscript @@ -217,7 +217,7 @@ def build(bld): env=env, ) - program_list = ["examples/stdp.cpp"] + program_list = ["examples/stdp.cpp", "examples/uart_tx.cpp"] for program in program_list: bld.program(