diff --git a/CMakeLists.txt b/CMakeLists.txt index ca21ecb..a08e5d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ add_library( ${CMAKE_CURRENT_SOURCE_DIR}/src/drivers/CANvenient_PEAK.c ${CMAKE_CURRENT_SOURCE_DIR}/src/drivers/CANvenient_SocketCAN.c ${CMAKE_CURRENT_SOURCE_DIR}/src/drivers/CANvenient_Softing.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/drivers/CANvenient_TinyCan.c ) set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) diff --git a/CMakeSettings.json b/CMakeSettings.json index 42c83b5..50415c8 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -28,30 +28,6 @@ "buildCommandArgs": "", "ctestCommandArgs": "", "inheritEnvironments": [ "msvc_x64_x64" ] - }, - { - "name": "x86-Debug", - "generator": "Ninja", - "configurationType": "Debug", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "", - "ctestCommandArgs": "", - "inheritEnvironments": [ "msvc_x86" ], - "variables": [] - }, - { - "name": "x86-Release", - "generator": "Ninja", - "configurationType": "RelWithDebInfo", - "buildRoot": "${projectDir}\\out\\build\\${name}", - "installRoot": "${projectDir}\\out\\install\\${name}", - "cmakeCommandArgs": "", - "buildCommandArgs": "", - "ctestCommandArgs": "", - "inheritEnvironments": [ "msvc_x86" ], - "variables": [] } ] } \ No newline at end of file diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 0bc1201..6885f78 --- a/README.md +++ b/README.md @@ -21,3 +21,4 @@ The following back-ends are currently implemented: - PCAN-Basic - SocketCAN - Softing CAN Layer 2 +- MHS Elektronik Tiny-CAN diff --git a/cmake/dep_TinyCan.cmake b/cmake/dep_TinyCan.cmake new file mode 100644 index 0000000..d25f3d4 --- /dev/null +++ b/cmake/dep_TinyCan.cmake @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.16) +project(TinyCan_devel C) + +add_library( + mhstcan + SHARED + ${CMAKE_CURRENT_SOURCE_DIR}/dev/lib/mhs_can_drv.c) + +set_target_properties(mhstcan PROPERTIES + WINDOWS_EXPORT_ALL_SYMBOLS ON + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") \ No newline at end of file diff --git a/cmake/deps.cmake b/cmake/deps.cmake index ebe959e..063b07e 100644 --- a/cmake/deps.cmake +++ b/cmake/deps.cmake @@ -152,11 +152,43 @@ if(WIN32) set(SOFTING_INCLUDE_DIR "${SOFTING_PATH}/CAN/CAN Layer2/APIDLL") set(SOFTING_LIBRARY "${SOFTING_PATH}/CAN/CAN Layer2/APIDLL/${SOFTING_PLATFORM}/canL2${SOFTING_POSTFIX}.lib") + # Tiny-Can + set(TINYCAN_VERSION "8.12") + + set(TINYCAN_PATH "${CMAKE_CURRENT_SOURCE_DIR}/deps/TinyCan") + set(TINYCAN_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/TinyCan_devel-prefix/src/TinyCan_devel-build") + + set(TINYCAN_DEVEL_PKG TinyCan_v${TINYCAN_VERSION}.zip) + set(TINYCAN_DEVEL_URL https://canopenterm.de/mirror) + set(TINYCAN_DEVEL_HASH c047e901ccb34bc764fb5dc5f2359dad82ff55c4) + + ExternalProject_Add(TinyCan_devel + URL ${TINYCAN_DEVEL_URL}/${TINYCAN_DEVEL_PKG} + URL_HASH SHA1=${TINYCAN_DEVEL_HASH} + DOWNLOAD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/deps + DOWNLOAD_NO_PROGRESS true + DOWNLOAD_EXTRACT_TIMESTAMP true + TLS_VERIFY true + SOURCE_DIR ${TINYCAN_PATH}/ + BUILD_BYPRODUCTS ${TINYCAN_BUILD_DIR}/mhstcan.lib + + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy + ${TINYCAN_BUILD_DIR}/mhstcan.dll + ${CMAKE_CURRENT_BINARY_DIR}/ + + PATCH_COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/dep_TinyCan.cmake" ${TINYCAN_PATH}/CMakeLists.txt + ) + + set(TINYCAN_INCLUDE_DIR "${TINYCAN_PATH}/dev/lib") + set(TINYCAN_LIBRARY ${TINYCAN_BUILD_DIR}/mhstcan.lib) + set(PLATFORM_DEPS Ixxat_devel Kvaser_devel PCAN_devel Softing_devel + TinyCan_devel ) set(PLATFORM_LIBS @@ -164,6 +196,7 @@ if(WIN32) ${KVASER_LIBRARY} ${PCAN_LIBRARY} ${SOFTING_LIBRARY} + ${TINYCAN_LIBRARY} ) include_directories( @@ -174,6 +207,7 @@ if(WIN32) SYSTEM ${PCAN_INCLUDE_DIR}/../src/pcan/driver SYSTEM ${PCAN_INCLUDE_DIR}/../src/pcan/lib SYSTEM ${SOFTING_INCLUDE_DIR} + SYSTEM ${TINYCAN_INCLUDE_DIR} ) add_dependencies( @@ -182,5 +216,6 @@ if(WIN32) Kvaser_devel PCAN_devel Softing_devel + TinyCan_devel ) endif() diff --git a/src/CANvenient.c b/src/CANvenient.c index 564853e..74b7b7b 100644 --- a/src/CANvenient.c +++ b/src/CANvenient.c @@ -18,6 +18,7 @@ #include "drivers/CANvenient_SocketCAN.h" #include "drivers/CANvenient_Softing.h" #include "drivers/CANvenient_PEAK.h" +#include "drivers/CANvenient_TinyCan.h" struct can_iface can_interface[CAN_MAX_INTERFACES] = {0}; char can_error_reason[1024] = {0}; @@ -50,6 +51,12 @@ CANVENIENT_API int can_find_interfaces(void) return status; } + status = tinycan_find_interfaces(); + if (status < 0) + { + return status; + } + return softing_find_interfaces(); } @@ -83,6 +90,8 @@ CANVENIENT_API int can_open(int index, enum can_baudrate baud) return socketcan_open(index); case CAN_VENDOR_SOFTING: return softing_open(index); + case CAN_VENDOR_MHS: + return tinycan_open(index); default: case CAN_VENDOR_NONE: set_error_reason("No CAN interface found at specified index."); @@ -116,6 +125,9 @@ CANVENIENT_API void can_close(int index) case CAN_VENDOR_SOFTING: softing_close(index); break; + case CAN_VENDOR_MHS: + tinycan_close(index); + break; default: case CAN_VENDOR_NONE: break; @@ -167,6 +179,8 @@ CANVENIENT_API int can_update(int index) break; case CAN_VENDOR_SOFTING: return softing_update(index); + case CAN_VENDOR_MHS: + return tinycan_update(index); default: case CAN_VENDOR_NONE: set_error_reason("No CAN interface found at specified index."); @@ -181,7 +195,7 @@ CANVENIENT_API int can_get_baudrate(int index, enum can_baudrate* baud) set_error_reason("Channel index is out-of-range."); return -1; } - else if (!baud) + else if (! baud) { set_error_reason("Output parameter is NULL."); return -1; @@ -256,6 +270,8 @@ CANVENIENT_API int can_set_baudrate(int index, enum can_baudrate baud) return socketcan_set_baudrate(index, baud); case CAN_VENDOR_SOFTING: return softing_set_baudrate(index, baud); + case CAN_VENDOR_MHS: + return tinycan_set_baudrate(index, baud); default: case CAN_VENDOR_NONE: set_error_reason("No CAN interface found at specified index."); @@ -283,6 +299,8 @@ CANVENIENT_API int can_send(int index, struct can_frame* frame) return socketcan_send(index, frame); case CAN_VENDOR_SOFTING: return softing_send(index, frame); + case CAN_VENDOR_MHS: + return tinycan_send(index, frame); default: case CAN_VENDOR_NONE: set_error_reason("No CAN interface found at specified index."); @@ -310,6 +328,8 @@ CANVENIENT_API int can_recv(int index, struct can_frame* frame, u64* timestamp) return socketcan_recv(index, frame, timestamp); case CAN_VENDOR_SOFTING: return softing_recv(index, frame, timestamp); + case CAN_VENDOR_MHS: + return tinycan_recv(index, frame, timestamp); default: case CAN_VENDOR_NONE: set_error_reason("No CAN interface found at specified index."); diff --git a/src/drivers/CANvenient_PEAK.c b/src/drivers/CANvenient_PEAK.c index a851764..094ea93 100644 --- a/src/drivers/CANvenient_PEAK.c +++ b/src/drivers/CANvenient_PEAK.c @@ -12,6 +12,9 @@ #include #include +#include "CANvenient.h" +#include "CANvenient_internal.h" + #ifdef _WIN32 #include #include @@ -21,9 +24,6 @@ static char* lookup_error_string(TPCANStatus pcan_status); static TPCANBaudrate lookup_pcan_baudrate(enum can_baudrate baud); #endif -#include "CANvenient.h" -#include "CANvenient_internal.h" - int peak_find_interfaces(void) { #ifdef _WIN32 diff --git a/src/drivers/CANvenient_TinyCan.c b/src/drivers/CANvenient_TinyCan.c new file mode 100755 index 0000000..80bccf7 --- /dev/null +++ b/src/drivers/CANvenient_TinyCan.c @@ -0,0 +1,466 @@ +/** @file CANvenient_TinyCan.c + * + * CANvenient is an abstraction layer for multiple CAN APIs on + * Windows and Linux. + * + * Copyright (c) 2026, MHS-Elektronik GmbH & Co. KG, Klaus Demlehner. + * Copyright (c) 2026, Michael Fitzmayer. + * All rights reserved. + * + * SPDX-License-Identifier: MIT + * + **/ + +#ifdef _WIN32 +#include +#include + +#include +#include +#include +#endif + +#include "CANvenient.h" +#include "CANvenient_internal.h" + +#ifdef _WIN32 +typedef struct _TTinyDevice TTinyDevice; + +struct _TTinyDevice +{ + uint32_t DeviceIndex; + char Snr[20]; +}; + +// TimeStampMode +// 0 = Disabled +// 1 = Software Time Stamps +// 2 = Hardware Time Stamps, UNIX-Format +// 3 = Hardware Time Stamps +// 4 = Hardware Time Stamp wenn verfügbar, ansonsten Software Time Stamps +#ifdef ENABLE_LOG +#pragma message("!!!!!! WARNING: CAN-API driver log is enabled !!!!!!") + +#define TREIBER_INIT_PARAM "logfile=tinycan.log;logflags=0x880000F3;FdMode=0;CanCallThread=0;TimeStampMode=4" +#else +#define TREIBER_INIT_PARAM "FdMode=0;CanCallThread=0;TimeStampMode=4" +#endif + +#define DEVICE_OPEN_PARAM "AutoStopCan=1" + +static uint32_t TinyCanDriverInit = 0; + +struct TCanVenientToTCanBaudrate +{ + enum can_baudrate CanVenientBaud; + uint16_t TCanBaudrate; +}; + +static const struct TCanVenientToTCanBaudrate CanVenientToTCanBaudrate[] = { + {CAN_BAUD_1M, CAN_1M_BIT}, + {CAN_BAUD_800K, CAN_800K_BIT}, + {CAN_BAUD_500K, CAN_500K_BIT}, + {CAN_BAUD_250K, CAN_250K_BIT}, + {CAN_BAUD_125K, CAN_125K_BIT}, + {CAN_BAUD_100K, CAN_100K_BIT}, + {CAN_BAUD_95K, 0xFFFF}, + {CAN_BAUD_83K, 0xFFFF}, + {CAN_BAUD_50K, CAN_50K_BIT}, + {CAN_BAUD_47K, 0xFFFF}, + {CAN_BAUD_33K, 0xFFFF}, + {CAN_BAUD_20K, CAN_20K_BIT}, + {CAN_BAUD_10K, CAN_10K_BIT}, + {CAN_BAUD_5K, 0xFFFF}, + {0, 0}}; + +#define CAN_ERROR_TCAN -1 +#define CAN_ERROR_QXMTFULL -2 +#define CAN_ERROR_QRCVEMPTY -3 + +#define CAN_ERROR_QOVERRUN -4 +#define CAN_ERROR_BUSPASSIVE -5 +#define CAN_ERROR_BUSOFF -6 + +#define CAN_ERROR_NODRIVER -5 +#define CAN_ERROR_REGTEST -6 +#define CAN_ERROR_RESOURCE -7 + +struct TCanVenientMhsError +{ + int32_t CanError; + const char* ErrorMsg; +}; + +static const struct TCanVenientMhsError CanVenientMhsError[] = { + {CAN_ERROR_TCAN, "Tiny-CAN API error."}, + {CAN_ERROR_QXMTFULL, "Transmit queue is full."}, + {CAN_ERROR_QRCVEMPTY, "Receive queue is empty."}, + + {CAN_ERROR_QOVERRUN, "Receive queue was read too late."}, + {CAN_ERROR_BUSPASSIVE, "Bus error: the CAN controller is error passive."}, + {CAN_ERROR_BUSOFF, "Bus error: the CAN controller is in bus-off state."}, + + {CAN_ERROR_NODRIVER, "Driver not loaded."}, + + {CAN_ERROR_REGTEST, "Test of the CAN controller hardware registers failed (no hardware found)."}, + + {CAN_ERROR_RESOURCE, "Resource (FIFO, Client, timeout) cannot be created."}, + {0, NULL}}; + +static const char* lookup_error_string(int32_t can_error, int32_t tcan_error) +{ + const struct TCanVenientMhsError* err_list; + const char* error_str; + (void)tcan_error; + + if (can_error) + { + error_str = "No error. Success."; + } + else + { + for (err_list = CanVenientMhsError; (error_str = err_list->ErrorMsg); err_list++) + { + if (can_error == err_list->CanError) + { + break; + } + } + } + if (! error_str) + { + error_str = "Unknown error."; + } + return (error_str); +} + +static char* mhs_strdup(const char* str) +{ + if (str) + { + char* new_str; + size_t len = strnlen(str, 100); + new_str = (char*)malloc(len); + if (! new_str) + { + return (NULL); + } + memcpy(new_str, str, len); + return (new_str); + } + else + { + return (NULL); + } +} +#endif + +int tinycan_find_interfaces(void) +{ + int res = 0; +#ifdef _WIN32 + uint32_t k, idx; + struct TCanDevicesList* can_dev_list; + int32_t num_devs, i; + char dev_name[100]; + TTinyDevice* tiny_device; + + if (! TinyCanDriverInit) + { + if (LoadDriver(NULL) >= 0) + { + if (CanInitDriver(TREIBER_INIT_PARAM) >= 0) + { + TinyCanDriverInit = 1; + } + } + } + if ((num_devs = CanExGetDeviceList(&can_dev_list, 0)) <= 0) + { + set_error_reason("No Tiny-CAN hardwre found"); + return (-1); + } + for (i = 0; i < num_devs; i++) + { + uint32_t already_registered = 0; + + snprintf(dev_name, sizeof(dev_name), "%s (S/N: %s)", can_dev_list[i].Description, can_dev_list[i].SerialNumber); + + for (k = 0; k < CAN_MAX_INTERFACES; k++) + { + if (can_interface[k].name && strcmp(can_interface[k].name, dev_name) == 0) + { + already_registered = 1; + break; + } + } + if (already_registered) + { + continue; + } + + if (0 != find_free_interface_slot(&idx)) + { + break; /* No free slot available: stop. */ + } + + if (! (tiny_device = malloc(sizeof(TTinyDevice)))) + { + res = -1; + break; + } + can_interface[idx].internal = tiny_device; + if (! (can_interface[idx].name = mhs_strdup(dev_name))) + { + free(can_interface[idx].internal); + can_interface[idx].internal = NULL; + res = -1; + } + + tiny_device->DeviceIndex = INDEX_INVALID; + snprintf(tiny_device->Snr, 20, "%s", can_dev_list[i].SerialNumber); + + can_interface[idx].vendor = CAN_VENDOR_MHS; + can_interface[idx].opened = 0; + can_interface[idx].baudrate = CAN_BAUD_1M; + } + CanExDataFree((void**)&can_dev_list); + if (res < 0) + { + set_error_reason("Memory allocation failed."); + } +#endif + return (res); +} + +int tinycan_open(int index) +{ +#ifdef _WIN32 + int32_t err; + TTinyDevice* tiny_device; + const struct TCanVenientToTCanBaudrate* b; + uint16_t tcan_baud; + + if (! (tiny_device = (TTinyDevice*)can_interface[index].internal)) + { + return (-1); + } + + // Find matching baudrate + tcan_baud = 0; + for (b = &CanVenientToTCanBaudrate[0]; b->TCanBaudrate; b++) + { + if (b->CanVenientBaud == can_interface[index].baudrate) + { + tcan_baud = b->TCanBaudrate; + break; + } + } + + if (! tcan_baud || tcan_baud == 0xFFFF) + { + return (-1); + } + + if (tiny_device->DeviceIndex == INDEX_INVALID) + { + err = CanExCreateDevice(&tiny_device->DeviceIndex, "CanRxDFifoSize=16384"); + if (err) + { + return (-1); + } + } + + (void)CanExSetAsUWord(tiny_device->DeviceIndex, "CanSpeed1", tcan_baud); + (void)CanExSetAsString(tiny_device->DeviceIndex, "Snr", tiny_device->Snr); + + err = CanDeviceOpen(tiny_device->DeviceIndex, DEVICE_OPEN_PARAM); + if (err) + { + return (-1); + } + + err = CanSetMode(tiny_device->DeviceIndex, OP_CAN_START, CAN_CMD_ALL_CLEAR); + if (err) + { + return (-1); + } + + can_interface[index].opened = 1; + return (0); + +#else + set_error_reason("Tiny-Can driver is only supported on Windows."); + (void)index; + return -1; +#endif +} + +void tinycan_close(int index) +{ +#ifdef _WIN32 + + TTinyDevice* tiny_device; + + if (! (tiny_device = (TTinyDevice*)can_interface[index].internal)) + { + return; + } + (void)CanDeviceClose(tiny_device->DeviceIndex); + can_interface[index].opened = 0; // <*> + +#else + set_error_reason("Tiny-Can driver is only supported on Windows."); + (void)index; +#endif +} + +int tinycan_update(int index) +{ +#ifdef _WIN32 + + uint32_t dev_idx; + struct TDeviceStatus status; + + dev_idx = ((TTinyDevice*)can_interface[index].internal)->DeviceIndex; + CanGetDeviceStatus(dev_idx, &status); + if (status.DrvStatus >= DRV_STATUS_CAN_OPEN) + { + /* + if (status.FifoStatus & FIFO_HW_SW_OVERRUN) + { + can_error = CAN_ERROR_QOVERRUN; + } + if (status.CanStatus == CAN_STATUS_BUS_OFF) + { + can_error = CAN_ERROR_BUSOFF; + } + else if (status.CanStatus == CAN_STATUS_PASSIV) + { + can_error = CAN_ERROR_BUSPASSIVE; + } + */ + } + else + { + can_release(index); + return (-1); + } + return (0); + +#else + + set_error_reason("Tiny-Can driver is only supported on Windows."); + (void)index; + return -1; +#endif +} + +int tinycan_set_baudrate(int index, enum can_baudrate baud) +{ +#ifdef _WIN32 + + can_close(index); + can_interface[index].baudrate = baud; + return can_open(index, baud); + +#else + set_error_reason("Tiny-Can driver is only supported on Windows."); + (void)index; + (void)baud; + return -1; +#endif +} + +int tinycan_send(int index, const struct can_frame* frame) +{ +#ifdef _WIN32 + + uint32_t dev_idx, len; + struct TCanMsg tcan_msg; + int32_t can_error, tcan_res; + + can_error = 0; + dev_idx = ((TTinyDevice*)can_interface[index].internal)->DeviceIndex; + len = frame->can_dlc; + tcan_msg.Flags.Long = len; + tcan_msg.Id = frame->can_id; + if (len) + { + memcpy(tcan_msg.MsgData, frame->data, len); + } + if ((tcan_res = CanTransmit(dev_idx, &tcan_msg, 1)) < 0) + { + can_error = CAN_ERROR_TCAN; + } + else if (tcan_res == 0) + { + can_error = CAN_ERROR_QXMTFULL; + } + if (can_error) + { + set_error_reason(lookup_error_string(can_error, tcan_res)); + return (-1); + } + return (0); + +#else + set_error_reason("Tiny-CAN driver is only supported on Windows."); + (void)index; + (void)frame; + return -1; +#endif +} + +int tinycan_recv(int index, struct can_frame* frame, u64* timestamp) +{ +#ifdef _WIN32 + + uint32_t dev_idx; + struct TCanMsg tcan_msg; + int32_t can_error, tcan_res; + + can_error = 0; + dev_idx = ((TTinyDevice*)can_interface[index].internal)->DeviceIndex; + if ((tcan_res = CanReceive(dev_idx, &tcan_msg, 1)) == 1) + { + if (! tcan_msg.MsgRTR) + { + uint8_t len = (uint8_t)tcan_msg.MsgLen; + frame->can_id = tcan_msg.Id; + frame->can_dlc = len; + if (len) + { + memcpy(frame->data, tcan_msg.MsgData, len); + } + *timestamp = 0; // T.D. + } + else + { + can_error = CAN_ERROR_QRCVEMPTY; + } + } + else if (tcan_res == 0) + { + can_error = CAN_ERROR_QRCVEMPTY; + } + else + { + can_error = CAN_ERROR_TCAN; + } + if (can_error) + { + set_error_reason(lookup_error_string(can_error, tcan_res)); + return (-1); + } + return (0); + +#else + + set_error_reason("Tiny-CAN driver is only supported on Windows."); + (void)index; + (void)frame; + (void)timestamp; + return -1; +#endif +} diff --git a/src/drivers/CANvenient_TinyCan.h b/src/drivers/CANvenient_TinyCan.h new file mode 100755 index 0000000..ba81fbf --- /dev/null +++ b/src/drivers/CANvenient_TinyCan.h @@ -0,0 +1,31 @@ +/** @file CANvenient_TinyCan.h + * + * CANvenient is an abstraction layer for multiple CAN APIs on + * Windows and Linux. + * + * Copyright (c) 2026, Michael Fitzmayer. All rights reserved. + * SPDX-License-Identifier: MIT + * + **/ + +#include "CANvenient.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + + int tinycan_find_interfaces(void); + + int tinycan_open(int index); + void tinycan_close(int index); + int tinycan_update(int index); + + int tinycan_set_baudrate(int index, enum can_baudrate baud); + + int tinycan_send(int index, const struct can_frame* frame); + int tinycan_recv(int index, struct can_frame* frame, u64* timestamp); + +#ifdef __cplusplus +} +#endif diff --git a/src/drivers/CANvenient_internal.h b/src/drivers/CANvenient_internal.h index 4665d24..bb33364 100644 --- a/src/drivers/CANvenient_internal.h +++ b/src/drivers/CANvenient_internal.h @@ -21,7 +21,8 @@ enum can_vendor CAN_VENDOR_KVASER, CAN_VENDOR_PEAK, CAN_VENDOR_SOCKETCAN, - CAN_VENDOR_SOFTING + CAN_VENDOR_SOFTING, + CAN_VENDOR_MHS }; /*