STM32 CAN总线通讯实验

STM32 CAN总线通讯实验

一、实验目的与原理

1.1 实验目的

  1. 掌握STM32 CAN控制器的工作原理与配置方法
  2. 学会使用CAN总线进行节点间通信
  3. 理解CAN协议帧结构(数据帧、远程帧、错误帧、过载帧)
  4. 掌握CAN总线的滤波与屏蔽技术
  5. 实现多节点CAN网络通信

1.2 CAN总线原理

CAN总线拓扑结构:
        ┌─────────┐
        │ 终端电阻 │
        │  120Ω   │
        └────┬────┘
             │
    ┌────────┼────────┐
    │        │        │
┌───▼───┐ ┌───▼───┐ ┌───▼───┐
│ CAN节点 │ │ CAN节点 │ │ CAN节点 │
│ (STM32) │ │ (STM32) │ │ (STM32) │
└───────┘ └───────┘ └───────┘
    │CANH     │CANH     │CANH
    │CANL     │CANL     │CANL
    └────────┴────────┘

1.3 CAN帧类型对比

帧类型 用途 特点
数据帧 发送数据 包含ID、数据、CRC等
远程帧 请求数据 无数据场,请求特定ID发送数据
错误帧 错误通知 检测到错误时发送
过载帧 延迟下一帧 接收节点未准备好时发送

二、硬件设计

2.1 硬件清单

模块 型号 数量 说明
主控 STM32F103C8T6 2 CAN节点
CAN收发器 TJA1050 2 CAN收发器
终端电阻 120Ω 2 总线匹配电阻
USB-CAN适配器 USBCAN-II 1 PC端监控(可选)
杜邦线 若干 连接线路

2.2 硬件连接

/* CAN硬件连接定义 */
// STM32F103C8T6 CAN引脚
#define CAN_TX_PIN       GPIO_Pin_12  // PB12 - CAN1_TX
#define CAN_RX_PIN       GPIO_Pin_11  // PB11 - CAN1_RX
#define CAN_GPIO_PORT    GPIOB

// TJA1050连接
// TJA1050_TXD -> PB12 (CAN1_TX)
// TJA1050_RXD -> PB11 (CAN1_RX)
// TJA1050_VCC -> 5V
// TJA1050_GND -> GND
// TJA1050_CANH -> CAN总线CANH
// TJA1050_CANL -> CAN总线CANL

// 终端电阻连接
// 总线两端各接120Ω电阻(CANH与CANL之间)

2.3 波特率计算

/* CAN波特率配置 */
// APB1时钟 = 36MHz
// 目标波特率 = 500kbps
// 计算公式:波特率 = APB1_CLK / ((Prescaler) * (BS1 + BS2 + 1))

// 配置示例:500kbps
#define CAN_PRESCALER   6        // 预分频器
#define CAN_BS1        8        // 时间段1 (8个时间单元)
#define CAN_BS2        1        // 时间段2 (1个时间单元)
// 波特率 = 36MHz / (6 * (8 + 1 + 1)) = 500kbps

// 其他常用波特率配置
typedef struct {
    uint32_t baudrate;      // 波特率
    uint16_t prescaler;    // 预分频器
    uint8_t bs1;           // 时间段1
    uint8_t bs2;           // 时间段2
} CAN_BaudrateConfig;

CAN_BaudrateConfig can_baudrates[] = {
    {1000, 3, 8, 1},   // 1Mbps
    {500,  6, 8, 1},   // 500kbps
    {250,  12, 8, 1},  // 250kbps
    {125,  24, 8, 1},  // 125kbps
    {100,  30, 8, 1},  // 100kbps
    {50,   60, 8, 1}   // 50kbps
};

三、软件设计(标准外设库)

3.1 CAN初始化

/* CAN初始化代码 */
#include "stm32f10x.h"
#include "stm32f10x_can.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
#include "misc.h"
#include <stdio.h>

