# 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](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L222-L226)) | 参数 | 值 | 说明 | |------|-----|------| | `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](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L271-L284)) | 参数 | 值 | 说明 | |------|-----|------| | `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](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L371-L399)) | 参数 | 值 | 说明 | |------|-----|------| | `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](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L20)) ```python CAN_DLL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ControlCAN.dll') ``` > 已修改为基于脚本所在目录定位 DLL,任意工作目录运行均可找到。 --- ## 四、核心函数详解 ### 4.1 GUI 窗口 `g_WindowStart()` ([CAN.py:72](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L72)) 构建主界面,包含: - **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](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L402)) - **作用**:发送 8 字节数据帧 - **参数**: - `BUFF`:8 字节数据列表 - `Sendstr`:用于日志显示的原始字符串 - **流程**:构造 `VCI_CAN_OBJ` 结构体 → 调用 `VCI_Transmit` → 日志显示结果 #### `ReadFile(BIN_PATH)` ([CAN.py:762](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L762)) - **作用**:读取 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](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L461)) - **作用**:独立子线程持续轮询接收 CAN 数据 - **特点**:每 1ms 调用一次 `receive()`,异常时退出线程 #### `receive(can_index)` ([CAN.py:478](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L478)) - **作用**:接收一帧数据并解析 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](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L688-L693)) | RID[2] | RID[3] | 含义 | |--------|--------|------| | `0x10` | `0x05` | 擦除 Memory | | `0x15` | `0x55 0x77` | 检查完整性:简单校验 OK | | `0x15` | `0x55 0x66` | 检查完整性:简单校验 ERR | --- ## 六、数据结构 ### `VCI_CAN_INIT_CONFIG` 初始化结构体([CAN.py:261](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L261)) ```c 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](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L358)) ```c 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 - 标准库:`tkinter`、`ctypes`、`threading`、`os`、`binascii`、`time`、`struct`、`re`、`urllib.request`、`inspect` - 第三方库:`pywin32`(`win32com.client`,**注:实际代码未使用,可删除该导入**) ### 运行命令 ```powershell & 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](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L8-L9)) - `from win32com.client import Dispatch` 和 `import win32com.client` 未被使用,可删除以减少依赖。 2. **RID 校验逻辑错误**([CAN.py:690-693](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L690-L693)) - 第 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](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L493-L502)) - `receive()` 中接收失败时无限循环重试,超时退出代码已被注释(原 500 次重试)。可能导致界面假死。 4. **BIN 文件路径硬编码默认值**([CAN.py:123](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L123)) - 默认路径 `C:\Users\HenchYoung\Desktop\LED2000.bin` 为原作者路径,需手动修改。 5. **发送帧 ID 固定**([CAN.py:375](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L375)) - `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:375](file:///d:/00_software/00_keil/01_keil_file/001_BMS/BMS_SLAVE/000_update/freertos/freetest/up/CAN.py#L375) 的 `TRANSMIT_ID` 为对应值,或改用创芯科技官方上位机软件。 --- *文档生成时间:2026-08-06* *文档作者:基于 CAN.py 源码分析生成*