esp32-rust-embedded

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

ESP32 Embedded Rust Specialist

ESP32 嵌入式Rust开发专家指南

Expert guidance for no-std Rust development on ESP32 microcontrollers using the ESP-RS ecosystem and Embassy async framework.
针对ESP32微控制器,基于ESP-RS生态系统和Embassy异步框架的no-std Rust开发专业指导。

ESP-RS Ecosystem Stack

ESP-RS生态系统栈

Core Dependencies

核心依赖

toml
esp-hal = { version = "1.0.0", features = ["esp32s3", "log-04", "unstable"] }
esp-rtos = { version = "0.2.0", features = ["embassy", "esp-alloc", "esp-radio", "esp32s3", "log-04"] }
esp-radio = { version = "0.17.0", features = ["esp-alloc", "esp32s3", "wifi", "smoltcp"] }
esp-bootloader-esp-idf = { version = "0.4.0", features = ["esp32s3", "log-04"] }
toml
esp-hal = { version = "1.0.0", features = ["esp32s3", "log-04", "unstable"] }
esp-rtos = { version = "0.2.0", features = ["embassy", "esp-alloc", "esp-radio", "esp32s3", "log-04"] }
esp-radio = { version = "0.17.0", features = ["esp-alloc", "esp32s3", "wifi", "smoltcp"] }
esp-bootloader-esp-idf = { version = "0.4.0", features = ["esp32s3", "log-04"] }

Embassy Framework

Embassy框架

toml
embassy-executor = { version = "0.9.1", features = ["log"] }
embassy-time = { version = "0.5.0", features = ["log"] }
embassy-net = { version = "0.7.1", features = ["dhcpv4", "tcp", "udp", "dns"] }
embassy-sync = { version = "0.7.2" }
toml
embassy-executor = { version = "0.9.1", features = ["log"] }
embassy-time = { version = "0.5.0", features = ["log"] }
embassy-net = { version = "0.7.1", features = ["dhcpv4", "tcp", "udp", "dns"] }
embassy-sync = { version = "0.7.2" }

Dependency Hierarchy

依赖层级

esp-radio (WiFi) -> esp-rtos (scheduler) -> esp-hal (HAL) -> esp-phy (PHY)
embassy-executor -> embassy-time -> embassy-sync -> embassy-net
esp-radio (WiFi) -> esp-rtos (调度器) -> esp-hal (硬件抽象层) -> esp-phy (物理层)
embassy-executor -> embassy-time -> embassy-sync -> embassy-net

Build & Flash

构建与烧录

Environment Setup

环境配置

bash
undefined
bash
undefined

Install ESP toolchain (one-time)

安装ESP工具链(仅需执行一次)

espup install source $HOME/export-esp.sh
espup install source $HOME/export-esp.sh

Configure credentials (.env file)

配置凭证(.env文件)

cp .env.dist .env
cp .env.dist .env

Edit: WIFI_SSID, WIFI_PSK, MQTT_HOSTNAME, MQTT_USERNAME, MQTT_PASSWORD

编辑以下内容:WIFI_SSID、WIFI_PSK、MQTT_HOSTNAME、MQTT_USERNAME、MQTT_PASSWORD

undefined
undefined

Build Commands

构建命令

bash
undefined
bash
undefined

Quick build and flash

快速构建并烧录

./run.sh
./run.sh

Manual release build (recommended)

手动构建发布版本(推荐)

cargo run --release
cargo run --release

Debug build (slower on device)

调试构建(设备上运行速度较慢)

cargo run
undefined
cargo run
undefined

Cargo Profile Optimization

Cargo配置文件优化

toml
[profile.dev]
opt-level = "s"  # Rust debug too slow for ESP32

[profile.release]
lto = 'fat'
opt-level = 's'
codegen-units = 1
toml
[profile.dev]
opt-level = "s"  # Rust默认调试模式在ESP32上运行过慢

[profile.release]
lto = 'fat'
opt-level = 's'
codegen-units = 1

Common Build Errors

常见构建错误

Linker error: undefined symbol
_stack_start
  • Check
    build.rs
    has linkall.x configuration
  • Verify esp-hal version compatibility
