1. 硬件方案选择与连接
在工业控制和车载电子领域,CAN总线通信的稳定性和实时性至关重要。海思平台作为嵌入式系统的主流选择,虽然性能强大,但大多数芯片并不原生集成CAN控制器。这就需要我们通过外部扩展方案来实现CAN功能,其中SPI转CAN是最常用且性价比最高的方式。
我实际项目中用过Microchip的MCP2515芯片,这款芯片支持CAN 2.0A/B协议,通过SPI接口与海思平台通信。硬件连接时需要特别注意四个关键信号线:SCK(时钟)、MOSI(主设备发送从设备接收)、MISO(主设备接收从设备发送)和CS(片选)。CS信号通常由GPIO控制,需要根据具体硬件设计确定使用哪个GPIO引脚。
中断引脚的处理往往是最容易出问题的地方。MCP2515的INT引脚需要连接到海思的GPIO中断输入引脚,这个连接关系一定要和硬件工程师反复确认。我就曾经遇到过原理图标注的GPIO引脚与实际PCB布线不一致的情况,导致调试了半天才发现中断无法触发。
电源匹配也是需要注意的细节。MCP2515通常采用3.3V供电,而海思平台的GPIO电平可能是1.8V或3.3V,需要确保电平匹配。如果存在电平差异,必须使用电平转换电路,否则可能无法正常通信甚至损坏芯片。
2. 内核配置与驱动编译
2.1 内核配置详解
内核配置是确保CAN功能正常工作的基础。首先需要通过make menuconfig进入内核配置界面,在Networking support选项中开启CAN总线子系统支持。关键配置包括CONFIG_CAN=y启用核心CAN协议栈,CONFIG_CAN_RAW=y启用原始CAN协议(这是用户态编程必备的),CONFIG_CAN_BCM=y和CONFIG_CAN_GW=y可以根据实际需求选择启用。
最重要的是要找到CAN Device Drivers下的SPI接口支持,并选中Microchip MCP251x SPI CAN controllers。这个选项可能位于不同的子菜单中,需要仔细查找。我建议使用搜索功能快速定位,在menuconfig界面按"/"键,然后输入MCP251x即可找到相关配置项。
配置完成后,记得保存配置并重新编译内核。如果使用的是模块化编译方式,还需要执行make modules来编译驱动模块。编译过程中要特别注意输出信息,确保没有报错。
2.2 设备树配置实战
设备树配置是嵌入式Linux开发的关键环节。对于MCP2515芯片,我们需要在设备树中正确配置SPI节点和中断信息。以下是一个典型的配置示例:
&spi_bus3 {
status = "okay";
can0: can@0 {
compatible = "microchip,mcp2515";
reg = <0>;
clocks = <&clk8m>;
spi-max-frequency = <8000000>;
interrupt-parent = <&gpio_chip6>;
interrupts = <5 IRQ_TYPE_EDGE_FALLING>;
status = "okay";
};
};
这里的clocks配置需要特别注意,必须与硬件使用的晶振频率一致。我曾经遇到过因为时钟配置错误导致通信速率不正常的问题,最后发现是硬件使用了8MHz晶振而我配置成了20MHz。
中断配置是最容易出错的地方。interrupt-parent指定了GPIO控制器,interrupts指定了具体的引脚号和中断触发方式。一定要与硬件设计完全一致,否则中断无法正常工作。
2.3 管脚复用配置
海思平台的管脚复用功能需要通过寄存器配置来实现。不同的海思芯片型号,管脚复用配置方式可能有所不同。通常需要在sys_config.fex文件或设备树中进行配置。
以下是一个典型的SPI管脚复用配置示例:
static void spi3_pin_mux(void)
{
void *iocfg2_base = sys_config_get_reg_iocfg2();
sys_writel(iocfg2_base + 0x0110, 0x01b3); /* SPI3_SCLK */
sys_writel(iocfg2_base + 0x0108, 0x0153); /* SPI3_SDO */
sys_writel(iocfg2_base + 0x0104, 0x0203); /* SPI3_SDI */
sys_writel(iocfg2_base + 0x010C, 0x0203); /* SPI3_CSN */
}
这些配置值需要参考海思芯片的具体数据手册,不同芯片的寄存器地址和配置值可能完全不同。建议直接找硬件工程师要现成的配置,或者参考官方提供的示例代码。
3. 驱动加载与调试
3.1 手动编译与加载驱动
在某些情况下,我们可能需要手动编译和加载驱动模块。首先需要获取MCP2515的驱动源代码,这通常可以在Linux内核源码的drivers/net/can/spi/目录下找到。
编译驱动模块的Makefile配置很关键:
obj-m += mcp251x.o
mcp251x-objs := mcp251x.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
编译完成后使用insmod命令加载模块:insmod mcp251x.ko。如果加载成功,可以在dmesg输出中看到"MCP2515 successfully initialized"的提示信息。
如果遇到模块版本不匹配的问题,可能需要使用--force参数强制加载,但这样可能会带来稳定性风险。更好的方法是重新编译与当前内核版本匹配的驱动模块。
3.2 中断调试技巧
中断工作正常是CAN通信的基础。我们可以通过/proc/interrupts文件来监控中断状态:
# 查看can0中断的注册状态
cat /proc/interrupts | grep can0
# 实时监控中断计数变化
watch -n 1 "cat /proc/interrupts | grep can0"
如果中断计数在CAN通信时没有增加,说明中断配置可能有问题。常见的问题包括中断引脚配置错误、中断触发方式设置不正确、或者设备树中的中断配置与硬件实际连接不匹配。
我遇到过一种特殊情况:中断能够触发但计数不增加,最后发现是中断处理函数中没有正确调用中断确认函数。这种问题需要通过阅读驱动源码来定位和解决。
3.3 时钟同步问题排查
时钟同步是SPI通信中的常见问题。MCP2515需要外部时钟信号,如果时钟不同步,会导致数据传输错误。我们可以通过示波器来观察SPI时钟信号和数据信号的波形。
首先检查时钟频率是否匹配:cat /sys/bus/spi/devices/spi2.0/clock_frequency。这个值应该与设备树中配置的spi-max-frequency一致。
如果发现数据传输错误,可以尝试降低SPI时钟频率。虽然MCP2515支持最高10MHz的SPI时钟,但在长距离传输或干扰较大的环境中,降低时钟频率可以提高稳定性。
4. 通信测试与性能优化
4.1 基础通信测试
CAN接口配置是测试的第一步。使用ip命令可以配置CAN接口的参数和状态:
# 设置CAN接口波特率为500kbps,这是工业领域常用速率
ip link set can0 type can bitrate 500000 sample-point 0.875
# 启用CAN接口
ip link set can0 up
# 查看接口详细信息
ip -details link show can0
配置完成后,可以使用candump和cansend命令进行基本的通信测试:
# 在一个终端中启动接收监控
candump can0
# 在另一个终端中发送测试数据
cansend can0 123#DEADBEEF
如果能够正常收发数据,说明基础通信功能已经正常。如果出现超时或错误,需要检查硬件连接和配置参数。
4.2 性能测试方法
在实际项目中,我们更需要关注通信的稳定性和可靠性。以下是一些实用的性能测试方法:
# 持续发送测试数据包
cansend can0 123#DEADBEEFDEADBEEF &
# 使用pv命令监控接收速率和丢包率
candump -tA can0 | pv -r > /dev/null
# 压力测试:高速率连续发送
for i in {1..1000}; do cansend can0 123#$(printf '%016x' $i); done
在进行性能测试时,要特别注意系统负载和中断处理情况。可以使用top命令监控系统负载,以及/proc/interrupts监控中断处理情况。
如果发现丢包率较高,可以尝试以下优化措施:降低通信速率、优化中断处理、增加接收缓冲区大小、或者调整SPI时钟频率。
4.3 用户态编程实例
在实际应用中,我们通常需要在用户态程序中进行CAN通信。以下是一个完整的使用示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
int create_can_socket(const char *ifname)
{
int s;
struct sockaddr_can addr;
struct ifreq ifr;
// 创建原始CAN套接字
if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
perror("Socket create failed");
return -1;
}
// 指定CAN接口名称
strcpy(ifr.ifr_name, ifname);
ioctl(s, SIOCGIFINDEX, &ifr);
// 绑定到指定接口
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Bind failed");
close(s);
return -1;
}
return s;
}
int send_can_frame(int s, canid_t can_id, const uint8_t *data, uint8_t dlc)
{
struct can_frame frame;
frame.can_id = can_id;
frame.can_dlc = dlc;
memcpy(frame.data, data, dlc);
return write(s, &frame, sizeof(frame));
}
这个示例提供了基本的CAN套接字创建和数据发送功能。在实际使用中,还需要添加错误处理、超时控制、数据接收等功能。
5. 常见问题与解决方案
5.1 驱动加载失败处理
驱动加载失败是最常见的问题之一。可能的原因包括内核版本不匹配、配置错误、权限问题等。首先查看dmesg输出,通常会有详细的错误信息。
如果出现"Unknown symbol"错误,说明驱动依赖的其他模块没有加载。可以使用modprobe命令先加载依赖模块。如果出现"Device or resource busy"错误,说明设备节点已经被占用,需要先释放资源。
我遇到过一种特殊情况:驱动加载成功但设备无法识别,最后发现是SPI控制器没有正确初始化。需要在加载CAN驱动之前,先确保SPI控制器驱动已经正常加载和工作。
5.2 通信稳定性优化
在实际工业环境中,通信稳定性往往比速度更重要。以下是一些优化建议:
首先优化中断处理。确保中断处理函数尽可能简短,避免在中断上下文中进行复杂操作。如果中断处理时间过长,可能导致中断丢失或系统响应缓慢。
其次调整SPI传输参数。可以尝试不同的SPI模式、时钟极性和相位设置,找到最稳定的配置。一般来说,模式0(CPOL=0, CPHA=0)是最常用的配置。
最后考虑增加错误检测和重传机制。在应用层实现超时重传、数据校验等功能,可以提高通信的可靠性。
5.3 性能调优技巧
对于需要高性能的应用场景,可以尝试以下调优技巧:
增加接收缓冲区大小可以减少丢包概率:echo 1000 > /sys/class/net/can0/rx_queue_len
调整SPI时钟频率到最佳值。不是越高越好,需要根据实际硬件条件和通信距离找到最优值。
使用多线程架构,将数据接收和处理分离到不同的线程中,避免因为处理延迟导致数据丢失。
考虑使用CAN FD(Flexible Data-rate)协议,如果硬件支持的话。CAN FD支持更高的数据传输速率和更大的数据帧。
在实际项目中,我建议先确保基本功能稳定,然后再逐步进行性能优化。每次只调整一个参数,测试效果后再决定下一步优化方向。
1077



被折叠的 条评论
为什么被折叠?



