Rust嵌入式开发库esp-println的使用,专为ESP系列芯片优化的高效日志打印工具

Rust嵌入式开发库esp-println的使用,专为ESP系列芯片优化的高效日志打印工具

esp-println是一个为Espressif设备提供print!println!dbg!实现和日志功能的库。

特性

  • 支持所有Espressif ESP32系列设备
  • 支持不同的通信方式:
    • UART(默认)
    • JTAG-Serial(仅适用于ESP32-C3、ESP32-C6、ESP32-H2、ESP32-S3)
    • No-op:将打印变为无操作
  • 支持defmt后端

使用方法

在Cargo.toml中添加依赖:

esp-println = { version = "0.11.0", features = ["esp32c2"] }

或者运行命令:

cargo add esp-println --features esp32c2

在代码中使用:

use esp_println::println;

fn main() {
    println!("Hello world");
}

日志功能

启用log-04特性并添加log crate v0.4依赖后,可以初始化一个简单的日志记录器:

init_logger(log::LevelFilter::Info);

默认启用了colors特性,可以显示彩色日志输出。

还可以使用:

init_logger_from_env();

此时会使用以下环境变量:

  • ESP_LOG:指定要显示的日志消息,类似于RUST_LOG。不支持正则表达式。例如:warn,test::foo=info,test::foo::bar=debug

defmt支持

使用defmt-espflash特性时,esp-println会安装一个defmt全局日志记录器。该记录器会输出到与println!()相同的数据流,并添加帧字节以便与其他非defmt输出一起使用。

完整示例代码

// 引入必要的库
use esp_println::println;
use log::{info, warn, error};

fn main() {
    // 初始化日志记录器,设置日志级别为Info
    esp_println::init_logger(log::LevelFilter::Info);
    
    // 使用println!宏打印信息
    println!("System started!");
    
    // 使用log宏记录不同级别的日志
    info!("This is an info message");
    warn!("This is a warning message");
    error!("This is an error message");
    
    // 打印变量值
    let count = 42;
    println!("The answer is: {}", count);
    
    // 使用dbg!宏调试
    let result = some_calculation();
    dbg!(result);
}

fn some_calculation() -> i32 {
    // 模拟一些计算
    42
}

注意事项

  • 如果遇到链接错误,请确保代码中有对esp_println的引用。如果不直接使用esp_println,可以添加use esp_println as _;到导入语句中
  • 最低支持的Rust版本(MSRV)为1.86.0

扩展完整示例代码

//! esp-println 完整使用示例
//! 演示了打印、日志记录和defmt集成

#![no_std]
#![no_main]

// 引入必要的库
use esp_println::{println, print};
use log::{info, warn, error, debug, trace};
use defmt::{info as defmt_info, warn as defmt_warn};

// 对于某些ESP目标可能需要这个
use esp_backtrace as _;

#[entry]
fn main() -> ! {
    // 1. 基本打印功能演示
    println!("\n=== 基本打印演示 ===");
    print!("Hello ");  // 不换行打印
    println!("World!");  // 带换行打印
    
    let x = 42;
    println!("变量值: x = {}", x);
    
    // 2. 日志功能演示
    println!("\n=== 日志功能演示 ===");
    // 初始化日志记录器,设置日志级别为Debug
    esp_println::init_logger(log::LevelFilter::Debug);
    
    info!("这是信息级别日志");
    warn!("这是警告级别日志");
    error!("这是错误级别日志");
    debug!("这是调试级别日志");
    trace!("这是跟踪级别日志 - 不会显示");
    
    // 3. dbg!宏演示
    println!("\n=== dbg!宏演示 ===");
    let values = [1, 2, 3];
    dbg!(values);  // 自动打印变量名和值
    
    // 4. 环境变量配置日志演示
    println!("\n=== 环境变量配置日志 ===");
    // 假设设置了`ESP_LOG=info,my_module=debug`
    esp_println::init_logger_from_env();
    
    info!("全局信息日志");
    debug!("全局调试日志 - 不会显示");
    
    mod my_module {
        use log::debug;
        pub fn test() {
            debug!("模块内调试日志 - 会显示");
        }
    }
    
    my_module::test();
    
    // 5. defmt集成演示 (需要启用defmt-espflash特性)
    #[cfg(feature = "defmt-espflash")]
    {
        println!("\n=== defmt集成演示 ===");
        defmt_info!("这是defmt信息日志");
        defmt_warn!("这是defmt警告日志: {}", x);
    }
    
    loop {
        // 嵌入式系统通常需要无限循环
    }
}

