C/C#开发基于WEC7的CAN通讯应用

 2014-9-5              

  英创EM335x工控主板,使用的Cortex-A8及WEC7操作系统。本文以EM335x工控主板光盘例程为例,简单介绍一下C和C#代码如何调用WEC7平台下CAN驱动,实现CAN通信的方法。

 

C代码

 

  客户可以在自己工程中添加例程中的EM335X_CAN.h及EM335X_CAN.cpp,使用里面封装好的EM335X_CAN类。
  #include 'EM335X_CAN.h'
  EM335X_CAN can;

 

  打开CAN
  调用EM335X_CAN类的OpenCAN方法。比如用250Kbps打开CAN1(默认只有CAN1)。
  DWORD dwCanNo = 1;
  DWORD dwBaudRate = 250000;
  can.OpenCAN( dwCanNo, dwBaud );

 

  关闭CAN
  调用EM335X_CAN类的CloseCAN方法。
  can.CloseCAN( );

 

  发CAN数据包
  调用EM335X_CAN类的WriteCAN方法。
  can.WriteCAN(&canmsg );

 

  接收CAN数据包

  例程中使用了一个独立的线程来接收。然后将收到的数据传递给PackagePro函数处理。参数一为数据buffer指针,参数二为数据的长度。
  int EM335X_CAN::PackagePro(char* pBuf , int len)

  用户可以根据应用具体需求,添加相应的逻辑代码。

 

  CAN发送接收数据包结构体定义
  不论是CAN发送,还是CAN接收,都是以数据包为单位发送接收的。单个数据包大小为16字节,结构体定义如下:
  typedef struct {
    CAN_ID id;
    BYTE dlc;
    BYTE data[8];
  }CAN_MESSAGE,*PCAN_MESSAGE;

  id,一个CAN_ID的结构体,该结构体定义在下面说明。
  dlc,1字节,数据长度,取值0-8;
  data,8字节,传输的数据。

  注:因为结构体对齐的原因,该结构体大小为16字节。

 

  CAN_ID结构体是一个整型,32位,用来记录CAN通信所需的ID信息。
  typedef struct{
    unsigned int id:29;
    unsigned int reserved:1;
    unsigned int remote:1;
    unsigned int extended:1;
  }CAN_ID;

  id,结构体整型的低29位,表示id号。
  reserved,第30位,用来标记是接收的数据包,还是发送的数据包,默认设置为0即可。
  remote,第31位,用来设置是数据帧还是远程帧。0为数据帧,1为远程帧。
  extended,第32位,用来设置是标准帧还是扩展帧。0为标准帧,1为扩展帧。

 

  CAN过滤条件Filter设置
  EM335x同样支持数据包过滤功能,设置Filter可以使得CAN只接收自己需要的数据包。

 

  调用EM335X_CAN类的SetFilter方法,可以添加一个过滤条件,或者删除一个已有的过滤条件。例如:
  bResult = can.SetFilter( &Filter, FALSE );

 

  第一个参数为过滤条件参数,为一个CAN_FILTER的结构体,在下面有说明。第二个参数如果为FALSE,则表示添加该过滤条件,如果为TRUE,则表示删除已有的该过滤条件。

 

  有多个过滤条件的情况下,只要数据包可以满足任意一个过滤条件,那么该数据包就可以被接收。

 

  CAN过滤条件Filter结构体定义
  CAN_FILTER结构体定义如下:(CAN_ID结构体的定义前面数据包结构体里有说明)
  typedef struct {
    CAN_ID id;
    CAN_ID mask;
  }CAN_FILTER,*PCAN_FILTER;

 

  这里的过滤逻辑如下:


  假设收到的数据包里的id,我们记为id_message,与过滤条件中的filter参数里的id和mask满足条件:(id_message&mask) == (id&mask),那么该数据包就可以接收,也就是说,mask表示需要进行对比的位,如果数据包的id这几位与filter设置里的id的这几位相同,那么该数据包就可以接收。

 

  比如:
  一个filter的mask = 0x03,即2进制的b0000 0011,即需要比较最后的两位。
  filter的id = 0x02,即2进制的b0000 0010。
  那么数据包id如果最后两位为 10,该数据包就可以通过过滤条件被接收。
  数据包id = 0xF7,即2进制b1111 0111,无法接收。
  数据包id = 0xE6,即2进制b1110 0110,可以接收。
  数据包id = 0x2E,即2进制b0010 1110,可以接收。

 

  CAN环回模式设置
  环回模式为,可以选择板子自己发送的数据包,是否自己也能同时接收到。

  调用EM335X_CAN类的CAN_Loopback方法,如果希望自己发送的CAN包,自己也能接收到,那么设置第二个参数为TRUE。如果希望关闭环回功能,那么第二个参数设置为FALSE。

 

  CAN其它命令
  EM335X_CAN类的CanCommand方法可以控制CAN复位,启动和停止。
  BOOL CanCommand( CAN_COMMAND eCommand);

  参数CAN_COMMAND是一个枚举型,它的定义如下,STOP= 0,START =1,RESET=3:
  typedef enum {
    STOP,
    START,
    RESET
  } CAN_COMMAND;

 

  1、复位CAN
  CAN复位会重置CAN驱动里的各个寄存器值,并执行相关的初始化操作。

 

  在打开CAN的时候,OpenCAN函数里已经调用了该函数实现CAN复位。用户可以根据自己应用的实际情况,决定在什么时机执行CAN复位。

 

  2、启动CAN
  在设置好CAN波特率,环回,filter等参数后,CAN驱动线程并没有马上启动,需要执行CAN启动,CAN线程才开始工作。

 

  在打开CAN的时候,OpenCAN函数在设置完参数后调用CAN启动。用户可以根据自己应用的实际情况,决定在什么时机执行该函数,例如:当CAN接收线程的接收到错误事件时,可以在错误处理代码里添加停止CAN,和重新启动CAN的调用。

 

  3、停止CAN
  停止CAN会关闭CAN驱动线程,在关闭CAN的时候,CloseCAN函数调用CAN停止。

 