undefined symbol:
esp_rtos_initialized
  • Ensure esp-rtos is started with timer:
rust
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_rtos::start(timg0.timer0);
Environment variable errors
  • Variables are compile-time via
    env!()
    macro
  • Changes require full rebuild
链接器错误:未定义符号
_stack_start
  • 检查
    build.rs
    是否配置了linkall.x
  • 验证esp-hal版本兼容性
未定义符号:
esp_rtos_initialized
  • 确保esp-rtos已通过计时器启动:
rust
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_rtos::start(timg0.timer0);
环境变量错误
  • 变量通过
    env!()
    宏在编译时注入
  • 修改变量后需要完全重新构建

No-Std Patterns

No-Std开发模式

Application Entry

应用入口

rust
#![no_std]
#![no_main]

use esp_rtos::main;

#[main]
async fn main(spawner: Spawner) {
    // Initialize logger
    init_logger(log::LevelFilter::Info);

    // Initialize HAL
    let peripherals = esp_hal::init(Config::default());

    // Setup heap allocator
    heap_allocator!(#[unsafe(link_section = ".dram2_uninit")] size: 73744);

    // Start RTOS scheduler
    let timg0 = TimerGroup::new(peripherals.TIMG0);
    esp_rtos::start(timg0.timer0);
}
rust
#![no_std]
#![no_main]

use esp_rtos::main;

#[main]
async fn main(spawner: Spawner) {
    // 初始化日志
    init_logger(log::LevelFilter::Info);

    // 初始化硬件抽象层
    let peripherals = esp_hal::init(Config::default());

    // 设置堆分配器
    heap_allocator!(#[unsafe(link_section = ".dram2_uninit")] size: 73744);

    // 启动RTOS调度器
    let timg0 = TimerGroup::new(peripherals.TIMG0);
    esp_rtos::start(timg0.timer0);
}

Memory Management

内存管理

  • Use
    esp-alloc
    for dynamic allocation
  • Prefer
    heapless
    collections with compile-time capacity
  • Use
    static_cell::StaticCell
    for 'static lifetime requirements
  • 使用
    esp-alloc
    进行动态内存分配
  • 优先使用具有编译时容量限制的
    heapless
    集合
  • 对于'static生命周期需求,使用
    static_cell::StaticCell

String Handling

字符串处理

rust
use alloc::string::String;      // Dynamic strings (heap)
use heapless::String;           // Bounded strings (stack)

let s: heapless::String<64> = heapless::String::new();
Avoid cloning when possible.
rust
use alloc::string::String;      // 动态字符串(堆内存)
use heapless::String;           // 有界字符串(栈内存)

let s: heapless::String<64> = heapless::String::new();
尽可能避免克隆操作。

StaticCell Pattern

StaticCell模式

rust
static CHANNEL: StaticCell<Channel<NoopRawMutex, Data, 3>> = StaticCell::new();

// In async function
let channel: &'static mut _ = CHANNEL.init(Channel::new());
let (sender, receiver) = (channel.sender(), channel.receiver());
rust
static CHANNEL: StaticCell<Channel<NoopRawMutex, Data, 3>> = StaticCell::new();

// 在异步函数中
let channel: &'static mut _ = CHANNEL.init(Channel::new());
let (sender, receiver) = (channel.sender(), channel.receiver());

Hardware Patterns

硬件操作模式

GPIO Configuration

GPIO配置

rust
use esp_hal::gpio::{Level, Output, OutputConfig, Pull, DriveMode};

// Standard output
let pin = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default());

// Open-drain for sensors like DHT11
let pin = Output::new(
    peripherals.GPIO1,
    Level::High,
    OutputConfig::default()
        .with_drive_mode(DriveMode::OpenDrain)
        .with_pull(Pull::None),
).into_flex();
rust
use esp_hal::gpio::{Level, Output, OutputConfig, Pull, DriveMode};

// 标准输出
let pin = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default());

// 用于DHT11等传感器的开漏模式
let pin = Output::new(
    peripherals.GPIO1,
    Level::High,
    OutputConfig::default()
        .with_drive_mode(DriveMode::OpenDrain)
        .with_pull(Pull::None),
).into_flex();

