读取芯片内部温度传感器

【ESP32-S3 Rust 入门】读取芯片内部温度传感器(寄存器级操作)

作者:CXi

日期:2025-06-16

硬件:ESP32-S3R8N8 嘉立创开发板

框架:esp-hal v1.1.0(Rust 原生 HAL)


一、项目简介

本教程将带你用 Rust 在 ESP32-S3 上读取芯片内部温度传感器 ,全程直接操作寄存器(不依赖高级 API),帮你理解嵌入式开发的底层原理。

你将学到:

  • ESP32-S3 内部温度传感器的工作原理
  • 如何用 Rust PAC 直接读写外设寄存器
  • esp-hal 项目的标准结构
  • 通过 RTT 输出调试信息

最终效果: 每 2 秒打印一次芯片温度,输出类似:

复制代码
32.1°C (raw=120)
31.8°C (raw=119)

二、硬件介绍

2.1 嘉立创 ESP32-S3R8N8 开发板

参数 规格
芯片 ESP32-S3
Flash 8MB (N8)
PSRAM 8MB (R8)
USB Type-C(内置 USB-JTAG 调试)
下载方式 USB 直连 / 外部 JTAG

2.2 温度传感器

ESP32-S3 内置一个 8 位 Sigma-Delta 温度传感器,无需外接任何元件:

  • 测量范围:-20°C ~ 110°C
  • 精度:未校准约 ±5°C(适合监测温度变化趋势)
  • 数据宽度:8 位(原始值 0~255)
  • 通过 SENS 外设寄存器控制

三、开发环境搭建

3.1 安装 Rust 工具链

ESP32-S3 使用 Xtensa 架构,需要 Espressif 定制的 Rust 工具链:

bash 复制代码
# 安装 espup(Espressif 的 Rust 工具链管理器)
cargo install espup

# 安装 Xtensa 目标工具链
espup install

# 使环境变量生效
source ~/.bashrc   # 或 source ~/.zshrc

3.2 安装 probe-rs(烧录 & 调试工具)

bash 复制代码
# 安装 probe-rs
cargo install probe-rs --features cli

# 验证安装,查看已连接的开发板
probe-rs list

3.3 验证环境

bash 复制代码
rustc --version
# 应显示 esp 工具链版本,如: rustc 1.88.0-nightly (xxxxxx yyyy-mm-dd)

probe-rs list
# 应检测到 ESP32-S3 芯片

四、项目结构

复制代码
ADC/
├── .cargo/
│   └── config.toml          # 编译目标 & 烧录器配置
├── Cargo.toml               # 依赖声明
├── rust-toolchain.toml      # 指定 esp 工具链
├── build.rs                 # 链接脚本辅助(帮助定位链接错误)
├── docs/
│   └── temperature_sensor.md # 温度传感器寄存器详细文档
└── src/
    ├── lib.rs               # 库入口(仅 #![no_std])
    └── bin/
        └── main.rs          # 主程序

4.1 关键配置文件

.cargo/config.toml --- 编译目标 & 烧录配置:

toml 复制代码
[target.xtensa-esp32s3-none-elf]
# probe-rs 烧录命令,烧录后自动运行
runner = "probe-rs run --chip=esp32s3 --preverify --always-print-stacktrace --no-location"

[build]
target = "xtensa-esp32s3-none-elf"   # Xtensa 编译目标

[unstable]
build-std = ["alloc", "core"]        # 从源码编译标准库(嵌入式必需)

rust-toolchain.toml --- 指定 Espressif 定制工具链:

toml 复制代码
[toolchain]
channel = "esp"    # 使用 esp 通道(支持 Xtensa 架构)

Cargo.toml --- 核心依赖:

toml 复制代码
[dependencies]
# ESP32-S3 硬件抽象层(unstable 特性启用实验性 API)
esp-hal = { version = "~1.1.0", features = ["esp32s3", "unstable"] }

# IDF bootloader 兼容层(必须,提供 esp_app_desc! 宏)
esp-bootloader-esp-idf = { version = "0.5.0", features = ["esp32s3"] }

# RTT 日志输出(通过 probe-rs 在电脑端查看)
rtt-target = "0.6.2"

# RTT panic 输出(程序崩溃时通过 RTT 打印错误信息)
panic-rtt-target = "0.2.0"

