汽车CAN J1939协议完整编程源码和STM32移植指南

汽车CAN J1939协议完整编程源码和STM32移植指南。J1939是商用车(卡车、工程机械等)的标准高层CAN协议,基于CAN 2.0B扩展帧。


一、J1939协议核心概念

1. 协议栈架构

复制代码
应用层 (J1939-71) → 车辆参数、诊断消息
传输层 (J1939-21) → 多帧传输、连接管理
网络层 (J1939-81) → 地址管理、命名
数据链路层 → CAN 2.0B扩展帧
物理层 → CAN_H/CAN_L

2. 29位CAN ID结构

位域 位数 说明 示例值
Priority 3 优先级(0-7, 0最高) 6
Reserved 1 保留位(固定0) 0
Data Page 1 数据页(0/1) 0
PDU Format 8 PDU格式(0-255) 0xF0
PDU Specific 8 目标地址或组扩展 0x21
Source Address 8 源地址(0-253) 0x80

PGN计算PGN = (Data Page << 16) + (PDU Format << 8) + (PDU Specific)


二、推荐开源J1939协议栈

1. Open-SAE-J1939(最完整)

  • 语言:ANSI C (C89/C99)
  • 特点:符合MISRA C、无动态内存分配、支持多平台
  • 功能:传输层、应用层、诊断层、网络管理
  • GitHubhzappen/Open-SAE-J1939dav1977/Open-SAE-J1939

2. SupperWater/J1939-Protocol(中文文档)

  • 语言:C语言
  • 特点:高可移植性、接口封装良好
  • GiteeSupperWater/J1939-Protocol

3. xiaoyuan-cloud/CAN2.0-J1939协议栈(RTOS支持)

  • 语言:C语言
  • 特点:支持裸机和RTOS、1ms心跳
  • Giteexiaoyuan-cloud/CAN2.0-J1939协议栈

4. STM32专用移植

  • 项目qinyuan/Open-SAE-J1939-STM32
  • 基础:基于STM32Cube的CAN_Networking示例
  • 特点:直接集成到STM32工程

三、Open-SAE-J1939核心源码解析

1. 项目结构

复制代码
Open-SAE-J1939/
├── Src/
│   ├── Open_SAE_J1939.c      # 主协议栈
│   ├── Open_SAE_J1939.h
│   ├── Hardware.c            # 硬件抽象层
│   └── Hardware.h
├── Examples/
│   ├── Startup.txt           # 启动示例
│   └── Various_Examples.c
└── Documentation/
    └── Open SAE J1939.pdf    # 协议文档

2. 核心数据结构

c 复制代码
// Open_SAE_J1939.h
typedef struct {
    /* ECU信息 */
    uint8_t this_ECU_address;          // 本ECU地址
    uint8_t this_ECU_name[8];          // 64位NAME
    uint32_t this_identifications;     // 身份标识
    
    /* 网络管理 */
    uint8_t claimed_addresses[256];    // 已声明地址表
    uint8_t number_of_claimed_addresses;
    
    /* 传输协议 */
    struct {
        uint8_t sequence_number;
        uint8_t data[1785];            // 最大数据量
        uint16_t data_index;
        uint16_t data_length;
    } tp_data;
    
    /* 消息队列 */
    J1939_MESSAGE messages[50];
    uint8_t message_index;
} J1939;

3. 关键API函数

c 复制代码
// 初始化J1939协议栈
void Open_SAE_J1939_Startup_ECU(J1939 *j1939);

// 监听消息(主循环调用)
void Open_SAE_J1939_Listen_For_Messages(J1939 *j1939);

// 发送请求PGN
void Open_SAE_J1939_Send_Request_PGN(J1939 *j1939, uint32_t PGN, 
                                     uint8_t destination_address);

// 发送确认消息
void Open_SAE_J1939_Send_ACK(J1939 *j1939, uint8_t control_byte, 
                            uint32_t PGN, uint8_t destination_address);

// 发送数据消息
void Open_SAE_J1939_Send_Data(J1939 *j1939, uint32_t PGN, 
                             uint8_t destination_address, 
                             uint8_t *data, uint8_t data_length);

四、STM32F103完整移植代码

1. 硬件连接