ADC Reading with Calibration

带校准的ADC读取

rust
use esp_hal::analog::adc::{Adc, AdcConfig, AdcCalCurve, Attenuation};

let mut adc_config = AdcConfig::new();
let pin = adc_config.enable_pin_with_cal::<_, AdcCalCurve<ADC2>>(
    peripherals.GPIO11,
    Attenuation::_11dB  // 0-3.3V range
);
let adc = Adc::new(peripherals.ADC2, adc_config);

// Read with nb::block!
let value = nb::block!(adc.read_oneshot(&mut pin))?;
rust
use esp_hal::analog::adc::{Adc, AdcConfig, AdcCalCurve, Attenuation};

let mut adc_config = AdcConfig::new();
let pin = adc_config.enable_pin_with_cal::<_, AdcCalCurve<ADC2>>(
    peripherals.GPIO11,
    Attenuation::_11dB  // 0-3.3V量程
);
let adc = Adc::new(peripherals.ADC2, adc_config);

// 使用nb::block!读取
let value = nb::block!(adc.read_oneshot(&mut pin))?;

Peripheral Bundles Pattern

外设捆绑模式

rust
pub struct SensorPeripherals {
    pub dht11_pin: GPIO1<'static>,
    pub moisture_pin: GPIO11<'static>,
    pub power_pin: GPIO16<'static>,
    pub adc2: ADC2<'static>,
}
rust
pub struct SensorPeripherals {
    pub dht11_pin: GPIO1<'static>,
    pub moisture_pin: GPIO11<'static>,
    pub power_pin: GPIO16<'static>,
    pub adc2: ADC2<'static>,
}

Async Task Architecture

异步任务架构

Task Definition

任务定义

rust
#[embassy_executor::task]
pub async fn my_task(sender: Sender<'static, NoopRawMutex, Data, 3>) {
    loop {
        // Do work
        sender.send(data).await;
        Timer::after(Duration::from_secs(5)).await;
    }
}
rust
#[embassy_executor::task]
pub async fn my_task(sender: Sender<'static, NoopRawMutex, Data, 3>) {
    loop {
        // 执行任务
        sender.send(data).await;
        Timer::after(Duration::from_secs(5)).await;
    }
}

Task Spawning

任务启动

rust
spawner.spawn(sensor_task(sender, peripherals)).ok();
spawner.spawn(update_task(stack, display, receiver)).ok();
rust
spawner.spawn(sensor_task(sender, peripherals)).ok();
spawner.spawn(update_task(stack, display, receiver)).ok();

Inter-Task Communication

任务间通信

Channel (multiple values)
rust
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Channel};

static CHANNEL: StaticCell<Channel<NoopRawMutex, Data, 3>> = StaticCell::new();
// sender.send(data).await / receiver.receive().await
Signal (single notification)
rust
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};

static SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
// SIGNAL.signal(()) / SIGNAL.wait().await
通道(多值传递)
rust
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::Channel};

static CHANNEL: StaticCell<Channel<NoopRawMutex, Data, 3>> = StaticCell::new();
// sender.send(data).await / receiver.receive().await
信号(单次通知)
rust
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};

static SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();
// SIGNAL.signal(()) / SIGNAL.wait().await

Reconnection Loop Pattern

重连循环模式

rust
'reconnect: loop {
    let mut client = initialize_client().await?;
    loop {
        match client.process().await {
            Ok(_) => { /* handle messages */ }
            Err(e) => {
                println!("Error: {:?}", e);
                continue 'reconnect;  // Reconnect on error
            }
        }
    }
}
rust
'reconnect: loop {
    let mut client = initialize_client().await?;
    loop {
        match client.process().await {
            Ok(_) => { /* 处理消息 */ }
            Err(e) => {
                println!("错误: {:?}", e);
                continue 'reconnect;  // 出错时重连
            }
        }
    }
}

Power Management

电源管理

Deep Sleep Configuration

深度睡眠配置

rust
use esp_hal::rtc_cntl::{Rtc, sleep::{RtcSleepConfig, TimerWakeupSource, RtcioWakeupSource, WakeupLevel}};

