ESP32 FOC Knob 触觉反馈旋钮组件实战指南
2026/9/20 15:11:56 网站建设 项目流程
  • 物联网
  • 嵌入式
  • 驱动开发
  • 硬件开发

【免费下载链接】esp-iot-solution

Espressif IoT Library. IoT Device Drivers, Documentations and Solutions.

项目地址:https://gitcode.com/GitHub_Trending/es/esp-iot-solution
点击查看免费下载

FOC Knob 是 ESP-IoT-Solution 中基于磁场定向控制(FOC)电机实现"触觉反馈旋钮"交互的组件,它通过无刷直流(BLDC)电机输出阻尼、卡位(detent)、限位等可编程力矩,让传统旋钮具备多种手感模式。本文以 foc_knob 组件文档 为核心骨架,结合组件源码与 FOC_knob_example 完整示例,带你掌握 7 种默认旋钮模式、参数定制方法、完整 API 调用方式以及基于 ESP32-S3-Motor-LCDkit 的从零构建流程。

FOC Knob 是什么

FOC Knob 是一个提供丰富 API 的组件,允许开发者定义多种工作模式(Modes of Operation),并灵活选用不同类型的 BLDC 电机,来实现具有触觉反馈(Haptic Feedback)的旋钮。其核心思想是:用电机代替机械阻尼结构——通过实时读取电机轴角度与速度,计算当前应施加的力矩,让旋钮在不同角度区间表现出"无阻尼顺滑"、"细密卡位"、"强卡位"、"两端限位"或"开关式"等不同手感。

组件默认模式定义在foc_knob_default.c文件中,用户可以直接使用这些默认模式,也可以按需修改参数以获得不同响应。组件的公开接口声明位于 foc_knob.h,C 语言实现位于 foc_knob.c。

支持的旋钮模式

默认情况下,组件在 foc_knob_default.c 中预定义了 7 种模式,对应模式编号与含义如下:

Mode描述
0. unbound no detents无边界,细顺滑旋转,无卡位点
1. Unbounded Fine detents无边界,细顺滑旋转,细密卡位点
2. Unbounded Strong detents无边界,细顺滑旋转,明显的卡位点
3. Bounded No detents限定在特定范围(0-360)内,细顺滑旋转,无卡位点
4. Bounded Fine detents限定范围内,较粗的旋转,细卡位点
5. Bounded Strong detents限定范围内,较粗的旋转,强卡位点
6. On/Off strong detents限定范围内,带明显停止点的 On/Off 开关式功能

模式编号的枚举定义在 foc_knob_default.h 中,从MOTOR_UNBOUND_NO_DETENTSMOTOR_ON_OFF_STRONG_DETENTS,并以MOTOR_MAX_MODES作为边界哨兵。

参数结构与自定义模式

每种模式都由一组参数描述。用户可以在下面的结构体中修改数值,以按需定制旋钮功能(该结构体定义见 foc_knob.h):