// CAN句柄
CAN_InitTypeDef        CAN_InitStructure;
CAN_FilterInitTypeDef  CAN_FilterInitStructure;
CanTxMsg TxMessage;
CanRxMsg RxMessage;

// 全局变量
volatile uint8_t can_rx_flag = 0;
volatile uint8_t can_tx_flag = 0;
volatile uint8_t can_error_flag = 0;
uint32_t can_error_code = 0;

/* CAN GPIO初始化 */
void CAN_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 使能GPIOB时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    // 配置CAN引脚
    GPIO_InitStructure.GPIO_Pin = CAN_TX_PIN;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽输出
    GPIO_Init(CAN_GPIO_PORT, &GPIO_InitStructure);
    
    GPIO_InitStructure.GPIO_Pin = CAN_RX_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;     // 上拉输入
    GPIO_Init(CAN_GPIO_PORT, &GPIO_InitStructure);
}

/* CAN控制器初始化 */
void CAN_Config(uint32_t baudrate) {
    // 使能CAN1时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);
    
    // 复位CAN控制器
    CAN_DeInit(CAN1);
    
    // 配置CAN参数
    CAN_InitStructure.CAN_TTCM = DISABLE;     // 禁止时间触发通信
    CAN_InitStructure.CAN_ABOM = ENABLE;      // 使能自动离线管理
    CAN_InitStructure.CAN_AWUM = ENABLE;      // 使能自动唤醒
    CAN_InitStructure.CAN_NART = DISABLE;     // 使能自动重传
    CAN_InitStructure.CAN_RFLM = DISABLE;     // 禁止接收FIFO锁定
    CAN_InitStructure.CAN_TXFP = DISABLE;     // 禁止发送FIFO优先级
    CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;  // 正常工作模式
    
    // 配置波特率
    CAN_InitStructure.CAN_SJW = CAN_SJW_1tq;    // 重新同步跳跃宽度
    CAN_InitStructure.CAN_BS1 = CAN_BS1_8tq;    // 时间段1
    CAN_InitStructure.CAN_BS2 = CAN_BS2_1tq;    // 时间段2
    CAN_InitStructure.CAN_Prescaler = 6;          // 预分频器,500kbps
    
    // 初始化CAN
    if (CAN_Init(CAN1, &CAN_InitStructure) == CAN_InitStatus_Failed) {
        printf("CAN Initialization Failed!\n");
        return;
    }
    
    printf("CAN Initialized at %d kbps\n", baudrate/1000);
}

/* CAN过滤器配置 */
void CAN_Filter_Config(void) {
    // 配置过滤器0
    CAN_FilterInitStructure.CAN_FilterNumber = 0;                // 过滤器0
    CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdMask;  // 掩码模式
    CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; // 32位
    
    // 接收所有标准ID(11位)
    CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000;        // 标准ID高16位
    CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000;         // 标准ID低16位
    CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000;    // 掩码高16位(全0表示不过滤)
    CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000;     // 掩码低16位
    
    CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0; // 关联到FIFO0
    CAN_FilterInitStructure.CAN_FilterActivation = ENABLE;      // 使能过滤器
    CAN_FilterInit(&CAN_FilterInitStructure);
    
    printf("CAN Filter Configured\n");
}

/* CAN中断配置 */
void CAN_NVIC_Config(void) {
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 配置CAN接收中断
    NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    // 使能CAN接收中断
    CAN_ITConfig(CAN1, CAN_IT_FMP0, ENABLE);  // FIFO0消息挂起中断
    CAN_ITConfig(CAN1, CAN_IT_TME, ENABLE);   // 发送邮箱空中断
    CAN_ITConfig(CAN1, CAN_IT_ERR, ENABLE);   // 错误中断
    
    printf("CAN Interrupt Configured\n");
}

3.2 CAN发送与接收

