Skip to content

BMAPI SDK software development guide

The entire series of bus analyzer products from BUSMUST support the BMAPI universal software programming interface, supporting various mainstream programming languages, including but not limited to:

  • C/C++ (Supports Windows/Linux operating systems, supports X86/X64/AARCH64 and other platforms)
  • Python (Supports python-can and udsoncan open-source application libraries)
  • Qt
  • C#
  • VB.NET
  • Labview

Simply put, as long as the development environment supports calling dll/so dynamic libraries, you can use BMAPI for secondary development, thereby implementing user-defined various bus analysis application layer functions. BUSMUST provides the official BMAPI SDK, which contains dynamic libraries and header files required for development, and provides a large number of examples for your reference.

This article will take ten minutes to guide you through how to use BMAPI to implement functions such as CANFD/LIN protocol message transmission and reception.

After receiving the bus analyzer hardware and before starting your secondary development work, please complete the following preparation steps.

Successfully use the analyzer for message transmission and reception in Busmaster

Section titled “Successfully use the analyzer for message transmission and reception in Busmaster”

This step is very critical. Because secondary development is relatively complex, error-prone, and has many variable factors that are difficult to troubleshoot, we strongly recommend that you first complete preliminary debugging in our verified official Busmaster environment. Completing this step will mean that the analyzer is compatible with your on-site bus hardware and can stably transmit and receive:

  1. Log in to the busmust.com official website, download and install the latest version of the Busmaster software. During the installation of this software, the device driver will be automatically installed (you need to disable driver signature enforcement). Correctly connect the bus analyzer to the bus that requires communication, then open Busmaster, select the connected analyzer channel, correctly configure the baud rate, termination resistor, and sampling point, and then click Connect.
  2. Observe in the Message Window whether the received messages are stable, and try sending messages in the Transmit Window to observe whether they can be successfully sent. If not, you need to return to step 2 to check the hardware connection and software configuration until stable transmission and reception are achieved.
  3. Record the baud rate, termination resistor, sampling point, and other parameters that can work stably. The subsequent secondary development work will require configuring this set of correct parameters to the analyzer hardware.

Successfully run any official example in the BMAPI SDK

Section titled “Successfully run any official example in the BMAPI SDK”

This step is relatively important for new customers who are not familiar with BMAPI. Because secondary development is relatively complex, error-prone, and has many variable factors that are difficult to troubleshoot, we strongly recommend that you select and successfully compile and run at least one example in the example folder of the SDK as needed. Completing this step will mean that you have successfully set up the development environment and can directly operate the analyzer hardware through the BMAPI interface:

  1. Log in to the busmust.com official website, download and extract the latest version of the BMAPI SDK. Note that you must extract it as a whole; you cannot extract only a single example, otherwise the examples will fail to compile.
  2. Open the doc/BMAPI.CHM document to familiarize yourself with the overall programming model. In the future, you can also search for function names here to obtain function help documentation (or directly review the comments in the BMAPI header files).
  3. Open the example folder, familiarize yourself with the various examples provided by the SDK, and select an example closest to your application scenario as a starting point. First, do not make any modifications to the source code, directly compile it and confirm that it can run normally (instead of calling BMAPI directly from scratch in your own complex program).

Common basic examples include (see the example folder for the full list of examples):

  • receive_only: Demonstrates how to receive messages only
  • transmit_only: Demonstrates how to send messages only
  • dual_thread_txrx_cpp: Demonstrates dual-thread asynchronous message transmission and reception
  • can_analyzer_qt: Demonstrates message transmission and reception in a Qt interface (this example has versions in multiple programming languages)

Q1: After clicking Connect in Busmaster, no messages are received, or even a BUSOFF error is displayed directly in the trace window.

A1: Check whether the hardware wiring is correct, whether the opposite device is powered on and not in a sleep state, use a multimeter to measure whether the bus termination resistance is 60 ohms (a single 120 ohms can also work during low-speed transmission), and whether the baud rate matches the opposite device (note that CANFD has two baud rates: the arbitration phase and the data phase). Then unplug and re-plug the analyzer device, reconnect, and try again.

Q2: Busmaster can receive messages, but after sending a message, the software prompts a BUSOFF error?

A2: This is similar to the above problem, but at least it indicates that the data phase baud rate is correct and the opposite ECU is working normally. It is recommended to further check:

  1. The wiring is stable and reliable, and not too long; twisted pair cables are recommended for longer connections;
  2. The bus termination resistance value complies with ISO specifications, with a 120-ohm resistor at each end of the bus. Our product can programmably enable one of the 120-ohm resistors;
  3. The mode in the baud rate setting interface is not listen only or lookback;
  4. The data phase baud rate matches your ECU (in-vehicle ECUs generally have a CAN-FD data phase of 2000000bps, which is different from the arbitration phase);
  5. The sampling point position matches your ECU. Click the Advanced button in the baud rate configuration interface to configure the analyzer sampling point position (ECUs are generally 75%-80%, however the default generated code for some microcontrollers is at 50%, in which case you need to adjust the ECU sampling point position).

Q3: I can send STD and STD+FD messages, but I cannot send STD+FD+BRS messages?

A3: CANFD has two baud rates: the arbitration phase and the data phase. When and only when the BRS flag is enabled, both baud rates will be used simultaneously. If we have correctly configured the arbitration phase baud rate and sampling point, but the data phase has not yet been configured properly, this phenomenon will occur. In addition, for higher data phase baud rates, the CANFD driver of the opposite ECU device needs to correctly set the Transmitter Delay Compensation (TDC) function.

Q4: The example fails to compile?

A4: Please completely extract the SDK and pre-install the appropriate build toolchain (including gcc), especially correctly installing libusb (sudo apt-get install libusb-1.0-0-dev). If it still reports a compilation error, feel free to send a screenshot of the error or paste it to the backend of the BUSMUST official WeChat account, and our technical support specialists will help you analyze the problem.

Q5: The example compiled successfully, but it cannot run under Linux, prompting that libbmapi64.so cannot be found.

A5: Under Linux, there is an environment variable LD_LIBRARY_PATH. Please add the directory where libbmapi64.so is located to this environment variable. It is recommended to refer to {SDKDIR}/bin/unix64/release/run.sh to create your own startup script, and use the startup script to quickly run the program.

Q6: The example compiled successfully, but the channel cannot be opened under Linux, prompting a LIBUSB_ERROR_ACCESS error.

A6: By default, sudo permissions are required to access USB devices. If you want to avoid using sudo, you can also use the “sudo chmod -R 777 /dev/bus/usb/” command to add read and write permissions for regular users to each USB device at once.

Q7: I am using Python, and I don’t know how to configure the environment or how to transmit and receive messages.

A7: The Python environment configuration is slightly more complex. Please be sure to read {SDKDIR}/Python开发必读.txt first. Our SDK has already adapted to two powerful third-party general open-source libraries, python-can and udsoncan. This means that when you operate the analyzer through Python, you basically do not need to call BMAPI directly. After the development environment is set up, you only need to refer to the massive amount of public information on the Internet regarding python-can and udsoncan to directly perform application layer programming. Programming questions regarding python-can and udsoncan are beyond the scope of BMAPI secondary development and will not be elaborated in this article. However, if you encounter any problems, you can still contact us through any technical support channel to get help.

First, let’s get to know the message objects of each protocol, as we will frequently manipulate them when transmitting and receiving messages.

In BMAPI, BM_DataTypeDef is the common data structure representing message data. All analyzer products use this abstract object as the frame header. Its internal definition is as follows:

typedef struct {
BM_DataHeaderTypeDef header; /**< data header, see BM_DataHeaderTypeDef for details. */
uint16_t length; /**< length in bytes of the payload byte array (header excluded) */
uint32_t timestamp; /**< 32-bit device local high precision timestamp in microseconds. */
uint8_t payload[BM_DATA_PAYLOAD_MAX_SIZE]; /**< buffer holding concrete message payload (i.e. a
CAN message in BM_CanMessageTypeDef format),
followed by an optional tail. */
} BM_DataTypeDef;

