mcp2515带spi的can驱动移植总结

from: http://blog.chinaunix.net/uid-25407623-id-4046632.html
mcp2515带spi的can驱动移植总结 2013-12-21 10:09:44

分类: Android平台

最近LZ接公司安排任务,移植一款CAN总线设备Mcp2515。由于在前次任务中有SPI经验,所以在接受任务是主要关注此设备采用SPI接口。所以一直没有关注CAN相关的知识,后续过程中遇到了不少麻烦,走了一些弯路。特把此次移植过程记录整理一下。

CAN总线是一种在汽车上广泛采用的总线协议,被设计作为汽车环境中的微控制器通讯。LZ理论知识有限,网上抄一句介绍的吧。如下:CAN(Controller Area Network)总线,即控制器局域网总线,是一种有效支持分布式控制或实时控制的串行通信网络。由于其高性能、高可靠性、及独特的设计和适宜的价格而广泛应用于工业现场控制、智能楼宇、医疗器械、交通工具以及传感器等领域,并已被公认为几种最有前途的现场总线之一。CAN总线规范已经被国际标准化组织制订为国际标准ISO11898,并得到了众多半导体器件厂商的支持。

我们的产品是作为CAN控制端,使用在一个电动汽车公司的汽车上的。该公司提供了单片机的终端设备进行通信调试。所以LZ这次任务还要写测试APK。这也是LZ第一次从APK到驱动的一次经验。使一次从上到下的经验,所以值得总结一下下。

此次调试是在EXYNOS4412三星四核CPU上,采用的是Android4.0.4文件系统和linux3.0.15的内核。是俺们公司的主打平台哦!吼吼!

调试开始了!

一.
电路信息

这是开发板的CAN小板图,从图上可知LZ需要连接的是VDDCSCLKSISOINTRESETVDD5.0几个pin。其中CSCLKSISOSPI总线的常用的接口,是LZ比较熟悉的。其中SI连接MOSI控制气的发送口,SO连接MISO控制气的接收口。其他几个pin也是很容易明白的。INT中断pinreset复位pin。在此LZ遗漏了一个严重的问题,导致后来驱动移植一直没有反应。就是LZ忽略了datasheet中的电源信息:

-工作电压范围2.7V5.5V

- 5mA典型工作电流

Mcp2515的工作电压是2.7V5V,这是适应了采用3.3V工作电压的CPU。但是LZ4412CPU采用的是1.8V。所以这里需要将上边的IOpin1.8V3.3V的电压转换IC进行转换。LZ后来在驱动调试中多次查问题无果后,重新仔细阅读了datasheet后才发现此问题。所以楼主在CPUCAN设备之间添加了MAX3390E转换IC进行电压转换。

二.驱动移植

Mcp2515有标准的驱动,可以从网上找到下载,linux的内核里边也有默认的驱动。所以LZ只要配置好内核,添加设备端的配置信息就好了。以下是我的配置信息:

#ifdef CONFIG_CAN_MCP251X

static struct s3c64xx_spi_csinfo spi0_mcp251x_csi[] = {            //spi总线CS片选pin配置

         [0] = {

                   .line =EXYNOS4_GPB(1),

                   .set_level= gpio_set_value,

//               .fb_delay =0x2,

         },

};

static struct spi_board_info spi_mcp251x_board_info[] __initdata ={                 //mcp251x设备信息

         {

                   .modalias  = "mcp2515",     

                   .max_speed_hz  = 6500000,                   //spi最大速率,配置为6500000

                   .bus_num  = 0,

                   .chip_select        = 0,

                   .mode                 = SPI_MODE_0,                     //采用的是SPI0

                   .controller_data= &spi0_mcp251x_csi[0],

         }

};

#endif

 

static void __init smdk4x12_machine_init(void)               //内核的机器初始部分

