236 lines
6.1 KiB
C++
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
|