五、完整代码 & 逐行解析

5.1 src/bin/main.rs --- 完整代码

rust 复制代码
// ESP32-S3 内部温度传感器 --- 直接操作 SENS 寄存器读取芯片温度
// 参考文档: docs/temperature_sensor.md
#![no_main]
#![no_std]

use esp_bootloader_esp_idf;
use esp_hal::{delay::Delay, main, peripherals::SENS};
use panic_rtt_target as _;

// IDF bootloader 要求的 app 描述符(包含版本、大小等元数据)
esp_bootloader_esp_idf::esp_app_desc!();
use rtt_target::{rprintln, rtt_init_print};

#[main]
fn main() -> ! {
    // 初始化 RTT 日志(host 端通过 probe-rs 查看输出)
    rtt_init_print!();
    let peripherals = esp_hal::init(esp_hal::Config::default());
    let delay = Delay::new();

    // ── 初始化温度传感器 ─────────────────────────────────
    let regs = SENS::regs();

    // ① 开启温度传感器外设时钟(不开时钟则寄存器读写无效)
    regs.sar_peri_clk_gate_conf()
        .modify(|_, w| w.tsens_clk_en().set_bit());

    // ② 强制上电:软件直接控制电源,不依赖硬件自动管理
    regs.sar_tsens_ctrl().modify(|_, w| {
        w.sar_tsens_power_up_force() // 绕过硬件自动上下电
            .set_bit()
            .sar_tsens_power_up() // 给传感器上电
            .set_bit()
    });

    // ③ 设置上电等待时间 = 0b11(最大值),确保模拟电路稳定
    regs.sar_tsens_ctrl2()
        .modify(|_, w| unsafe { w.sar_tsens_xpd_force().bits(3) });

    // ④ 等待 300µs,让传感器内部模拟电路稳定
    delay.delay_micros(300);

    // ── 循环读取温度 ────────────────────────────────────
    loop {
        // 触发一次转换:将内部 ADC 结果锁存到输出寄存器
        regs.sar_tsens_ctrl()
            .modify(|_, w| w.sar_tsens_dump_out().set_bit());

        // 忙等待:直到转换完成(ready 位变为 1)
        while !regs.sar_tsens_ctrl().read().sar_tsens_ready().bit_is_set() {}

        // 读取原始 8 位 ADC 值(范围 0~255)
        let raw = regs.sar_tsens_ctrl().read().sar_tsens_out().bits();

        // 清除触发信号,为下次转换做准备
        regs.sar_tsens_ctrl()
            .modify(|_, w| w.sar_tsens_dump_out().clear_bit());

        // 换算公式(无校准 / DAC=0 时的简化版):
        //   °C = raw × 0.4386 - 20.52
        // 例: raw=120 → 120 × 0.4386 - 20.52 ≈ 32.1°C
        let temp = raw as f32 * 0.4386 - 20.52;
        rprintln!("{:.1}°C (raw={})", temp, raw);

        delay.delay_millis(2000);
    }
}

5.2 逐段解析

文件头 & 导入
rust 复制代码
#![no_main]  // 嵌入式程序没有标准 main,由运行时入口接管
#![no_std]   // 不使用标准库(嵌入式无 OS,无法使用 std)

use esp_hal::{delay::Delay, main, peripherals::SENS};
//                       ^^^^^          ^^^^^^^^^^^^^^
//                       延时工具        SENS 外设寄存器(温度传感器在这里)

关键概念:

  • #![no_std] --- 嵌入式铁律,表示不链接标准库(std),只用核心库(core
  • #![no_main] --- 禁用 C 运行时的 main 入口,改用 #[main] 宏标记入口
  • peripherals::SENS --- SENS 是 ESP32-S3 的"传感器控制外设",温度传感器寄存器都在这里
RTT 日志
rust 复制代码
use rtt_target::{rprintln, rtt_init_print};

RTT(Real-Time Transfer) 是 Segger 的调试输出协议:

对比 串口(UART) RTT
速度 115200 bps 几乎零延迟
占用引脚 需要 TX/RX 引脚 通过 USB-JTAG 直接传输
CPU 开销 较高 极低
本项目 --- ✅ 使用 RTT

rprintln! 宏的作用类似 println!,但输出通过 probe-rs 传到电脑终端。

SENS 寄存器初始化

