From 63f2dd4659275a232e59d23fd14e5bb742ead01f Mon Sep 17 00:00:00 2001 From: mengwei <1090557361@qq.com> Date: Mon, 25 May 2026 21:21:07 +0800 Subject: [PATCH 1/2] feat(ec718): support direct binpkg flashing --- crates/luatos-cli/src/cmd_flash.rs | 34 +++++++++++++++++++++++++++++- crates/luatos-cli/src/main.rs | 30 +++++++++++++++++++++++--- crates/luatos-flash/src/ec718.rs | 16 ++++++++++++++ 3 files changed, 76 insertions(+), 4 deletions(-) diff --git a/crates/luatos-cli/src/cmd_flash.rs b/crates/luatos-cli/src/cmd_flash.rs index 8167389..f8c80cd 100644 --- a/crates/luatos-cli/src/cmd_flash.rs +++ b/crates/luatos-cli/src/cmd_flash.rs @@ -24,9 +24,11 @@ fn check_script_size(image_len: usize, partition_size: usize) -> anyhow::Result< } pub fn cmd_flash_run( - soc: &str, + soc: Option<&str>, + binpkg: Option<&str>, port: &str, baud: Option, + force_br: Option, script_folders: Option<&[String]>, step: u8, format: &OutputFormat, @@ -46,6 +48,36 @@ pub fn cmd_flash_run( let on_progress = make_progress_callback(format, "flash.run", step); + if let Some(binpkg) = binpkg { + if soc.is_some() { + anyhow::bail!("--soc and --binpkg are mutually exclusive"); + } + if baud.is_some() { + anyhow::bail!("--baud is only supported with --soc flashing"); + } + if script_folders.is_some() { + anyhow::bail!("--script is only supported with --soc flashing"); + } + if reset_config.is_some() { + anyhow::bail!("--auto-reset is not supported with EC718 --binpkg flashing"); + } + + let boot_port = luatos_flash::ec718::auto_enter_boot_mode(Some(port), &on_progress)?; + luatos_flash::ec718::flash_ec718_binpkg(binpkg, &boot_port, force_br.unwrap_or(0), &on_progress, cancel)?; + match format { + OutputFormat::Text => { + println!("EC718 BINPKG flash completed successfully."); + } + OutputFormat::Json | OutputFormat::Jsonl => event::emit_result(format, "flash.run", "ok", serde_json::json!({ "chip": "ec7xx", "format": "binpkg" }))?, + } + return Ok(()); + } + + let soc = soc.ok_or_else(|| anyhow::anyhow!("Either --soc or --binpkg must be specified"))?; + if force_br.is_some() { + anyhow::bail!("--force-br is only supported with EC718 --binpkg flashing"); + } + // Detect chip type from SOC info.json let info = luatos_soc::read_soc_info(soc)?; let chip = info.chip.chip_type.as_str(); diff --git a/crates/luatos-cli/src/main.rs b/crates/luatos-cli/src/main.rs index cd2b3ae..e30d0e3 100644 --- a/crates/luatos-cli/src/main.rs +++ b/crates/luatos-cli/src/main.rs @@ -5,9 +5,10 @@ // luatos-cli soc info # Show SOC file info // luatos-cli soc unpack -o dir # Extract SOC file // luatos-cli flash run --soc --port COM6 +// luatos-cli flash run --binpkg --port COM6 // luatos-cli flash test --soc --port COM6 -use clap::{Parser, Subcommand}; +use clap::{ArgGroup, Parser, Subcommand}; mod cmd_build; mod cmd_device; @@ -170,16 +171,27 @@ impl SignalLevel { #[derive(Subcommand)] enum FlashCommands { /// Full firmware flash (ROM + optional script) + #[command(group( + ArgGroup::new("firmware") + .required(true) + .args(["soc", "binpkg"]) + ))] Run { /// Path to .soc file #[arg(long)] - soc: String, + soc: Option, + /// Path to EC718 .binpkg file + #[arg(long)] + binpkg: Option, /// Serial port (e.g. COM6) #[arg(long)] port: String, /// Override baud rate #[arg(long)] baud: Option, + /// EC718 agentboot baud override for --binpkg (0 keeps current default) + #[arg(long)] + force_br: Option, /// Script folder (optional, can specify multiple) #[arg(long)] script: Vec, @@ -638,8 +650,10 @@ fn main() { Commands::Flash { action, progress_step } => match action { FlashCommands::Run { soc, + binpkg, port, baud, + force_br, script, auto_reset, dtr_boot, @@ -659,7 +673,17 @@ fn main() { } else { None }; - cmd_flash::cmd_flash_run(&soc, &port, baud, script_opt, progress_step, &cli.format, reset_config) + cmd_flash::cmd_flash_run( + soc.as_deref(), + binpkg.as_deref(), + &port, + baud, + force_br, + script_opt, + progress_step, + &cli.format, + reset_config, + ) } FlashCommands::Script { soc, diff --git a/crates/luatos-flash/src/ec718.rs b/crates/luatos-flash/src/ec718.rs index dac7b0d..7bd9a09 100644 --- a/crates/luatos-flash/src/ec718.rs +++ b/crates/luatos-flash/src/ec718.rs @@ -1298,6 +1298,22 @@ pub fn flash_ec718(soc_path: &str, port: &str, on_progress: &ProgressCallback, c log::info!("EC718 chip: {}, {} entries, force_br={}", binpkg.chip, binpkg.entries.len(), force_br,); + flash_ec718_parsed(binpkg, force_br, port, on_progress, cancel) +} + +/// Flash EC718 firmware from a raw .binpkg file via native Rust protocol. +pub fn flash_ec718_binpkg(binpkg_path: &str, port: &str, force_br: u32, on_progress: &ProgressCallback, cancel: Arc) -> Result<()> { + on_progress(&FlashProgress::info("Preparing", 0.0, "Parsing BINPKG file...")); + + let fdata = std::fs::read(binpkg_path).with_context(|| format!("Failed to read binpkg file: {binpkg_path}"))?; + let binpkg = parse_binpkg(&fdata)?; + + log::info!("EC718 binpkg chip: {}, {} entries, force_br={}", binpkg.chip, binpkg.entries.len(), force_br,); + + flash_ec718_parsed(binpkg, force_br, port, on_progress, cancel) +} + +fn flash_ec718_parsed(binpkg: BinpkgResult, force_br: u32, port: &str, on_progress: &ProgressCallback, cancel: Arc) -> Result<()> { // Determine port type and agentboot binary let port_type = detect_port_type(port); let agentboot = match port_type { From bdf1972b01fd0dd6983d4a6ce2455b6893eb77c3 Mon Sep 17 00:00:00 2001 From: mengwei <1090557361@qq.com> Date: Tue, 26 May 2026 11:38:12 +0800 Subject: [PATCH 2/2] feat(ec718): add uart binpkg flashing mode --- crates/luatos-cli/src/cmd_flash.rs | 53 +++- crates/luatos-cli/src/main.rs | 27 +- crates/luatos-flash/src/ec718.rs | 256 ++++++++++++++++-- .../src-tauri/src/commands/flash.rs | 2 +- crates/luatos-mfgui/src/worker.rs | 2 +- docs/ec718-flash-protocol.md | 67 ++++- 6 files changed, 356 insertions(+), 51 deletions(-) diff --git a/crates/luatos-cli/src/cmd_flash.rs b/crates/luatos-cli/src/cmd_flash.rs index f8c80cd..365450e 100644 --- a/crates/luatos-cli/src/cmd_flash.rs +++ b/crates/luatos-cli/src/cmd_flash.rs @@ -2,7 +2,7 @@ use anyhow::Context; use crate::{ event::{self, MessageLevel}, - OutputFormat, + Ec718PortMode, OutputFormat, }; /// 检查脚本镜像大小是否超过分区容量,超出则报错并给出详细信息 @@ -28,7 +28,9 @@ pub fn cmd_flash_run( binpkg: Option<&str>, port: &str, baud: Option, - force_br: Option, + agent_baud: Option, + at_baud: Option, + port_mode: Ec718PortMode, script_folders: Option<&[String]>, step: u8, format: &OutputFormat, @@ -62,8 +64,18 @@ pub fn cmd_flash_run( anyhow::bail!("--auto-reset is not supported with EC718 --binpkg flashing"); } - let boot_port = luatos_flash::ec718::auto_enter_boot_mode(Some(port), &on_progress)?; - luatos_flash::ec718::flash_ec718_binpkg(binpkg, &boot_port, force_br.unwrap_or(0), &on_progress, cancel)?; + let (boot_port, forced_port_type) = match port_mode { + Ec718PortMode::Auto => (luatos_flash::ec718::auto_enter_boot_mode(Some(port), &on_progress)?, None), + Ec718PortMode::Usb => ( + luatos_flash::ec718::auto_enter_boot_mode(Some(port), &on_progress)?, + Some(luatos_flash::ec718::Ec718PortType::Usb), + ), + Ec718PortMode::Uart => { + on_progress(&luatos_flash::FlashProgress::info("Fallback", 2.0, &format!("使用用户指定端口: {} (UART模式)", port))); + (port.to_string(), Some(luatos_flash::ec718::Ec718PortType::Uart)) + } + }; + luatos_flash::ec718::flash_ec718_binpkg(binpkg, &boot_port, forced_port_type, agent_baud, at_baud, &on_progress, cancel)?; match format { OutputFormat::Text => { println!("EC718 BINPKG flash completed successfully."); @@ -74,13 +86,22 @@ pub fn cmd_flash_run( } let soc = soc.ok_or_else(|| anyhow::anyhow!("Either --soc or --binpkg must be specified"))?; - if force_br.is_some() { - anyhow::bail!("--force-br is only supported with EC718 --binpkg flashing"); - } - // Detect chip type from SOC info.json let info = luatos_soc::read_soc_info(soc)?; let chip = info.chip.chip_type.as_str(); + let is_ec718_chip = matches!(chip, "ec7xx" | "air8000" | "air780epm" | "air780ehm" | "air780ehv" | "air780ehg"); + + if !is_ec718_chip { + if agent_baud.is_some() { + anyhow::bail!("--agent-baud/--agbaud is only supported with EC718 flashing"); + } + if at_baud.is_some() { + anyhow::bail!("--at-baud is only supported with EC718 flashing"); + } + if port_mode != Ec718PortMode::Auto { + anyhow::bail!("--port-mode is only supported with EC718 flashing"); + } + } match chip { "bk72xx" | "air8101" => { @@ -118,8 +139,18 @@ pub fn cmd_flash_run( } "ec7xx" | "air8000" | "air780epm" | "air780ehm" | "air780ehv" | "air780ehg" => { // EC718 series: auto-detect boot mode, reboot if needed - let boot_port = luatos_flash::ec718::auto_enter_boot_mode(Some(port), &on_progress)?; - luatos_flash::ec718::flash_ec718(soc, &boot_port, &on_progress, cancel)?; + let (boot_port, forced_port_type) = match port_mode { + Ec718PortMode::Auto => (luatos_flash::ec718::auto_enter_boot_mode(Some(port), &on_progress)?, None), + Ec718PortMode::Usb => ( + luatos_flash::ec718::auto_enter_boot_mode(Some(port), &on_progress)?, + Some(luatos_flash::ec718::Ec718PortType::Usb), + ), + Ec718PortMode::Uart => { + on_progress(&luatos_flash::FlashProgress::info("Fallback", 2.0, &format!("使用用户指定端口: {} (UART模式)", port))); + (port.to_string(), Some(luatos_flash::ec718::Ec718PortType::Uart)) + } + }; + luatos_flash::ec718::flash_ec718(soc, &boot_port, forced_port_type, agent_baud, at_baud, &on_progress, cancel)?; match format { OutputFormat::Text => { println!("EC718 flash completed successfully."); @@ -460,7 +491,7 @@ pub fn cmd_flash_test( "ec7xx" | "air8000" | "air780epm" | "air780ehm" | "air780ehv" | "air780ehg" => { let on_progress2 = make_progress_callback(format, "flash.test", step); let boot_port = luatos_flash::ec718::auto_enter_boot_mode(Some(port), &on_progress2)?; - luatos_flash::ec718::flash_ec718(soc, &boot_port, &on_progress2, cancel.clone())?; + luatos_flash::ec718::flash_ec718(soc, &boot_port, None, None, None, &on_progress2, cancel.clone())?; Vec::new() } _ => { diff --git a/crates/luatos-cli/src/main.rs b/crates/luatos-cli/src/main.rs index e30d0e3..2513036 100644 --- a/crates/luatos-cli/src/main.rs +++ b/crates/luatos-cli/src/main.rs @@ -41,6 +41,13 @@ enum OutputFormat { Jsonl, } +#[derive(Clone, Copy, PartialEq, clap::ValueEnum)] +pub(crate) enum Ec718PortMode { + Auto, + Usb, + Uart, +} + #[derive(Subcommand)] enum Commands { /// Serial port tools @@ -189,9 +196,15 @@ enum FlashCommands { /// Override baud rate #[arg(long)] baud: Option, - /// EC718 agentboot baud override for --binpkg (0 keeps current default) - #[arg(long)] - force_br: Option, + /// EC718 agentboot/download baud override (default: UART 921600, USB unchanged) + #[arg(long = "agent-baud", visible_alias = "agbaud", alias = "force-br")] + agent_baud: Option, + /// EC718 UART1 AT reset baud override (default: 115200) + #[arg(long = "at-baud")] + at_baud: Option, + /// EC718 download port mode for --binpkg/--soc + #[arg(long, value_enum, default_value = "auto")] + port_mode: Ec718PortMode, /// Script folder (optional, can specify multiple) #[arg(long)] script: Vec, @@ -653,7 +666,9 @@ fn main() { binpkg, port, baud, - force_br, + agent_baud, + at_baud, + port_mode, script, auto_reset, dtr_boot, @@ -678,7 +693,9 @@ fn main() { binpkg.as_deref(), &port, baud, - force_br, + agent_baud, + at_baud, + port_mode, script_opt, progress_step, &cli.format, diff --git a/crates/luatos-flash/src/ec718.rs b/crates/luatos-flash/src/ec718.rs index 7bd9a09..2e7325d 100644 --- a/crates/luatos-flash/src/ec718.rs +++ b/crates/luatos-flash/src/ec718.rs @@ -20,7 +20,7 @@ use std::sync::Arc; use std::time::Duration; use anyhow::{bail, Context, Result}; -use serialport::SerialPort; +use serialport::{ClearBuffer, SerialPort}; use sha2::{Digest, Sha256}; use crate::{FlashProgress, ProgressCallback}; @@ -74,6 +74,12 @@ const LPC_SYS_RESET_ACK: &[u8] = b"ZzZzZzZz"; const FIXED_PROTOCOL_RSP_LEN: usize = 6; const MAX_DATA_BLOCK_SIZE: usize = 0x10000; // 64KB +// FlashTools UART detect-mode defaults for EC618/EC718. +const UART_BOOT_BAUD: u32 = 115200; +const UART_AGENT_BAUD: u32 = 921600; +const UART_AT_RESET_SEQUENCE: &[u8] = b"AT+ECRST=delay,650\r\n"; +const UART_DOWNLOAD_DIAG_FRAME: &[u8] = b"\x7e\x00\x02\x7e"; + // Storage types const STYPE_AP_FLASH: u8 = 0x0; const STYPE_CP_FLASH: u8 = 0x1; @@ -138,7 +144,20 @@ impl Ec718PortType { fn baudrate(&self) -> u32 { match self { Ec718PortType::Usb => 921600, - Ec718PortType::Uart => 115200, + Ec718PortType::Uart => UART_BOOT_BAUD, + } + } + + fn agent_baudrate(&self, soc_force_br: u32, override_baud: Option) -> u32 { + if let Some(baud) = override_baud { + baud + } else if soc_force_br != 0 { + soc_force_br + } else { + match self { + Ec718PortType::Usb => 0, + Ec718PortType::Uart => UART_AGENT_BAUD, + } } } } @@ -148,8 +167,13 @@ pub fn detect_port_type(port_name: &str) -> Ec718PortType { if let Ok(ports) = serialport::available_ports() { for p in &ports { if p.port_name == port_name { - if let serialport::SerialPortType::UsbPort(_) = &p.port_type { - return Ec718PortType::Usb; + if let serialport::SerialPortType::UsbPort(usb) = &p.port_type { + if (usb.vid == BOOT_VID && usb.pid == BOOT_PID) || (usb.vid == LOG_VID && usb.pid == LOG_PID) { + return Ec718PortType::Usb; + } + return Ec718PortType::Uart; + } else { + return Ec718PortType::Uart; } } } @@ -157,6 +181,79 @@ pub fn detect_port_type(port_name: &str) -> Ec718PortType { Ec718PortType::Uart } +fn resolve_port_type(port_name: &str, forced_port_type: Option) -> Ec718PortType { + forced_port_type.unwrap_or_else(|| detect_port_type(port_name)) +} + +/// Trigger EC618/EC718 UART detect download mode. +/// +/// FlashTools' `config_pkg_product_uart.ini` uses detect=1/reset=2: open the +/// UART1 at 115200, send `AT+ECRST=delay,650`, send the download DIAG frame, +/// then listen for BootROM. +/// If the firmware is hung and cannot receive AT, callers should retry while +/// the user manually presses RESET. +fn trigger_uart_detect_reset(port_name: &str, at_baudrate: u32, on_progress: &ProgressCallback) -> Result<()> { + on_progress(&FlashProgress::info( + "Rebooting", + 1.5, + &format!("Sending UART1 AT reset on {} at {} baud...", port_name, at_baudrate), + )); + + let mut port = serialport::new(port_name, at_baudrate) + .timeout(Duration::from_millis(800)) + .preserve_dtr_on_open() + .open() + .with_context(|| format!("Failed to open UART reset port {}", port_name))?; + + port.write_data_terminal_ready(false).context("Failed to clear UART reset DTR")?; + port.write_request_to_send(false).context("Failed to clear UART reset RTS")?; + let _ = port.clear(ClearBuffer::All); + port.write_all(UART_AT_RESET_SEQUENCE).context("Failed to send UART AT reset")?; + port.flush().context("Failed to flush UART AT reset")?; + std::thread::sleep(Duration::from_millis(200)); + port.write_all(UART_DOWNLOAD_DIAG_FRAME).context("Failed to send UART download DIAG frame")?; + port.flush().context("Failed to flush UART download DIAG frame")?; + std::thread::sleep(Duration::from_millis(50)); + drop(port); + + Ok(()) +} + +fn open_ec718_flash_port(port_name: &str, port_type: Ec718PortType, baudrate: u32) -> Result> { + let mut port = serialport::new(port_name, baudrate) + .timeout(Duration::from_millis(800)) + .preserve_dtr_on_open() + .open() + .with_context(|| format!("Failed to open serial port {}", port_name))?; + + match port_type { + Ec718PortType::Usb => { + port.write_data_terminal_ready(true).context("Failed to set DTR")?; + } + Ec718PortType::Uart => { + port.write_data_terminal_ready(false).context("Failed to clear DTR")?; + port.write_request_to_send(false).context("Failed to clear RTS")?; + } + } + + let _ = port.clear(ClearBuffer::All); + Ok(port) +} + +fn configure_agent_baud(port: &mut dyn SerialPort, agent_baudrate: u32) -> Result<()> { + if agent_baudrate == 0 { + return Ok(()); + } + + port.set_baud_rate(agent_baudrate) + .with_context(|| format!("Failed to switch serial port to agent baud {}", agent_baudrate))?; + port.set_timeout(Duration::from_millis(800)) + .with_context(|| format!("Failed to set serial timeout after switching to {}", agent_baudrate))?; + let _ = port.clear(ClearBuffer::All); + std::thread::sleep(Duration::from_millis(100)); + Ok(()) +} + // ─── Checksum Functions ───────────────────────────────────────────────────── /// CRC8-Maxim (Dallas/Maxim 1-Wire CRC) for length field encoding. @@ -490,6 +587,14 @@ pub fn parse_binpkg(fdata: &[u8]) -> Result { // ─── Sync Protocol ────────────────────────────────────────────────────────── fn burn_sync(port: &mut dyn SerialPort, sync_type: SyncType, counter: u32) -> Result<()> { + burn_sync_inner(port, sync_type, counter, false) +} + +fn burn_sync_and_drain_uart_tail(port: &mut dyn SerialPort, sync_type: SyncType, counter: u32) -> Result<()> { + burn_sync_inner(port, sync_type, counter, true) +} + +fn burn_sync_inner(port: &mut dyn SerialPort, sync_type: SyncType, counter: u32, drain_uart_tail: bool) -> Result<()> { log::debug!("burn_sync {:?} counter={}", sync_type, counter); let handshake = sync_type.handshake_value(); let send_buf = handshake.to_le_bytes(); @@ -505,6 +610,14 @@ fn burn_sync(port: &mut dyn SerialPort, sync_type: SyncType, counter: u32) -> Re if recv_buf.len() < 4 { continue; } + if drain_uart_tail && matches!(sync_type, SyncType::DlBoot) { + if recv_buf == send_buf { + log::debug!("sync done"); + drain_uart_detect_tail(port)?; + return Ok(()); + } + continue; + } if matches!(sync_type, SyncType::DlBoot) { if let Some(extra) = com_read(port, 1)? { log::debug!("sync extra byte: {:02x}", extra[0]); @@ -522,6 +635,34 @@ fn burn_sync(port: &mut dyn SerialPort, sync_type: SyncType, counter: u32) -> Re bail!("Sync failed for {:?}", sync_type); } +fn drain_uart_detect_tail(port: &mut dyn SerialPort) -> Result<()> { + let mut drained = Vec::new(); + let old_timeout = port.timeout(); + port.set_timeout(Duration::from_millis(20)) + .context("Failed to set short timeout while draining UART detect tail")?; + + for _ in 0..16 { + match com_read(port, 64) { + Ok(Some(mut data)) => { + drained.append(&mut data); + std::thread::sleep(Duration::from_millis(5)); + } + Ok(None) => break, + Err(e) => { + let _ = port.set_timeout(old_timeout); + return Err(e).context("Failed to drain UART detect tail"); + } + } + } + + port.set_timeout(old_timeout).context("Failed to restore serial timeout after draining UART detect tail")?; + if !drained.is_empty() { + let drained_hex = drained.iter().map(|b| format!("{:02x}", b)).collect::>().join(""); + log::debug!("drained UART detect tail: {}", drained_hex); + } + Ok(()) +} + // ─── Command Send/Receive ─────────────────────────────────────────────────── /// Send a DL command and receive response. @@ -1290,7 +1431,15 @@ pub fn build_log_probe() -> Vec { /// Flash EC718 firmware via native Rust protocol. /// /// Handles full flash: agentboot + all partitions from .soc file. -pub fn flash_ec718(soc_path: &str, port: &str, on_progress: &ProgressCallback, cancel: Arc) -> Result<()> { +pub fn flash_ec718( + soc_path: &str, + port: &str, + forced_port_type: Option, + agent_baud_override: Option, + at_baud_override: Option, + on_progress: &ProgressCallback, + cancel: Arc, +) -> Result<()> { on_progress(&FlashProgress::info("Preparing", 0.0, "Parsing SOC file...")); // Parse SOC file @@ -1298,39 +1447,65 @@ pub fn flash_ec718(soc_path: &str, port: &str, on_progress: &ProgressCallback, c log::info!("EC718 chip: {}, {} entries, force_br={}", binpkg.chip, binpkg.entries.len(), force_br,); - flash_ec718_parsed(binpkg, force_br, port, on_progress, cancel) + flash_ec718_parsed(binpkg, force_br, port, forced_port_type, agent_baud_override, at_baud_override, on_progress, cancel) } /// Flash EC718 firmware from a raw .binpkg file via native Rust protocol. -pub fn flash_ec718_binpkg(binpkg_path: &str, port: &str, force_br: u32, on_progress: &ProgressCallback, cancel: Arc) -> Result<()> { +pub fn flash_ec718_binpkg( + binpkg_path: &str, + port: &str, + forced_port_type: Option, + agent_baud_override: Option, + at_baud_override: Option, + on_progress: &ProgressCallback, + cancel: Arc, +) -> Result<()> { on_progress(&FlashProgress::info("Preparing", 0.0, "Parsing BINPKG file...")); let fdata = std::fs::read(binpkg_path).with_context(|| format!("Failed to read binpkg file: {binpkg_path}"))?; let binpkg = parse_binpkg(&fdata)?; - log::info!("EC718 binpkg chip: {}, {} entries, force_br={}", binpkg.chip, binpkg.entries.len(), force_br,); + log::info!( + "EC718 binpkg chip: {}, {} entries, agent_baud_override={:?}", + binpkg.chip, + binpkg.entries.len(), + agent_baud_override, + ); - flash_ec718_parsed(binpkg, force_br, port, on_progress, cancel) + flash_ec718_parsed(binpkg, 0, port, forced_port_type, agent_baud_override, at_baud_override, on_progress, cancel) } -fn flash_ec718_parsed(binpkg: BinpkgResult, force_br: u32, port: &str, on_progress: &ProgressCallback, cancel: Arc) -> Result<()> { +fn flash_ec718_parsed( + binpkg: BinpkgResult, + force_br: u32, + port: &str, + forced_port_type: Option, + agent_baud_override: Option, + at_baud_override: Option, + on_progress: &ProgressCallback, + cancel: Arc, +) -> Result<()> { // Determine port type and agentboot binary - let port_type = detect_port_type(port); + let port_type = resolve_port_type(port, forced_port_type); let agentboot = match port_type { Ec718PortType::Usb => AGENTBOOT_EC718M_USB, Ec718PortType::Uart => AGENTBOOT_EC718M_UART, }; - on_progress(&FlashProgress::info("Connecting", 1.0, &format!("Opening {} ({:?} mode)...", port, port_type))); - - // Open serial port let baudrate = port_type.baudrate(); - let mut serial = serialport::new(port, baudrate) - .timeout(Duration::from_millis(800)) - .open() - .with_context(|| format!("Failed to open serial port {}", port))?; + let agent_baudrate = port_type.agent_baudrate(force_br, agent_baud_override); + + if port_type == Ec718PortType::Uart { + trigger_uart_detect_reset(port, at_baud_override.unwrap_or(UART_BOOT_BAUD), on_progress)?; + } + + on_progress(&FlashProgress::info( + "Connecting", + 1.0, + &format!("Opening {} ({:?} mode, {} baud)...", port, port_type, baudrate), + )); - serial.write_data_terminal_ready(true).context("Failed to set DTR")?; + let mut serial = open_ec718_flash_port(port, port_type, baudrate)?; if cancel.load(Ordering::Relaxed) { bail!("Cancelled"); @@ -1340,20 +1515,42 @@ fn flash_ec718_parsed(binpkg: BinpkgResult, force_br: u32, port: &str, on_progre on_progress(&FlashProgress::info( "Connecting", 2.0, - "Waiting for bootloader (DLBOOT sync)...\n\ - 提示: 如果等待超时, 请手动按住BOOT按钮并复位模组进入下载模式", + if port_type == Ec718PortType::Uart { + "Waiting for UART detect bootloader (DLBOOT sync)...\n\ + 提示: 如果模组已死机或无法响应AT, 请在此阶段按一下RESET后重试" + } else { + "Waiting for bootloader (DLBOOT sync)...\n\ + 提示: 如果等待超时, 请手动按住BOOT按钮并复位模组进入下载模式" + }, )); - if let Err(e) = burn_sync(serial.as_mut(), SyncType::DlBoot, 2) { + let dlboot_sync_result = if port_type == Ec718PortType::Uart { + burn_sync_and_drain_uart_tail(serial.as_mut(), SyncType::DlBoot, 2) + } else { + burn_sync(serial.as_mut(), SyncType::DlBoot, 2) + }; + + if let Err(e) = dlboot_sync_result { on_progress(&FlashProgress::info( "Connecting", -1.0, - "⚠ 无法连接到bootloader. 请按住BOOT按钮, 然后复位或重新上电模组, 再重试刷机.", + if port_type == Ec718PortType::Uart { + "⚠ 无法通过UART detect连接bootloader. 如果模组已死机, 请先启动刷机命令, 然后按一下RESET再重试." + } else { + "⚠ 无法连接到bootloader. 请按住BOOT按钮, 然后复位或重新上电模组, 再重试刷机." + }, )); - return Err(e).context( - "DLBOOT sync failed. The module may not be in boot mode.\n\ - Please hold the BOOT button, reset/power-cycle the module, then retry.", - ); + return if port_type == Ec718PortType::Uart { + Err(e).context( + "DLBOOT sync failed in UART detect mode.\n\ + If the module firmware is hung and cannot receive AT, start flashing first, press RESET, then retry.", + ) + } else { + Err(e).context( + "DLBOOT sync failed. The module may not be in boot mode.\n\ + Please hold the BOOT button, reset/power-cycle the module, then retry.", + ) + }; } on_progress(&FlashProgress::info("Connecting", 5.0, "Bootloader connected")); @@ -1365,7 +1562,8 @@ fn flash_ec718_parsed(binpkg: BinpkgResult, force_br: u32, port: &str, on_progre // Stage 2: Download agentboot on_progress(&FlashProgress::info("AgentBoot", 6.0, &format!("Downloading agentboot ({} bytes)...", agentboot.len()))); - burn_agboot(serial.as_mut(), agentboot, force_br)?; + burn_agboot(serial.as_mut(), agentboot, agent_baudrate)?; + configure_agent_baud(serial.as_mut(), agent_baudrate)?; on_progress(&FlashProgress::info("AgentBoot", 15.0, "AgentBoot loaded")); @@ -1635,6 +1833,6 @@ mod tests { let on_progress: ProgressCallback = Box::new(|p| { eprintln!("[{:>6.1}%] {} — {}", p.percent, p.stage, p.message); }); - flash_ec718(soc, port, &on_progress, cancel).expect("flash_ec718"); + flash_ec718(soc, port, None, None, &on_progress, cancel).expect("flash_ec718"); } } diff --git a/crates/luatos-gui/src-tauri/src/commands/flash.rs b/crates/luatos-gui/src-tauri/src/commands/flash.rs index ee0799e..ea34a5d 100644 --- a/crates/luatos-gui/src-tauri/src/commands/flash.rs +++ b/crates/luatos-gui/src-tauri/src/commands/flash.rs @@ -48,7 +48,7 @@ pub async fn flash_run( } "ec7xx" | "air8000" | "air780epm" | "air780ehm" | "air780ehv" | "air780ehg" => { let boot_port = luatos_flash::ec718::auto_enter_boot_mode(Some(&port), &on_progress).map_err(|e| format!("EC718 进入下载模式失败: {e}"))?; - luatos_flash::ec718::flash_ec718(&soc_path, &boot_port, &on_progress, cancel).map_err(|e| format!("EC718 刷机失败: {e}"))?; + luatos_flash::ec718::flash_ec718(&soc_path, &boot_port, None, None, None, &on_progress, cancel).map_err(|e| format!("EC718 刷机失败: {e}"))?; } _ => { return Err(format!("不支持的芯片类型: {chip}")); diff --git a/crates/luatos-mfgui/src/worker.rs b/crates/luatos-mfgui/src/worker.rs index 1a8a8e0..1c6026f 100644 --- a/crates/luatos-mfgui/src/worker.rs +++ b/crates/luatos-mfgui/src/worker.rs @@ -89,7 +89,7 @@ fn do_flash(soc_path: &str, port: Option<&str>, tx: &Sender, cancel: let cb = make_cb(tx); let boot_port = luatos_flash::ec718::auto_enter_boot_mode(port, &cb)?; let cb2 = make_cb(tx); - luatos_flash::ec718::flash_ec718(soc_path, &boot_port, &cb2, Arc::clone(&cancel))?; + luatos_flash::ec718::flash_ec718(soc_path, &boot_port, None, None, None, &cb2, Arc::clone(&cancel))?; } "sf32lb58" => { let port_str = port.ok_or_else(|| anyhow::anyhow!("SF32LB58 需要指定串口"))?; diff --git a/docs/ec718-flash-protocol.md b/docs/ec718-flash-protocol.md index 7550e8b..68597c6 100644 --- a/docs/ec718-flash-protocol.md +++ b/docs/ec718-flash-protocol.md @@ -105,10 +105,51 @@ DIAG_REBOOT_DOWNLOAD_MS = 0x42 // 重启到下载模式 4. 等待模组以 VID=0x17D1 重新枚举 ### UART模式 -- 使用物理UART引脚 (TX/RX), 非USB CDC +- 使用物理 UART1/串口1 引脚 (TX/RX), 非USB CDC - 需要外部USB转UART适配器 -- 可能需要手动控制BOOT引脚 -- 波特率: 115200 (初始同步) +- 当前仅支持 UART1/串口1 下载; UART2等其他物理串口不支持串口下载 +- FlashTools detect模式不需要手动控制BOOT引脚, 但要求固件能响应AT复位命令 +- AT复位波特率: 默认 115200, 可通过 CLI `--at-baud` 覆盖 +- BootROM DLBOOT初始同步波特率: 115200 +- AgentBoot/下载波特率: 默认 921600, 可通过 CLI `--agent-baud` / `--agbaud` 覆盖 + +#### UART detect进入下载模式流程 + +该流程对应 FlashTools `config_pkg_product_uart.ini` 中的 `detect=1`, `reset=2`, `atreset=at+ecrst=delay,650`, `atbaud=115200`, `agbaud=921600`。 + +``` +1. 打开物理 UART1/串口1 端口, at_baud baud (默认 115200) +2. 清 DTR/RTS, 清空串口缓冲 +3. 发送: AT+ECRST=delay,650\r\n +4. 等待约 200ms +5. 发送: 0x7E 0x00 0x02 0x7E +6. 关闭并重新打开同一个 UART1/串口1 端口, 115200 baud +7. 立即执行 DLBOOT sync +8. 下载 ec718m_uart.bin agentboot +9. image_head(AGBT, baud=921600 或用户指定值) +10. 串口切换到 agent baud 后继续 AGBOOT/LPC 烧录 +``` + +注意: +- 如果固件已经死机, 不能响应 `AT+ECRST`, UART detect会失败。此时可以先启动刷机命令, 在等待 DLBOOT sync 阶段按一下 RESET 后重试。 +- 如果固件把 UART1 AT 波特率改成非 115200, 需要用 `--at-baud` 指定运行态AT波特率。 +- USB模式仍使用USB下载口和 `ec718m_usb.bin`; UART模式使用物理 UART1/串口1 和 `ec718m_uart.bin`。 +- CLI 默认 `--port-mode auto`, 会保持原来的USB自动进入下载逻辑。需要强制走UART时使用 `--port-mode uart`。 + +示例: +```bash +# USB/自动模式: 保持原有行为 +luatos-cli flash run --binpkg firmware.binpkg --port COM3 + +# 强制物理UART模式 +luatos-cli flash run --binpkg firmware.binpkg --port COM6 --port-mode uart + +# 固件运行态UART1 AT波特率不是115200时 +luatos-cli flash run --binpkg firmware.binpkg --port COM6 --port-mode uart --at-baud 9600 + +# 某些USB-UART不稳定时降低下载波特率 +luatos-cli flash run --binpkg firmware.binpkg --port COM6 --port-mode uart --agent-baud 460800 +``` ## 刷机协议详解 @@ -205,7 +246,7 @@ DIAG_REBOOT_DOWNLOAD_MS = 0x42 // 重启到下载模式 1. 打开端口 → DLBOOT sync 2. AgentBoot下载: a. base_info(HEAD) → 获取设备信息 - b. image_head(AGBT, baud=921600) → 发送agentboot镜像头 + b. image_head(AGBT, baud=agent_baud) → 发送agentboot镜像头 c. DLBOOT sync (确认) d. base_info(BL) → bootloader信息 e. package_data(agentboot_bin) → 发送agentboot二进制 @@ -239,6 +280,24 @@ DIAG_REBOOT_DOWNLOAD_MS = 0x42 // 重启到下载模式 - ec718m_uart.bin: 47890字节 (UART模式) - 来源: https://github.com/yuzhan-tech/luatos-tools +## CLI EC718刷机参数 + +`flash run` 支持两种EC718固件输入: +- `--soc firmware.soc`: 从SOC包中提取 `.binpkg` 后烧录 +- `--binpkg firmware.binpkg`: 直接烧录原始 `.binpkg` + +EC718专用参数: +- `--port-mode auto|usb|uart`: 端口模式, 默认 `auto` +- `--at-baud `: 覆盖 UART1 detect 阶段的 AT 复位波特率, 默认 115200 +- `--agent-baud `: 覆盖 AgentBoot/下载波特率 +- `--agbaud `: `--agent-baud` 的兼容短别名 +- `--force-br `: 旧参数兼容别名, 不在帮助中主动展示 + +模式说明: +- `auto`: 保持旧行为。优先识别USB下载口, 否则通过USB命令口发送AT+DIAG进入USB下载模式, 最后才回退到用户指定端口。 +- `usb`: 强制按USB下载口处理, 使用 `ec718m_usb.bin`。 +- `uart`: 直接使用 `--port` 指定的物理 UART1/串口1 端口, 使用 UART detect 流程和 `ec718m_uart.bin`。 + ## 日志输出 - 协议: 0x7E HDLC帧 (与Air1601/CCM4211的0xA5帧不同!)