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_ */ 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..46a217ddd 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" @@ -24,6 +27,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); @@ -36,9 +40,10 @@ 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; /******************************************************************************* * Functions @@ -59,6 +64,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; } /****************************************************************************** @@ -382,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)) { @@ -394,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 @@ -412,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 @@ -437,6 +469,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 +491,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); @@ -470,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. @@ -483,8 +522,12 @@ 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; +#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: @@ -666,9 +709,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 8adb64a77..84affd446 100644 --- a/engine/IO/src/luos_phy.c +++ b/engine/IO/src/luos_phy.c @@ -53,10 +53,14 @@ #include "luos_io.h" #include "service.h" #include "filter.h" +#include "node.h" /******************************************************************************* * Definitions ******************************************************************************/ +#ifndef DETECTION_TIMEOUT_MS + #define DETECTION_TIMEOUT_MS 10000 +#endif typedef struct __attribute__((__packed__)) { @@ -74,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. - 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. - bool PhyExeptSourceDone; // We put this bit to 1 when all the phys except the source one are done with their 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. // ******************** Job management ******************** // io_jobs are stores from the newest to the oldest. @@ -192,8 +196,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 +216,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 +232,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)); @@ -326,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); @@ -358,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); @@ -504,7 +545,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 +587,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 +636,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 +650,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 +918,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) @@ -895,9 +959,13 @@ 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.ack = ((job->alloc_msg->header.target_mode == NODEIDACK) || (job->alloc_msg->header.target_mode == SERVICEIDACK)); + phy_job.msg_pt = job->alloc_msg; + phy_job.size = job->size; +#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 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 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..0de865261 --- /dev/null +++ b/network/robus_network/HAL/LINUX/robus_hal.c @@ -0,0 +1,643 @@ +/****************************************************************************** + * @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 +#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; + struct timespec tx_start; + clock_gettime(CLOCK_MONOTONIC, &tx_start); + 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; + } + } + +// 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); + + // 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) + { + // 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); + } + } + 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..abd699725 --- /dev/null +++ b/network/robus_network/HAL/LINUX/robus_hal_config.h @@ -0,0 +1,92 @@ +/****************************************************************************** + * @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_ */ 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..4e2ffa7f9 100644 --- a/network/robus_network/src/port_manager.c +++ b/network/robus_network/src/port_manager.c @@ -34,12 +34,31 @@ ***************************************************/ #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 + #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 +#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 +71,11 @@ typedef enum /******************************************************************************* * Variables ******************************************************************************/ -PortState_t Port_ExpectedState = POKE; -uint32_t port_detected = 0; +volatile PortState_t Port_ExpectedState = POKE; +uint32_t port_detected = 0; +#ifdef NORT +static uint32_t ptp_release_enter_tick = 0; +#endif /******************************************************************************* * Function ******************************************************************************/ @@ -76,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; @@ -119,11 +142,11 @@ 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)) @@ -131,6 +154,9 @@ uint8_t PortMng_PokePort(uint8_t PortNbr) // Someone reply, reverse the detection to wake up on release condition RobusHAL_SetPTPReverseState(PortNbr); Port_ExpectedState = RELEASE; +#ifdef NORT + ptp_release_enter_tick = LuosHAL_GetSystick(); +#endif // Port poked by node ctx.port.activ = PortNbr; ctx.port.keepLine = true; @@ -153,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 @@ -169,6 +196,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/reception.c b/network/robus_network/src/reception.c index 473479e6a..e3bc73ad0 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 @@ -62,8 +69,10 @@ /******************************************************************************* * 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; /******************************************************************************* * 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.rx.callback = Recep_GetHeader; + 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/robus.c b/network/robus_network/src/robus.c index a95cd1f5b..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 @@ -67,6 +67,9 @@ void Robus_Init(void) ******************************************************************************/ void Robus_Loop(void) { +#ifdef NORT + PortMng_WatchdogCheck(); +#endif RobusHAL_Loop(); } 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 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