ESP32-S3 的温度传感器不能直接用,需要按顺序完成 4 步初始化:

复制代码
┌──────────────┐     ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│ ① 开时钟     │ ──▶ │ ② 强制上电    │ ──▶ │ ③ 设置等待    │ ──▶ │ ④ 等 300µs   │
│ tsens_clk_en │     │ power_up     │     │ xpd_force    │     │ 模拟电路稳定  │
└──────────────┘     └──────────────┘     └──────────────┘     └──────────────┘

① 开时钟

rust 复制代码
regs.sar_peri_clk_gate_conf()
    .modify(|_, w| w.tsens_clk_en().set_bit());

ESP32-S3 为省电,所有外设时钟默认关闭。不开时钟 = 寄存器读写无效。

② 强制上电

rust 复制代码
regs.sar_tsens_ctrl().modify(|_, w| {
    w.sar_tsens_power_up_force()  // bit 23: 设为 1 表示"软件控制电源"
        .set_bit()
        .sar_tsens_power_up()     // bit 22: 设为 1 表示"上电"
        .set_bit()
});
  • power_up_force = 1:绕过硬件自动电源管理,由软件全权控制
  • power_up = 1:给温度传感器上电

③ 设置等待时间

rust 复制代码
regs.sar_tsens_ctrl2()
    .modify(|_, w| unsafe { w.sar_tsens_xpd_force().bits(3) });

xpd_force = 0b11(十进制 3)表示最大等待时间。模拟电路上电后需要时间稳定,设最大值最保险。

④ 等待稳定

rust 复制代码
delay.delay_micros(300);  // 等 300 微秒
温度读取循环
复制代码
触发(dump_out=1) ──▶ 等待(ready=1) ──▶ 读取(out) ──▶ 清除(dump_out=0)
rust 复制代码
// 触发:告诉传感器"开始转换"
regs.sar_tsens_ctrl()
    .modify(|_, w| w.sar_tsens_dump_out().set_bit());

// 等待:轮询 ready 位,直到转换完成
while !regs.sar_tsens_ctrl().read().sar_tsens_ready().bit_is_set() {}

// 读取:取 8 位原始值
let raw = regs.sar_tsens_ctrl().read().sar_tsens_out().bits();

// 清除:重置触发信号
regs.sar_tsens_ctrl()
    .modify(|_, w| w.sar_tsens_dump_out().clear_bit());
温度换算
rust 复制代码
let temp = raw as f32 * 0.4386 - 20.52;
原始值 (raw) 计算过程 温度
0 0 × 0.4386 - 20.52 -20.5°C
50 50 × 0.4386 - 20.52 1.4°C
100 100 × 0.4386 - 20.52 23.3°C
120 120 × 0.4386 - 20.52 32.1°C
200 200 × 0.4386 - 20.52 67.2°C
255 255 × 0.4386 - 20.52 91.3°C

校准提示: ESP32-S3 每颗芯片的 efuse 中烧录了校准值 deltaT,使用校准后公式为:

°C = raw - deltaT / 10,精度可提升到 ±2°C。


六、Rust PAC 寄存器操作速查

本项目使用 PAC(Peripheral Access Crate) 直接操作寄存器,核心 API 如下:

6.1 regs.xxx().read() --- 读寄存器

rust 复制代码
let val = regs.sar_tsens_ctrl().read().sar_tsens_out().bits();
//        ^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^^^^^^^^^^
//        访问 sar_tsens_ctrl 寄存器     读取 sar_tsens_out 字段的原始值

6.2 regs.xxx().modify(|_, w| ...) --- 改寄存器

rust 复制代码
regs.sar_tsens_ctrl().modify(|_, w| {
    w.sar_tsens_power_up_force()  // 选择字段
        .set_bit()                // 设为 1
        .sar_tsens_power_up()     // 链式设置下一个字段
        .set_bit()
});
// modify 的闭包参数: |r, w|
//   r = 当前寄存器值(只读,本例用 _ 忽略)
//   w = 写入器(可链式设置多个字段)

6.3 常用方法

方法 作用 示例
.set_bit() 置 1 w.tsens_clk_en().set_bit()
.clear_bit() 置 0 w.sar_tsens_dump_out().clear_bit()
.bits(n) 写入任意值 w.sar_tsens_xpd_force().bits(3)
.bit_is_set() 判断是否为 1 read().sar_tsens_ready().bit_is_set()
.bit_is_clear() 判断是否为 0 read().xxx().bit_is_clear()

