參考文章:http://blog.csdn.net/xiaohuangzhilin/article/details/48968207
http://www.embedu.org/Column/Column596.htm
https://wenku.baidu.com/view/65baea51bb68a98271fefaa7.html
硬件環境:快捷N32926開發板
Socket CAN介紹
Socket CAN是在Linux下CAN協議實現的一種實現方法。Linux下最早使用CAN的方法是基於字符設備來實現的,與之不同的是Socket CAN使用伯克利的Socket接口和Linux網絡協議棧,這種方法使得CAN設備驅動可以通過網絡接口來調用。Socket CAN的接口被設計的儘量接近TCP/IP的協議,讓那些熟悉網絡編程的程序員能夠比較容易的學習和使用。
使用內核自帶MCP251X驅動,通過spi連接mcp2515模塊,以SocketCAN的方式實現。
一、修改內核驅動代碼
1. 修改inux-2.6.35.4/arch/arm/mach-w55fa92/dev.c文件。
//設置晶振頻率
static struct mcp251x_platform_data mcp251x_info = {
.oscillator_frequency = 8 * 1000 * 1000, //頻率8M
};
... ...
static struct spi_board_info w55fa92_spi0_board_info[] __initdata = {
/*
statement; //註釋掉原有內容
*/
{
.modalias = "mcp2515", //設備名稱
.bus_num = 0, //連接在spi控制器0上
.chip_select = 0, //片選線號
.platform_data = &mcp251x_info, //晶振頻率
.irq = W55FA92_IRQ(2), //中斷號
.max_speed_hz = 2 * 1000 * 1000, //SPI最大速率
.mode = SPI_MODE_0, //選擇模式CPOL=0, CPHA=0
},
};
最後w55fa92_dev_init函數中的spi_register_board_info會將該spi設備進行註冊。
2.修改linux-2.6.35.4/drivers/net/can/mcp251x.c文件,配置中斷引腳。
在mcp251x_hw_probe函數中配置中斷引腳
writel(readl(REG_GPIOB_OMD) & ~((1 << 5)), REG_GPIOB_OMD); // input
writel(readl(REG_GPIOB_PUEN) | ((1 << 5)), REG_GPIOB_PUEN); // pull-up
writel(readl(REG_IRQSRCGPB) & ~(0xC00), REG_IRQSRCGPB); // GPB5 as nIRQ0 source
writel((readl(REG_IRQENGPB)& ~(0x00200000)) | 0x00000020, REG_IRQENGPB); // falling edge trigge
writel((readl(REG_AIC_SCR1)& ~(0x00C70000)) | 0x00C70000, REG_AIC_SCR1);
enable_irq(W55FA92_IRQ(2));
在mcp251x_can_ist函數開始讀取中斷狀態,在結束清除中斷。
static irqreturn_t mcp251x_can_ist(int irq, void *dev_id)
{
u32 src;
src = readl(REG_IRQTGSRC0);
... ...
writel(src & 0x00200000, REG_IRQTGSRC0);
return 0;
}
在mcp251x_open函數中清中斷。
static int mcp251x_open(struct net_device *net)
{
writel((1 << W55FA92_IRQ(2)), REG_AIC_SCCR); // force clear previous interrupt, if any.
writel(readl(REG_IRQTGSRC0) & 0x000001C, REG_IRQTGSRC0); // clear source
ret = request_threaded_irq(W55FA92_IRQ(2), NULL, mcp251x_can_ist,
IRQF_TRIGGER_FALLING, DEVICE_NAME, priv);
}
3.修改menuconfig配置文件
配置spi驅動,除下述選項外,其他不選。
[*] SPIsupport —>
-*- Utilitiesfor Bitbanging SPImasters
<*> NuvotonW55FA92 seriesSPI
配置can驅動,除下述選項外,其他不選。
[*]Networkingsupport->
<*>CAN bus subsystemsupport->
<*>RawCAN Protocal
<*>BroadcastManage CAN Protocal
CANDevice Drivers->
<*>Platform CAN driver withNetlink support
[*]CANbit-timing calculation
<*>Microchip MCP251x SPI CANcontrollers
通過以上步驟完成mcp2515驅動移植。驅動註冊成功在內核啓動時會提示mcp251xspi0.0: probed
。使用ifconfig -a命令可以看到can0設備。
二、通信測試
1、移植iprout2,測試和使用SocketCAN。
(1)下載iproute2的源碼http://www.kernel.org/pub/linux/utils/net/iproute2/。我這裏下載的是iproute2 3.6.0。
(2)解壓iproute2-3.6.0.tar.xz,修改Makefile。
CC = arm-linux-gcc
SUBDIRS=lib ip
(3)修改完成執行make命令,生成ip命令,拷貝到開發板文件/bin目錄。
(4)一些ip命令
ifconfig can0 down //關閉can0,以便配置
ip link set can0 up type can bitrate 250000 //設置can0波特率
ip -details link show can0 //顯示can0信息
遇到的問題:
(1)修改iprout目錄下的ip/ipnetns.c文件,註釋掉所有unshare函數相關的語句。快捷提供的交叉編譯工具中沒有定義這個函數。
(2)修改iprout目錄下的ip/ip6tunnel.c文件,添加struct in6_addr in6addr_any定義。
(3)缺少libresolv.so.0等庫,在開發板中添加相應的庫。
2、移植canutils
(1)下載canutils的最新源碼http://www.pengutronix.de/software/socket-can/download/canutils 。
(2)因爲canutils編譯需要libsocketcan庫的支持,需要下載libsocketcan。http://www.pengutronix.de/software/libsocketcan/download/ 筆者下載的是libsocketcan 0.0.9。
(3)解壓libsocketcan-0.0.9.tar.bz2。執行configure命令。
./configure –host=arm-none-linux-gnueabi –prefix=/home/…
(4)執行make編譯庫;
(5)執行make install 生成庫。至此,libsocketcan編譯完畢。
(6)解壓canutils-4.0.6.tar.bz2,執行configure命令。
./configure –host=arm-none-linux-gnueabi –prefix=/home/… libsocketcan_LIBS=-lsocketcan LDFLAGS=-L/home/…/socketcan/lib CPPFLAGS=-I/home/…/socketcan/include
(7)修改完成執行make命令,生成四個目錄,分別拷貝到開發板文件系統的相應目錄。
(8)使用canutils工具:
配置CAN的總線通訊波特率:
canconfig canX bitrate + 波特率
開啓 / 重啓 / 關閉CAN總線
canconfig canX start
canconfig canX restart
canconfig canX stop
發送信息
cansend canX –-identifier=ID + 數據
接收數據
candump canX
使用濾波器接收ID匹配的數據
candump canX –-filter=ID:mask
3、通信測試
通過CAN連接兩個開發板。
開發板A執行命令candump canX
開發板B執行命令cansend canX –-identifier=0x123 0x12
開發板A終端收到信息<0x123> [1] 12
,表示數據接收成功。
三、SocketCAN編程
(1)初始化
SocketCAN 中大部分的數據結構和函數在頭文件 linux/can.h 中進行了定義。 CAN 總線套接字的創建採用標準的網絡套接字操作來完成。網絡套接字在頭文件 sys/socket.h 中定義。
int s;
struct sockaddr_can addr;
struct ifreq ifr;
s = socket(PF_CAN, SOCK_RAW, CAN_RAW);//創建 SocketCAN 套接字
strcpy(ifr.ifr_name, "can0" );
ioctl(s, SIOCGIFINDEX, &ifr);//指定 can0 設備
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr)); //將套接字與 can0 綁定
(2)數據發送
CAN幀結構體定義
struct can_frame {
canid_t can_id;//CAN 標識符
__u8 can_dlc; //數據場的長度
__u8 data[8]; //數據
};
can_id 爲幀的標識符
#define CAN_EFF_FLAG 0x80000000U //擴展幀的標識
#define CAN_RTR_FLAG 0x40000000U //遠程幀的標識
#define CAN_ERR_FLAG 0x20000000U //錯誤幀的標識,用於錯誤檢查
使用write函數進行數據發送
struct can_frame frame;
frame.can_id = 0x123; //如果爲擴展幀,那麼 frame.can_id = CAN_EFF_FLAG | 0x123;
frame.can_dlc = 1; //數據長度爲 1
frame.data[0] = 0xAB; //數據內容爲 0xAB
int nbytes = write(s, &frame, sizeof(frame)); //發送數據
if(nbytes != sizeof(frame)) //如果 nbytes 不等於幀長度,就說明發送失敗
printf("Error\n!");
(3)數據接收
使用read函數進行數據接收
struct can_frame frame;
int nbytes = read(s, &frame, sizeof(frame));
(4)過濾規則設置
在數據接收時,系統可以根據預先設置的過濾規則,實現對報文的過濾。過濾規則使用 can_filter 結構體來實現,定義如下:
struct can_filter {
canid_t can_id;
canid_t can_mask;
};
過濾的規則爲:接收到的數據幀的 can_id & mask == can_id & mask。
通過這條規則可以在系統中過濾掉所有不符合規則的報文,使得應用程序不需要對無關的報文進行處理。在 can_filter 結構的 can_id 中,符號位 CAN_INV_FILTER 在置位時可以實現 can_id 在執行過濾前的位反轉。
用戶可以爲每個打開的套接字設置多條獨立的過濾規則,使用方法如下:
struct can_filter rfilter[2];
rfilter[0].can_id = 0x123;
rfilter[0].can_mask = CAN_SFF_MASK; //#define CAN_SFF_MASK 0x000007FFU
rfilter[1].can_id = 0x200;
rfilter[1].can_mask = 0x700;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &rfilter, sizeof(rfilter));//設置規則
在極端情況下,如果應用程序不需要接收報文,可以禁用過濾規則。這樣的話,原始套接字就會忽略所有接收到的報文。在這種僅僅發送數據的應用中,可以在內核中省略接收隊列,以此減少 CPU 資源的消耗。禁用方法如下:
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); //禁用過濾規則
通過錯誤掩碼可以實現對錯誤幀的過濾, 例如:
can_err_mask_t err_mask = (CAN_ERR_TX_TIMEOUT | CAN_ERR_BUSOFF);
setsockopt(s, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, err_mask, sizeof(err_mask));
(5)迴環功能設置
在默認情況下, 本地迴環功能是開啓的,可以使用下面的方法關閉迴環/開啓功能:
int loopback = 0; // 0 表示關閉, 1 表示開啓( 默認)
setsockopt(s, SOL_CAN_RAW, CAN_RAW_LOOPBACK, &loopback, sizeof(loopback));
在本地迴環功能開啓的情況下,所有的發送幀都會被迴環到與 CAN 總線接口對應的套接字上。 默認情況下,發送 CAN 報文的套接字不想接收自己發送的報文,因此發送套接字上的迴環功能是關閉的。可以在需要的時候改變這一默認行爲:
int ro = 1; // 0 表示關閉( 默認), 1 表示開啓
setsockopt(s, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &ro, sizeof(ro));
四、問題
(1)實際測試過程中循環不停的發送和接收數據,需要在發送之間添加延時。但是該開發板最低延時只能設置爲10ms,可修改內核HZ參數,或者將CAN隊列長度設置爲1000,執行命令echo 1000 >> /sys/class/net/can0/tx_queue_len
。
(2)測試發現500Kbps速率下,10s傳輸的數據只有2KB多,由於我用CAN做的語音傳輸,實際速度不能滿足要求。使用示波器測量發現spi的傳輸速度有問題,原因未知,最後也沒有解決。最終採用了GPIO模擬SPI的方式重新編寫了驅動。