Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NTCAN hardware plugin #504

Merged
merged 1 commit into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/transport_layer/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ int main()
#ifndef ISOBUS_VIRTUALCAN_AVAILABLE
std::cout << "This example requires the VirtualCAN plugin to be available. If using CMake, set the `-DCAN_DRIVER=VirtualCAN`." << std::endl;
return -1;
#endif
#else

std::shared_ptr<CANHardwarePlugin> originatorDriver = std::make_shared<VirtualCANPlugin>("test-channel");
std::shared_ptr<CANHardwarePlugin> recipientDriver = std::make_shared<VirtualCANPlugin>("test-channel");
Expand Down Expand Up @@ -175,7 +175,7 @@ int main()
}
std::this_thread::sleep_for(std::chrono::milliseconds(4));
}

#endif
CANHardwareInterface::stop();
return 0;
}
Expand Down
44 changes: 44 additions & 0 deletions hardware_integration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ if("SYS_TEC" IN_LIST CAN_DRIVER)
list(APPEND HARDWARE_INTEGRATION_INCLUDE "UsbCanLs.h")
list(APPEND HARDWARE_INTEGRATION_INCLUDE "UsbCanUp.h")
endif()
if("NTCAN" IN_LIST CAN_DRIVER)
list(APPEND HARDWARE_INTEGRATION_SRC "ntcan_plugin.cpp")
list(APPEND HARDWARE_INTEGRATION_INCLUDE "ntcan_plugin.hpp")
endif()

# Prepend the source directory path to all the source files
prepend(HARDWARE_INTEGRATION_SRC ${HARDWARE_INTEGRATION_SRC_DIR}
Expand Down Expand Up @@ -304,6 +308,46 @@ if("SYS_TEC" IN_LIST CAN_DRIVER)
)
endif()
endif()
if("NTCAN" IN_LIST CAN_DRIVER)
if(MSVC)
# See https://gitlab.kitware.com/cmake/cmake/-/issues/15170
set(CMAKE_SYSTEM_PROCESSOR ${MSVC_CXX_ARCHITECTURE_ID})
endif()
if(WIN32)
if(DEFINED ENV{CanSdkDir})
if(CMAKE_SYSTEM_PROCESSOR STREQUAL "AMD64" OR CMAKE_SYSTEM_PROCESSOR
STREQUAL "x64")
message(STATUS "Detected x64, linking to NTCAN x64 library")
target_link_libraries(HardwareIntegration
PRIVATE $ENV{CanSdkDir}/lib/VC/amd64/ntcan.lib)
target_include_directories(HardwareIntegration
PUBLIC $ENV{CanSdkDir}/include)
elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64" OR CMAKE_SYSTEM_PROCESSOR
STREQUAL "X86")
message(STATUS "Detected x86, linking to NTCAN x86 library")
target_link_libraries(HardwareIntegration
PRIVATE $ENV{CanSdkDir}/lib/VC/i386/ntcan.lib)
target_include_directories(HardwareIntegration
PUBLIC $ENV{CanSdkDir}/include)
else()
message(
FATAL_ERROR
"NTCAN Selected but no supported processor arch was detected. Only x64 and x86 are supported."
)
endif()
ad3154 marked this conversation as resolved.
Show resolved Hide resolved
else()
message(
FATAL_ERROR
"NTCAN Selected but no NTCAN SDK was found. Set the 'CanSdkDir' environment variable to the path of the NTCAN SDK."
)
endif()
else()
message(
FATAL_ERROR
"NTCAN Selected but no supported OS was detected. Only Windows is supported currently."
)
endif()
endif()

# Mark the compiled CAN drivers available to other modules. In the form:
# `ISOBUS_<uppercase CAN_DRIVER>_AVAILABLE` as a preprocessor definition.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,8 @@
#include "isobus/hardware_integration/sys_tec_windows_plugin.hpp"
#endif

#ifdef ISOBUS_NTCAN_AVAILABLE
#include "isobus/hardware_integration/ntcan_plugin.hpp"
#endif

#endif // AVAILABLE_CAN_DRIVERS_HPP
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//================================================================================================
/// @file ntcan_plugin.hpp
///
/// @brief An interface for using a ESD NTCAN driver.
/// @attention Use of the NTCAN driver is governed in part by their license, and requires you
/// to install their driver first, which in-turn requires you to agree to their terms and conditions.
/// @author Alex "Y_Less" Cole
/// @author Daan Steenbergen
///
/// @copyright 2024 The Open-Agriculture Developers
//================================================================================================
#ifndef NTCAN_PLUGIN_HPP
#define NTCAN_PLUGIN_HPP