Please pay attention to several of the key members (for detailed definitions, please refer to BMAPI.CHM):

  • BM_DataTypeDef.header.type: Message type, for example:
  • BM_CAN_FD_DATA: Represents that a received CANFD message is stored in BM_DataTypeDef.payload
  • BM_CAN_FD_DATA | BM_ACK_DATA: Represents that a sent CANFD message is stored in BM_DataTypeDef.payload, i.e., TEF (Transmit Event FIFO)
  • BM_DataTypeDef.timestamp: The 32-bit hardware timestamp of the message (if you need to obtain a 64-bit timestamp, please call BM_GetDataPtpTimestamp instead of getting the value of this member)

When the data type indicated by (BM_DataTypeDef.header.type & ~BM_ACK_DATA) is BM_CAN_FD_DATA, it means that BM_DataTypeDef.payload actually stores a BM_CanMessageTypeDef structure. Its internal definition is as follows:

typedef struct {
BM_MessageIdTypeDef id; /**< CAN message ID, see BM_MessageIdTypeDef for details. */
union {
BM_TxMessageCtrlTypeDef
tx; /**< TX CAN message control fields, invalid if this is NOT a TX can message. */
BM_RxMessageCtrlTypeDef
rx; /**< RX CAN message control fields, invalid if this is NOT a RX can message. */
} ctrl; /**< CAN message control fields, whether TX or RX is taken depends on the message
direction. */
uint8_t payload[64]; /**< CAN message payload */
} BM_CanMessageTypeDef;

Please pay attention to several of the key members (for detailed definitions, please refer to BMAPI.CHM):

  • BM_CanMessageTypeDef.id.SID: The CAN ID of a standard frame (the extended frame ID format is slightly more complex; it is recommended to use the two helper macros BM_GET_CAN_MSG_ID and BM_SET_CAN_MSG_ID)
  • BM_CanMessageTypeDef.ctrl.tx.DLC (or rx.DLC, the two are always consistent): The DLC of the message. Please note that this is the CANFD length code and not the number of bytes. For example, DLC=0xF means the message payload length is 64 bytes, not 15 bytes.

Other message flags such as ctrl.tx.IDE (represents an extended frame), ctrl.tx.FDF (represents a CANFD frame), ctrl.tx.BRS (represents using the high-speed data phase baud rate), where ctrl.tx.XXX is always completely consistent with ctrl.rx.XXX.

When the data type indicated by (BM_DataTypeDef.header.type & ~BM_ACK_DATA) is BM_LIN_DATA, it means that BM_DataTypeDef.payload actually stores a BM_LinMessageTypeDef structure. Its internal definition is as follows:

typedef struct {
uint8_t id; /**< LIN message ID */
uint8_t padding[3];
union {
BM_LinMessageCtrlTypeDef
lin; /**< LIN message control fields, invalid if this is NOT a LIN message. */
} ctrl; /**< Message control fields. */
uint8_t payload[8]; /**< LIN message payload */
} BM_LinMessageTypeDef;

Please pay attention to several of the key members (for detailed definitions, please refer to BMAPI.CHM):

  • BM_LinMessageTypeDef.id: The frame ID of the LIN message (value 0-63), not the PID
  • BM_LinMessageTypeDef.ctrl.lin.DLC: The DLC of the message (0-8), which is also the payload length
  • ctrl.lin.ENHANCED_CHECKSUM: Enable enhanced checksum
  • ctrl.lin.TRANSMIT: 1 represents that this is a write request, 0 represents that this is a master read request
  • ctrl.lin.ERRORS: The analyzer supports receiving erroneous LIN frames; ERRORS is the error reason

Congratulations, you are now fully prepared and can officially begin your secondary software development work according to the following programming model.

First, please enumerate and discover device channels, open the channel, and complete initialization. The following is pseudocode:

BM_ChannelInfoTypeDef channelinfos[MAX_CHANNEL_COUNT];
BM_ChannelHandle channels[MAX_CHANNEL_COUNT];
int nchannels = MAX_CHANNEL_COUNT;
/* Initialize the BMAPI library. This only needs to be run once when the program starts. Do not call it repeatedly. */
BM_Init();
/* Enumerate (discover) the connected channels and save the enumerated channel information in the channelinfos array */
BM_Enumerate(channelinfos, &nchannels);
for (int channelid = 0; channelid < nchannels; channelid++) {
#ifdef TARGET_CHANNEL_NAME
// BM_ChannelInfoTypeDef.name contains the device serial number and port index, so it is globally unique.
// It can be used for one-to-one mapping with physical ports, and is also the primary information for distinguishing different devices and ports in multi-device connection scenarios.
if (strcmp(channelinfos[channelid].name, TARGET_CHANNEL_NAME) != 0)
continue;
#endif
/* Open all the channels to be used one by one */
BM_BitrateTypeDef bitrate = {0};
bitrate.nbitrate = 500; /* Arbitration segment bitrate */
bitrate.dbitrate = 2000; /* Data segment bitrate */
bitrate.nsamplepos = 75; /* Arbitration segment sample point */
bitrate.dsamplepos = 80; /* Data segment sample point */
error = BM_OpenEx(&channels[channelid], /* Upon successful opening, this output parameter will hold the opened channel handle */
&channelinfos[channelid], /* This is the channel information just enumerated */
BM_CAN_NORMAL_MODE, /* Port working mode, default is normal */
BM_TRESISTOR_120, /* Enable/disable termination resistor */
&bitrate, /* Bitrate configuration structure */
NULL, 0 /* Hardware filter structure, can be left unconfigured if not needed */
);
if (error != BM_ERROR_OK) {
printf("Failed to open %s, error=0x%08x.\n", channelinfos[channelid].name, error);
}
}

In the pseudocode above, BM_CAN_NORMAL_MODE sets the analyzer to normal mode. The analyzer supports several working modes:

Mode Function Definition
BM_CAN_NORMAL_MODE CAN normal mode, supports CAN and CANFD
BM_CAN_CLASSIC_MODE CAN classic mode, does not support CANFD
BM_CAN_LISTEN_ONLY_MODE Listen-only to CANFD bus, cannot send messages, does not automatically reply ACK, does not interfere with the bus
BM_CAN_CONFIGURATION_MODE Disable CAN port
BM_CAN_NORMAL_MODE | BM_CAN_NOACK_MODE CAN no-acknowledgement mode. In this mode, sending a message returns immediately without waiting for transmission to complete, and it is impossible to determine whether the transmission was successful
BM_CAN_EXTERNAL_LOOPBACK_MODE Bus loopback mode, messages sent to the bus can also be received by oneself
BM_CAN_INTERNAL_LOOPBACK_MODE Self-loopback mode, messages sent by oneself will be received by oneself, but the bus cannot receive them
BM_LIN_MASTER_MODE LIN master mode, supports master read, master write, and simultaneous bus listening
BM_LIN_SLAVE_MODE LIN slave mode, supports responding to master read requests, and simultaneous bus listening

BMAPI supports receiving messages of multiple protocols. Regardless of the hardware channel type, we can use the previously mentioned abstract data type BM_Data and abstract APIs like BM_Read to read messages.

Polling is a straightforward reception method. The application only needs to periodically call the BM_Read function in some way to continuously read the received messages. When BM_Read successfully reads a message, it returns BM_ERROR_OK; if there are no new messages to read in the receive buffer at the moment, the function returns the BM_ERROR_QRCVEMPTY (receive buffer empty) error code.

Please note that the BM_ERROR_QRCVEMPTY error code does not represent an error in the true sense. It merely indicates that there is temporarily no new data to read, and you can try reading again later.

To ensure sufficient reading speed, be sure to continuously loop and read until the buffer is empty each time the timer expires, rather than reading only one message per timer tick.

The pseudocode for polling reception is as follows:

