Socket CAN 是 Linux 内核中实现 CAN 总线通信的框架,它提供了基于套接字(Socket)的 API 接口,使 CAN 设备可以像网络设备一样被访问。
负责与硬件 CAN 控制器通信,需要实现以下功能: - 硬件初始化 - 发送和接收 CAN 帧 - 错误处理 - 比特率设置
将 CAN 控制器抽象为网络设备(net_device),通过 ifconfig 等工具管理。
实现 PF_CAN 协议族,提供套接字接口。
#include <linux/can/dev.h>
#include <linux/netdevice.h>
static const struct net_device_ops can_netdev_ops = {
.ndo_open = can_open,
.ndo_stop = can_close,
.ndo_start_xmit = can_start_xmit,
.ndo_change_mtu = can_change_mtu,
};
static int can_probe(struct platform_device *pdev)
{
struct net_device *dev;
struct can_priv *priv;
// 分配网络设备
dev = alloc_candev(sizeof(struct can_priv), 1);
if (!dev)
return -ENOMEM;
priv = netdev_priv(dev);
// 设置设备操作
dev->netdev_ops = &can_netdev_ops;
// 设置 CAN 特定参数
priv->can.clock.freq = 8000000; // 8MHz 时钟
priv->can.bittiming_const = &can_bittiming_const;
priv->can.do_set_bittiming = can_set_bittiming;
priv->can.do_set_mode = can_set_mode;
priv->can.ctrlmode_supported = CAN_CTRLMODE_3_SAMPLES;
// 注册网络设备
register_candev(dev);
return 0;
}
// 发送处理函数
static netdev_tx_t can_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct can_priv *priv = netdev_priv(dev);
struct can_frame *cf = (struct can_frame *)skb->data;
if (can_dropped_invalid_skb(dev, skb))
return NETDEV_TX_OK;
// 硬件发送
if (can_transmit(priv, cf)) {
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
return NETDEV_TX_OK;
}
// 接收处理函数
static void can_rx_handler(struct can_priv *priv, struct can_frame *cf)
{
struct sk_buff *skb;
struct net_device_stats *stats = &priv->dev->stats;
skb = alloc_can_skb(priv->dev, &cf);
if (!skb) {
stats->rx_dropped++;
return;
}
netif_rx(skb);
stats->rx_packets++;
stats->rx_bytes += cf->can_dlc;
}
int s = socket(PF_CAN, SOCK_RAW, CAN_RAW);
struct sockaddr_can addr;
struct ifreq ifr;
strcpy(ifr.ifr_name, "can0");
ioctl(s, SIOCGIFINDEX, &ifr);
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
bind(s, (struct sockaddr *)&addr, sizeof(addr));
struct can_frame frame;
// 发送
frame.can_id = 0x123;
frame.can_dlc = 8;
strcpy(frame.data, "12345678");
write(s, &frame, sizeof(frame));
// 接收
int nbytes = read(s, &frame, sizeof(frame));
if (nbytes > 0) {
printf("Received ID: 0x%X DLC: %d Data: %s\n",
frame.can_id, frame.can_dlc, frame.data);
}
对于使用设备树的系统,CAN 控制器需要在设备树中配置:
can0: can@1c00000 {
compatible = "vendor,can-controller";
reg = <0x1c00000 0x1000>;
interrupts = <0 45 4>;
clocks = <&clk 20>;
clock-names = "can_clk";
status = "okay";
};
# 设置比特率
ip link set can0 type can bitrate 500000
# 启动接口
ip link set can0 up
# 监控 CAN 总线
candump can0
# 发送 CAN 帧
cansend can0 123#1122334455667788
# 查看 CAN 统计信息
ip -details -statistics link show can0
# 启用调试输出
echo 8 > /proc/sys/kernel/printk
需要在内核配置中启用 CONFIG_CANFD,并在驱动中设置:
priv->can.ctrlmode_supported = CAN_CTRLMODE_FD;
实现错误帧检测和报告:
priv->can.do_get_berr_counter = can_get_berr_counter;
支持硬件时间戳需要在驱动中实现:
priv->can.do_get_berr_counter = can_get_berr_counter;
CAN 接口无法启动
发送失败
接收丢帧
通过以上框架和示例,您可以开发出功能完善的 Linux CAN 设备驱动,并利用 Socket CAN 提供的丰富接口实现各种 CAN 总线应用。