复制代码
STM32F103C8T6 CAN引脚:
PA11 → CAN_RX
PA12 → CAN_TX

2. CAN驱动配置

c 复制代码
// can.c
#include "stm32f1xx_hal.h"

CAN_HandleTypeDef hcan;
CAN_FilterTypeDef sFilterConfig;

void CAN_Init(void)
{
    // 使能时钟
    __HAL_RCC_CAN1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // 配置CAN引脚
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
    // CAN初始化
    hcan.Instance = CAN1;
    hcan.Init.Prescaler = 9;           // 1MHz时钟,125kbps
    hcan.Init.Mode = CAN_MODE_NORMAL;
    hcan.Init.SyncJumpWidth = CAN_SJW_1TQ;
    hcan.Init.TimeSeg1 = CAN_BS1_13TQ;
    hcan.Init.TimeSeg2 = CAN_BS2_2TQ;
    hcan.Init.TimeTriggeredMode = DISABLE;
    hcan.Init.AutoBusOff = DISABLE;
    hcan.Init.AutoWakeUp = DISABLE;
    hcan.Init.AutoRetransmission = ENABLE;
    hcan.Init.ReceiveFifoLocked = DISABLE;
    hcan.Init.TransmitFifoPriority = DISABLE;
    
    if (HAL_CAN_Init(&hcan) != HAL_OK) {
        Error_Handler();
    }
    
    // 配置过滤器(接收所有扩展帧)
    sFilterConfig.FilterBank = 0;
    sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
    sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
    sFilterConfig.FilterIdHigh = 0x0000;
    sFilterConfig.FilterIdLow = 0x0000;
    sFilterConfig.FilterMaskIdHigh = 0x0000;
    sFilterConfig.FilterMaskIdLow = 0x0000;
    sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
    sFilterConfig.FilterActivation = ENABLE;
    sFilterConfig.SlaveStartFilterBank = 14;
    
    HAL_CAN_ConfigFilter(&hcan, &sFilterConfig);
    
    // 启动CAN
    HAL_CAN_Start(&hcan);
    HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING);
}

3. J1939移植接口

c 复制代码
// j1939_port.c - 硬件抽象层
#include "Open_SAE_J1939.h"
#include "can.h"

// CAN发送回调(J1939调用)
void Open_SAE_J1939_Send_Message(uint32_t ID, uint8_t data[8], uint8_t data_length)
{
    CAN_TxHeaderTypeDef TxHeader;
    uint32_t TxMailbox;
    
    // 配置J1939 CAN ID
    TxHeader.StdId = 0;
    TxHeader.ExtId = ID;          // 29位扩展ID
    TxHeader.IDE = CAN_ID_EXT;    // 扩展帧
    TxHeader.RTR = CAN_RTR_DATA;  // 数据帧
    TxHeader.DLC = data_length;   // 数据长度
    TxHeader.TransmitGlobalTime = DISABLE;
    
    // 发送CAN帧
    if (HAL_CAN_AddTxMessage(&hcan, &TxHeader, data, &TxMailbox) != HAL_OK) {
        // 发送失败处理
    }
}

// CAN接收回调(中断中调用)
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
    CAN_RxHeaderTypeDef RxHeader;
    uint8_t RxData[8];
    
    // 读取CAN消息
    if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK) {
        // 只处理扩展帧
        if (RxHeader.IDE == CAN_ID_EXT) {
            // 传递给J1939协议栈
            Open_SAE_J1939_Read_Message(RxHeader.ExtId, RxData, RxHeader.DLC);
        }
    }
}

// 获取时间戳(1ms精度)
uint32_t Open_SAE_J1939_Get_Timestamp(void)
{
    return HAL_GetTick();
}

// 延时函数
void Open_SAE_J1939_Delay(uint32_t milliseconds)
{
    HAL_Delay(milliseconds);
}

4. J1939配置文件

c 复制代码
// j1939_config.h
#ifndef J1939_CONFIG_H
#define J1939_CONFIG_H

// 硬件选择
#define PROCESSOR_STM32F103