#include <string>
#include "ntcan.h"

#include "isobus/hardware_integration/can_hardware_plugin.hpp"
#include "isobus/isobus/can_hardware_abstraction.hpp"
#include "isobus/isobus/can_message_frame.hpp"

namespace isobus
{
/// @brief A CAN Driver for ESD NTCAN Devices
class NTCANPlugin : public CANHardwarePlugin
{
public:
/// @brief Constructor for the ESD NTCAN CAN driver
/// @param[in] channel The logical net number assigned to the physical CAN port to use.
/// @param[in] baudrate The baudrate to use for the CAN connection.
explicit NTCANPlugin(int channel, int baudrate = NTCAN_BAUD_250);

/// @brief The destructor for NTCANPlugin
virtual ~NTCANPlugin() = default;

/// @brief Returns if the connection with the hardware is valid
/// @returns `true` if connected, `false` if not connected
bool get_is_valid() const override;

/// @brief Closes the connection to the hardware
void close() override;

/// @brief Connects to the hardware you specified in the constructor's channel argument
void open() override;

/// @brief Returns a frame from the hardware (synchronous), or `false` if no frame can be read.
/// @param[in, out] canFrame The CAN frame that was read
/// @returns `true` if a CAN frame was read, otherwise `false`
bool read_frame(isobus::CANMessageFrame &canFrame) override;

/// @brief Writes a frame to the bus (synchronous)
/// @param[in] canFrame The frame to write to the bus
/// @returns `true` if the frame was written, otherwise `false`
bool write_frame(const isobus::CANMessageFrame &canFrame) override;

/// @brief Changes previously set configuration parameters. Only works if the device is not open.
/// @param[in] channel The logical net number assigned to the physical CAN port to use.
/// @param[in] baudrate The baudrate to use for the CAN connection.
/// @returns True if the configuration was changed, otherwise false (if the device is open false will be returned)
bool reconfigure(int channel, int baudrate = NTCAN_BAUD_250);

private:
int net; ///< The logical net number assigned to the physical CAN port to use.
int baudrate; ///< The baudrate to use for the CAN connection.
std::uint64_t timestampFreq; ///< The frequency of the timestamp
std::uint64_t timestampOffset; ///< The offset of the timestamps
NTCAN_HANDLE handle = NTCAN_NO_HANDLE; ///< The handle as defined in the NTCAN driver API
NTCAN_RESULT openResult = NTCAN_SUCCESS; ///< Stores the result of the call to begin CAN communication. Used for is_valid check later.
};
}

#endif // NTCAN_PLUGIN_HPP
166 changes: 166 additions & 0 deletions hardware_integration/src/ntcan_plugin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//================================================================================================
/// @file ntcan_plugin.cpp
///
/// @brief An interface for using a ESD NTCAN driver.
/// @attention Use of the NTCAN driver is governed in part by their license, and requires you
/// to install their driver first, which in-turn requires you to agree to their terms and conditions.
/// @author Alex "Y_Less" Cole
/// @author Daan Steenbergen
///
/// @copyright 2024 The Open-Agriculture Developers
//================================================================================================

#include "isobus/hardware_integration/ntcan_plugin.hpp"
#include "isobus/isobus/can_stack_logger.hpp"

#include <chrono>
#include <thread>

