341 lines
14 KiB
Markdown
341 lines
14 KiB
Markdown
# 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 源码分析生成*
|