pub fn enter_deep(wakeup_pin: &mut dyn RtcPin, rtc_cntl: LPWR, duration: Duration) -> ! {
    // GPIO wake source
    let wakeup_pins: &mut [(&mut dyn RtcPin, WakeupLevel)] = &mut [(wakeup_pin, WakeupLevel::Low)];
    let ext0 = RtcioWakeupSource::new(wakeup_pins);

    // Timer wake source
    let timer = TimerWakeupSource::new(duration.into());

    let mut rtc = Rtc::new(rtc_cntl);
    let mut config = RtcSleepConfig::deep();
    config.set_rtc_fastmem_pd_en(false);  // Keep RTC fast memory powered

    rtc.sleep(&config, &[&ext0, &timer]);
    unreachable!();
}
rust
use esp_hal::rtc_cntl::{Rtc, sleep::{RtcSleepConfig, TimerWakeupSource, RtcioWakeupSource, WakeupLevel}};

pub fn enter_deep(wakeup_pin: &mut dyn RtcPin, rtc_cntl: LPWR, duration: Duration) -> ! {
    // GPIO唤醒源
    let wakeup_pins: &mut [(&mut dyn RtcPin, WakeupLevel)] = &mut [(wakeup_pin, WakeupLevel::Low)];
    let ext0 = RtcioWakeupSource::new(wakeup_pins);

    // 计时器唤醒源
    let timer = TimerWakeupSource::new(duration.into());

    let mut rtc = Rtc::new(rtc_cntl);
    let mut config = RtcSleepConfig::deep();
    config.set_rtc_fastmem_pd_en(false);  // 保持RTC快速内存供电

    rtc.sleep(&config, &[&ext0, &timer]);
    unreachable!();
}

RTC Fast Memory Persistence

RTC快速内存持久化

rust
use esp_hal::ram;

#[ram(unstable(rtc_fast))]
pub static BOOT_COUNT: RtcCell<u32> = RtcCell::new(0);

// Survives deep sleep - read/write with .get()/.set()
let count = BOOT_COUNT.get();
BOOT_COUNT.set(count + 1);
rust
use esp_hal::ram;

#[ram(unstable(rtc_fast))]
pub static BOOT_COUNT: RtcCell<u32> = RtcCell::new(0);

// 数据可在深度睡眠后保留 - 使用.get()/.set()进行读写
let count = BOOT_COUNT.get();
BOOT_COUNT.set(count + 1);

Power Optimization

电源优化建议

  • Toggle sensor power pins only during reads
  • Use power save mode on displays
  • Gracefully disconnect WiFi before sleep
  • Keep awake duration minimal
  • 仅在读取时开启传感器电源引脚
  • 开启显示屏的省电模式
  • 进入睡眠前优雅断开WiFi连接
  • 尽可能缩短唤醒时长

WiFi Networking

WiFi网络

Connection Setup

连接配置

rust
use esp_radio::wifi::{self, ClientConfig, ModeConfig, WifiController};

let init = esp_radio::init().unwrap();
let (controller, interfaces) = wifi::new(&init, wifi_peripheral, Default::default()).unwrap();

let client_config = ModeConfig::Client(
    ClientConfig::default()
        .with_ssid(env!("WIFI_SSID").try_into().unwrap())
        .with_password(env!("WIFI_PSK").try_into().unwrap()),
);

controller.set_config(&client_config)?;
controller.start_async().await?;
controller.connect_async().await?;
rust
use esp_radio::wifi::{self, ClientConfig, ModeConfig, WifiController};

let init = esp_radio::init().unwrap();
let (controller, interfaces) = wifi::new(&init, wifi_peripheral, Default::default()).unwrap();

let client_config = ModeConfig::Client(
    ClientConfig::default()
        .with_ssid(env!("WIFI_SSID").try_into().unwrap())
        .with_password(env!("WIFI_PSK").try_into().unwrap()),
);

controller.set_config(&client_config)?;
controller.start_async().await?;
controller.connect_async().await?;

Embassy-Net Stack

Embassy-Net网络栈

rust
use embassy_net::{Config, Stack, StackResources};