{

struct device *spi_mcp251x_dev = &exynos_device_spi0.dev;            //设备指针指向SPI0

……

……            

#ifdef CONFIG_CAN_MCP251X

         sclk =clk_get(spi_mcp251x_dev, "dout_spi0");                          //spi总线时钟CLK配置

         if (IS_ERR(sclk))

                   dev_err(spi_mcp251x_dev,"failed to get sclk for SPI-0\n");

         prnt =clk_get(spi_mcp251x_dev, "mout_mpll_user");

         if (IS_ERR(prnt))

                   dev_err(spi_mcp251x_dev,"failed to get prnt\n");

         if(clk_set_parent(sclk, prnt))

                   printk(KERN_ERR"Unable to set parent %s of clock %s.\n",

                                     prnt->name,sclk->name);

 

         clk_set_rate(sclk,100 * 1000 * 1000);

         clk_put(sclk);

         clk_put(prnt);

 

         if (!gpio_request(EXYNOS4_GPB(1),"SPI_CS0")) {

                   gpio_direction_output(EXYNOS4_GPB(1),0);

                   s3c_gpio_cfgpin(EXYNOS4_GPB(1),S3C_GPIO_SFN(1));

                   s3c_gpio_setpull(EXYNOS4_GPB(1),S3C_GPIO_PULL_UP);

                   exynos_spi_set_info(0,EXYNOS_SPI_SRCCLK_SCLK,

                            ARRAY_SIZE(spi0_mcp251x_csi));

         }

 

         spi_register_board_info(spi_mcp251x_board_info,ARRAY_SIZE(spi_mcp251x_board_info));        //注册SPI设备函数

#endif

……

……

}

 

static struct platform_device *smdk4x12_devices[] __initdata = {

……

……

#ifdef CONFIG_CAN_MCP251X              //注册SPI0设备

         &exynos_device_spi0,

#endif

……

……

}

                       这里需要注意的是,在设备注册中不能有于此相冲突的其它设备的注册。LZ把没有用到的原有注册都注释掉了。不过还得感谢这些注册设备给我了很好的结构参考。接下来就是menuconfig的配置了。关于这一点网上有很多介绍。LZ参考了网友的信息:

1 [*]Networking support->
2 <*>CAN bus subsystem support->
3 <*>Raw CAN Protocal
4 <*>Broadcast Manage CAN Protocal
5 CAN Device Drivers->
6 <*>Platform CAN driver with Netlink support
7 [*]CAN bit-timing calculation
8 <*>Microchip MCP251x SPI CAN controllers
9
10 Device drivers->
11 [*]SPI support ->
12<*> Samsung S3C64XX series type SPI

         完成以上配置后,编译内核,启动。

[    5.585238] CAN devicedriver interface

[    5.648409]mcp251x_power_enable power on reset

[    5.662537] mcp251xspi0.0: probed

内核显示mcp251x 模块probe成功。LZ用示波器测量CAN设备上的时钟源晶振,显示为16MHZ,再在终端敲netcfg命令(没权限的话先su一下)。显示:

lo       UP                                  127.0.0.1/8  0x0000004900:00:00:00:00:00

can0     UP                                    0.0.0.0/0  0x000000c100:00:00:00:00:00

于是LZ知道设备出来了。驱动移植至此完成。(*^__^*)嘻嘻……

三.通信测试

CAN总线就两条连线,CANHCANL通常电压值为CAN_H = 3.5V CAN_L= 1.5V。在连接好调试板和我们的控制设备后,开始调试。由于LZCAN完全是空白。所以LZ只能广泛查阅网上资料。感谢网友们的积极贡献文档。LZ收获颇丰,不仅查到了测试工具以及相当多的介绍文档,还得到了一份上层的APK代码(这是老大帮忙弄到的)。于是LZ开整。

首先,测试socket CAN需要两个测试工具。iproute2canutilsIproute这个工具在我们的代码里边已经有一份。然后canutils在我得到的APK源码里边也有。下边是两个工具的下载地址:

下载iproute2的最新源码http://www.kernel.org/pub/linux/utils/net/iproute2/

下载canutils的最新源码http://www.pengutronix.de/software/socket-can/download/canutils

另外,因为canutils编译需要libsocketcan库的支持,需要下载libsocketcan。下载libsocketcan的最新源码http://www.pengutronix.de/software/libsocketcan/download/

首先,使用ip命令。这是网上得到的命令:
ifconfig can0 down //
关闭can0,以便配置
ip link set can0 up type can bitrate 250000 //
设置can0波特率
ip -details link show can0 //
显示can0信息

但是楼主发现,版本库里边的ip命令根本不支持can相关的命令。于是楼主参照上边的介绍,又下载了一份iproute2-2.6.39.tar.gz。参照网上编译过程:

(1)     解压iproute2-3.6.0.tar.xz,修改Makefile33行。
33 #CC = gcc
34 CC = arm-none-linux-gnueabi-gcc