typedef struct { int32_t num_positions; // 位置数量 int32_t position; // 当前位置 float position_width_radians; // 每个位置的宽度(弧度) float detent_strength_unit; // 正常旋转时的卡位强度 float endstop_strength_unit; // 到达端点时的卡位强度 float snap_point; // 每个位置的吸附点 const char *descriptor; // 描述 } foc_knob_param_t;

各字段的语义与用法如下:

  • num_positions:位置总数。取0或负值表示"无边界"(unbounded)模式,电机可无限旋转;取正值表示"有边界"(bounded)模式,旋钮被限制在0 ~ num_positions-1个位置区间内。
  • position:该模式的初始位置(index)。
  • position_width_radians:单个位置对应的角度宽度,单位为弧度。该值直接决定每个卡位之间的角度间距,例如5 * PI / 180表示每 5° 一个卡位。
  • detent_strength_unit:正常旋转过程中,将旋钮"拉回"卡位中心所施加的力矩强度(与 P 系数成正比,见下文力矩计算)。
  • endstop_strength_unit:到达边界时阻止继续旋转的力矩强度。
  • snap_point:吸附点阈值系数,决定角度偏离卡位中心多大比例时会"跳"到下一个卡位。
  • descriptor:人类可读的描述字符串,可通过foc_knob_get_state()读取。

默认模式的参数取值

foc_knob_default.c 中 7 种默认模式的完整参数如下,可作为自定义模式的参考基线:

static const foc_knob_param_t unbound_no_detents[] = { {0, 0, 1 * PI / 180, 1, 0, 1.1, "Unbounded\nNo detents"}, }; static const foc_knob_param_t unbound_fine_detents[] = { {0, 0, 5 * PI / 180, 0.8, 0, 1.1, "Unbounded\nFine detents"}, }; static const foc_knob_param_t unbound_coarse_detents[] = { {0, 0, 10 * PI / 180, 1.2, 0, 1.1, "Unbounded\nStrong detents"}, }; static const foc_knob_param_t bound_no_detents[] = { {360, 180, 1 * PI / 180, 0.3, 1, 1.1, "Bounded\nNo detents"}, }; static const foc_knob_param_t bound_fine_dents[] = { {32, 0, 5 * PI / 180, 0.6, 1, 1.1, "Bounded\nFine detents"}, }; static const foc_knob_param_t bound_coarse_dents[] = { {13, 0, 10 * PI / 180, 1.1, 1, 0.55, "Bounded\nStrong detents"}, }; static const foc_knob_param_t on_off_strong_dents[] = { { 2, 0, 60 * PI / 180, 1, 1, 0.55, "On/Off strong detents"}, };

这些参数数组通过default_foc_knob_param_lst[]指针数组与模式编号一一对应,DEFAULT_PARAM_LIST_NUM由数组大小推导得出(见 foc_knob_default.c)。从默认值可以观察到设计规律:

  • 无边界模式num_positions = 0,因此不存在限位;有边界模式则根据position_width_radians反推位置数(如 360 位置 × 1°/位)。
  • endstop_strength_unit只在有边界模式中非零,作为边界"软墙"强度。
  • 强卡位模式(如bound_coarse_dents)的snap_point更低(0.55),吸附更灵敏,手感更干脆。

组件 API 详解

组件的完整 API 均声明在 foc_knob.h,核心接口如下。

创建与销毁

foc_knob_handle_t foc_knob_create(const foc_knob_config_t *config); esp_err_t foc_knob_delete(foc_knob_handle_t handle);

foc_knob_create()要求传入非空的config且必须提供 PID 回调函数pid_cb,否则返回 NULL(见 foc_knob.c)。配置结构体为:

typedef struct { foc_knob_param_t const **param_lists; /*!< 模式参数列表,若为 NULL 则使用默认列表 */ uint16_t param_list_num; /*!< 模式数量 */ float max_torque_out_limit; /*!< 最大输出力矩限制(越界时) */ float max_torque; /*!< 最大力矩限制 */ foc_knob_pid_cb_t pid_cb; /*!< PID 回调函数 */ } foc_knob_config_t;

param_lists为 NULL 时,组件会打印日志并自动使用default_foc_knob_param_lstDEFAULT_PARAM_LIST_NUM。内部会为每个模式分别分配一份位置记录(position[]),并创建互斥量保证线程安全。

运行与模式切换

float foc_knob_run(foc_knob_handle_t handle, float shaft_velocity, float shaft_angle); esp_err_t foc_knob_change_mode(foc_knob_handle_t handle, uint16_t mode);
  • foc_knob_run()每个控制周期必须调用一次的核心函数,输入当前轴速度(rad/s)与轴角度(rad),返回应施加到电机上的力矩(torque)。示例应用在motor_task中以约 1 ms 周期调用(见 main.cpp)。
  • foc_knob_change_mode()用于运行中动态切换模式;若mode超出模式数量或对应模式参数为 NULL,返回ESP_ERR_INVALID_ARG

事件回调

组件通过事件机制通知应用层"发生了什么",事件枚举与注册接口如下:

typedef enum { FOC_KNOB_INC = 0, /*!< 位置增加 */ FOC_KNOB_DEC, /*!< 位置减少 */ FOC_KNOB_H_LIM, /*!< 计数达到最大上限 */ FOC_KNOB_L_LIM, /*!< 计数达到最小下限 */ FOC_KNOB_ANGLE_CHANGE, /*!< 角度变化 */ FOC_KNOB_EVENT_MAX, /*!< 事件总数(哨兵) */ } foc_knob_event_t; esp_err_t foc_knob_register_cb(foc_knob_handle_t handle, foc_knob_event_t event, foc_knob_cb_t cb, void *usr_data); esp_err_t foc_knob_unregister_cb(foc_knob_handle_t handle, foc_knob_event_t event);

回调类型为void (*)(void *foc_knob_handle, void *user_data)。示例中通过foc_knob_inc_cb/foc_knob_dec_cb读取状态、foc_knob_h_lim_cb/foc_knob_l_lim_cb打印到达边界信息(见 main.cpp)。

状态与位置查询

esp_err_t foc_knob_get_state(foc_knob_handle_t handle, foc_knob_state_t *state); esp_err_t foc_knob_get_event(foc_knob_handle_t handle, foc_knob_event_t *event); esp_err_t foc_knob_get_current_mode_position(foc_knob_handle_t handle, int32_t *position); esp_err_t foc_knob_set_currect_mode_position(foc_knob_handle_t handle, int32_t position); esp_err_t foc_knob_get_position(foc_knob_handle_t handle, uint16_t mode, int32_t *position); esp_err_t foc_knob_set_position(foc_knob_handle_t handle, uint16_t mode, int32_t position);

其中foc_knob_state_t是对当前模式的实时快照,包含angle_to_detent_center(当前角度与卡位中心的偏差)、position_width_radiansnum_positionspositiondescriptor。注意set_position系列接口会同时将角度偏差清零并重置中心校准标志center_adjusted,使旋钮从新位置重新校准(见 foc_knob.c)。

底层力矩计算原理

foc_knob_run()的内部流程(见 foc_knob.c)可分解为五个关键步骤,理解这些步骤有助于调出自定义手感:

  1. 中心校准:首次运行(或模式切换后)把当前角度记录为卡位中心current_detent_center
  2. 静止漂移修正:通过对轴速度做 EWMA(指数加权移动平均)滤波判断旋钮是否静止;静止超过 500 ms 且角度偏差小于 5° 时,缓慢地把卡位中心向当前角度靠拢,避免传感器零点漂移导致的手感偏移。
  3. 吸附点(Snap Point)判定:当角度偏差超过position_width_radians * snap_point时,判定越过卡位边界,位置position相应增减并触发FOC_KNOB_DEC/FOC_KNOB_INC事件;到达边界位置时额外触发FOC_KNOB_L_LIM/FOC_KNOB_H_LIM
  4. 死区处理:将角度偏差钳制在卡位宽度 20%(且不超过 1°)的死区内,避免在卡位中心附近因传感器噪声产生抖动。
  5. PID 力矩输出:通过用户提供的pid_cb(P, D, limit, error)计算最终力矩。其中越界时P使用endstop_strength_unit * 4、限制为max_torque_out_limit,否则使用detent_strength_unit * 4、限制为max_torqueD系数则按卡位宽度在 3°~8° 之间做分段线性缩放——细卡位需要较大 D 系数以产生清晰的"咔哒"感,粗卡位则降低 D 系数以避免放大力矩噪声。当轴速度超过CONFIG_FOC_KNOB_MAX_VELOCITY时直接输出 0 力矩,防止高速旋转时力矩失控。

示例应用中的motor_pid_cb把 P/D/limit 直接注入 SimpleFOC 的motor.PID_velocity并返回其输出(见 main.cpp),实现"力矩环由 FOC Knob 组件调控、FOC 底层由 SimpleFOC 驱动"的分层架构。

硬件要求

本示例专为ESP32-S3-Motor-LCDkit开发板设计,配套硬件如下:

  1. BLDC 电机:兼容 5V 2804 型号无刷电机(示例使用 7 对极电机,见 main.cpp)。
  2. 位置传感器:适合使用基于霍尔效应的传感器,如 MT6701 或 AS5600。示例通过 SPI 接口连接 MT6701(SCLK=GPIO12、MISO=GPIO13、CS=GPIO11,见 main.cpp)。

示例还使用按键(GPIO0)切换模式:每次按下,模式从MOTOR_UNBOUND_NO_DETENTS循环递增到下一个模式,并触发一次短暂的"电机抖动"反馈(motor_shake_func),见 main.cpp。

从零构建与烧录

以 ESP32-S3 为目标芯片,完整构建流程如下(详见 示例 README):

第 1 步:设置目标芯片

idf.py set-target esp32s3

第 2 步:编译工程

idf.py build

第 3 步:烧录并启动 IDF Monitor

idf.py flash monitor

工程默认配置位于 sdkconfig.defaults,已启用 240 MHz CPU 主频并将 FreeRTOS tick 设为 1000 Hz(1 ms 节拍),与motor_task的 1 ms 控制周期相匹配。示例应用同时依赖esp-simplefoc(电机 FOC 驱动)与iot_button(模式切换按键)两个组件,可在 main/CMakeLists.txt 与组件清单中查看依赖关系。

可调配置项

组件通过 Kconfig 提供一项全局配置(见 Kconfig):

CONFIG_FOC_KNOB_MAX_VELOCITY

取值范围1 ~ 100,默认60。该值表示旋钮的最大允许轴速度(rad/s),超过该速度时组件输出零力矩。Kconfig 帮助信息特别提示:最大速度设得越小,旋钮被甩动后的回弹速度也越小,手感更柔和,适合调整旋钮的"惯性"表现。可通过idf.py menuconfig进入 "FOC Knob" 菜单修改。

小结

FOC Knob 组件把"旋钮手感"抽象成了foc_knob_param_t参数与 7 种内置模式的组合:num_positions决定有界/无界,position_width_radians决定卡位密度,detent_strength_unitendstop_strength_unit分别决定旋转手感和边界硬度,snap_point决定吸附灵敏度。借助foc_knob_run()的周期调用与事件回调机制,开发者无需关心 FOC 底层细节,即可在 ESP32-S3 上快速实现音量旋钮、菜单选择器、温控拨盘、开关切换等各类触觉反馈交互界面。如需进一步理解实现细节,建议通读 foc_knob.c 的力矩计算主循环,并结合 main.cpp 观察完整的应用集成方式。

  • 物联网
  • 嵌入式
  • 驱动开发
  • 硬件开发

【免费下载链接】esp-iot-solution

Espressif IoT Library. IoT Device Drivers, Documentations and Solutions.

项目地址:https://gitcode.com/GitHub_Trending/es/esp-iot-solution
点击查看免费下载

相关推荐

上一篇:用 CocoIndex 增量索引 Entire AI 编码会话:构建可语义搜索的个人开发历史
下一篇:昇腾 NPU 算子性能优化实战:使用 unit_flag 指令级标志位实现 MMAD 计算与 FixPipe 搬出流水并行

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询