zephyr/rims_app/src/uart.cpp
2025-04-22 11:25:15 +02:00

236 lines
6.1 KiB
C++

#include "uart.hpp"
#include "log.hpp"
#include "messenger.hpp"
#include "syscalls/uart.h"
#include "zephyr/device.h"
#include "zephyr/irq.h"
#include "zephyr/spinlock.h"
#include "zephyr/sys/__assert.h"
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <exception>
#include <thread>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#define UART_DEVICE_NODE
namespace rims {
// static int uartQueueWaitCounter{};
static AsyncUART *defaultUartInterface = nullptr;
class uart_not_ready_error : public std::exception {
// exception interface
public:
constexpr const char *what() const noexcept override {
return "uart device not ready";
}
};
class uart_rx_not_ready_error : public std::exception {
public:
constexpr const char *what() const noexcept override {
return "uart RX not ready to read data";
}
};
class uart_rx_buffer_overflow : public std::exception {
public:
constexpr const char *what() const noexcept override {
return "uart RX buffer overflow";
}
};
// k_work structure to hold data for log information
struct k_work_log_data {
struct k_work work;
const char *msg;
};
// Work handler function
void log_worker(struct k_work *work) {
struct k_work_log_data *log_data = CONTAINER_OF(work, struct k_work_log_data, work);
ULOG_ERROR("%s", log_data->msg);
}
// Global log work item
static struct k_work_log_data uart_log_work;
AsyncUART::AsyncUART() {
_dev = DEVICE_DT_GET(DT_NODELABEL(usart1));
auto ret = uart_irq_callback_user_data_set(_dev, AsyncUART::uartCallback, this);
if (ret < 0) {
throw uart_not_ready_error{}; // catch this somewhere??
}
uart_irq_rx_enable(_dev);
}
void AsyncUART::loop() {
if (!device_is_ready(_dev)) {
/// TODO throw?
return; // Exit if the UART device is not ready
}
while (1) {
std::this_thread::sleep_for(std::chrono::seconds{2});
}
}
static std::size_t buffer_copy_wait {};
// free function, only need to copy data to uart's TX_BUFFER and that's it
void AsyncUART::transmit(AsyncUART *dev, std::span<std::uint8_t> bytes) {
if (bytes.empty()) return;
__ASSERT(bytes.size_bytes() <= dev->tx_buffer.capacity(), "for now, all bytes needs to fir in tx buffer");
while (dev->tx_buffer.free() < bytes.size_bytes()) {
buffer_copy_wait++;
std::this_thread::sleep_for(std::chrono::microseconds{20});
}
k_spinlock_key_t key = k_spin_lock(&dev->tx_lock);
__ASSERT(dev->no_copyInProgress(), "multiple copies at the same time are not allowed");
// copy all data to TX
dev->tx_buffer.put_n(bytes.data(), bytes.size());
// if disabled, enable tx interrupts
if (not dev->tx_irq_enabled()) dev->tx_irq_enable();
k_spin_unlock(&dev->tx_lock, key);
}
void AsyncUART::uartCallback(const device *dev, void *user_data) {
auto _this = static_cast<AsyncUART *>(user_data);
_this->uartISR();
}
void AsyncUART::uartISR() {
__ASSERT(device_is_ready(_dev), "device needs to work");
try {
while (uart_irq_update(_dev) && uart_irq_is_pending(_dev)) {
if (uart_irq_rx_ready(_dev)) {
readByteUart();
}
if (uart_irq_tx_ready(_dev)) {
writeByteUart();
}
}
} catch (const uart_rx_buffer_overflow &e) {
handleRxBufferOverflowError(e);
} catch (const uart_rx_not_ready_error &e) {
handleRxNotReadyError(e); // Offload logging to a worker thread
} catch (const std::exception &e) { // nothing can go here really
[[maybe_unused]] volatile auto what = e.what();
while (1) {
}
}
}
void AsyncUART::readByteUart() {
if (_faultFlag) {
if (rxByte() == 0) _faultFlag = false;
}
// throw on buffer overflow
else if (rxBuffer().full()) {
throw uart_rx_buffer_overflow{};
}
// push_back returns last placed byte, if the byte is 0x00 we got end of frame
else if (rxBuffer().push_back(rxByte()) == 0) {
if(rxBuffer().size()>1){
processMessage();
}else{
rxBuffer().clean();
}
}
}
uint8_t AsyncUART::rxByte() {
int recv_len;
uint8_t buffer[1];
recv_len = uart_fifo_read(_dev, buffer, 1);
if (recv_len < 0) {
// LOG_ERR("Failed to read UART FIFO");
throw uart_rx_not_ready_error{};
};
return buffer[0];
}
void AsyncUART::processMessage() {
buffer buf{.data = {rxBuffer().data(), rxBuffer().size()}, .device = this};
switchRxBuffer();
k_msgq_put(&messenger_buffer_arrived_queue, &buf, K_MSEC(10));
}
AsyncUART::rx_buffer_t &AsyncUART::rxBuffer() {
return rx_buffers[_currentBufferIndex];
}
void AsyncUART::switchRxBuffer() {
_currentBufferIndex = _currentBufferIndex == 1 ? 0 : 1;
rxBuffer().clean();
}
bool AsyncUART::txHasByte() const {
return not tx_buffer.empty();
}
void AsyncUART::txByte(uint8_t byte) {
[[maybe_unused]] auto send_len = uart_fifo_fill(_dev, &byte, 1);
__ASSERT(send_len == 1, "fifo fill has to work as expected");
}
void AsyncUART::writeByteUart() {
if (txHasByte()) {
txByte(tx_buffer.get());
} else {
tx_irq_disable();
}
}
void AsyncUART::handleRxBufferOverflowError(const uart_rx_buffer_overflow &e) {
// log what went wrong, this is safe
// Offload logging to a worker thread
uart_log_work.msg = e.what();
k_work_submit(&uart_log_work.work);
// clean current buffer as is is usless for us now
rxBuffer().clean();
// indicate the driver to skip bytes until end of frame
_faultFlag = true;
}
void AsyncUART::handleRxNotReadyError(const uart_rx_not_ready_error &e) {
// log what went wrong, this is safe
uart_log_work.msg = e.what();
k_work_submit(&uart_log_work.work);
}
int UARTThread::do_hardwarenInit() {
return 0;
}
void UARTThread::threadMain() {
k_work_init(&uart_log_work.work, log_worker);
/// runs in context of new thread, on new thread stack etc.
AsyncUART thread{};
defaultUartInterface = &thread;
thread.loop();
}
AsyncUART *defaultUart() {
return defaultUartInterface;
}
} // namespace rims