mcp2515帶spi的can驅動移植總結

最近LZ接公司安排任務,移植一款CAN總線設備Mcp2515。因爲在前次任務中有SPI經驗,因此在接受任務是主要關注此設備採用SPI接口。因此一直沒有關注CAN相關的知識,後續過程當中遇到了很多麻煩,走了一些彎路。特把這次移植過程記錄整理一下。 java

CAN總線是一種在汽車上普遍採用的總線協議,被設計做爲汽車環境中的微控制器通信。LZ理論知識有限,網上抄一句介紹的吧。以下:CAN(Controller Area Network)總線,即控制器局域網總線,是一種有效支持分佈式控制或實時控制的串行通訊網絡。因爲其高性能、高可靠性、及獨特的設計和適宜的價格而普遍應用於工業現場控制、智能樓宇、醫療器械、交通工具以及傳感器等領域,並已被公認爲幾種最有前途的現場總線之一。CAN總線規範已經被國際標準化組織制訂爲國際標準ISO11898,並獲得了衆多半導體器件廠商的支持。 linux

咱們的產品是做爲CAN控制端,使用在一個電動汽車公司的汽車上的。該公司提供了單片機的終端設備進行通訊調試。因此LZ此次任務還要寫測試APK。這也是LZ第一次從APK到驅動的一次經驗。使一次從上到下的經驗,因此值得總結一下下。 android

這次調試是在EXYNOS4412三星四核CPU上,採用的是Android4.0.4文件系統和linux3.0.15的內核。是俺們公司的主打平臺哦!吼吼! shell

調試開始了! 數組

一.
電路信息 網絡

這是開發板的CAN小板圖,從圖上可知LZ須要鏈接的是VDDCSCLKSISOINTRESETVDD5.0幾個pin。其中CSCLKSISOSPI總線的經常使用的接口,是LZ比較熟悉的。其中SI鏈接MOSI控制氣的發送口,SO鏈接MISO控制氣的接收口。其餘幾個pin也是很容易明白的。INT中斷pinreset復位pin。在此LZ遺漏了一個嚴重的問題,致使後來驅動移植一直沒有反應。就是LZ忽略了datasheet中的電源信息: 架構

-工做電壓範圍2.7V5.5V socket

- 5mA典型工做電流 分佈式

Mcp2515的工做電壓是2.7V5V,這是適應了採用3.3V工做電壓的CPU。可是LZ4412CPU採用的是1.8V。因此這裏須要將上邊的IOpin1.8V3.3V的電壓轉換IC進行轉換。LZ後來在驅動調試中屢次查問題無果後,從新仔細閱讀了datasheet後才發現此問題。因此樓主在CPUCAN設備之間添加了MAX3390E轉換IC進行電壓轉換。 ide

二.驅動移植

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此 次移植,波折仍是有那麼幾個,但也都終於跳出來了。發現,之因此有那麼多問題和波折,主要是對架構不是很理解,思路不清晰。因此很容易在出問題的時候亂了 陣腳。其次,細心的查閱資料,從中獲取須要的信息也是很是重要的。看文檔不仔細的後果那是,哎,繞了好多圈啊,說多了都是淚啊!不過終於完成了!吼吼。俺 要作下個任務去了。

相關文章
相關標籤/搜索