七、编译 & 烧录

7.1 编译

bash 复制代码
cd examples/ADC

# 编译(首次较慢,后续增量编译很快)
cargo build

编译成功输出类似:

复制代码
Finished `dev` profile [optimized for size] target(s) in 25.37s

7.2 烧录 & 运行

bash 复制代码
# 一条命令:编译 + 烧录 + 运行
cargo run

前提: 嘉立创开发板通过 USB 连接电脑,probe-rs list 能检测到芯片。

7.3 查看输出

烧录成功后,终端直接显示 RTT 输出:

复制代码
     Running `probe-rs run --chip=esp32s3 ...`
      Erasing ✔ [00:00:01] [████████████████████] 128.00 KiB/128.00 KiB @ 85.35 KiB/s
  Programming ✔ [00:00:02] [████████████████████] 126.42 KiB/126.42 KiB @ 58.71 KiB/s
    Finished in 3.47s
32.1°C (raw=120)
31.8°C (raw=119)
32.3°C (raw=121)
...(每 2 秒输出一行)

Ctrl+C 停止。


八、常见问题

Q1:编译报错 can't find crate for core

原因: 未正确安装 Xtensa 工具链。

解决:

bash 复制代码
espup install
source ~/.bashrc   # 或 ~/.zshrc

Q2:烧录报错 No debug probe was found

原因: 开发板未连接或驱动问题。

解决:

  1. 检查 USB 线是否支持数据传输(非纯充电线)
  2. macOS 下通常免驱;Windows 需安装 Zadig 或 WinUSB 驱动
  3. 运行 probe-rs list 确认检测到芯片

Q3:温度值偏差很大(±10°C)

原因: 未使用 efuse 校准值,原始公式精度有限。

说明: 本示例使用简化公式 raw × 0.4386 - 20.52,误差约 ±5°C。芯片在 efuse 中有每颗芯片的校准值,读取后可提升精度。详见 docs/temperature_sensor.md

Q4:RTT 输出乱码或无输出

解决:

  1. 确认 Cargo.toml 中有 rtt-targetpanic-rtt-target 依赖
  2. 确认代码中有 rtt_init_print!() 宏调用
  3. 尝试重新烧录:cargo run

九、关键概念总结

概念 说明
#![no_std] 嵌入式必须,不使用标准库
#![no_main] 嵌入式必须,不使用 C 运行时 main
PAC Peripheral Access Crate,寄存器级访问层
HAL Hardware Abstraction Layer,硬件抽象层(本项目部分使用)
SENS ESP32-S3 的传感器控制外设,温度传感器寄存器在此
RTT Real-Time Transfer,通过 USB-JTAG 实时输出调试信息
probe-rs Rust 嵌入式烧录 & 调试工具
esp-hal Espressif 官方 Rust HAL 库
espup Espressif 的 Rust 工具链安装器

十、参考资料


作者:CXi

如有疑问,欢迎在 GitHub Issues 中交流。

相关推荐
望眼欲穿的程序猿1 小时前
ADC 模拟电压采集
嵌入式硬件·rust
IT方大同1 小时前
(嵌入式操作系统)信号量
嵌入式硬件·c#
codexu_4612291872 小时前
NoteGen 里一条记录如何变成 Markdown
前端·笔记·rust·tauri
意法半导体STM322 小时前
【官方原创】如何为STM32CubeMX2配置Visual Studio Code配置方案
vscode·stm32·单片机·嵌入式硬件·策略模式·stm32cubemx·嵌入式开发
Rust研习社2 小时前
Rust 错误处理的黄金搭档:一个定义错误,一个传播错误
后端·rust·编程语言
自小吃多2 小时前
IVD设备-以GB4793.1做安规摸底
笔记·嵌入式硬件
雾削木3 小时前
B语言经典教程现代化重构
java·前端·stm32·单片机·嵌入式硬件
Hello-FPGA3 小时前
Camera Link 与 CoaXPress 技术对比 如何选择你的相机接口
单片机·嵌入式硬件
Digitally3 小时前
如何快速将文件从电脑传输到平板电脑
stm32·嵌入式硬件·电脑