最近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須要鏈接的是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 socket
- 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進行電壓轉換。 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總線就兩條連線,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.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,修改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 [<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進行編譯。
我在學習示例代碼後理解,要調用到驅動層。上層代碼能夠分層四個部分。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此 次移植,波折仍是有那麼幾個,但也都終於跳出來了。發現,之因此有那麼多問題和波折,主要是對架構不是很理解,思路不清晰。因此很容易在出問題的時候亂了 陣腳。其次,細心的查閱資料,從中獲取須要的信息也是很是重要的。看文檔不仔細的後果那是,哎,繞了好多圈啊,說多了都是淚啊!不過終於完成了!吼吼。俺 要作下個任務去了。