C#代码

 

  C#代码参考了C代码,相对C接口稍微做了调整。我们同样封装了一个CAN的类在EM335x_CAN_API.cs中,方便客户添加到自己工程中。

 

  打开CAN
  打开CAN的流程为:打开CAN设备,获得设备句柄,初始化CAN,然后设置CAN的参数(波特率,环回模式),创建CAN接收线程,最后启动CAN,然后CAN驱动线程开始工作。

 

  1、打开CAN设备
  int CanNo = 1;
  hCAN = CAN.OpenCAN(CanNo);

 

  2、重置CAN
  执行Reset操作,初始化CAN。
  bRet = CAN.CAN_Command(hCAN, (uint)CAN_COMMAND.RESET);

 

  3、设置波特率
  设置CAN的波特率,如250Kbps:
  uBaud = 250000;
  bRet = CAN.CAN_SetBaudRate(hCAN, uBaud);

 

  4、设置CAN环回模式
  如果希望自己发送的CAN包,自己也能接收到,那么可以设置第二个参数为1,例程中暂时关闭该功能,所以设置的0。
  bRet = CAN.CAN_Loopback(hCAN, 0);

 

  5、创建单独的接收线程
  因为接收时,函数需要等待CAN接收事件,为阻塞状态,不宜直接写在主线程中,这里添加一个接收线程,专门处理CAN数据接收。
