05树莓派5 解析OBD_II数据
一,方案确定
由于树莓派没有can外设,只能通过转换芯片来实现,可以是串口转can,也可以现在spi转can。我这里使用的是spi转can的方案,采用MCP2515模块对OBD_II数据信息进行解析。
MCP2515模块连接:
VCC -> 3.3V
GND -> GND
CS -> SPI片选引脚 (默认SPI0_CE0/GPIO8)
SO -> SPI_MISO (GPIO9)
SI -> SPI_MOSI (GPIO10)
SCK -> SPI_SCLK (GPIO11)
INT -> GPIO25 (物理引脚22)
三,环境搭建加载驱动
安装socketcan工具以及cantools工具
sudo apt install can-utils
pip3 install cantools
修改config.txt文件用于加载mcp2515的驱动,这个文件以前是在/boot/中,打开之后他会指引你到/boot/firmware目录中。
#打开文件
sudo nano /boot/firmware/config.txt
#末尾添加
dtparam=spi=on
dtoverlay=mcp2515-can0,oscillator=8000000,interrupt=25,cs=0
需要注意的是自己的模块晶振的大小,我的这里是8Mhz,修改这里oscillator=8000000
然后重启
sudo reboot
配置验证方法:
检查SPI设备:
ls /dev/spi*
# 应显示: /dev/spidev0.0 /dev/spidev1.0
检查CAN接口:
ip link show
# 应显示: can0: <NOARP,ECHO> mtu 16 qdisc noop state DOWN
查看加载的驱动:
dtparam=spi=on
dtoverlay=mcp2515-can0,oscillator=12000000,interrupt=25,spimaxfrequency=2000000
检测MCP2515是否被正确挂载
ifconfig -a
常见问题解决:
CAN接口不工作:
检查晶振频率是否匹配(常见有8M/16M/20M),我的这里是8M的晶振
验证接线是否正确
常用命令一、CAN 接口管理命令
命令功 能示例说明
ip link set | 启用 CAN 接口 | sudo ip link set can0 type can bitrate 500000 | 设置波特率 500kbps |
ip link set up | 启动接口 | sudo ip link set can0 up | 激活 CAN 接口 |
ip link set down | 关闭接口 | sudo ip link set can0 down | 禁用 CAN 接口 |
ip -d link show | 查看接口状态 | ip -d link show can0 | 显示详细信息 |
ifconfig | 基础状态查看 | ifconfig can0 | 检查接口状态 |
二、数据收发操作
命令功能示例说明
cansend | 发送 CAN 帧 | cansend can0 123#AABBCCDD | 发送标准帧 ID=0x123 |
cangen | 自动生成 CAN 帧 | cangen can0 -g 100 -I 123 -D i | 每 100ms 发送随机数据 |
candump | 监听 CAN 数据 | candump can0 | 实时显示所有帧 |
canplayer | 回放 CAN 日志 | canplayer -I candump.log | 从文件回放数据 |
cansequence | 序列测试 | cansequence -p can0 -g 50 | 每 50ms 发送序列帧 |
三、高级监听与过滤
命令功能示例说明
带过滤的 candump | ID 过滤 | candump can0,100:7FF | 只显示 ID 0x100-0x7FF |
时间戳监听 | 精确时间 | candump -ta can0 | 显示微秒级时间戳 |
颜色高亮 | 视觉区分 | candump -c can0 | 不同 ID 用不同颜色 |
数据格式 | 多种输出 | candump -l can0 | 记录到文件 (candump.log) |
回环测试 | 自发自收 | sudo ip link set can0 type can loopback on | 启用回环模式 |
四、错误诊断与统计
命令功能示例说明
ip -s link | 错误统计 | ip -s -s link show can0 | 显示错误计数器 |
canbusload | 总线负载 | canbusload can0@500000 | 计算总线利用率 |
canerror | 错误帧检测 | canerror can0 | 监听错误帧 |
canecho | 回显接收 | canecho can0 | 显示接收的原始数据 |
cansniffer | 高级嗅探 | cansniffer -c can0 | 按 ID 分组显示 |
五、系统配置管理
文件/命令功能配置示例说明
/etc/network/interfaces | 开机自启 | <pre>auto can0 iface can0 can bitrate 500000</pre> | 系统启动时自动启用 |
ip details | 查看参数 | ip -details link show can0 | 显示当前配置 |
波特率计算 | 复杂速率 | ip link set can0 type can tq 125 prop-seg 6 phase-seg1 7 phase-seg2 2 sjw 1 | 手动配置时序参数 |
CAN FD 配置 | 高速模式 | sudo ip link set can0 type can bitrate 500000 dbitrate 2000000 fd on |
六、CAN 四种模式设置命令
模式命令功能说明典型应用场景
正常模式 (Normal) | sudo ip link set can0 type can bitrate 500000 sudo ip link set can0 up | 标准通信模式,可正常收发数据 | 实际总线通信 |
回环模式 (Loopback) | sudo ip link set can0 down sudo ip link set can0 type can bitrate 500000 loopback on sudo ip link set can0 up | 自发自收,不连接物理总线 | 驱动自测、应用开发 |
只听模式 (Listen-Only) | sudo ip link set can0 down sudo ip link set can0 type can bitrate 500000 listen-only on sudo ip link set can0 up | 只接收数据,不发送任何帧 | 总线监听、逆向分析 |
三态模式 (Triple-Sampling) | sudo ip link set can0 down sudo ip link set can0 type can bitrate 500000 triple-sampling on sudo ip link set can0 up | 每个位采样3次提高 |
永久配置(开机自启)
编辑 /etc/network/interfaces:
auto can0 iface can0 can bitrate 500000 loopback off # 正常模式 # listen-only on # 听只听模式 # triple-sampling on # 三态模式 up /sbin/ip link set $IFACE up down /sbin/ip link set $IFACE down
每次修改都要先关闭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 <arpa/inet.h> #include <netinet/in.h> #include <linux/can.h> #include <linux/can/raw.h> #include <time.h> #include <pthread.h> #include <sys/time.h> #include <jansson.h> // JSON 库 // OBD-II PID定义 #define PID_ENGINE_RPM 0x0C #define PID_VEHICLE_SPEED 0x0D #define PID_COOLANT_TEMP 0x05 #define PID_FUEL_PRESSURE 0x0A #define PID_FUEL_LEVEL 0x2F #define PID_ENGINE_LOAD 0x04 // CAN总线配置 #define CAN_INTERFACE "can0" #define CAN_BITRATE 500000 // UDP配置 #define UDP_IP "127.0.0.1" // 目标IP地址 #define UDP_PORT 5000 // 目标端口 #define MAX_UDP_PACKET 1024 // 最大UDP包大小 // 数据结构存储OBD数据 typedef struct { int rpm; int speed; int coolant_temp; float fuel_pressure; float fuel_level; int engine_load; struct timeval timestamp; } OBDData; // 全局变量 volatile int running = 1; OBDData current_data; pthread_mutex_t data_mutex = PTHREAD_MUTEX_INITIALIZER; int udp_sock = -1; struct sockaddr_in udp_addr; // CAN总线初始化 int init_can_socket(const char *ifname) { int s; struct sockaddr_can addr; struct ifreq ifr; // 创建socket if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) { perror("Socket creation failed"); return -1; } // 设置接口名 strcpy(ifr.ifr_name, ifname); if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { perror("I/O control failed"); close(s); return -1; } // 绑定socket到CAN接口 addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("Socket binding failed"); close(s); return -1; } return s; } // UDP初始化 int init_udp_socket(const char *ip, int port) { int sock; if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("UDP socket creation failed"); return -1; } memset(&udp_addr, 0, sizeof(udp_addr)); udp_addr.sin_family = AF_INET; udp_addr.sin_port = htons(port); // 转换IP地址 if (inet_pton(AF_INET, ip, &udp_addr.sin_addr) <= 0) { perror("Invalid UDP address"); close(sock); return -1; } // 设置超时选项 struct timeval tv; tv.tv_sec = 0; tv.tv_usec = 100000; // 100ms超时 setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv)); return sock; } // 发送OBD请求 void send_obd_request(int can_sock, uint8_t pid) { struct can_frame frame; // 标准OBD请求格式 frame.can_id = 0x7DF; // 广播地址 frame.can_dlc = 8; // 数据长度 // OBD-II请求格式: [模式, PID, 填充0] frame.data[0] = 0x02; // 数据字节数 frame.data[1] = 0x01; // 模式: 显示当前数据 frame.data[2] = pid; // 请求的PID frame.data[3] = 0x00; // 填充 frame.data[4] = 0x00; frame.data[5] = 0x00; frame.data[6] = 0x00; frame.data[7] = 0x00; // 发送请求 if (write(can_sock, &frame, sizeof(frame)) != sizeof(frame)) { perror("CAN frame send failed"); } } // 解析OBD响应 void parse_obd_response(struct can_frame *frame, OBDData *data) { // 检查是否为ECU响应 (ID 0x7E8 - 0x7EF) if (frame->can_id < 0x7E8 || frame->can_id > 0x7EF) return; // 检查响应格式: [长度, 模式+0x40, PID, 数据...] if (frame->data[0] < 2 || frame->data[1] != 0x41) return; uint8_t pid = frame->data[2]; gettimeofday(&data->timestamp, NULL); // 获取精确时间戳 pthread_mutex_lock(&data_mutex); // 根据PID解析数据 switch (pid) { case PID_ENGINE_RPM: // 公式: (256 * A + B) / 4 data->rpm = (frame->data[3] * 256 + frame->data[4]) / 4; break; case PID_VEHICLE_SPEED: // 公式: A data->speed = frame->data[3]; break; case PID_COOLANT_TEMP: // 公式: A - 40 data->coolant_temp = frame->data[3] - 40; break; case PID_FUEL_PRESSURE: // 公式: 3 * A data->fuel_pressure = frame->data[3] * 3.0; break; case PID_FUEL_LEVEL: // 公式: 100 * A / 255 data->fuel_level = frame->data[3] * 100.0 / 255.0; break; case PID_ENGINE_LOAD: // 公式: 100 * A / 255 data->engine_load = frame->data[3] * 100 / 255; break; } pthread_mutex_unlock(&data_mutex); } // 将OBD数据转换为JSON格式 char* create_obd_json(OBDData *data) { json_t *root = json_object(); // 添加时间戳(毫秒精度) char timestamp_str[64]; struct tm *tm_info; time_t seconds = data->timestamp.tv_sec; tm_info = localtime(&seconds); strftime(timestamp_str, sizeof(timestamp_str), "%Y-%m-%dT%H:%M:%S", tm_info); // 添加毫秒部分 char full_timestamp[70]; snprintf(full_timestamp, sizeof(full_timestamp), "%s.%03ldZ", timestamp_str, data->timestamp.tv_usec / 1000); // 添加数据字段 json_object_set_new(root, "timestamp", json_string(full_timestamp)); json_object_set_new(root, "rpm", json_integer(data->rpm)); json_object_set_new(root, "speed", json_integer(data->speed)); json_object_set_new(root, "coolant_temp", json_integer(data->coolant_temp)); json_object_set_new(root, "fuel_pressure", json_real(data->fuel_pressure)); json_object_set_new(root, "fuel_level", json_real(data->fuel_level)); json_object_set_new(root, "engine_load", json_integer(data->engine_load)); // 生成JSON字符串 char *json_str = json_dumps(root, JSON_COMPACT | JSON_PRESERVE_ORDER); json_decref(root); return json_str; } // 通过UDP发送JSON数据 int send_json_via_udp(const char *json_str) { if (udp_sock < 0) return -1; size_t len = strlen(json_str); if (len > MAX_UDP_PACKET - 1) { fprintf(stderr, "JSON too large for UDP packet\n"); return -1; } ssize_t sent = sendto(udp_sock, json_str, len, 0, (struct sockaddr *)&udp_addr, sizeof(udp_addr)); if (sent < 0) { perror("UDP send failed"); return -1; } return sent; } // 数据采集线程 void *data_acquisition_thread(void *arg) { int can_sock = *(int *)arg; struct can_frame frame; int nbytes; // 初始化PID轮询顺序 uint8_t pids[] = { PID_ENGINE_RPM, PID_VEHICLE_SPEED, PID_COOLANT_TEMP, PID_FUEL_PRESSURE, PID_FUEL_LEVEL, PID_ENGINE_LOAD }; int pid_count = sizeof(pids) / sizeof(pids[0]); int current_pid = 0; struct timeval last_request; gettimeofday(&last_request, NULL); while (running) { struct timeval now; gettimeofday(&now, NULL); // 计算时间差(毫秒) long elapsed = (now.tv_sec - last_request.tv_sec) * 1000 + (now.tv_usec - last_request.tv_usec) / 1000; // 每200ms发送一个新的PID请求 if (elapsed >= 200) { send_obd_request(can_sock, pids[current_pid]); current_pid = (current_pid + 1) % pid_count; last_request = now; } // 读取CAN帧(非阻塞) struct timeval tv = {0, 10000}; // 10ms超时 fd_set readfds; FD_ZERO(&readfds); FD_SET(can_sock, &readfds); if (select(can_sock + 1, &readfds, NULL, NULL, &tv) > 0) { nbytes = read(can_sock, &frame, sizeof(struct can_frame)); if (nbytes < 0) { perror("CAN read error"); continue; } if (nbytes == sizeof(struct can_frame)) { parse_obd_response(&frame, ¤t_data); } } usleep(5000); // 5ms延迟减少CPU使用 } return NULL; } // 数据发送线程 void *data_sending_thread(void *arg) { (void)arg; // 未使用参数 while (running) { // 创建JSON数据 pthread_mutex_lock(&data_mutex); char *json_str = create_obd_json(¤t_data); pthread_mutex_unlock(&data_mutex); // 发送UDP数据 if (json_str) { send_json_via_udp(json_str); free(json_str); } // 每秒发送10次 (100ms间隔) usleep(100000); } return NULL; } int main() { printf("OBD-II 数据采集与转发程序\n"); printf("目标UDP: %s:%d\n", UDP_IP, UDP_PORT); // 初始化CAN总线 int can_sock = init_can_socket(CAN_INTERFACE); if (can_sock < 0) { fprintf(stderr, "CAN初始化失败\n"); return EXIT_FAILURE; } // 初始化UDP udp_sock = init_udp_socket(UDP_IP, UDP_PORT); if (udp_sock < 0) { fprintf(stderr, "UDP初始化失败\n"); close(can_sock); return EXIT_FAILURE; } // 初始化数据结构 memset(¤t_data, 0, sizeof(OBDData)); gettimeofday(¤t_data.timestamp, NULL); // 创建数据采集线程 pthread_t acq_thread; if (pthread_create(&acq_thread, NULL, data_acquisition_thread, &can_sock) != 0) { fprintf(stderr, "无法创建采集线程\n"); close(can_sock); close(udp_sock); return EXIT_FAILURE; } // 创建数据发送线程 pthread_t send_thread; if (pthread_create(&send_thread, NULL, data_sending_thread, NULL) != 0) { fprintf(stderr, "无法创建发送线程\n"); running = 0; pthread_join(acq_thread, NULL); close(can_sock); close(udp_sock); return EXIT_FAILURE; } // 主线程等待退出 printf("程序运行中,按Enter键退出...\n"); getchar(); // 等待用户输入 running = 0; // 清理资源 pthread_join(acq_thread, NULL); pthread_join(send_thread, NULL); close(can_sock); close(udp_sock); printf("程序已退出\n"); return EXIT_SUCCESS; }
# 安装json库 sudo apt install libjansson-dev #编译 gcc odb.c -o odb -ljansson -lpthread ./odb
测试程序
include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/time.h> #include <jansson.h> // JSON解析库 #include <time.h> #define UDP_PORT 5000 // 监听端口 #define BUFFER_SIZE 1024 // 缓冲区大小 #define STATS_INTERVAL 5 // 统计信息输出间隔(秒) typedef struct { long total_packets; // 总接收包数 long valid_json; // 有效JSON包数 long invalid_json; // 无效JSON包数 long parse_errors; // JSON解析错误数 long missing_fields; // 缺失字段数 double min_latency; // 最小延迟(ms) double max_latency; // 最大延迟(ms) double total_latency; // 总延迟 struct timeval last_print; // 上次统计时间 } ReceiverStats; // 解析时间戳字符串为timeval结构 int parse_timestamp(const char *timestamp_str, struct timeval *tv) { struct tm tm; long milliseconds; char *dot = strchr(timestamp_str, '.'); char *tz = strchr(timestamp_str, 'Z'); if (!dot || !tz) return -1; // 解析日期和时间部分 strptime(timestamp_str, "%Y-%m-%dT%H:%M:%S", &tm); // 解析毫秒部分 milliseconds = atol(dot + 1); // 转换为time_t tv->tv_sec = mktime(&tm); tv->tv_usec = milliseconds * 1000; // 毫秒转微秒 return 0; } // 计算当前时间与时间戳之间的延迟(毫秒) double calculate_latency(const struct timeval *timestamp, const struct timeval *current) { long sec_diff = current->tv_sec - timestamp->tv_sec; long usec_diff = current->tv_usec - timestamp->tv_usec; if (usec_diff < 0) { sec_diff--; usec_diff += 1000000; } return (sec_diff * 1000.0) + (usec_diff / 1000.0); } // 更新统计信息 void update_stats(ReceiverStats *stats, int valid, double latency) { stats->total_packets++; if (valid) { stats->valid_json++; // 更新延迟统计 if (stats->min_latency < 0 || latency < stats->min_latency) { stats->min_latency = latency; } if (latency > stats->max_latency) { stats->max_latency = latency; } stats->total_latency += latency; } else { stats->invalid_json++; } } // 打印统计信息 void print_stats(const ReceiverStats *stats) { double avg_latency = (stats->valid_json > 0) ? stats->total_latency / stats->valid_json : 0.0; printf("\n=== 接收统计 ===\n"); printf("总包数: %ld\n", stats->total_packets); printf("有效JSON: %ld (%.2f%%)\n", stats->valid_json, (stats->total_packets > 0) ? (100.0 * stats->valid_json / stats->total_packets) : 0.0); printf("无效JSON: %ld (%.2f%%)\n", stats->invalid_json, (stats->total_packets > 0) ? (100.0 * stats->invalid_json / stats->total_packets) : 0.0); printf("解析错误: %ld\n", stats->parse_errors); printf("缺失字段: %ld\n", stats->missing_fields); printf("延迟(ms): 最小=%.2f, 最大=%.2f, 平均=%.2f\n", stats->min_latency, stats->max_latency, avg_latency); printf("================\n"); } // 处理接收到的JSON数据 void process_json_data(const char *json_str, ReceiverStats *stats) { json_t *root; json_error_t error; // 解析JSON root = json_loads(json_str, 0, &error); if (!root) { stats->parse_errors++; fprintf(stderr, "JSON解析错误 (行 %d, 列 %d): %s\n", error.line, error.column, error.text); return; } // 获取当前时间 struct timeval current_time; gettimeofday(¤t_time, NULL); // 提取时间戳 json_t *timestamp_json = json_object_get(root, "timestamp"); if (!json_is_string(timestamp_json)) { stats->missing_fields++; json_decref(root); return; } const char *timestamp_str = json_string_value(timestamp_json); struct timeval data_time; if (parse_timestamp(timestamp_str, &data_time) ){ fprintf(stderr, "时间戳格式错误: %s\n", timestamp_str); stats->missing_fields++; json_decref(root); return; } // 计算延迟 double latency = calculate_latency(&data_time, ¤t_time); // 提取数据字段 int rpm = 0, speed = 0, coolant_temp = 0, engine_load = 0; float fuel_pressure = 0.0, fuel_level = 0.0; json_t *rpm_json = json_object_get(root, "rpm"); if (json_is_integer(rpm_json)) rpm = json_integer_value(rpm_json); json_t *speed_json = json_object_get(root, "speed"); if (json_is_integer(speed_json)) speed = json_integer_value(speed_json); json_t *coolant_temp_json = json_object_get(root, "coolant_temp"); if (json_is_integer(coolant_temp_json)) coolant_temp = json_integer_value(coolant_temp_json); json_t *engine_load_json = json_object_get(root, "engine_load"); if (json_is_integer(engine_load_json)) engine_load = json_integer_value(engine_load_json); json_t *fuel_pressure_json = json_object_get(root, "fuel_pressure"); if (json_is_real(fuel_pressure_json)) fuel_pressure = json_real_value(fuel_pressure_json); else if (json_is_integer(fuel_pressure_json)) fuel_pressure = json_integer_value(fuel_pressure_json); json_t *fuel_level_json = json_object_get(root, "fuel_level"); if (json_is_real(fuel_level_json)) fuel_level = json_real_value(fuel_level_json); else if (json_is_integer(fuel_level_json)) fuel_level = json_integer_value(fuel_level_json); // 打印接收到的数据 printf("\n===== 车辆数据 =====\n"); printf("时间戳: %s (延迟: %.2f ms)\n", timestamp_str, latency); printf("发动机转速: %d RPM\n", rpm); printf("车速: %d km/h\n", speed); printf("冷却液温度: %d ℃\n", coolant_temp); printf("燃油压力: %.1f kPa\n", fuel_pressure); printf("燃油液位: %.1f%%\n", fuel_level); printf("发动机负载: %d%%\n", engine_load); printf("====================\n"); // 更新统计信息 update_stats(stats, 1, latency); // 清理JSON对象 json_decref(root); } int main() { int sockfd; struct sockaddr_in servaddr, cliaddr; socklen_t len; char buffer[BUFFER_SIZE]; ReceiverStats stats = {0}; // 初始化统计 stats.min_latency = -1; stats.max_latency = -1; gettimeofday(&stats.last_print, NULL); printf("===== OBD数据UDP接收测试程序 =====\n"); printf("监听端口: %d\n", UDP_PORT); printf("按Ctrl+C退出...\n\n"); // 创建UDP套接字 if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } memset(&servaddr, 0, sizeof(servaddr)); memset(&cliaddr, 0, sizeof(cliaddr)); // 配置服务器地址 servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = INADDR_ANY; servaddr.sin_port = htons(UDP_PORT); // 绑定套接字 if (bind(sockfd, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { perror("bind failed"); close(sockfd); exit(EXIT_FAILURE); } // 设置接收超时 struct timeval tv; tv.tv_sec = 1; // 1秒超时 tv.tv_usec = 0; setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); while (1) { len = sizeof(cliaddr); ssize_t n = recvfrom(sockfd, (char *)buffer, BUFFER_SIZE, 0, (struct sockaddr *)&cliaddr, &len); if (n < 0) { // 超时或其他错误,检查是否应该打印统计信息 struct timeval now; gettimeofday(&now, NULL); double elapsed = (now.tv_sec - stats.last_print.tv_sec) + (now.tv_usec - stats.last_print.tv_usec) / 1000000.0; if (elapsed >= STATS_INTERVAL) { print_stats(&stats); stats.last_print = now; } continue; } // 确保字符串以空字符结束 buffer[n] = '\0'; // 打印原始数据 printf("收到 %ld 字节数据:\n%s\n", n, buffer); // 处理JSON数据 process_json_data(buffer, &stats); // 检查是否应该打印统计信息 struct timeval now; gettimeofday(&now, NULL); double elapsed = (now.tv_sec - stats.last_print.tv_sec) + (now.tv_usec - stats.last_print.tv_usec) / 1000000.0; if (elapsed >= STATS_INTERVAL) { print_stats(&stats); stats.last_print = now; } } close(sockfd); return 0; }
#编译 gcc obd_rive.c -o obd_rive -ljansson -lpthread ./obd_rive
五,成果演示
可以看到设置模拟器转载7035,树莓派解析出来的数据也是7035