let config = Config::dhcpv4(DhcpConfig::default());
let (stack, runner) = embassy_net::new(wifi_interface, config, stack_resources, seed);

// Wait for link and IP
loop {
    if stack.is_link_up() { break; }
    Timer::after(Duration::from_millis(500)).await;
}

loop {
    if let Some(config) = stack.config_v4() {
        println!("IP: {}", config.address);
        break;
    }
    Timer::after(Duration::from_millis(500)).await;
}
rust
use embassy_net::{Config, Stack, StackResources};

let config = Config::dhcpv4(DhcpConfig::default());
let (stack, runner) = embassy_net::new(wifi_interface, config, stack_resources, seed);

// 等待链路建立并获取IP
loop {
    if stack.is_link_up() { break; }
    Timer::after(Duration::from_millis(500)).await;
}

loop {
    if let Some(config) = stack.config_v4() {
        println!("IP地址: {}", config.address);
        break;
    }
    Timer::after(Duration::from_millis(500)).await;
}

Graceful WiFi Shutdown

WiFi优雅关闭

rust
pub static STOP_WIFI_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();

// In connection task
STOP_WIFI_SIGNAL.wait().await;
controller.stop_async().await?;

// Before deep sleep
STOP_WIFI_SIGNAL.signal(());
rust
pub static STOP_WIFI_SIGNAL: Signal<CriticalSectionRawMutex, ()> = Signal::new();

// 在连接任务中
STOP_WIFI_SIGNAL.wait().await;
controller.stop_async().await?;

// 进入深度睡眠前
STOP_WIFI_SIGNAL.signal(());

Sensor Patterns

传感器操作模式

ADC Sampling with Warmup

带预热的ADC采样

rust
async fn sample_adc_with_warmup<PIN, ADC>(
    adc: &mut Adc<ADC, Blocking>,
    pin: &mut AdcPin<PIN, ADC>,
    warmup_ms: u64,
) -> Option<u16> {
    Timer::after(Duration::from_millis(warmup_ms)).await;
    nb::block!(adc.read_oneshot(pin)).ok()
}
rust
async fn sample_adc_with_warmup<PIN, ADC>(
    adc: &mut Adc<ADC, Blocking>,
    pin: &mut AdcPin<PIN, ADC>,
    warmup_ms: u64,
) -> Option<u16> {
    Timer::after(Duration::from_millis(warmup_ms)).await;
    nb::block!(adc.read_oneshot(pin)).ok()
}

Power-Controlled Sensor Read

电源可控的传感器读取

rust
async fn read_sensor(adc: &mut Adc, pin: &mut AdcPin, power: &mut Output) -> Option<u16> {
    power.set_high();
    let result = sample_adc_with_warmup(adc, pin, 50).await;
    power.set_low();
    result
}
rust
async fn read_sensor(adc: &mut Adc, pin: &mut AdcPin, power: &mut Output) -> Option<u16> {
    power.set_high();
    let result = sample_adc_with_warmup(adc, pin, 50).await;
    power.set_low();
    result
}

Outlier-Resistant Averaging

抗异常值平均算法

rust
fn calculate_average<T: Copy + Ord + Into<u32>>(samples: &mut [T]) -> Option<T> {
    if samples.len() <= 2 { return None; }

    samples.sort_unstable();
    let trimmed = &samples[1..samples.len() - 1];  // Remove min/max

    let sum: u32 = trimmed.iter().map(|&x| x.into()).sum();
    (sum / trimmed.len() as u32).try_into().ok()
}
rust
fn calculate_average<T: Copy + Ord + Into<u32>>(samples: &mut [T]) -> Option<T> {
    if samples.len() <= 2 { return None; }

    samples.sort_unstable();
    let trimmed = &samples[1..samples.len() - 1];  // 移除最小值和最大值

    let sum: u32 = trimmed.iter().map(|&x| x.into()).sum();
    (sum / trimmed.len() as u32).try_into().ok()
}

Display Integration

显示屏集成

ST7789 Parallel Interface

ST7789并行接口

rust
use mipidsi::{Builder, options::ColorInversion};

