官方文档:https://esp-rs.github.io/book/introduction.html
安装 rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
如果需要立即使用,使用. "$HOME/.cargo/env"
更新命令行环境
工具
risc:
rustup toolchain install nightly --component rust-src
这里不做这个其实也行,编译时会检查 rust-toolchain.toml 文件,会自动执行这个操作
或使用安装工具同时支持 risc 和 xtensa:
cargo install espup
espup install
会在 /home/你的用户名/ 下生成一个 esport-esp.sh,如果要对工程运行 cargo build 或 cargo run 就需要提前引入到命令行里
建议在 ~/.bashrc 的最后新加一行 . $HOME/export-esp.sh
其内容是
export LIBCLANG_PATH="/home/xiaguangbo/.rustup/toolchains/esp/xtensa-esp32-elf-clang/esp-16.0.0-20230516/esp-clang/lib"
export PATH="/home/xiaguangbo/.rustup/toolchains/esp/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/bin:$PATH"
export PATH="/home/xiaguangbo/.rustup/toolchains/esp/riscv32-esp-elf/esp-13.2.0_20230928/riscv32-esp-elf/bin:$PATH"
以上都需要:
sudo apt install libudev-dev libssl-dev git pkg-config clang python3.11-venv
cargo install ldproxy cargo-generate espflash
两种工具链切换需要执行一次cargo clean
创建模板工程
使用 std 方式,不使用 no_std,std 依赖 esp-idf,但不需要手动下载,会在第一次编译时下载
riscv 和 xtensa 架构的都可以,支持列表看官方文档
工程名自己定义,这里以 hhhh 作工程名
xiaguangbo@debian:/media/xiaguangbo/linux_data/project/x/xfoc/project$ cargo generate esp-rs/esp-idf-template cargo
⚠️ Favorite `esp-rs/esp-idf-template` not found in config, using it as a git repository: https://github.com/esp-rs/esp-idf-template.git
🤷 Project Name: hhhh
🔧 Destination: /media/xiaguangbo/linux_data/project/x/xfoc/project/hhhh ...
🔧 project-name: hhhh ...
🔧 Generating template ...
✔ 🤷 Which MCU to target? · esp32c3
✔ 🤷 Configure advanced template options? · true 这里也可以使用默认的 false,不过 ESP-IDF version 则会为稳定版。建议不改,这里只是说怎么改。master的可能出现rust库适配问题导致工程无法建立
✔ 🤷 Enable STD support? · true
? 🤷 Configure project to use Dev Containers (VS Code and GitHub Codespaces)? ✔ 🤷 Configure project to use Dev Containers (VS Code and GitHub Codespaces)? · false
? 🤷 Configure project to support Wokwi simulation with Wokwi VS Code extensio✔ 🤷 Configure project to support Wokwi simulation with Wokwi VS Code extension? · false
✔ 🤷 Add CI files for GitHub Action? · false
✔ 🤷 ESP-IDF version (master = UNSTABLE) · master
🔧 Moving generated files into: `/media/xiaguangbo/linux_data/project/x/xfoc/project/hhhh`...
🔧 Initializing a fresh Git repository
✨ Done! New project created /media/xiaguangbo/linux_data/project/x/xfoc/project/hhhh
工程默认是40M晶振,所有使用26M晶振的需要在sdkconfig.defaultsl里添加CONFIG_XTAL_FREQ_26=y
,否则delay不准,正常情况实测延时偏差小于2%。配置生效需要cargo clean
再cargo run
。
建议添加CONFIG_FREERTOS_HZ=1000
,默认的是100,会让FreeRtos::delay_ms(xxx)最小延时变成10ms
目前 esp32c2 2MB 版本需要修改.cargo/config.toml
里的runner
项为runner = "espflash flash --monitor --flash-size 2mb"
,不然esp32c2会启动报错。使用esp32c2 4MB 版本时只会用到2MB,如果需要空间全能用就改成--flash-size 4mb
cd 到工程里
cargo run
第一次会在工程里下载 .embuild/espressif/esp-idf,可能会不断因网络问题失败,再执行。
可能会提示 riscv32-esp-elf-13.2.0_20230928-x86_64-linux-gnu.tar.xz 下载不下来,可以手动下载到 .embuild/espressif/dist 里。
可能会提示 git submodule update --init --recursive,就到 .embuild/espressif/esp-idf/master 里执行一下。
将 esp32c3 连接到电脑上,并把串口的权限改为 777。假设串口是 /dev/ttyUSB0,就执行 sudo chmod 777 /dev/ttyUSB0。每次 usb 断开连上都需要,或者一劳永逸:查看串口设备的用户组`ls -l /dev/ttyUSB0`,应该是dialout,把当前用户添加到dialout `sudo usermod -a -G dialout $USER`,立即生效`su - $USER`
cargo run
就会下载程序到 esp32c3 里并打开串口监控
如果出现打开串口时权限不足 Permission denied:
$ su - root
root@debian:~# usermod -aG dialout hhh # ls -l /dev/ttyUSB0 可以看到 用户组是dialout
root@debian:~# exit
然后重启
手动打开串口监控:espflash monitor
cargo run 编译 esp-idf-sys 时会检查 esp-idf,如果连不上 github/esp…仓库会报错。。。。vscode 的 rust-analyzer 插件也会因这个原因出问题
其他
可能会用到的命令:
git submodule update --init --recursive
git clone --recursive --depth 1 --shallow-submodules --branch master https://github.com/espressif/esp-idf.git /media/xiaguangbo/linux_data/project/x/xfoc/project/esp32s3/.embuild/espressif/esp-idf/master
使用自定义esp-idf的位置:
在.cargo/config.toml
里的[env]
下添加下面的,位置可以自定义,如果是相对位置则以Cargo.toml
所在的位置为原点
ESP_IDF_TOOLS_INSTALL_DIR = "custom:../../env/espressif"
示例
使用 i2c 读取 as5600 的测到的磁铁的角度:
use esp_idf_svc::hal::delay::Delay;
use esp_idf_hal::delay::{FreeRtos, BLOCK};
use esp_idf_hal::i2c::*;
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::prelude::*;
const AS5600_ADDRESS: u8 = 0x36;
const ANGLE_HIGHT_REGISTER_ADDR: u8 = 0x0c;
const ANGLE_LOW_REGISTER_ADDR: u8 = 0x0d;
fn main() {
// It is necessary to call this function once. Otherwise some patches to the runtime
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_svc::sys::link_patches();
// Bind the log crate to the ESP Logging facilities
esp_idf_svc::log::EspLogger::initialize_default();
log::info!("Hello, world!");
let peripherals = Peripherals::take().unwrap();
let i2c = peripherals.i2c0;
let scl = peripherals.pins.gpio6;
let sda = peripherals.pins.gpio7;
let config = I2cConfig::new().baudrate(100.kHz().into());
let mut i2c = I2cDriver::new(i2c, sda, scl, &config).unwrap();
loop {
FreeRtos::delay_ms(1000);
i2c.write(AS5600_ADDRESS, &[ANGLE_HIGHT_REGISTER_ADDR], BLOCK)
.unwrap();
let mut buffer_h: [u8; 1] = [0; 1];
i2c.read(AS5600_ADDRESS, &mut buffer_h, BLOCK).unwrap();
i2c.write(AS5600_ADDRESS, &[ANGLE_LOW_REGISTER_ADDR], BLOCK)
.unwrap();
let mut buffer_l: [u8; 1] = [0; 1];
i2c.read(AS5600_ADDRESS, &mut buffer_l, BLOCK).unwrap();
log::info!(
"as5600: {}",
(((buffer_h[0] as u16) << 8 | (buffer_l[0] as u16)) as f32) / 4096.0 * 360.0
);
}
}
pin 模拟 onewire 读取 ds18b20 的温度,并从另一个 uart 发出来:
use embedded_hal::digital;
use esp_idf_svc::hal::delay;
use esp_idf_svc::hal::gpio;
use esp_idf_svc::hal::peripherals;
use esp_idf_svc::hal::uart;
use esp_idf_svc::hal::units;
use std::fmt::Write;
const CMD_SKIP: u8 = 0xcc;
const CMD_WRITE_REG: u8 = 0x4e;
const CMD_TEMP_START: u8 = 0x44;
const CMD_READ_REG: u8 = 0xbe;
fn main() {
// It is necessary to call this function once. Otherwise some patches to the runtime
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_svc::sys::link_patches();
// Bind the log crate to the ESP Logging facilities
esp_idf_svc::log::EspLogger::initialize_default();
log::info!("Hello, world!");
let peripherals = peripherals::Peripherals::take().unwrap();
// onewire
let mut onewire = gpio::PinDriver::input_output_od(peripherals.pins.gpio0).unwrap();
onewire.set_pull(gpio::Pull::Floating).unwrap(); // use external pull-up
// uart
let config = uart::config::Config::default().baudrate(units::Hertz(115_200));
let mut uart = uart::UartDriver::new(
peripherals.uart1,
peripherals.pins.gpio1,
peripherals.pins.gpio2,
Option::<gpio::AnyIOPin>::None,
Option::<gpio::AnyIOPin>::None,
&config,
)
.unwrap();
// delay
let delay = delay::Delay::new_default();
// ds18b20
// wait reset
while !onewire_reset(&mut onewire, &delay) {
delay::FreeRtos::delay_ms(1000);
}
// set accuracy
onewire_write_byte(&mut onewire, &delay, CMD_SKIP);
onewire_write_byte(&mut onewire, &delay, CMD_WRITE_REG);
onewire_write_byte(&mut onewire, &delay, 0xff);
onewire_write_byte(&mut onewire, &delay, 0x00);
onewire_write_byte(&mut onewire, &delay, 0x7f);
loop {
delay::FreeRtos::delay_ms(1000);
// ds18b20
// measurement temp
if !onewire_reset(&mut onewire, &delay) {
continue;
}
onewire_write_byte(&mut onewire, &delay, CMD_SKIP);
onewire_write_byte(&mut onewire, &delay, CMD_TEMP_START);
// read temp
if !onewire_reset(&mut onewire, &delay) {
continue;
}
onewire_write_byte(&mut onewire, &delay, CMD_SKIP);
onewire_write_byte(&mut onewire, &delay, CMD_READ_REG);
let temp_l = onewire_read_byte(&mut onewire, &delay);
let temp_h = onewire_read_byte(&mut onewire, &delay);
let temp = (((temp_h as i16) << 8 | (temp_l as i16)) as f64) * 0.0625;
log::info!("temp: {:.1}", temp);
// uart
writeln!(uart, "temp: {:.1}", temp).unwrap();
}
}
fn onewire_reset<T>(pin: &mut T, delay: &delay::Delay) -> bool
where
T: digital::InputPin + digital::OutputPin,
{
pin.set_low().unwrap();
delay.delay_us(600);
pin.set_high().unwrap();
delay.delay_us(80);
if pin.is_low().unwrap() {
log::info!("onewire: is exist");
delay.delay_us(900);
if pin.is_high().unwrap() {
log::info!("onewire: reset ok");
true
} else {
log::warn!("onewire: reset err");
false
}
} else {
log::warn!("onewire: reset err");
false
}
}
fn onewire_write_byte<T>(pin: &mut T, delay: &delay::Delay, mut byte: u8)
where
T: digital::InputPin + digital::OutputPin,
{
for _ in 0..8 {
let bit = byte & 0x01 != 0;
byte >>= 1;
if bit {
pin.set_low().unwrap();
delay.delay_us(5);
pin.set_high().unwrap();
delay.delay_us(90);
} else {
pin.set_low().unwrap();
delay.delay_us(90);
pin.set_high().unwrap();
delay.delay_us(5);
}
}
}
fn onewire_read_byte<T>(pin: &mut T, delay: &delay::Delay) -> u8
where
T: digital::InputPin + digital::OutputPin,
{
let mut byte: u8 = 0;
for _ in 0..8 {
byte >>= 1;
pin.set_low().unwrap();
delay.delay_us(5);
pin.set_high().unwrap();
delay.delay_us(5);
if pin.is_high().unwrap() {
byte |= 0x80;
}
delay.delay_us(60);
}
byte
}
Cargo.toml:
[dependencies]
...
esp-idf-hal = "*"
embedded-hal = "*"