ESP32-S3驱动双轴按键摇杆模块:ADC采样与GPIO读取实战指南

ESP32-S3驱动双轴按键摇杆模块:ADC采样与GPIO读取实战指南 ESP32-S3驱动双轴按键摇杆模块ADC采样与GPIO读取实战指南最近在做一个遥控小车的项目需要用到摇杆来控制方向和选择功能于是找到了这种常见的双轴按键摇杆模块。这种模块在游戏手柄、遥控器、菜单导航等场景中非常实用。今天我就以ESP32-S3为例手把手教大家如何驱动它包括读取X、Y轴的模拟量以及Z轴按键的数字量。这个教程非常适合刚开始接触ESP32-S3外设开发的朋友跟着做一遍你就能掌握ADC采样和GPIO输入的基本操作以后做机器人控制、智能设备交互就轻松多了。1. 认识你的摇杆模块咱们先来看看要驱动的“主角”——双轴按键摇杆模块。它其实就是一个PS2游戏手柄上那种金属摇杆电位器。模块有三个输出信号VRX (X轴)模拟电压输出对应摇杆左右移动。VRY (Y轴)模拟电压输出对应摇杆上下移动。SW (按键)数字开关输出对应将摇杆垂直向下按的动作Z轴。简单来说X、Y轴告诉你摇杆偏到了哪个位置是模拟量连续变化的而Z轴按键告诉你摇杆有没有被按下去是数字量只有0和1两种状态。模块的基本规格工作电压3.3V ~ 5V ESP32-S3的IO口电压是3.3V所以我们用3.3V供电完全没问题控制方式ADC读取VRX/VRY GPIO读取SW它是怎么工作的模块内部X轴和Y轴各连接了一个10KΩ的电位器可变电阻。当你移动摇杆时电位器的抽头位置改变导致VRX和VRY引脚输出的电压也随之变化。举个例子如果用5V供电摇杆在原始居中状态时X和Y输出的电压大约在2.5V左右。当你把摇杆向某个方向推到底时对应轴的输出电压会减小最低可以降到0V。我们ESP32-S3的ADC模数转换器就是负责把这个变化的电压值转换成我们能处理的数字量。2. 硬件连接与引脚分配接线非常简单一共只需要连接4根线电源2根信号2根。我建议大家用杜邦线直接连接方便测试。模块引脚说明模块引脚标号信号类型连接到ESP32-S3作用GND电源地任意GND引脚提供公共参考地5V或VCC电源正3.3V引脚供电接3.3V即可VRX模拟输出GPIO1(配置为ADC1通道0)读取X轴位置VRY模拟输出GPIO2(配置为ADC1通道1)读取Y轴位置SW数字输出GPIO3(配置为输入内部上拉)读取按键状态注意模块上可能标的是“5V”但接ESP32-S3的3.3V引脚是完全正常工作的而且这样更安全避免了5V电压误接到其他GPIO口的风险。按照上表用杜邦线连接好即可。连接好后硬件部分就准备好了。3. 创建驱动代码BSP层在嵌入式开发中我们通常会把针对某个硬件的操作代码封装起来形成一个“板级支持包”BSP。这样主程序会非常简洁而且以后换用其他型号的摇杆或单片机只需要修改BSP层代码就行。接下来我们在ESP-IDF工程中创建两个文件bsp_joystick.c和bsp_joystick.h。3.1 头文件定义 (bsp_joystick.h)头文件里主要做两件事包含必要的库以及定义我们需要的常量和函数接口。#ifndef _BSP_JOYSTICK_H_ #define _BSP_JOYSTICK_H_ #include stdio.h #include esp_log.h #include freertos/FreeRTOS.h #include freertos/task.h #include driver/gpio.h #include esp_adc_cal.h // 硬件引脚定义 #define VRX_PIN 1 // X轴接GPIO1 (ADC1通道0) #define VRY_PIN 2 // Y轴接GPIO2 (ADC1通道1) #define SW_PIN 3 // 按键接GPIO3 // ADC相关参数定义 #define DEFAULT_VREF 1100 // ADC校准的默认参考电压单位是毫伏(mV) #define x_channel ADC_CHANNEL_0 // VRX对应ADC1的通道0 #define y_channel ADC_CHANNEL_1 // VRY对应ADC1的通道1 #define width ADC_WIDTH_BIT_12 // ADC分辨率设为12位 (0-4095) #define atten ADC_ATTEN_DB_11 // ADC衰减设为11dB量程为0-3.3V #define unit ADC_UNIT_1 // 使用ADC1单元 // 采样次数多次采样取平均可以滤除抖动让读数更稳定 #define SAMPLES 30 // 函数声明 void ADC_Init(void); // 初始化ADC和按键GPIO unsigned int Get_Joystick_Percentage_value(char dir); // 获取摇杆位置百分比 char Get_SW_state(void); // 获取按键状态 #endif代码要点解析ADC_WIDTH_BIT_12设置ADC为12位分辨率这意味着转换后的数字值范围是0到40952^12 - 1对应着0V到参考电压。ADC_ATTEN_DB_11这是衰减设置。因为我们供电是3.3V摇杆最大输出电压也是3.3V左右所以选择11dB的衰减使得ADC的量程正好是0-3.3V。SAMPLES定义了采样次数为30次。在Get_Adc_Dma_Value函数中我们会连续采样30次然后取平均值这样可以有效减少偶然的电压波动或干扰带来的读数跳动。3.2 源文件实现 (bsp_joystick.c)这里是驱动的核心包含了所有具体的初始化、读取逻辑。#include bsp_joystick.h // 用于存储ADC校准特性的指针校准能让ADC读数更准确 esp_adc_cal_characteristics_t *adc_chars; // 几个简单的延时函数后面可能会用到 void delay_ms(unsigned int ms) { vTaskDelay(ms / portTICK_PERIOD_MS); } void delay_us(unsigned int us) { ets_delay_us(us); } /****************************************************************** * 函数名称ADC_Init * 函数说明初始化ADC和按键GPIO * 形 参无 * 返 回 值无 ******************************************************************/ void ADC_Init(void) { // 1. 首先配置按键(SW)引脚为输入模式并启用内部上拉电阻 gpio_config_t sw_config { .pin_bit_mask (1ULL SW_PIN), // 配置SW_PIN引脚 .mode GPIO_MODE_INPUT, // 设置为输入模式 .pull_up_en GPIO_PULLUP_ENABLE, // 使能内部上拉电阻 .pull_down_en GPIO_PULLDOWN_DISABLE, // 不使能内部下拉电阻 .intr_type GPIO_INTR_DISABLE // 不使能引脚中断我们采用轮询方式 }; gpio_config(sw_config); // 应用配置 // 2. 配置ADC1的位宽分辨率 adc1_config_width(width); // 设置为12位分辨率 // 3. 配置ADC通道的衰减决定量程 // ADC_ATTEN_DB_0: 量程约0~1.1V // ADC_ATTEN_DB_2_5:量程约0~1.5V // ADC_ATTEN_DB_6: 量程约0~2.2V // ADC_ATTEN_DB_11: 量程约0~3.3V -- 我们选这个 adc1_config_channel_atten(x_channel, atten); // 配置X轴通道 adc1_config_channel_atten(y_channel, atten); // 配置Y轴通道 // 4. 为ADC校准特性结构体分配内存 adc_chars calloc(1, sizeof(esp_adc_cal_characteristics_t)); // 5. 初始化ADC校准使ADC能准确地将读数转换为电压值 esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_chars); } /****************************************************************** * 函数名称Get_Adc_Dma_Value * 函数说明对指定ADC通道进行多次采样并计算平均值 * 形 参CHx - 哪个通道0代表X轴1代表Y轴 * 返 回 值ADC采样平均值0-4095 ******************************************************************/ unsigned int Get_Adc_Dma_Value(char CHx) { unsigned char i 0; unsigned int AdcValue 0; int channel_to_read; // 实际要读取的ADC通道 // 根据传入的参数决定读取X轴还是Y轴 if(CHx 0) { channel_to_read x_channel; } else { channel_to_read y_channel; } // 循环采样SAMPLES次这里是30次然后累加 for(i0; i SAMPLES; i) { AdcValue adc1_get_raw(channel_to_read); } // 计算平均值 AdcValue AdcValue / SAMPLES; return AdcValue; } /****************************************************************** * 函数名称Get_Joystick_Percentage_value * 函数说明读取摇杆值并返回百分比0%~100% * 形 参dir - 0读取摇杆左右值(X轴)1读取摇杆上下值(Y轴) * 返 回 值摇杆位置百分比 ******************************************************************/ unsigned int Get_Joystick_Percentage_value(char dir) { int adc_new 0; int Percentage_value 0; // 1. 获取ADC原始值0-4095 adc_new Get_Adc_Dma_Value(dir); // 2. 将原始值转换为百分比 // 公式(当前ADC值 / 最大ADC值) * 100% Percentage_value ((float)adc_new / 4095.0) * 100; return Percentage_value; } /****************************************************************** * 函数名称Get_SW_state * 函数说明读取摇杆按键是否被按下 * 形 参无 * 返 回 值0摇杆被按下1摇杆没有按下 ******************************************************************/ char Get_SW_state(void) { // 读取SW引脚的电平 // 因为内部上拉默认未按下时为高电平(1) // 当按键按下时引脚被拉到GND变为低电平(0) if( gpio_get_level(SW_PIN) 0 ) { return 0; // 被按下 } else { return 1; // 未按下 } }关键点讲解GPIO上拉电阻在ADC_Init函数中我们配置SW引脚时启用了内部上拉电阻GPIO_PULLUP_ENABLE。这意味着当按键没有按下时引脚内部被拉高到3.3V我们读到的是1当按键按下引脚外部连接到GND0V内部上拉被“拉低”我们读到0。这是一种非常常见的按键检测电路。ADC校准esp_adc_cal_characterize这个函数很重要。ESP32的ADC有一定误差这个函数会根据我们提供的参考电压DEFAULT_VREF生成一个校准表后续可以用esp_adc_cal_raw_to_voltage函数将ADC原始值直接转换成更精确的电压值单位mV。虽然我们这个例程用的是百分比但知道这个功能对以后的项目很有帮助。平均值滤波在Get_Adc_Dma_Value函数中我们采样30次然后取平均。这是一个简单的软件滤波方法可以有效消除电源噪声或干扰带来的偶然性跳动让摇杆的读数更平滑稳定。在实际项目中这个技巧非常实用。4. 在主程序中测试驱动写好了最后一步就是在主程序里调用它看看效果。打开你的main.c文件。#include stdio.h #include bsp_joystick.h void app_main(void) { // 初始化摇杆模块包括ADC和按键GPIO ADC_Init(); printf(Joystick Demo Start......\r\n); while(1) { // 1. 读取并打印按键状态 // Get_SW_state()返回0表示按下1表示松开 printf(SW %s\r\n, ( (Get_SW_state() )?up:down) ); // 2. 读取并打印X轴位置百分比 printf(X %d%%\r\n, Get_Joystick_Percentage_value(0) ); // 3. 读取并打印Y轴位置百分比 printf(Y %d%%\r\n, Get_Joystick_Percentage_value(1) ); // 延时100毫秒再读避免打印太快看不清 vTaskDelay(100 / portTICK_PERIOD_MS); } }代码逻辑很简单调用ADC_Init()完成硬件初始化。进入死循环每隔100毫秒读取一次摇杆状态。通过串口打印出来SW显示按键是“up”松开还是“down”按下X和Y显示当前摇杆在两个方向上的位置百分比居中大约50%推到最左边/最下边接近0%推到最右边/最上边接近100%。5. 上电测试与结果分析将代码编译、下载到你的ESP32-S3开发板。打开串口调试工具比如Putty或ESP-IDF自带的Monitor设置好波特率通常是115200。上电后你应该能看到串口持续打印信息。现在可以动手玩一下摇杆了移动摇杆观察X和Y的百分比变化。将摇杆向左推到底X值应接近0%向右推到底接近100%上下移动同理。松开摇杆让它回中值应该在50%左右。按下摇杆垂直向下按压摇杆你会看到SW的状态从up变成down松开后又变回up。如果一切正常恭喜你你已经成功用ESP32-S3驱动了双轴按键摇杆模块。可能遇到的问题及排查读数跳动即使摇杆不动百分比数字也在小范围跳动。这是正常的因为ADC采样存在噪声。我们代码里已经做了30次平均滤波如果跳动还是很大可以尝试增加SAMPLES的次数或者检查一下电源是否稳定。数值范围不对比如居中时不是50%。这可能是电位器的线性度问题或者供电电压有微小偏差。如果对精度要求高可以在代码里做“校准”先读取摇杆在四个极限位置和中心位置的值然后通过一个映射函数来修正。按键读数相反如果发现按下时显示up松开时显示down检查一下硬件连接确认SW引脚是否接触良好或者模块内部的上拉/下拉逻辑是否不同。这个驱动框架非常清晰你可以轻松地将它集成到你的机器人、游戏控制器或者任何需要方向输入的项目中。希望这篇教程能帮你扫清入门路上的一个小障碍。