timeout() {
BM_DataTypeDef msg;
while (BM_Read(channel, &msg) == BM_ERROR_OK)
process_rx(msg);
}

In addition to the BM_Read abstract API, there are also some helper functions for specific bus protocols. These functions call BM_Read internally while providing more specific and user-friendly operation interfaces externally:

BM_ReadCanMessage

BM_ReadLinMessage

For details, please refer to BMAPI.CHM.

Blocking bulk reception is another straightforward reception method. The application only needs to call the BM_ReadMultiple function, specifying the receive buffer, the expected number of messages to receive, and the timeout duration, to automatically block subsequent program execution. When BM_ReadMultiple successfully reads the expected number of messages, the function immediately returns BM_ERROR_OK (without needing to wait for the timeout to expire); otherwise, it returns BM_ERROR_BUSTIMEOUT after the timeout, at which point the application can determine the actual number of received messages through the pointer-type output parameter.

The pseudocode for blocking bulk reception is as follows:

BM_DataTypeDef msgs[MAX_RX_MSG_COUNT];
int n = MAX_RX_MSG_COUNT;
BM_ReadMultiple(channel, msgs, &n, RX_TIMEOUT);
// After the function returns, n, which is an input-output type parameter, will be updated to the actual number of received messages
for (int i = 0; i < n; i++)
process_rx(msgs[i]);

The polling method mentioned earlier is very simple but lacks real-time performance. Consider a scenario: if a message is captured by the hardware while our application is asleep, the application cannot process this message in time. It can only read and process the message after the sleep ends and the next poll occurs. To improve reception real-time performance, BMAPI introduces the “asynchronous notification” mechanism.

BMAPI provides a notification event (BM_NotificationHandle) for each channel (BM_ChannelHandle). When this channel receives a message or completes sending its own message, a notification event will be triggered. We can use BM_WaitForNotifications in the application to wait for this notification event. When the event occurs, it immediately stops waiting, reads, and starts processing the newly received message, thereby achieving an “asynchronous” reception method between hardware capture and software processing logic.

The asynchronous mode is an efficient programming model. In fact, the previously mentioned BM_ReadMultiple function and the Busmaster host software internally both use this asynchronous notification mechanism.

The pseudocode for asynchronous reception is as follows:

BM_NotificationHandle notification = NULL;
/* First, obtain the notification handle */
BM_GetNotification(channel, notification);
/* Then start waiting for the asynchronous notification (supports waiting for multiple notifications simultaneously, only waiting for one here) */
int rxChannelId = BM_WaitForNotifications(notification, 1, RX_TIMEOUT);
if (rxChannelId >= 0) {
/* Immediately read the new messages corresponding to the notification event via BM_Read */
for (int i = 0; i < openedChannelCount; i++)
while (BM_Read(channels[i], &msg) == BM_ERROR_OK)
process_rx(msg);
/* No additional sleep operation is needed */
}

Please note the “coalescing” effect of BM_WaitForNotifications:

  • When the message rate is high, multiple messages have already been received between two notification event processings. In this case, one wait corresponds to multiple messages;
  • When there are many channels, multiple channels have generated events between two notification event processings, but only the foremost channel ID with a notification event is returned each time. In this case, one wait corresponds to multiple channels.

Therefore, to ensure that all messages can be processed in a timely manner, be sure to use a two-dimensional loop to completely read empty the receive buffers of all channels every time any notification event is received, to avoid data accumulation.

BMAPI supports sending messages of multiple protocols. Regardless of the hardware channel type, we can use the previously mentioned abstract data type BM_Data and abstract APIs like BM_Write to write messages.

Synchronous blocking send is a straightforward sending method. That is, the application only needs to call the BM_Write function, specifying a single message to be transmitted and the timeout duration, to automatically block subsequent program execution. When BM_Write successfully sends the message to the physical bus, the function immediately returns BM_ERROR_OK (without needing to wait for the timeout to expire); otherwise, it returns BM_ERROR_BUSTIMEOUT after the timeout.

The pseudocode for blocking send is as follows:

BM_DataTypeDef msg;
uint32_t timestamp = 0;
/* Initialize the message to be sent */
uint8_t payload[64] = {0x11, 0x22, 0x33, 0x44};
BM_INIT_CAN_FD_DATA(msg, 0x123 /*id*/, 8 /*dlc*/, 0 /*ide*/, 0 /*fdf*/, 0 /*brs*/, 0, 0, payload);
if (BM_Write(channel, &msg, TX_TIMEOUT, &timestamp) == BM_ERROR_OK)
printf("Message sent to bus @%u\n", timestamp);

In addition to the BM_Write abstract API, there are also some helper functions for specific bus protocols. These functions call BM_Write internally while providing more specific and user-friendly operation interfaces externally:

BM_WriteCanMessage

BM_WriteLinMessage

For details, please refer to BMAPI.CHM.

Q1: Calling BM_Write always results in a send timeout. What could be the reasons?

A1: A timeout indicates that the message cannot be sent to the bus, or although it was sent to the bus, we did not receive the automatic acknowledgement (this is an underlying mechanism of the CAN protocol, completed automatically by the hardware). There are many reasons for this problem. It is recommended to first try using the verified Busmaster host software to open the same port, use the same configuration parameters such as bitrate, and try to send, observing whether the transmission is successful. If not, modify the configuration parameters. After debugging successfully with Busmaster, return to the development environment and use the same configuration parameters as Busmaster to initialize the channel.

Q2: After a send timeout occurs for some reason, successful sending is no longer possible, and a BUSOFF is reported.

A2: Similarly, please first use Busmaster to ensure stable sending and receiving, and then try sending in your secondary development software. You can refer to the “FAQ” section earlier in the text to understand how to troubleshoot BUSOFF issues in Busmaster. Additionally, you can refer to the “Fault Recovery” section later in the text to understand how to recover from the fault state and continue sending and receiving operations after an unexpected BUSOFF fault occurs.

Q3: Do the various function operations in BMAPI support multithreading?

A3: All WriteXXX and ReadXXX functions support multithreading, but OpenEx and various SetXXX functions do not support multithreading.

When it is known in advance that a large number of messages will be sent, it is recommended to directly use the blocking bulk send method to reduce the additional overhead caused by API calls, thereby significantly improving the sending speed. Typically, single-frame blocking send can only achieve a sending speed of around 1000 fps, while bulk blocking send can reach the theoretical sending speed limit of the analyzer device.

The application only needs to call the BM_WriteMultiple function, specifying the send buffer, the expected number of messages to send, and the timeout duration, to automatically block subsequent program execution. When BM_WriteMultiple successfully sends the expected number of messages, the function immediately returns BM_ERROR_OK (without needing to wait for the timeout to expire); otherwise, it returns BM_ERROR_BUSTIMEOUT after the timeout, at which point the application can determine the actual number of successfully sent messages through the pointer-type output parameter.

The pseudocode for blocking bulk send is as follows:

BM_DataTypeDef msgs[MAX_TX_MSG_COUNT];
uint32_t timestamps[MAX_TX_MSG_COUNT];
int n = MAX_TX_MSG_COUNT;
/* Prepare the full content of the messages in the transmit buffer in advance */
prepare_tx_msg(msgs, n);
/* Bulk send, after the function returns, n, which is an input-output parameter, will be changed to the actual number of messages successfully sent */
BM_WriteMultiple(channel, msgs, &n, TX_TIMEOUT, timestamps);
for (int i = 0; i < n; i++)
printf("TX[%d].TS (on CAN bus) = %u\n", i, timestamps[i]);

When sending a large number of messages in a single batch, blocking bulk transmission can already achieve extremely high send speeds. However, when there is a lot of transmit-receive interaction, there is often no opportunity to send a large number of messages at once, and the additional overhead brought by the “waiting for transmission to complete” inherent in the blocking mode becomes prominent again. To completely eliminate the additional overhead caused by blocking, BMAPI provides an asynchronous transmission mode that requires no blocking wait.

