本文主要介绍在英创Linux工控主板(ESM8000、ESM7000和ESM6800H)上,采用Python和C#编程,实现对CAN总线接口的访问。Python和C#实例,均采用Visual Studio Code作为基本的编程工具,其代码可在主板 + ESMARC评估底板上运行,其基本的硬件环境采用采用ESM8000工控主板 + 评估底板构成,如下图所示:
在Linux平台,CAN总线接口设备已纳入网络的socket架构。英创的Linux主板有两路CAN接口,网络名称为”can0”和”can1”。在本文后面的实例代码中使用can0接口,评估底板上有相应的CAN驱动电路,与开发主机的CAN适配器连接,就可进行测试了。
可选择任何一款CAN调试模块,如PCAN,作为CAN总线测试的对端,通过其APP可看到CAN数据帧的收发情况。
Python应用实例
Python支持can接口操作库文件Python-CAN已经安装在ESM8000板卡文件系统中,调用import can 即可调用API函数对于can接口进行读写操作。对于socketcan的操作模式,一般需要调用ip 命令来激活can接口,并设置相应的波特率。
#>ip link set can0 type can bitrate 2500000
#>ip link set can0 up
Python CAN实例代码如下:
import can
import os
print( "Step9_CAN v1.0\n" )
# ip can0 up
os.system( "ifconfig can0 down" )
os.system( "ip link set can0 type can bitrate 250000" )
os.system( "ifconfig can0 up" )
print( "ifconfig can0 up" )
bus = can.interface.Bus(bustype='socketcan', channel='can0', bitrate=250000)
#send one message first
msg = can.Message(arbitration_id=0x80, data=[0, 20, 8, 1, 3, 1, 4, 1], is_extended_id=False)
bus.send(msg)
while True:
msg = bus.recv()
if msg:
str="rcv: "+msg.data
print(str)
#send message back
try:
msg:arbitration_id=0x80
bus.send(msg)
print("Message sent on {}".format(bus.channel_info))
except can.CanError:
print("Message NOT sent")
C#应用实例
C#的网络通讯功能需要System.Net.Sockets类库,.NET Core已包含该库,也认可从NuGet下载最新版本的库。在NuGet上的CAN总线类库已经很久未更新,可能与新近的.NET 5有兼容性问题,故选择了一个第三方的开源案例:
GitHub - jormenjanssen/netcore-can-example: SocketCan example on dotnet-core。实例通过C#的InteropServices机制,直接调用Linux socket相关函数,并不依赖于libsocketcan库。
运行程序前,需要通过Linux命令ifconfig,让can0接口up。
鉴于本程序代码较多(包含6个C#文件),仅例出main函数。感兴趣的客户可与英创技术部门联系,获取完整代码。
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Riwo.Rimote.VirtualCan;
using Riwo.Rimote.VirtualCan.Linux;
namespace Step9_Can
{
class Program
{
//static void Main(string[] args)
static async Task Main(string[] args)
{
Console.WriteLine("Step9_CAN V1.0");
// This is an example application for .Net Core 3+ to support SocketCAN with CAN 2.0B.
// For CAN-FD (Flexible data rate) some small changes are required which are not included in this example.
// To run this example make sure your CANBus is up and running (for testing i used network interface can0 (FlexCan interface off) an NXP Imx6 processor for testing but X86/X64/ARM64 should definitely work)
// You also could use VCAN software emulation see: https://elinux.org/Bringing_CAN_interface_up#Virtual_Interfaces
// For testing i recommend using the linux can-utils (candump, cansend, etc.), they should be available in modern distro's otherwise: https://github.com/linux-can/can-utils
// I tested this application with .Net Core 3 Preview 7 on a Poky (Thud) based Yocto image on an Toradex Colibri IMX6 DL and Colibri Imx6ULL SOM
// First configure the bitrate (I used 250K for testing): "ip link set can0 type can bitrate 250000"
// Then set the canbus up: "ip link set can0 up"
// Last but not least verify it's up and running by: "ip link show can0" this gives me the following result on my test system:
// 3: can0: <NOARP,UP,LOWER_UP,ECHO> mtu 16 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 10 link / ca
await SimpleLoopBackAsync("can0", CancellationToken.None).ConfigureAwait(false);
}
static async Task SimpleLoopBackAsync(string adapter, CancellationToken cancellationToken)
{
var factory = new SocketCanFactory();
var incomingBuffer = new byte[CanFrame.FrameLength];
using var socket = factory.CreateSocket(adapter);
{
Console.WriteLine($"Created adapter: {adapter}");
while (true)
{
await socket.ReceiveAsync(incomingBuffer, SocketFlags.None, CancellationToken.None).ConfigureAwait(false);
var incomingFrame = new CanFrame(incomingBuffer);
Console.WriteLine($"Received {nameof(CanFrame)} Id: {incomingFrame.Id} IsRtr: {incomingFrame.IsRemoteTransmissionRequest} Length: {incomingFrame.DataLength} IsError: {incomingFrame.IsErrorMessage} Data: {ByteArrayToString(incomingFrame.Data)}");
try
{
var sendFrame = new CanFrame {IsExtendedFrame = true, Id = incomingFrame.Id - 1, DataLength = 4};
sendFrame.Data[0] = 1;
sendFrame.Data[1] = 2;
sendFrame.Data[2] = 3;
sendFrame.Data[3] = 4;
await socket.SendAsync(sendFrame.FrameBytes, SocketFlags.None).ConfigureAwait(false);
}
catch (SocketException se) when (se.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
{
// Default the buffer space is only 10 message you can extend this on Linux or wait a while before retransmitting.
// RUN: "ip link set can0 txqueuelen 1000" to increase size to 1000 messages where can0 is the name of the SocketCAN interface in Linux
Console.WriteLine("No buffer space available waiting a few ms");
await Task.Delay(TimeSpan.FromMilliseconds(10), cancellationToken).ConfigureAwait(false);
}
}
}
}
private static string ByteArrayToString(Span<byte> ba)
{
var hex = new StringBuilder(ba.Length * 2);
foreach (var b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
}
}