创建线程:
  revThread = new Thread(new ThreadStart(BeginReceive));
  threadStop = false;
  revThread.Start(); // 启动waitforMessage线程

 

  6、启动CAN
  当准备就绪,就可以启动CAN设备了。
  bRet = CAN.CAN_Command(hCAN, (uint)CAN_COMMAND.START);

 

  关闭CAN
  主要是结束接收线程,停止CAN,及关闭CAN设备句柄等。
  revThread.Abort(); // 结束线程
  revThread.Join();
  bRet = CAN.CAN_Command(hCAN, (uint)CAN_COMMAND.STOP);
  bRet = CAN.CloseCAN(hCAN);

 

  发CAN数据包
  调用WriteFile发送CAN数据包。
  bRet = CAN.WriteFile(hCAN, ref pktSend, CAN.sizePacket, ref uLen, 0);

 

  接收CAN数据包
  调用ReadFile发送CAN数据包。
  bResult = CAN.ReadFile(hCAN, ref pktRev, CAN.sizePacket, ref uLen, 0);

 

  CAN发送接收数据包结构体定义
  不论是CAN发送WriteFile,还是CAN接收ReadFile,都是以数据包为单位发送接收的。单个数据包大小为16字节,结构体定义如下:
  [StructLayout(LayoutKind.Explicit, Size = 16)]
  public struct CAN_MESSAGE
  {
    [FieldOffset(0)]
    public uint id;
    [FieldOffset(4)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
    public byte[] data; // 数据字节
  }

 

  id是一个整型4字节,32位,用来记录CAN通信所需的ID信息。
  id的低29位,表示id号。
  id的第30位,用来标记是接收的数据包,还是发送的数据包。
  id的第31位,用来设置是数据帧还是远程帧。该位为0则是数据帧,该位为1则是远程帧。
  id的第32位,用来设置是标准帧还是扩展帧。该位为0则是标准帧,该位为1则是扩展帧。

 

  data为12字节byte数组。
  data[0]为CAN数据包内数据的长度,取值0-8;
  data[1]-data[8],8字节,为CAN数据包内传输的数据。
  data[9]-data[11]未使用。

  注:结构体这样设计的主要原因还是因为要和C代码的驱动接口,做成这样效率会高些。

 

  例如,我们要发送一个,id为5的标准数据帧,数据长度为3,分别是0x01,0x02,0x03,代码如下: 
  CAN_MESSAGE pktSend = new CAN_MESSAGE();
  pktSend.id = 5;
  // 如果是数据帧就不变,如果是远程帧就
  // pktSend.id = pktSend.id | 0x20000000; // remote
  // 如果是标准帧就不变,如果是扩展帧就
  // pktSend.id = pktSend.id | 0x40000000; // extended
  pktSend.data[0] = 3;
  pktSend.data[1] = 0x01;
  pktSend.data[2] = 0x02;
  pktSend.data[3] = 0x03;

 

  例如,我们要发送一个,id为55的扩展帧,数据长度为6,分别是0x04,0x04,0x04,0x05,0x05,0x05,代码如下:
  CAN_MESSAGE pktSend = new CAN_MESSAGE();
  pktSend.id = 5;
  // 如果是数据帧就不变,如果是远程帧就
  // pktSend.id = pktSend.id | 0x20000000; // remote
  // 如果是标准帧就不变,如果是扩展帧就
  pktSend.id = pktSend.id | 0x40000000; // extended
  pktSend.data[0] = 6;
  pktSend.data[1] = 0x04;
  pktSend.data[2] = 0x04;
  pktSend.data[3] = 0x04
  pktSend.data[4] = 0x05;
  pktSend.data[5] = 0x05;
  pktSend.data[6] = 0x05;

 

  CAN过滤条件Filter设置
  EM335x同样支持数据包过滤功能,设置Filter可以使得CAN只接收自己需要的数据包。

 

  调用EM335X_CAN类的SetFilter方法,可以添加一个过滤条件,或者删除一个已有的过滤条件。例如:
  CAN.CAN_SetFilter (hCAN, Filter, false );

 

  第一个参数为CAN句柄,第二个参数为过滤条件参数,为一个CAN_FILTER的结构体,在下面有说明。第三个参数如果为FALSE,则表示添加该过滤条件,如果为TRUE,则表示删除已有的该过滤条件。

 

  有多个过滤条件的情况下,只要数据包可以满足任意一个过滤条件,那么该数据包就可以被接收。

 

  CAN过滤条件Filter结构体定义
  CAN_FILTER结构体定义如下:
  [StructLayout(LayoutKind.Explicit, Size = 8)]
  public struct CAN_FILTER
  {
    [FieldOffset(0)]
    public uint id;
    [FieldOffset(4)]
    public uint mask;
  }

 

  这里的过滤逻辑如下:

 

  假设收到的数据包里的id,我们记为id_message,与过滤条件中的filter参数里的id和mask满足条件:(id_message&mask) == (id&mask),那么该数据包就可以接收,也就是说,mask表示需要进行对比的位,如果数据包的id这几位与filter设置里的id的这几位相同,那么该数据包就可以接收。

 

  比如:
  一个filter的mask = 0x03,即2进制的b0000 0011,即需要比较最后的两位。
  filter的id = 0x02,即2进制的b0000 0010。
  那么数据包id如果最后两位为 10,该数据包就可以通过过滤条件被接收。
  数据包id = 0xF7,即2进制b1111 0111,无法接收。
  数据包id = 0xE6,即2进制b1110 0110,可以接收。

 

  详细信息,可以电话,邮件或论坛提问方式咨询英创工程师。