The application only needs to specify the timeout parameter as 0 when calling BM_Write or BM_WriteMultiple to automatically enable asynchronous transmission mode. At this time, the write function is only responsible for putting the data into the transmit buffer and then returns immediately, without waiting for the transmission to complete. BMAPI will automatically send the messages to the bus in an internal background thread when it is able to send them.

Asynchronous transmission mode can easily reach the theoretical transmission speed limit of the analyzer device. However, for many enterprise-level application scenarios, the application cannot simply assume that the transmission is inevitably successful. It needs to obtain the transmission result, and even obtain the timestamp of the successful transmission on the bus for performance analysis, etc. In asynchronous mode, you can read the transmission results (including timestamps) through the BM_Read interface:

When the BM_ACK_DATA flag bit is set in the BM_DataTypeDef.header.type output by BM_Read, i.e., (BM_DataTypeDef.header.type & BM_ACK_DATA) != 0, it indicates that what was read is a transmission complete event, meaning the message corresponding to this event has been successfully sent to the target bus.

Otherwise, it indicates that what was read is a regular message received by the analyzer from other nodes on the bus.

Regardless of whether BM_Read reads a transmission complete event or a normal received message, all information in data (including payload, timestamp, etc.) is valid and can be used for display or analysis.

It is recommended that you use the classic dual-thread programming model with separated transmission and reception:

  • First, complete the analyzer channel initialization in the main thread by calling functions such as BM_OpenEx, and create a background reception thread.
  • The background reception thread executes the reception code corresponding to the “Asynchronous Notification Reception” section mentioned earlier, processing transmission complete events as well as normal received messages.
  • You can execute any asynchronous transmission code (timeout=0) in the main thread at any time.

This multi-threaded transmission and reception-separated programming model not only ensures the ultimate transmission speed, but also achieves rigorous handling of transmission complete events. It is the operation mode officially recommended by BMAPI, and in fact, this is also how it is implemented internally in BUSMASTER. Its pseudocode is as follows:

/* Background reception thread */
int RxThread(BM_ChannelHandle channel) {
BM_NotificationHandle notification = NULL;
BM_GetNotification(channel, notification);
int rxChannelId = BM_WaitForNotifications(notification, 1, RX_TIMEOUT);
if (rxChannelId >= 0) {
for (int i = 0; i < openedChannelCount; i++)
while (BM_Read(channels[i], &msg) == BM_ERROR_OK)
if (msg.header.type & BM_ACK_DATA)
process_tx_complete_event(msg); /* Handle transmission complete event */
else
process_rx(msg); /* Handle standard received message */
}
}
/* Main thread */
int main(void) {
/* First complete channel initialization */
BM_ChannelHandle channel = NULL;
BM_OpenEx(&channel, ...);
/* Then create a background reception thread (responsible for handling standard received messages and transmission complete events), passing the channel handle to the background thread */
CreateThread(RxThread, channel);
/* After this, you can asynchronously send single-frame or multi-frame messages at any time */
BM_Write(channel, &data, 0 /* A timeout of 0 represents asynchronous transmission */, NULL);
}

Ignoring Asynchronous Transmission Results

Section titled “Ignoring Asynchronous Transmission Results”

For some simple test scenarios, we do not care about the exact moment when the message is successfully sent, or even whether the message is sent successfully. At this time, you can disable transmission complete events through the special mode flag bit BM_CAN_NOACK_MODE. This improves transmission speed via asynchronous transmission while preventing asynchronous “transmission complete events” from occupying the receive buffer and reducing code complexity. The pseudocode is as follows:

/* Disable transmission complete events */
BM_SetCanMode(channel, BM_CAN_NORMAL_MODE | BM_CAN_NOACK_MODE);
Sleep(10);
/* Now you can simply send asynchronously, at this point any asynchronous transmission will return immediately without checking whether the transmission was successful */
BM_Write(channel, &data, 0 /* Asynchronous transmission */, NULL);

Please note that if you do not intend to use the BM_CAN_NOACK_MODE mode in your design, be sure to promptly fetch all transmission complete events via BM_Read. Otherwise, when transmission complete events accumulate and fill up the receive buffer, BMAPI will be unable to continue sending or receiving messages.

For UDS diagnostic-related operations commonly used in the automotive industry, BMAPI provides underlying ISOTP flow control support. Through the two APIs, BM_WriteIsotp and BM_ReadIsotp, you can directly pass in the diagnostic message content buffer and length to conveniently send and receive diagnostic messages, without needing to focus on the underlying packet splitting, flow control, and hardware transmission/reception operations of the diagnostic protocol.

Please note that using these two functions requires providing a configuration structure of type BM_IsotpConfigTypeDef, which is used to indicate the various parameters of the current diagnostic session:

typedef struct {
uint8_t version; /**< Currently must be set to 0x01 */
uint8_t mode;
/**< See BM_IsotpModeTypeDef for details, Default mode is normal (non-extended-addressing) UDS
* client(tester) */
struct {
uint16_t a; /**< A timeout in milliseconds: =N_As if writing as tester or reading as ECU,
otherwise =N_Ar */
uint16_t b; /**< B timeout in milliseconds: =N_Bs if writing as tester or reading as ECU,
otherwise =N_Br */
uint16_t c; /**< C timeout in milliseconds: =N_Cs if writing as tester or reading as ECU,
otherwise =N_Cr */
} testerTimeout;
struct {
uint16_t a; /**< A timeout in milliseconds: =N_Ar if writing as tester or reading as ECU,
otherwise =N_As */
uint16_t b; /**< B timeout in milliseconds: =N_Br if writing as tester or reading as ECU,
otherwise =N_Bs */
uint16_t c; /**< C timeout in milliseconds: =N_Cr if writing as tester or reading as ECU,
otherwise =N_Cs */
} ecuTimeout;
struct {
uint8_t stmin;
/**< STmin raw value (0x00-0x7F or 0xF1-0xF9) if Busmust device is acting as UDS server. Set
* as 0 if acting as UDS client(normal case). */
uint8_t blockSize;
/**< Blocksize if can card is acting as UDS server, 0 means no further FC is needed. Set as
* 0 if acting as UDS client(normal case). */
uint8_t fcFrameLength; /**< Flow control frame length in bytes */
uint8_t hardwareIsotpDisabled;
/**< Disable BM_WriteIsotp to use 3rd generation's Hardware ISOTP support. CAUTION!!!
* 0=ENABLED (by default), 1=DISABLED */
} flowcontrol;
uint8_t extendedAddress; /**< UDS Address in Extended Addressing mode */
uint8_t paddingEnabled; /**< Enable padding for unused payload bytes */
uint8_t paddingValue; /**< Padding byte value (i.e. 0xCC) for unused payload bytes */
uint8_t longPduEnabled;
/**< Enable long PDU (only if CAN message DLC>8 and (CAN_DL>8 or FF_DL>4095)), otherwise
* BM_WriteIsotp returns an error on long write request */
uint8_t functionalAddressingEnabled;
/**< Enable BM_ReadIsotp() to handle functional addressing UDS requests, currently only 0x7DF is
* supported */
uint8_t padding[1];
BM_IsotpCallbackHandle callbackFunc;
/**< Callback function when any progress is made, used typically by GUI to show progress bar */
uintptr_t callbackUserarg;
/**< Callback userarg when any progress is made, used typically by GUI to show progress bar */
BM_DataTypeDef testerDataTemplate;
/**< All tester messages will be formatted/checked using this template, configure CAN message ID
* and IDE/FDF flags here */
BM_DataTypeDef ecuDataTemplate;
/**< All ECU messages will be formatted/checked using this template, configure CAN message ID
* and IDE/FDF flags here */
} BM_IsotpConfigTypeDef;

Please pay attention to several of the more critical members among them (for detailed definitions, please refer to BMAPI.CHM):

  • BM_IsotpConfigTypeDef.ecuDataTemplate.SID: Message ID used by the ECU role in a diagnostic session
  • BM_IsotpConfigTypeDef.testerDataTemplate.SID: Message ID used by the tester role in a diagnostic session

