Creation of USB User-Space Driver for CAN Interfaces under macOS®
Copyright © 2012-2023 Uwe Vogt, UV Software, Berlin (info@mac-can.com) \
All rights reserved.
Version $Rev$
Running CAN and CAN FD on Mac is the mission of the MacCAN project.
The MacCAN-Core repo is not aimed at building a driver or a library.
It provides the source code of an abstraction (or rather of a wrapper) of Apple´s IOUsbKit to create USB user-space drivers for CAN interfaces from various vendors under macOS.
I´m working with the CAN bus since the late 1990th, mainly on microcontrollers
(CANopen, SAE J1939, ISO-TP, UDS, etc.), but also on PC. See my blog for the things I did.
Sometimes I tend to ask myself some stupid questions like “Is it possible to …?”.
In 2010 I asked a well known search engine if it is possible to run CAN on a Mac.
I only found the request of a student, who had to do a semester work with CAN bus.
But that poor boy only owned a Mac.
In the absence of a CAN driver for Mac, I started developing an OS X user space driver for my PEAK USB-to-CAN dongle.
Many thanks to Uwe Wilhelm, CEO of PEAK-System Technik GmbH, who had supported me with technical information and several hardware.
The PCBUSB library realizes a USB user-space driver under macOS for PCAN® USB interfaces from PEAK-System Technik.
See the MacCAN website for further information, downloads and links.
The library supports up to 8 PCAN-USB and PCAN-USB FD devices.
It offers an easy to use API that is almost compatible to PEAK´s PCANBasic DLL on Windows.
Standard CAN frames (11-bit identifier) as well as extended CAN frames (29-bit identifier) are supported.
PCAN-USB FD devices can be operated in CAN 2.0 mode and in CAN FD mode.
The library comes with an Objective-C wrapper and a demo App; see https://youtu.be/v0U_WN7s3ao/ \
Furthermore, it can be used with the Qt® Serial Bus API on a Mac; see https://doc.qt.io/qt-5/qtserialbus-index.html
The PCBUSB library is closed source, please note the copyright and license agreements.
Rusoku Technologies is an innovative engineering team based in Lithuania, Europe.
They offer CAN adapter at a reasonable price.
Drivers and utilities for Windows and Linux are available as open-source.
See Rusoku´s website for the products and the services they offer.
The MacCAN-TouCAN driver for TouCAN USB interfaces from Rusoku is the first driver implementation based on MacCAN-Core.
And it is open source;
goto https://github.com/mac-can/RusokuCAN.dylib.
The Swedish company Kvaser AB supplies advanced CAN solutions to engineers designing and deploying systems for manifold areas of applications.
They offer a wide range of CAN hardware and CAN software, mainly for applications under Windows but also under Linux.
Welcome the MacCAN-KvaserCAN driver for CAN interfaces from Kvaser as the next member of the MacCAN family.
Find its source code on GitHub;
goto https://github.com/mac-can/KvaserCAN-Library.
/// \name MacCAN API
/// \brief MacCAN API based on CAN Interface API Version 3 (CAN API V3).
/// \note To implement a MacCAN driver derive a class from abstract class
/// CMacCAN, and override all pure virtual functions and optionally
/// the static function 'ProbeChannel'.
/// \{
class CMacCAN {
public:
/// \brief CAN channel states
enum EChannelState {
ChannelOccupied = CANBRD_OCCUPIED, ///< channel is available, but occupied
ChannelAvailable = CANBRD_PRESENT, ///< channel is available and can be used
ChannelNotAvailable = CANBRD_NOT_PRESENT, ///< channel is not available
ChannelNotTestable = CANBRD_NOT_TESTABLE ///< channel is not testable
};
/// \brief Common error codes (CAN API V3 compatible)
enum EErrorCodes {
NoError = CANERR_NOERROR, ///< no error!
BusOFF = CANERR_BOFF, ///< busoff status
ErrorWarning = CANERR_EWRN, ///< error warning status
BusError = CANERR_BERR, ///< bus error
ControllerOffline = CANERR_OFFLINE, ///< not started
ControllerOnline = CANERR_ONLINE, ///< already started
MessageLost = CANERR_MSG_LST, ///< message lost
TransmitterBusy = CANERR_TX_BUSY, ///< transmitter busy
ReceiverEmpty = CANERR_RX_EMPTY, ///< receiver empty
ErrorFrame = CANERR_ERR_FRAME, ///< error frame
Timeout = CANERR_TIMEOUT, ///< timed out
ResourceError = CANERR_RESOURCE, ///< resource allocation
InvalidBaudrate = CANERR_BAUDRATE, ///< illegal baudrate
IllegalParameter = CANERR_ILLPARA, ///< illegal parameter
NullPointer = CANERR_NULLPTR, ///< null-pointer assignment
NotInitialized = CANERR_NOTINIT, ///< not initialized
AlreadyInitialized = CANERR_YETINIT, ///< already initialized
InvalidLibrary = CANERR_LIBRARY, ///< illegal library
NotSupported = CANERR_NOTSUPP, ///< not supported
FatalError = CANERR_FATAL, ///< fatal error
VendorSpecific = CANERR_VENDOR ///< offset for vendor-specific error code
};
/// \name MacCAN Driver Interface
/// \brief Methods to be overridden by a MacCAN driver implementation.
/// \{
public:
/// \brief probes if the CAN interface (hardware and driver) given by
/// the argument 'channel' is present, and if the requested
/// operation mode is supported by the CAN controller.
//
/// \note When a requested operation mode is not supported by the
/// CAN controller, error CANERR_ILLPARA will be returned.
//
/// \param[in] channel - channel number of the CAN interface
/// \param[in] opMode - operation mode to be checked
/// \param[in] param - pointer to channel-specific parameters
/// \param[out] state - state of the CAN channel:
/// < 0 - channel is not present,
/// = 0 - channel is present,
/// > 0 - channel is present, but in use
//
/// \returns 0 if successful, or a negative value on error.
//
static MacCAN_Return_t ProbeChannel(int32_t channel, MacCAN_OpMode_t opMode, const void *param, EChannelState &state);
static MacCAN_Return_t ProbeChannel(int32_t channel, MacCAN_OpMode_t opMode, EChannelState &state);
/// \brief initializes the CAN interface (hardware and driver) given by
/// the argument 'channel'.
/// The operation state of the CAN controller is set to 'stopped';
/// no communication is possible in this state.
//
/// \param[in] channel - channel number of the CAN interface
/// \param[in] opMode - operation mode of the CAN controller
/// \param[in] param - pointer to channel-specific parameters
//
/// \returns handle of the CAN interface if successful,
/// or a negative value on error.
//
virtual MacCAN_Return_t InitializeChannel(int32_t channel, MacCAN_OpMode_t opMode, const void *param = NULL) = 0;
/// \brief stops any operation of the CAN interface and sets the operation
/// state of the CAN controller to 'stopped'.
//
/// \returns 0 if successful, or a negative value on error.
///
virtual MacCAN_Return_t TeardownChannel() = 0;
/// \brief signals waiting event objects of the CAN interface. This can
/// be used to terminate blocking operations in progress
/// (e.g. by means of a Ctrl-C handler or similar).
//
/// \remarks Some drivers are using waitable objects to realize blocking
/// operations by a call to WaitForSingleObject (Windows) or
/// pthread_cond_wait (POSIX), but these waitable objects are
/// no cancellation points. This means that they cannot be
/// terminated by Ctrl-C (SIGINT).
//
/// \note Even though this is not the case with Darwin, we support
/// this feature for compatibility reasons..
//
/// \returns 0 if successful, or a negative value on error.
///
virtual MacCAN_Return_t SignalChannel() = 0;
/// \brief initializes the operation mode and the bit-rate settings of the
/// CAN interface and sets the operation state of the CAN controller
/// to 'running'.
//
/// \note All statistical counters (tx/rx/err) will be reset by this.
//
/// \param[in] bitrate - bit-rate as btr register or baud rate index
//
/// \returns 0 if successful, or a negative value on error.
//
virtual MacCAN_Return_t StartController(MacCAN_Bitrate_t bitrate) = 0;
/// \brief stops any operation of the CAN interface and sets the operation
/// state of the CAN controller to 'stopped'; no communication is
/// possible in this state.
//
/// \returns 0 if successful, or a negative value on error.
//
virtual MacCAN_Return_t ResetController() = 0;
/// \brief transmits one message over the CAN bus. The CAN controller must
/// be in operation state 'running'.
//
/// \param[in] message - the message to send
/// \param[in] timeout - time to wait for the transmission of the message:
/// 0 means the function returns immediately,
/// 65535 means blocking read, and any other
/// value means the time to wait in milliseconds
//
/// \returns 0 if successful, or a negative value on error.
//
virtual MacCAN_Return_t WriteMessage(MacCAN_Message_t message, uint16_t timeout = 0U) = 0;
/// \brief read one message from the message queue of the CAN interface, if
/// any message was received. The CAN controller must be in operation
/// state 'running'.
//
/// \param[out] message - the message read from the message queue, if any
/// \param[in] timeout - time to wait for the reception of a message:
/// 0 means the function returns immediately,
/// 65535 means blocking read, and any other
/// value means the time to wait in milliseconds
//
/// \returns 0 if successful, or a negative value on error.
//
virtual MacCAN_Return_t ReadMessage(MacCAN_Message_t &message, uint16_t timeout = CANREAD_INFINITE) = 0;
/// \brief retrieves the status register of the CAN interface.
//
/// \param[out] status - 8-bit status register.
//
/// \returns 0 if successful, or a negative value on error.
//
virtual MacCAN_Return_t GetStatus(MacCAN_Status_t &status) = 0;
/// \brief retrieves the bus-load (in percent) of the CAN interface.
//
/// \param[out] load - bus-load in [%]
//
/// \returns 0 if successful, or a negative value on error.
//
virtual MacCAN_Return_t GetBusLoad(uint8_t &load) = 0;
/// \brief retrieves the bit-rate setting of the CAN interface. The
/// CAN controller must be in operation state 'running'.
//
/// \param[out] bitrate - bit-rate setting
//
/// \returns 0 if successful, or a negative value on error.
//
virtual MacCAN_Return_t GetBitrate(MacCAN_Bitrate_t &bitrate) = 0;
/// \brief retrieves the transmission rate of the CAN interface. The
/// CAN controller must be in operation state 'running'.
//
/// \param[out] speed - transmission rate
//
/// \returns 0 if successful, or a negative value on error.
//
virtual MacCAN_Return_t GetBusSpeed(MacCAN_BusSpeed_t &speed) = 0;
/// \brief retrieves a property value of the CAN interface.
//
/// \param[in] param - property id to be read
/// \param[out] value - pointer to a buffer for the value to be read
/// \param[in] nbytes - size of the given buffer in bytes
//
/// \returns 0 if successful, or a negative value on error.
//
virtual MacCAN_Return_t GetProperty(uint16_t param, void *value, uint32_t nbytes) = 0;
/// \brief modifies a property value of the CAN interface.
//
/// \param[in] param - property id to be written
/// \param[in] value - pointer to a buffer with the value to be written
/// \param[in] nbytes - size of the given buffer in bytes
//
/// \returns 0 if successful, or a negative value on error.
//
virtual MacCAN_Return_t SetProperty(uint16_t param, const void *value, uint32_t nbytes) = 0;
/// \brief retrieves the hardware version of the CAN controller
/// board as a zero-terminated string.
//
/// \returns pointer to a zero-terminated string, or NULL on error.
//
virtual char *GetHardwareVersion() = 0;
/// \brief retrieves the firmware version of the CAN controller
/// board as a zero-terminated string.
//
/// \returns pointer to a zero-terminated string, or NULL on error.
//
virtual char *GetFirmwareVersion() = 0;
/// \}
/// \name MacCAN Bit-rate converstion
/// \brief Methods for bit-rate conversion.
/// \note To be overridden when required.
/// \{
public:
static MacCAN_Return_t MapIndex2Bitrate(int32_t index, MacCAN_Bitrate_t &bitrate);
static MacCAN_Return_t MapString2Bitrate(const char *string, MacCAN_Bitrate_t &bitrate);
static MacCAN_Return_t MapBitrate2String(MacCAN_Bitrate_t bitrate, char *string, size_t length);
static MacCAN_Return_t MapBitrate2Speed(MacCAN_Bitrate_t bitrate, MacCAN_BusSpeed_t &speed);
/// \}
/// \name CAN FD Data Length Code
/// \brief Methods for DLC conversion.
/// \{
public:
static uint8_t Dlc2Len(uint8_t dlc);
static uint8_t Len2Dlc(uint8_t len);
/// \}
};
/// \}
[To Be Continued]
The MacCAN-Core sources are maintained in a SVN repo to synchronized them between the different MacCAN driver repos.
This work is dual-licensed under the terms of the BSD 2-Clause “Simplified” License and under the terms of the GNU General Public License v3.0 (or any later version).
You can choose between one of them if you use this work in whole or in part.
SPDX-License-Identifier: BSD-2-Clause OR GPL-3.0-or-later
Mac and macOS are trademarks of Apple Inc., registered in the U.S. and other countries. \
PCAN is a registered trademark of PEAK-System Technik GmbH, Darmstadt, Germany. \
Qt is a registered trademark of The Qt Company Ltd. and its subsidiaries. \
All other company, product and service names mentioned herein are trademarks, registered trademarks or service marks of their respective owners.
E-Mail: mailto://info@mac.can.com \
Internet: https://www.mac-can.net