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 [<can-interface>] [Options] <can-msg>

<can-msg> 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 此次移植,波折还是有那么几个,但也都终于跳出来了。发现,之所以有那么多问题和波折,主要是对架构不是很理解,思路不清晰。所以很容易在出问题的时候乱了阵脚。其次,细心的查阅资料,从中获取需要的信息也是非常重要的。看文档不仔细的后果那是,哎,绕了好多圈啊,说多了都是泪啊!不过终于完成了!吼吼。俺 要做下个任务去了。
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: MCP2515是一款由Microchip公司生产的CAN控制器芯片,广泛应用于汽车电子领域。它通过SPI接口与主控设备通信,实现CAN总线通信功能。 典型的MCP2515 SPI应用电路原理图如下所示: 1. 主控设备通过SPI总线与MCP2515进行通信,其中包括四个信号线:SCK、MISO、MOSI和SS。SCK是时钟线,主控设备通过产生时钟信号来控制MCP2515的数据传输;MISO是主控设备接收MCP2515传输的数据;MOSI是主控设备发送数据给MCP2515;SS是片选信号,用于选中MCP2515芯片。 2. MCP2515通过SPI接口与主控设备进行通信后,可以完成CAN总线通信功能。CAN总线上通过CANH和CANL两个差分信号线进行数据传输。MCP2515将主控设备传输的数据转换成CAN总线上的差分信号,同时也能将CAN总线上的差分信号转换成主控设备可以处理的数据。 3. MCP2515还包括一些电源和优势电路,如VDD是芯片的正电源供应,VSS是地线,VREF是基准电压引脚,用于提供参考电压;RESET是复位引脚,用于将MCP2515芯片复位恢复到初始状态。 4. MCP2515还包括一些外部连接器和滤波器电路,用于外部连接和滤波器功能。 总之,MCP2515典型的SPI应用电路主要包括SPI接口、CAN总线接口、电源和优势电路、复位引脚以及外部连接器和滤波器电路。通过这个电路,MCP2515可以与主控设备进行SPI通信,并实现CAN总线通信功能,广泛应用于汽车电子领域中的数据传输和控制。 ### 回答2: MCP2515是一款经典的SPI控制器,通常应用于CAN总线控制器中。它与微控制器或微处理器之间通过SPI接口进行通信,用于实现CAN总线的控制和通信。 典型的MCP2515应用电路原理图如下:首先,主控芯片(如单片机)通过SPI总线与MCP2515相连接。SPI总线由四根线组成,分别是时钟线SCK、主机输出从机输入线MOSI、主机输入从机输出线MISO和片选线SS。 在原理图中,引脚VCC和GND分别接5V和GND电源供电。引脚RESET连接到主控芯片的一个IO口上,用于复位MCP2515。引脚INT连接到主控芯片的另一个IO口上,用于通知主控芯片CAN总线上是否有中断事件。 MCP2515的引脚CANH和CANL分别连接到CAN总线的CAN_H和CAN_L线上。CAN总线是一种差分信号线,用于CAN节点之间的数据通信。通过MCP2515可以控制CAN总线上的数据收发。 在SPI接口方面,MCP2515的引脚SCK连接到主控芯片的SCK线上,用于传输时钟信号。引脚MOSI连接到主控芯片的MOSI线上,用于主机输出数据。引脚MISO连接到主控芯片的MISO线上,用于从机输出数据。引脚SS可以通过软件控制或硬件控制,用于通知MCP2515是否处于选中状态。 以上就是MCP2515典型SPI应用电路的原理图。通过这个电路,主控芯片可以与MCP2515进行SPI通信,实现对CAN总线的控制和数据传输。这种电路在汽车电子、工业控制等领域中应用广泛,可以实现CAN总线与微控制器的高效连接和通信。 ### 回答3: MCP2515典型SPI(串行外设接口)应用电路原理图主要是指用于控制MCP2515控制器的电路原理图。MCP2515是一种高性能CAN(控制器局域网)总线控制器,主要用于汽车、工业控制、通信等领域的数据传输和通信。 MCP2515典型SPI应用电路原理图包括MCP2515控制器、微控制器、晶体振荡器、电容、电阻等元件。其中,MCP2515控制器是核心部件,用于处理CAN总线的通信协议和数据传输。 原理图中,MCP2515控制器通过SPI接口与微控制器进行数据交换和控制。SPI接口主要包括SCK(时钟信号)、SDI(数据输入)、SDO(数据输出)和SS(片选信号)。通过SPI接口,微控制器可以发送指令和数据给MCP2515控制器,并接收MCP2515控制器发送的数据。 晶体振荡器和附属电路提供系统的时钟信号,确保MCP2515控制器的正常工作。电容和电阻用于滤波和稳压,提供电路的稳定性和可靠性。 此外,原理图中还包括CAN总线的连接,用于与外部设备进行数据交换。CAN总线主要包括CAN_H(高速通道)和CAN_L(低速通道),用于传输CAN总线的差分信号,实现数据的发送和接收。 通过搭建MCP2515典型SPI应用电路,可以实现CAN总线的控制和数据传输。这对于需要用到CAN总线的应用来说非常重要,如车辆诊断、电力检测、自动化控制等领域。 总的来说,MCP2515典型SPI应用电路原理图是一种基于MCP2515控制器的电路设计,通过SPI接口和CAN总线实现数据的控制和传输。它在汽车和工业控制等领域具有广泛的应用前景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值