BM_IsotpConfigTypeDef.mode: Operating mode. When you use BMAPI to implement a tester (e.g., performing firmware upgrades), set it to BM_ISOTP_NORMAL_TESTER; when you use BMAPI to simulate an ECU, set it to BM_ISOTP_NORMAL_ECU

  • BM_IsotpConfigTypeDef.ecuTimeout.b: When you use BMAPI to implement a tester, after the tester sends an FF frame, it needs to wait for the ECU’s FC frame response. This timeout parameter specifies the timeout duration in milliseconds

When you need to perform extensive UDS communication, you can try using BM_WriteIsotp instead of basic APIs such as BM_Write and BM_WriteMultiple to simplify program design (no need for manual flow control) while achieving better transmission performance.

It is worth mentioning that the firmware of the BUSMUST third-generation analyzer features a built-in ISOTP (ISO15765) protocol flow control mechanism. Upon receiving a Flow Control (FC) frame from the peer, it can immediately initiate the transmission of Consecutive Frame (CF) data with an extremely short delay (at the microsecond level), and back-to-back transmission can be achieved between multiple consecutive frames. This significantly shortens the idle waiting time during ISOTP transmission. Furthermore, the CAN card firmware guarantees “hard real-time”, thereby avoiding sporadic timeout errors caused by random stuttering in operating systems like Windows. If you need to disable this feature, you can set BM_IsotpConfigTypeDef.flowcontrol.hardwareIsotpDisabled to true.

The pseudocode for performing UDS diagnostic operations using the ISOTP API is as follows:

/* Prepare UDS request and response buffers, note that there is no need to add ISOTP flow control headers to these buffers */
uint8_t request[] = {0x2e, 0x01, 0x80, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66};
uint8_t response[4096];
uint32_t len = sizeof(response);
/* Prepare ISOTP configuration */
BM_IsotpConfigTypeDef isotp = {0};
isotp.version = 1;
isotp.mode = BM_ISOTP_NORMAL_TESTER; /* Acting as a UDS Tester to download data to ECU */
BM_INIT_CAN_FD_DATA(isotp.testerDataTemplate, TESTER_MSG_ID, 8 /*dlc*/, 0 /*ide*/, 0 /*fdf*/,
0 /*brs*/, 0, 0, NULL);
BM_INIT_CAN_FD_DATA(isotp.ecuDataTemplate, ECU_MSG_ID, 8 /*dlc*/, 0 /*ide*/, 0 /*fdf*/, 0 /*brs*/,
0, 0, NULL);
isotp.paddingEnabled = 1;
isotp.paddingValue = 0xCCU;
isotp.ecuTimeout.b = 200; /* timeout for ECU to respond */
isotp.flowcontrol.hardwareIsotpDisabled = 0; /* Enable HW ISOTP (if available) */
/* Send UDS request */
BM_WriteIsotp(channel, request, sizeof(request), TX_TIMEOUT, &isotp);
/* Receive UDS response */
if (BM_ReadIsotp(channel, response, &len, RX_TIMEOUT, &isotp) == BM_ERROR_OK) {
for (int i = 0; i < len; i++)
printf("RESPONSE[%d] = %02x\n", i, response[i]);
}

In many application scenarios, it is necessary to periodically send messages of multiple IDs with strict control precision over the message time slots, such as in CANFD protocol node simulation, or LIN protocol host scheduling tables. Since the Windows/Linux major operating systems of the host computer cannot guarantee hard real-time, sporadic stuttering or timeout issues are often quite difficult to resolve.

To address this situation, the third-generation analyzer hardware from Bama Technology provides up to 64 scheduled transmission tasks. You can configure these transmission tasks via BM_SetTxTasks, which are automatically executed by the hardware to ensure a time scheduling precision within 1ms.

In BMAPI, each transmission task is defined by a BM_TxTaskTypeDef structure, the general internal layout of which is as follows:

typedef struct {
uint8_t type; /**< Type ID of the TX task, see BM_TxTaskTypeTypeDef for details. */
union {
uint8_t version; /**< Version of BM_TxTaskTypeDef, set to 1 for BMAPI1.x. */
uint8_t unused; /**< For backward-compatibility only */
};
uint8_t flags; /**< CAN message control Flags, see BM_MessageFlagsTypeDef for details. */
struct {
uint8_t length : 7; /**< Length of payload in unit given by 'lengthunit' (not DLC) */
uint8_t lengthunit
: 1; /**< Unit of length, 0=1B, 1=128B, default as 0, that is, length in bytes */
/**< Note that not payload buffer might not be 100% used,
user would need to check specific data header in payload for further information,
i.e. ((BM_CanfdDataTypeDef*)txtask.payload)->ctrl.tx.DLC */
};
uint16_t delay; /**< Delay within tx cycle, that is, offset of tx timing slot within tx cycle
(given by 'cycle' field)*/
/**< i.e. If cycle=50 and delay=10, this txtask will be executed at 10, 60, 110, 160, etc. */
uint16_t cycle; /**< ms delay between rounds */
uint16_t nrounds; /**< num of cycles, nrounds=0xFFFFU indicates INFINITE */
uint16_t nmessages; /**< messages per round, default as 1 message/cycle */
union {
uint32_t id; /**< Generic ID field, normally you would need to set specific ID structure
instead (e.g. CAN.SID). */
struct {
uint32_t SID : 11; /**< CAN Standard ID */
uint32_t EID : 18; /**< CAN Extended ID */
uint32_t SID11 : 1; /**< Reserved */
uint32_t unimplemented1 : 2; /**< Reserved */
} can;
struct {
uint8_t ID; /**< LIN message id, 0~63 */
uint8_t CHECKSUM; /**< LIN manual checksum value, only valid if (flags &
BM_LIN_MESSAGE_FLAGS_USER_CHECKSUM) != 0 */
uint16_t reserved;
} lin;
};
/* Omitted here due to length limits; each transmission task supports various test patterns, and the parameters of these test patterns can be controlled here */
/* For example, the incdata type test pattern can be used to simulate an E2E alive counter */
... pattern...
/* Currently, transmission tasks do not support full E2E functionality, especially the checksum feature, but related structure members are reserved and omitted here */
... e2e... uint8_t payload[64]; /**< Default payload data, note this is also the template
payload of the unchanged part in a volatile TX task */
} BM_TxTaskTypeDef;

Please pay attention to several key members (for detailed definitions, please refer to BMAPI.CHM):

  • BM_TxTaskTypeDef.type: The type corresponding to the current transmission task, for example:
  • BM_TXTASK_FIXED: Repeatedly sends a completely fixed ID and payload content
  • BM_TXTASK_INCDATA: Fixed message ID, but the payload content cyclically increments according to the specified bit position and width, which can be used for packet loss checking, or even to simulate an E2E alive counter
  • BM_TXTASK_INCID: Fixed message payload content, scanning a specified message ID range
  • BM_TxTaskTypeDef.id: Specifies the message ID corresponding to the current transmission task (CANFD and LIN have different id formats)
  • BM_TxTaskTypeDef.cycle: Message transmission cycle in ms. The cycle control precision is better than 1ms (hard real-time guaranteed)
  • BM_TxTaskTypeDef.delay: Message transmission time slot delay, that is, the slot delay of the current message ID within the schedule table cycle. For example, if a transmission task has cycle=50 and delay=5, the message will be activated and sent once respectively at the 5th ms, 55th ms, 105th ms, 155th ms, …, which can be used to accurately simulate LIN scheduling tables, etc.

The pseudocode for sending messages using the scheduled task API is as follows:

BM_TxTaskTypeDef txtasks[64] = {0};
for (int i = 0; i < scheduleTable.length; i++) {
txtasks[i].type = BM_TXTASK_FIXED;
txtasks[i].cycle = scheduleTable.cycle; /* Scheduling cycle / message transmission cycle */
txtasks[i].delay = scheduleTable.items[i].delay; /* Frame delay within the scheduling cycle */
txtasks[i].nrounds = 0xFFFFU; /* Infinite periodic transmission, never stops */
txtasks[i].nmessages = 1; /* Send 1 message each time the timer expires */
txtasks[i].id = scheduleTable.items[i].id;
txtasks[i].length = scheduleTable.items[i].len; /* Message payload length, in bytes */
txtasks[i].flags = 0; /* Additional options can be specified based on the protocol for CANFD or LIN, such as specifying CANFD.BRS */
memset(txtasks[i].payload, 0, sizeof(txtasks[i].payload)); /* Send message payload content as needed */
}
if (BM_SetTxTasks(channel, txtasks, scheduleTable.length) != BM_ERROR_OK)
printf("Failed to configure tx tasks.\n");
/* At this point, automatic periodic transmission of each message has already started */
Sleep(1000);
/* Stop automatic transmission by writing an all-empty task list */
BM_TxTaskTypeDef dummy[64] = {0};
BM_SetTxTasks(channel, dummy, 64);

Scheduled task transmission can achieve full-load transmission rates with extremely low CPU usage while maintaining 1ms transmission timing accuracy. It is recommended for scenarios that require periodic message transmission.

Offline (Standalone) Automatic Transmission

Section titled “Offline (Standalone) Automatic Transmission”

Standard 3rd-generation X1/X2/X4/XL series analyzers support lightweight offline configuration: small-capacity configurations such as CAN mode, baud rate, terminal resistor, and hardware scheduled transmission tasks can be saved to the device’s internal storage, enabling the device to perform basic automatic transmission without a USB cable connected.

Please note: Standard 3rd-generation devices do not have a TF card slot, do not support TF card large-file offline recording, and do not support playing back large-file logs from a TF card. 2.5-generation recorders like the X2R/X4R feature an external TF card and support complete offline functions including device configuration, large-file offline recording, large-file offline playback, and standalone operation. Do not equate the lightweight offline configuration capability of standard 3rd-generation devices with the complete TF card offline recording/playback capability of the X2R/X4R.

Typically, you need to save the CAN mode, baud rate, terminal resistor, and task table configurations after configuring the transmission tasks, for example:

BM_SaveConfig(channel,
BM_CAN_MODE | BM_CAN_BITRATE | BM_CAN_TERMINAL_RESISTOR | BM_CAN_TXTASK_TABLE);

After completing the above steps, devices that support this capability can automatically apply the configuration and enter the corresponding lightweight offline automatic transmission mode upon reboot. If customers require large-capacity offline recording or file playback, please use models that support complete offline/recording/gateway capabilities, such as the X2R/X4R or GWR.

Although the scheduled tasks mentioned earlier support test pattern control, they currently still cannot fulfill the complete E2E requirements in AUTOSAR, because E2E often requires dynamic checksums, and these checksums are frequently OEM private algorithms that cannot be conveniently pre-installed in the analyzer firmware.

To support E2E while maximizing the utilization of the hardware’s automatic transmission capabilities, BUSMUST 3rd-generation analyzer devices have added a brand-new “sequence playback” transmission function on top of the existing single-frame transmission and scheduled transmission tasks. You can create or load a message sequence in advance, download this sequence to the CAN analyzer’s sequence playback buffer, and then simply call the BM_SetReplay interface each time to start a sequence playback. Alternatively, you can configure it for cyclic playback, and the hardware will automatically and continuously loop the messages in the buffer for you. The related pseudocode is as follows:

BM_DeviceHandle device;
BM_GetDevice(channel, &device);
/* Prepare all message contents in the transmission buffer in advance */
BM_DataTypeDef msgs[MAX_TX_MSG_COUNT] = {0};
int n = MAX_TX_MSG_COUNT;
/* Prepare all message contents in the transmission buffer in advance */
prepare_tx_msg(msgs, n);
/* Temporarily switch the write target buffer to the sequence transmission buffer REPLAYQ_BUFFER */
BM_SetBuffer(device, BM_WRITE_BUFFER, BM_REPLAYQ_BUFFER);
/* Batch download the message contents to the sequence transmission buffer REPLAYQ_BUFFER, but these contents will not be immediately sent to the bus */
BM_WriteMultiple(channel, msgs, &n, TX_TIMEOUT, NULL);
/* Remember to switch back to the default mode after writing to REPLAYQ_BUFFER is complete */
BM_SetBuffer(device, BM_WRITE_BUFFER, BM_DEFAULT_BUFFER);
/* Configure and start fully automatic sequence playback */
BM_ReplayConfigTypeDef replay = {0};
replay.version = 1;
replay.mode = BM_STORAGE_ALWAYS_ON; /* Play the sequence immediately after calling BM_SetReplay */
strcpy(replay.path.format, "<RAMBUF>");
replay.channels = 0xFFFFU; /* Play messages of any channel in the sequence */
replay.direction = 0x3U; /* Play messages of any direction in the sequence */
replay.cyclic = 1; /* Set to 1 to enable cyclic playback of this sequence, otherwise play it once */
BM_SetReplay(device, &replay); /* Configure sequence playback, and immediately play the downloaded sequence once */
/* At this point, automatic periodic transmission of each message has already started */
Sleep(1000);
/* Setting the mode to DISABLED can turn off automatic sequence playback */
replay.mode = BM_STORAGE_DISABLED;
BM_SetReplay(device, &replay);

Offline (Standalone) Automatic Transmission

Section titled “Offline (Standalone) Automatic Transmission”

Standard 3rd-generation X1/X2/X4/XL series analyzers support a small-capacity sequence/buffer playback capability, but do not have a TF card slot; this should not be understood as the TF card large-file offline playback found in the X2R/X4R. The BM_REPLAYQ_BUFFER example above is primarily applicable to scenarios where a sequence is downloaded and played back while connected to a PC.

For standalone automatic transmission or playing back large-file logs from a TF card, please select models that support complete offline/recording/gateway capabilities, such as the X2R/X4R or GWR, and refer to the corresponding product manuals and SDK examples. For recording/gateway devices that support external storage offline capabilities, two adjustments are usually required based on the pseudocode above:

  • When setting the download target buffer, do not use BM_REPLAYQ_BUFFER in RAM, but use BM_REPLAYFILE_BUFFER in non-volatile memory
  • When issuing the sequence transmission configuration, do not use for the sequence buffer path name, but use 0000.BBD

Finally, after BM_SetReplay, call the following code to store the relevant configurations in non-volatile memory:

BM_SaveConfig(channel,
BM_CAN_MODE | BM_CAN_BITRATE | BM_CAN_TERMINAL_RESISTOR | BM_CAN_REPLAY_CONFIG);

After completing the above steps, recording/gateway devices that support external storage offline capabilities can automatically apply the configuration and enter the corresponding offline working mode upon reboot. Standard 3rd-generation devices can support lightweight configuration saving and small-capacity sequence/buffer capabilities, but do not support TF card large-file recording/playback.

The table below compares the various transmission methods introduced earlier. Please choose the most appropriate transmission method based on the needs of your actual scenario.

Method Development Difficulty Transmission Speed Timing Accuracy Universality Feature Highlights
Blocking Transmission Easy Low Low Supported by all series
Blocking Batch Transmission Easy High Low Supported by all series
Asynchronous Transmission Complex Extremely High Medium Supported by all series
ISOTP Transmission Moderate Extremely High N/A Supported only by 3rd-generation Supports hardware ISOTP acceleration
Scheduled Task Transmission Moderate Extremely High High 3rd-generation supports 64, first two generations support only 1 Standard 3rd-generation supports lightweight offline task saving; TF card large-file offline capability requires recorder/gateway models like X2R/X4R/GWR
Sequence Transmission Moderate Extremely High High Supported only by 3rd-generation Standard 3rd-generation supports small-capacity sequence/buffer playback; TF card large-file offline playback requires recorder/gateway models like X2R/X4R/GWR