// 功能配置
#define J1939_ACCEPT_CMD_ADDRESS     1   // 接受命令地址
#define J1939_ACCEPT_ADDRESS_CLAIMED 1   // 接受地址声明
#define J1939_ADDRESS_CLAIMING       1   // 启用地址声明
#define J1939_TP_CM                  1   // 传输协议连接管理
#define J1939_TP_DT                  1   // 传输协议数据传输

// 网络配置
#define J1939_THIS_ECU_NAME {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
#define J1939_THIS_IDENTIFICATIONS 0x12345678
#define J1939_THIS_ECU_ADDRESS 0x80      // 默认地址

// 内存配置
#define J1939_MESSAGE_QUEUE_SIZE 50      // 消息队列大小
#define J1939_TP_BUFFER_SIZE 1785        // TP缓冲区大小

#endif

5. 主应用程序

c 复制代码
// main.c
#include "stm32f1xx_hal.h"
#include "Open_SAE_J1939.h"

J1939 j1939_ecu;

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    CAN_Init();
    
    // 初始化J1939 ECU
    Open_SAE_J1939_Startup_ECU(&j1939_ecu);
    
    // 设置ECU信息
    uint8_t name[8] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0};
    memcpy(j1939_ecu.this_ECU_name, name, 8);
    j1939_ecu.this_identifications = 0x87654321;
    j1939_ecu.this_ECU_address = 0x80;
    
    // 启动地址声明
    Open_SAE_J1939_Send_Address_Claimed(&j1939_ecu);
    
    while (1) {
        // 监听CAN消息
        Open_SAE_J1939_Listen_For_Messages(&j1939_ecu);
        
        // 处理接收到的消息
        if (j1939_ecu.message_index > 0) {
            J1939_MESSAGE msg = j1939_ecu.messages[0];
            Process_J1939_Message(&msg);
            
            // 移除已处理消息
            for (uint8_t i = 0; i < j1939_ecu.message_index - 1; i++) {
                j1939_ecu.messages[i] = j1939_ecu.messages[i + 1];
            }
            j1939_ecu.message_index--;
        }
        
        // 发送发动机转速(示例)
        static uint32_t last_send = 0;
        if (HAL_GetTick() - last_send > 100) {  // 100ms周期
            Send_Engine_Speed(&j1939_ecu);
            last_send = HAL_GetTick();
        }
        
        HAL_Delay(1);  // 1ms延时
    }
}

// 处理J1939消息
void Process_J1939_Message(J1939_MESSAGE *msg)
{
    uint32_t pgn = (msg->ID >> 8) & 0x3FFFF;  // 提取PGN
    
    switch (pgn) {
        case 0xF004:  // 发动机转速
            if (msg->data_length >= 8) {
                uint16_t rpm = (msg->data[5] << 8) | msg->data[4];
                rpm *= 0.125;  // 解析为RPM
                // 处理转速数据
            }
            break;
            
        case 0xFEEE:  // 地址声明
            // 处理地址声明
            break;
            
        case 0x00EA00:  // 请求PGN
            // 处理PGN请求
            break;
    }
}

// 发送发动机转速(PGN 0xF004)
void Send_Engine_Speed(J1939 *j1939)
{
    uint8_t data[8] = {0};
    uint16_t rpm = 1500;  // 示例:1500 RPM
    
    // 发动机转速数据格式
    data[0] = 0xFF;  // 发动机实例
    data[1] = 0xFF;  // 发动机类型
    data[2] = 0xFF;  // 保留
    data[3] = 0xFF;  // 保留
    data[4] = (rpm / 0.125) & 0xFF;        // 转速低字节
    data[5] = ((rpm / 0.125) >> 8) & 0xFF; // 转速高字节
    data[6] = 0xFF;  // 保留
    data[7] = 0xFF;  // 保留
    
    // 发送消息(优先级6,目标地址全局)
    Open_SAE_J1939_Send_Data(j1939, 0xF004, 0xFF, data, 8);
}

五、常用PGN示例代码

1. 发动机参数(PGN 0xF004)

