test/freetest/up/CAN_程序说明.md

14 KiB

CAN.py 程序说明文档

一、程序概述

CAN.py 是一个基于 Python 的 CAN 通信上位机程序,用于通过 USB-CAN 适配器与 STM32 单片机进行 CAN 总线通信。其核心功能是实现 UDS(统一诊断服务)BootLoader,支持向单片机发送诊断指令和烧写 BIN 固件。

适用硬件

  • CAN 适配器:创芯科技 CANalyst-II / USBCAN-2 系列(设备类型 VCI_USB_CAN_2 = 4)
  • 下位机:STM32F429IGT6(BootLoader 标题所示,可适配其他 STM32)
  • 依赖 DLL:ControlCAN.dll(需与脚本同目录)

技术栈

模块 用途
tkinter 构建 GUI 图形界面
ctypes 调用 ControlCAN.dll 中的 C 函数
threading 子线程接收 CAN 数据,避免阻塞主界面
os / binascii 文件读取与字节转换

二、程序结构

CAN.py
├── 导入与 DLL 加载          (1~22行)
├── ToolTip 工具提示类        (24~64行)
├── GUI 窗口构建 g_WindowStart (72~162行)
├── 按钮回调函数              (165~213行)
│   ├── FUNC_8bitCANSend     发送 8 位 CAN 指令
│   └── FUNC_BINSend         发送 BIN 固件
├── CAN 基础操作              (220~340行)
│   ├── connect              打开设备
│   ├── init                 初始化通道
│   └── start                启动通道
├── CAN 收发函数              (348~753行)
│   ├── transmitBIN          发送单帧数据
│   ├── transmit             发送默认数据
│   ├── receive_by_thread    接收线程入口
│   └── receive              接收并解析 UDS 响应
├── CAN_Start                初始化+启动通道
├── ReadFile                 读取并发送 BIN 文件
└── __main__ 程序入口        (831~857行)

三、关键配置参数

1. 设备与通道参数(CAN.py:222-226)

参数 说明
VCI_USB_CAN_2 4 CAN 卡类别(USBCAN-2A/2C/CANalyst-II)
DEV_INDEX 0 设备索引(第一个适配器为 0)
STATUS_OK 1 返回值 1=成功,0=失败

2. CAN 初始化参数(CAN.py:271-284)

参数 说明
ACC_CODE 0x80000000 过滤验收码
ACC_MASK 0xFFFFFFFF 过滤屏蔽码(接收所有)
FILTER 0 滤波模式:接收所有类型帧
TIMING_0 0x00 波特率 T0,500kbps
TIMING_1 0x1C 波特率 T1
MODE 0 工作模式:正常工作

波特率说明:Timing0=0x00, Timing1=0x1C 对应 500kbps,与 BMS 项目下位机配置一致。

3. 帧结构参数(CAN.py:371-399)

参数 说明
TRANSMIT_ID 0x1 发送帧 ID
RECEIVE_ID 0x0 接收帧 ID
TRANSMIT_SEND_TYPE 1 单次发送(失败不重发,响应快)
REMOTE_FLAG 0 数据帧
EXTERN_FLAG 0 标准帧(11 位 ID)
DATA_LEN 8 数据长度 DLC
RECEIVE_LEN 2500 接收缓存区长度
TRANSMIT_LEN 1 每次发送单帧

4. DLL 路径配置(CAN.py:20)

CAN_DLL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ControlCAN.dll')

已修改为基于脚本所在目录定位 DLL,任意工作目录运行均可找到。


四、核心函数详解

4.1 GUI 窗口 g_WindowStart() (CAN.py:72)

构建主界面,包含:

  • CAN 指令输入框:默认 0x10 0x02 0x00 0x00 0x00 0x00 0x00 0x00
  • BIN 固件路径输入框
  • 两个按钮:发送 8 位指令 / 发送 BIN 固件
  • 日志文本框:黑色背景绿色字体,显示通信日志

启动流程:

  1. 创建界面 → 2. connect() 打开设备 → 3. CAN_Start() 初始化并启动通道 → 4. 启动接收线程 → 5. win.mainloop() 进入消息循环

4.2 CAN 设备控制

函数 DLL 接口 作用
connect() VCI_OpenDevice 打开 USB-CAN 设备
init(can_index) VCI_InitCAN 初始化指定 CAN 通道(设置波特率、滤波等)
start(can_index) VCI_StartCAN 启动指定 CAN 通道
CAN_Start() - 依次调用 init + start 启动通道 1