Inside the bus analyzer, messages transmitted and received over the bus are temporarily placed in a buffer and then collectively transferred via USB. This implementation logic avoids the excessive overhead caused by the high-frequency transmission of a large number of short messages on the USB bus. However, in certain extreme cases, the minor delay caused by buffering and collectively transferring is also unacceptable. How should this be handled?

To further reduce the reception delay caused by temporarily placing messages in a buffer and waiting for collective transfer, BMAPI provides an unbuffered mode. In unbuffered mode, the message buffers inside the BMAPI and the analyzer hardware can be disabled, achieving immediate transfer upon arrival. When combined with an asynchronous notification mechanism, reception latency can be minimized to the extreme.

You can enter unbuffered mode via the following function calls:

BM_DeviceHandle device;
BM_GetDevice(channel, &device);
BM_SetBuffer(device, BM_READ_BUFFER, BM_NO_BUFFER); /* Disable the hardware-side message buffer */
BM_SetBuffer(device, BM_WRITE_BUFFER, BM_NO_BUFFER); /* Disable the host-side message buffer */

Please note:

  • For multi-channel devices, unbuffered mode takes effect on all channels simultaneously. It cannot be enabled or disabled for a specific channel alone. Therefore, please ensure that no channel is transmitting or receiving messages during the operation.
  • Unbuffered mode is designed to achieve extremely low latency, so it only supports asynchronous implementation. This means that, without other special configurations (such as NO_ACK), sending a message will return immediately, and the transmission result (including the timestamp of when it was successfully sent to the bus) will be returned to the host via BM_Read.

Additionally, although unbuffered mode reduces latency, the fast transmission of small messages leads to excessive overhead, which in turn reduces overall throughput. Therefore, after you no longer need to perform operations that require extremely low latency, it is recommended to execute the following pseudocode to exit unbuffered mode:

BM_SetBuffer(device, BM_READ_BUFFER, BM_DEFAULT_BUFFER);
BM_SetBuffer(device, BM_WRITE_BUFFER, BM_DEFAULT_BUFFER);

A testing team is considering using a BUSMUST CANFD bus analyzer to simulate an ECU’s diagnostic response on the PC host side. This requires the simulator to be able to respond quickly to underlying ISOTP requests of UDS. For example, when this simulation program receives 10 14 2E 01 80 12 34 56, it needs to respond with the 30 00 00 flow control frame with minimal delay.

At this time, the following pseudocode can be used to achieve ultra-low latency transmission and reception, and to measure the actual response delay:

BM_DeviceHandle device;
BM_GetDevice(channel, &device);
BM_SetBuffer(device, BM_READ_BUFFER, BM_NO_BUFFER); /* Disable the hardware-side message buffer */
BM_SetBuffer(device, BM_WRITE_BUFFER, BM_NO_BUFFER); /* Disable the host-side message buffer */
BM_WaitForNotifications(notification, 1, RX_TIMEOUT); /* Wait for ISOTP FF */
BM_Read(channel, &ffmsg); /* Read ISOTP FF */
BM_Write(channel, &fcmsg, -1 /* Timeout value ignored*/, NULL); /* Write ISOTP FC */
BM_WaitForNotifications(notification, 1, TX_TIMEOUT); /* Read ISOTP FC Write Compete Event */
BM_Read(channel, &fcmsg); /* Read ISOTP FC Transmit Complete Event (with timestamp) */
uint64_t ffts = 0;
uint64_t fcts = 0;
BM_GetDataPtpTimestamp(channel, &ffmsg, &ffts);
BM_GetDataPtpTimestamp(channel, &fcmsg, &fcts);
printf("%u ns elapsed from FF to FC.\n", (uint32_t)(fcts - ffts));

Using a baud rate configuration of 500kbps, the above tests were conducted using unbuffered mode and the default buffered mode respectively. The typical latency values are 300us and 1.2ms respectively, demonstrating a significant performance improvement.

The entire BUSMUST series of analyzers supports a 32-bit hardware timestamp in microseconds, which can be used to precisely analyze relative timing issues of messages, such as the time interval between two messages, the message cycle of a specific ID, etc. Whether for sent or received messages, you simply need to obtain the BM_DataTypeDef.timestamp corresponding to the message after it has been successfully sent or received. The 32-bit hardware timestamp is a basic feature of the analyzer, enabled by default, requiring no additional configuration, and will not be mentioned again later.

In addition, the BUSMUST third-generation analyzers also support PTP (Precision Time Protocol) time synchronization, enabling time synchronization across analyzer devices! After PTP time synchronization, each sent and received message can obtain a hardware-automatically-recorded 64-bit non-wrapping PTP nanosecond timestamp, which can be used in scenarios with high requirements for message timestamp accuracy.

The advantage of the PTP timestamp over the device’s local timestamp is that it contains absolute year, month, day, hour, minute, second, and nanosecond information. Non-wrapping means that within the foreseeable lifespan of the device, this timestamp will not encounter wrapping or resetting to zero issues.

The PTP feature is disabled by default. If you need to enable PTP, please call BM_SetPtpMode after opening the corresponding channel, and call BM_SyncPtpTimes after successfully opening all channels to synchronize the PTP time between the host and all these channels. The pseudocode is as follows:

BM_ChannelInfoTypeDef channelinfos[MAX_CHANNEL_COUNT];
BM_ChannelHandle channels[MAX_CHANNEL_COUNT];
int n = MAX_CHANNEL_COUNT;
BM_Enumerate(channelinfos, &n);
for (int i = 0; i < n; i++) {
/* First open the channel, please refer to the initialization section earlier */
BM_OpenEx(&channels[i], ...);
/* Then enable PTP synchronization */
BM_SetPtpMode(channels[i], BM_PTP_INPUT_USB_SOF);
}
/* Finally, perform a single time synchronization for all channels together with the host, distributing the host's absolute PTP time to each channel */
if (BM_SyncPtpTimes(channels, n) != BM_ERROR_OK)
printf("Failed to sync PTP timestamps with host.\n");
/* For third-generation devices, there is no need to synchronize again after time synchronization; the hardware will automatically maintain synchronization at all times */

BMAPI provides multiple APIs for obtaining time, please use them as needed:

  • BM_GetHostPtpTime(): Obtains the real-time PTP time of the PC host, in the format of a 64-bit nanosecond value starting from 1970-1-1

BM_GetPtpTime(channel): Obtains the real-time PTP time of the analyzer device, in the format of a 64-bit nanosecond value starting from 1970-1-1

BM_GetDataPtpTime(channel): Obtains the historical PTP time at the moment the message was captured, in the format of a 64-bit nanosecond value starting from 1970-1-1 (please note that this API returned microseconds in the older BMAPI SDK 1.13.0 version; it is recommended to update the SDK to unify it to nanoseconds)

  • BM_GetTimestamp(channel): Obtains the real-time local time of the analyzer device, in the format of a 32-bit microsecond value starting from power-on

data.timestamp: Obtains the historical local timestamp at the moment the message was captured, in the format of a 32-bit microsecond value starting from power-on

For example, you can conveniently obtain the PTP time corresponding to a specific sent or received message through the following pseudocode, that is, the historical year, month, day, hour, minute, second, and nanosecond corresponding to the moment the message appeared on the bus:

uint64_t ptpns = 0;
BM_Read(channel, &data); /* First, read a sent or received message record in any way */
BM_GetDataPtpTimestamp(channel, &data, &ptpns); /* Then obtain its timestamp */
/* The following uses C runtime library functions to demonstrate timestamp formatting */
time_t s = (time_t)(ptpns / 1000000000ULL);
uint32_t ns = (uint32_t)(ptpns % 1000000000ULL);
struct tm *t = localtime(&s);
printf("Data PTP timestamp: %04d-%02d-%02d %02d:%02d:%02d.%09u",
t->tm_year + 1900, // Year (e.g., 2025)
t->tm_mon + 1, // Month (1-12)
t->tm_mday, // Day (1-31)
t->tm_hour, // Hour (0-23)
t->tm_min, // Minute (0-59)
t->tm_sec, // Second (0-59)
ns // Nanosecond
);