/* CAN发送函数 */
uint8_t CAN_Send_Message(uint32_t id, uint8_t *data, uint8_t length) {
    uint8_t mailbox;
    uint16_t timeout = 0xFFFF;
    
    // 配置发送消息
    TxMessage.StdId = id;           // 标准ID(11位)
    TxMessage.ExtId = 0;            // 扩展ID(不使用)
    TxMessage.IDE = CAN_Id_Standard; // 标准帧
    TxMessage.RTR = CAN_RTR_Data;    // 数据帧
    TxMessage.DLC = length;          // 数据长度(0-8字节)
    
    // 填充数据
    for (uint8_t i = 0; i < length; i++) {
        TxMessage.Data[i] = data[i];
    }
    
    // 选择空邮箱并发送
    mailbox = CAN_Transmit(CAN1, &TxMessage);
    
    // 等待发送完成
    while ((CAN_TransmitStatus(CAN1, mailbox) != CAN_TxStatus_Ok) && timeout) {
        timeout--;
    }
    
    if (timeout == 0) {
        printf("CAN Send Timeout!\n");
        return 0;
    }
    
    printf("CAN Message Sent: ID=0x%03X, Length=%d\n", id, length);
    return 1;
}

/* CAN接收中断服务函数 */
void USB_LP_CAN1_RX0_IRQHandler(void) {
    if (CAN_GetITStatus(CAN1, CAN_IT_FMP0) != RESET) {
        // 接收消息
        CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);
        
        // 设置接收标志
        can_rx_flag = 1;
        
        // 清除中断标志
        CAN_ClearITPendingBit(CAN1, CAN_IT_FMP0);
    }
    
    // 发送完成中断
    if (CAN_GetITStatus(CAN1, CAN_IT_TME) != RESET) {
        can_tx_flag = 1;
        CAN_ClearITPendingBit(CAN1, CAN_IT_TME);
    }
    
    // 错误中断
    if (CAN_GetITStatus(CAN1, CAN_IT_ERR) != RESET) {
        can_error_flag = 1;
        can_error_code = CAN_GetLastErrorCode(CAN1);
        CAN_ClearITPendingBit(CAN1, CAN_IT_ERR);
    }
}

/* 处理接收到的CAN消息 */
void Process_CAN_Message(void) {
    if (can_rx_flag) {
        printf("CAN Message Received:\n");
        printf("  ID: 0x%03X\n", RxMessage.StdId);
        printf("  IDE: %s\n", (RxMessage.IDE == CAN_Id_Standard) ? "Standard" : "Extended");
        printf("  RTR: %s\n", (RxMessage.RTR == CAN_RTR_Data) ? "Data" : "Remote");
        printf("  DLC: %d bytes\n", RxMessage.DLC);
        
        if (RxMessage.DLC > 0) {
            printf("  Data: ");
            for (uint8_t i = 0; i < RxMessage.DLC; i++) {
                printf("%02X ", RxMessage.Data[i]);
            }
            printf("\n");
        }
        
        can_rx_flag = 0;
    }
}

四、实验内容

4.1 基础实验:自发自收