let di = display_interface_parallel_gpio::Generic8BitBus::new(/*pins*/);
let mut display = Builder::new(ST7789, di)
    .display_size(320, 170)
    .invert_colors(ColorInversion::Inverted)
    .init(&mut delay)?;
rust
use mipidsi::{Builder, options::ColorInversion};

let di = display_interface_parallel_gpio::Generic8BitBus::new(/*引脚配置*/);
let mut display = Builder::new(ST7789, di)
    .display_size(320, 170)
    .invert_colors(ColorInversion::Inverted)
    .init(&mut delay)?;

Power Save Mode

省电模式

rust
display.set_display_on(false)?;  // Enter power save
// Before deep sleep
power_pin.set_low();
rust
display.set_display_on(false)?;  // 进入省电模式
// 进入深度睡眠前
power_pin.set_low();

Error Handling

错误处理

Module Error Pattern

模块错误模式

rust
#[derive(Debug)]
pub enum Error {
    Wifi(WifiError),
    Display(display::Error),
    Mqtt(MqttError),
}

impl From<WifiError> for Error {
    fn from(e: WifiError) -> Self { Self::Wifi(e) }
}
rust
#[derive(Debug)]
pub enum Error {
    Wifi(WifiError),
    Display(display::Error),
    Mqtt(MqttError),
}

impl From<WifiError> for Error {
    fn from(e: WifiError) -> Self { Self::Wifi(e) }
}

Fallible Main Pattern

可失败的主函数模式

rust
#[main]
async fn main(spawner: Spawner) {
    if let Err(error) = main_fallible(spawner).await {
        println!("Error: {:?}", error);
        software_reset();
    }
}

async fn main_fallible(spawner: Spawner) -> Result<(), Error> {
    // Application logic with ? operator
}
rust
#[main]
async fn main(spawner: Spawner) {
    if let Err(error) = main_fallible(spawner).await {
        println!("错误: {:?}", error);
        software_reset();
    }
}

async fn main_fallible(spawner: Spawner) -> Result<(), Error> {
    // 使用?运算符处理应用逻辑
}

Dependency Updates

依赖更新

Safe Update Process

安全更新流程

bash
cargo outdated
cargo update -p esp-hal
cargo build --release
cargo clippy -- -D warnings
bash
cargo outdated
cargo update -p esp-hal
cargo build --release
cargo clippy -- -D warnings

Breaking Change Patterns

常见破坏性变更

  • GPIO API changes frequently (OutputConfig)
  • Timer initialization changes
  • Feature flag renames
  • Always check esp-hal release notes
  • GPIO API频繁变更(OutputConfig)
  • 计时器初始化方式变更
  • 功能标志重命名
  • 务必查看esp-hal发布说明

Version Alignment

版本对齐

Update Embassy crates together:
bash
cargo update -p embassy-executor -p embassy-time -p embassy-sync -p embassy-net
同步更新Embassy相关依赖:
bash
cargo update -p embassy-executor -p embassy-time -p embassy-sync -p embassy-net

Debugging

调试

Serial Logging

串口日志

rust
use esp_println::println;
init_logger(log::LevelFilter::Info);
println!("Debug: value = {}", value);
rust
use esp_println::println;
init_logger(log::LevelFilter::Info);
println!("调试: 值 = {}", value);

Common Runtime Issues

常见运行时问题

  • WiFi fails: Check 2.4GHz network, signal strength
  • MQTT fails: Verify DNS resolution, broker credentials
  • Sensors fail: Check warmup delays, power pin toggling
  • Display blank: Ensure GPIO15 is HIGH (power enable)
  • Sleep wake fails: Verify RTC fast memory config
  • WiFi连接失败:检查是否为2.4GHz网络、信号强度
  • MQTT连接失败:验证DNS解析、代理凭证
  • 传感器读取失败:检查预热延迟、电源引脚切换
  • 显示屏黑屏:确保GPIO15为高电平(电源使能)
  • 睡眠唤醒失败:验证RTC快速内存配置

Software Reset

软件复位

rust
use esp_hal::system::software_reset;
software_reset();  // Clean restart on unrecoverable error
rust
use esp_hal::system::software_reset;
software_reset();  // 发生不可恢复错误时执行干净重启