This is the last post in a 3-part series about CANlib development:
In our last video we showed how to create a console application that could configure and read messages from the CAN bus using basic API calls available in our CANlib SDK. In this video, we will focus on how to make your read loop event driven to be more efficient.
In the previous examples we used a Windows C# console application. Since we want to show the event driven loop in a multiple thread application, this example will use a Windows Presentation Foundation program where the main thread handles the graphic user interface (GUI) and a background thread reads the data from the CAN bus.
We have a simple graphic user interface with one button to start the background thread and one button to stop the background thread. We’ve also added a text box to display the received messages. We’ve designed this GUI using XAML.
We will use a .NET Background Worker thread to perform all of the CAN bus manipulation. This means that we must include the System ComponentModel namespace and create the background worker when the main window is created. All that is left for the GUI to do is to start the background worker when the Bus On button is pressed, stop the background worker when the Bus Off button is pressed, and display the string reported from the background thread.
Our background worker will execute the ReceiveMessageLoop function to retrieve data from the CAN bus. Like in our previous examples, we need to include canlibCLSNET namespace and setup a handle to the Kvaser device. We have placed the handle setup calls in the InitializeChannel function. We initialize the library, get a handle, set the bit rate and make the channel active on the bus. We have also added a call to canIoCtl to get a Windows event handle that will trigger when something happens in the driver.That something could be a CAN bus message received, message transmitted, or a change in the CAN controller state. Regardless of why the event triggered, you must empty the receive buffer or you will not get another event trigger. You will notice that the pointer returned by canIoCtl must be placed inside a Class derived from the WaitHandle class. To use the WaitHandle class we must include the System.Threading namespace.
We now have a properly initialized channel handle and an event handle, so back in the ReceiveMessageLoop we loop until the GUI requests the background worker to stop. Inside the loop we wait up to 1 second for an event to occur using the WaitAny method. If no event occurs, we loop again. If the CAN event does trigger, we loop reading messages with the canRead call until the function indicates the receive buffer is empty with a canERR_NOMSG status. For each message processed, we pass the message information back to the GUI with the ReportProgress method built into the BackgroundWorker class.
If the GUI requests the background worker to stop, we fall out of the while loop to the ReleaseChannel function which handles our standard cleanup by making the channel inactive and closing the handle.
Now the application is still responsive while using less CPU resources since we are only checking for messages and updating the display when a CAN event or user event occurs. Keep in mind when using multiple threads, that the handle returned by the canOpenChannel call is not thread safe. So each thread must claim their own handle to the physical channel with the canOpenChannel function.