test/freetest/up/CAN.py

1284 lines
50 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import sys
import threading
import time
import tkinter
import tkinter.messagebox
import tkinter.filedialog
import configparser
import os
import binascii
import ctypes
# 读取Dll动态链接库
from ctypes import *
def resource_dir():
"""资源目录: PyInstaller打包后DLL解压于_MEIPASS, 源码模式为脚本目录"""
if getattr(sys, 'frozen', False):
return sys._MEIPASS
return os.path.dirname(os.path.abspath(__file__))
def app_dir():
"""配置目录: 打包后config.ini持久化于exe同目录, 源码模式为脚本目录"""
if getattr(sys, 'frozen', False):
return os.path.dirname(sys.executable)
return os.path.dirname(os.path.abspath(__file__))
# 依赖的DLL文件(打包时内嵌, 运行时解压到_MEIPASS)
CAN_DLL_PATH = os.path.join(resource_dir(), 'ControlCAN.dll')
# 读取DLL文件
Can_DLL = windll.LoadLibrary(CAN_DLL_PATH)
class ToolTip(object):
def __init__(self, widget):
self.widget = widget
self.tip_window = None
def show_tip(self, tip_text):
"Display text in a tooltip window"
if self.tip_window or not tip_text:
return
x, y, _cx, cy = self.widget.bbox("insert")
# get size of widget
x = x + self.widget.winfo_rootx() + 250
# calculate to display tooptip
y = y + cy + self.widget.winfo_rooty() + 0
# below and to the right
self.tip_window = tw = tkinter.Toplevel(self.widget)
# create new tooltip window
tw.wm_overrideredirect(True)
# remove all Window Manager (wm)
tw.wm_geometry("+%d+%d" % (x, y))
# create window size
label = tkinter.Label(tw, text=tip_text, justify=tkinter.LEFT,
background="#ffffe0", relief=tkinter.SOLID,
borderwidth=1, font=("微软雅黑", 10))
label.pack(ipadx=1)
def hide_tip(self):
tw = self.tip_window
self.tip_window = None
if tw:
tw.destroy()
def create_ToolTip(widget, text):
tooltip = ToolTip(widget)
def enter(event):
tooltip.show_tip(text)
def leave(event):
tooltip.hide_tip()
widget.bind('<Enter>', enter)
widget.bind('<Leave>', leave)
def Show_Update_log():
LOG_str = "[LogStart] " + "20220327_BETA1.0: 新规作成基本CAN通信功能实现\n"
LOG_str = LOG_str + "[LogStart] " + "20220409_BETA1.1: 接收改用线程\n"
LOG_str = LOG_str + "[LogStart] " + "20220410_BETA1.2: UDS框架接入\n"
LOG_str = LOG_str + "[LogStart] " + "20260807_BETA2.0: UI重构(可配置CAN ID/按钮集成/文件选择)\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# ==================== 配置管理 ====================
CONFIG_PATH = os.path.join(app_dir(), 'config.ini')
def load_config():
"""读取配置文件, 不存在则返回默认配置"""
config = configparser.ConfigParser()
config.read(CONFIG_PATH, encoding='utf-8')
if not config.has_section('CAN'):
config['CAN'] = {'tx_id': '0x180', 'rx_id': '0x181'}
if not config.has_section('UI'):
config['UI'] = {'window_width': '900', 'window_height': '600'}
if not config.has_section('Last'):
config['Last'] = {'bin_path': ''}
return config
def save_config(config):
"""保存配置到文件"""
try:
with open(CONFIG_PATH, 'w', encoding='utf-8') as f:
config.write(f)
except Exception as e:
print("保存配置失败: %s" % str(e))
def parse_hex(s):
"""解析十六进制字符串(支持0x前缀/十进制)"""
s = s.strip()
if s.lower().startswith('0x'):
return int(s, 16)
return int(s)
def apply_can_id():
"""应用CAN ID设置"""
global TRANSMIT_ID, RECEIVE_ID
try:
new_tx = parse_hex(tx_id_var.get())
new_rx = parse_hex(rx_id_var.get())
if not (0 <= new_tx <= 0x7FF and 0 <= new_rx <= 0x7FF):
raise ValueError("ID超出标准帧范围(0-0x7FF)")
TRANSMIT_ID = new_tx
RECEIVE_ID = new_rx
# 保存到配置文件
config = load_config()
config['CAN']['tx_id'] = hex(new_tx)
config['CAN']['rx_id'] = hex(new_rx)
save_config(config)
log_append("CAN ID已更新: TX=0x%03X RX=0x%03X" % (TRANSMIT_ID, RECEIVE_ID), "iap")
update_status()
except Exception as e:
log_append("CAN ID格式错误: %s" % str(e), "error")
def log_append(msg, level="normal"):
"""日志追加到界面 (level: normal/iap/error)"""
color = {"normal": "lime", "iap": "cyan", "error": "red"}.get(level, "lime")
prefix = {"normal": "", "iap": "[IAP]", "error": "[ERR]"}.get(level, "")
textLOG.tag_config(level, foreground=color)
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "] " \
+ prefix + " " + msg + "\n"
textLOG.insert(tkinter.END, LOG_str, (level,))
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update_idletasks()
g_tx_count = 0
g_rx_count = 0
g_can_connected = False
def update_status():
"""更新状态栏"""
status_can_label.config(text="● CAN已连接" if g_can_connected else "○ CAN未连接",
fg="green" if g_can_connected else "red")
status_tx_label.config(text="TX: %d" % g_tx_count)
status_rx_label.config(text="RX: %d" % g_rx_count)
def browse_bin():
"""文件选择器: 选择BIN固件文件"""
filepath = tkinter.filedialog.askopenfilename(
title="选择BIN固件文件",
filetypes=[("BIN文件", "*.bin"), ("HEX文件", "*.hex"), ("所有文件", "*.*")])
if filepath:
BIN_path_entry.set(filepath)
# 保存路径到配置
config = load_config()
config['Last']['bin_path'] = filepath
save_config(config)
def g_WindowStart():
global win #界面实体
global CAN_Send_entry #发送数据的内容
global BIN_path_entry #BIN文件路径
global textLOG #textbox的数据
global scroll #滚动条实体
global thread
global thread2
global tx_id_var, rx_id_var
global status_can_label, status_tx_label, status_rx_label
global g_can_connected
# 读取配置
config = load_config()
try:
tx_id_default = config['CAN']['tx_id']
rx_id_default = config['CAN']['rx_id']
except KeyError:
tx_id_default, rx_id_default = '0x180', '0x181'
# 创建主窗口
win = tkinter.Tk()
win.title("BMS IAP 升级工具 v2.0 (CAN-UDS)")
win.geometry("%sx%s" % (config['UI'].get('window_width', '900'),
config['UI'].get('window_height', '600')))
win.minsize(800, 500)
win.columnconfigure(0, weight=1)
win.rowconfigure(4, weight=1) # 日志区随窗口拉伸
# ===== 配置区: CAN ID =====
config_frame = tkinter.LabelFrame(win, text=" CAN 配置 ", font=("微软雅黑", 10))
config_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=5)
tkinter.Label(config_frame, text="CAN TX ID:", font=("微软雅黑", 10)).pack(side="left", padx=(10, 2))
tx_id_var = tkinter.StringVar(value=tx_id_default)
tkinter.Entry(config_frame, textvariable=tx_id_var, width=8, font=("Consolas", 11)).pack(side="left", padx=2)
tkinter.Label(config_frame, text="CAN RX ID:", font=("微软雅黑", 10)).pack(side="left", padx=(15, 2))
rx_id_var = tkinter.StringVar(value=rx_id_default)
tkinter.Entry(config_frame, textvariable=rx_id_var, width=8, font=("Consolas", 11)).pack(side="left", padx=2)
tkinter.Button(config_frame, text="应用", command=apply_can_id,
bg="lightblue", font=("微软雅黑", 10)).pack(side="left", padx=(15, 10))
tkinter.Label(config_frame, text="波特率: 500kbps", font=("微软雅黑", 9),
fg="gray").pack(side="right", padx=10)
# ===== 指令按钮区 =====
cmd_frame = tkinter.LabelFrame(win, text=" UDS 快捷指令 ", font=("微软雅黑", 10))
cmd_frame.grid(row=1, column=0, sticky="ew", padx=10, pady=5)
cmd_buttons = [
("会话控制", cmd_session, "0x10 0x02 进入编程会话"),
("读版本", cmd_read_version, "0x22 0xF1 0x00 读取Bootloader版本"),
("请求Seed", cmd_request_seed, "0x27 0x01 获取安全访问Seed"),
("发送Key", cmd_send_key, "0x27 0x02 发送Key(~Seed)解锁"),
("擦除Flash", cmd_erase, "0x31 0x01 0xFF 0x01 擦除App区"),
("请求下载", cmd_download, "0x34 + 固件大小 请求下载"),
("开始传输", cmd_transfer_start, "0x36 0x01 进入纯数据模式"),
("传输退出", cmd_transfer_exit, "0x37 结束传输"),
("CRC校验", cmd_crc_check, "0x31 0x01 0xFF 0x02 触发CRC校验"),
("ECU复位", cmd_ecu_reset, "0x11 0x01 复位跳转App"),
]
for i, (text, cmd, tip) in enumerate(cmd_buttons):
btn = tkinter.Button(cmd_frame, text=text, command=cmd,
font=("微软雅黑", 10), width=11,
bg="#e8e8e8", relief="raised")
btn.grid(row=i // 5, column=i % 5, padx=4, pady=3, sticky="ew")
create_ToolTip(btn, tip)
for col in range(5):
cmd_frame.columnconfigure(col, weight=1)
# ===== 手动指令区 =====
manual_frame = tkinter.LabelFrame(win, text=" 手动指令下发 ", font=("微软雅黑", 10))
manual_frame.grid(row=2, column=0, sticky="ew", padx=10, pady=5)
CAN_Send_entry = tkinter.StringVar()
CAN_Send_entry.set("0x10 0x02 0x00 0x00 0x00 0x00 0x00 0x00")
entry_cmd = tkinter.Entry(manual_frame, textvariable=CAN_Send_entry,
font=("Consolas", 12), width=40)
entry_cmd.pack(side="left", padx=10, pady=4, fill="x", expand=True)
create_ToolTip(entry_cmd, 'CAN指令8位(十六进制), 字节间用半角空格隔开')
tkinter.Button(manual_frame, text="发送指令", command=cmd_manual_send,
bg="pink", fg="red", font=("微软雅黑", 11),
width=10).pack(side="left", padx=10)
# ===== 固件区 =====
fw_frame = tkinter.LabelFrame(win, text=" BIN 固件 ", font=("微软雅黑", 10))
fw_frame.grid(row=3, column=0, sticky="ew", padx=10, pady=5)
BIN_path_entry = tkinter.StringVar()
BIN_path_entry.set(config['Last'].get('bin_path', ''))
entry_bin = tkinter.Entry(fw_frame, textvariable=BIN_path_entry,
font=("Consolas", 11), width=40)
entry_bin.pack(side="left", padx=10, pady=4, fill="x", expand=True)
tkinter.Button(fw_frame, text="浏览...", command=browse_bin,
font=("微软雅黑", 10), width=8).pack(side="left", padx=5)
tkinter.Button(fw_frame, text="IAP一键升级", command=cmd_iap_upgrade,
bg="palegreen", fg="darkgreen", font=("微软雅黑", 12),
width=14).pack(side="left", padx=10)
# ===== 日志区 (可拉伸) =====
log_frame = tkinter.LabelFrame(win, text=" 日志 ", font=("微软雅黑", 10))
log_frame.grid(row=4, column=0, sticky="nsew", padx=10, pady=5)
log_frame.rowconfigure(0, weight=1)
log_frame.columnconfigure(0, weight=1)
textLOG = tkinter.Text(log_frame, bg="black", fg="lime",
font=("Consolas", 11), state="disabled", wrap="word")
scroll = tkinter.Scrollbar(log_frame, orient="vertical", command=textLOG.yview)
textLOG.configure(yscrollcommand=scroll.set)
textLOG.grid(row=0, column=0, sticky="nsew", padx=(5, 0), pady=5)
scroll.grid(row=0, column=1, sticky="ns", pady=5)
textLOG.tag_config("normal", foreground="lime")
textLOG.tag_config("iap", foreground="cyan")
textLOG.tag_config("error", foreground="red")
# 绑定鼠标滚轮
def on_mousewheel(event):
textLOG.yview_scroll(int(-1 * (event.delta / 120)), "units")
textLOG.bind("<MouseWheel>", on_mousewheel)
# ===== 状态栏 =====
status_frame = tkinter.Frame(win)
status_frame.grid(row=5, column=0, sticky="ew", padx=10, pady=2)
status_can_label = tkinter.Label(status_frame, text="○ CAN未连接", fg="red",
font=("微软雅黑", 9))
status_can_label.pack(side="left", padx=10)
status_tx_label = tkinter.Label(status_frame, text="TX: 0", font=("微软雅黑", 9))
status_tx_label.pack(side="left", padx=10)
status_rx_label = tkinter.Label(status_frame, text="RX: 0", font=("微软雅黑", 9))
status_rx_label.pack(side="left", padx=10)
# 启动日志
textLOG.config(state="normal")
LOG_str = "[PrgStart] BMS IAP 升级工具 v2.0\n"
textLOG.insert(tkinter.END, LOG_str)
Show_Update_log()
textLOG.config(state="disabled")
log_append("程序启动")
# 启动CAN
ret = connect()
g_can_connected = (ret == STATUS_OK)
CAN_Start()
update_status()
win.update()
# 线程接收
thread = threading.Thread(target=receive_by_thread, args=(CAN_INDEX_1,), daemon=True)
thread.start()
print(thread.is_alive())
print("threadREV.start()")
# 窗口关闭时保存配置
def on_close():
config = load_config()
config['UI']['window_width'] = str(win.winfo_width())
config['UI']['window_height'] = str(win.winfo_height())
config['Last']['bin_path'] = BIN_path_entry.get()
save_config(config)
win.destroy()
win.protocol("WM_DELETE_WINDOW", on_close)
# 进入消息循环,可以写控件
win.mainloop()
# ==================== 指令处理 ====================
def cmd_manual_send():
"""手动发送8位CAN指令"""
Sendstr = CAN_Send_entry.get()
Send_BUFF = str(Sendstr).strip().split(" ")
if len(Send_BUFF) < 1:
log_append("指令为空", "error")
return
Send_BUFFHEX = [0, 0, 0, 0, 0, 0, 0, 0]
try:
for i in range(0, len(Send_BUFF)):
if Send_BUFF[i] == "":
continue
Send_BUFFHEX[i] = int(str(Send_BUFF[i]), 16)
except Exception as e:
log_append("指令格式错误: %s" % str(e), "error")
return
transmitBIN(CAN_INDEX_1, Send_BUFFHEX, Sendstr)
win.update()
def cmd_iap_upgrade():
"""一键IAP升级"""
SendPath = str(BIN_path_entry.get()).strip()
if not SendPath:
log_append("请先选择BIN固件文件", "error")
return
if not os.path.exists(SendPath):
log_append("文件不存在: %s" % SendPath, "error")
return
IAP_Upgrade(SendPath)
print("IAP upgrade done")
# ==================== UDS 快捷指令 ====================
def cmd_session():
cmd_manual_send_set([0x10, 0x02, 0, 0, 0, 0, 0, 0], "0x10 0x02 进入编程会话")
def cmd_read_version():
cmd_manual_send_set([0x22, 0xF1, 0x00, 0, 0, 0, 0, 0], "0x22 0xF1 0x00 读版本")
def cmd_request_seed():
cmd_manual_send_set([0x27, 0x01, 0, 0, 0, 0, 0, 0], "0x27 0x01 请求Seed")
def cmd_send_key():
cmd_manual_send_set([0x27, 0x02, 0xED, 0xCB, 0xA9, 0x87, 0, 0], "0x27 0x02 发送Key")
def cmd_erase():
cmd_manual_send_set([0x31, 0x01, 0xFF, 0x01, 0, 0, 0, 0], "0x31 0x01 0xFF 0x01 擦除Flash")
def cmd_download():
"""请求下载: 0x34 + 固件大小(4字节大端)"""
fw_path = str(BIN_path_entry.get()).strip()
if not fw_path or not os.path.exists(fw_path):
log_append("请先选择BIN固件文件", "error")
return
fw_size = os.path.getsize(fw_path)
if not (0 < fw_size <= 36 * 1024):
log_append("固件大小超限(1-36864字节)", "error")
return
size_bytes = list(fw_size.to_bytes(4, 'big'))
data = [0x34] + size_bytes
desc = "0x34 固件大小=%d字节" % fw_size
cmd_manual_send_set(data, desc)
def cmd_transfer_start():
cmd_manual_send_set([0x36, 0x01, 0, 0, 0, 0, 0, 0], "0x36 0x01 开始传输")
def cmd_transfer_exit():
cmd_manual_send_set([0x37, 0, 0, 0, 0, 0, 0, 0], "0x37 传输退出")
def cmd_crc_check():
cmd_manual_send_set([0x31, 0x01, 0xFF, 0x02, 0, 0, 0, 0], "0x31 0x01 0xFF 0x02 CRC校验")
def cmd_ecu_reset():
cmd_manual_send_set([0x11, 0x01, 0, 0, 0, 0, 0, 0], "0x11 0x01 ECU复位")
def cmd_manual_send_set(data, desc):
"""发送一组指令(填充到8字节)"""
while len(data) < 8:
data.append(0)
Sendstr = " ".join("0x%02X" % b for b in data)
transmitBIN(CAN_INDEX_1, data, Sendstr + " (" + desc + ")")
win.update()
# VCI_OpenDevice 打开设备:
# CAN卡类别为 USBCAN-2A, USBCAN-2C, CANalyst-II
VCI_USB_CAN_2 = 4
# CAN卡下标索引, 比如当只有一个USB-CAN适配器时, 索引号为0, 这时再插入一个USB-CAN适配器那么后面插入的这个设备索引号就是1, 以此类推
DEV_INDEX = 0
# 打开设备, 一个设备只能打开一次
STATUS_OK = 1
# return: 1=OK 0=ERROR
def connect():
# VCI_USB_CAN_2: 设备类型
# DEV_INDEX: 设备索引
# RESERVED: 保留参数
ret = Can_DLL.VCI_OpenDevice(VCI_USB_CAN_2, DEV_INDEX, RESERVED)
if ret == STATUS_OK:
print('VCI_OpenDevice: 设备开启成功')
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "] " + "VCI_OpenDevice: 设备开启成功\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
else:
print('VCI_OpenDevice: 设备开启失败')
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "] " + "VCI_OpenDevice: 设备开启失败\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
return ret
#VCI_InitCAN 初始化指定CAN通道
# 通道初始化参数结构
# AccCode: 过滤验收码
# AccMask: 过滤屏蔽码
# Reserved: 保留字段
# Filter: 滤波模式 0/1=接收所有类型 2=只接收标准帧 3=只接收扩展帧
# Timing0: 波特率 T0
# Timing1: 波特率 T1
# Mode: 工作模式 0=正常工作 1=仅监听模式 2=自发自收测试模式
class VCI_CAN_INIT_CONFIG(Structure):
_fields_ = [
("AccCode", c_uint),
("AccMask", c_uint),
("Reserved", c_uint),
("Filter", c_ubyte),
("Timing0", c_ubyte),
("Timing1", c_ubyte),
("Mode", c_ubyte)
]
# 过滤验收码
ACC_CODE = 0x80000000
# 过滤屏蔽码
ACC_MASK = 0xFFFFFFFF
# 保留字段
RESERVED = 0
# 滤波模式 0/1=接收所有类型
FILTER = 0
# 波特率 T0
TIMING_0 = 0x00 #500k
# 波特率 T1
TIMING_1 = 0x1C
# 工作模式 0=正常工作
MODE = 0
# 初始化通道
# return: 1=OK 0=ERROR
def init(can_index):
init_config = VCI_CAN_INIT_CONFIG(ACC_CODE, ACC_MASK, RESERVED, FILTER, TIMING_0, TIMING_1, MODE)
# VCI_USB_CAN_2: 设备类型
# DEV_INDEX: 设备索引
# can_index: CAN通道索引
# init_config: 请求参数体
ret = Can_DLL.VCI_InitCAN(VCI_USB_CAN_2, DEV_INDEX, can_index, byref(init_config))
if ret == STATUS_OK:
print('VCI_InitCAN: 通道 ' + str(can_index + 1) + ' 初始化成功')
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "] " + "VCI_InitCAN: 通道 " \
+ str(can_index + 1) + " 初始化成功\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
else:
print('VCI_InitCAN: 通道 ' + str(can_index + 1) + ' 初始化失败')
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "] " + "VCI_InitCAN: 通道 "\
+ str(can_index + 1) + " 初始化失败\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
return ret
# 打开通道 VCI_StartCAN 打开指定CAN通道
# return: 1=OK 0=ERROR
def start(can_index):
# VCI_USB_CAN_2: 设备类型
# DEV_INDEX: 设备索引
# can_index: CAN通道索引
ret = Can_DLL.VCI_StartCAN(VCI_USB_CAN_2, DEV_INDEX, can_index)
if ret == STATUS_OK:
print('VCI_StartCAN: 通道 ' + str(can_index + 1) + ' 打开成功')
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "] " + "VCI_StartCAN: 通道 " \
+ str(can_index + 1) + " 打开成功\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
else:
print('VCI_StartCAN: 通道 ' + str(can_index + 1) + ' 打开失败')
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "] " + "VCI_StartCAN: 通道 " \
+ str(can_index + 1) + " 打开失败\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
return ret
# CAN帧结构体 VCI_Transmit 发送数据:
# ID: 帧ID, 32位变量, 数据格式为靠右对齐
# TimeStamp: 设备接收到某一帧的时间标识, 时间标示从CAN卡上电开始计时, 计时单位为0.1ms
# TimeFlag: 是否使用时间标识, 为1时TimeStamp有效, TimeFlag和TimeStamp只在此帧为接收帧时才有意义
# SendType: 发送帧类型 0=正常发送(发送失败会自动重发, 重发时间为4秒, 4秒内没有发出则取消) 1=单次发送(只发送一次, 发送失败不会自动重发, 总线只产生一帧数据)[二次开发, 建议1, 提高发送的响应速度]
# RemoteFlag: 是否是远程帧 0=数据帧 1=远程帧(数据段空)
# ExternFlag: 是否是扩展帧 0=标准帧(11位ID) 1=扩展帧(29位ID)
# DataLen: 数据长度DLC(<=8), 即CAN帧Data有几个字节, 约束了后面Data[8]中的有效字节
# Data: CAN帧的数据, 由于CAN规定了最大是8个字节, 所以这里预留了8个字节的空间, 受DataLen约束, 如DataLen定义为3, 即Data[0]、Data[1]、Data[2]是有效的
# Reserved: 保留字段
class VCI_CAN_OBJ(Structure):
_fields_ = [
("ID", c_uint),
("TimeStamp", c_uint),
("TimeFlag", c_ubyte),
("SendType", c_ubyte),
("RemoteFlag", c_ubyte),
("ExternFlag", c_ubyte),
("DataLen", c_ubyte),
("Data", c_ubyte * 8),
("Reserved", c_ubyte * 3)
]
# 要发送的参数
TRANSMIT_DATA = 5
# 保留字段
RESERVED = 0
# 发送帧ID (上位机→板子)
TRANSMIT_ID = 0x180
# 接收帧ID (板子→上位机)
RECEIVE_ID = 0x181
# 时间标识
TIME_STAMP = 0
# 是否使用时间标识
TIME_FLAG = 0
# 发送帧类型
TRANSMIT_SEND_TYPE = 1
# 接收帧类型
RECEIVE_SEND_TYPE = 0
# 是否是远程帧
REMOTE_FLAG = 0
# 是否是扩展帧
EXTERN_FLAG = 0
# 数据长度DLC
DATA_LEN = 8
# 用来接收的帧结构体数组的长度, 适配器中为每个通道设置了2000帧左右的接收缓存区
RECEIVE_LEN = 2500
# 接收保留字段
WAIT_TIME = 0
# 要发送的参数
TRANSMIT_DATA = 5
# 要发送的帧结构体数组的长度(发送的帧数量), 最大为1000, 建议设为1, 每次发送单帧, 以提高发送效率
TRANSMIT_LEN = 1
# 发送数据
# return: 1=OK 0=ERROR
def transmitBIN(can_index, BUFF, Sendstr):
ubyte_array_8 = c_ubyte * 8
DATA = ubyte_array_8(BUFF[0], BUFF[1], BUFF[2], BUFF[3], BUFF[4], BUFF[5],
BUFF[6], BUFF[7])
ubyte_array_3 = c_ubyte * 3
RESERVED_3 = ubyte_array_3(RESERVED, RESERVED, RESERVED)
can_obj = VCI_CAN_OBJ(TRANSMIT_ID, TIME_STAMP, TIME_FLAG, TRANSMIT_SEND_TYPE, REMOTE_FLAG, EXTERN_FLAG, DATA_LEN,
DATA, RESERVED_3)
# VCI_USB_CAN_2: 设备类型
# DEV_INDEX: 设备索引
# can_index: CAN通道索引
# can_obj: 请求参数体
# TRANSMIT_LEN: 发送的帧数量
ret = Can_DLL.VCI_Transmit(VCI_USB_CAN_2, DEV_INDEX, can_index, byref(can_obj), TRANSMIT_LEN)
if ret == STATUS_OK:
global g_tx_count
g_tx_count += 1
update_status()
print('VCI_Transmit: 通道 ' + str(can_index + 1) + ' 发送数据成功')
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "] " + "VCI_Transmit: 通道 " \
+ str(can_index + 1) + "发送数据成功\n"
textLOG.insert(tkinter.INSERT, LOG_str)
LOG_str = "CAN指令8位:" + Sendstr + "\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
else:
print('VCI_Transmit: 通道 ' + str(can_index + 1) + ' 发送数据失败')
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "] " + "VCI_Transmit: 通道 " \
+ str(can_index + 1) + " 发送数据失败\n"
textLOG.insert(tkinter.INSERT, LOG_str)
LOG_str = "CAN指令8位:" + Sendstr + "\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
def transmit(can_index):
ubyte_array_8 = c_ubyte * 8
DATA = ubyte_array_8(TRANSMIT_DATA, TRANSMIT_DATA, TRANSMIT_DATA, TRANSMIT_DATA, TRANSMIT_DATA, TRANSMIT_DATA,
TRANSMIT_DATA, TRANSMIT_DATA)
ubyte_array_3 = c_ubyte * 3
RESERVED_3 = ubyte_array_3(RESERVED, RESERVED, RESERVED)
can_obj = VCI_CAN_OBJ(TRANSMIT_ID, TIME_STAMP, TIME_FLAG, TRANSMIT_SEND_TYPE, REMOTE_FLAG, EXTERN_FLAG, DATA_LEN,
DATA, RESERVED_3)
# VCI_USB_CAN_2: 设备类型
# DEV_INDEX: 设备索引
# can_index: CAN通道索引
# can_obj: 请求参数体
# TRANSMIT_LEN: 发送的帧数量
ret = Can_DLL.VCI_Transmit(VCI_USB_CAN_2, DEV_INDEX, can_index, byref(can_obj), TRANSMIT_LEN)
if ret == STATUS_OK:
print('VCI_Transmit: 通道 ' + str(can_index + 1) + ' 发送数据成功')
else:
print('VCI_Transmit: 通道 ' + str(can_index + 1) + ' 发送数据失败')
def receive_by_thread(can_index):
t_ret = 0
t_end = 1
while(t_end):
time.sleep(0.001)
# IAP升级模式: 暂停异步接收, 由send_and_wait同步处理
if g_iap_mode:
continue
try:
# win.update()
t_ret = receive(CAN_INDEX_1)
except:
print("receive_by_thread end")
t_end = 0
#返回值不使用
# 接收数据 VCI_Receive 接收数据:
# return: 1=OK 0=ERROR
def receive(can_index):
t_over = 0
ubyte_array_8 = c_ubyte * 8
DATA = ubyte_array_8(RESERVED, RESERVED, RESERVED, RESERVED, RESERVED, RESERVED, RESERVED, RESERVED)
ubyte_array_3 = c_ubyte * 3
RESERVED_3 = ubyte_array_3(RESERVED, RESERVED, RESERVED)
# 参数结构参考122行
can_obj = VCI_CAN_OBJ(RECEIVE_ID, TIME_STAMP, TIME_FLAG, RECEIVE_SEND_TYPE, REMOTE_FLAG, EXTERN_FLAG, DATA_LEN, DATA, RESERVED_3)
# VCI_USB_CAN_2: 设备类型
# DEV_INDEX: 设备索引
# can_index: CAN通道索引
# can_obj: 请求参数体
# RECEIVE_LEN: 用来接收帧结构体数组的长度
# WAIT_TIME: 保留参数
ret = Can_DLL.VCI_Receive(VCI_USB_CAN_2, DEV_INDEX, can_index, byref(can_obj), RECEIVE_LEN, WAIT_TIME)
while ret != STATUS_OK:
# print('VCI_Receive: 通道 ' + str(can_index + 1) + ' 接收数据失败, 正在重试')
ret = Can_DLL.VCI_Receive(VCI_USB_CAN_2, DEV_INDEX, can_index, byref(can_obj), RECEIVE_LEN, WAIT_TIME)
time.sleep(0.001)
t_over = t_over + 1
try:
win.update()
except:
print("receive end")
return ret
#未接收到信息,子线程继续尝试接收
# print(t_over)
# if t_over == 500:
# print("接收超时")
# textLOG.config(state="normal")
# LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_receiveERR] " + "接收超时(500回读取)\n"
# textLOG.insert(tkinter.INSERT, LOG_str)
# # 移动到最新一行
# textLOG.yview_moveto(1)
# textLOG.config(state="disabled")
# return ret
else:
t_over = 0
global g_rx_count
g_rx_count += 1
update_status()
print('VCI_Receive: 通道 ' + str(can_index + 1) + ' 接收数据成功')
print('ID: ', can_obj.ID)
print('DataLen: ', can_obj.DataLen)
print('Data: ', list(can_obj.Data))
t_ReceiveData = list(can_obj.Data)
Str_Receive = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Receive] "
Str_Receive = Str_Receive + "0x" + str('%02x' % t_ReceiveData[0]) + " "
Str_Receive = Str_Receive + "0x" + str('%02x' % t_ReceiveData[1]) + " "
Str_Receive = Str_Receive + "0x" + str('%02x' % t_ReceiveData[2]) + " "
Str_Receive = Str_Receive + "0x" + str('%02x' % t_ReceiveData[3]) + " "
Str_Receive = Str_Receive + "0x" + str('%02x' % t_ReceiveData[4]) + " "
Str_Receive = Str_Receive + "0x" + str('%02x' % t_ReceiveData[5]) + " "
Str_Receive = Str_Receive + "0x" + str('%02x' % t_ReceiveData[6]) + " "
Str_Receive = Str_Receive + "0x" + str('%02x' % t_ReceiveData[7]) + "\n"
textLOG.config(state="normal")
textLOG.insert(tkinter.INSERT, Str_Receive)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
# print(chr(int("0x4f", 16)))
# print(chr(int("0x4b", 16)))
# print(chr(int(hex(0x4f), 16)))
# print(chr(int(hex(0x4b), 16)))
# 应用程序指令集
if t_ReceiveData[0] == 0x7f:
print("不支持的指令")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "不支持的指令\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x50 and t_ReceiveData[1] == 0x03:
print("指令成功,进入扩展会话模式")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "接收指令成功,进入扩展会话模式\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x50 and t_ReceiveData[1] == 0x01:
print("指令成功进入01默认会话模式")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "进入01默认会话模式\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0xc5 and t_ReceiveData[1] == 0x02:
print("指令成功关闭DTC")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "接收指令成功关闭DTC\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0xc5 and t_ReceiveData[1] == 0x01:
print("指令成功开启DTC")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "接收指令成功开启DTC\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x68 and t_ReceiveData[1] == 0x03:
print("指令成功,禁止非诊断报文的发送和接收")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "接收指令成功,禁止非诊断报文的发送和接收\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x68 and t_ReceiveData[1] == 0x01:
print("指令成功,允许非诊断报文的发送和接收")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "接收指令成功,允许非诊断报文的发送和接收\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x54:
print("指令成功,清除诊断信息")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "接收指令成功,清除诊断信息\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
# boot指令集
if t_ReceiveData[0] == 0x50 and t_ReceiveData[1] == 0x02:
print("进入编程会话模式")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "进入编程会话模式请在30S内执行相关指令超时后将执行旧APP程序\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x62:
print("读取DID数据成功")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "读取DID"\
+ str('%02x' % t_ReceiveData[1]) + str('%02x' % t_ReceiveData[2]) + "数据成功:"\
+ hex(t_ReceiveData[3]) + "_" + chr(int(hex(t_ReceiveData[3]), 16)) + " " \
+ hex(t_ReceiveData[4]) + "_" + chr(int(hex(t_ReceiveData[4]), 16)) + "\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x67 and t_ReceiveData[1] == 0x01:
print("获取seed成功")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "安全访问_获取seed成功请验证key"\
+ hex(t_ReceiveData[2]) + " " \
+ hex(t_ReceiveData[3]) + "\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x67 and t_ReceiveData[1] == 0x02:
print("验证key成功")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "验证key成功解锁成功\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x6e:
print("写入DID数据成功")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "写入DID"+\
str('%02x' % t_ReceiveData[1]) + str('%02x' % t_ReceiveData[2]) + "数据成功\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x71 and t_ReceiveData[1] == 0x01:
print("执行RID成功")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "执行RID"+\
str('%02x' % t_ReceiveData[2]) + str('%02x' % t_ReceiveData[3]) + "成功"
if t_ReceiveData[2] == 0x10 and t_ReceiveData[3] == 0x05:
LOG_str = LOG_str + "(擦除Memory)\n"
elif t_ReceiveData[2] == 0x15 and t_ReceiveData[3] == 0x55 and t_ReceiveData[3] == 0x77:
LOG_str = LOG_str + "(检查完整性,简单校验OK)\n"
elif t_ReceiveData[2] == 0x15 and t_ReceiveData[3] == 0x55 and t_ReceiveData[3] == 0x66:
LOG_str = LOG_str + "(检查完整性,简单校验ERR)\n"
else:
LOG_str = LOG_str + "\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x74:
print("请求下载成功")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "请求下载成功,最大数据块:"
if t_ReceiveData[1] != 0x00:
t_len = t_ReceiveData[1]>>4
t_lenSize = t_len
for len_i in range(2, 2 + t_lenSize):
LOG_str = LOG_str + str('%02x' % t_ReceiveData[len_i])
LOG_str = LOG_str + "请开始传输BIN文件\n"
else:
LOG_str = LOG_str + "请开始传输BIN文件\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x76 and t_ReceiveData[1] == 0x01:
print("传输完成")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "传输完成\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x77:
print("退出传输完成")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "退出传输完成\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
if t_ReceiveData[0] == 0x51 and t_ReceiveData[1] == 0x55:
print("跳转APP")
textLOG.config(state="normal")
LOG_str = "[" + time.strftime('%H:%M:%S', time.localtime(time.time())) + "][CAN_Analyse] " + "程序即将跳转APP\n"
textLOG.insert(tkinter.INSERT, LOG_str)
# 移动到最新一行
textLOG.yview_moveto(1)
textLOG.config(state="disabled")
win.update()
return ret
# win.update()
return ret
def CAN_Start():
# 初始化CAN1
init(CAN_INDEX_1)
# 启动CAN1
start(CAN_INDEX_1)
# ==================== IAP 升级相关 ====================
# IAP模式标志: True时暂停异步接收线程, 由send_and_wait同步收发
g_iap_mode = False
def iap_log(msg):
"""IAP日志输出到界面(青色)"""
log_append(msg, "iap")
def can_send(data_list):
"""发送一帧CAN数据(8字节, 不更新日志, 用于IAP流程)"""
global g_tx_count
ubyte_array_8 = c_ubyte * 8
DATA = ubyte_array_8(*data_list[:8])
ubyte_array_3 = c_ubyte * 3
RESERVED_3 = ubyte_array_3(0, 0, 0)
can_obj = VCI_CAN_OBJ(TRANSMIT_ID, 0, 0, 1, 0, 0, 8, DATA, RESERVED_3)
ret = Can_DLL.VCI_Transmit(VCI_USB_CAN_2, DEV_INDEX, CAN_INDEX_1, byref(can_obj), 1)
if ret == STATUS_OK:
g_tx_count += 1
return ret
def can_recv(timeout_ms=2000):
"""同步接收一帧CAN数据(阻塞等待, 超时返回None)"""
global g_rx_count
ubyte_array_8 = c_ubyte * 8
DATA = ubyte_array_8(0, 0, 0, 0, 0, 0, 0, 0)
ubyte_array_3 = c_ubyte * 3
RESERVED_3 = ubyte_array_3(0, 0, 0)
can_obj = VCI_CAN_OBJ(0, 0, 0, 0, 0, 0, 8, DATA, RESERVED_3)
start_time = time.time()
while True:
ret = Can_DLL.VCI_Receive(VCI_USB_CAN_2, DEV_INDEX, CAN_INDEX_1,
byref(can_obj), RECEIVE_LEN, 0)
if ret == STATUS_OK:
g_rx_count += 1
return list(can_obj.Data)
if (time.time() - start_time) * 1000 > timeout_ms:
return None
time.sleep(0.001)
def send_and_wait(data, timeout_ms=2000):
"""发送数据并同步等待响应帧"""
can_send(data)
return can_recv(timeout_ms)
def IAP_Upgrade(bin_path):
"""IAP完整升级流程: 握手→擦除→下载→传输→校验→复位"""
global g_iap_mode
# 读取固件文件
try:
with open(bin_path, 'rb') as f:
fw_data = f.read()
except Exception as e:
iap_log("错误: 无法读取固件文件 - %s" % str(e))
return
fw_size = len(fw_data)
iap_log("=" * 40)
iap_log("开始IAP升级")
iap_log("固件路径: %s" % bin_path)
iap_log("固件大小: %d 字节 (%.1f KB)" % (fw_size, fw_size / 1024))
# 进入IAP模式(暂停异步接收线程)
g_iap_mode = True
time.sleep(0.02) # 等待接收线程退出当前循环
try:
# ===== 阶段1: 握手 =====
iap_log("--- 阶段1: 握手 ---")
# 0x10 进入编程会话
resp = send_and_wait([0x10, 0x02, 0, 0, 0, 0, 0, 0])
if not resp or resp[0] != 0x50:
iap_log("错误: 进入编程会话失败 resp=%s" % resp)
return
iap_log("进入编程会话成功")
# 0x22 读版本
resp = send_and_wait([0x22, 0xF1, 0x00, 0, 0, 0, 0, 0])
if not resp or resp[0] != 0x62:
iap_log("错误: 读版本失败 resp=%s" % resp)
return
ver = "".join(chr(b) for b in resp[3:8] if 32 <= b < 127)
iap_log("Bootloader版本: %s" % ver)
# 0x27 安全访问 (Seed/Key)
resp = send_and_wait([0x27, 0x01, 0, 0, 0, 0, 0, 0])
if not resp or resp[0] != 0x67:
iap_log("错误: 请求Seed失败 resp=%s" % resp)
return
seed = resp[2:6]
key = [~b & 0xFF for b in seed] # Key = ~Seed
iap_log("Seed: %s" % " ".join("%02X" % b for b in seed))
resp = send_and_wait([0x27, 0x02] + key, 2000)
if not resp or resp[0] != 0x67:
iap_log("错误: Key验证失败 resp=%s" % resp)
return
iap_log("安全解锁成功")
# ===== 阶段2: 数据传输 =====
iap_log("--- 阶段2: 数据传输 ---")
# 0x31 擦除App区
resp = send_and_wait([0x31, 0x01, 0xFF, 0x01, 0, 0, 0, 0], 5000)
if not resp or resp[0] != 0x71:
iap_log("错误: 擦除App区失败 resp=%s" % resp)
return
iap_log("擦除App区成功")
# 0x34 请求下载(声明固件大小, 支持断电续传)
size_bytes = list(fw_size.to_bytes(4, 'big'))
resp = send_and_wait([0x34] + size_bytes, 5000)
if not resp or resp[0] != 0x74:
iap_log("错误: 请求下载失败 resp=%s" % resp)
return
# 解析续传偏移量(resp[2:6]为offset大端4字节)
offset = int.from_bytes(bytes(resp[2:6]), 'big')
if offset > 0:
iap_log("断电续传: 从偏移 %d 字节 (%.1f KB) 处继续" % (offset, offset / 1024))
else:
iap_log("全新升级, 从头开始传输")
# 0x36 01 开始数据传输(进入纯数据模式)
resp = send_and_wait([0x36, 0x01, 0, 0, 0, 0, 0, 0])
if not resp or resp[0] != 0x76:
iap_log("错误: 开始传输失败 resp=%s" % resp)
return
iap_log("进入纯数据模式, 开始传输固件...")
# 纯数据模式: 8字节/帧, 无响应, 从offset处开始发送
total = fw_size - offset
sent = 0
last_progress = 0
for i in range(offset, fw_size, 8):
chunk = list(fw_data[i:i + 8])
# 不足8字节补0xFF
while len(chunk) < 8:
chunk.append(0xFF)
can_send(chunk)
sent += min(8, fw_size - i)
# 每1KB显示一次进度
if sent - last_progress >= 1024:
pct = sent * 100 // total
iap_log("传输进度: %d/%d 字节 (%d%%)" % (sent, total, pct))
last_progress = sent
time.sleep(0.001) # 1ms间隔, 防止总线拥塞
iap_log("传输进度: %d/%d 字节 (100%%)" % (total, total))
# 等待传输完成响应(板子收够fw_size后自动发0x76)
resp = can_recv(3000)
if not resp or resp[0] != 0x76:
iap_log("错误: 等待传输完成响应超时 resp=%s" % resp)
return
iap_log("数据传输完成")
# 0x37 传输退出
resp = send_and_wait([0x37, 0, 0, 0, 0, 0, 0, 0])
if not resp or resp[0] != 0x77:
iap_log("错误: 传输退出失败 resp=%s" % resp)
return
iap_log("传输退出成功")
# ===== 阶段3: 校验烧写 =====
iap_log("--- 阶段3: 校验烧写 ---")
# 0x31 CRC校验
resp = send_and_wait([0x31, 0x01, 0xFF, 0x02, 0, 0, 0, 0], 5000)
if not resp or resp[0] != 0x71:
iap_log("错误: CRC校验失败 resp=%s" % resp)
return
# 读取板子计算的CRC32
board_crc = int.from_bytes(bytes(resp[4:8]), 'big')
# 计算本地CRC32 (与板子一致: IEEE 802.3)
local_crc = binascii.crc32(fw_data) & 0xFFFFFFFF
iap_log("板子CRC: 0x%08X, 本地CRC: 0x%08X" % (board_crc, local_crc))
if board_crc != local_crc:
iap_log("错误: CRC校验不匹配! 升级失败")
return
iap_log("CRC校验通过")
# 0x11 复位跳转App
resp = send_and_wait([0x11, 0x01, 0, 0, 0, 0, 0, 0])
if not resp or resp[0] != 0x51:
iap_log("错误: 复位失败 resp=%s" % resp)
return
iap_log("复位成功, 等待跳转App...")
iap_log("=" * 40)
iap_log("IAP升级成功! 固件已烧写并校验通过")
except Exception as e:
iap_log("异常: %s" % str(e))
finally:
g_iap_mode = False # 恢复异步接收线程
def ReadFile(BIN_PATH):
# filepath='C:\\Users\\\HenchYoung\\Desktop\\LED2000.bin'
filepath = BIN_PATH
binfile = open(filepath, 'rb') #打开二进制文件
size = os.path.getsize(filepath) #获得文件大小
# for i in range(size):
# data = binfile.read(1) #每次输出一个字节
# print(data)
# print(i)
i = 0
Send_BUFF = [0,0,0,0,0,0,0,0]
# Send_BUFF = [b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00', b'\x00']
# with open("C:\\Users\\HenchYoung\\Desktop\\hex.c", "w") as f:
# f.write("NEW ")
while 1:
c = binfile.read(1)
# 将字节转换成16进制
ssss = str(binascii.b2a_hex(c))[2:-1]
# print(str(binascii.b2a_hex(c))[2:-1])
if not c:
t_sendstr = ""
for sdata in Send_BUFF:
t_sendstr = t_sendstr + " " + hex(int(str(sdata),16))
t_sendstr = t_sendstr + "\n" + str(BIN_PATH)
t_sendstr = t_sendstr + " 固件发送完成,共计:" + str(i) + "字节"
transmitBIN(CAN_INDEX_1, Send_BUFF, t_sendstr)
# with open("C:\\Users\\HenchYoung\\Desktop\\hex.c", "a") as f:
# for se in Send_BUFF:
# f.write(" ")
# f.write(str(se))
# f.write(" ")
# f.write("\n")
Send_BUFF = [0,0,0,0,0,0,0,0]
time.sleep(0.001)
break
# Send_BUFF[i%8] = int(str(ssss),16)
Send_BUFF[i % 8] = int(str(ssss),16)
i = i + 1
if i % 8 == 0:
t_sendstr = ""
for sdata in Send_BUFF:
t_sendstr = t_sendstr + " " + hex(int(str(sdata), 16))
transmitBIN(CAN_INDEX_1, Send_BUFF, t_sendstr)
# with open("C:\\Users\\HenchYoung\\Desktop\\hex.c", "a") as f:
# for se in Send_BUFF:
# f.write(" ")
# f.write(str(se))
# f.write(" ")
# f.write("\n")
Send_BUFF = [0,0,0,0,0,0,0,0]
time.sleep(0.001)
if i == size:
break
# print(ssss)
# print(int(str(ssss),16))
# print(c)
# print(bytes().fromhex(ssss)) # 将16进制转换为字节
# if i % 16 == 0:
# time.sleep(0.001)
# 写每一行等待的时间
print(i)
binfile.close()
print(size)
if __name__ == '__main__':
global win #界面实体
global CAN_Send_entry #发送数据的内容
global BIN_path_entry #BIN文件路径
global textLOG #textbox的数据
global scroll #滚动条实体
global CAN_INDEX_1
global CAN_INDEX_2
global g_winOVER
global threadREV
g_winOVER = 0
CAN_INDEX_1 = 0
CAN_INDEX_2 = 1
time1 = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
g_WindowStart()
#关闭
Can_DLL.VCI_CloseDevice(VCI_USB_CAN_2, 0)
print("CAN end")
print(time1)
print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())))
# print(time.strftime('%H:%M:%S', time.localtime(time.time())))