CAN接口COM组件在WinCE平台上的实现

 2009-7-15              
        控制器局域网(CAN)是一个串行、异步、多主的通信协议,它以其高性能、高可靠性以及灵活的设计受到人们的重视,应用越来越广泛。英创公司的ARM9工控主板如EM9161,EM9260等均支持CAN总线接口,并实现了基于Windows CE规范的流式驱动程序(Stream Device Driver),对于大多数基于C/C++开发环境的应用,英创公司以静态库can_api.lib形式提供了相应接口函数,客户通过相应的API函数即可完成对CAN接口的操作。但对于使用像C#这样高级语言的应用,由于一般不支持静态库的调用,因此需要新的方法来解决这个问题。我们目前采用的基本方法是在该静态库的基础上,针对CAN通讯接口的数据收发以及对出错情况监测的应用,封装一个基于CAN通讯接口应用的COM组件,该组件中提供了一套更为简洁的接口方法函数,可以方便多种开发工具的调用,如:C#、VB、LabView等。使用时客户只需在系统中一次性注册该组件,并在应用程序中引用对应的DLL或TLB文件,就能方便使用其相应的接口方法函数了。

        本文介绍的基于英创工控主板CAN通讯接口的COM组件提供五个接口方法函数:打开CAN接口,关闭CAN接口,向CAN接口写数据,从CAN接口读取数据以及读取接口错误代码。客户方调用COM组件打开CAN接口后,COM组件服务器便在组件内部创建两个线程,一个用于接收CAN接口数据,一个用于CAN通讯错误处理。数据接收线程里通过WaitForSingleObject来等待CAN接口的接收事件发生,当CAN接口收到数据后,将数据放入指定的接收数据缓存中,客户通过调用读数据函数,将数据从缓存中读出。后面会对各函数做详细的说明。

1、数组作为组件参数

        作为通信类组件,数组常常作为接口方法函数参数传递,以交换数据,COM 组件是运行在分布式环境中的,对于一个组件程序(DLL或EXE),使用者可能是在本机的某个进程内加载组件(INPROC_SERVER),也可能是从另一个进程中调用组件的进程(LOCAL_SERVER),也可能是在这台计算机上调用远程计算机上的组件(REMOTE_SERVER),同时,组件也可能是跨语言调用的,因此对于将数组作为组件参数传递时与我们常用的参数传递方法有较大区别。

        在C语言中,通常用数组名作为函数参数。如下例程,求10次得分的总和。

      int Sum( int array[10] )
      {
            int i, sum;
            for( sum =0, i = 0;i<10; i++ )
            sum += array[i];
            return( sum );
      }

      main( )
      {
            int sum;
            int score[10] = {75,80,81,76,75,86,90,77,80,81};
            sum = Sum( score );
      }

        例中array是主调函数中的实参数组名,在被调函数中声明了形参数组,且大小为10,但实现上,在形参中指定数组大小不起任何作用,因为数组名作为实参传递时,仅是把实参数组的起始地址传递给形参数据,所以被调函数也可以声明成如下形式:Sum( int array[ ] )或Sum( int *array )。

        在COM组件中,缺省情况下,指针参数总是被假设为指向单个实例的指针,而不是数组,在参数中传递一个数据最简单的方法是使用固定数组技术,也就是在参数中指名数组维数信息。假设Sum为一组件接口方法函数,则其接口就应该描述为:HRESULT Sum( [in] int array[10] )。然而,当你自己用C++写一个COM组件程序(dll形式),然后再用C++来本地调用(准确的说是套间内调用)这个组件是,也可以将接口方法描述为:HRESULT Sum( [in] int array[] )或HRESULT Sum( [in] int *array),并且程序也能得到正常的运行结果,但这样的接口方法描述存在两个问题:1、当用C#引用组件的DLL或TLB时,array会被认为是个指针,而在C#中声明的数组,数组名不能作为实参传递。2、当这个方法函数是跨套间调用时,接口代理仅会将array[0]的值解析出来,通过COM的RPC通信协议将数据发送到COM服务器存根,此时在接口方法内再操作其它array元素将出错。所以正确的接口描述应该是HRESULT Sum( [in] int array[10] ),接口代理总是在OPRC请求消息中分配10*sizeof(int)个字节空间,将后把10个元素一起拷贝到消息中,一旦服务器收到些ORPC请求消息,接口存根直接用接收到的缓冲区作为函数的参数。

        固定数组是最简单,最紧凑,执行效率最高的数据表示形式,除了固定数组外,还可以采用可变数组、开放数组、安全数组等作为组件方法函数参数来传递一个数组,在本例中,由于CAN协议规定了一帧数据最多不会超过13个字节长度,因此采用13个字节的固定数组是一个较好的选择。对于其它类型数组请参考相应资料。

2、CAN组件接口方法函数

