基于Tkinter制作简易的CAN bootloader上位机

发布时间:2023年12月20日

1.前言

之前基于S32K144EVB和Tkinter编写了一个简易的串口bootloader上位机,链接如下:

但在实际应用过程中,使用CAN通信升级MCU的APP程序更为常见。因此,笔者花了几天时间,做了一个简易的CAN bootloader上位机。

2.测试设备

整个测试台架示意图如下:

测试台架

需要用到的测试设备如下:

  • S32K144EVB-Q100
  • 12V电源
  • USBCAN-E-mini
  • PC

使用S32K144EVB的CAN功能时,需要12V供电,因为开发板使用的是CAN SBC-UJA1169,而不是常见的CAN收发器TJA1042。

MCU的Bootloader程序和升级文件,来源于公众号《汽车电子expert成长之路》,链接如下:

关于bootloader的流程以及设计思路,上述的链接文档讲解的非常详细,这里就不在赘述。

3.上位机

3.1 参考资料

ZLG致远电子官网有基于Python Tkiner的例程,链接为:

  • https://www.zlg.cn/data/upload/software/Can/zlgcan_demo.rar

本文介绍的上位机的布局框架基本沿用该例程,主要修改点为增加下位机的通信交互以及加载升级文件的功能,删除了常规的报文发送、接收以及报文回显功能。

3.2 上位机主要功能

上位机主要功能

整个上位机的主要功能如上图所示,

  • 主线程负责整体界面的显示,包含设备选择通道配置设备信息数据发送状态以及ECU刷写等五个子组件:
    • 设备选择组件用于选择USBCAN卡的型号;
    • 通道配置组件用于选择使用的CAN通道以及相关的工作模式、波特率等信息;
    • 设备信息组件用于显示USBCAN卡的硬件版本、固件版本、驱动版本等设备信息;
    • 数据发送状态组件用于显示当前数据发送的状态,如是否开始发送、当前发送的是第几行文件数据
    • ECU刷写组件包含升级文件选择框,发送按钮以及升级进度条;
  • 子线程负责发送主线程准备好的文件内容,以及接收MCU反馈的状态信息。

3.3 上位机发送流程

上位机发送流程

上位机和MCU的交互流程如上图所示,

  1. 500ms之内上位机发送DOWN_LINK,MCU收到指令之后回复UP_BUSY

测试时如果无法控制MCU的Reset和上位机开始发送之间的延时在500ms之内,可以将bootloader程序的接收超时时间改为5s。

  1. MCU再次回复UP_BUSY,接着回复UP_READY
  2. 上位机逐行发送S19文件,MCU收到数据之后回复UP_BUSY
    上位机发送S19文件,以字符的ASCII编码方式发送
  3. MCU将CAN数据保存到指定的数组之后,回复UP_READY
  4. 上位机发完一行数据之后,发送DOWN_LINE_END,MCU收到指令之后回复UP_BUSY
  5. MCU再次回复UP_BUSY并将接收到的一行数据进行处理,如果是合法数据,刷入对应的地址,否则丢弃(第一次刷写flash会进行大规模擦除);
  6. MCU处理完数据之后,回复UP_READY
  7. 上位机发完最后一行数据并且等待MCU回复UP_READY之后,发送DOWN_FILE_END,MCU收到指令之后回复UP_BUSY,然后再回复UP_PRGEND
  8. 至此上位机工作结束,MCU进行跳转APP的操作。

这部分功能的主要代码如下:

# 固定为can帧
is_canfd_msg = False
if is_canfd_msg:
    msg = ZCAN_TransmitFD_Data()
else:
    msg = ZCAN_Transmit_Data()
# "正常发送"
msg.transmit_type = 0
try:
    msg.frame.can_id = DOWN_ID
except:
    msg.frame.can_id = 0
# "数据帧"   
msg.frame.rtr = 0
# "标准帧"
msg.frame.eff = 0

if not is_canfd_msg:
    msg.frame.can_dlc = 8
    msg_len = msg.frame.can_dlc
else:
    msg.frame.brs = 1 if self.cmbMsgCANFD.current() == 2 else 0
    msg.frame.len = self.__dlc2len(self.cmbMsgLen.current())
    msg_len = msg.frame.len


data = ("FF FF FF FF FF FF FF FF").split(' ')
for i in range(msg_len):
    if i < len(data):
        try:
            msg.frame.data[i] = int(data[i], 16)
        except:
            msg.frame.data[i] = 0
    else:
        msg.frame.data[i] = 0