/* 实验1:CAN自发自收 */
void CAN_Loopback_Test(void) {
    uint8_t test_data[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
    
    printf("=== CAN Loopback Test ===\n");
    
    // 配置为回环模式
    CAN_InitStructure.CAN_Mode = CAN_Mode_LoopBack;
    CAN_Init(CAN1, &CAN_InitStructure);
    
    // 发送测试数据
    if (CAN_Send_Message(0x123, test_data, 8)) {
        printf("Loopback test data sent\n");
    }
    
    // 延时等待接收
    for (volatile uint32_t i = 0; i < 1000000; i++);
    
    // 检查接收数据
    if (can_rx_flag) {
        Process_CAN_Message();
        printf("Loopback test PASSED!\n");
    } else {
        printf("Loopback test FAILED!\n");
    }
}

4.2 双机通信实验

/* 实验2:双机CAN通信 */
typedef enum {
    NODE_A = 0x100,   // 节点A的ID
    NODE_B = 0x200    // 节点B的ID
} NodeID;

// 节点A程序
void NodeA_Task(void) {
    uint8_t tx_data[8];
    static uint8_t counter = 0;
    
    // 发送数据到节点B
    tx_data[0] = counter++;
    tx_data[1] = 0xAA;
    tx_data[2] = 0xBB;
    tx_data[3] = 0xCC;
    
    CAN_Send_Message(NODE_B, tx_data, 4);
    
    // 处理接收到的数据
    Process_CAN_Message();
}

// 节点B程序
void NodeB_Task(void) {
    uint8_t tx_data[8];
    static uint8_t counter = 0;
    
    // 发送数据到节点A
    tx_data[0] = counter++;
    tx_data[1] = 0xDD;
    tx_data[2] = 0xEE;
    tx_data[3] = 0xFF;
    
    CAN_Send_Message(NODE_A, tx_data, 4);
    
    // 处理接收到的数据
    Process_CAN_Message();
}

4.3 多节点通信实验

/* 实验3:多节点CAN网络 */
#define MAX_NODES 5
#define BROADCAST_ID 0x7FF  // 广播ID

typedef struct {
    uint32_t node_id;        // 节点ID
    uint8_t node_type;       // 节点类型
    uint8_t status;         // 节点状态
    uint32_t last_heartbeat; // 最后心跳时间
} CAN_Node;

CAN_Node can_network[MAX_NODES];

/* 发送心跳包 */
void Send_Heartbeat(uint32_t my_node_id) {
    uint8_t heartbeat_data[8];
    
    heartbeat_data[0] = 0x01;  // 心跳包类型
    heartbeat_data[1] = my_node_id & 0xFF;
    heartbeat_data[2] = (my_node_id >> 8) & 0xFF;
    heartbeat_data[3] = 0x00;   // 状态正常
    
    CAN_Send_Message(BROADCAST_ID, heartbeat_data, 4);
}

/* 处理网络消息 */
void Process_Network_Message(void) {
    if (can_rx_flag) {
        if (RxMessage.StdId == BROADCAST_ID) {
            // 广播消息
            if (RxMessage.Data[0] == 0x01) {
                // 心跳包
                uint32_t node_id = RxMessage.Data[1] | (RxMessage.Data[2] << 8);
                printf("Heartbeat from node 0x%04X\n", node_id);
                
                // 更新节点状态
                for (uint8_t i = 0; i < MAX_NODES; i++) {
                    if (can_network[i].node_id == node_id) {
                        can_network[i].last_heartbeat = Get_SystemTick();
                        can_network[i].status = RxMessage.Data[3];
                        break;
                    }
                }
            }
        }
        can_rx_flag = 0;
    }
}

参考代码 STM32 CAN总线通讯实验 www.youwenfan.com/contentcst/133244.html

五、高级应用

5.1 CANopen协议实现

/* CANopen协议基础实现 */
#define COB_ID_NMT       0x000  // 网络管理
#define COB_ID_SYNC      0x080  // 同步
#define COB_ID_EMCY      0x080  // 紧急
#define COB_ID_PDO1_TX  0x180  // 过程数据对象1发送
#define COB_ID_PDO1_RX  0x200  // 过程数据对象1接收
#define COB_ID_SDO_TX   0x580  // 服务数据对象发送
#define COB_ID_SDO_RX   0x600  // 服务数据对象接收

/* CANopen NMT命令 */
typedef enum {
    NMT_START_REMOTE = 0x01,
    NMT_STOP_REMOTE = 0x02,
    NMT_ENTER_PREOP = 0x80,
    NMT_RESET_NODE = 0x81,
    NMT_RESET_COMM = 0x82
} NMT_Command;

/* 发送NMT命令 */
void CANopen_Send_NMT(uint8_t node_id, NMT_Command cmd) {
    uint8_t nmt_data[2];
    
    nmt_data[0] = cmd;
    nmt_data[1] = node_id;
    
    CAN_Send_Message(COB_ID_NMT, nmt_data, 2);
}

/* 发送PDO数据 */
void CANopen_Send_PDO(uint8_t node_id, uint8_t *data, uint8_t length) {
    uint32_t cob_id = COB_ID_PDO1_TX + node_id;
    CAN_Send_Message(cob_id, data, length);
}

5.2 汽车OBD-II诊断协议

/* OBD-II诊断协议 */
#define PID_ENGINE_RPM    0x0C  // 发动机转速
#define PID_VEHICLE_SPEED 0x0D  // 车速
#define PID_COOLANT_TEMP 0x05  // 冷却液温度
#define PID_FUEL_LEVEL   0x2F  // 燃油液位

/* 发送OBD请求 */
void OBD_Send_Request(uint8_t pid) {
    uint8_t obd_data[8];
    
    obd_data[0] = 0x02;  // 数据长度
    obd_data[1] = 0x01;  // 模式1:显示当前数据
    obd_data[2] = pid;   // PID
    obd_data[3] = 0x00;  // 填充
    obd_data[4] = 0x00;
    obd_data[5] = 0x00;
    obd_data[6] = 0x00;
    obd_data[7] = 0x00;
    
    CAN_Send_Message(0x7DF, obd_data, 8);  // 广播请求ID
}

/* 解析OBD响应 */
void Parse_OBD_Response(void) {
    if (can_rx_flag && RxMessage.StdId == 0x7E8) {
        uint8_t mode = RxMessage.Data[1];
        uint8_t pid = RxMessage.Data[2];
        uint16_t value;
        
        if (mode == 0x41) {  // 模式1响应
            switch (pid) {
                case PID_ENGINE_RPM:
                    value = (RxMessage.Data[3] << 8) | RxMessage.Data[4];
                    printf("Engine RPM: %d RPM\n", value / 4);
                    break;
                case PID_VEHICLE_SPEED:
                    printf("Vehicle Speed: %d km/h\n", RxMessage.Data[3]);
                    break;
                case PID_COOLANT_TEMP:
                    printf("Coolant Temp: %d °C\n", RxMessage.Data[3] - 40);
                    break;
            }
        }
        can_rx_flag = 0;
    }
}

六、主程序

6.1 完整主程序

/* 主程序 */
#include "stm32f10x.h"
#include "system_stm32f10x.h"

volatile uint32_t system_tick = 0;

void SysTick_Init(void) {
    SysTick_Config(SystemCoreClock / 1000);  // 1ms中断
}

void SysTick_Handler(void) {
    system_tick++;
}

uint32_t Get_SystemTick(void) {
    return system_tick;
}

int main(void) {
    // 系统初始化
    SystemInit();
    SysTick_Init();
    
    // 初始化串口
    USART1_Init();
    printf("\r\n=== STM32 CAN Bus Experiment ===\r\n");
    
    // 初始化CAN硬件
    CAN_GPIO_Init();
    CAN_Config(500000);  // 500kbps
    CAN_Filter_Config();
    CAN_NVIC_Config();
    
    // 测试CAN回环模式
    CAN_Loopback_Test();
    
    printf("\r\nStarting normal mode...\r\n");
    
    // 切换到正常模式
    CAN_InitStructure.CAN_Mode = CAN_Mode_Normal;
    CAN_Init(CAN1, &CAN_InitStructure);
    
    uint32_t last_send_time = 0;
    uint8_t node_id = NODE_A;  // 设置为节点A
    
    while (1) {
        uint32_t current_time = Get_SystemTick();
        
        // 每秒发送一次数据
        if (current_time - last_send_time >= 1000) {
            if (node_id == NODE_A) {
                NodeA_Task();
            } else {
                NodeB_Task();
            }
            last_send_time = current_time;
        }
        
        // 处理接收消息
        Process_CAN_Message();
        
        // 检查错误
        if (can_error_flag) {
            printf("CAN Error: 0x%08lX\r\n", can_error_code);
            can_error_flag = 0;
        }
        
        // 延时
        for (volatile uint32_t i = 0; i < 10000; i++);
    }
}

七、调试与测试

7.1 测试方法

/* CAN总线测试工具 */
void CAN_Test_Menu(void) {
    printf("\r\n=== CAN Test Menu ===\r\n");
    printf("1. Loopback Test\r\n");
    printf("2. Send Test Message\r\n");
    printf("3. Monitor Bus Traffic\r\n");
    printf("4. Error Counter\r\n");
    printf("5. Baudrate Test\r\n");
    printf("6. Stress Test\r\n");
    printf("Select option: ");
}

/* 压力测试 */
void CAN_Stress_Test(void) {
    uint32_t start_time = Get_SystemTick();
    uint32_t messages_sent = 0;
    uint8_t test_data[8] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x11, 0x22};
    
    printf("Starting CAN stress test for 10 seconds...\r\n");
    
    while ((Get_SystemTick() - start_time) < 10000) {
        if (CAN_Send_Message(0x123, test_data, 8)) {
            messages_sent++;
        }
        for (volatile uint32_t i = 0; i < 1000; i++);  // 小延时
    }
    
    printf("Stress test completed: %lu messages sent in 10 seconds\r\n", messages_sent);
    printf("Average rate: %lu messages/second\r\n", messages_sent / 10);
}