(1)StartCAN( /*[in]*/ UINT canNo, /*[in]*/ UCHAR baud, /*[in]*/ BYTE acceptanceFilter[9],
                         /*[in]*/ BYTE size, /*[out,retval]*/ BOOL *pBool )

      功能描述:打开指定CAN接口。

      参数说明:
      /*[in]*/ UINT canNo  设置要打开的CAN接口号
      canNo =1 :主板上带的CAN接口
                 =2 :通过ISA总线扩展的CAN接口
      /*[in]*/ UCHAR baud 设置通讯波特率
      baud  = 0 :10Kbps        1 :20Kbps       2 :50bps
                   3 :100bps        4 :125Kbps      5 :250Kbps
                   6 :500bps        7:1Mbps
      /*[in]*/BYTE acceptanceFilter[9] 根据通讯报文格式定义过滤器的配置,定义为9个字节的过滤器,其中前4个字节用于定义过滤器的接收码,后4个字节用于定义过滤器的接收屏蔽码,最后一个字节用于定义选择单/双滤波模式。
      acceptanceFilter[8] = 0:双滤波
                                     = 1:单滤波
      /*[in]*/BYTE size定义的过滤器的大小(应该设置为9)
      /*[out,retval]*/BOOL *pBool  CAN接口打开成功/失败标志
      TRUE:CAN接口打开成功
      FALSE:CAN接口打开失败

(2)WriteCAN(/*[in]*/ BYTE buf[13], /*[in]*/ DWORD bufLen, /*[out,retval]*/ BOOL *pBool )

      功能描述:向CAN接口写数据

      参数说明:
      /*[in]*/ BYTE buf[13] 准备通过CAN接口发送的数据包
      /*[in]*/ DWORD bufLen 发送数据包长度
      /*[out,retval]*/ BOOL *pBool 数据发送成功/失败标志
      TRUE:数据发送成功
      FALSE:数据发送失败

(3)ReadCAN( /*[out]*/ BYTE buf[13], /*[out,retval]*/ BOOL *pBool )

      功能描述:从接收缓存中读取一帧CAN接口数据

      参数说明:
      /*[out]*/ BYTE buf[13] 数据接收缓存(缓存区需大于13个字节)
      /*[out,retval]*/ BOOL *pBool CAN接口是否有数据标志
      TRUE:CAN接口收到数据(返回值为TRUE时需再次调用本函数,直到返回值FLASH,以将数据全部读出)
      FALSE:CAN接口没有收到数据

(4)GetErrorCode(/*[out]*/ DWORD *ECCRegCode,/*[out]*/ DWORD errorArray[16],
                               /*[out,retval]*/ int *errorCount)

      功能描述:得到错误代码

      参数说明:
      /*[out]*/ DWORD *ECCRegCode  CAN接口中错误代码捕捉寄存器的值
      /*[out]*/ DWORD errorArray[16] 最近16次的CAN接口通讯错误编码(缓存区需大于16个字节)
      /*[out,retval]*/ int *errorCount 总错误次数

(5)StopCAN( )

      功能描述:关闭CAN通讯接口

3、CAN组件调用

        在使用COM组件之前需要注册组件,COM组件的注册过程请参考本网站相关文章或参考相应书籍,这里不再赘述。下面是在EVC中调用CAN组件接口方法函数的一些程序片段。

      //从Program ID得到Class ID 
      hr = CLSIDFromProgID( OLESTR( ' ComCAN.CoCAN ' ), &clsid );
      if( FAILED( hr ) )
      {
            return -1;
      }

      //从Class ID得到ICoSerial接口指针
      hr = CoCreateInstance( clsid, NULL, CLSCTX_INPROC_SERVER, __uuidof(ICoCAN ),
                                            ( void** )&pICoCAN );
      if( FAILED( hr ))
      {
            return -1;
      }

      //打开CAN接口
      //过滤器设置
      Filter[0] = 0;         // ACR0
      Filter[1] = 0x5f;    // ACR1
      Filter[2] = 0;         // ACR2
      Filter[3] = 0x1f;    // ACR3
      Filter[4] = 0xff;     // AMR0
      Filter[5] = 0xff;     // AMR1
      Filter[6] = 0xff;     // AMR2
      Filter[7] = 0xff;     // AMR3
      Filter[8] = DUAL_FILTER_MODE; // 设置滤波器模式
      Baud = CAN_TIMING_250K;      // 波特率:250Kbps
      CanNo = 1; 
      bResult = pICoCAN->StartCAN( CanNo, Baud, (BYTE*)Filter, 9 );
      if( !bResult )
            return -1;

      //数据接收和发送
      while( TURE )
      {
            bResult = pICoCAN->ReadCAN( (BYTE*)buf );
            if( bResult )
            {
                  dLen = buf[0]&0x0f;
                  if( buf[0]&0x80 )     // 扩展帧
                  {
                        pICoCAN->WriteCAN( (BYTE*)buf, dLen+5 );
                  }
                  else        // 标准帧
                  {
                        pICoCAN->WriteCAN( (BYTE*)buf, dLen+3 );
                  }
            }
            Sleep( 10 );
      }