Please note that because the time spent capturing a message and transmitting it via USB is not zero, the timestamp obtained by calling BM_GetPtpTime whether before or after capturing the message will have a slight deviation from the BM_GetDataPtpTime value. This is a normal phenomenon. If you need to verify PTP synchronization accuracy, please connect multiple channels or multiple devices to the same target bus and receive the same physical message, then compare the timestamps corresponding to that message received by these channels or devices.

Many enterprise-level application scenarios require unattended operation, thus placing extremely high demands on the overall robustness of the solution. As a device that directly contacts the bus, a bus analyzer is inevitably susceptible to interference from various abnormal conditions on the bus site and may enter a fault state, for example:

  • Poor contact of the DB9 terminal, unexpected power loss of the peer ECU device, etc., may cause a channel currently sending CANFD messages to enter a BUSOFF-like state stipulated by the CANFD specification
  • Loose USB cables, insufficient power supply from the host USB port, etc., may cause the analyzer’s USB communication to disconnect, or even cause the device to disappear from the computer
  • Although extensively validated, the analyzer firmware or the BMAPI library, as software programs, are not immune to omissions, and it cannot be ruled out that some software defects may cause anomalies such as interrupted transmission and reception
  • During secondary development, some incorrect programming patterns and API calling sequences can also cause BMAPI runtime errors, such as common uninitialized or null handle errors

To improve the stability of your application and ensure that the above errors can be detected in a timely manner and recovery from these errors can be achieved as much as possible, it is recommended that you incorporate fault detection and automatic recovery logic into your production applications. This way, in the event that the system enters an abnormal state due to some unexpected reason, it can quickly recover from the fault, avoiding the need for manual intervention to restore service after prolonged outages.

It is recommended that you integrate the following recoverFromError example code into your project, triggered by the following two conditions:

  • Periodically execute the recoverFromError function to scan whether your system has encountered a fault; in this case, pass -1 for port to represent a full scan
  • When write APIs such as BM_Write return an error, it indicates that the system has already encountered a serious problem, and you should immediately call recoverFromError to attempt to forcefully fix the problem; in this case, pass the faulty channel number for port to represent forcefully fixing only that channel
void recoverFromError(int port /* = -1*/) {
#ifdef QT_VERSION_MAJOR
#define DEMO_PRINT qWarning
#elif defined(_WIN32)
#define DEMO_PRINT OutputDebugString
#else
#define DEMO_PRINT printf
#endif
// Note: It takes some time to run BM_Close and BM_OpenEx,
// and channel handles will be invalidated when recovering from error,
// make sure no other threads are using channel handles (e.g. calling BM_Write) during the
// recovery, or, use single-threaded design, just like this demo.
for (int i = 0; i < openedChannelCount; i++) {
uint64_t currentTs = BM_GetHostPtpTime();
uint64_t elapsed = currentTs - channelRecoveryConfigs[i].previousRecoveryTs;
if (elapsed >= 1000000000ULL) {
BM_CanStatusInfoTypeDef canStatus;
BM_StatusTypeDef status = BM_GetStatus(channels[i], &canStatus);
if (status & BM_ERROR_INITIALIZE) {
// BM_Init() is not called yet.
// Read our SDK documentation for details.
DEMO_PRINT("BM_ERROR_INITIALIZE\n");
} else if (status & BM_ERROR_ILLPARAMVAL) {
// Channel handle is invalid.
// Maybe it's not opened yet (using BM_OpenEx) or already closed?
DEMO_PRINT("BM_ERROR_ILLPARAMVAL\n");
BM_OpenEx(&channels[i], &channelRecoveryConfigs[i].info,
channelRecoveryConfigs[i].mode, channelRecoveryConfigs[i].tres,
&channelRecoveryConfigs[i].bitrate,
&channelRecoveryConfigs[i].rxfilters[0], 1);
channelRecoveryConfigs[i].previousRecoveryTs = currentTs;
} else if (status & BM_ERROR_ILLOPERATION) {
// USB Device operation failed.
// Maybe the device is unplugged from host PC?
DEMO_PRINT("BM_ERROR_ILLOPERATION\n");
// Close all channels in the same device.
uint64_t recoveredChannelMask = 0;
for (int j = i; j < openedChannelCount; j++) {
BM_ChannelInfoTypeDef siblingInfo;
BM_GetChannelInfo(channels[j], &siblingInfo);
if (memcmp(channelRecoveryConfigs[i].info.sn, siblingInfo.sn,
sizeof(siblingInfo.sn)) == 0 &&
memcmp(channelRecoveryConfigs[i].info.uid, siblingInfo.uid,
sizeof(siblingInfo.uid)) == 0) {
BM_Close(channels[j]);
channels[j] = NULL;
recoveredChannelMask |= 1ULL << j;
}
}
// Try reset device and remove device from opened device list kept by bmapi.
// BM_ResetDevice(channels[i]);
for (int j = i; j < openedChannelCount; j++) {
if (recoveredChannelMask & (1ULL << j)) {
BM_OpenEx(&channels[j], &channelRecoveryConfigs[j].info,
channelRecoveryConfigs[j].mode, channelRecoveryConfigs[j].tres,
&channelRecoveryConfigs[j].bitrate,
&channelRecoveryConfigs[j].rxfilters[0], 1);
channelRecoveryConfigs[j].previousRecoveryTs = currentTs;
}
}
} else if ((status & BM_ERROR_BUSOFF) || (status & BM_ERROR_BUSPASSIVE) ||
(status == BM_ERROR_OK && (canStatus.TXBO || canStatus.TXBP))) {
// BUSOFF
// Maybe the remote CAN device is disconnected,
// or you might want to check your bitrate, sample-position, tres configuration.
if (i == port) {
DEMO_PRINT("BUSOFF RECOVERY\n");
// Sometimes BUSOFF might be reported from a good channel.
// We only perform BUSOFF recovery on the channels that are reported to have tx
// problems (e.g. tx timeout).
BM_CanModeTypeDef oldmode;
BM_GetCanMode(channels[i], &oldmode);
BM_SetCanMode(channels[i], BM_CAN_INTERNAL_LOOPBACK_MODE);
static BM_CanMessageTypeDef dummyMessages[256] = {0};
uint32_t nmessages =
(uint32_t)(sizeof(dummyMessages) / sizeof(dummyMessages[0]));
for (uint32_t k = 0; k < nmessages; k++) {
dummyMessages[k].ctrl.tx.DLC = 1;
}
BM_WriteMultipleCanMessage(channels[i], dummyMessages, &nmessages, NULL, 1000,
NULL);
BM_SetCanMode(channels[i], oldmode);
BM_ClearBuffer(channels[i]);
channelRecoveryConfigs[i].previousRecoveryTs = currentTs;
}
}
}
}
}

It is worth mentioning that in the example code above, recovering from the BUSOFF state uses a relatively gentle implementation that complies with the CAN standard specification: when a BUSOFF-like error occurs, it indicates that the TEC (Transmit Error Counter) is no lower than 128. We can first set the channel to INTERNAL LOOPBACK to ensure that subsequent transmissions will definitely succeed without interfering with the bus, then rapidly send a large number of messages to let the TEC naturally decrease to 0, thereby exiting the BUSOFF state, and then return to the previous working mode.

Software development is always a complex and error-prone process. If you encounter technical issues while using BMAPI for secondary software development, you are welcome to obtain official technical support through the following channels:

  • Send a private message via our official WeChat account. If the issue is not resolved in a timely manner, you can also click the “Consult Customer Service” button that pops up after leaving your message to directly communicate 1-on-1 in real-time with our enterprise customer service.
  • If your issue includes logs or source code packages, please send an email to support@busmust.com and clearly describe the problem symptoms.
  • For general basic technical questions regarding product models and feature support, you can also consult the Wangwang customer service of our Taobao flagship store.