c 复制代码
// 发送发动机参数
void Send_Engine_Parameters(J1939 *j1939, uint8_t sa)
{
    uint8_t data[8];
    
    // 发动机转速(0.125 RPM/bit)
    uint16_t rpm = 1500;
    data[0] = 0x00;  // 发动机实例
    data[1] = 0x00;  // 发动机类型
    data[2] = 0xFF;  // 保留
    data[3] = 0xFF;  // 保留
    data[4] = (rpm / 0.125) & 0xFF;
    data[5] = ((rpm / 0.125) >> 8) & 0xFF;
    data[6] = 0xFF;  // 保留
    data[7] = 0xFF;  // 保留
    
    // 优先级3,PGN 0xF004,目标地址sa
    uint32_t can_id = (3 << 26) | (0xF004 << 8) | sa;
    Open_SAE_J1939_Send_Message(can_id, data, 8);
}

2. 车辆速度(PGN 0xFEF1)

c 复制代码
// 发送车辆速度
void Send_Vehicle_Speed(J1939 *j1939, uint8_t sa, float speed_kmh)
{
    uint8_t data[8] = {0};
    
    // 速度(1/256 km/h per bit)
    uint16_t speed = speed_kmh * 256;
    data[0] = speed & 0xFF;
    data[1] = (speed >> 8) & 0xFF;
    
    // 其他参数
    data[2] = 0xFF;  // 保留
    data[3] = 0xFF;  // 保留
    data[4] = 0xFF;  // 保留
    data[5] = 0xFF;  // 保留
    data[6] = 0xFF;  // 保留
    data[7] = 0xFF;  // 保留
    
    // 优先级6,PGN 0xFEF1
    uint32_t can_id = (6 << 26) | (0xFEF1 << 8) | sa;
    Open_SAE_J1939_Send_Message(can_id, data, 8);
}

3. 诊断消息DM1(PGN 0xFECA)

c 复制代码
// 发送激活的诊断故障码
void Send_DM1_Active_DTCs(J1939 *j1939, uint8_t sa)
{
    uint8_t data[8];
    
    // 灯状态
    data[0] = 0x00;  // 保护灯:关闭
    data[0] |= 0x01; // 红灯:激活
    data[0] |= 0x02; // 琥珀灯:激活
    data[0] |= 0x04; // 保护灯:关闭
    data[0] |= 0x08; // 闪烁:关闭
    
    // SPN 190 - 发动机转速
    data[1] = 190 & 0xFF;          // SPN低字节
    data[2] = (190 >> 8) & 0xFF;   // SPN中字节
    data[3] = (190 >> 16) & 0x07;  // SPN高3位
    data[3] |= (4 << 3);           // FMI 4 - 数据异常
    
    // 发生次数
    data[4] = 1;  // 发生1次
    
    // 其他
    data[5] = 0xFF;
    data[6] = 0xFF;
    data[7] = 0xFF;
    
    // 优先级6,PGN 0xFECA
    uint32_t can_id = (6 << 26) | (0xFECA << 8) | sa;
    Open_SAE_J1939_Send_Message(can_id, data, 8);
}

六、多帧传输(TP)示例

1. 发送长消息(>8字节)

c 复制代码
// 发送多帧数据
void Send_Long_Message(J1939 *j1939, uint8_t da, uint8_t *data, uint16_t length)
{
    // 使用传输协议发送
    Open_SAE_J1939_Send_TP_Data(j1939, 0x00EB00, da, data, length);
}

// 接收多帧数据回调
void TP_Data_Received_Callback(uint32_t pgn, uint8_t sa, uint8_t *data, uint16_t length)
{
    printf("收到长消息,PGN: 0x%06lX,长度: %d\n", pgn, length);
    
    // 处理数据
    switch (pgn) {
        case 0x00EB00:  // 传输协议连接管理
            Process_TP_Message(data, length);
            break;
    }
}

参考代码 汽车CAN1939协议编程源码 www.youwenfan.com/contentcsu/69982.html

七、调试与测试工具

1. PC端测试工具

  • CANoe/CANalyzer:Vector公司的专业工具
  • PCAN-View:PEAK-System的免费工具
  • SavvyCAN:开源CAN分析仪
  • CANable:开源USB转CAN适配器

2. STM32调试配置

c 复制代码
// 调试输出(通过串口)
void J1939_Debug_Print(const char *format, ...)
{
    char buffer[256];
    va_list args;
    va_start(args, format);
    vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);
    
    // 通过串口输出
    HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), 1000);
}

