pATAq 发表于 2024-5-10 16:49:34

玩转创想三维 K1 系列主板之一:在线更新 MCU 固件


MCU 固件

# 前言

本文是摸索创想三维 K1 系列软硬件系统的一些内容分享。本系列文章由于与他们的软件工程师无沟通渠道,仅能凭经验与黑盒测试,故有盲人摸象之可能,如有谬误,欢迎指正交流。**下一期会讲如何编译创想三维魔改版 Klipper 固件。**

由于有为 K1 系列 MCU 升级固件的需求,被告知使用私有 bootloader,具体用法不透露。好在主板上预留 SWD 调试引脚,但是对普通用户不友好,希望有 Katapult Bootloader 那种支持无接触线刷升级 Klipper 固件的方式就好了。后来在探索 CrealityOS 过程中,我们查看启动服务发现一个 `/etc/init.d/S13mcu_update`的自启动服务,看名称和升级固件有关,大喜过望,失望而归,提供的 mcu_util 工作不符合预期(可能是我使用方法有误,具体见下文。不过 S13mcu_update 很值得一看,逻辑思路比较完善)。

> 可以在 (https://github.com/Guilouz/Creality-K1-Extracted-Firmwares) 处查看已解包系统镜像。

![](https://www.emojidquan.com/Pics/meme/202302/0bc31b8eaa41.jpg)

我们约定,直接控制打印机硬件的部分称为主板,运行 Linux 系统的部分称为上位机。

**运行测试环境:**

1. K1C 和 K1Max 主板(两块主板硬件配置有所不同)
2. Ubuntu 22.04 Arm64
3. CrealityOS

**本文涉及的内容:**

1. 如何使用原厂 bootloader 升级打印机主板固件

| MCU               | 设备地址            | 备注         |
| ----------------- | --------------------- | -------------- |
| mcu (mcu0)      | /dev/ttyS7            | 主 MCU         |
| nozzle_mcu      | /dev/ttyS1            | 热端 MCU       |
| leveling_mcu      | /dev/ttyS9            | 热床调平 MCU   |
| rpi (host as mcu) | /tmp/klipper_host_mcu | 上位机作为 MCU |

## 1、升级主板固件(精简版)

需要使用 chs 进行 root,安装 supervisorctl 用来管理服务(非必须,随 moonraker 安装)。

### 1.1 使用第三方 mcu_util.py 工具

此处以 `mcu0` 为例,由于官方提供的工具 `mcu_util` 握手之后会超时报错,所以我们使用第三方工具。

```shell
# 关闭 klipper,取消对串口设备的占用
supervisorctl stop klipper

# 部署第三方固件烧录工具
cd && git clone https://github.com/cryoz/k1_mcu_flasher && cd k1_mcu_flasher

# 为 mcu0(/dev/ttyS7) 烧录固件,以原厂固件为例
# 流程:重置 mcu0 ——> 等待 2s ——> 上传固件
mcu_reset.sh && sleep 2 && ./mcu_util.py -c -v -i /dev/ttyS7 -u -f /usr/share/klipper/fw/K1/mcu0_120_G32-mcu0_004_000.bin

# k1_mcu_flasher 用法
usage: mcu_util.py [-h] [-v] [-c] -i PORT [-f FILE] [-u] [-s] [-g]
Creality K1 MCU Flasher
optional arguments:
-h, --help            show this help message and exit
-v, --verbose         Debug output
-c, --handshake       Attempt handshake before operation
-i PORT, --port PORTserial device
-f FILE, --file FILEfirmware file
-u, --update          Update firmware from file
-s, --appstart      Attempt to start fw
-g, --version         Get version

# 其他用法示例
# 注意:握手仅需要一次,每个选项都包含握手操作,所以不需要先单独握手在执行其他操作
## 与 mcu0 进行握手,并显示详情
./mcu_util.py -c -v -i /dev/ttyS7

## 与 mcu0 握手并获取 mcu 固件版本
./mcu_util.py -c -v -i /dev/ttyS7 -g

## 启动固件(如果没有自动启动)
./mcu_util.py -c -v -i /dev/ttyS7 -s
```

### 1.2 同样操作使用原厂 mcu_util 超时报错

偶尔可以握手成功。

```shell
mcu_reset.sh && sleep 2 && mcu_util -c -v -i /dev/ttyS7 -u -f /usr/share/klipper/fw/K1/mcu0_120_G32-mcu0_004_000.bin
usart_send_Process: get_sector_size
usart_rec_Process: select time out, state = 6
usart_rec_Process: select time out, state = 6
usart_rec_Process: select time out, state = 6
usart_rec_Process: timeout
usart_sent_retval: 1, usart_rec_retval: 1
```

**再次询问工程师此工具正确用法,无回复。**

### 1.3 烧录更新其他三个 MCU

* 热端和调平 MCU 使用 RS232 串口与上位机通讯,没有专门的重置方式,应该有未知的固件重启命令,或者采用 Klipper 通用的命令进入 bl 状态,具体见之前文章 [无接触线刷Klipper固件:千秋万载,一统江湖之Katapult](https://www.cnblogs.com/sjqlwy/p/18022222/Katapult) ,等待后续测试
* 还有一种是思路是写一个 initi.d 开机启动服务,在 mcu 上电 15s 内完成握手
* host_as_mcu 可以使用 klipper_mcu 服务停止

## 2、K1 Bootloader 烧录流程解析

> 注:此部分属于对 k1_mcu_flasher 的注释,只能说作者是专业的。
>
> 原程序 /usr/bin/mcu_util 是二进制文件(怀疑是加密的 shell 脚本)。

### mcu_util.py 代码分析

- 导入必要的模块:
- `binascii` 用于编码和解码十六进制数据。
- `io` 用于读取文件。
- `pathlib` 用于处理文件路径。
- `argparse` 用于解析命令行参数。
- `sys` 用于退出程序。
- `serial` 用于与串行设备通信。
- 定义 `crc` 函数:用于计算 CRC 校验和。
- 定义 `debug` 函数:用于打印调试信息。
- 定义 `_handshake` 函数:用于与 MCU 进行握手。
- 定义 `_get_version` 函数:用于获取 MCU 的版本信息。
- 定义 `_get_sector_size` 函数:用于获取 MCU 的扇区大小。
- 定义 `_app_start` 函数:用于启动 MCU 的应用程序。
- 定义 `_flash_fw` 函数:用于更新 MCU 的固件。
- 定义 `open_port` 函数:用于打开指定的串行端口。
- 定义 `handshake` 函数:用于执行握手操作。
- 定义 `get_version` 函数:用于获取 MCU 的版本信息。
- 定义 `app_start` 函数:用于启动 MCU 的应用程序。
- 定义 `update` 函数:用于更新 MCU 的固件。
- 解析命令行参数:使用 `argparse` 模块解析命令行参数。
- 根据命令行参数执行相应的操作:如果指定了 `c` 或 `-handshake` 选项,则执行握手操作;如果指定了 `g` 或 `-version` 选项,则获取 MCU 的版本信息;如果指定了 `u` 或 `-update` 选项,则更新 MCU 的固件;如果指定了 `s` 或 `-appstart` 选项,则启动 MCU 的应用程序。
- 如果没有指定任何操作,则打印帮助信息并退出。
- 执行相应的操作并返回退出代码。

### 2.1 Handshake stage 握手阶段

> bootloader waiting 15 secs after startup for handshake, then launch app if app corrupted by crc16 - bootloader waiting for handshake forever ALL stages requred passing handshake stage ONCE **send: 0x75, receive ack: 0x75**

- bootloader (bl) 启动后等待 15s 用于握手,之后启动 app (klipper)。
- 如果 app 的 crc16 检校失败,bl 会一直等待握手
- 所有阶段都需要握手通过 1 次且只需要一次
- 发送 0x75,返回 0x75
- 编者注:握手是为了在 15s 内截住 bootloader,不让其启动 App。mcu_util.py 的每个命令都有握手操作。

### 2.2 Version stage 版本验证阶段

> bootloader checks for crc16 of app, if passed - combine hw version string (in bootloader area) and fw version string (in fw area) if crc16 not passed - sending 25 bytes of 0x00 send 00ff (ff - crc), receive string (25 bytes+crc) of combined hw version and fw version

- bl 会检查 app 的 crc16
- 如果通过检测,附加 hw 和 fw 版本字符
- 如果未通过检测,发送 25 字节的 0x00
- 向 bl 发送 00ff (ff - crc),返回 (25 bytes+crc),以及 hw 和 sw 版本
- 说的不清楚,有需求的具体看源码

```python
      # 解码返回信息
      r = ser.read(26)
      if len(r) > 0:
            debug(f'rcv data {hexlify(r)}', v)
            if len(r) == 26 and r == crc(r[:-1]):
                debug(f'version received! {r[:-1]}', v)
                result = bytes(r[:-1]).decode(encoding='latin')
```

> 在这个代码中,`r`是一个字节数组,`[:-1]`表示取`r`的前`len(r)-1`个元素,即去掉最后一个字节。然后,使用`decode`方法将字节数组转换为字符串,并指定编码为`latin`。这将把字节数组转换为一个使用`latin`编码的字符串。

### 2.3 Get sector size stage 获取扇区大小阶段

> mostly = 1, multiplier for receive buffer of firmware send 03fc (fc - crc), receive sector size (1 byte+crc)

- 多数情况为固件大小的 1 倍
- 发送 03fc (fc - crc),返回扇区大小

### 2.4 App start stage 启动 App 阶段

> bootloader check crc16 of fw in flash, if succeded - passes program flow to fw entrypoint send 02fd (fd - crc), receive ack 0x75

- bl 检查闪存内固件的 crc16,如果通过,进入正常 App
- 发送 02fd (fd - crc), 返回 0x75

### 2.5 Flash FW stage 固件烧录更新阶段

> receive fw by chunks, size of chunks = sector size << 16, to ram, then writes to flash.
> 1. update request: send 0xfe (fe - crc), receive ack 0x75
> 2. send fw size: send dword of size with leading crc, receive ack 0x75
> 3. send chunks by chunk-size, receive statuses: 0x75 - chunk succeded 0x20 - all firmware flashed 0x21 - error in write ram->rom stage 0x1f - bad crc of received data
>

- 分块传输固件到 mcu ram 中,然后写入 flash
- 发送更新请求
- 发送固件大小,会生成crc
- 发送大小,返回状态符(见上)



由于创想对 Klipper 做了很多魔改,我们先使用官方的 Klipper 仓库,默认支持 GD32F303。

我们要为 Arm Crotex-M 编译固件,用到 (https://developer.arm.com/downloads/-/gnu-rm),目前仅支持 AArch64/x86_64 而不支持 MIPS 架构 CPU 所以只能在其他设备上编译固件,再进行烧录。也由于这点,CrealityOS 的 klipper 直接精简掉了编译固件的源码。

```shell
# 下载创想三维修改版 Klipper
git clone https://github.com/CrealityOfficial/K1_Series_Klipper && cd K1_Series_Klipper
# 由于原来的是 MIPS 的,需要删除 c_helper.so 重新编译
python
# 由于我使用 Ubuntu 22.04,需要降级到 gcc-arm-none-eabi 10.0 以下以正确编译 prtouch_v2
sudo apt preference
# 安装缺少的软件包
sudo apt install
# 修改编译参数
make menuconfig
make
```

tent 发表于 2024-5-13 10:42:57

好高深的感觉。。。坐等最终方案

维立信测试仪器 发表于 2024-5-15 15:53:33

主要就是来学习的

wellf 发表于 2024-8-29 14:57:21

从B站过来的。。。
页: [1]
查看完整版本: 玩转创想三维 K1 系列主板之一:在线更新 MCU 固件