1284 lines
50 KiB
Python
1284 lines
50 KiB
Python
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())))
|
||
|
||
|