// 在协议栈中添加调试
#define J1939_DEBUG 1
#if J1939_DEBUG
    #define J1939_PRINT(...) J1939_Debug_Print(__VA_ARGS__)
#else
    #define J1939_PRINT(...)
#endif

八、完整工程获取

1. GitHub/Gitee项目

bash 复制代码
# Open-SAE-J1939
git clone https://github.com/hzappen/Open-SAE-J1939.git

# STM32移植版
git clone https://gitee.com/qinyuan/Open-SAE-J1939-STM32.git

# 中文协议栈
git clone https://gitee.com/SupperWater/J1939-Protocol.git

2. 快速开始步骤

  1. 选择协议栈 :推荐Open-SAE-J1939(功能最全)
  2. 移植硬件层 :修改Hardware.c中的发送/接收函数
  3. 配置ECU信息:设置NAME、地址等参数
  4. 集成到工程:添加到STM32CubeIDE或Keil工程
  5. 测试验证:使用CAN工具验证通信

九、常见问题解决

问题 可能原因 解决方案
无法接收消息 CAN过滤器配置错误 检查过滤器掩码设置
地址声明失败 NAME冲突 修改唯一的64位NAME
TP传输失败 超时设置太短 增加TP超时时间
内存不足 消息队列太大 减小J1939_MESSAGE_QUEUE_SIZE
实时性差 主循环频率低 确保1ms调用Listen_For_Messages

十、进阶功能扩展

1. 支持ISO 11783(农业机械)

c 复制代码
// 在Open-SAE-J1939基础上添加ISO 11783支持
#include "ISO_11783/ISO_11783-7_Application_Layer/Application_Layer.h"

// 初始化ISO 11783
ISO_11783 iso11783;
Open_SAE_J1939_Startup_ISO_11783(&iso11783);

2. 添加UDS诊断(ISO 14229)

c 复制代码
// 集成UDS诊断服务
void UDS_Service_Handler(J1939 *j1939, uint8_t *request, uint8_t length)
{
    switch (request[0]) {
        case 0x10:  // 诊断会话控制
            Handle_Diagnostic_Session_Control(request, length);
            break;
        case 0x22:  // 按标识符读取数据
            Handle_Read_Data_ByIdentifier(request, length);
            break;
        case 0x2E:  // 按标识符写入数据
            Handle_Write_Data_ByIdentifier(request, length);
            break;
    }
}

十一、性能优化建议

  1. 内存优化

    • 使用静态内存池
    • 合理设置队列大小
    • 禁用不需要的功能模块
  2. 实时性优化

    • 使用CAN接收中断
    • 减少Listen_For_Messages中的处理时间
    • 优先级消息立即发送
  3. 代码优化

    • 启用编译器优化(-O2)
    • 使用查表法替代计算
    • 内联关键函
相关推荐
LCG元1 小时前
STM32实战:基于OpenMV与STM32的智能视觉追踪小车(颜色识别+舵机控制)
stm32·单片机·嵌入式硬件
星夜夏空992 小时前
STM32单片机学习(13) —— 串口通信协议
stm32·单片机·学习
崇山峻岭之间2 小时前
单片机时钟配置:HSE改为HSI
单片机·嵌入式硬件
Dotrust东信创智2 小时前
智驾、座舱、车身“三域互联”:基于SOME/IP的SOA跨域测试方案
汽车·汽车电子
BSD_HY2 小时前
薄膜开关技术深度解析:PET与PC材质对比、工业4.0接口设计及汽车电子产品应用
汽车·人机交互·制造·材质·薄膜开关
Luminbox紫创测控2 小时前
汽车(EV)内外饰材料老化测试与标准
人工智能·测试工具·汽车·安全性测试·测试标准
jake·tang2 小时前
深度解析 VESC 参数辨识源码:电阻、电感与磁链
arm开发·c++·嵌入式硬件·算法·数学建模·傅立叶分析
崇山峻岭之间2 小时前
单片机时钟配置03
单片机·嵌入式硬件
木燚垚2 小时前
基于STM32的智能灶台控制系统设计与实现
stm32·单片机·嵌入式硬件·智能家居