mcp2515带spi的can驱动移植总结

来源:互联网 发布:java图形界面框架 编辑:程序博客网 时间:2024/05/16 04:51
作者:杨渊明
最近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此次移植,波折还是有那么几个,但也都终于跳出来了。发现,之所以有那么多问题和波折,主要是对架构不是很理解,思路不清晰。所以很容易在出问题的时候乱了阵脚。其次,细心的查阅资料,从中获取需要的信息也是非常重要的。看文档不仔细的后果那是,哎,绕了好多圈啊,说多了都是泪啊!不过终于完成了!吼吼。俺要做下个任务去了。
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 4岁宝宝支体能力差怎么办 当与游客发生矛盾时员工怎么办 顾客与同事发生矛盾你该怎么办 当一个人犯下的过错难以原谅怎么办 开车把别人撞成植物人了该怎么办 结婚证丢了孩子上不上学了怎么办 办房产证前加户主姓名怎么办手续 孩子上学户口跟房产不在一起怎么办 我家小孩被别的家长打了怎么办 要是小孩骗家长说老师打他怎么办 孩子在学校被家长打小孩怎么办 小孩在学校被老师打淤青家长怎么办 小孩和家长一吵架就说死怎么办 王者荣耀号被别人家长联接了怎么办 儿子12岁总是跟大人顶嘴怎么办 课堂上有学生和你顶嘴你怎么办 小孩看到大人吵架就哭了 怎么办 五年级的孩子叛逆爱发脾气怎么办 6个月孩子多动怎么办呀 小孩被大人打了很生气该怎么办 小孩在学校调皮被老师打了怎么办 四岁宝宝咳嗽半个月了怎么办 三岁宝宝咳嗽半个月了还不好怎么办 孩子在幼儿园不听话天天罚站怎么办 孩子个性太强脾气太倔不听话怎么办 孩子有十七了太不听话了怎么办 小孩的学籍填错了怎么办还能改吗 生完孩子后脾气暴躁易怒怎么办 生完孩子之后变懒了怎么办呀? 孩子三年级了成绩不突出家长怎么办 自私势力的父母想把我害死怎么办 父母如果养出自私的孩子怎么办 大人得了地图舌怎么办要怎么治疗 请问我家小孩有心理儿问题怎么办 想离婚妻子拿孩子命威胁怎么办 爸妈50多岁了吵架很严重怎么办 我和别人没离婚的老婆在一起怎么办 老婆起诉我离婚我不想理怎么办 上大学的孩子对考试无所谓怎么办 家里的人对我已经没有信心了怎么办 我是做股票配资的找不到客户怎么办