作者:杨渊明 最近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 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总线就两条连线,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此次移植,波折还是有那么几个,但也都终于跳出来了。发现,之所以有那么多问题和波折,主要是对架构不是很理解,思路不清晰。所以很容易在出问题的时候乱了阵脚。其次,细心的查阅资料,从中获取需要的信息也是非常重要的。看文档不仔细的后果那是,哎,绕了好多圈啊,说多了都是泪啊!不过终于完成了!吼吼。俺要做下个任务去了。 |
mcp2515带spi的can驱动移植总结
最新推荐文章于 2024-09-13 14:31:07 发布