(2)     因为我们只需要iprout2ip命令,所以修改Makefile的第42行。
42 #SUBDIRS=lib ip tc bridge misc netem genl man
43 SUBDIRS=lib ip

修改完成执行make命令,生成ip命令。但是LZ发现还是不支持CAN类型。LZ就郁闷了。对照版本库里边,是一样的效果。但是代码中明明有CAN相关的代码,没有调用到嘛!LZ此时错误的放弃了这个工具。然后LZapk中的canutils相关代码移到系统中编译。生成了cansendcandump,两个文件。参考命令为:

              cansend   can0 -e 0x81 0x00 0x00 0x00 0x40 0x55

candump can0

LZ发现,也没什么效果。用示波器测量CAN总线上的信号,发现对方已经不停地发送信号过来了,但是这边却还是安安静静,很害羞滴不肯回应。LZ此时有些乱了阵脚,又是查证电路问题,又是下载新的canutils-4.0.6.tar.bz2来进行编译。编译canutils-4.0.6发现少了很多头文件,一直不能编译成功。LZ这时在两头来来回回,没有收获。后老大说,还是要先从工具着手,先把ip命令不支持的问题解决。于是,LZ在仔细阅读ip相关的代码后发现,之所以IP命令不支持CAN设备,是因为CAN相关的代码是以so库的形式调用的,但是代码根本没有编译成库,所以根本没有调用。于是问题就比较清晰了。只需要把link_can做成so动态库,并修改好ip中调用库的获取路径,就能够调用的到了,同时也学习到了怎样写编译成动态库的Android.mk编译文件的相关知识。

       于是IP命令成功调用到了驱动层的mcp251x.c文件中。Cansendcandump命令也终于调用到了驱动里边去了。但是却只有第一次调用到,后边就中途退出了。楼主遇到了一个likelyulikely的问题。关于这个问题,LZ查了一些资料和解释,费了一些时间理解,才明白了它的功能。程序跑到协议层自动退出,说明上层调用或设置了错误的参数。lZ还没大胆到去怀疑协议层出来问题。所以,翻出了对方的板子的单片机的代码来阅读了一番,查阅相关信息。发现,对方CAN的传输速率为125000。于是配置了CAN bus的速率为125000。命令如下:

iplink set can0 up type can bitrate 1250000

于是,中断跑出来了,说明和设备端沟通上了,通信成功。此时应该开始测试收发数据了。candump命令比较简单,只要收数据就好了,所以楼主在测试candump的时候成功接收到了数据。但是cansend命令参照网友的介绍进行使用就没有成功。LZ查阅发送的另一端设备代码,并参考cansend help。发现,cansend的一个参数可能要添加。就是设备IDcansendi选项。接收端的设备ID8。于是cansend命令如下:

cansend   can0 -i 8 -e 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88

于是接收端屏上反应出数据有改变,通信成功。

Cansend帮助文档如下:

shell@android :/ # cansend

Usage: cansend [] [Options]

can consist of up to 8 bytes given as a spaceseparated list

Options:

 -i, --identifier=ID    CAN Identifier (default = 1)

 -r  --rtr             send remote request

 -e  --extended send extended frame

 -f, --family=FAMILY    Protocol family (default PF_CAN = 29)

 -t, --type=TYPE        Socket type, see man 2 socket (defaultSOCK_RAW = 3)

 -p, --protocol=PROTO   CAN protocol (default CAN_RAW = 1)

 -l                     send message infinitetimes

     --loop=COUNT       send message COUNT times

 -v, --verbose          be verbose

 -h, --help             this help

     --version         print version information and exit

 

四.写测试apk

       最后就是要写测试apk了,毕竟人家用户是不可能在终端上使用的嘛。LZ手上有一份apk测试代码的示例。由于没有太多写apk的经验,也问同事要了一些参考资料。

       LZ发现,写apk有两种,一种是系统相关的apk,一种是完全独立的apk。我写的是系统相关的。因为我是在系统里边添加代码进行编译。独立apk是用ndk进行编译。

       我在学习示例代码后理解,要调用到驱动层。上层代码可以分层四个部分。Packageservicejnilib库。Packageservice之间用一个aidl文件连接。而jniservice和库之间的连接。Package层主要做了两个按键和一个textView文本显示框。采用后台进程的方式candump数据并显示到textView中。后台进程用的是AsyncTask方式。其中碰到了好多问题,都是由于对JAVA太不熟悉的缘故,常常用C的方式理解。然后是service,由于功能非常简单,所以都没什么操作。最主要就是把函数接口调用下去就行。它的调用是在frameworks/base/services/java/com/android/server/SystemServer.java中添加为默认启动的服务,则可以在开机后默认启动此服务,代码如下。