# 发送帧数
msg_num = 1
# 发送次数,多次发送功能未实现
msg_cnt = 1
# 发送间隔(ms)
period  = 1
# ID递增
id_is_add = False

self.OutputText.insert(tk.END,"Request MCU to receive the file !\r\n")
for i in range(100):
    self.MsgSend(msg, is_canfd_msg, msg_num, msg_cnt, period, id_is_add)
    time.sleep(0.005)
    if not USE_THREAD:
        self.MsgReadFunc()
    if(self.mcu_Status == UP_READY_STATUS): 
        break
if(self.mcu_Status == UP_READY_STATUS):
    self.OutputText.insert(tk.END,"Start sending the file !\r\n")
    self.progressbarSend['maximum']=len(self.str_appFile)
    for appFile_line in range(len(self.str_appFile)):
        self.progressbarSend["value"] = appFile_line + 1
        self.OutputText.insert(tk.END,"The data of line "+str(appFile_line+1)+ " was sent!\r\n")
        # dispaly update
        self.OutputText.yview_moveto(1)
        strToSend = self.str_appFile[appFile_line].strip()
        listToSend = list(strToSend)
        NumOfFrame = len(listToSend)//msg_len
        LenOfLastFrame = len(listToSend)%msg_len
        NumOfSend = 0
        for j in range(NumOfFrame):
            for i in range(msg_len):
                msg.frame.data[i] = ord(listToSend[NumOfSend])
                logging.debug('No %d row, No %d column, No %d frame, data[%d] is 0x%x', appFile_line, NumOfSend, j, i, msg.frame.data[i])
                NumOfSend += 1
            while True:
                logging.debug('In while 1:send first 8N data of line')
                if not USE_THREAD:
                    self.MsgReadFunc()
                if(self.mcu_Status == UP_READY_STATUS): 
                    self.mcu_Status = UP_ERR_Str                     
                    self.MsgSend(msg, is_canfd_msg, msg_num, msg_cnt, period, id_is_add)
                    break

        msg.frame.can_dlc = LenOfLastFrame
        for i in range(LenOfLastFrame):
            msg.frame.data[i] = ord(listToSend[NumOfSend])
            logging.debug('No %d row, No %d column, No %d frame, data[%d] is 0x%x', appFile_line, NumOfSend, j+1, i, msg.frame.data[i])
            NumOfSend += 1
        while True:
            logging.debug('In while 2:send rest data of line')
            if not USE_THREAD:
                self.MsgReadFunc()
            if(self.mcu_Status == UP_READY_STATUS):  
                self.mcu_Status = UP_ERR_Str                     
                self.MsgSend(msg, is_canfd_msg, msg_num, msg_cnt, period, id_is_add)
                break
        # one line of file was send    
        if(appFile_line == (len(self.str_appFile)-1)):
            msg.frame.data[0] =  DOWN_FILE_END_CMD
        else:
            msg.frame.data[0] =  DOWN_LINE_END_CMD
        while True: 
            # last line of S19 is inactive,so don't send DOWN_LINE_END_CMD to programing
            if msg.frame.data[0] ==  DOWN_FILE_END_CMD:
                logging.debug('In while 3:send cmd of DOWN_FILE_END')
            elif msg.frame.data[0] ==  DOWN_LINE_END_CMD:
                logging.debug('In while 3:send cmd of DOWN_LINE_END')
            if not USE_THREAD:
                self.MsgReadFunc()
            if(self.mcu_Status == UP_READY_STATUS): 
                self.mcu_Status = UP_ERR_Str                      
                self.MsgSend(msg, is_canfd_msg, msg_num, msg_cnt, period, id_is_add)
                break                  
        msg.frame.can_dlc = 8
    # all line of file was send
    self.OutputText.insert(tk.END,"The file was sent successfully !\r\n")

升级测试

整个GUI测试情况如下动图所示,

测试情况.gif

例程分享

此次文中提到的测试设备的程序以及上位机源码已分享到gitee,链接接如下:

  • https://gitee.com/Yingming_Cai/tkinter_-s32-k144-evb_-can_-bootloader.git

如果觉得本文对您有用,,不妨给个一键三连!!!

文章来源:https://blog.csdn.net/bjxdbz/article/details/135094644
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。