uart fixes

This commit is contained in:
Bartosz Wieczorek 2025-03-03 17:02:22 +01:00
parent 25999cde55
commit 242521678d
18 changed files with 488 additions and 440 deletions

View File

@ -180,18 +180,17 @@
status = "okay";
};
&gpdma1 {
status = "okay";
};
//&gpdma1 {
// status = "okay";
//};
&usart1 {
pinctrl-0 = <&usart1_tx_pb14 &usart1_rx_pb15>;
pinctrl-names = "default";
dmas = <&gpdma1 7 22 (STM32_DMA_PERIPH_TX | STM32_DMA_PRIORITY_LOW) // GPDMA1_REQUEST_USART1_TX
&gpdma1 2 21 (STM32_DMA_PERIPH_RX | STM32_DMA_PRIORITY_LOW)>; // GPDMA1_REQUEST_USART1_RX
dma-names = "tx", "rx";
// dmas = <&gpdma1 7 22 (STM32_DMA_PERIPH_TX | STM32_DMA_PRIORITY_LOW) // GPDMA1_REQUEST_USART1_TX
// &gpdma1 2 21 (STM32_DMA_PERIPH_RX | STM32_DMA_PRIORITY_LOW)>; // GPDMA1_REQUEST_USART1_RX
// dma-names = "tx", "rx";
current-speed = <921600>;
status = "okay";

View File

@ -836,7 +836,6 @@ static int uart_stm32_fifo_fill_visitor(const struct device *dev, const void *tx
/* Lock interrupts to prevent nested interrupts or thread switch */
key = irq_lock();
while ((size - num_tx > 0) && LL_USART_IsActiveFlag_TXE(usart)) {
/* TXE flag will be cleared with byte write to DR|RDR register */
@ -844,7 +843,6 @@ static int uart_stm32_fifo_fill_visitor(const struct device *dev, const void *tx
fill_fn(usart, tx_data, num_tx);
num_tx++;
}
irq_unlock(key);
return num_tx;

View File

@ -6,8 +6,6 @@ project(rims_controller C CXX)
set(CMAKE_C_STANDARD 11)
add_compile_definitions(CONFIG_UART_ASYNC_API)
list(APPEND CMAKE_MODULE_PATH ${ZEPHYR_BASE}/modules/nanopb)
include(nanopb)

View File

@ -2,41 +2,41 @@
Request/Response Architecture
Purpose:
The architecture supports interactions where a client sends a request (e.g., Set or Get) to configure or query the system.
The system processes the request and responds with a result (e.g., requested data on success, or a failure).
The architecture supports interactions where a client sends a request (e.g., Set or Get) to
configure or query the system. The system processes the request and responds with a result (e.g.,
requested data on success, or a failure).
Message Flow:
A request message encapsulates the client's intent (e.g., to set or retrieve a parameter).
A response message provides the outcome of that intent, including any data or error codes.
For each configurable aspect (e.g., a parameter or operation), you define four types of messages:
Set<Param>Request: Specifies the new value for the parameter.
Set<Param>Response: Acknowledges the result of the Set operation.
Get<Param>Request: Requests the current value of the parameter.
Get<Param>Response: Returns the value of the parameter or an error.
A request message encapsulates the client's intent (e.g., to set or retrieve a parameter).
A response message provides the outcome of that intent, including any data or error codes.
For each configurable aspect (e.g., a parameter or operation), you define four types of
messages: Set<Param>Request: Specifies the new value for the parameter. Set<Param>Response:
Acknowledges the result of the Set operation. Get<Param>Request: Requests the current value of the
parameter. Get<Param>Response: Returns the value of the parameter or an error.
Request Fields:
Common fields include a requestid (for tracking) and parameter-specific data, such as ModeOfOperationType or Error.
Response Fields:
Responses use a oneof structure to return either the result of the operation (e.g., the current mode) or an error.
Common fields include a requestid (for tracking) and parameter-specific data, such as
ModeOfOperationType or Error. Response Fields: Responses use a oneof structure to return either the
result of the operation (e.g., the current mode) or an error.
Ingress and Egress Messages
IngressMessages:
Encapsulates all incoming requests (Set or Get).
The oneof field allows only one request type to be sent at a time.
Encapsulates all incoming requests (Set or Get).
The oneof field allows only one request type to be sent at a time.
EgressMessages:
Encapsulates all outgoing responses (Set or Get responses).
Similarly, the oneof field ensures that only one response type is included.
EngressMessages can also send a message that is not a response to given request but also Failure or temperature updates
Encapsulates all outgoing responses (Set or Get responses).
Similarly, the oneof field ensures that only one response type is included.
EngressMessages can also send a message that is not a response to given request but also
Failure or temperature updates
*/
enum MessageIDs{
Temperature = 0;
Ctrl = 1;
Log = 2;
enum MessageIDs {
Temperature = 0;
Ctrl = 1;
Log = 2;
};

View File

@ -3,56 +3,62 @@ syntax = "proto3";
package config;
// channel configuration
enum Error{
NoError = 0;
enum Error {
NoError = 0;
}
enum ModeOfOperationType{
/**
Device is currently OFF
*/
Off = 0;
/**
In manual operation, no PID loop will be enabled on the device.
In this mode there is no way to set output temperature, you can only set the output power
Mode will be applied to all channels
*/
Manual = 1;
/**
TODO not implemented ;(
*/
Automatic = 2;
enum ModeOfOperationType {
/**
Device is currently OFF
*/
Off = 0;
/**
In manual operation, no PID loop will be enabled on the device.
In this mode there is no way to set output temperature, you can only set the output
power
Mode will be applied to all channels
*/
Manual = 1;
/**
TODO not implemented ;(
*/
Automatic = 2;
}
/// Request / Response API
message ModeOfOperationRequest{
// not specyfying this, resulte in a "read only" request
ModeOfOperationType type = 1;
message ModeOfOperationRequest
{
// not specyfying this, resulte in a "read only" request
ModeOfOperationType type = 1;
};
message ModeOfOperationResponse{
oneof resp {
ModeOfOperationType type = 1;
Error error = 254;
}
message ModeOfOperationResponse
{
oneof resp
{
ModeOfOperationType type = 1;
Error error = 254;
}
}
// only those messages are send
message IngressMessages
{
uint32 requestID = 255; // Unique request ID
oneof data {
ModeOfOperationRequest modeOfOperationRequest = 1;
}
uint32 requestID = 255; // Unique request ID
oneof data
{
ModeOfOperationRequest modeOfOperationRequest = 1;
}
};
message EgressMessages
{
optional uint32 requestID = 255; // Unique request ID
oneof data{
ModeOfOperationResponse modeOfOperationResponse = 1;
}
optional uint32 requestID = 255; // Unique request ID
oneof data
{
ModeOfOperationResponse modeOfOperationResponse = 1;
}
};

View File

@ -4,58 +4,64 @@ package ctrl;
// channel configuration
enum Error {
NoError = 0;
NoError = 0;
WrongMode = 1;
WrongChannel = 2;
WrongMode = 1;
WrongChannel = 2;
}
message MaxPower{
message MaxPower
{
}
// Parameters for Phase Modulation
message PhaseModulationParams
{
/**/
/**/
}
// Parameters for Group Modulation
message GroupModulationParams
{
uint32 cycles = 1; // Duration of each group in milliseconds (optional)
uint32 cycles = 1; // Duration of each group in milliseconds (optional)
}
message ManualPowerControlRequest{
uint32 channelID = 1; // required
oneof modulation{
PhaseModulationParams phase = 2;
GroupModulationParams group = 3;
}
message ManualPowerControlRequest
{
uint32 channelID = 1; // required
oneof modulation
{
PhaseModulationParams phase = 2;
GroupModulationParams group = 3;
}
}
message ManualPowerControlResponse{
oneof modulation{
PhaseModulationParams phase = 2;
GroupModulationParams group = 3;
Error error = 254;
}
message ManualPowerControlResponse
{
oneof modulation
{
PhaseModulationParams phase = 2;
GroupModulationParams group = 3;
Error error = 254;
}
}
// only those messages are send through interface
message IngressMessages
{
uint32 requestID = 255; // Unique request ID
oneof data {
ManualPowerControlRequest manualPowerControlRequest = 1;
}
uint32 requestID = 255; // Unique request ID
oneof data
{
ManualPowerControlRequest manualPowerControlRequest = 1;
}
};
message EgressMessages
{
optional uint32 requestID = 255; // Unique request ID
oneof data{
ManualPowerControlResponse manualPowerControlResponse = 1;
// also broadcast
}
optional uint32 requestID = 255; // Unique request ID
oneof data
{
ManualPowerControlResponse manualPowerControlResponse = 1;
// also broadcast
}
};

View File

@ -29,7 +29,7 @@ message LogEntry
LogLevel level = 1; // Log severity level
string sourceFile = 2 [ (nanopb).max_size = 32 ]; // Source file where log was generated
uint32 lineNumber = 3; // Line number in the source file
string logLine = 4 [ (nanopb).max_size = 200 ]; // The formatted log message
string logLine = 4 [ (nanopb).max_size = 120 ]; // The formatted log message
fixed64 systick = 5; // optional systick
}

View File

@ -3,16 +3,16 @@ syntax = "proto3";
import "nanopb.proto";
enum Error {
NoError = 0;
BadCRC = 1;
BadId = 2;
UnknownId = 3;
NoError = 0;
BadCRC = 1;
BadId = 2;
UnknownId = 3;
}
message Message
{
fixed32 id = 1;
bytes data = 2 [ (nanopb).max_size = 250 ];
fixed32 crc = 3;
optional Error error = 4;
fixed32 id = 1;
bytes data = 2 [ (nanopb).max_size = 250 ];
fixed32 crc = 3;
optional Error error = 4;
}

View File

@ -32,16 +32,24 @@ CONFIG_EARLY_CONSOLE=n
CONFIG_BOOT_BANNER=n
CONFIG_SERIAL=y
CONFIG_UART_ASYNC_API=y
CONFIG_DMA=y
# not working as expected for TX
CONFIG_UART_ASYNC_API=n
CONFIG_DMA=n
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_ADC=y
CONFIG_LOG=n
CONFIG_LOG_MODE_IMMEDIATE=n # Log messages are output immediately
CONFIG_LOG_BACKEND_UART=n # Use UART for log output
#CONFIG_LOG=n
#CONFIG_LOG_MODE_IMMEDIATE=n # Log messages are output immediately
#CONFIG_LOG_BACKEND_UART=n # Use UART for log output
#CONFIG_MAIN_STACK_SIZE=512
#CONFIG_MAIN_STACK_SIZE=4096
#
CONFIG_CRC=y
CONFIG_ASSERT=y
CONFIG_ASSERT=n
#CONFIG_NUM_PREEMPT_PRIORITIES=0

View File

@ -1,6 +1,7 @@
#pragma once
#include "zephyr.hpp"
#include "zephyr/sys/__assert.h"
#include <array>
#include <cstddef>
#include <cstring>
@ -21,11 +22,49 @@ namespace rims {
// virtual void pop_back() = 0;
// };
template <typename T, std::size_t Capacity> class static_vector {
private:
std::array<T, Capacity> buffer;
std::size_t count = 0;
public:
constexpr std::size_t size() const noexcept {
return count;
}
constexpr void clean() noexcept {
count = 0;
}
constexpr const T *data() const noexcept {
return buffer.data();
}
constexpr T *data() noexcept {
return buffer.data();
}
constexpr bool full() const noexcept {
return count >= Capacity;
}
constexpr bool empty() const noexcept {
return size() == 0;
}
constexpr T &push_back(const T &value) {
if (count >= Capacity) {
throw std::out_of_range("static_vector is full");
}
buffer[count++] = value;
return buffer[count - 1];
}
};
template <class T, std::size_t N> class circular_buffer {
public:
explicit circular_buffer() = default;
T &emplace_front(T &&item) {
T &emplace_front(const T &item) {
if (size() == capacity()) {
std::destroy_at(std::addressof(buf_[head_]));
std::memset(std::addressof(buf_[head_]), 0, sizeof(T));
@ -43,7 +82,7 @@ template <class T, std::size_t N> class circular_buffer {
}
T get() {
assert(not empty());
__ASSERT_NO_MSG(not empty());
// Read data and advance the tail (we now have a free space)
T val = std::move(*reinterpret_cast<T *>(std::addressof(buf_[tail_])));
std::destroy_at(std::addressof(buf_[tail_]));
@ -60,7 +99,7 @@ template <class T, std::size_t N> class circular_buffer {
tail_ = (tail_ + 1) % N;
full_ = false;
}
void pop_front() {
std::destroy_at(std::addressof(buf_[head_]));
std::memset(std::addressof(buf_[head_]), 0, sizeof(T));
@ -350,10 +389,9 @@ template <typename T, size_t N> class zephyr_fifo_buffer {
if (_elements.full()) return false;
auto &el = _elements.emplace_front(ZephyrFifoElement<T>{}); // create new element
if (fn(el.item)) // fill new data
k_fifo_put(&_fifo, &el); // put data into a queue
else
_elements.pop_front(); //cleanup
if (fn(el.item)) // fill new data
k_fifo_put(&_fifo, &el); // put data into a queue
else _elements.pop_front(); // cleanup
return true;
}

View File

@ -8,7 +8,6 @@
namespace rims {
K_FIFO_DEFINE(klogFifoQueue);
zephyr_fifo_buffer<log_EgressMessages, 10> logEgressFifoQueueBuffer{klogFifoQueue};
static std::size_t g_droppedLogs{0};
@ -48,7 +47,7 @@ void Log::critical(const char *fmt, ...) const {
va_end(args);
}
constexpr log_LogLevel toPbLogLevel(Log::Level level) {
static constexpr log_LogLevel toPbLogLevel(Log::Level level) {
return static_cast<log_LogLevel>(level);
}
@ -65,11 +64,10 @@ static void setBasename(char *destination, const char *path) {
}
void Log::formatLog(Level level, const char *fmt, va_list args) const {
if (should_log(level)) {
// handle case when queue if full and we are waitnig for free space
bool ok{false};
// do {
do {
ok = logEgressFifoQueueBuffer.try_produce([&](log_EgressMessages &logmsg) {
logmsg = log_EgressMessages_init_zero;
@ -80,12 +78,16 @@ void Log::formatLog(Level level, const char *fmt, va_list args) const {
logmsg.data.logEntry.lineNumber = this->_sl.line();
setBasename(logmsg.data.logEntry.sourceFile, this->_sl.file_name());
vsnprintf(logmsg.data.logEntry.logLine, 200, fmt, args);
vsnprintf(logmsg.data.logEntry.logLine, sizeof(logmsg.data.logEntry.logLine), fmt, args);
/// TODO too long logs?
return true;
});
if (not ok) g_droppedLogs++;// std::this_thread::sleep_for(std::chrono::microseconds{100});
// } while (not ok);
if (not ok) {
g_droppedLogs++;
std::this_thread::sleep_for(std::chrono::microseconds{100});
}
} while (not ok);
}
}

View File

@ -1,45 +1,30 @@
#include "common.hpp"
#include "log.hpp"
#include "placement_unique_ptr.hpp"
#include "message_decode.hpp"
#include "phase_modulation.hpp"
#include "syscalls/kernel.h"
#include "temperature_measurements.hpp"
#include "uart.hpp"
#include "zephyr.hpp"
#include "zero_cross_detection.hpp"
#include <array>
#include <chrono>
#include <cmath>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <thread>
#include <utility>
#include <zephyr/kernel.h>
#include <zephyr/kernel.h>
#include <zephyr/kernel/thread.h>
#include <zephyr/kernel/thread_stack.h>
// priority lower value -> higher priority, can be negative
// constexpr int power_measurements_priority = 10;
// constexpr int temperature_measurements_priority = 5;
// constexpr int zero_cross_detection_priority = 4;
// constexpr int phase_control_priority = 4;
// constexpr int pid_priority = 0;
using namespace rims;
static K_THREAD_STACK_DEFINE(k_messengerStack, 1024);
TStack messengerStack{k_messengerStack, K_THREAD_STACK_SIZEOF(k_messengerStack)};
static K_THREAD_STACK_DEFINE(k_uartStack, 1024);
static K_THREAD_STACK_DEFINE(k_uartStack, 2048);
TStack uartStack{k_uartStack, K_THREAD_STACK_SIZEOF(k_uartStack)};
static K_THREAD_STACK_DEFINE(k_temperatureSamplerStack, 3000);
static K_THREAD_STACK_DEFINE(k_temperatureSamplerStack, 2048);
TStack temperatureSamplerStack{k_temperatureSamplerStack, K_THREAD_STACK_SIZEOF(k_temperatureSamplerStack)};
static K_THREAD_STACK_DEFINE(k_zeroCrossDetectionStack, 1500);
@ -64,13 +49,13 @@ template <typename T> class LazyInit {
unique_placed_ptr<T> obj;
public:
template <typename... Args> void init(Args &&...args) {
template <typename... Args> constexpr void init(Args &&...args) {
obj = placement_unique<T>(_buf)(std::forward<Args>(args)...);
}
T &operator*() {
constexpr T &operator*() {
return *obj;
}
T *operator->() {
constexpr T *operator->() {
return obj.get();
}
};
@ -102,44 +87,11 @@ int main() {
zcd->init_hw();
zcd->start();
// phaseModulation->init_hw();
// phaseModulation->start();
phaseModulation->init_hw();
phaseModulation->start();
zephyr::semaphore::sem semaphore{0, 1};
RecurringSemaphoreTimer tm{semaphore, std::chrono::seconds{2}};
{
auto interval = tm.interval().count();
auto period = tm.period().count();
auto kinterval = tm._interval.ticks;
auto kperiod = tm._period.ticks;
ULOG_INFO("interval: %lld period: %lld us", interval, period);
ULOG_INFO("kinterval: %lld kperiod: %lld t", kinterval, kperiod);
}
tm.start();
{
auto interval = tm.interval().count();
auto period = tm.period().count();
auto kinterval = tm._interval.ticks;
auto kperiod = tm._period.ticks;
ULOG_INFO("st interval: %lld period: %lld us", interval, period);
ULOG_INFO("st kinterval: %lld kperiod: %lld t", kinterval, kperiod);
}
std::array<k_poll_event, 1> events;
zephyr::event_pool::k_init(events[0], semaphore);
int i{};
while (1) {
auto ret = zephyr::event_pool::k_poll_forever(events);
if (ret == 0) {
zephyr::event_pool::k_poll_handle(events[0], [&]() {
k_sem_take(&semaphore, K_NO_WAIT);
ULOG_INFO("hello from main thread nr %d!", i++);
});
}
// std::this_thread::sleep_for(std::chrono::milliseconds{4000});
std::this_thread::sleep_for(std::chrono::milliseconds{50});
}
// auto getStats = [](auto &diffs) {

View File

@ -17,8 +17,6 @@
#include "uart.hpp"
#include "thread_analyzer.hpp"
#include <array>
#include <cstring>
#include <functional>
@ -105,9 +103,9 @@ void rims::MessengerThread::event_logEgress()
logEgressFifoQueueBuffer.try_consume([&](log_EgressMessages &in) { ipcPush(in, log_EgressMessages_msg, 4); });
}
void MessengerThread::handle_brokenIngress()
void MessengerThread::handle_badCRC()
{
/// TODO
}
void MessengerThread::threadMain() {
@ -171,7 +169,7 @@ void MessengerThread::event_dataArrived() {
/// TODO check CRC for data
/// TODO check checksum od ID
if(zephyr::crc::crc32_ieee({databegin, datasize}) != crc){
/// TODO
handle_badCRC();
}
stream = pb_istream_from_buffer(databegin, datasize);
@ -250,10 +248,10 @@ void MessengerThread::handle_configIngressMsg(pb_istream_t &stream, AsyncUART *c
template <typename Msg> void MessengerThread::ipcPush(Msg &m, const pb_msgdesc_t &fields, int id) {
// encode embedded message directly to the buffer of output message to save stack
auto ostream = pb_ostream_from_buffer(message.data.bytes, 250); // TODO max data size
auto ostream = pb_ostream_from_buffer(message.data.bytes, sizeof(message.data.bytes)); // TODO max data size
pb_encode(&ostream, &fields, &m);
// set ID od sender
// set IF od sender
message.id = id; /// TODO make response ID
message.data.size = ostream.bytes_written;
message.crc = zephyr::crc::crc32_ieee({message.data.bytes, message.data.size});
@ -276,13 +274,14 @@ bool MessengerThread::decode(pb_istream_t &stream, const pb_msgdesc_t *fields, v
bool MessengerThread::encode(Message &msg, AsyncUART *cb) {
auto outputStream = pb_ostream_from_buffer(outBuf+8, sizeof(outBuf)-8);
// msg.crc = 0xffffffff; /// TODO calculate CRC
pb_encode(&outputStream, &Message_msg, &message);
unsigned encoded_len;
cobs_ret_t const result = cobs_encode(outBuf+8, outputStream.bytes_written, outBuf, sizeof(outBuf), &encoded_len);
/// TODO create exceptions for this part insted of return values
if (result == COBS_RET_SUCCESS) {
// encoding succeeded, 'encoded' and 'encoded_len' hold details.
cb->transmit(cb, std::span{reinterpret_cast<std::byte *>(outBuf), encoded_len});

View File

@ -20,35 +20,32 @@ extern k_msgq messenger_buffer_arrived_queue;
class MessengerThread : public ZephyrThread {
public:
MessengerThread(TStackBase &stack);
static void send(){
static void send() {
}
protected:
void threadMain() override;
private:
struct requestData {
int type;
int requestId;
AsyncUART *cb;
};
void event_dataArrived();
void event_temperatureEgress();
void event_ctrlEgress();
void event_configEgress();
void event_logEgress();
void handle_brokenIngress();
void handle_badCRC();
void handle_temperatureIngressMsg(pb_istream_t &stream, AsyncUART *cb);
void handle_ctrlIngressMsg(pb_istream_t &stream, AsyncUART *cb);
void handle_configIngressMsg(pb_istream_t &stream, AsyncUART *cb);
bool decode(pb_istream_t &stream, const pb_msgdesc_t *fields, void *dest) const;
bool encode(Message &msg, AsyncUART *cb);

View File

@ -255,7 +255,6 @@ void TemperatureSamplerOrchestrator::handle_samplerConfigRequest( //
void TemperatureSamplerOrchestrator::action_takeSample() {
zephyr::semaphore::k_sem_take_now(_samplerSem);
ULOG_INFO("s");
_current_channel = (_current_channel + 1) % channelNumber;
_channel[_current_channel].take_sample();
/// TODO change channel

View File

@ -1,196 +1,217 @@
#include "uart.hpp"
#include "common.hpp"
#include "log.hpp"
#include "message_decode.hpp"
#include "syscalls/uart.h"
#include <array>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <stdexcept>
#include <exception>
#include <thread>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
#define UART_DEVICE_NODE
// static const struct device *uart_dev = ;
namespace rims {
K_MSGQ_DEFINE(usartEgressQueue, 100, 4, 1);
K_SEM_DEFINE(uart_tx_done, 1, 1);
// static const device *uart_dev = DEVICE_DT_GET(UART_DEVICE_NODE);
constexpr uint16_t rx_buf_size = 300;
constexpr uint8_t rx_buf_num = 2;
static uint8_t rx_buf_channel{0};
static uint8_t rx_buf[rx_buf_num][rx_buf_size]; // DMA RX buffer
static int uartQueueWaitCounter{};
AsyncUART *defaultUartInterface;
class uart_error : public std::exception {
// static int uartQueueWaitCounter{};
static AsyncUART *defaultUartInterface = nullptr;
class uart_not_ready_error : public std::exception {
// exception interface
public:
const char *what() const noexcept override {
return "uard device not ready";
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));
k_work_init(&_work, AsyncUART::workHandler);
uart_callback_set(_dev, AsyncUART::uartCallback, this);
// Start DMA-based reception on the first buffer
// printk("Setup DMA\n");
int ret = uart_rx_enable(_dev, rx_buf[rx_buf_channel], rx_buf_size, 0); // Timeout 1ms
auto ret = uart_irq_callback_user_data_set(_dev, AsyncUART::uartCallback, this);
if (ret < 0) {
throw uart_error{}; // catch this somewhere??
throw uart_not_ready_error{}; // catch this somewhere??
}
uart_irq_rx_enable(_dev);
}
void AsyncUART::loop() {
if (!device_is_ready(_dev)) {
// printk("%s device not ready, exit\n", __PRETTY_FUNCTION__);
///TODO throw?
/// TODO throw?
return; // Exit if the UART device is not ready
}
// static char buffer[300]{};
while (1) {
std::this_thread::sleep_for(std::chrono::seconds{2});
// int offset = 0;
// offset += std::sprintf(buffer + offset, "\n\r");
// offset += std::sprintf(buffer + offset, " UART_TX_DONE : %4d\n\r", uartInterruptsCounters[UART_TX_DONE]);
// offset += std::sprintf(buffer + offset, " UART_TX_ABORTED : %4d\n\r", uartInterruptsCounters[UART_TX_ABORTED]);
// offset += std::sprintf(buffer + offset, " UART_RX_RDY : %4d\n\r", uartInterruptsCounters[UART_RX_RDY]);
// offset += std::sprintf(buffer + offset, " UART_RX_BUF_REQUEST : %4d\n\r", uartInterruptsCounters[UART_RX_BUF_REQUEST]);
// offset += std::sprintf(buffer + offset, "UART_RX_BUF_RELEASED : %4d\n\r", uartInterruptsCounters[UART_RX_BUF_RELEASED]);
// offset += std::sprintf(buffer + offset, " UART_RX_DISABLED : %4d\n\r", uartInterruptsCounters[UART_RX_DISABLED]);
// offset += std::sprintf(buffer + offset, " UART_RX_STOPPED : %4d\n\r", uartInterruptsCounters[UART_RX_STOPPED]);
// offset += std::sprintf(buffer + offset, " TOTAL tx bytes : %4d\n\r", sent_bytes);
// offset += std::sprintf(buffer + offset, " TOTAL rx bytes : %4d\n\r", received_bytes);
// offset += std::sprintf(buffer + offset, " waits : %4d\n\r", uartQueueWaitCounter);
// offset += std::sprintf(buffer + offset, " work total [us] : %4lld\n\r", _workTotal.count() / 1000);
// offset += std::sprintf(buffer + offset, " isr total [us]: %4lld\n\r", _isrTotal.count() / 1000);
// buffer[offset + 1] = 0;
// AsyncUART::transmit(defaultUart(), std::span{reinterpret_cast<std::byte *>(buffer), strlen(buffer)});
}
}
// free function, only need to copy data to uart's TX_BUFFER and that's it
void AsyncUART::transmit(AsyncUART *dev, std::span<std::byte> bytes) {
while (dev->txActive()) {
std::this_thread::sleep_for(std::chrono::microseconds{10}); // wait for finelize transfer
uartQueueWaitCounter++;
}
// assert length ok
memcpy(dev->_txBuffer, bytes.data(), bytes.size());
dev->_txBufferLength = bytes.size();
if (bytes.empty()) return;
k_work_submit(&dev->_work);
}
void AsyncUART::workHandler(k_work *work) {
CONTAINER_OF(work, AsyncUART, _work)->workHandler();
}
int g_ret=0;
void AsyncUART::workHandler() {
auto start = clock::now();
size_t sent = 0;
size_t CHUNK_SIZE = 8;
int maxwaits = 10;
while ((sent < _txBufferLength) && (maxwaits> 0)) {
volatile auto r = k_sem_take(&uart_tx_done, K_MSEC(10));
if (r == 0) {
auto bytes = std::min(CHUNK_SIZE, _txBufferLength - sent);
// sending 1 byte through UART hangs the device, to ommit this situation on larget streams,
// we can split 9 to 5+4 bytes, instead of 8+1
if (bytes == 9) bytes = 5;
if (bytes == 1) {
/// if you really ahve only one byte to send, use non async api
uart_poll_out(_dev, static_cast<unsigned char>(_txBuffer[sent]));
// we need to manualy set the semaphore to protect from lock
k_sem_give(&uart_tx_done);
} else {
g_ret = uart_tx(_dev, reinterpret_cast<uint8_t *>(_txBuffer) + sent, bytes, 50);
if (g_ret != 0) {
while (true) {
k_busy_wait(1);
}
}
bool first = true;
for (auto byte : bytes) {
if (first) {
if (dev->tx_buffer.empty()) {
dev->tx_buffer.emplace_front((uint8_t)byte);
uart_irq_tx_enable(dev->_dev); // enable interrupt
continue;
}
sent += bytes;
}else{
maxwaits--;
first = false;
}
}
_txBufferLength = 0;
auto stop = clock::now();
_workTotal += {stop - start};
while (dev->tx_buffer.full()) {
std::this_thread::sleep_for(std::chrono::microseconds{12});
};
dev->tx_buffer.emplace_front((uint8_t)byte);
}
}
void AsyncUART::uartCallback(const device *dev, uart_event *evt, void *user_data) {
void AsyncUART::uartCallback(const device *dev, void *user_data) {
auto _this = static_cast<AsyncUART *>(user_data);
_this->callback(evt);
_this->uartISR();
}
void AsyncUART::callback(uart_event *evt) {
auto start = clock::now();
_nterruptsCounters[evt->type]++;
buffer buf;
switch (evt->type) {
case UART_TX_DONE:
sent_bytes += evt->data.tx.len;
k_sem_give(&uart_tx_done);
break;
case UART_TX_ABORTED:
while (true) {
k_busy_wait(1);
void AsyncUART::uartISR() {
try {
while (uart_irq_update(_dev) && uart_irq_is_pending(_dev)) {
if (rxHasByte()) {
readByteUart();
}
if (uart_irq_tx_ready(_dev)) {
putByteUart();
}
}
k_sem_give(&uart_tx_done);
break;
case UART_RX_RDY:
// Process received bytes
received_bytes += evt->data.rx.len;
buf = buffer{
.data = reinterpret_cast<std::byte *>(evt->data.rx.buf + evt->data.rx.offset), .size = evt->data.rx.len, .device = this
}; // add callback
k_msgq_put(&messenger_buffer_arrived_queue, &buf, K_MSEC(10));
[[fallthrough]];
case UART_RX_BUF_REQUEST:
rx_buf_channel = rx_buf_channel == 1 ? 0 : 1;
uart_rx_buf_rsp(_dev, rx_buf[rx_buf_channel], rx_buf_size);
break;
case UART_RX_BUF_RELEASED:
// rx_buf_channel = rx_buf_channel == 1 ? 0 : 1;
break;
case UART_RX_DISABLED:
// printk("UART_RX_DISABLED\n");
uart_rx_enable(_dev, rx_buf[rx_buf_channel], rx_buf_size, 0);
break;
default:
break;
} 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) {
}
}
auto stop = clock::now();
_isrTotal += stop - start;
}
bool AsyncUART::rxHasByte() const {
return uart_irq_rx_ready(_dev);
}
void AsyncUART::readByteUart() {
if (faultFlag) {
if (rxByte() == 0) faultFlag = false;
}
// throw on buffer overflow
else if (tx_buffer.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) {
processMessage();
}
}
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 = reinterpret_cast<std::byte *>(rxBuffer().data()), .size = 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 tx_buffer.size();
}
bool AsyncUART::txByte(uint8_t byte) {
auto send_len = uart_fifo_fill(_dev, &byte, 1);
if (send_len != 1) {
// LOG_ERR("Drop %d bytes", rb_len - send_len);
while (1) {
/// just loop here
}
}
return false;
}
void AsyncUART::putByteUart() {
bool hasData = txHasByte();
if (hasData) txByte(tx_buffer.get());
else uart_irq_tx_disable(_dev);
}
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() {
@ -198,6 +219,7 @@ int UARTThread::do_hardwarenInit() {
}
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;

View File

@ -1,9 +1,10 @@
#pragma once
#include "circular_buffer.hpp"
#include "common.hpp"
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <span>
#include <zephyr/drivers/uart.h>
#include <zephyr/kernel.h>
@ -13,37 +14,46 @@ namespace rims {
class AsyncUART;
AsyncUART *defaultUart();
class uart_rx_buffer_overflow;
class uart_rx_not_ready_error;
class AsyncUART {
using rx_buffer_t = static_vector<uint8_t, 256>;
using tx_buffer_t = circular_buffer<uint8_t, 256>;
public:
AsyncUART();
void loop();
static void transmit(AsyncUART *dev, std::span<std::byte> bytes);
static void workHandler(k_work *work);
static void uartCallback(const struct device *dev, struct uart_event *evt, void *user_data);
constexpr bool txFree() const {
return _txBufferLength == 0;
}
constexpr bool txActive() const {
return _txBufferLength != 0;
}
void workHandler();
void callback(struct uart_event *evt);
static void uartCallback(const struct device *dev, void *user_data);
void uartISR();
const device *_dev;
static constexpr int txBufferSize = 300;
private:
std::array<rx_buffer_t, 2> rx_buffers;
uint8_t _currentBufferIndex{};
bool faultFlag = false;
tx_buffer_t tx_buffer;
std::byte _txBuffer[txBufferSize];
std::size_t _txBufferLength = 0;
k_work _work;
// ISR RX part
inline bool rxHasByte() const;
inline void readByteUart(); // process byte
inline uint8_t rxByte(); // low level read byte from device
inline void processMessage();
inline rx_buffer_t &rxBuffer();
inline void switchRxBuffer();
clock::duration _isrTotal{}, _workTotal{};
std::array<int, 7> _nterruptsCounters{};
size_t received_bytes = 0;
size_t sent_bytes = 0;
// ISR TX part
inline void putByteUart();
inline bool txHasByte() const;
inline bool txByte(uint8_t byte); // low level write byte to device
// exception handlers
inline void handleRxNotReadyError(const uart_rx_not_ready_error &e);
inline void handleRxBufferOverflowError(const uart_rx_buffer_overflow &e);
};
class UARTThread : public ZephyrThread {

View File

@ -2,109 +2,123 @@ syntax = "proto3";
package temperature;
enum Error{
NoError = 0;
UnknownChannel = 1;
TemperatureSensorDisconnected = 2;
TemperatureSensorBroken = 3;
enum Error {
NoError = 0;
UnknownChannel = 1;
TemperatureSensorDisconnected = 2;
TemperatureSensorBroken = 3;
}
/* CONFIGURATION */
message SamplerConfig{
optional uint32 samples = 3; // optional, if ommited return current value
optional uint32 period_ms = 4; // optional
message SamplerConfig
{
optional uint32 samples = 3; // optional, if ommited return current value
optional uint32 period_ms = 4; // optional
}
/* INTERNAL STRUCTURES */
// Last temp sample
message TemperatureCurrent {
uint32 channel_id = 1;
// last update
float temp_c = 2;
message TemperatureCurrent
{
uint32 channel_id = 1;
// last update
float temp_c = 2;
}
// Statictics of temperature on given channel
message TemperatureStatistics {
uint32 channel_id = 1;
message TemperatureStatistics
{
uint32 channel_id = 1;
// last update
float temp_c = 2;
// last update
float temp_c = 2;
// avarage from last nsamples
float temp_avg_c = 3;
// avarage from last nsamples
float temp_avg_c = 3;
// standard deviation of temperature on given channel from last nsamples
float temp_stddev_c = 4;
// standard deviation of temperature on given channel from last nsamples
float temp_stddev_c = 4;
// samples taken
uint32 n_samples = 5;
// samples taken
uint32 n_samples = 5;
}
/* CONFIG API */
message SamplerConfigRequest{
uint32 channel_id = 1;
SamplerConfig config = 2;
message SamplerConfigRequest
{
uint32 channel_id = 1;
SamplerConfig config = 2;
}
message SamplerConfigResponse{
oneof data {
SamplerConfig config = 2;
Error error = 254;
}
message SamplerConfigResponse
{
oneof data
{
SamplerConfig config = 2;
Error error = 254;
}
}
// API
message GetCurrentTemperatureRequest{
uint32 channel_id = 1;
message GetCurrentTemperatureRequest
{
uint32 channel_id = 1;
};
message GetCurrentTemperatureResponse{
oneof data {
TemperatureCurrent temperatureCurrent = 1;
Error error = 254;
}
message GetCurrentTemperatureResponse
{
oneof data
{
TemperatureCurrent temperatureCurrent = 1;
Error error = 254;
}
}
message GetTemperatureRequest{
uint32 channel_id = 1;
message GetTemperatureRequest
{
uint32 channel_id = 1;
};
message GetTemperatureResponse{
oneof data {
TemperatureStatistics temperatureStats = 1;
Error error = 254;
}
message GetTemperatureResponse
{
oneof data
{
TemperatureStatistics temperatureStats = 1;
Error error = 254;
}
}
// only those messages are send through wire at temperature endpoint
message IngressMessages
{
uint32 requestID = 255;
oneof data {
// data access
GetCurrentTemperatureRequest currentTemperatureRequest = 1;
GetTemperatureRequest temperatureRequest = 2;
// configuration
SamplerConfigRequest samplerConfigRequest = 3;
}
uint32 requestID = 255;
oneof data
{
// data access
GetCurrentTemperatureRequest currentTemperatureRequest = 1;
GetTemperatureRequest temperatureRequest = 2;
// configuration
SamplerConfigRequest samplerConfigRequest = 3;
}
};
message EgressMessages
{
optional uint32 requestID = 255;
oneof data{
// data access
GetCurrentTemperatureResponse currentTemperatureResponse = 1;
GetTemperatureResponse temperatureResponse = 2;
// configuration
SamplerConfigResponse samplerConfigResponse = 3;
// broadcast
TemperatureStatistics broadcastTemperatureStatistics = 16;
}
optional uint32 requestID = 255;
oneof data
{
// data access
GetCurrentTemperatureResponse currentTemperatureResponse = 1;
GetTemperatureResponse temperatureResponse = 2;
// configuration
SamplerConfigResponse samplerConfigResponse = 3;
// broadcast
TemperatureStatistics broadcastTemperatureStatistics = 16;
}
};