4.3 数据发送

transmitBIN(can_index, BUFF, Sendstr) (CAN.py:402)

  • 作用:发送 8 字节数据帧
  • 参数:
    • BUFF:8 字节数据列表
    • Sendstr:用于日志显示的原始字符串
  • 流程:构造 VCI_CAN_OBJ 结构体 → 调用 VCI_Transmit → 日志显示结果

ReadFile(BIN_PATH) (CAN.py:762)

  • 作用:读取 BIN 固件并按 8 字节分包发送
  • 流程:
    1. 打开二进制文件,获取文件大小
    2. 循环读取字节,每凑齐 8 字节调用 transmitBIN 发送一帧
    3. 每帧间隔 time.sleep(0.001)(1ms)
    4. 文件读取完毕后发送最后一包(不足 8 字节补 0)

4.4 数据接收

receive_by_thread(can_index) (CAN.py:461)

  • 作用:独立子线程持续轮询接收 CAN 数据
  • 特点:每 1ms 调用一次 receive(),异常时退出线程

receive(can_index) (CAN.py:478)

  • 作用:接收一帧数据并解析 UDS 响应
  • 流程:
    1. 调用 VCI_Receive 读取数据
    2. 接收失败时循环重试(无超时退出,已被注释)
    3. 接收成功后打印 ID、DataLen、Data
    4. 根据首字节(SID)进入不同的 UDS 响应解析分支

五、UDS 指令解析

程序通过 receive() 函数中的 if-else 分支解析下位机的 UDS 响应。响应帧首字节为服务 ID(SID)。

5.1 应用程序指令集(诊断会话控制)

首字节 次字节 含义
0x7f - 不支持的指令(否定响应)
0x50 0x03 进入扩展会话模式成功
0x50 0x01 进入默认 01 会话模式成功
0xc5 0x02 关闭 DTC 成功
0xc5 0x01 开启 DTC 成功
0x68 0x03 禁止非诊断报文收发成功
0x68 0x01 允许非诊断报文收发成功
0x54 - 清除诊断信息成功

5.2 Boot 指令集(BootLoader 烧写流程)

首字节 次字节 含义
0x50 0x02 进入编程会话模式(30 秒内需执行烧写指令)
0x62 - 读取 DID 数据成功
0x67 0x01 安全访问:获取 seed 成功
0x67 0x02 安全访问:验证 key 成功(解锁)
0x6e - 写入 DID 数据成功
0x71 0x01 执行 RID 成功(擦除/完整性校验)
0x74 - 请求下载成功(返回最大数据块长度)
0x76 0x01 传输完成
0x77 - 退出传输完成
0x51 0x55 跳转 APP(烧写完成,跳转到应用程序)

5.3 RID 执行结果细分(CAN.py:688-693)

RID[2] RID[3] 含义
0x10 0x05 擦除 Memory
0x15 0x55 0x77 检查完整性:简单校验 OK
0x15 0x55 0x66 检查完整性:简单校验 ERR

六、数据结构

VCI_CAN_INIT_CONFIG 初始化结构体(CAN.py:261)

struct VCI_CAN_INIT_CONFIG {
    UINT AccCode;    // 过滤验收码
    UINT AccMask;    // 过滤屏蔽码
    UINT Reserved;   // 保留字段
    UCHAR Filter;    // 滤波模式
    UCHAR Timing0;   // 波特率 T0
    UCHAR Timing1;   // 波特率 T1
    UCHAR Mode;      // 工作模式
};

VCI_CAN_OBJ 帧结构体(CAN.py:358)

struct VCI_CAN_OBJ {
    UINT  ID;           // 帧 ID(右对齐)
    UINT  TimeStamp;    // 时间标识(单位 0.1ms)
    UCHAR TimeFlag;     // 是否使用时间标识
    UCHAR SendType;     // 发送类型(0=正常,1=单次)
    UCHAR RemoteFlag;   // 远程帧标志(0=数据帧,1=远程帧)
    UCHAR ExternFlag;   // 扩展帧标志(0=标准帧,1=扩展帧)
    UCHAR DataLen;      // 数据长度 DLC(<=8)
    UCHAR Data[8];      // 数据内容
    UCHAR Reserved[3];  // 保留字段
};

七、典型工作流程

