【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
原因: 开发板未连接或驱动问题。
解决:
- 检查 USB 线是否支持数据传输(非纯充电线)
- macOS 下通常免驱;Windows 需安装 Zadig 或 WinUSB 驱动
- 运行
probe-rs list确认检测到芯片
Q3:温度值偏差很大(±10°C)
原因: 未使用 efuse 校准值,原始公式精度有限。
说明: 本示例使用简化公式 raw × 0.4386 - 20.52,误差约 ±5°C。芯片在 efuse 中有每颗芯片的校准值,读取后可提升精度。详见 docs/temperature_sensor.md。
Q4:RTT 输出乱码或无输出
解决:
- 确认
Cargo.toml中有rtt-target和panic-rtt-target依赖 - 确认代码中有
rtt_init_print!()宏调用 - 尝试重新烧录:
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 中交流。