使用建议

  1. 选择适合的通信方式:

    • 常规开发使用UART
    • ESP32-C3/C6等支持JTAG-Serial的设备可以获得更好性能
    • 发布版本考虑使用No-op减少代码大小
  2. 日志级别选择:

    • 开发阶段使用Debug或更低级别
    • 生产环境建议使用Info或更高级别
  3. 性能考虑:

    • 频繁的日志输出可能影响实时性能
    • 考虑使用dbg!宏仅在调试时启用
  4. 内存使用:

    • 格式化字符串会占用栈空间
    • 对于长字符串考虑分多次打印

1 回复

esp-println - 专为ESP系列芯片优化的Rust日志打印工具

介绍

esp-println是专为ESP系列芯片(如ESP32、ESP32-C3等)设计的Rust日志打印库,提供了高效的println!宏实现。相比标准库的println!,它针对ESP芯片进行了优化,更适合嵌入式开发环境。

主要特点:

  • 轻量级实现,减少代码体积
  • 支持多种ESP芯片系列
  • 与标准println!相似的API,学习成本低
  • 可配置的输出目标(串口等)

使用方法

基本使用

首先在Cargo.toml中添加依赖:

[dependencies]
esp-println = "0.3"

然后在代码中使用:

use esp_println::println;

fn main() {
    println!("Hello, ESP world!");  // 输出到默认串口
    println!("Value: {}", 42);      // 支持格式化
}

初始化配置

在使用前通常需要初始化日志系统:

use esp_println::{logger::init_logger, Printer};

// 初始化日志系统,设置波特率
init_logger(Printer::new(115200));

不同日志级别

esp-println也提供了不同日志级别的宏:

use esp_println::{debug, error, info, warn};

fn some_function() {
    debug!("调试信息");
    info!("常规信息");
    warn!("警告信息");
    error!("错误信息");
}

自定义输出目标

可以自定义输出到不同的串口:

use esp_println::{Printer, println};
use embedded_hal::serial::Write;

// 假设uart1是你的串口实例
let printer = Printer::new_with_uart(uart1);
println!(printer, "这条消息会输出到uart1");

示例项目

一个完整的ESP32示例:

#![no_std]
#![no_main]

use esp32_hal::{clock::ClockControl, pac::Peripherals, prelude::*, timer::TimerGroup};
use esp_backtrace as _;
use esp_println::{println, init_logger, Printer};

#[entry]
fn main() -> ! {
    let peripherals = Peripherals::take().unwrap();
    let system = peripherals.DPORT.split();
    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

    // 初始化日志系统,波特率115200
    init_logger(Printer::new(115200));
    
    // 打印启动信息
    println!("系统启动完成");
    println!("CPU频率: {} MHz", clocks.cpu_clock.to_MHz());
    
    loop {
        println!("心跳...");
        TimerGroup::new(peripherals.TIMG1, &clocks).timer0.delay_ms(1000u32);
    }
}

完整示例DEMO

以下是一个更完整的ESP32-C3示例,展示了多级日志和自定义串口的使用:

#![no_std]
#![no_main]

use esp32c3_hal::{
    clock::ClockControl,
    pac::Peripherals,
    prelude::*,
    timer::TimerGroup,
    Rtc,
    Uart,
    IO,
};
use esp_backtrace as _;
use esp_println::{println, debug, info, warn, error, init_logger, Printer};

#[entry]
fn main() -> ! {
    let peripherals = Peripherals::take().unwrap();
    let system = peripherals.SYSTEM.split();
    let clocks = ClockControl::boot_defaults(system.clock_control).freeze();

    // 初始化串口0作为默认日志输出
    let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
    let pins = io.pins;
    let uart0 = Uart::new(peripherals.UART0, &clocks);
    
    // 初始化日志系统,波特率115200
    init_logger(Printer::new_with_uart(uart0));

    // 打印启动信息
    println!("系统启动完成");
    info!("CPU频率: {} MHz", clocks.cpu_clock.to_MHz());
    debug!("调试信息只会出现在debug模式下");
    
    // 初始化RTC
    let mut rtc = Rtc::new(peripherals.RTC_CNTL);
    rtc.rwdt.disable();
    
    // 创建一个计时器组
    let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
    let mut timer0 = timer_group0.timer0;
    
    let mut counter = 0;
    loop {
        counter += 1;
        
        match counter % 4 {
            0 => println!("循环计数: {}", counter),
            1 => debug!("调试计数: {}", counter),
            2 => info!("信息计数: {}", counter),
            3 => warn!("警告计数: {}", counter),
            _ => error!("错误计数: {}", counter),
        }
        
        timer0.delay_ms(500u32);
    }
}

注意事项

  1. 确保你的芯片支持所选波特率
  2. 在no_std环境下使用需要添加#![no_std]属性
  3. 对于复杂项目,考虑结合logcrate使用更完整的日志系统

esp-println是ESP嵌入式开发的实用工具,能显著简化调试和日志输出过程。

回到顶部