From f96e72dbb8f70241ac2995ccb96af3cb5b90caca Mon Sep 17 00:00:00 2001 From: kirl <18827922006@163.com> Date: Sat, 1 Aug 2026 00:40:46 +0800 Subject: [PATCH] =?UTF-8?q?webui=E6=81=A2=E5=A4=8D=E7=9B=B4=E7=BA=BFmm?= =?UTF-8?q?=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/actuator/actuator.c | 3 + components/web_ctrl/web_ctrl.c | 388 ++++++++++++++++++++++++++++++++- 2 files changed, 380 insertions(+), 11 deletions(-) diff --git a/components/actuator/actuator.c b/components/actuator/actuator.c index 0b537df..8e2e3c6 100644 --- a/components/actuator/actuator.c +++ b/components/actuator/actuator.c @@ -51,6 +51,9 @@ void actuator_init(void) /* 配置舵机限位: 抬起=80°, 放下=160° */ servo_set_limits(80.0f, 160.0f); + /* 上电默认归位到抬起状态 */ + servo_set_up(); + /* 配置 4 路电机: 闭环位置模式 + 使能 */ const uint8_t addrs[] = { ACTUATOR_ADDR_ANGLE, ACTUATOR_ADDR_X, diff --git a/components/web_ctrl/web_ctrl.c b/components/web_ctrl/web_ctrl.c index ad0dbd7..154caa6 100644 --- a/components/web_ctrl/web_ctrl.c +++ b/components/web_ctrl/web_ctrl.c @@ -8,6 +8,7 @@ #include "magnet.h" #include "servo.h" #include "protocol.h" +#include "actuator.h" #include #include @@ -118,37 +119,64 @@ button:active{opacity:.7}
-

电机使能

-
+

轴控制 (XYZA)

+
→电机2 (CW) 0~210
+
→电机3+4 (CCW) 0~335
+
→电机1 (±: CW/CCW)
+
°
+
RPM
+
- - + + +
+
+ +
+

电机使能

+
+
+ +

速度模式

-
+
RPM
- - + +

位置模式

-
+
RPM
- - + + +
+
+ +
+

角度控制

+
+
°
+
RPM
+
+
+
+ +
@@ -174,6 +202,36 @@ button:active{opacity:.7}
+
+

读取参数

+
+
+ +
+
+ +
+

回零校准 (Origin)

+
+
+
+
RPM
+
ms
+
RPM
+
mA
+
ms
+
+
+
+ + +
+
+ + +
+
+
@@ -208,6 +266,39 @@ function api(cmd){ l.innerHTML='
请求失败
'+l.innerHTML }) } +function apiEach(ep,id,ext){ + var addrs=v(id).split('|'); + for(var i=0;i'; + h+=' (raw: '+d.val32+' / 0x'+d.val32.toString(16).toUpperCase()+')'; + } + if(d.val16!==undefined && d.angle===undefined){ + h+='
'+d.val16+' (16bit)'; + } + h+='
raw: '+d.hex+''; + document.getElementById('rp_out').innerHTML = h; + }).catch(function(e){ + document.getElementById('rp_out').innerHTML='读取失败' + }); +} fetch('/api/status').then(function(r){return r.json()}).then(function(d){ document.getElementById('wifi').innerHTML='WiFi: '+d.ssid; document.getElementById('ip').textContent='IP: '+d.ip @@ -336,6 +427,20 @@ static int get_query_int(httpd_req_t *req, const char *key, int def) return def; } +static float get_query_float(httpd_req_t *req, const char *key, float def) +{ + char buf[128]; + size_t len = httpd_req_get_url_query_len(req) + 1; + if (len > sizeof(buf)) len = sizeof(buf); + if (httpd_req_get_url_query_str(req, buf, len) == ESP_OK) { + char val[16]; + if (httpd_query_key_value(buf, key, val, sizeof(val)) == ESP_OK) { + return (float)atof(val); + } + } + return def; +} + /* ============================================================ * HTTP 处理器 * ============================================================ */ @@ -479,6 +584,204 @@ static esp_err_t api_servo_handler(httpd_req_t *req) return ESP_FAIL; } +/* ---- 轴联动: XYZA 同时驱动 (mm + °) ---- */ +static esp_err_t api_axis_handler(httpd_req_t *req) +{ + float x_mm = get_query_float(req, "x", 0.0f); + float y_mm = get_query_float(req, "y", 0.0f); + float z_deg = get_query_float(req, "zrot", 0.0f); + float s_ang = get_query_float(req, "servo", -1.0f); + int vel = get_query_int(req, "vel", ACTUATOR_DEFAULT_VEL); + int acc = get_query_int(req, "acc", ACTUATOR_DEFAULT_ACC); + + actuator_motor_mm(ACTUATOR_ADDR_X, x_mm, (uint16_t)vel, (uint8_t)acc, true); + actuator_motor_mm(ACTUATOR_ADDR_Y1, y_mm, (uint16_t)vel, (uint8_t)acc, true); + actuator_motor_angle(ACTUATOR_ADDR_ANGLE, z_deg, (uint16_t)vel, (uint8_t)acc, true); + if (s_ang >= 0.0f) + actuator_servo_set_angle(s_ang); + + char resp[128]; + snprintf(resp, sizeof(resp), + "轴联动: X=%.1fmm Y=%.1fmm Z=%.1f° Servo=%.1f° vel=%d acc=%d", + (double)x_mm, (double)y_mm, (double)z_deg, (double)s_ang, vel, acc); + httpd_resp_set_type(req, "text/plain; charset=utf-8"); + httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +/* ---- 直线距离控制: mm → 脉冲 ---- */ +static esp_err_t api_mm_handler(httpd_req_t *req) +{ + int addr = get_query_int(req, "addr", 2); + float mm = get_query_float(req, "mm", 0.0f); + int vel = get_query_int(req, "vel", ACTUATOR_DEFAULT_VEL); + int acc = get_query_int(req, "acc", ACTUATOR_DEFAULT_ACC); + int raf = get_query_int(req, "raf", 1); + + actuator_motor_mm((uint8_t)addr, mm, (uint16_t)vel, (uint8_t)acc, (bool)raf); + + uint32_t clk = ACTUATOR_MM_TO_PULSES(mm); + char resp[96]; + snprintf(resp, sizeof(resp), + "直线模式: 地址%d %.2fmm → %lu脉冲 %s vel=%dRPM acc=%d", + addr, (double)mm, (unsigned long)clk, raf ? "绝对" : "相对", vel, acc); + httpd_resp_set_type(req, "text/plain; charset=utf-8"); + httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +/* ---- 角度直控: ° → 脉冲 ---- */ +static esp_err_t api_angle_handler(httpd_req_t *req) +{ + int addr = get_query_int(req, "addr", 1); + float angle = get_query_float(req, "angle", 0.0f); + int vel = get_query_int(req, "vel", ACTUATOR_DEFAULT_VEL); + int acc = get_query_int(req, "acc", ACTUATOR_DEFAULT_ACC); + int raf = get_query_int(req, "raf", 1); + + actuator_motor_angle((uint8_t)addr, angle, (uint16_t)vel, (uint8_t)acc, (bool)raf); + + uint32_t clk = EMM_V5_DEG_TO_CLK(angle); + char resp[96]; + snprintf(resp, sizeof(resp), + "角度模式: 地址%d %.1f° → %lu脉冲 %s vel=%dRPM acc=%d", + addr, (double)angle, (unsigned long)clk, raf ? "绝对" : "相对", vel, acc); + httpd_resp_set_type(req, "text/plain; charset=utf-8"); + httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +/* ---- 当前位置清零 ---- */ +static esp_err_t api_reset_pos_handler(httpd_req_t *req) +{ + int addr = get_query_int(req, "addr", 1); + Emm_V5_Reset_CurPos_To_Zero((uint8_t)addr); + char resp[32]; + snprintf(resp, sizeof(resp), "电机 %d 位置已清零", addr); + httpd_resp_set_type(req, "text/plain; charset=utf-8"); + httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +/* ---- 解除堵转保护 ---- */ +static esp_err_t api_reset_clog_handler(httpd_req_t *req) +{ + int addr = get_query_int(req, "addr", 1); + Emm_V5_Reset_Clog_Pro((uint8_t)addr); + char resp[32]; + snprintf(resp, sizeof(resp), "电机 %d 堵转保护已解除", addr); + httpd_resp_set_type(req, "text/plain; charset=utf-8"); + httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +/* ---- 读取系统参数 ---- */ +static esp_err_t api_read_param_handler(httpd_req_t *req) +{ + int addr = get_query_int(req, "addr", 1); + int param = get_query_int(req, "param", 0); + + Emm_V5_Read_Sys_Params((uint8_t)addr, (EmmSysParams_t)param); + + uint8_t rx[128]; + uint8_t len = Emm_V5_Receive_Data(rx, sizeof(rx)); + + uint32_t val32 = 0; + uint16_t val16 = 0; + int sign = 0; + float angle = 0.0f; + + if (len >= 8) { + sign = rx[2]; + val32 = ((uint32_t)rx[3] << 24) | ((uint32_t)rx[4] << 16) | + ((uint32_t)rx[5] << 8) | ((uint32_t)rx[6]); + uint16_t pos16 = (uint16_t)(val32 & 0xFFFF); + angle = (sign ? -1.0f : 1.0f) * pos16 * 360.0f / 65536.0f; + } + if (len >= 6) { + val16 = ((uint16_t)rx[3] << 8) | ((uint16_t)rx[4]); + } + + char json[512]; + int off = snprintf(json, sizeof(json), + "{\"addr\":%d,\"param\":%d,\"len\":%d,\"sign\":%d" + ",\"val32\":%lu,\"val16\":%u,\"angle\":%.2f", + addr, param, len, sign, + (unsigned long)val32, (unsigned)val16, (double)angle); + off += snprintf(json + off, sizeof(json) - off, ",\"hex\":\""); + for (int i = 0; i < len && off < (int)sizeof(json) - 4; i++) { + off += snprintf(json + off, sizeof(json) - off, "%02X", rx[i]); + } + off += snprintf(json + off, sizeof(json) - off, "\"}"); + httpd_resp_set_type(req, "application/json"); + httpd_resp_send(req, json, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +/* ---- 回零: 设置零点 ---- */ +static esp_err_t api_origin_set_handler(httpd_req_t *req) +{ + int addr = get_query_int(req, "addr", 1); + int svf = get_query_int(req, "svf", 0); + Emm_V5_Origin_Set_O((uint8_t)addr, (bool)svf); + char resp[48]; + snprintf(resp, sizeof(resp), "电机 %d 零点已设置 (svF=%d)", addr, svf); + httpd_resp_set_type(req, "text/plain; charset=utf-8"); + httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +/* ---- 回零: 配置参数 ---- */ +static esp_err_t api_origin_config_handler(httpd_req_t *req) +{ + int addr = get_query_int(req, "addr", 1); + int svf = get_query_int(req, "svf", 0); + int omode = get_query_int(req, "omode", 2); /* 默认: 多圈无限位碰撞 */ + int odir = get_query_int(req, "odir", 0); + int ovel = get_query_int(req, "ovel", 200); + int otm = get_query_int(req, "otm", 10000); + int slvel = get_query_int(req, "slvel", 100); + int slma = get_query_int(req, "slma", 500); + int slms = get_query_int(req, "slms", 100); + int potf = get_query_int(req, "potf", 0); + + Emm_V5_Origin_Modify_Params((uint8_t)addr, (bool)svf, + (uint8_t)omode, (uint8_t)odir, (uint16_t)ovel, (uint32_t)otm, + (uint16_t)slvel, (uint16_t)slma, (uint16_t)slms, (bool)potf); + char resp[128]; + snprintf(resp, sizeof(resp), + "电机 %d 回零参数已配置 (mode=%d dir=%d vel=%d tm=%lu svF=%d potF=%d)", + addr, omode, odir, ovel, (unsigned long)otm, svf, potf); + httpd_resp_set_type(req, "text/plain; charset=utf-8"); + httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +/* ---- 回零: 触发回零 ---- */ +static esp_err_t api_origin_go_handler(httpd_req_t *req) +{ + int addr = get_query_int(req, "addr", 1); + int omode = get_query_int(req, "omode", 2); /* 默认: 多圈无限位碰撞 */ + Emm_V5_Origin_Trigger_Return((uint8_t)addr, (uint8_t)omode, false); + char resp[48]; + snprintf(resp, sizeof(resp), "电机 %d 回零已触发 (mode=%d)", addr, omode); + httpd_resp_set_type(req, "text/plain; charset=utf-8"); + httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + +/* ---- 回零: 强制中断 ---- */ +static esp_err_t api_origin_stop_handler(httpd_req_t *req) +{ + int addr = get_query_int(req, "addr", 1); + Emm_V5_Origin_Interrupt((uint8_t)addr); + char resp[32]; + snprintf(resp, sizeof(resp), "电机 %d 回零已中断", addr); + httpd_resp_set_type(req, "text/plain; charset=utf-8"); + httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN); + return ESP_OK; +} + /* favicon — 204 No Content, 避免浏览器反复请求 */ static esp_err_t favicon_handler(httpd_req_t *req) { @@ -602,7 +905,7 @@ static void http_server_init(void) httpd_handle_t server = NULL; httpd_config_t cfg = HTTPD_DEFAULT_CONFIG(); cfg.stack_size = 8192; /* 默认 4096 不够, 增大 */ - cfg.max_uri_handlers = 16; + cfg.max_uri_handlers = 30; cfg.max_open_sockets = 4; cfg.lru_purge_enable = true; @@ -672,6 +975,44 @@ static void http_server_init(void) }; httpd_register_uri_handler(server, &uri_servo); + /* ---- 轴联动 + 直线/角度 ---- */ + httpd_uri_t uri_axis = { + .uri = "/api/axis", .method = HTTP_GET, + .handler = api_axis_handler, .user_ctx = NULL + }; + httpd_register_uri_handler(server, &uri_axis); + + httpd_uri_t uri_mm = { + .uri = "/api/mm", .method = HTTP_GET, + .handler = api_mm_handler, .user_ctx = NULL + }; + httpd_register_uri_handler(server, &uri_mm); + + httpd_uri_t uri_angle = { + .uri = "/api/angle", .method = HTTP_GET, + .handler = api_angle_handler, .user_ctx = NULL + }; + httpd_register_uri_handler(server, &uri_angle); + + /* ---- 底层调试 API ---- */ + httpd_uri_t uri_reset_pos = { + .uri = "/api/reset_pos", .method = HTTP_GET, + .handler = api_reset_pos_handler, .user_ctx = NULL + }; + httpd_register_uri_handler(server, &uri_reset_pos); + + httpd_uri_t uri_reset_clog = { + .uri = "/api/reset_clog", .method = HTTP_GET, + .handler = api_reset_clog_handler, .user_ctx = NULL + }; + httpd_register_uri_handler(server, &uri_reset_clog); + + httpd_uri_t uri_read_param = { + .uri = "/api/read_param", .method = HTTP_GET, + .handler = api_read_param_handler, .user_ctx = NULL + }; + httpd_register_uri_handler(server, &uri_read_param); + /* favicon.ico — 返回空, 避免 404 警告 */ httpd_uri_t uri_favicon = { .uri = "/favicon.ico", @@ -681,6 +1022,31 @@ static void http_server_init(void) }; httpd_register_uri_handler(server, &uri_favicon); + /* ---- 回零校准 API ---- */ + httpd_uri_t uri_origin_set = { + .uri = "/api/origin_set", .method = HTTP_GET, + .handler = api_origin_set_handler, .user_ctx = NULL + }; + httpd_register_uri_handler(server, &uri_origin_set); + + httpd_uri_t uri_origin_config = { + .uri = "/api/origin_config", .method = HTTP_GET, + .handler = api_origin_config_handler, .user_ctx = NULL + }; + httpd_register_uri_handler(server, &uri_origin_config); + + httpd_uri_t uri_origin_go = { + .uri = "/api/origin_go", .method = HTTP_GET, + .handler = api_origin_go_handler, .user_ctx = NULL + }; + httpd_register_uri_handler(server, &uri_origin_go); + + httpd_uri_t uri_origin_stop = { + .uri = "/api/origin_stop", .method = HTTP_GET, + .handler = api_origin_stop_handler, .user_ctx = NULL + }; + httpd_register_uri_handler(server, &uri_origin_stop); + /* ---- 协议状态 API ---- */ httpd_uri_t uri_proto_status = { .uri = "/api/proto/status",