mcp2515带spi的can驱动移植总结

作者:杨渊明
最近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需要连接的是VDD,CS,CLK,SI,SO,INT,RESET,VDD5.0几个pin。其中CS,CLK,SI,SO是SPI总线的常用的接口,是LZ比较熟悉的。其中SI连接MOSI控制气的发送口,SO连接MISO控制气的接收口。其他几个pin也是很容易明白的。INT中断pin,reset复位pin。在此LZ遗漏了一个严重的问题,导致后来驱动移植一直没有反应。就是LZ忽略了datasheet中的电源信息:
-工作电压范围2.7V至5.5V
- 5mA典型工作电流
Mcp2515的工作电压是2.7V至5V,这是适应了采用3.3V工作电压的CPU。但是LZ的4412的CPU采用的是1.8V。所以这里需要将上边的IOpin用1.8V转3.3V的电压转换IC进行转换。LZ后来在驱动调试中多次查问题无果后,重新仔细阅读了datasheet后才发现此问题。所以楼主在CPU与CAN设备之间添加了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");
         prnt =clk_get(spi_mcp251x_dev, "mout_mpll_user");
         if (IS_ERR(prnt))
                   dev_err(spi_mcp251x_dev,"failed to get prnt");
         if(clk_set_parent(sclk, prnt))
                   printk(KERN_ERR"Unable to set parent %s of clock %s.",
                                     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

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总线就两条连线,CANH和CANL,通常电压值为CAN_H = 3.5V 和CAN_L= 1.5V。在连接好调试板和我们的控制设备后,开始调试。由于LZ对CAN完全是空白。所以LZ只能广泛查阅网上资料。感谢网友们的积极贡献文档。LZ收获颇丰,不仅查到了测试工具以及相当多的介绍文档,还得到了一份上层的APK代码(这是老大帮忙弄到的)。于是LZ开整。
首先,测试socket CAN需要两个测试工具。iproute2和canutils。Iproute这个工具在我们的代码里边已经有一份。然后canutils在我得到的APK源码里边也有。下边是两个工具的下载地址:
下载iproute2的最新源码 http://www.kernel/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,修改Makefile第33行。
33 #CC = gcc
34 CC = arm-none-linux-gnueabi-gcc 
(2)     因为我们只需要iprout2的ip命令,所以修改Makefile的第42行。
42 #SUBDIRS=lib ip tc bridge misc netem genl man
43 SUBDIRS=lib ip 
修改完成执行make命令,生成ip命令。但是LZ发现还是不支持CAN类型。LZ就郁闷了。对照版本库里边,是一样的效果。但是代码中明明有CAN相关的代码,没有调用到嘛! LZ此时错误的放弃了这个工具。然后LZ把apk中的canutils相关代码移到系统中编译。生成了cansend和candump,两个文件。参考命令为:
              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文件中。Cansend和candump命令也终于调用到了驱动里边去了。但是却只有第一次调用到,后边就中途退出了。楼主遇到了一个likely和ulikely的问题。关于这个问题,LZ查了一些资料和解释,费了一些时间理解,才明白了它的功能。程序跑到协议层自动退出,说明上层调用或设置了错误的参数。lZ还没大胆到去怀疑协议层出来问题。所以,翻出了对方的板子的单片机的代码来阅读了一番,查阅相关信息。发现,对方CAN的传输速率为125000。于是配置了CAN bus的速率为125000。命令如下:
iplink set can0 up type can bitrate 1250000
于是,中断跑出来了,说明和设备端沟通上了,通信成功。此时应该开始测试收发数据了。candump命令比较简单,只要收数据就好了,所以楼主在测试candump的时候成功接收到了数据。但是cansend命令参照网友的介绍进行使用就没有成功。LZ查阅发送的另一端设备代码,并参考cansend help。发现,cansend的一个参数可能要添加。就是设备ID,cansend的i选项。接收端的设备ID为8。于是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进行编译。
       我在学习示例代码后理解,要调用到驱动层。上层代码可以分层四个部分。Package,service,jni,lib库。Package和service之间用一个aidl文件连接。而jni是service和库之间的连接。Package层主要做了两个按键和一个textView文本显示框。采用后台进程的方式candump数据并显示到textView中。后台进程用的是AsyncTask方式。其中碰到了好多问题,都是由于对JAVA太不熟悉的缘故,常常用C的方式理解。然后是service,由于功能非常简单,所以都没什么操作。最主要就是把函数接口调用下去就行。它的调用是在frameworks/base/services/java/com/android/server/SystemServer.java中添加为默认启动的服务,则可以在开机后默认启动此服务,代码如下。
//yym add 20130329 str       line:481         
                   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
第三个部分是jni。JNINativeMethod方式。系统的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层里边了。这里也是主要功能处理的地方。从上边我用终端命令IP,cansend,candump进行通信的过程来看,过程如下:
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_init,can_native_dump,can_native_send三个函数中。支持从上到下的功能基本调通。测试的apk基本写成。功能完善还要到后边确定这个case可以开始时再进行。
       LZ此次移植,波折还是有那么几个,但也都终于跳出来了。发现,之所以有那么多问题和波折,主要是对架构不是很理解,思路不清晰。所以很容易在出问题的时候乱了阵脚。其次,细心的查阅资料,从中获取需要的信息也是非常重要的。看文档不仔细的后果那是,哎,绕了好多圈啊,说多了都是泪啊!不过终于完成了!吼吼。俺要做下个任务去了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值