和普通按键只需要通过电平来识别触发不同,我们需要对xy轴摇杆传感器进行测量它的信号值,因此需要用到模数转换器 (ADC) 。
对了,xy轴摇杆传感器长这样,就是小孩子玩具车配的遥控器所用的那个摇杆。

它有5个引脚,分别为:GND、VCC、VRx、VRy、SW。
其中 VRx、VRy 是两个模拟输入引脚,用于测量摇杆的X、Y轴信号。 SW 是一个按键引脚,用于检测摇杆的按键。
ESP32-S3 集成了两个 12 位 SAR ADC, 支持生成ADC 单次转换结果和连续转换结果。然而,并不是所有的GPIO引脚都支持ADC转换。通过查询技术参考手册得知分布如下:

依赖如下:
[dependencies]
esp-hal = { version = "~1.0", features = [ "esp32s3","unstable","defmt"] }
anyhow = {version = "=1.0.102", default-features = false}
esp-bootloader-esp-idf = { version = "0.4.0", features = ["esp32s3"] }
critical-section = "1.2.0"
esp-alloc = "0.9.0"
rtt-target = "0.6.2"
defmt = "1.0.1"
defmt-rtt = "1.1.0"
nb = "1.1.0"
xy轴的读数
esp_hal 的 adc 功能放在 analog模块里。
先初始化 gpio 引脚:
let cpu_clock = hal::clock::CpuClock::max();
let config = hal::Config::default().with_cpu_clock(cpu_clock);
let peripherals = hal::init(config);
我这里把 x和y轴的引脚都放在了gpio1和gpio2里。通过上图可知,gpio1和gpio2 归属于 adc1 模块。
let mut adc1_config = AdcConfig::new();
let mut x_adc = adc1_config.enable_pin(peripherals.GPIO1, Attenuation::_11dB);
let mut y_adc = adc1_config.enable_pin(peripherals.GPIO2, Attenuation::_11dB);
let mut adc1 = Adc::new(peripherals.ADC1, adc1_config);
设置为 Attenuation::_11dB 的来源于官方技术手册:https://documentation.espressif.com/esp32-s3_technical_reference_manual_cn.pdf
第39章有讲到,衰减可配置为 0 dB、2.5 dB、6 dB 和 12 dB。而 rust 的实现上,其实是0 dB、2.5 dB、6 dB 和 11 dB。
然后在循环里读取数据:
loop {
// 读取 X 轴 ADC 值
let x_value: u16 = nb::block!(adc1.read_oneshot(&mut x_adc)).unwrap();
// 读取 Y 轴 ADC 值
let y_value: u16 = nb::block!(adc1.read_oneshot(&mut y_adc)).unwrap();
// ADC 值范围: 0-4095 (12位 ADC)
// 中点值约为 2048(摇杆居中)
println!("X: {}, Y: {}", x_value, y_value);
delay.delay_millis(100);
}
刷入固件,我们会在控制台里看到x和y的值。通过拨动摇杆,我们可以看到x和y的值不断变化。
z 轴的检测
z 轴本质就是一个安装在摇杆上的按键。就是普通按键的处理流程:通过GPIO中断来判断按键是否被按下。
先定义一个静态的变量,用于保存按键状态:】
static Z_BUTTON: Mutex<RefCell<Option<Input>>> = Mutex::new(RefCell::new(None));
设置中断处理函数:
let mut io = Io::new(peripherals.IO_MUX);
io.set_interrupt_handler(button_handler);
SW 接在35号引脚上,我们还需要设置监听下降沿触发的中断:
let z_pin = peripherals.GPIO35;
let config = InputConfig::default().with_pull(Pull::Up);
let mut z_button = Input::new(z_pin, config);
critical_section::with(|cs| {
z_button.listen(esp_hal::gpio::Event::FallingEdge);
Z_BUTTON.borrow_ref_mut(cs).replace(z_button);
});
针对回调 button_handler 实现如下:
#[handler]
#[ram]
fn button_handler() {
critical_section::with(|cs| {
let mut z_btn = Z_BUTTON.borrow_ref_mut(cs);
if let Some(ref mut btn) = *z_btn {
if btn.is_interrupt_set() {
println!("Button pressed!");
btn.clear_interrupt();
}
}
});
}