//yym add 20130329 str       line481        

                   try {

                 Slog.i(TAG,"Flexcan Service");

                ServiceManager.addService("flexcan", new FlexcanService());

            } catch(Throwable e) {

                 Slog.e(TAG,"Failure starting Flexcan Service", e);

           }

//yym add 20130329 end

第三个部分是jniJNINativeMethod方式。系统的jni需要在frameworks/base/services/jni/onload.cpp中添加注册,楼主照样添加了:

intregister_android_server_FlexcanService(JNIEnv* env);

register_android_server_FlexcanService(env);

JNI层的代码调用service层的功能时有一个专门的调用方式,LZ在使用时可能是字符敲错了。移植失败。这里记录下来。

                   jclassframe_cls = env->FindClass("com/android/server/Frame");

                   if(frame_cls== NULL) {

                            LOGE("FlexcanJNI: find class FlexcanService error!!");

                            returnNULL;

                   }                                                                                 //首先获取类的源

 

1.调用处理单个数据的函数。

                   jmethodIDsetDlc = env->GetMethodID(frame_cls,

                                     "setDlc","(I)V");                                       //映射类的方法函数过来

                   if( setDlc== NULL) {

                            LOGE("FlexcanJNI: setDlc error!!");

                            returnNULL;

                   }

                   jobjectmyFrame = frame;

                   if(myFrame==NULL) {

                            LOGE("FlexcanJNI: frame NULL error!!");

                            returnNULL;

                   }

                   env->CallVoidMethod(myFrame,setID,can_id);        //调用方法函数,并传递参数。

2.调用处理buffer多个数据的函数

                   jmethodID setBuf= env->GetMethodID(frame_cls,

                                     "setBuf","([I)V");

                   if(setBuf==NULL){

                            LOGE("FlexcanJNI: setBuf error!!");

                            returnNULL;

                   }                                                                                  //映射类的方法函数过来

 

 

                   jobjectmyFrame = frame;

                   if(myFrame==NULL) {

                            LOGE("FlexcanJNI: frame NULL error!!");

                            returnNULL;

                   }

 

                   jintArrayarr;

                   arr =env->NewIntArray(8);                                       //new一个数组

                   if(arr ==NULL) {

                            LOGE("FlexcanJNI: arr init error!!");

                            returnNULL;

                   }

                   env->SetIntArrayRegion(arr,0,8,data);                      //将数据传到数组里边去

 

                   env->CallVoidMethod(myFrame,setBuf, arr);          //调用方法函数,并传递参数。

 

                   env->DeleteLocalRef(arr);                                         //销毁数组

最后,JNI就调用到了lib层里边了。这里也是主要功能处理的地方。从上边我用终端命令IPcansendcandump进行通信的过程来看,过程如下:

1.    ip link set can0 up type canbitrate 125000

2.    ip link set can0 up type can

3.    cansend  can0 -i 8 -e 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88

4.    candump can0

所以,我只要将上述命令的处理过程从原函数代码中移植过来就可以了。但是LZ发现,设置bitrate这个步骤可以再mcp2515的驱动中更加简便的设置,由于我们的设备不需要兼容其它别的设备,bitrate还不需要更改,所以就取巧了一下,将bitrate在驱动中直接默认设置好了。免去了上层的设置。然后把其它三个功能移植分别移植到了can_initcan_native_dumpcan_native_send三个函数中。支持从上到下的功能基本调通。测试的apk基本写成。功能完善还要到后边确定这个case可以开始时再进行。

       LZ 此次移植,波折还是有那么几个,但也都终于跳出来了。发现,之所以有那么多问题和波折,主要是对架构不是很理解,思路不清晰。所以很容易在出问题的时候乱了阵脚。其次,细心的查阅资料,从中获取需要的信息也是非常重要的。看文档不仔细的后果那是,哎,绕了好多圈啊,说多了都是泪啊!不过终于完成了!吼吼。俺 要做下个任务去了。

你可能感兴趣的:(kernel)