From 692df4517ee5a0c2777610326467043369087876 Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Fri, 10 Apr 2026 18:57:09 +0200 Subject: [PATCH 01/12] feat: add NORT compile-time flag for non-real-time bus operation Co-Authored-By: Claude Opus 4.6 (1M context) --- engine/IO/src/luos_phy.c | 77 ++++++++++++++++++++++++++++++++--- engine/core/inc/struct_luos.h | 5 +++ engine/core/src/service.c | 8 ++++ 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/engine/IO/src/luos_phy.c b/engine/IO/src/luos_phy.c index 8adb64a77..b79aabccb 100644 --- a/engine/IO/src/luos_phy.c +++ b/engine/IO/src/luos_phy.c @@ -57,6 +57,9 @@ /******************************************************************************* * Definitions ******************************************************************************/ +#ifndef DETECTION_TIMEOUT_MS + #define DETECTION_TIMEOUT_MS 10000 +#endif typedef struct __attribute__((__packed__)) { @@ -77,8 +80,8 @@ typedef struct port_t topology_source; // The source port. Where we receive the topological detection signal from. uint32_t topology_done; // We put this bits to 1 when a phy ended the topology detection. bool topology_running; // We put this bits to 1 when a phy is running the topology detection. - bool find_next_node_job; // We put this bits to 1 to indicate that we will need to find another node. - bool resetAllNeed; // We put this bits to 1 to indicate that we will need to reset all the nodes. We need it to avoid to reset all phy at reset message reception, allowing the phy's to send their reset message. + volatile bool find_next_node_job; // We put this bits to 1 to indicate that we will need to find another node. + volatile bool resetAllNeed; // We put this bits to 1 to indicate that we will need to reset all the nodes. We need it to avoid to reset all phy at reset message reception, allowing the phy's to send their reset message. bool PhyExeptSourceDone; // We put this bit to 1 when all the phys except the source one are done with their detection. // ******************** Job management ******************** @@ -192,8 +195,15 @@ bool Phy_Busy(void) ******************************************************************************/ void Phy_Loop(void) { + +#ifdef NORT + Phy_SetIrqState(false); +#endif if (phy_ctx.resetAllNeed == true) { +#ifdef NORT + Phy_SetIrqState(true); +#endif if (Phy_TxAllComplete() == SUCCEED) { Phy_ResetAll(); @@ -205,6 +215,12 @@ void Phy_Loop(void) return; } } +#ifdef NORT + else + { + Phy_SetIrqState(true); + } +#endif // Manage received data allocation if (phy_ctx.phy[1].rx_alloc_job) { @@ -215,14 +231,38 @@ void Phy_Loop(void) // Manage complete message received dispatching Phy_Dispatch(); // Check if we need to find the next node +#ifdef NORT + Phy_SetIrqState(false); +#endif if (phy_ctx.find_next_node_job == true) { phy_ctx.find_next_node_job = false; +#ifdef NORT + Phy_SetIrqState(true); +#endif // Wait for the node to send all its messages. + uint32_t tx_wait_start = LuosHAL_GetSystick(); while (Phy_TxAllComplete() == FAILED) - ; + { + if (LuosHAL_GetSystick() - tx_wait_start > DETECTION_TIMEOUT_MS) + { + break; + } + } +#ifdef NORT + // On Linux, PTP falling edge may be detected before PORT_DATA is + // consumed (both arrive in the same RX thread iteration). Dispatch + // any pending messages before proceeding with the next detection step. + Phy_Dispatch(); +#endif Phy_FindNextNode(); } +#ifdef NORT + else + { + Phy_SetIrqState(true); + } +#endif // Compute phy job statistics /* uint8_t stat = (uint8_t)((job nbr * 100) / (MAX_MSG_NB)); @@ -504,7 +544,11 @@ _CRITICAL void Phy_ComputeHeader(luos_phy_t *phy_ptr) // Someone need to receive this message phy_ptr->rx_keep = true; phy_ptr->rx_alloc_job = true; - phy_ptr->rx_ack = ((((header_t *)phy_ptr->rx_buffer_base)->target_mode == SERVICEIDACK) || (((header_t *)phy_ptr->rx_buffer_base)->target_mode == NODEIDACK)); +#ifdef NORT + phy_ptr->rx_ack = false; +#else + phy_ptr->rx_ack = ((((header_t *)phy_ptr->rx_buffer_base)->target_mode == SERVICEIDACK) || (((header_t *)phy_ptr->rx_buffer_base)->target_mode == NODEIDACK)); +#endif } else { @@ -542,12 +586,16 @@ bool Phy_Need(luos_phy_t *phy_ptr, header_t *header) // This concerns Luos phy and all external phy return true; break; +#ifndef NORT case SERVICEIDACK: +#endif case SERVICEID: // If the target is not the phy_ptr, and the source service is known, we need to keep this message return (!Phy_IndexFilter(phy_ptr->services, header->target)) && (Phy_IndexFilter(phy_ptr->services, header->source)); break; +#ifndef NORT case NODEIDACK: +#endif case NODEID: if (header->target == 0) { @@ -587,7 +635,9 @@ inline phy_target_t Phy_ComputeTargets(luos_phy_t *phy_ptr, header_t *header) switch (header->target_mode) { +#ifndef NORT case SERVICEIDACK: +#endif case SERVICEID: // Check all phy service id for (int i = 0; i < phy_ctx.phy_nb; i++) @@ -599,7 +649,9 @@ inline phy_target_t Phy_ComputeTargets(luos_phy_t *phy_ptr, header_t *header) } } break; +#ifndef NORT case NODEIDACK: +#endif case NODEID: // If the target is our node and our node have a node_id or if we don't have a node_id and we are waiting for one. if (((header->target == Node_Get()->node_id) && (header->target != 0)) @@ -865,10 +917,21 @@ static void Phy_Dispatch(void) { static bool running = false; int i = 0; +#ifdef NORT + Phy_SetIrqState(false); + if ((running) || (phy_ctx.io_job_nb == 0)) + { + Phy_SetIrqState(true); + return; + } + running = true; + Phy_SetIrqState(true); +#else if ((running) || (phy_ctx.io_job_nb == 0)) { return; } +#endif // Interpreat received messages and create tasks for it. Phy_SetIrqState(false); while (i < phy_ctx.io_job_nb) @@ -897,7 +960,11 @@ static void Phy_Dispatch(void) phy_job_t phy_job; phy_job.msg_pt = job->alloc_msg; phy_job.size = job->size; - phy_job.ack = ((job->alloc_msg->header.target_mode == NODEIDACK) || (job->alloc_msg->header.target_mode == SERVICEIDACK)); +#ifdef NORT + phy_job.ack = false; +#else + phy_job.ack = ((job->alloc_msg->header.target_mode == NODEIDACK) || (job->alloc_msg->header.target_mode == SERVICEIDACK)); +#endif phy_job.timestamp = Luos_IsMsgTimstamped(job->alloc_msg); phy_job.phy_data = NULL; diff --git a/engine/core/inc/struct_luos.h b/engine/core/inc/struct_luos.h index 278976af0..eabe510c6 100644 --- a/engine/core/inc/struct_luos.h +++ b/engine/core/inc/struct_luos.h @@ -95,6 +95,11 @@ typedef enum IDACK = SERVICEIDACK /*!< This define is deprecated, please use SERVICEIDACK instead. */ } target_mode_t; +#ifdef NORT + #define SERVICEIDACK SERVICEID + #define NODEIDACK NODEID +#endif + /****************************************************************************** * @struct header_t * @brief This structure is used specify data and destination of datas. diff --git a/engine/core/src/service.c b/engine/core/src/service.c index d20876f7f..922d1cf74 100644 --- a/engine/core/src/service.c +++ b/engine/core/src/service.c @@ -239,7 +239,9 @@ service_t *Service_GetConcerned(const header_t *header) // Find if we are concerned by this message. switch (header->target_mode) { +#ifndef NORT case SERVICEIDACK: +#endif case SERVICEID: // Check all service id for (i = 0; i < service_ctx.number; i++) @@ -261,7 +263,9 @@ service_t *Service_GetConcerned(const header_t *header) } break; case BROADCAST: +#ifndef NORT case NODEIDACK: +#endif case NODEID: return &service_ctx.list[0]; break; @@ -326,7 +330,9 @@ service_filter_t Service_GetFilter(const msg_t *msg) // Find if we are concerned by this message. switch (msg->header.target_mode) { +#ifndef NORT case SERVICEIDACK: +#endif case SERVICEID: // Check all service id for (i = 0; i < service_ctx.number; i++) @@ -363,7 +369,9 @@ service_filter_t Service_GetFilter(const msg_t *msg) } } break; +#ifndef NORT case NODEIDACK: +#endif case NODEID: LUOS_ASSERT(msg->header.target != DEFAULTID); // check if the message is for the node From 1c6763900899db089dd5eed7f077171e9a2ef43c Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Fri, 10 Apr 2026 18:58:18 +0200 Subject: [PATCH 02/12] feat: add detect ACK mode mirroring for slave nodes Co-Authored-By: Claude Opus 4.6 (1M context) --- engine/IO/inc/luos_io.h | 2 ++ engine/IO/src/luos_io.c | 28 ++++++++++++++++++++++++---- engine/IO/src/luos_phy.c | 5 +++-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/engine/IO/inc/luos_io.h b/engine/IO/inc/luos_io.h index 473e5857b..266ff08c3 100644 --- a/engine/IO/inc/luos_io.h +++ b/engine/IO/inc/luos_io.h @@ -28,6 +28,8 @@ void LuosIO_Loop(void); int LuosIO_TopologyDetection(service_t *service, connection_t *connection_table); error_return_t LuosIO_Send(service_t *service, msg_t *msg); +uint8_t LuosIO_GetDetectAckMode(void); + // Job management error_return_t LuosIO_GetNextJob(phy_job_t **job); void LuosIO_RmJob(phy_job_t *job); diff --git a/engine/IO/src/luos_io.c b/engine/IO/src/luos_io.c index e8fac013d..2a9701f45 100644 --- a/engine/IO/src/luos_io.c +++ b/engine/IO/src/luos_io.c @@ -24,6 +24,7 @@ static int LuosIO_StartTopologyDetection(service_t *service); static int LuosIO_DetectNextNodes(service_t *service); static error_return_t LuosIO_ConsumeMsg(const msg_t *input); static void LuosIO_TransmitLocalRoutingTable(service_t *service, msg_t *routeTB_msg); +static void LuosIO_SetDetectAckMode(uint8_t mode); // Phy_callbacks static void LuosIO_MsgHandler(luos_phy_t *phy_ptr, phy_job_t *job); @@ -37,8 +38,9 @@ connection_t *connection_table_ptr = NULL; luos_phy_t *luos_phy; service_filter_t service_filter[MAX_MSG_NB]; // Service filter table. Each of these filter will be linked with jobs. uint8_t service_filter_index = 0; // Index of the next service filter to use. -service_t *detection_service = NULL; -bool Flag_DetectServices = false; +service_t *detection_service = NULL; +bool Flag_DetectServices = false; +static uint8_t detect_ack_mode = NODEIDACK; /******************************************************************************* * Functions @@ -59,6 +61,17 @@ void LuosIO_Reset(luos_phy_t *phy_ptr) Service_ClearId(); // Reset the data reception context Luos_ReceiveData(NULL, NULL, NULL); + detect_ack_mode = NODEIDACK; +} + +static void LuosIO_SetDetectAckMode(uint8_t mode) +{ + detect_ack_mode = mode; +} + +uint8_t LuosIO_GetDetectAckMode(void) +{ + return detect_ack_mode; } /****************************************************************************** @@ -437,6 +450,9 @@ error_return_t LuosIO_ConsumeMsg(const msg_t *input) // Reinit Phy Phy_Reset(); } + // Store the ACK mode used by the detecting master so we mirror it in our responses + // Must be after LuosIO_Reset which calls Node_Init and would overwrite this value + LuosIO_SetDetectAckMode(input->header.target_mode); // Save our new node id // We have to do it this way because Node_Get()->node_id is a bitfield and input->data is not well aligned. uint16_t node_id; @@ -456,7 +472,7 @@ error_return_t LuosIO_ConsumeMsg(const msg_t *input) port_t *input_port = Phy_GetTopologysource(); input_port->node_id = Node_Get()->node_id; - output_msg.header.target_mode = NODEIDACK; + output_msg.header.target_mode = LuosIO_GetDetectAckMode(); output_msg.header.target = 1; output_msg.header.cmd = PORT_DATA; output_msg.header.size = sizeof(port_t); @@ -483,7 +499,7 @@ error_return_t LuosIO_ConsumeMsg(const msg_t *input) case 0: // Send back a local routing table output_msg.header.cmd = RTB; - output_msg.header.target_mode = NODEIDACK; + output_msg.header.target_mode = LuosIO_GetDetectAckMode(); output_msg.header.target = input->header.source; LuosIO_TransmitLocalRoutingTable(0, &output_msg); break; @@ -666,9 +682,13 @@ error_return_t LuosIO_ConsumeMsg(const msg_t *input) //**************************************** bootloader section **************************************** case BOOTLOADER_RESET: +#ifdef WITH_BOOTLOADER LuosHAL_SetMode((uint8_t)BOOT_MODE); LuosHAL_Reboot(); return SUCCEED; +#else + return FAILED; +#endif break; #ifdef WITH_BOOTLOADER diff --git a/engine/IO/src/luos_phy.c b/engine/IO/src/luos_phy.c index b79aabccb..5359e8b6e 100644 --- a/engine/IO/src/luos_phy.c +++ b/engine/IO/src/luos_phy.c @@ -53,6 +53,7 @@ #include "luos_io.h" #include "service.h" #include "filter.h" +#include "node.h" /******************************************************************************* * Definitions @@ -366,7 +367,7 @@ error_return_t Phy_FindNextNode(void) // We find a new node on this specific output_port // Send the output_port information to master as a partial CONNECTION_DATA and ask it to generate and send a new node_id. msg_t msg; - msg.header.target_mode = NODEIDACK; + msg.header.target_mode = LuosIO_GetDetectAckMode(); msg.header.target = 1; msg.header.cmd = CONNECTION_DATA; msg.header.size = sizeof(port_t); @@ -398,7 +399,7 @@ error_return_t Phy_FindNextNode(void) // We find a new node on this specific output_port // Send the output_port information to master as a partial CONNECTION_DATA and ask it to generate and send a new node_id. msg_t msg; - msg.header.target_mode = NODEIDACK; + msg.header.target_mode = LuosIO_GetDetectAckMode(); msg.header.target = 1; msg.header.cmd = CONNECTION_DATA; msg.header.size = sizeof(port_t); From 60773c96f8e54279c59bfc81a0c5419c75bb54d4 Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Fri, 10 Apr 2026 18:58:24 +0200 Subject: [PATCH 03/12] feat: add Linux Luos HAL for Raspberry Pi Co-Authored-By: Claude Opus 4.6 (1M context) --- engine/HAL/LINUX/luos_hal.c | 199 +++++++++++++++++++++++++++++ engine/HAL/LINUX/luos_hal.h | 55 ++++++++ engine/HAL/LINUX/luos_hal_config.h | 69 ++++++++++ 3 files changed, 323 insertions(+) create mode 100644 engine/HAL/LINUX/luos_hal.c create mode 100644 engine/HAL/LINUX/luos_hal.h create mode 100644 engine/HAL/LINUX/luos_hal_config.h diff --git a/engine/HAL/LINUX/luos_hal.c b/engine/HAL/LINUX/luos_hal.c new file mode 100644 index 000000000..64b0fd8fb --- /dev/null +++ b/engine/HAL/LINUX/luos_hal.c @@ -0,0 +1,199 @@ +/****************************************************************************** + * @file luosHAL + * @brief Luos Hardware Abstration Layer. Describe Low layer fonction + * @Family Linux (Raspberry Pi) + * @author Luos + * @version 0.0.0 + ******************************************************************************/ +#include "luos_hal.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +pthread_mutex_t luos_recursive_mutex; + +/******************************************************************************* + * Function + ******************************************************************************/ +static void LuosHAL_SystickInit(void); +static void LuosHAL_FlashInit(void); + +/****************************************************************************** + * @brief Luos HAL general initialisation + * @param None + * @return None + ******************************************************************************/ +void LuosHAL_Init(void) +{ + // Initialize the recursive mutex FIRST, before anything else. + // MsgAlloc_Init() and Phy_Init() call Phy_SetIrqState() during + // initialization, so the mutex must be ready before they run. + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&luos_recursive_mutex, &attr); + pthread_mutexattr_destroy(&attr); + + LuosHAL_SystickInit(); + LuosHAL_FlashInit(); + LuosHAL_StartTimestamp(); +} + +/****************************************************************************** + * @brief Luos HAL IRQ state control via recursive mutex + * @param Enable: false=lock, true=unlock + * @return None + ******************************************************************************/ +void LuosHAL_SetIrqState(bool Enable) +{ + if (Enable == false) + { + pthread_mutex_lock(&luos_recursive_mutex); + } + else + { + pthread_mutex_unlock(&luos_recursive_mutex); + } +} + +/****************************************************************************** + * @brief Luos HAL general systick tick at 1ms initialize + * @param None + * @return None + ******************************************************************************/ +static void LuosHAL_SystickInit(void) +{ +} + +/****************************************************************************** + * @brief Luos HAL general systick tick at 1ms + * @param None + * @return tick Counter + ******************************************************************************/ +uint32_t LuosHAL_GetSystick(void) +{ + struct timespec time; + uint32_t ms; + time_t s; + clock_gettime(CLOCK_MONOTONIC, &time); + s = time.tv_sec; + ms = round(time.tv_nsec / 1.0e6); + if (ms > 999) + { + s++; + ms = 0; + } + ms += s * 1000; + return ms; +} + +/****************************************************************************** + * @brief Luos GetTimestamp + * @param None + * @return uint64_t + ******************************************************************************/ +uint64_t LuosHAL_GetTimestamp(void) +{ + struct timespec time; + clock_gettime(CLOCK_MONOTONIC, &time); + uint64_t timestamp = time.tv_nsec + time.tv_sec * 1000000000; + return timestamp; +} + +/****************************************************************************** + * @brief Luos start Timestamp + * @param None + * @return None + ******************************************************************************/ +void LuosHAL_StartTimestamp(void) +{ +} + +/****************************************************************************** + * @brief Luos stop Timestamp + * @param None + * @return None + ******************************************************************************/ +void LuosHAL_StopTimestamp(void) +{ +} + +/****************************************************************************** + * @brief Flash Initialisation + * @param None + * @return None + ******************************************************************************/ +static void LuosHAL_FlashInit(void) +{ + for (uint16_t i = 0; i < FLASH_PAGE_NUMBER; i++) + { + for (uint16_t j = 0; j < FLASH_PAGE_SIZE; j++) + { + stub_flash_x86[i][j] = 0; + } + } +} + +/****************************************************************************** + * @brief Write flash page where Luos keep permanente information + * @param Address page / size to write / pointer to data to write + * @return + ******************************************************************************/ +void LuosHAL_FlashWriteLuosMemoryInfo(uint32_t addr, uint16_t size, uint8_t *data) +{ +} + +/****************************************************************************** + * @brief read information from page where Luos keep permanente information + * @param Address info / size to read / pointer callback data to read + * @return + ******************************************************************************/ +void LuosHAL_FlashReadLuosMemoryInfo(uint32_t addr, uint16_t size, uint8_t *data) +{ + memset(data, 0xFF, size); +} + +/****************************************************************************** + * @brief Set boot mode -- no-op on Linux (no bootloader) + * @param mode + * @return + ******************************************************************************/ +void LuosHAL_SetMode(uint8_t mode) +{ +} + +/****************************************************************************** + * @brief Save node ID to persistent file + * @param node_id + * @return + ******************************************************************************/ +void LuosHAL_SaveNodeID(uint16_t node_id) +{ + // Ensure the parent directory exists + char path_copy[256]; + strncpy(path_copy, LUOS_PERSIST_PATH, sizeof(path_copy) - 1); + path_copy[sizeof(path_copy) - 1] = '\0'; + mkdir(dirname(path_copy), 0755); + + FILE *f = fopen(LUOS_PERSIST_PATH, "wb"); + if (f) + { + fwrite(&node_id, sizeof(uint16_t), 1, f); + fclose(f); + } +} + +/****************************************************************************** + * @brief software reboot -- no-op on Linux + * @param + * @return + ******************************************************************************/ +void LuosHAL_Reboot(void) +{ +} diff --git a/engine/HAL/LINUX/luos_hal.h b/engine/HAL/LINUX/luos_hal.h new file mode 100644 index 000000000..90a38e9c6 --- /dev/null +++ b/engine/HAL/LINUX/luos_hal.h @@ -0,0 +1,55 @@ +/****************************************************************************** + * @file luosHAL + * @brief Luos Hardware Abstration Layer. Describe Low layer fonction + * @Family Linux (Raspberry Pi) + * @author Luos + * @version 0.0.0 + ******************************************************************************/ +#ifndef _LUOSHAL_H_ +#define _LUOSHAL_H_ + +#include +#include +#include "luos_hal_config.h" + +/******************************************************************************* + * Definitions + ******************************************************************************/ +#ifndef _CRITICAL + #define _CRITICAL +#endif + +#define LUOS_UUID ((uint32_t *)0x00000001) + +#define ADDRESS_ALIASES_FLASH ADDRESS_LAST_PAGE_FLASH +#define ADDRESS_BOOT_FLAG_FLASH (ADDRESS_LAST_PAGE_FLASH + PAGE_SIZE) - 4 + +/******************************************************************************* + * Function + ******************************************************************************/ +void LuosHAL_Init(void); +void LuosHAL_SetIrqState(bool Enable); +uint32_t LuosHAL_GetSystick(void); +void LuosHAL_FlashWriteLuosMemoryInfo(uint32_t addr, uint16_t size, uint8_t *data); +void LuosHAL_FlashReadLuosMemoryInfo(uint32_t addr, uint16_t size, uint8_t *data); + +// bootloader functions +void LuosHAL_SetMode(uint8_t mode); +void LuosHAL_Reboot(void); +void LuosHAL_SaveNodeID(uint16_t); + +#ifdef BOOTLOADER_CONFIG +void LuosHAL_DeInit(void); +void LuosHAL_JumpToApp(uint32_t); +uint8_t LuosHAL_GetMode(void); +uint16_t LuosHAL_GetNodeID(void); +void LuosHAL_EraseMemory(uint32_t, uint16_t); +void LuosHAL_ProgramFlash(uint32_t, uint16_t, uint8_t *); +#endif + +// timestamp functions +uint64_t LuosHAL_GetTimestamp(void); +void LuosHAL_StartTimestamp(void); +void LuosHAL_StopTimestamp(void); + +#endif /* _LUOSHAL_H_ */ diff --git a/engine/HAL/LINUX/luos_hal_config.h b/engine/HAL/LINUX/luos_hal_config.h new file mode 100644 index 000000000..a010ef3eb --- /dev/null +++ b/engine/HAL/LINUX/luos_hal_config.h @@ -0,0 +1,69 @@ +/****************************************************************************** + * @file luosHAL_Config + * @brief This file allow you to configure LuosHAL according to your design + * this is the default configuration created by Luos team for this MCU Family + * Do not modify this file if you want to ovewrite change define in you project + * @Family Linux (Raspberry Pi) + * @author Luos + * @version 0.0.0 + ******************************************************************************/ +#ifndef _LUOSHAL_CONFIG_H_ +#define _LUOSHAL_CONFIG_H_ + +#include + +#ifndef MCUFREQ + #define MCUFREQ 1500000000 +#endif + +/******************************************************************************* + * DEFINE STUB FLASH FOR LINUX + ******************************************************************************/ +#ifndef FLASH_PAGE_SIZE + #define FLASH_PAGE_SIZE 0x100 +#endif +#ifndef FLASH_PAGE_NUMBER + #define FLASH_PAGE_NUMBER 8 +#endif +static uint32_t stub_flash_x86[FLASH_PAGE_NUMBER][FLASH_PAGE_SIZE]; +static uint32_t *last_page_stub_flash_x86 = &stub_flash_x86[FLASH_PAGE_NUMBER - 1][FLASH_PAGE_SIZE]; + +/******************************************************************************* + * SINGLE RECURSIVE MUTEX FOR ALL CRITICAL SECTIONS + * Consolidates IRQ state, MSGALLOC_MUTEX, and LUOS_MUTEX into one + * recursive mutex to prevent lock-ordering deadlocks. + ******************************************************************************/ +extern pthread_mutex_t luos_recursive_mutex; + +#ifndef MSGALLOC_MUTEX_LOCK + #define MSGALLOC_MUTEX_LOCK pthread_mutex_lock(&luos_recursive_mutex); +#endif +#ifndef MSGALLOC_MUTEX_UNLOCK + #define MSGALLOC_MUTEX_UNLOCK pthread_mutex_unlock(&luos_recursive_mutex); +#endif + +#ifndef LUOS_MUTEX_LOCK + #define LUOS_MUTEX_LOCK pthread_mutex_lock(&luos_recursive_mutex); +#endif +#ifndef LUOS_MUTEX_UNLOCK + #define LUOS_MUTEX_UNLOCK pthread_mutex_unlock(&luos_recursive_mutex); +#endif + +/******************************************************************************* + * FLASH CONFIG + ******************************************************************************/ +#ifndef PAGE_SIZE + #define PAGE_SIZE (uint32_t) FLASH_PAGE_SIZE +#endif +#ifndef ADDRESS_LAST_PAGE_FLASH + #define ADDRESS_LAST_PAGE_FLASH (uint32_t) last_page_stub_flash_x86 +#endif + +/******************************************************************************* + * NODE PERSISTENCE + ******************************************************************************/ +#ifndef LUOS_PERSIST_PATH + #define LUOS_PERSIST_PATH "/var/lib/luos/node_config" +#endif + +#endif /* _LUOSHAL_CONFIG_H_ */ From 5256b847eb11241e8244e91e08d19bac5e1f5cd1 Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Fri, 10 Apr 2026 19:00:32 +0200 Subject: [PATCH 04/12] feat: add non-real-time system support for Robus Co-Authored-By: Claude Opus 4.6 (1M context) --- network/robus_network/inc/port_manager.h | 3 ++ network/robus_network/robus_config.h | 3 ++ network/robus_network/src/port_manager.c | 45 +++++++++++++++++++++--- network/robus_network/src/robus.c | 3 ++ 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/network/robus_network/inc/port_manager.h b/network/robus_network/inc/port_manager.h index a71ba3a73..9b96f683b 100644 --- a/network/robus_network/inc/port_manager.h +++ b/network/robus_network/inc/port_manager.h @@ -30,5 +30,8 @@ void PortMng_PtpHandler(uint8_t PortNbr); error_return_t PortMng_PokeNextPort(uint8_t *portId); uint8_t PortMng_PortPokedStatus(void); bool PortMng_Busy(void); +#ifdef NORT +void PortMng_WatchdogCheck(void); +#endif #endif /* _PORTMANAGER_H_ */ diff --git a/network/robus_network/robus_config.h b/network/robus_network/robus_config.h index 1d76971ad..a5519c345 100644 --- a/network/robus_network/robus_config.h +++ b/network/robus_network/robus_config.h @@ -30,4 +30,7 @@ #define CRC_SIZE 2 +// Uncomment for non-real-time platforms (disables ACK modes, enables PTP watchdog) +// #define NORT + #endif /* _ROBUS_CONFIG_H_ */ diff --git a/network/robus_network/src/port_manager.c b/network/robus_network/src/port_manager.c index 5bff7d5fc..6c28eea0a 100644 --- a/network/robus_network/src/port_manager.c +++ b/network/robus_network/src/port_manager.c @@ -40,6 +40,18 @@ #include "robus_hal.h" #include "luos_hal.h" +#ifndef PTP_PUSH_DELAY_MS + #define PTP_PUSH_DELAY_MS 2 +#endif +#ifndef PTP_READ_DELAY_MS + #define PTP_READ_DELAY_MS 3 +#endif +#ifdef NORT + #ifndef PTP_RELEASE_TIMEOUT_MS + #define PTP_RELEASE_TIMEOUT_MS 500 + #endif +#endif + /******************************************************************************* * Definitions ******************************************************************************/ @@ -52,8 +64,11 @@ typedef enum /******************************************************************************* * Variables ******************************************************************************/ -PortState_t Port_ExpectedState = POKE; +volatile PortState_t Port_ExpectedState = POKE; uint32_t port_detected = 0; +#ifdef NORT +static uint32_t ptp_release_enter_tick = 0; +#endif /******************************************************************************* * Function ******************************************************************************/ @@ -119,18 +134,21 @@ uint8_t PortMng_PokePort(uint8_t PortNbr) RobusHAL_PushPTP(PortNbr); // Wait a little just to be sure everyone can read it uint32_t start_tick = LuosHAL_GetSystick(); - while (LuosHAL_GetSystick() - start_tick < 2) + while (LuosHAL_GetSystick() - start_tick < PTP_PUSH_DELAY_MS) ; // Release the ptp line RobusHAL_SetPTPDefaultState(PortNbr); - while (LuosHAL_GetSystick() - start_tick < 3) + while (LuosHAL_GetSystick() - start_tick < PTP_READ_DELAY_MS) ; // Read the line state if (RobusHAL_GetPTPState(PortNbr)) { // Someone reply, reverse the detection to wake up on release condition RobusHAL_SetPTPReverseState(PortNbr); - Port_ExpectedState = RELEASE; + Port_ExpectedState = RELEASE; +#ifdef NORT + ptp_release_enter_tick = LuosHAL_GetSystick(); +#endif // Port poked by node ctx.port.activ = PortNbr; ctx.port.keepLine = true; @@ -169,6 +187,25 @@ error_return_t PortMng_PokeNextPort(uint8_t *portId) PortMng_Reset(); return FAILED; } +#ifdef NORT +/****************************************************************************** + * @brief Check for stuck PTP detection and recover + * @param None + * @return None + ******************************************************************************/ +void PortMng_WatchdogCheck(void) +{ + if (Port_ExpectedState == RELEASE + && (LuosHAL_GetSystick() - ptp_release_enter_tick > PTP_RELEASE_TIMEOUT_MS)) + { + Port_ExpectedState = POKE; + ctx.port.keepLine = false; + Phy_TopologyDone(Robus_GetPhy()); + PortMng_Reset(); + Phy_TopologyNext(); + } +} +#endif /****************************************************************************** * @brief reinit the detection state machine * @param None diff --git a/network/robus_network/src/robus.c b/network/robus_network/src/robus.c index a95cd1f5b..ba1f41f01 100644 --- a/network/robus_network/src/robus.c +++ b/network/robus_network/src/robus.c @@ -67,6 +67,9 @@ void Robus_Init(void) ******************************************************************************/ void Robus_Loop(void) { +#ifdef NORT + PortMng_WatchdogCheck(); +#endif RobusHAL_Loop(); } From ff842bbf20e765053606479f5c9f3b7c29b5fb7c Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Fri, 10 Apr 2026 19:00:39 +0200 Subject: [PATCH 05/12] feat: add Linux Robus HAL for Raspberry Pi RS485 bus participation Co-Authored-By: Claude Opus 4.6 (1M context) --- network/robus_network/HAL/LINUX/robus_hal.c | 613 ++++++++++++++++++ network/robus_network/HAL/LINUX/robus_hal.h | 33 + .../HAL/LINUX/robus_hal_config.h | 88 +++ 3 files changed, 734 insertions(+) create mode 100644 network/robus_network/HAL/LINUX/robus_hal.c create mode 100644 network/robus_network/HAL/LINUX/robus_hal.h create mode 100644 network/robus_network/HAL/LINUX/robus_hal_config.h diff --git a/network/robus_network/HAL/LINUX/robus_hal.c b/network/robus_network/HAL/LINUX/robus_hal.c new file mode 100644 index 000000000..cab920aa1 --- /dev/null +++ b/network/robus_network/HAL/LINUX/robus_hal.c @@ -0,0 +1,613 @@ +/****************************************************************************** + * @file robus_HAL + * @brief Robus Hardware Abstration Layer. Describe Low layer fonction + * @Family Linux (Raspberry Pi) + * @author Luos + * @version 0.0.0 + ******************************************************************************/ +#include "robus_hal.h" +#include "reception.h" +#include "context.h" +#include "luos_hal.h" +#include "port_manager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/******************************************************************************* + * Definitions + ******************************************************************************/ +#define DEFAULT_TIMEOUT 100 + +/******************************************************************************* + * Variables + ******************************************************************************/ +static int serial_fd = -1; +static int timer_fd = -1; +static pthread_t rx_thread; +static volatile bool running = false; +static volatile bool rx_enabled = true; +static volatile bool tx_in_progress = false; +static uint64_t timeout_ns_per_bit = 0; + +// GPIO via libgpiod v2 +static struct gpiod_chip *gpio_chip = NULL; +static struct gpiod_line_request *tx_en_req = NULL; +static struct gpiod_line_request *ptp_reqs[NBR_PORT]; +static unsigned int ptp_gpio_offsets[NBR_PORT]; +static volatile uint8_t ptp_last_value[NBR_PORT]; +static volatile bool ptp_edge_detected[NBR_PORT]; +static volatile int8_t ptp_edge_direction[NBR_PORT]; // 1=rising only, -1=falling only, 0=both + +/******************************************************************************* + * Function prototypes + ******************************************************************************/ +static void *RobusHAL_RxThread(void *arg); +static void RobusHAL_GPIOInit(void); +static void RobusHAL_CRCInit(void); + +/****************************************************************************** + * @brief Robus HAL initialization + ******************************************************************************/ +void RobusHAL_Init(void) +{ + RobusHAL_GPIOInit(); + RobusHAL_CRCInit(); + RobusHAL_ComInit(ROBUS_NETWORK_BAUDRATE); +} + +/****************************************************************************** + * @brief Robus HAL main loop + ******************************************************************************/ +void RobusHAL_Loop(void) +{ +} + +/****************************************************************************** + * @brief Initialize serial communication + ******************************************************************************/ +void RobusHAL_ComInit(uint32_t Baudrate) +{ + serial_fd = open(ROBUS_COM_DEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (serial_fd < 0) + { + perror("RobusHAL: Failed to open serial port"); + return; + } + + struct termios tty; + memset(&tty, 0, sizeof(tty)); + if (tcgetattr(serial_fd, &tty) != 0) + { + perror("RobusHAL: tcgetattr failed"); + close(serial_fd); + serial_fd = -1; + return; + } + + cfmakeraw(&tty); + // Only 1 Mbps is supported. For other baud rates, add a Baudrate-to-Bx mapping. + _Static_assert(ROBUS_NETWORK_BAUDRATE == 1000000, "Linux HAL only supports 1 Mbps"); + (void)Baudrate; + cfsetispeed(&tty, B1000000); + cfsetospeed(&tty, B1000000); + + tty.c_cflag |= (CLOCAL | CREAD); + tty.c_cflag &= ~CRTSCTS; + tty.c_cflag &= ~CSTOPB; + tty.c_cflag &= ~CSIZE; + tty.c_cflag |= CS8; + tty.c_cc[VMIN] = 0; + tty.c_cc[VTIME] = 0; + + if (tcsetattr(serial_fd, TCSANOW, &tty) != 0) + { + perror("RobusHAL: tcsetattr failed"); + close(serial_fd); + serial_fd = -1; + return; + } + tcflush(serial_fd, TCIOFLUSH); + + // Ensure non-blocking after tcsetattr (some drivers clear it) + int flags = fcntl(serial_fd, F_GETFL, 0); + fcntl(serial_fd, F_SETFL, flags | O_NONBLOCK); + + timeout_ns_per_bit = 1000000000ULL / Baudrate; + + timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); + if (timer_fd < 0) + { + perror("RobusHAL: timerfd_create failed"); + close(serial_fd); + serial_fd = -1; + return; + } + + running = true; + if (pthread_create(&rx_thread, NULL, RobusHAL_RxThread, NULL) != 0) + { + perror("RobusHAL: pthread_create failed"); + running = false; + close(timer_fd); + close(serial_fd); + timer_fd = -1; + serial_fd = -1; + return; + } +} + +/****************************************************************************** + * @brief Set TX state (RS485 direction) + ******************************************************************************/ +void RobusHAL_SetTxState(uint8_t Enable) +{ + if (tx_en_req == NULL) + { + return; + } + enum gpiod_line_value val = Enable ? GPIOD_LINE_VALUE_ACTIVE : GPIOD_LINE_VALUE_INACTIVE; + gpiod_line_request_set_value(tx_en_req, TX_EN_GPIO_LINE, val); +} + +/****************************************************************************** + * @brief Set RX state + ******************************************************************************/ +void RobusHAL_SetRxState(uint8_t Enable) +{ + rx_enabled = (Enable == true); +} + +/****************************************************************************** + * @brief Transmit data over serial + ******************************************************************************/ +void RobusHAL_ComTransmit(uint8_t *data, uint16_t size) +{ + if (serial_fd < 0) + { + return; + } + + // Guard against recursion: Transmit_End() -> Transmit_Process() -> + // ComTransmit(). On MCU this doesn't happen because ComTransmit starts + // async DMA and returns. On Linux it's synchronous, so the chain would + // send all queued messages in a burst without yielding to the RX thread. + // When recursive, just return — the next TX will be triggered by the + // main loop calling Transmit_Process() after yielding to the RX thread. + if (tx_in_progress) + { + // Transmit_Process already set ctx.tx.status and ctx.rx.callback before + // calling us. Undo both so Transmit_End() won't remove this unsent job + // and incoming bytes aren't misinterpreted as collision echoes. + // The next timer-triggered Transmit_End→Transmit_Process will send it. + ctx.tx.status = TX_DISABLE; + ctx.rx.callback = Recep_GetHeader; + return; + } + tx_in_progress = true; + + // Enable RS485 transceiver TX direction + RobusHAL_SetTxState(true); + ROBUS_DBG("[HAL_TX] Sending %d bytes\n", size); + + uint16_t remaining = size; + uint8_t *ptr = data; + while (remaining > 0) + { + ssize_t written = write(serial_fd, ptr, remaining); + if (written > 0) + { + ptr += written; + remaining -= written; + } + else if (written < 0 && errno != EAGAIN && errno != EINTR) + { + break; + } + } + + tcdrain(serial_fd); + + // Switch RS485 transceiver back to RX mode + RobusHAL_SetTxState(false); + + // Consume echo bytes: on half-duplex RS485 we receive our own TX. + // Read exactly 'size' bytes (our echo) so the RX thread only sees responses. + { + uint8_t discard; + int remaining = size; + while (remaining > 0) + { + int n = read(serial_fd, &discard, 1); + if (n == 1) + { + remaining--; + } + else if (n < 0 && errno == EAGAIN) + { + struct pollfd pfd = {.fd = serial_fd, .events = POLLIN}; + poll(&pfd, 1, 5); + } + else + { + break; + } + } + } + ROBUS_DBG("[HAL_TX] Done, consumed %d echo bytes, TX_EN OFF\n", size); + + // On MCU, echo bytes flow through Recep_GetCollision() which advances + // the collision state machine. On Linux, echoes are consumed above at + // the HAL level, so the collision state machine never runs. We must + // complete the TX path that MCU collision detection normally handles: + // 1. Reset RX callback to prevent false collision on next message + // 2. Mark TX as successful so Transmit_End() removes the job + // 3. Call Transmit_End() to clean up (recursion guard above prevents chaining) + if (ctx.rx.callback == Recep_GetCollision) + { + ctx.rx.callback = Recep_GetHeader; + } + ctx.tx.status = TX_OK; + Transmit_End(); + + // Keep tx.lock true to prevent Transmit_Process from sending the next + // message within the same Phy_Dispatch loop. On MCU, DMA keeps tx.lock + // true until the TX complete ISR fires. Here we simulate that by holding + // the lock until ComTransmit returns, so the next TX only happens from + // the RX thread's timer or the next main loop iteration. + ctx.tx.lock = true; + tx_in_progress = false; + + // Arm timer to release tx.lock and trigger the next Transmit_Process + RobusHAL_ResetTimeout(DEFAULT_TIMEOUT); +} + +/****************************************************************************** + * @brief Get TX lock state (bus busy detection) + ******************************************************************************/ +uint8_t RobusHAL_GetTxLockState(void) +{ + return false; +} + +/****************************************************************************** + * @brief Reset/set the frame timeout timer + ******************************************************************************/ +void RobusHAL_ResetTimeout(uint16_t nbrbit) +{ + if (timer_fd < 0) + { + return; + } + if (nbrbit == 0) + { + struct itimerspec its = {0}; + timerfd_settime(timer_fd, 0, &its, NULL); + ROBUS_DBG("[HAL_TMR] DISARMED (fd=%d)\n", timer_fd); + return; + } + uint64_t timeout_ns = (uint64_t)nbrbit * timeout_ns_per_bit; + struct itimerspec its = { + .it_value.tv_sec = timeout_ns / 1000000000, + .it_value.tv_nsec = timeout_ns % 1000000000, + .it_interval = {0, 0}, + }; + int ret = timerfd_settime(timer_fd, 0, &its, NULL); + ROBUS_DBG("[HAL_TMR] Set timeout %llu ns (fd=%d, ret=%d)\n", + (unsigned long long)timeout_ns, timer_fd, ret); +} + +/****************************************************************************** + * @brief Set RX detection pin (no-op on Linux) + ******************************************************************************/ +void RobusHAL_SetRxDetecPin(uint8_t Enable) +{ +} + +/****************************************************************************** + * @brief Set PTP to default state (input, polled for edges) + ******************************************************************************/ +void RobusHAL_SetPTPDefaultState(uint8_t PTPNbr) +{ + if (PTPNbr >= NBR_PORT || gpio_chip == NULL) + { + return; + } + ROBUS_DBG("[HAL_PTP] SetDefault port %d\n", PTPNbr); + + if (ptp_reqs[PTPNbr] != NULL) + { + gpiod_line_request_release(ptp_reqs[PTPNbr]); + ptp_reqs[PTPNbr] = NULL; + } + + struct gpiod_line_settings *settings = gpiod_line_settings_new(); + gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT); + gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN); + + struct gpiod_line_config *line_cfg = gpiod_line_config_new(); + unsigned int offset = ptp_gpio_offsets[PTPNbr]; + gpiod_line_config_add_line_settings(line_cfg, &offset, 1, settings); + + struct gpiod_request_config *req_cfg = gpiod_request_config_new(); + gpiod_request_config_set_consumer(req_cfg, "robus_ptp"); + + ptp_reqs[PTPNbr] = gpiod_chip_request_lines(gpio_chip, req_cfg, line_cfg); + + gpiod_request_config_free(req_cfg); + gpiod_line_config_free(line_cfg); + gpiod_line_settings_free(settings); + + if (ptp_reqs[PTPNbr] != NULL) + { + enum gpiod_line_value val = gpiod_line_request_get_value(ptp_reqs[PTPNbr], ptp_gpio_offsets[PTPNbr]); + ptp_last_value[PTPNbr] = (val == GPIOD_LINE_VALUE_ACTIVE) ? 1 : 0; + ptp_edge_direction[PTPNbr] = 1; // Rising edge only (detect poke = LOW→HIGH) + ptp_edge_detected[PTPNbr] = true; + } +} + +/****************************************************************************** + * @brief Set PTP to reverse state (input, polled for edges) + ******************************************************************************/ +void RobusHAL_SetPTPReverseState(uint8_t PTPNbr) +{ + if (PTPNbr >= NBR_PORT || gpio_chip == NULL) + { + return; + } + + if (ptp_reqs[PTPNbr] != NULL) + { + gpiod_line_request_release(ptp_reqs[PTPNbr]); + ptp_reqs[PTPNbr] = NULL; + } + + struct gpiod_line_settings *settings = gpiod_line_settings_new(); + gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_INPUT); + gpiod_line_settings_set_bias(settings, GPIOD_LINE_BIAS_PULL_DOWN); + + struct gpiod_line_config *line_cfg = gpiod_line_config_new(); + unsigned int offset = ptp_gpio_offsets[PTPNbr]; + gpiod_line_config_add_line_settings(line_cfg, &offset, 1, settings); + + struct gpiod_request_config *req_cfg = gpiod_request_config_new(); + gpiod_request_config_set_consumer(req_cfg, "robus_ptp"); + + ptp_reqs[PTPNbr] = gpiod_chip_request_lines(gpio_chip, req_cfg, line_cfg); + + gpiod_request_config_free(req_cfg); + gpiod_line_config_free(line_cfg); + gpiod_line_settings_free(settings); + + if (ptp_reqs[PTPNbr] != NULL) + { + enum gpiod_line_value val = gpiod_line_request_get_value(ptp_reqs[PTPNbr], ptp_gpio_offsets[PTPNbr]); + ptp_last_value[PTPNbr] = (val == GPIOD_LINE_VALUE_ACTIVE) ? 1 : 0; + ptp_edge_direction[PTPNbr] = -1; // Falling edge only (detect release = HIGH→LOW) + ptp_edge_detected[PTPNbr] = true; + } +} + +/****************************************************************************** + * @brief Push PTP line (output HIGH) + ******************************************************************************/ +void RobusHAL_PushPTP(uint8_t PTPNbr) +{ + if (PTPNbr >= NBR_PORT || gpio_chip == NULL) + { + return; + } + ROBUS_DBG("[HAL_PTP] Push port %d\n", PTPNbr); + + if (ptp_reqs[PTPNbr] != NULL) + { + gpiod_line_request_release(ptp_reqs[PTPNbr]); + ptp_reqs[PTPNbr] = NULL; + } + + struct gpiod_line_settings *settings = gpiod_line_settings_new(); + gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_OUTPUT); + gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_ACTIVE); + + struct gpiod_line_config *line_cfg = gpiod_line_config_new(); + unsigned int offset = ptp_gpio_offsets[PTPNbr]; + gpiod_line_config_add_line_settings(line_cfg, &offset, 1, settings); + + struct gpiod_request_config *req_cfg = gpiod_request_config_new(); + gpiod_request_config_set_consumer(req_cfg, "robus_ptp"); + + ptp_reqs[PTPNbr] = gpiod_chip_request_lines(gpio_chip, req_cfg, line_cfg); + + gpiod_request_config_free(req_cfg); + gpiod_line_config_free(line_cfg); + gpiod_line_settings_free(settings); + + ptp_edge_detected[PTPNbr] = false; +} + +/****************************************************************************** + * @brief Get PTP line state + ******************************************************************************/ +uint8_t RobusHAL_GetPTPState(uint8_t PTPNbr) +{ + if (PTPNbr >= NBR_PORT || ptp_reqs[PTPNbr] == NULL) + { + return 0; + } + enum gpiod_line_value val = gpiod_line_request_get_value(ptp_reqs[PTPNbr], ptp_gpio_offsets[PTPNbr]); + return (val == GPIOD_LINE_VALUE_ACTIVE) ? 1 : 0; +} + +/****************************************************************************** + * @brief Compute CRC16 (software) + ******************************************************************************/ +void RobusHAL_ComputeCRC(uint8_t *data, uint8_t *crc) +{ + uint16_t dbyte = data[0]; + *(uint16_t *)crc ^= dbyte << 8; + for (uint8_t j = 0; j < 8; ++j) + { + uint16_t mix = *(uint16_t *)crc & 0x8000; + *(uint16_t *)crc = (*(uint16_t *)crc << 1); + if (mix) + *(uint16_t *)crc = *(uint16_t *)crc ^ 0x0007; + } +} + +/****************************************************************************** + * @brief GPIO Initialization + ******************************************************************************/ +static void RobusHAL_GPIOInit(void) +{ + gpio_chip = gpiod_chip_open(ROBUS_GPIO_CHIP); + if (gpio_chip == NULL) + { + perror("RobusHAL: Failed to open GPIO chip"); + return; + } + + // TX_EN: output, initially inactive + struct gpiod_line_settings *settings = gpiod_line_settings_new(); + gpiod_line_settings_set_direction(settings, GPIOD_LINE_DIRECTION_OUTPUT); + gpiod_line_settings_set_output_value(settings, GPIOD_LINE_VALUE_INACTIVE); + + struct gpiod_line_config *line_cfg = gpiod_line_config_new(); + unsigned int tx_offset = TX_EN_GPIO_LINE; + gpiod_line_config_add_line_settings(line_cfg, &tx_offset, 1, settings); + + struct gpiod_request_config *req_cfg = gpiod_request_config_new(); + gpiod_request_config_set_consumer(req_cfg, "robus_tx_en"); + + tx_en_req = gpiod_chip_request_lines(gpio_chip, req_cfg, line_cfg); + + gpiod_request_config_free(req_cfg); + gpiod_line_config_free(line_cfg); + gpiod_line_settings_free(settings); + + // PTP lines: start as input with pull-down (no events yet) + ptp_gpio_offsets[0] = PTPA_GPIO_LINE; +#if (NBR_PORT > 1) + ptp_gpio_offsets[1] = PTPB_GPIO_LINE; +#endif + + for (uint8_t i = 0; i < NBR_PORT; i++) + { + ptp_reqs[i] = NULL; + ptp_last_value[i] = 0; + ptp_edge_detected[i] = false; + ptp_edge_direction[i] = 1; // Rising edge by default + } +} + +/****************************************************************************** + * @brief CRC Initialization (nothing to do for software CRC) + ******************************************************************************/ +static void RobusHAL_CRCInit(void) +{ +} + +/****************************************************************************** + * @brief RX thread -- replaces UART, timer, and PTP ISRs + ******************************************************************************/ +static void *RobusHAL_RxThread(void *arg) +{ + (void)arg; + uint8_t byte; + + while (running) + { + ROBUS_DBG("[HAL_LOOP] top\n"); + struct pollfd fds[2]; + int nfds = 0; + + fds[nfds].fd = serial_fd; + fds[nfds].events = POLLIN; + fds[nfds].revents = 0; + int serial_idx = nfds++; + + fds[nfds].fd = timer_fd; + fds[nfds].events = POLLIN; + fds[nfds].revents = 0; + int timer_idx = nfds++; + + // Use short timeout to sample PTP GPIO values frequently + int ret = poll(fds, nfds, 1); + + ROBUS_DBG("[HAL_LOCK] acquiring mutex...\n"); + pthread_mutex_lock(&luos_recursive_mutex); + ROBUS_DBG("[HAL_LOCK] acquired\n"); + + if (ret > 0) + { + // --- Serial byte received (processed FIRST so pending messages + // like PORT_DATA are consumed before PTP triggers next step) --- + if (fds[serial_idx].revents & POLLIN) + { + int count = 0; + while (read(serial_fd, (uint8_t *)&byte, 1) == 1) + { + count++; + if (rx_enabled) + { + ROBUS_DBG("[HAL_RX] 0x%02X\n", byte); + RobusHAL_ResetTimeout(DEFAULT_TIMEOUT); + Recep_data(&byte); + } + } + ROBUS_DBG("[HAL_RX] read loop done, got %d bytes\n", count); + } + + // --- Timer expired --- + if (fds[timer_idx].revents & POLLIN) + { + uint64_t expirations; + read(timer_fd, &expirations, sizeof(expirations)); + ROBUS_DBG("[HAL_RX] Timeout\n"); + if (ctx.tx.lock == true && RobusHAL_GetTxLockState() == false) + { + RobusHAL_SetTxState(false); + RobusHAL_SetRxState(true); + } + Recep_Timeout(); + } + } + + // --- PTP: detect edges by reading GPIO value directly --- + // Processed LAST so serial data (PORT_DATA) is consumed before + // PTP falling edge triggers the next detection step. + for (uint8_t i = 0; i < NBR_PORT; i++) + { + if (!ptp_edge_detected[i] || ptp_reqs[i] == NULL) + { + continue; + } + enum gpiod_line_value val = gpiod_line_request_get_value(ptp_reqs[i], ptp_gpio_offsets[i]); + uint8_t current = (val == GPIOD_LINE_VALUE_ACTIVE) ? 1 : 0; + if (current != ptp_last_value[i]) + { + int8_t direction = (int8_t)current - (int8_t)ptp_last_value[i]; // +1=rising, -1=falling + ptp_last_value[i] = current; + // Only fire if direction matches what was requested + if (ptp_edge_direction[i] == 0 || ptp_edge_direction[i] == direction) + { + ROBUS_DBG("[HAL_PTP] Edge on port %d (val=%d)\n", i, current); + PortMng_PtpHandler(i); + } + } + } + + pthread_mutex_unlock(&luos_recursive_mutex); + ROBUS_DBG("[HAL_LOCK] released\n"); + } + return NULL; +} diff --git a/network/robus_network/HAL/LINUX/robus_hal.h b/network/robus_network/HAL/LINUX/robus_hal.h new file mode 100644 index 000000000..485ea5cdb --- /dev/null +++ b/network/robus_network/HAL/LINUX/robus_hal.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * @file robus_HAL + * @brief Robus Hardware Abstration Layer. Describe Low layer fonction + * @Family Linux (Raspberry Pi) + * @author Luos + * @version 0.0.0 + ******************************************************************************/ +#ifndef _RobusHAL_H_ +#define _RobusHAL_H_ + +#include +#include +#include "robus_hal_config.h" + +/******************************************************************************* + * Function + ******************************************************************************/ +void RobusHAL_Init(void); +void RobusHAL_Loop(void); +void RobusHAL_ComInit(uint32_t Baudrate); +void RobusHAL_SetTxState(uint8_t Enable); +void RobusHAL_SetRxState(uint8_t Enable); +void RobusHAL_ComTransmit(uint8_t *data, uint16_t size); +uint8_t RobusHAL_GetTxLockState(void); +void RobusHAL_ResetTimeout(uint16_t nbrbit); +void RobusHAL_SetRxDetecPin(uint8_t Enable); +void RobusHAL_SetPTPDefaultState(uint8_t PTPNbr); +void RobusHAL_SetPTPReverseState(uint8_t PTPNbr); +void RobusHAL_PushPTP(uint8_t PTPNbr); +uint8_t RobusHAL_GetPTPState(uint8_t PTPNbr); +void RobusHAL_ComputeCRC(uint8_t *data, uint8_t *crc); + +#endif /* _RobusHAL_H_ */ diff --git a/network/robus_network/HAL/LINUX/robus_hal_config.h b/network/robus_network/HAL/LINUX/robus_hal_config.h new file mode 100644 index 000000000..7bfffb58f --- /dev/null +++ b/network/robus_network/HAL/LINUX/robus_hal_config.h @@ -0,0 +1,88 @@ +/****************************************************************************** + * @file robusHAL_Config + * @brief This file allow you to configure RobusHAL according to your design + * this is the default configuration created by Luos team for this MCU Family + * Do not modify this file if you want to ovewrite change define in you project + * @Family Linux (Raspberry Pi) + * @author Luos + * @version 0.0.0 + ******************************************************************************/ +#ifndef _ROBUSHAL_CONFIG_H_ +#define _ROBUSHAL_CONFIG_H_ + +#include "luos_hal.h" + +// Critical section (no-op, handled by recursive mutex in luos_hal.c) +#define _CRITICAL + +// No hardware peripherals +#define DISABLE 0x00 + +// Enable NORT -- required for Linux HAL (non-real-time platform) +#define NORT + +// Number of network PHYs (Robus + Serial = 2, adjust if more networks are added) +#ifndef LOCAL_PHY_NB + #define LOCAL_PHY_NB 2 +#endif + +// Serial port device +#ifndef ROBUS_COM_DEVICE + #define ROBUS_COM_DEVICE "/dev/ttyAMA0" +#endif + +// GPIO chip device +#ifndef ROBUS_GPIO_CHIP + #define ROBUS_GPIO_CHIP "/dev/gpiochip0" +#endif + +// RS485 TX_EN GPIO (mandatory -- controls RS485 transceiver direction) +#ifndef TX_EN_GPIO_LINE + #define TX_EN_GPIO_LINE 4 +#endif + +// PTP GPIO configuration (mandatory -- required for topology detection) +#ifndef PTPA_GPIO_LINE + #define PTPA_GPIO_LINE 17 +#endif +#ifndef PTPB_GPIO_LINE + #define PTPB_GPIO_LINE 27 +#endif + +// PTP stub values for compatibility +#define PTPA_PIN PTPA_GPIO_LINE +#define PTPA_PORT NULL +#define PTPA_IRQ DISABLE + +#define PTPB_PIN PTPB_GPIO_LINE +#define PTPB_PORT NULL +#define PTPB_IRQ DISABLE + +// Timer +#ifndef TIMERDIV + #define TIMERDIV 1 +#endif + +// CRC in software +#ifndef USE_CRC_HW + #define USE_CRC_HW 0 +#endif + +// PTP timing overrides for Linux scheduler latency +#define PTP_PUSH_DELAY_MS 20 +#define PTP_READ_DELAY_MS 30 + +// IRQ handler stubs (not used on Linux -- RX thread replaces ISRs) +#define ROBUS_COM_IRQHANDLER void RobusHAL_ComIrqStub +#define ROBUS_TIMER_IRQHANDLER void RobusHAL_TimerIrqStub +#define PINOUT_IRQHANDLER RobusHAL_PinoutIrqStub + +// Conditional debug macro +#ifdef LUOS_DEBUG_PRINT + #include + #define ROBUS_DBG(fmt, ...) do { fprintf(stderr, fmt, ##__VA_ARGS__); } while(0) +#else + #define ROBUS_DBG(fmt, ...) ((void)0) +#endif + +#endif /* _ROBUSHAL_CONFIG_H_ */ From 5147a87eb242fc701a04dba1b142f5bae60bfa6a Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Wed, 22 Apr 2026 17:34:15 +0200 Subject: [PATCH 06/12] perf(robus_hal/linux): replace tcdrain with precise wire-time sleep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On BCM-family PL011 (RPi), tcdrain() blocks ~7.9 ms per call regardless of frame size. The driver polls FR.BUSY which is sticky on this hardware; TIOCOUTQ returns 0 immediately after write(), confirming the kernel TX buffer is drained well before tcdrain returns. Measured on a 10-byte frame at 1 Mbps (100 µs wire time): tcdrain=7578 µs p50, write=20 µs p50 — tcdrain accounted for 96% of the P1 RTT floor. Replace tcdrain() with clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME) for (size * 10 bits * ns_per_bit) + 50 µs margin. The per-bit timing uses the existing timeout_ns_per_bit, so higher baudrates work without further changes. Co-Authored-By: Claude Opus 4.7 (1M context) --- network/robus_network/HAL/LINUX/robus_hal.c | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/network/robus_network/HAL/LINUX/robus_hal.c b/network/robus_network/HAL/LINUX/robus_hal.c index cab920aa1..e48e77c25 100644 --- a/network/robus_network/HAL/LINUX/robus_hal.c +++ b/network/robus_network/HAL/LINUX/robus_hal.c @@ -22,6 +22,7 @@ #include #include #include +#include /******************************************************************************* * Definitions @@ -201,6 +202,8 @@ void RobusHAL_ComTransmit(uint8_t *data, uint16_t size) uint16_t remaining = size; uint8_t *ptr = data; + struct timespec tx_start; + clock_gettime(CLOCK_MONOTONIC, &tx_start); while (remaining > 0) { ssize_t written = write(serial_fd, ptr, remaining); @@ -215,7 +218,23 @@ void RobusHAL_ComTransmit(uint8_t *data, uint16_t size) } } - tcdrain(serial_fd); + // Wait until bytes have actually cleared the UART, then drop DE. + // tcdrain() is unusable on BCM-family PL011: it polls FR.BUSY which is + // sticky on this hardware and blocks ~7.9 ms per call regardless of + // frame size. TIOCOUTQ confirms the kernel TX buffer is drained well + // before tcdrain returns. Sleep for the computed wire time (10 bits + // per byte at the configured baudrate) plus a CPU-scheduling margin. + #define TX_WIRE_MARGIN_NS 50000ULL // 50 µs for CFS wakeup jitter + uint64_t wire_ns = (uint64_t)size * 10ULL * timeout_ns_per_bit + TX_WIRE_MARGIN_NS; + uint64_t deadline_ns = (uint64_t)tx_start.tv_sec * 1000000000ULL + + (uint64_t)tx_start.tv_nsec + wire_ns; + struct timespec deadline = { + .tv_sec = deadline_ns / 1000000000ULL, + .tv_nsec = deadline_ns % 1000000000ULL, + }; + while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, NULL) == EINTR) + { + } // Switch RS485 transceiver back to RX mode RobusHAL_SetTxState(false); From 8fba0f59d1b0557e43f3f6888d3f58a7d49301ff Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Wed, 22 Apr 2026 18:22:36 +0200 Subject: [PATCH 07/12] fix(robus_hal/linux): reset RX state between back-to-back frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After a valid frame's CRC, the Robus state machine sets ctx.rx.callback = Recep_Drop to ignore trailing garbage until Recep_Timeout fires DEFAULT_TIMEOUT bit-times later. This assumes an inter-frame silence at least as long as the timeout. On RS485 half-duplex without bus arbitration (the Linux HAL has GetTxLockState stubbed to false), two uncoordinated nodes can TX back-to-back with a gap far shorter than that. Observed in S1: Tartine's bg_pub and its CMD_ECHO_REPLY arrived 14 µs apart — all ten reply bytes were dropped by Recep_Drop, producing a 300 ms timeout on iter 528. When the RX thread reads a byte while the callback is Recep_Drop, immediately call Recep_Reset so the byte is treated as the start of a new frame. Fix is Linux-HAL-local; MCU behavior is untouched. Verified via event trace: echo bytes matched cleanly during the timeout (no TX collision), and reply bytes were fully received within 700 µs but not dispatched until the next iter's TX kicked the state machine. Co-Authored-By: Claude Opus 4.7 (1M context) --- network/robus_network/HAL/LINUX/robus_hal.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/network/robus_network/HAL/LINUX/robus_hal.c b/network/robus_network/HAL/LINUX/robus_hal.c index e48e77c25..4ab4ddbe8 100644 --- a/network/robus_network/HAL/LINUX/robus_hal.c +++ b/network/robus_network/HAL/LINUX/robus_hal.c @@ -578,6 +578,17 @@ static void *RobusHAL_RxThread(void *arg) count++; if (rx_enabled) { + // If the previous byte closed a valid frame, reset state + // so this byte starts a new frame. Robus normally relies + // on the DEFAULT_TIMEOUT inter-frame gap to trigger + // Recep_Timeout → Recep_Reset, but on RS485 without + // arbitration two nodes can TX back-to-back with a gap + // much shorter than the timeout, leaving the state in + // Recep_Drop and silently discarding the next frame. + if (ctx.rx.callback == Recep_Drop) + { + Recep_Reset(); + } ROBUS_DBG("[HAL_RX] 0x%02X\n", byte); RobusHAL_ResetTimeout(DEFAULT_TIMEOUT); Recep_data(&byte); From 48bb1aa678f0ebf76a61bcca4c8afb5e49329a54 Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Fri, 10 Apr 2026 19:00:51 +0200 Subject: [PATCH 08/12] feat: add conditional debug instrumentation for Robus protocol layers Co-Authored-By: Claude Opus 4.6 (1M context) --- engine/IO/src/luos_io.c | 27 ++++++++++++++++ network/robus_network/src/port_manager.c | 9 ++++++ network/robus_network/src/reception.c | 39 +++++++++++++++++------- network/robus_network/src/transmission.c | 20 +++++++----- 4 files changed, 77 insertions(+), 18 deletions(-) diff --git a/engine/IO/src/luos_io.c b/engine/IO/src/luos_io.c index 2a9701f45..6f596bde1 100644 --- a/engine/IO/src/luos_io.c +++ b/engine/IO/src/luos_io.c @@ -6,6 +6,9 @@ ******************************************************************************/ #include +#ifdef LUOS_DEBUG_PRINT + #include +#endif #include "luos_io.h" #include "msg_alloc.h" #include "service.h" @@ -395,6 +398,10 @@ error_return_t LuosIO_ConsumeMsg(const msg_t *input) //**************************************** detection section **************************************** // Only the master node should receive this message case CONNECTION_DATA: +#ifdef LUOS_DEBUG_PRINT + printf("[DETECT] Received CONNECTION_DATA: source=%d, size=%d\n", + input->header.source, input->header.size); +#endif LUOS_ASSERT(connection_table_ptr != NULL); if (input->header.size == sizeof(port_t)) { @@ -407,6 +414,10 @@ error_return_t LuosIO_ConsumeMsg(const msg_t *input) output_msg.header.target = 0; // We target the node_id 0 because the node receiving this message don't have a node_id yet. This node need to be the only one to receive it. output_msg.header.target_mode = NODEIDACK; memcpy(output_msg.data, (void *)&last_node, sizeof(uint16_t)); +#ifdef LUOS_DEBUG_PRINT + printf("[DETECT] Sending NODE_ID=%d (target_mode=%d), waiting for PORT_DATA...\n", + last_node, output_msg.header.target_mode); +#endif Luos_SendMsg(service, &output_msg); } else @@ -425,6 +436,14 @@ error_return_t LuosIO_ConsumeMsg(const msg_t *input) // Only the master node should receive this message case PORT_DATA: +#ifdef LUOS_DEBUG_PRINT + { + port_t *port_info = (port_t *)input->data; + printf("[DETECT] Received PORT_DATA: source=%d, size=%d, port node_id=%d, phy_id=%d\n", + input->header.source, input->header.size, + port_info->node_id, port_info->phy_id); + } +#endif LUOS_ASSERT(connection_table_ptr != NULL); // This is the last part (input port) of a connection_ data // Check that we receive a full port information @@ -486,6 +505,10 @@ error_return_t LuosIO_ConsumeMsg(const msg_t *input) break; case LOCAL_RTB: +#ifdef LUOS_DEBUG_PRINT + printf("[DETECT] Received LOCAL_RTB: source=%d, size=%d\n", + input->header.source, input->header.size); +#endif // Depending on the size of this message we have to make different operations // If size is 0 someone ask to get local_route table back // If size is 2 someone ask us to generate a local route table based on the given service ID then send local route table back. @@ -501,6 +524,10 @@ error_return_t LuosIO_ConsumeMsg(const msg_t *input) output_msg.header.cmd = RTB; output_msg.header.target_mode = LuosIO_GetDetectAckMode(); output_msg.header.target = input->header.source; +#ifdef LUOS_DEBUG_PRINT + printf("[DETECT] Sending RTB response: target=%d, target_mode=%d\n", + output_msg.header.target, output_msg.header.target_mode); +#endif LuosIO_TransmitLocalRoutingTable(0, &output_msg); break; default: diff --git a/network/robus_network/src/port_manager.c b/network/robus_network/src/port_manager.c index 6c28eea0a..efcbc54a6 100644 --- a/network/robus_network/src/port_manager.c +++ b/network/robus_network/src/port_manager.c @@ -40,6 +40,13 @@ #include "robus_hal.h" #include "luos_hal.h" +#ifdef LUOS_DEBUG_PRINT + #include + #define ROBUS_DBG(fmt, ...) printf(fmt, ##__VA_ARGS__) +#else + #define ROBUS_DBG(fmt, ...) ((void)0) +#endif + #ifndef PTP_PUSH_DELAY_MS #define PTP_PUSH_DELAY_MS 2 #endif @@ -91,6 +98,7 @@ _CRITICAL void PortMng_Init(void) ******************************************************************************/ _CRITICAL void PortMng_PtpHandler(uint8_t PortNbr) { + ROBUS_DBG("[PTP] Handler port=%d\n", PortNbr); if (Port_ExpectedState == RELEASE) { Port_ExpectedState = POKE; @@ -171,6 +179,7 @@ error_return_t PortMng_PokeNextPort(uint8_t *portId) if (!(port_detected & (1 << port))) { // This port have not been poked + ROBUS_DBG("[PTP] Poking port %d\n", port); if (PortMng_PokePort(port)) { // Poke succeed, we have a node here, return the port id diff --git a/network/robus_network/src/reception.c b/network/robus_network/src/reception.c index 473479e6a..1b8854a79 100644 --- a/network/robus_network/src/reception.c +++ b/network/robus_network/src/reception.c @@ -49,6 +49,13 @@ /******************************************************************************* * Definitions ******************************************************************************/ +#ifdef LUOS_DEBUG_PRINT + #include + #define ROBUS_DBG(fmt, ...) printf(fmt, ##__VA_ARGS__) +#else + #define ROBUS_DBG(fmt, ...) ((void)0) +#endif + #ifdef DEBUG #include #endif @@ -64,6 +71,8 @@ ******************************************************************************/ uint8_t data_rx[sizeof(msg_t)] = {0}; // Buffer to store the received data uint16_t crc_val = 0; // CRC value +static uint8_t collision_data_count = 0; +static uint16_t recep_crc = 0; /******************************************************************************* * Function @@ -132,6 +141,12 @@ _CRITICAL void Recep_GetHeader(luos_phy_t *phy_robus, volatile uint8_t *data) printf("cmd : 0x%04x\n", phy_robus->rx_msg->header.cmd); /*!< msg definition. */ printf("size : 0x%04x\n", phy_robus->rx_msg->header.size); /*!< Size of the data field. */ #endif + ROBUS_DBG("[RX] Header: target=%d, src=%d, cmd=%d, size=%d, mode=%d\n", + phy_robus->rx_msg->header.target, + phy_robus->rx_msg->header.source, + phy_robus->rx_msg->header.cmd, + phy_robus->rx_msg->header.size, + phy_robus->rx_msg->header.target_mode); // Switch state machine to data reception ctx.rx.callback = Recep_GetData; @@ -156,7 +171,6 @@ _CRITICAL void Recep_GetHeader(luos_phy_t *phy_robus, volatile uint8_t *data) ******************************************************************************/ _CRITICAL void Recep_GetData(luos_phy_t *phy_robus, volatile uint8_t *data) { - static uint16_t crc; if (phy_robus->rx_keep == false) { ctx.rx.callback = Recep_Drop; @@ -171,8 +185,9 @@ _CRITICAL void Recep_GetData(luos_phy_t *phy_robus, volatile uint8_t *data) } else if (phy_robus->received_data > phy_robus->rx_size) { - crc = crc | ((uint16_t)(*data) << 8); - if (crc == crc_val) + recep_crc = recep_crc | ((uint16_t)(*data) << 8); + ROBUS_DBG("[RX] Data complete, CRC %s\n", (recep_crc == crc_val) ? "PASS" : "FAIL"); + if (recep_crc == crc_val) { // Message is OK // Check if we need to send an ack @@ -202,7 +217,7 @@ _CRITICAL void Recep_GetData(luos_phy_t *phy_robus, volatile uint8_t *data) else { // This is the first byte of the CRC, store it - crc = (uint16_t)(*data); + recep_crc = (uint16_t)(*data); } phy_robus->received_data++; } @@ -214,34 +229,33 @@ _CRITICAL void Recep_GetData(luos_phy_t *phy_robus, volatile uint8_t *data) ******************************************************************************/ _CRITICAL void Recep_GetCollision(luos_phy_t *phy_robus, volatile uint8_t *data) { - static uint8_t data_count = 0; // Check data integrity - if ((ctx.tx.data[data_count++] != *data) || (!ctx.tx.lock) || (ctx.rx.status.rx_framing_error == true)) + if ((ctx.tx.data[collision_data_count++] != *data) || (!ctx.tx.lock) || (ctx.rx.status.rx_framing_error == true)) { // Data dont match, or we don't start to send the message, there is a collision ctx.tx.collision = true; // Stop TX trying to save input datas RobusHAL_SetTxState(false); // Save the received data into the allocator to be able to continue the reception - for (uint8_t i = 0; i < data_count - 1; i++) + for (uint8_t i = 0; i < collision_data_count - 1; i++) { Recep_GetHeader(phy_robus, (volatile uint8_t *)&ctx.tx.data[i]); } Recep_GetHeader(phy_robus, data); // Switch to get header. ctx.rx.callback = Recep_GetHeader; - ctx.tx.status = TX_NOK; - data_count = 0; + ctx.tx.status = TX_NOK; + collision_data_count = 0; } else { - if (data_count == COLLISION_DETECTION_NUMBER) + if (collision_data_count == COLLISION_DETECTION_NUMBER) { #ifdef SELFTEST selftest_SetRxFlag(); #endif // Collision detection end - data_count = 0; + collision_data_count = 0; RobusHAL_SetRxState(false); RobusHAL_ResetTimeout(0); if (ctx.tx.status == TX_NOK) @@ -275,6 +289,7 @@ _CRITICAL void Recep_Drop(luos_phy_t *phy_robus, volatile uint8_t *data) ******************************************************************************/ _CRITICAL void Recep_Timeout(void) { + ROBUS_DBG("[RX] Timeout\n"); if ((ctx.rx.callback != Recep_GetHeader) && (ctx.rx.callback != Recep_Drop)) { ctx.rx.status.rx_timeout = true; @@ -293,6 +308,8 @@ _CRITICAL void Recep_Reset(void) luos_phy_t *phy_robus = Robus_GetPhy(); Phy_ResetMsg(phy_robus); crc_val = 0xFFFF; + collision_data_count = 0; + recep_crc = 0; ctx.rx.status.rx_framing_error = false; ctx.rx.status.rx_error = false; ctx.rx.callback = Recep_GetHeader; diff --git a/network/robus_network/src/transmission.c b/network/robus_network/src/transmission.c index eae2bfa7e..7e79393b6 100644 --- a/network/robus_network/src/transmission.c +++ b/network/robus_network/src/transmission.c @@ -51,6 +51,12 @@ /******************************************************************************* * Definitions ******************************************************************************/ +#ifdef LUOS_DEBUG_PRINT + #include + #define ROBUS_DBG(fmt, ...) printf(fmt, ##__VA_ARGS__) +#else + #define ROBUS_DBG(fmt, ...) ((void)0) +#endif /******************************************************************************* * Variables @@ -165,20 +171,18 @@ _CRITICAL void Transmit_Process() // Lock the bus ctx.tx.lock = true; RobusHAL_SetRxDetecPin(false); - // Switch reception in collision detection mode + // Fill tx_data BEFORE switching the callback, while IRQs are disabled. + // On Linux, the RX thread could otherwise read stale tx_data. Phy_SetIrqState(false); - ctx.rx.callback = Recep_GetCollision; - Phy_SetIrqState(true); - ctx.tx.data = tx_data; - if (!nbrRetry) { - // This is the first time we try to send this message, we need to backup the original crc value and the job data to the TX_data buffer crc_val = jobEncaps->crc; memcpy(tx_data, job->data_pt, job->size); - // Add the end of the message in the end of the buffer memcpy(&tx_data[job->size], jobEncaps->unmaped, jobEncaps->size); } + ctx.tx.data = tx_data; + ctx.rx.callback = Recep_GetCollision; + Phy_SetIrqState(true); // Put timestamping on data here if (job->timestamp) @@ -201,6 +205,7 @@ _CRITICAL void Transmit_Process() // We will prepare to transmit something enable tx status with precomputed value of the initial_transmit_status ctx.tx.status = initial_transmit_status; // We still have something to send, no reset occured + ROBUS_DBG("[TX] Start, size=%d\n", (int)(job->size + jobEncaps->size)); RobusHAL_ComTransmit(tx_data, (job->size + jobEncaps->size)); Phy_SetIrqState(true); } @@ -235,6 +240,7 @@ _CRITICAL static uint8_t Transmit_GetLockStatus(void) ******************************************************************************/ _CRITICAL void Transmit_End(void) { + ROBUS_DBG("[TX] End: status=%d, collision=%d\n", ctx.tx.status, ctx.tx.collision); if (ctx.tx.status == TX_OK) { // A job have been sucessfully transmitted From cfebc511fef3c324e17eff6b558eddf6f2ffedc7 Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Thu, 23 Apr 2026 11:50:38 +0200 Subject: [PATCH 09/12] feat: add RPI Gate example project using Robus network Mirrors the native gate_wscom project structure but runs the Luos network over Robus (RS485 on RPI GPIO via the LINUX Robus HAL). The Gate exposes itself to external clients over a WebSocket pipe bound on all interfaces, so pyluos can connect from anywhere on the LAN. Two build envs: rpi_robus (default, real RS485 hardware) and rpi_ws (WebSocket broker for dev without hardware). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../projects/RPI/gate_wscom/node_config.h | 126 ++++++++++++++++++ .../projects/RPI/gate_wscom/platformio.ini | 67 ++++++++++ examples/projects/RPI/gate_wscom/src/main.c | 67 ++++++++++ examples/projects/RPI/setup_luos_rpi.sh | 63 +++++++++ 4 files changed, 323 insertions(+) create mode 100644 examples/projects/RPI/gate_wscom/node_config.h create mode 100644 examples/projects/RPI/gate_wscom/platformio.ini create mode 100644 examples/projects/RPI/gate_wscom/src/main.c create mode 100755 examples/projects/RPI/setup_luos_rpi.sh diff --git a/examples/projects/RPI/gate_wscom/node_config.h b/examples/projects/RPI/gate_wscom/node_config.h new file mode 100644 index 000000000..aad05c72e --- /dev/null +++ b/examples/projects/RPI/gate_wscom/node_config.h @@ -0,0 +1,126 @@ + + +/****************************************************************************** + * @file node_config.h + * @brief This file allow you to use standard preprocessor definitions to + * configure your project, Luos and Luos HAL libraries + * + * # Introduction + * This file is for the luos user. You may here configure your project and + * define your custom Luos service and custom Luos command for your product + * + * Luos libraries offer a minimal standard configuration to optimize + * memory usage. In some case you have to modify standard value to fit + * with your need concerning among of data transiting through the network + * or network speed for example + * + * Luos libraries can be use with a lot a MCU family. Luos compagny give you + * a default configuration, for specific MCU family, in robus_hal_config.h. + * This configuration can be modify here to fit with you design by + * preprocessor definitions of MCU Hardware needs + * + * # Usage + * This file should be place a the root folder of your project and include + * where build flag preprocessor definitions are define in your IDE + * -include node_config.h + * + * @author Luos + * @version 0.0.0 + ******************************************************************************/ +#ifndef _NODE_CONFIG_H_ +#define _NODE_CONFIG_H_ + +/******************************************************************************* + * PROJECT DEFINITION + *******************************************************************************/ + +/******************************************************************************* + * LUOS LIBRARY DEFINITION + ******************************************************************************* + * Define | Default Value | Description + * :---------------------|------------------------------------------------------ + * MAX_LOCAL_SERVICE_NUMBER | 5 | Service number in the node + * MAX_NODE_NUMBER | 20 | Node number in the device + * MAX_SERVICE_NUMBER | 20 | Service number in the device + * MSG_BUFFER_SIZE | 3*SIZE_MSG_MAX (405 Bytes) | Size in byte of the Luos buffer TX and RX + * MAX_MSG_NB | 2*MAX_LOCAL_SERVICE_NUMBER | Message number in Luos buffer + * NBR_PORT | 2 | PTP Branch number Max 8 + * NBR_RETRY | 10 | Send Retry number in case of NACK or collision + ******************************************************************************/ +#define MAX_LOCAL_SERVICE_NUMBER 2 +#define MAX_LOCAL_PROFILE_NUMBER 1 +#define MAX_MSG_NB 200 +#define MSG_BUFFER_SIZE 2048 + +/******************************************************************************* + * LUOS HAL LIBRARY DEFINITION +******************************************************************************* + * Define | Description + * :-----------------------|----------------------------------------------- + * MCUFREQ | Put your the MCU frequency (value in Hz) + * TIMERDIV | Timer divider clock (see your clock configuration) + * USE_CRC_HW | define to 0 if there is no Module CRC in your MCU + * USE_TX_IT | define to 1 to not use DMA transfers for Luos Tx + * + * PORT_CLOCK_ENABLE | Enable clock for port + * PTPx | A,B,C,D etc. PTP Branch Pin/Port/IRQ + * TX_LOCK_DETECT | Disable by default use if not busy flag in USART Pin/Port/IRQ + * RX_EN | Rx enable for driver RS485 always on Pin/Port + * TX_EN | Tx enable for driver RS485 Pin/Port + * COM_TX | Tx USART Com Pin/Port/Alternate + * COM_RX | Rx USART Com Pin/Port/Alternate + * PINOUT_IRQHANDLER | Callback function for Pin IRQ handler + + * ROBUS_COM_CLOCK_ENABLE | Enable clock for USART + * ROBUS_COM | USART number + * ROBUS_COM_IRQ | USART IRQ number + * ROBUS_COM_IRQHANDLER | Callback function for USART IRQ handler + + * ROBUS_DMA_CLOCK_ENABLE | Enable clock for DMA + * ROBUS_DMA | DMA number + * ROBUS_DMA_CHANNEL | DMA channel (depending on MCU DMA may need special config) + + * ROBUS_TIMER_CLOCK_ENABLE | Enable clock for Timer + * ROBUS_TIMER | Timer number + * ROBUS_TIMER_IRQ | Timer IRQ number + * ROBUS_TIMER_IRQHANDLER | Callback function for Timer IRQ handler +******************************************************************************/ + +/******************************************************************************* + * FLASH CONFIGURATION FOR APP WITH BOOTLOADER + ******************************************************************************** + * Define | Default Value | Description + * :---------------------|------------------------------------------------------ + * BOOT_START_ADDRESS | FLASH_BASE = 0x8000000 | Start address of Bootloader in flash + * SHARED_MEMORY_ADDRESS | 0x0800C000 | Start address of shared memory to save boot flag + * APP_START_ADDRESS | 0x0800C800 | Start address of application with bootloader + * APP_END_ADDRESS | FLASH_BANK1_END=0x0801FFFF | End address of application with bootloader + ******************************************************************************/ + +/******************************************************************************* + * GATE SERIAL COM DEFINITION + ******************************************************************************* + * Define | Default Value | Description + * :-------------------------|------------------------------------------------------ + * GATE_BUFF_SIZE | 1024 | Json receive buffer size + * PIPE_RX_BUFFER_SIZE | 1024 | Receive pipe buffer size + * PIPE_TX_BUFFER_SIZE | 2048 | Transmit pipe buffer size + * INIT_TIME | 150 | Wait init time before first detection + ******************************************************************************/ +#define GATE_BUFF_SIZE 65000 +#define PIPE_RX_BUFFER_SIZE 65000 +#define PIPE_TX_BUFFER_SIZE 65000 +#define INIT_TIME 150 +#define GATE_REFRESH_TIME_S 0.05f + +/******************************************************************************* + * ROBUS LINUX HAL - RPI GPIO MAPPING + ******************************************************************************/ +// RS485 direction enable +#define TX_EN_GPIO_LINE 13 + +// PTP topology - single port +#define NBR_PORT 1 +#define PTPA_GPIO_LINE 6 + +#endif /* _NODE_CONFIG_H_ */ diff --git a/examples/projects/RPI/gate_wscom/platformio.ini b/examples/projects/RPI/gate_wscom/platformio.ini new file mode 100644 index 000000000..3557de3ff --- /dev/null +++ b/examples/projects/RPI/gate_wscom/platformio.ini @@ -0,0 +1,67 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[platformio] +default_envs = rpi_robus + +[env:rpi_robus] +lib_ldf_mode =off +lib_extra_dirs = + $PROJECT_DIR/../../../../tool_services/ + $PROJECT_DIR/../../../../../ + $PROJECT_DIR/../../../../network/ +platform = native +lib_deps = + luos_engine@^3.1.0 + robus_network + Pipe + Gate +build_unflags = -Os +build_flags = + -I inc + -include node_config.h + -O1 + -lpthread + -lgpiod + -lm + -D LUOSHAL=LINUX + -D GATEFORMAT=TinyJSON + -D PIPEMODE=WS + -D PIPEHAL=native + -D PIPE_WS_SERVER_ADDR=\"ws://0.0.0.0:9342\" ; Watch out you need to escape the " using \ +build_type = debug + +[env:rpi_ws] +lib_ldf_mode =off +lib_extra_dirs = + $PROJECT_DIR/../../../../tool_services/ + $PROJECT_DIR/../../../../../ + $PROJECT_DIR/../../../../network/ +platform = native +lib_deps = + luos_engine@^3.1.0 + ws_network + Pipe + Gate +build_unflags = -Os +build_flags = + -DWS + -I inc + -include node_config.h + -O1 + -lpthread + -lm + -D LUOSHAL=NATIVE + -D WS_BROKER_ADDR=\"ws://127.0.0.1:8000/\" ; Watch out you need to escape the " using \ + -D WS_NETWORK_BROKER_ADDR=\"ws://127.0.0.1:8000/\" ; Watch out you need to escape the " using \ + -D GATEFORMAT=TinyJSON + -D PIPEMODE=WS + -D PIPEHAL=native + -D PIPE_WS_SERVER_ADDR=\"ws://0.0.0.0:9342\" ; Watch out you need to escape the " using \ +build_type = debug diff --git a/examples/projects/RPI/gate_wscom/src/main.c b/examples/projects/RPI/gate_wscom/src/main.c new file mode 100644 index 000000000..407f10f20 --- /dev/null +++ b/examples/projects/RPI/gate_wscom/src/main.c @@ -0,0 +1,67 @@ +#include "luos_engine.h" +#ifndef WS + #include "robus_network.h" +#else + #include "ws_network.h" +#endif +#include "pipe.h" +#include "gate.h" +#include + +#ifndef WIN32 + #include + #include + #include + #include + #include +#endif + +void *Gate_Pipe_LoopThread(void *vargp) +{ + while (1) + { + Pipe_Loop(); + Gate_Loop(); + } + return NULL; +} +#ifndef _WIN32 +void handler(int sig) +{ + void *array[10]; + size_t size; + + // get void*'s for all entries on the stack + size = backtrace(array, 10); + + // print out all the frames to stderr + fprintf(stderr, "Error: signal %d:\n", sig); + backtrace_symbols_fd(array, size, STDERR_FILENO); + exit(1); +} +#endif + +int main(void) +{ +#ifndef _WIN32 + signal(SIGSEGV, handler); // install our handler +#endif + Luos_Init(); +#ifndef WS + Robus_Init(); +#else + Ws_Init(); +#endif + Pipe_Init(); + Gate_Init(); + // Create a thread to convert messages into Json and stream them using Websocket + pthread_t thread_id; + pthread_create(&thread_id, NULL, Gate_Pipe_LoopThread, NULL); + while (1) + { + Luos_Loop(); +#ifdef WS + Ws_Loop(); +#endif + } +} diff --git a/examples/projects/RPI/setup_luos_rpi.sh b/examples/projects/RPI/setup_luos_rpi.sh new file mode 100755 index 000000000..e2040ac0f --- /dev/null +++ b/examples/projects/RPI/setup_luos_rpi.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# setup_luos_rpi.sh - Prepare a fresh Raspberry Pi OS for Luos/Robus development +# Run this script on the RPI itself (e.g., via SSH) +set -e + +echo "=== Luos RPI Setup ===" + +# Install build dependencies +echo "[1/5] Installing build dependencies..." +sudo apt-get update +sudo apt-get install -y build-essential libgpiod-dev python3-pip python3-venv + +# Install PlatformIO Core CLI +echo "[2/5] Installing PlatformIO..." +if ! command -v pio &> /dev/null; then + curl -fsSL -o /tmp/get-platformio.py https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py + python3 /tmp/get-platformio.py + export PATH="$HOME/.platformio/penv/bin:$PATH" + if ! grep -q '.platformio/penv/bin' ~/.bashrc; then + echo 'export PATH="$HOME/.platformio/penv/bin:$PATH"' >> ~/.bashrc + fi + echo "PlatformIO installed: $(pio --version)" +else + echo "PlatformIO already installed: $(pio --version)" +fi + +# Disable serial console +echo "[3/5] Disabling serial console..." +CMDLINE="/boot/firmware/cmdline.txt" +if grep -q 'console=serial0' "$CMDLINE"; then + sudo sed -i 's/console=serial0,[0-9]* //g' "$CMDLINE" + echo "Serial console disabled in $CMDLINE" + REBOOT_NEEDED=1 +else + echo "Serial console already disabled" +fi + +# Enable PL011 UART on GPIO14/15 (disable Bluetooth UART) +echo "[4/5] Configuring PL011 UART..." +CONFIG="/boot/firmware/config.txt" +if ! grep -q 'dtoverlay=disable-bt' "$CONFIG"; then + echo "" | sudo tee -a "$CONFIG" + echo "# Disable Bluetooth to free PL011 UART for Luos RS485" | sudo tee -a "$CONFIG" + echo "dtoverlay=disable-bt" | sudo tee -a "$CONFIG" + echo "PL011 UART enabled on GPIO14/15" + REBOOT_NEEDED=1 +else + echo "PL011 UART already configured" +fi + +# Disable bluetooth service +echo "[5/5] Disabling bluetooth service..." +sudo systemctl disable hciuart.service 2>/dev/null || true +sudo systemctl disable bluetooth.service 2>/dev/null || true + +echo "" +echo "=== Setup Complete ===" +if [ "${REBOOT_NEEDED:-0}" = "1" ]; then + echo "*** REBOOT REQUIRED for UART changes to take effect ***" + echo "Run: sudo reboot" +else + echo "No reboot needed. Ready to build." +fi From 1da4ab0e1ff81547ee357902b59ddd081b6ffd1c Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Thu, 23 Apr 2026 11:50:44 +0200 Subject: [PATCH 10/12] fix(robus_network): include robus_hal.h before port_manager.h port_manager.h conditionally declares PortMng_WatchdogCheck() when NORT is defined, but on the LINUX HAL NORT is defined inside robus_hal_config.h (pulled in via robus_hal.h). The previous include order parsed port_manager.h first, so the prototype was skipped and robus.c/port_manager.c fell back to an implicit declaration once NORT got defined later by robus_hal.h. Reorder the includes so the HAL header is parsed first and NORT is visible to port_manager.h. Co-Authored-By: Claude Opus 4.7 (1M context) --- network/robus_network/src/port_manager.c | 2 +- network/robus_network/src/robus.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/network/robus_network/src/port_manager.c b/network/robus_network/src/port_manager.c index efcbc54a6..577c0db7a 100644 --- a/network/robus_network/src/port_manager.c +++ b/network/robus_network/src/port_manager.c @@ -34,10 +34,10 @@ ***************************************************/ #include +#include "robus_hal.h" #include "port_manager.h" #include "transmission.h" #include "context.h" -#include "robus_hal.h" #include "luos_hal.h" #ifdef LUOS_DEBUG_PRINT diff --git a/network/robus_network/src/robus.c b/network/robus_network/src/robus.c index ba1f41f01..d93eae413 100644 --- a/network/robus_network/src/robus.c +++ b/network/robus_network/src/robus.c @@ -7,11 +7,11 @@ #include #include #include "robus_network.h" +#include "robus_hal.h" #include "transmission.h" #include "reception.h" #include "port_manager.h" #include "context.h" -#include "robus_hal.h" #include "robus_config.h" /******************************************************************************* * Definitions From a29a7d9dbd000f54e4f837e6dd0b198552729e85 Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Thu, 23 Apr 2026 12:20:51 +0200 Subject: [PATCH 11/12] style: apply clang-format to branch-modified files Align assignments, wrap multi-line macro, and fix whitespace to match the CI clang-format 1.5.0 expectations on files touched by this branch (engine/IO, robus_network/HAL/LINUX, robus_network/src). Co-Authored-By: Claude Opus 4.7 (1M context) --- engine/IO/src/luos_io.c | 6 +-- engine/IO/src/luos_phy.c | 12 +++--- network/robus_network/HAL/LINUX/robus_hal.c | 42 +++++++++---------- .../HAL/LINUX/robus_hal_config.h | 6 ++- network/robus_network/src/port_manager.c | 4 +- network/robus_network/src/reception.c | 10 ++--- 6 files changed, 42 insertions(+), 38 deletions(-) diff --git a/engine/IO/src/luos_io.c b/engine/IO/src/luos_io.c index 6f596bde1..46a217ddd 100644 --- a/engine/IO/src/luos_io.c +++ b/engine/IO/src/luos_io.c @@ -40,9 +40,9 @@ volatile uint16_t last_node = 0; connection_t *connection_table_ptr = NULL; luos_phy_t *luos_phy; service_filter_t service_filter[MAX_MSG_NB]; // Service filter table. Each of these filter will be linked with jobs. -uint8_t service_filter_index = 0; // Index of the next service filter to use. -service_t *detection_service = NULL; -bool Flag_DetectServices = false; +uint8_t service_filter_index = 0; // Index of the next service filter to use. +service_t *detection_service = NULL; +bool Flag_DetectServices = false; static uint8_t detect_ack_mode = NODEIDACK; /******************************************************************************* diff --git a/engine/IO/src/luos_phy.c b/engine/IO/src/luos_phy.c index 5359e8b6e..84affd446 100644 --- a/engine/IO/src/luos_phy.c +++ b/engine/IO/src/luos_phy.c @@ -78,12 +78,12 @@ typedef struct IRQ_STATE phy_irq_states[LOCAL_PHY_NB + 1]; // Store the irq state functions of phys aving one. // ******************** Topology management ******************** - port_t topology_source; // The source port. Where we receive the topological detection signal from. - uint32_t topology_done; // We put this bits to 1 when a phy ended the topology detection. - bool topology_running; // We put this bits to 1 when a phy is running the topology detection. + port_t topology_source; // The source port. Where we receive the topological detection signal from. + uint32_t topology_done; // We put this bits to 1 when a phy ended the topology detection. + bool topology_running; // We put this bits to 1 when a phy is running the topology detection. volatile bool find_next_node_job; // We put this bits to 1 to indicate that we will need to find another node. volatile bool resetAllNeed; // We put this bits to 1 to indicate that we will need to reset all the nodes. We need it to avoid to reset all phy at reset message reception, allowing the phy's to send their reset message. - bool PhyExeptSourceDone; // We put this bit to 1 when all the phys except the source one are done with their detection. + bool PhyExeptSourceDone; // We put this bit to 1 when all the phys except the source one are done with their detection. // ******************** Job management ******************** // io_jobs are stores from the newest to the oldest. @@ -959,8 +959,8 @@ static void Phy_Dispatch(void) // Phy[y] is concerned by this message. // Generate the job and put it in the phy queue phy_job_t phy_job; - phy_job.msg_pt = job->alloc_msg; - phy_job.size = job->size; + phy_job.msg_pt = job->alloc_msg; + phy_job.size = job->size; #ifdef NORT phy_job.ack = false; #else diff --git a/network/robus_network/HAL/LINUX/robus_hal.c b/network/robus_network/HAL/LINUX/robus_hal.c index 4ab4ddbe8..0de865261 100644 --- a/network/robus_network/HAL/LINUX/robus_hal.c +++ b/network/robus_network/HAL/LINUX/robus_hal.c @@ -32,17 +32,17 @@ /******************************************************************************* * Variables ******************************************************************************/ -static int serial_fd = -1; -static int timer_fd = -1; +static int serial_fd = -1; +static int timer_fd = -1; static pthread_t rx_thread; static volatile bool running = false; -static volatile bool rx_enabled = true; -static volatile bool tx_in_progress = false; -static uint64_t timeout_ns_per_bit = 0; +static volatile bool rx_enabled = true; +static volatile bool tx_in_progress = false; +static uint64_t timeout_ns_per_bit = 0; // GPIO via libgpiod v2 -static struct gpiod_chip *gpio_chip = NULL; -static struct gpiod_line_request *tx_en_req = NULL; +static struct gpiod_chip *gpio_chip = NULL; +static struct gpiod_line_request *tx_en_req = NULL; static struct gpiod_line_request *ptp_reqs[NBR_PORT]; static unsigned int ptp_gpio_offsets[NBR_PORT]; static volatile uint8_t ptp_last_value[NBR_PORT]; @@ -218,16 +218,16 @@ void RobusHAL_ComTransmit(uint8_t *data, uint16_t size) } } - // Wait until bytes have actually cleared the UART, then drop DE. - // tcdrain() is unusable on BCM-family PL011: it polls FR.BUSY which is - // sticky on this hardware and blocks ~7.9 ms per call regardless of - // frame size. TIOCOUTQ confirms the kernel TX buffer is drained well - // before tcdrain returns. Sleep for the computed wire time (10 bits - // per byte at the configured baudrate) plus a CPU-scheduling margin. - #define TX_WIRE_MARGIN_NS 50000ULL // 50 µs for CFS wakeup jitter - uint64_t wire_ns = (uint64_t)size * 10ULL * timeout_ns_per_bit + TX_WIRE_MARGIN_NS; +// Wait until bytes have actually cleared the UART, then drop DE. +// tcdrain() is unusable on BCM-family PL011: it polls FR.BUSY which is +// sticky on this hardware and blocks ~7.9 ms per call regardless of +// frame size. TIOCOUTQ confirms the kernel TX buffer is drained well +// before tcdrain returns. Sleep for the computed wire time (10 bits +// per byte at the configured baudrate) plus a CPU-scheduling margin. +#define TX_WIRE_MARGIN_NS 50000ULL // 50 µs for CFS wakeup jitter + uint64_t wire_ns = (uint64_t)size * 10ULL * timeout_ns_per_bit + TX_WIRE_MARGIN_NS; uint64_t deadline_ns = (uint64_t)tx_start.tv_sec * 1000000000ULL - + (uint64_t)tx_start.tv_nsec + wire_ns; + + (uint64_t)tx_start.tv_nsec + wire_ns; struct timespec deadline = { .tv_sec = deadline_ns / 1000000000ULL, .tv_nsec = deadline_ns % 1000000000ULL, @@ -314,8 +314,8 @@ void RobusHAL_ResetTimeout(uint16_t nbrbit) ROBUS_DBG("[HAL_TMR] DISARMED (fd=%d)\n", timer_fd); return; } - uint64_t timeout_ns = (uint64_t)nbrbit * timeout_ns_per_bit; - struct itimerspec its = { + uint64_t timeout_ns = (uint64_t)nbrbit * timeout_ns_per_bit; + struct itimerspec its = { .it_value.tv_sec = timeout_ns / 1000000000, .it_value.tv_nsec = timeout_ns % 1000000000, .it_interval = {0, 0}, @@ -476,7 +476,7 @@ void RobusHAL_ComputeCRC(uint8_t *data, uint8_t *crc) *(uint16_t *)crc ^= dbyte << 8; for (uint8_t j = 0; j < 8; ++j) { - uint16_t mix = *(uint16_t *)crc & 0x8000; + uint16_t mix = *(uint16_t *)crc & 0x8000; *(uint16_t *)crc = (*(uint16_t *)crc << 1); if (mix) *(uint16_t *)crc = *(uint16_t *)crc ^ 0x0007; @@ -521,7 +521,7 @@ static void RobusHAL_GPIOInit(void) for (uint8_t i = 0; i < NBR_PORT; i++) { - ptp_reqs[i] = NULL; + ptp_reqs[i] = NULL; ptp_last_value[i] = 0; ptp_edge_detected[i] = false; ptp_edge_direction[i] = 1; // Rising edge by default @@ -625,7 +625,7 @@ static void *RobusHAL_RxThread(void *arg) uint8_t current = (val == GPIOD_LINE_VALUE_ACTIVE) ? 1 : 0; if (current != ptp_last_value[i]) { - int8_t direction = (int8_t)current - (int8_t)ptp_last_value[i]; // +1=rising, -1=falling + int8_t direction = (int8_t)current - (int8_t)ptp_last_value[i]; // +1=rising, -1=falling ptp_last_value[i] = current; // Only fire if direction matches what was requested if (ptp_edge_direction[i] == 0 || ptp_edge_direction[i] == direction) diff --git a/network/robus_network/HAL/LINUX/robus_hal_config.h b/network/robus_network/HAL/LINUX/robus_hal_config.h index 7bfffb58f..abd699725 100644 --- a/network/robus_network/HAL/LINUX/robus_hal_config.h +++ b/network/robus_network/HAL/LINUX/robus_hal_config.h @@ -80,7 +80,11 @@ // Conditional debug macro #ifdef LUOS_DEBUG_PRINT #include - #define ROBUS_DBG(fmt, ...) do { fprintf(stderr, fmt, ##__VA_ARGS__); } while(0) + #define ROBUS_DBG(fmt, ...) \ + do \ + { \ + fprintf(stderr, fmt, ##__VA_ARGS__); \ + } while (0) #else #define ROBUS_DBG(fmt, ...) ((void)0) #endif diff --git a/network/robus_network/src/port_manager.c b/network/robus_network/src/port_manager.c index 577c0db7a..4e2ffa7f9 100644 --- a/network/robus_network/src/port_manager.c +++ b/network/robus_network/src/port_manager.c @@ -72,7 +72,7 @@ typedef enum * Variables ******************************************************************************/ volatile PortState_t Port_ExpectedState = POKE; -uint32_t port_detected = 0; +uint32_t port_detected = 0; #ifdef NORT static uint32_t ptp_release_enter_tick = 0; #endif @@ -153,7 +153,7 @@ uint8_t PortMng_PokePort(uint8_t PortNbr) { // Someone reply, reverse the detection to wake up on release condition RobusHAL_SetPTPReverseState(PortNbr); - Port_ExpectedState = RELEASE; + Port_ExpectedState = RELEASE; #ifdef NORT ptp_release_enter_tick = LuosHAL_GetSystick(); #endif diff --git a/network/robus_network/src/reception.c b/network/robus_network/src/reception.c index 1b8854a79..e3bc73ad0 100644 --- a/network/robus_network/src/reception.c +++ b/network/robus_network/src/reception.c @@ -69,8 +69,8 @@ /******************************************************************************* * Variables ******************************************************************************/ -uint8_t data_rx[sizeof(msg_t)] = {0}; // Buffer to store the received data -uint16_t crc_val = 0; // CRC value +uint8_t data_rx[sizeof(msg_t)] = {0}; // Buffer to store the received data +uint16_t crc_val = 0; // CRC value static uint8_t collision_data_count = 0; static uint16_t recep_crc = 0; @@ -243,9 +243,9 @@ _CRITICAL void Recep_GetCollision(luos_phy_t *phy_robus, volatile uint8_t *data) } Recep_GetHeader(phy_robus, data); // Switch to get header. - ctx.rx.callback = Recep_GetHeader; - ctx.tx.status = TX_NOK; - collision_data_count = 0; + ctx.rx.callback = Recep_GetHeader; + ctx.tx.status = TX_NOK; + collision_data_count = 0; } else { From 5a12116dabdee323b5f8f13c200afdb1ab2558ce Mon Sep 17 00:00:00 2001 From: Nicolas Rabault Date: Fri, 24 Apr 2026 15:53:15 +0200 Subject: [PATCH 12/12] feat(ws_network): add Ws_SetBroker for runtime broker override Adds a writable 128-byte buffer behind the existing s_url pointer and exposes ws_hal_set_broker / Ws_SetBroker to override the compile-time WS_NETWORK_BROKER_ADDR default. Must be called before Ws_Init; no effect afterwards (Ws_Init captures the URL during mg_ws_connect). Additive change: existing callers that rely on the compile-time default are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) --- network/ws_network/HAL/NATIVE/ws_hal.c | 9 ++++++++- network/ws_network/inc/ws_network.h | 1 + network/ws_network/src/ws_network.c | 13 +++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/network/ws_network/HAL/NATIVE/ws_hal.c b/network/ws_network/HAL/NATIVE/ws_hal.c index 36bf2525c..825346158 100644 --- a/network/ws_network/HAL/NATIVE/ws_hal.c +++ b/network/ws_network/HAL/NATIVE/ws_hal.c @@ -33,7 +33,14 @@ volatile uint8_t *tx_data = 0; // Mongoose connection information struct mg_mgr ws_mgr; // Event manager struct mg_connection *c; // Client connection -static const char *s_url = WS_NETWORK_BROKER_ADDR; +static char s_url_buf[128] = WS_NETWORK_BROKER_ADDR; +static const char *s_url = s_url_buf; + +void ws_hal_set_broker(const char *url) +{ + strncpy(s_url_buf, url, sizeof(s_url_buf) - 1); + s_url_buf[sizeof(s_url_buf) - 1] = '\0'; +} volatile bool ws_connected = false; /******************************************************************************* * Function diff --git a/network/ws_network/inc/ws_network.h b/network/ws_network/inc/ws_network.h index a78042f26..2818d628c 100644 --- a/network/ws_network/inc/ws_network.h +++ b/network/ws_network/inc/ws_network.h @@ -20,6 +20,7 @@ extern "C" /******************************************************************************* * Function ******************************************************************************/ + void Ws_SetBroker(const char *url); void Ws_Init(void); void Ws_Loop(void); diff --git a/network/ws_network/src/ws_network.c b/network/ws_network/src/ws_network.c index 06df318e9..af4aee6ad 100644 --- a/network/ws_network/src/ws_network.c +++ b/network/ws_network/src/ws_network.c @@ -77,6 +77,9 @@ static void Ws_JobHandler(luos_phy_t *phy_ptr, phy_job_t *job); static error_return_t Ws_RunTopology(luos_phy_t *phy_ptr, uint8_t *portId); static void Ws_Reset(luos_phy_t *phy_ptr); +// Implemented in HAL/NATIVE/ws_hal.c (and HAL equivalents). +extern void ws_hal_set_broker(const char *url); + /******************************************************************************* * Variables ******************************************************************************/ @@ -87,6 +90,16 @@ volatile wait_ack_t ping_status = INACTIVE; // This flag indicate the status of * Function ******************************************************************************/ +/****************************************************************************** + * @brief Override the broker URL set at compile time. Must be called before Ws_Init. + * @param url NUL-terminated ws://host:port string, max 127 chars (bytes beyond are truncated). + * @return None + ******************************************************************************/ +void Ws_SetBroker(const char *url) +{ + ws_hal_set_broker(url); +} + /****************************************************************************** * @brief Initialisation of the WebSocket communication * @param None