7.1 程序启动流程

__main__
  └── g_WindowStart()
        ├── 创建 GUI 界面
        ├── connect()           # 打开 USB-CAN 设备
        ├── CAN_Start()
        │     ├── init(0)       # 初始化通道 1(500kbps)
        │     └── start(0)      # 启动通道 1
        ├── 启动接收线程
        └── win.mainloop()      # 进入消息循环

7.2 发送 8 位 CAN 指令流程

用户点击"发送8位指令"按钮
  └── FUNC_8bitCANSend()
        ├── 解析输入字符串为 8 字节十六进制数组
        └── transmitBIN()
              └── VCI_Transmit()  # 调用 DLL 发送

7.3 烧写 BIN 固件流程

用户点击"发送BIN固件"按钮
  └── FUNC_BINSend()
        └── ReadFile(路径)
              ├── 打开 BIN 文件
              ├── 循环读取字节(每 8 字节一帧)
              ├── transmitBIN() 发送每一帧
              └── 间隔 1ms 避免总线拥塞

7.4 接收解析流程

接收线程 (daemon=True)
  └── receive_by_thread()
        └── while True:
              ├── sleep(1ms)
              └── receive()
                    ├── VCI_Receive() 读取数据
                    ├── 失败:循环重试
                    └── 成功:按 SID 分支解析 UDS 响应
                          └── 更新日志文本框

八、运行环境与依赖

必需文件

  • CAN.py - 主程序
  • ControlCAN.dll - CAN 适配器驱动 DLL(需与脚本同目录)
  • ControlCAN.lib - 配套库文件

Python 依赖

  • Python 3.x
  • 标准库:tkinterctypesthreadingosbinasciitimestructreurllib.requestinspect
  • 第三方库:pywin32(win32com.client,注:实际代码未使用,可删除该导入)

运行命令

& C:/Users/gjf/AppData/Local/Programs/Python/Python313/python.exe d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py

九、已知问题与改进建议

9.1 已知问题

  1. 无用导入(CAN.py:8-9)

    • from win32com.client import Dispatchimport win32com.client 未被使用,可删除以减少依赖。
  2. RID 校验逻辑错误(CAN.py:690-693)

    • 第 690 行 t_ReceiveData[3] == 0x55 and t_ReceiveData[3] == 0x77 同一变量不可能同时等于两个值,应为 t_ReceiveData[3] == 0x77
    • 第 692 行 t_ReceiveData[3] == 0x55 and t_ReceiveData[3] == 0x66 同理,应为 t_ReceiveData[3] == 0x66
  3. 接收无超时退出(CAN.py:493-502)

    • receive() 中接收失败时无限循环重试,超时退出代码已被注释(原 500 次重试)。可能导致界面假死。
  4. BIN 文件路径硬编码默认值(CAN.py:123)

    • 默认路径 C:\Users\HenchYoung\Desktop\LED2000.bin 为原作者路径,需手动修改。
  5. 发送帧 ID 固定(CAN.py:375)

    • TRANSMIT_ID = 0x1 固定,无法在界面修改。

9.2 改进建议

  • 删除无用的 win32com 导入,避免强制安装 pywin32
  • 修复 RID 校验的逻辑错误
  • 增加接收超时退出机制,避免界面假死
  • BIN 文件路径支持文件选择对话框(filedialog.askopenfilename)
  • 发送帧 ID 支持界面输入
  • 接收线程增加异常恢复机制
  • 日志增加文件保存功能,便于排查问题
  • 与 BMS 项目对接时,需确认 CAN 通信参数(波特率、帧 ID)与下位机一致

十、与 BMS 项目的关系

当前 BMS 项目下位机配置(参考项目内存约束):

  • 波特率:500kbps(与本上位机 TIMING_0/1 一致)
  • 支持标准帧 ID:0x100 / 0x101 / 0x180 / 0x181 / 0x182
  • DLC 转发:按原始帧 DLC 转发(符合 CANopen 协议)
  • 1ms 帧率无丢帧

注意:本上位机 TRANSMIT_ID = 0x1,与 BMS 下位机支持的帧 ID(0x100/0x101/0x180/0x181/0x182)不一致。若用于 BMS 项目调试,需修改 CAN.py:375TRANSMIT_ID 为对应值,或改用创芯科技官方上位机软件。


文档生成时间:2026-08-06 文档作者:基于 CAN.py 源码分析生成