如何在C语言中通过SocketCAN接口创建含非8位数据的CAN帧
CAN帧数据拆分与位级存储实操方案
先给你指出原代码里的明显错误:
- 给
frame2设置ID后,错误修改了frame.can_dlc,应该是frame2.can_dlc = 6(三个16位数据正好占6字节) - 直接把16位数值赋值给8位的
frame.data[x]会被截断,比如32766是0x7FFE,赋值后只剩0xFE,完全丢失高字节数据
一、处理16位数据:拆分高低字节
CAN帧的data是8位字节数组,16位数据必须拆成高字节和低字节分别存入相邻的数组元素,用C语言位操作就能实现:
- 低字节:取数值的最后8位,用
val & 0xFF - 高字节:把数值右移8位后取最后8位,用
(val >> 8) & 0xFF
不管是无符号uint16_t还是有符号int16_t,这个方法都适用(有符号数的算术移位不影响字节拆分,还原时按对应类型读取即可)。
比如你要发送的三个16位数值,拆分后存储的代码:
// 用stdint.h的明确类型避免歧义 int16_t val1 = 32766; int16_t val2 = -32765; int16_t val3 = 20542; // 拆分存入frame2的data数组 frame2.data[0] = val1 & 0xFF; // val1的低字节 frame2.data[1] = (val1 >> 8) & 0xFF; // val1的高字节 frame2.data[2] = val2 & 0xFF; frame2.data[3] = (val2 >> 8) & 0xFF; frame2.data[4] = val3 & 0xFF; frame2.data[5] = (val3 >> 8) & 0xFF;
二、处理小于8位的位级数据:位打包
把多个小数值塞进一个字节,需要用位掩码和移位操作打包。举个实际例子:
- 把一个4位数值(0-15)和一个3位数值(0-7)塞进同一个字节:4位数值放高4位,3位放低3位,剩余1位留空或设0。
打包代码示例:
uint8_t four_bit_val = 12; // 二进制0b1100 uint8_t three_bit_val = 5; // 二进制0b101 // 打包:4位值左移4位,3位值直接放低3位,合并结果 frame3.data[0] = (four_bit_val << 4) | (three_bit_val & 0x07); // 最终结果是0b11000101 = 0xC5
如果要还原数据,反向操作即可:
uint8_t packed = frame3.data[0]; uint8_t extracted_four = (packed >> 4) & 0x0F; // 提取高4位 uint8_t extracted_three = packed & 0x07; // 提取低3位
三、完整修正后的代码
包含三个帧的实现,同时修复原代码错误:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <linux/can.h> #include <linux/can/raw.h> #include <stdint.h> // 引入明确的整数类型 int main(int argc, char **argv) { int s; struct sockaddr_can addr; struct ifreq ifr; struct can_frame frame_8bit; // 8个8位数值的帧 struct can_frame frame_16bit; // 三个16位数值的帧 struct can_frame frame_bit; // 位级存储的帧 if ((s = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) { perror("Socket"); return 1; } strcpy(ifr.ifr_name, "vcan0" ); ioctl(s, SIOCGIFINDEX, &ifr); memset(&addr, 0, sizeof(addr)); addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("Bind"); return 1; } // 第一帧:8个8位数值(原代码正确部分) frame_8bit.can_id = 0x500; frame_8bit.can_dlc = 8; frame_8bit.data[0] = 122; frame_8bit.data[1] = 150; frame_8bit.data[2] = -27; frame_8bit.data[3] = -22; frame_8bit.data[4] = 160; frame_8bit.data[5] = 160; frame_8bit.data[6] = -60; frame_8bit.data[7] = 90; // 第二帧:三个16位数值 frame_16bit.can_id = 0x501; frame_16bit.can_dlc = 6; // 3*2=6字节 int16_t val1 = 32766; int16_t val2 = -32765; int16_t val3 = 20542; // 拆分高低字节存入 frame_16bit.data[0] = val1 & 0xFF; frame_16bit.data[1] = (val1 >> 8) & 0xFF; frame_16bit.data[2] = val2 & 0xFF; frame_16bit.data[3] = (val2 >> 8) & 0xFF; frame_16bit.data[4] = val3 & 0xFF; frame_16bit.data[5] = (val3 >> 8) & 0xFF; // 第三帧:位级存储示例(一个字节存4位+3位数值,另一个字节存2个2位数值) frame_bit.can_id = 0x502; frame_bit.can_dlc = 2; uint8_t four_bit = 12; uint8_t three_bit = 5; uint8_t two_bit1 = 3; uint8_t two_bit2 = 1; // 打包第一个字节 frame_bit.data[0] = (four_bit << 4) | (three_bit & 0x07); // 打包第二个字节:两个2位数值,分别占高2位和低2位 frame_bit.data[1] = (two_bit1 << 6) | (two_bit2 & 0x03); // 发送三个帧 if (write(s, &frame_8bit, sizeof(struct can_frame)) != sizeof(struct can_frame)) { perror("Write frame_8bit"); return 1; } if (write(s, &frame_16bit, sizeof(struct can_frame)) != sizeof(struct can_frame)) { perror("Write frame_16bit"); return 1; } if (write(s, &frame_bit, sizeof(struct can_frame)) != sizeof(struct can_frame)) { perror("Write frame_bit"); return 1; } if (close(s) < 0) { perror("Close"); return 1; } return 0; }
内容的提问来源于stack exchange,提问作者willem12
相关产品推荐
相关产品推荐