/* 错误统计 */
void CAN_Error_Statistics(void) {
    uint8_t esr = CAN1->ESR;
    
    printf("CAN Error Statistics:\r\n");
    printf("  REC (Receive Error Counter): %d\r\n", (esr >> 24) & 0xFF);
    printf("  TEC (Transmit Error Counter): %d\r\n", (esr >> 16) & 0xFF);
    printf("  LEC (Last Error Code): %d\r\n", (esr >> 4) & 0x07);
    
    switch ((esr >> 4) & 0x07) {
        case 0: printf("  No Error\r\n"); break;
        case 1: printf("  Stuff Error\r\n"); break;
        case 2: printf("  Form Error\r\n"); break;
        case 3: printf("  Acknowledgment Error\r\n"); break;
        case 4: printf("  Bit Recessive Error\r\n"); break;
        case 5: printf("  Bit Dominant Error\r\n"); break;
        case 6: printf("  CRC Error\r\n"); break;
        case 7: printf("  Set by Software\r\n"); break;
    }
}

八、常见问题与解决

8.1 故障排除

问题 可能原因 解决方法
无法发送数据 波特率不匹配 检查所有节点的波特率配置
接收不到数据 过滤器配置错误 检查过滤器掩码设置
总线错误频繁 终端电阻缺失 在总线两端各接120Ω电阻
通信不稳定 接线问题 检查CANH/CANL是否短路或断路
只有一个节点能发 自动离线管理 确保ABOM位使能