namespace isobus
{
NTCANPlugin::NTCANPlugin(int channel, int baudrate) :
net(channel),
baudrate(baudrate)
{
}

bool NTCANPlugin::get_is_valid() const
{
return (NTCAN_SUCCESS == openResult) && (NTCAN_NO_HANDLE != handle);
}

void NTCANPlugin::close()
{
canClose(handle);
handle = NTCAN_NO_HANDLE;
}

void NTCANPlugin::open()
{
if (NTCAN_NO_HANDLE != handle)
{
isobus::CANStackLogger::error("[NTCAN]: Attempting to open a connection that is already open");
}
std::uint32_t mode = 0;
std::int32_t txQueueSize = 8;
std::int32_t rxQueueSize = 8;
std::int32_t txTimeOut = 100;
std::int32_t rxTimeOut = 1000;

openResult = canOpen(net, mode, txQueueSize, rxQueueSize, txTimeOut, rxTimeOut, &handle);

if (NTCAN_SUCCESS != openResult)
{
isobus::CANStackLogger::error("[NTCAN]: Error trying to open the connection");
return;
}

CAN_IF_STATUS status{ 0 };

openResult = canSetBaudrate(handle, baudrate);
if (NTCAN_SUCCESS != openResult)
{
isobus::CANStackLogger::error("[NTCAN]: Error trying to set the baudrate");
close();
return;
}

openResult = canStatus(handle, &status);
if (NTCAN_SUCCESS != openResult)
{
isobus::CANStackLogger::error("[NTCAN]: Error trying to get the status");
close();
return;
}

if (NTCAN_FEATURE_TIMESTAMP == (status.features & NTCAN_FEATURE_TIMESTAMP))
{
std::uint64_t timestamp = 0;
openResult = canIoctl(handle, NTCAN_IOCTL_GET_TIMESTAMP_FREQ, &timestampFreq);
if (NTCAN_SUCCESS == openResult)
{
openResult = canIoctl(handle, NTCAN_IOCTL_GET_TIMESTAMP, &timestamp);
}
if (NTCAN_SUCCESS == openResult)
{
auto now = std::chrono::system_clock::now();
auto unix = now.time_since_epoch();
long long millis = std::chrono::duration_cast<std::chrono::microseconds>(unix).count();
timestampOffset = millis - timestamp;
}
}

std::int32_t ids = (1 << 11);
openResult = canIdRegionAdd(handle, 0, &ids);
if (NTCAN_SUCCESS == openResult && ids != (1 << 11))
{
openResult = NTCAN_INSUFFICIENT_RESOURCES;
isobus::CANStackLogger::error("[NTCAN]: Error trying to add the standard ID region");
close();
return;
}

ids = (1 << 29);
openResult = canIdRegionAdd(handle, NTCAN_20B_BASE, &ids);
if (NTCAN_SUCCESS == openResult && ids != (1 << 29))
{
openResult = NTCAN_INSUFFICIENT_RESOURCES;
isobus::CANStackLogger::error("[NTCAN]: Error trying to add the extended ID region");
close();
return;
}
}

bool NTCANPlugin::read_frame(isobus::CANMessageFrame &canFrame)
{
NTCAN_RESULT result;
CMSG_T msgCanMessage{ 0 };
bool retVal = false;
std::int32_t count = 1;

result = canReadT(handle, &msgCanMessage, &count, nullptr);

if (NTCAN_SUCCESS == result && 1 == count)
{
canFrame.dataLength = msgCanMessage.len;
memcpy(canFrame.data, msgCanMessage.data, msgCanMessage.len);
canFrame.identifier = (msgCanMessage.id & ((1 << 29) - 1));
canFrame.isExtendedFrame = (NTCAN_20B_BASE == (msgCanMessage.id & NTCAN_20B_BASE));
canFrame.timestamp_us = msgCanMessage.timestamp * 1000000 / timestampFreq + timestampOffset;
retVal = true;
}
else
{
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
return retVal;
}

bool NTCANPlugin::write_frame(const isobus::CANMessageFrame &canFrame)
{
NTCAN_RESULT result;
CMSG_T msgCanMessage{ 0 };
std::int32_t count = 1;

msgCanMessage.id = canFrame.isExtendedFrame ? (canFrame.identifier | NTCAN_20B_BASE) : canFrame.identifier;
msgCanMessage.len = canFrame.dataLength;
memcpy(msgCanMessage.data, canFrame.data, canFrame.dataLength);

result = canWriteT(handle, &msgCanMessage, &count, nullptr);

return (NTCAN_SUCCESS == result && 1 == count);
}

bool NTCANPlugin::reconfigure(int channel, int baudrate)
{
bool retVal = false;

if (!get_is_valid())
{
net = channel;
this->baudrate = baudrate;
retVal = true;
}
return retVal;
}
} // namespace isobus
1 change: 1 addition & 0 deletions sphinx/source/api/hardware/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ When compiling with CMake, a default CAN driver plug-in will be selected for you
- :code:`-DCAN_DRIVER=WindowsInnoMakerUSB2CAN` for the InnoMaker USB2CAN adapter (Windows)
- :code:`-DCAN_DRIVER=TouCAN` for the Rusoku TouCAN (Windows)
- :code:`-DCAN_DRIVER=SYS_TEC` for a SYS TEC sysWORXX USB CAN adapter (Windows)
- :code:`-DCAN_DRIVER=NTCAN` for the NTCAN driver (Windows)

Or specify multiple using a semicolon separated list: :code:`-DCAN_DRIVER="<driver1>;<driver2>"`

Expand Down
Loading