8.2 优化建议

/* CAN性能优化 */
void CAN_Optimization_Tips(void) {
    printf("CAN Optimization Tips:\r\n");
    printf("1. Use appropriate baudrate for cable length\r\n");
    printf("2. Enable automatic retransmission for reliability\r\n");
    printf("3. Implement proper filtering to reduce CPU load\r\n");
    printf("4. Use FIFO0 and FIFO1 for different priorities\r\n");
    printf("5. Monitor error counters for bus health\r\n");
    printf("6. Implement watchdog for error recovery\r\n");
    printf("7. Use DMA for high-speed data transfer\r\n");
    printf("8. Consider time-triggered CAN for real-time systems\r\n");
}

总结

本实验实现了STM32 CAN总线的完整功能:

核心功能:

  1. CAN控制器配置:波特率、工作模式、过滤器
  2. 数据收发:标准帧、扩展帧、远程帧
  3. 中断处理:接收、发送、错误中断
  4. 多节点通信:网络管理、心跳监测

实验内容:

  1. 自发自收测试:验证CAN控制器基本功能
  2. 双机通信:两个STM32节点互发数据
  3. 多节点网络:构建CAN总线网络
  4. 协议实现:CANopen、OBD-II诊断协议

应用场景:

专注于matlab/simulink,电子电路,编程