|
|
@@ -1,5 +1,7 @@
|
|
|
#include <errno.h>
|
|
|
#include <inttypes.h>
|
|
|
+#include <math.h>
|
|
|
+#include <stdio.h>
|
|
|
#include <string.h>
|
|
|
#include <unistd.h>
|
|
|
|
|
|
@@ -91,10 +93,231 @@ static float motor_to_joint_angle(int index, float motor_angle)
|
|
|
return (motor_angle - joint->zero_offset_rad) * joint->direction;
|
|
|
}
|
|
|
|
|
|
+static bool feedback_is_safe_for_arm(void)
|
|
|
+{
|
|
|
+ const uint32_t current_ms = now_ms();
|
|
|
+ static uint32_t last_log_ms = 0;
|
|
|
+ const bool should_log = current_ms - last_log_ms > 1000;
|
|
|
+ bool safe = true;
|
|
|
+
|
|
|
+ xSemaphoreTake(g_feedback_mutex, portMAX_DELAY);
|
|
|
+ for (int i = 0; i < GUGUJI_JOINT_COUNT; ++i) {
|
|
|
+ const guguji_joint_config_t *joint = &GUGUJI_JOINTS[i];
|
|
|
+ const rs00_feedback_t feedback = g_feedback[i];
|
|
|
+ const bool feedback_stale =
|
|
|
+ feedback.last_feedback_ms == 0 ||
|
|
|
+ current_ms - feedback.last_feedback_ms > GUGUJI_MOTOR_FEEDBACK_TIMEOUT_MS;
|
|
|
+ const float joint_angle = motor_to_joint_angle(i, feedback.position_rad);
|
|
|
+ const bool outside_limit =
|
|
|
+ joint_angle < joint->lower_limit_rad - GUGUJI_ARM_POSITION_LIMIT_MARGIN_RAD ||
|
|
|
+ joint_angle > joint->upper_limit_rad + GUGUJI_ARM_POSITION_LIMIT_MARGIN_RAD;
|
|
|
+
|
|
|
+ if (!feedback_stale && !outside_limit) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ safe = false;
|
|
|
+ if (should_log) {
|
|
|
+ if (feedback_stale) {
|
|
|
+ ESP_LOGE(TAG,
|
|
|
+ "拒绝使能: joint=%s can_id=%u 反馈过期或尚未收到",
|
|
|
+ joint->name,
|
|
|
+ joint->can_id);
|
|
|
+ } else {
|
|
|
+ ESP_LOGE(TAG,
|
|
|
+ "拒绝使能: joint=%s can_id=%u 当前关节角 %.3f rad 超出限位 %.3f~%.3f rad "
|
|
|
+ "(motor=%.3f zero=%.3f direction=%.1f)",
|
|
|
+ joint->name,
|
|
|
+ joint->can_id,
|
|
|
+ joint_angle,
|
|
|
+ joint->lower_limit_rad,
|
|
|
+ joint->upper_limit_rad,
|
|
|
+ feedback.position_rad,
|
|
|
+ joint->zero_offset_rad,
|
|
|
+ joint->direction);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ xSemaphoreGive(g_feedback_mutex);
|
|
|
+
|
|
|
+ if (!safe && should_log) {
|
|
|
+ last_log_ms = current_ms;
|
|
|
+ }
|
|
|
+ return safe;
|
|
|
+}
|
|
|
+
|
|
|
+static bool command_targets_are_near_feedback(const guguji_command_packet_t *command)
|
|
|
+{
|
|
|
+ const uint32_t current_ms = now_ms();
|
|
|
+ static uint32_t last_log_ms = 0;
|
|
|
+ const bool should_log = current_ms - last_log_ms > 1000;
|
|
|
+ bool safe = true;
|
|
|
+
|
|
|
+ xSemaphoreTake(g_feedback_mutex, portMAX_DELAY);
|
|
|
+ for (int i = 0; i < GUGUJI_JOINT_COUNT; ++i) {
|
|
|
+ const guguji_joint_config_t *joint = &GUGUJI_JOINTS[i];
|
|
|
+ const rs00_feedback_t feedback = g_feedback[i];
|
|
|
+ const bool feedback_stale =
|
|
|
+ feedback.last_feedback_ms == 0 ||
|
|
|
+ current_ms - feedback.last_feedback_ms > GUGUJI_MOTOR_FEEDBACK_TIMEOUT_MS;
|
|
|
+ if (feedback_stale) {
|
|
|
+ safe = false;
|
|
|
+ if (should_log) {
|
|
|
+ ESP_LOGE(TAG,
|
|
|
+ "拒绝控制: joint=%s can_id=%u 反馈过期或尚未收到",
|
|
|
+ joint->name,
|
|
|
+ joint->can_id);
|
|
|
+ }
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const float current_joint_angle = motor_to_joint_angle(i, feedback.position_rad);
|
|
|
+ const float requested_joint_target = command->positions_rad[i];
|
|
|
+ const float clamped_joint_target = clampf_local(
|
|
|
+ requested_joint_target,
|
|
|
+ joint->lower_limit_rad,
|
|
|
+ joint->upper_limit_rad);
|
|
|
+ const float error = fabsf(clamped_joint_target - current_joint_angle);
|
|
|
+ if (error <= GUGUJI_MAX_COMMAND_POSITION_ERROR_RAD) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ safe = false;
|
|
|
+ if (should_log) {
|
|
|
+ ESP_LOGE(TAG,
|
|
|
+ "拒绝控制: joint=%s can_id=%u 目标跳变过大 target=%.3f current=%.3f "
|
|
|
+ "error=%.3f max=%.3f",
|
|
|
+ joint->name,
|
|
|
+ joint->can_id,
|
|
|
+ clamped_joint_target,
|
|
|
+ current_joint_angle,
|
|
|
+ error,
|
|
|
+ GUGUJI_MAX_COMMAND_POSITION_ERROR_RAD);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ xSemaphoreGive(g_feedback_mutex);
|
|
|
+
|
|
|
+ if (!safe && should_log) {
|
|
|
+ last_log_ms = current_ms;
|
|
|
+ }
|
|
|
+ return safe;
|
|
|
+}
|
|
|
+
|
|
|
+static const char *twai_state_name(int state)
|
|
|
+{
|
|
|
+ switch (state) {
|
|
|
+ case 0:
|
|
|
+ return "active";
|
|
|
+ case 1:
|
|
|
+ return "warning";
|
|
|
+ case 2:
|
|
|
+ return "passive";
|
|
|
+ case 3:
|
|
|
+ return "bus_off";
|
|
|
+ default:
|
|
|
+ return "unknown";
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void build_joint_mask_names(uint32_t joint_mask, char *buffer, size_t buffer_size)
|
|
|
+{
|
|
|
+ if (buffer_size == 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ size_t offset = 0;
|
|
|
+ buffer[0] = '\0';
|
|
|
+ for (int i = 0; i < GUGUJI_JOINT_COUNT; ++i) {
|
|
|
+ if ((joint_mask & (1u << i)) == 0) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const int written = snprintf(
|
|
|
+ buffer + offset,
|
|
|
+ buffer_size - offset,
|
|
|
+ "%s%s",
|
|
|
+ offset > 0 ? "," : "",
|
|
|
+ GUGUJI_JOINTS[i].name);
|
|
|
+ if (written < 0) {
|
|
|
+ buffer[0] = '\0';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if ((size_t)written >= buffer_size - offset) {
|
|
|
+ buffer[buffer_size - 1] = '\0';
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ offset += (size_t)written;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (offset == 0) {
|
|
|
+ snprintf(buffer, buffer_size, "none");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void log_fault_mask_if_changed(uint32_t fault_mask)
|
|
|
+{
|
|
|
+ static uint32_t last_logged_fault_mask = UINT32_MAX;
|
|
|
+ if (fault_mask == last_logged_fault_mask) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ last_logged_fault_mask = fault_mask;
|
|
|
+
|
|
|
+ if (fault_mask == 0) {
|
|
|
+ ESP_LOGI(TAG, "故障状态恢复: fault_mask=0x%08" PRIX32, fault_mask);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ char motor_faults[192] = {0};
|
|
|
+ char feedback_timeouts[192] = {0};
|
|
|
+ build_joint_mask_names(fault_mask & GUGUJI_FAULT_MOTOR_MASK, motor_faults, sizeof(motor_faults));
|
|
|
+ build_joint_mask_names((fault_mask & GUGUJI_FAULT_FEEDBACK_TIMEOUT_MASK) >> 8, feedback_timeouts, sizeof(feedback_timeouts));
|
|
|
+
|
|
|
+ ESP_LOGW(
|
|
|
+ TAG,
|
|
|
+ "故障状态变化: fault_mask=0x%08" PRIX32
|
|
|
+ " motor_fault=%s feedback_timeout=%s imu_offline=%s mag_offline=%s",
|
|
|
+ fault_mask,
|
|
|
+ motor_faults,
|
|
|
+ feedback_timeouts,
|
|
|
+ (fault_mask & GUGUJI_FAULT_IMU_OFFLINE) != 0 ? "yes" : "no",
|
|
|
+ (fault_mask & GUGUJI_FAULT_MAG_OFFLINE) != 0 ? "yes" : "no");
|
|
|
+}
|
|
|
+
|
|
|
static void stop_all_motors(void)
|
|
|
{
|
|
|
+ static uint32_t last_error_log_ms = 0;
|
|
|
+ const uint32_t current_ms = now_ms();
|
|
|
+ const bool log_errors = current_ms - last_error_log_ms > 1000;
|
|
|
+ bool logged_error = false;
|
|
|
+
|
|
|
for (int i = 0; i < GUGUJI_JOINT_COUNT; ++i) {
|
|
|
- rs00_mit_send_stop(GUGUJI_JOINTS[i].can_id);
|
|
|
+ const esp_err_t err = rs00_mit_send_stop(GUGUJI_JOINTS[i].can_id);
|
|
|
+ if (err != ESP_OK && log_errors) {
|
|
|
+ ESP_LOGW(TAG, "发送停止帧失败: joint=%s can_id=%u err=%s",
|
|
|
+ GUGUJI_JOINTS[i].name,
|
|
|
+ GUGUJI_JOINTS[i].can_id,
|
|
|
+ esp_err_to_name(err));
|
|
|
+ logged_error = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (logged_error) {
|
|
|
+ last_error_log_ms = current_ms;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void request_active_reports(bool enabled)
|
|
|
+{
|
|
|
+ ESP_LOGI(TAG, "%s RS00 主动上报", enabled ? "开启" : "关闭");
|
|
|
+ for (int i = 0; i < GUGUJI_JOINT_COUNT; ++i) {
|
|
|
+ const esp_err_t err = rs00_mit_send_active_report(GUGUJI_JOINTS[i].can_id, enabled);
|
|
|
+ if (err != ESP_OK) {
|
|
|
+ ESP_LOGW(TAG, "发送主动上报配置失败: joint=%s can_id=%u enabled=%d err=%s",
|
|
|
+ GUGUJI_JOINTS[i].name,
|
|
|
+ GUGUJI_JOINTS[i].can_id,
|
|
|
+ enabled ? 1 : 0,
|
|
|
+ esp_err_to_name(err));
|
|
|
+ }
|
|
|
+ vTaskDelay(pdMS_TO_TICKS(2));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -118,7 +341,13 @@ static bool prepare_for_ota(void *context)
|
|
|
static void clear_all_motor_faults(void)
|
|
|
{
|
|
|
for (int i = 0; i < GUGUJI_JOINT_COUNT; ++i) {
|
|
|
- rs00_mit_send_clear_fault(GUGUJI_JOINTS[i].can_id);
|
|
|
+ const esp_err_t err = rs00_mit_send_clear_fault(GUGUJI_JOINTS[i].can_id);
|
|
|
+ if (err != ESP_OK) {
|
|
|
+ ESP_LOGW(TAG, "发送清错帧失败: joint=%s can_id=%u err=%s",
|
|
|
+ GUGUJI_JOINTS[i].name,
|
|
|
+ GUGUJI_JOINTS[i].can_id,
|
|
|
+ esp_err_to_name(err));
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -126,9 +355,21 @@ static void enable_all_motors(void)
|
|
|
{
|
|
|
for (int i = 0; i < GUGUJI_JOINT_COUNT; ++i) {
|
|
|
// RS00 MIT 协议中 0 表示 MIT 运控模式;上电默认也是 MIT,这里再写一次便于调试。
|
|
|
- rs00_mit_send_set_mode(GUGUJI_JOINTS[i].can_id, 0);
|
|
|
+ esp_err_t err = rs00_mit_send_set_mode(GUGUJI_JOINTS[i].can_id, 0);
|
|
|
+ if (err != ESP_OK) {
|
|
|
+ ESP_LOGW(TAG, "发送 MIT 模式帧失败: joint=%s can_id=%u err=%s",
|
|
|
+ GUGUJI_JOINTS[i].name,
|
|
|
+ GUGUJI_JOINTS[i].can_id,
|
|
|
+ esp_err_to_name(err));
|
|
|
+ }
|
|
|
vTaskDelay(pdMS_TO_TICKS(2));
|
|
|
- rs00_mit_send_enable(GUGUJI_JOINTS[i].can_id);
|
|
|
+ err = rs00_mit_send_enable(GUGUJI_JOINTS[i].can_id);
|
|
|
+ if (err != ESP_OK) {
|
|
|
+ ESP_LOGW(TAG, "发送使能帧失败: joint=%s can_id=%u err=%s",
|
|
|
+ GUGUJI_JOINTS[i].name,
|
|
|
+ GUGUJI_JOINTS[i].can_id,
|
|
|
+ esp_err_to_name(err));
|
|
|
+ }
|
|
|
vTaskDelay(pdMS_TO_TICKS(2));
|
|
|
}
|
|
|
}
|
|
|
@@ -272,6 +513,7 @@ static void udp_receive_task(void *arg)
|
|
|
static void can_feedback_task(void *arg)
|
|
|
{
|
|
|
(void)arg;
|
|
|
+ uint32_t last_unknown_id_log_ms = 0;
|
|
|
while (true) {
|
|
|
rs00_feedback_t feedback = {0};
|
|
|
if (!rs00_mit_poll_feedback(&feedback, 10)) {
|
|
|
@@ -279,6 +521,12 @@ static void can_feedback_task(void *arg)
|
|
|
}
|
|
|
const int joint_index = find_joint_by_motor_id(feedback.motor_id);
|
|
|
if (joint_index < 0) {
|
|
|
+ const uint32_t current_ms = now_ms();
|
|
|
+ if (current_ms - last_unknown_id_log_ms > 1000) {
|
|
|
+ ESP_LOGW(TAG, "收到未配置的 RS00 反馈: motor_id=%u,请检查 robot_config.h CAN ID",
|
|
|
+ feedback.motor_id);
|
|
|
+ last_unknown_id_log_ms = current_ms;
|
|
|
+ }
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
@@ -288,6 +536,53 @@ static void can_feedback_task(void *arg)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+static void can_diagnostics_task(void *arg)
|
|
|
+{
|
|
|
+ (void)arg;
|
|
|
+ while (true) {
|
|
|
+ rs00_mit_diagnostics_t diagnostics = {0};
|
|
|
+ const esp_err_t err = rs00_mit_get_diagnostics(&diagnostics);
|
|
|
+ if (err == ESP_OK) {
|
|
|
+ char id_counts[160] = {0};
|
|
|
+ size_t offset = 0;
|
|
|
+ for (int i = 0; i < GUGUJI_JOINT_COUNT; ++i) {
|
|
|
+ const uint8_t can_id = GUGUJI_JOINTS[i].can_id;
|
|
|
+ const uint32_t count = can_id <= RS00_MIT_TRACKED_ID_MAX
|
|
|
+ ? diagnostics.feedback_count_by_id[can_id]
|
|
|
+ : 0;
|
|
|
+ const int written = snprintf(
|
|
|
+ id_counts + offset,
|
|
|
+ sizeof(id_counts) - offset,
|
|
|
+ "%s%u:%" PRIu32,
|
|
|
+ i > 0 ? " " : "",
|
|
|
+ can_id,
|
|
|
+ count);
|
|
|
+ if (written < 0 || (size_t)written >= sizeof(id_counts) - offset) {
|
|
|
+ id_counts[sizeof(id_counts) - 1] = '\0';
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ offset += (size_t)written;
|
|
|
+ }
|
|
|
+
|
|
|
+ ESP_LOGI(TAG,
|
|
|
+ "CAN诊断: state=%s tx_err=%u rx_err=%u bus_err=%" PRIu32
|
|
|
+ " raw_rx=%" PRIu32 " parsed_feedback=%" PRIu32 " invalid_rx=%" PRIu32
|
|
|
+ " id_counts=[%s]",
|
|
|
+ twai_state_name(diagnostics.twai_state),
|
|
|
+ diagnostics.tx_error_count,
|
|
|
+ diagnostics.rx_error_count,
|
|
|
+ diagnostics.bus_error_count,
|
|
|
+ diagnostics.raw_rx_count,
|
|
|
+ diagnostics.parsed_feedback_count,
|
|
|
+ diagnostics.invalid_rx_count,
|
|
|
+ id_counts);
|
|
|
+ } else {
|
|
|
+ ESP_LOGW(TAG, "读取 CAN 诊断失败: %s", esp_err_to_name(err));
|
|
|
+ }
|
|
|
+ vTaskDelay(pdMS_TO_TICKS(2000));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
static void sensor_task(void *arg)
|
|
|
{
|
|
|
(void)arg;
|
|
|
@@ -310,6 +605,7 @@ static void control_task(void *arg)
|
|
|
(void)arg;
|
|
|
bool motors_enabled = false;
|
|
|
uint32_t last_clear_fault_sequence = UINT32_MAX;
|
|
|
+ uint32_t last_disarm_feedback_poll_ms = 0;
|
|
|
|
|
|
while (true) {
|
|
|
if (guguji_ota_is_active()) {
|
|
|
@@ -335,11 +631,28 @@ static void control_task(void *arg)
|
|
|
xSemaphoreGive(g_command_mutex);
|
|
|
|
|
|
if (!have_command || command_age_ms > GUGUJI_COMMAND_TIMEOUT_MS) {
|
|
|
+ const bool stale_command_was_estop =
|
|
|
+ have_command && (command.flags & GUGUJI_COMMAND_FLAG_ESTOP) != 0;
|
|
|
+ const bool stale_command_was_armed =
|
|
|
+ have_command &&
|
|
|
+ (command.flags & GUGUJI_COMMAND_FLAG_ARM) != 0 &&
|
|
|
+ (command.flags & GUGUJI_COMMAND_FLAG_DISARM) == 0 &&
|
|
|
+ !stale_command_was_estop;
|
|
|
+ const bool motors_were_enabled = motors_enabled;
|
|
|
if (motors_enabled) {
|
|
|
stop_all_motors();
|
|
|
motors_enabled = false;
|
|
|
}
|
|
|
- g_robot_state = have_command ? GUGUJI_STATE_TIMEOUT : GUGUJI_STATE_DISARMED;
|
|
|
+ if (!have_command) {
|
|
|
+ g_robot_state = GUGUJI_STATE_DISARMED;
|
|
|
+ } else if (stale_command_was_estop) {
|
|
|
+ g_robot_state = GUGUJI_STATE_ESTOP;
|
|
|
+ } else if (stale_command_was_armed || motors_were_enabled) {
|
|
|
+ g_robot_state = GUGUJI_STATE_TIMEOUT;
|
|
|
+ } else {
|
|
|
+ // 通信测试或主动失能后,主机停止发送保持包属于正常结束,不触发黄色超时报警。
|
|
|
+ g_robot_state = GUGUJI_STATE_DISARMED;
|
|
|
+ }
|
|
|
vTaskDelay(pdMS_TO_TICKS(GUGUJI_CONTROL_PERIOD_MS));
|
|
|
continue;
|
|
|
}
|
|
|
@@ -364,11 +677,27 @@ static void control_task(void *arg)
|
|
|
stop_all_motors();
|
|
|
motors_enabled = false;
|
|
|
}
|
|
|
+ const uint32_t current_ms = now_ms();
|
|
|
+ if (current_ms - last_disarm_feedback_poll_ms >= GUGUJI_DISARM_FEEDBACK_POLL_MS) {
|
|
|
+ // RS00 主动上报默认可能关闭;失能时发送停止帧可安全触发反馈应答。
|
|
|
+ stop_all_motors();
|
|
|
+ last_disarm_feedback_poll_ms = current_ms;
|
|
|
+ }
|
|
|
g_robot_state = GUGUJI_STATE_DISARMED;
|
|
|
vTaskDelay(pdMS_TO_TICKS(GUGUJI_CONTROL_PERIOD_MS));
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
+ if (!feedback_is_safe_for_arm() || !command_targets_are_near_feedback(&command)) {
|
|
|
+ if (motors_enabled) {
|
|
|
+ stop_all_motors();
|
|
|
+ motors_enabled = false;
|
|
|
+ }
|
|
|
+ g_robot_state = GUGUJI_STATE_ESTOP;
|
|
|
+ vTaskDelay(pdMS_TO_TICKS(GUGUJI_CONTROL_PERIOD_MS));
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
if (!motors_enabled) {
|
|
|
enable_all_motors();
|
|
|
motors_enabled = true;
|
|
|
@@ -468,6 +797,7 @@ static void telemetry_task(void *arg)
|
|
|
|
|
|
g_latest_fault_mask = fault_mask;
|
|
|
packet.fault_mask = fault_mask;
|
|
|
+ log_fault_mask_if_changed(fault_mask);
|
|
|
|
|
|
guguji_finalize_telemetry_packet(&packet);
|
|
|
sendto(sock, &packet, sizeof(packet), 0, (struct sockaddr *)&peer_addr, peer_len);
|
|
|
@@ -546,10 +876,12 @@ void app_main(void)
|
|
|
wifi_init();
|
|
|
ESP_ERROR_CHECK(guguji_sensors_init());
|
|
|
ESP_ERROR_CHECK(rs00_mit_init(GUGUJI_TWAI_TX_GPIO, GUGUJI_TWAI_RX_GPIO));
|
|
|
+ request_active_reports(true);
|
|
|
|
|
|
xTaskCreatePinnedToCore(udp_receive_task, "udp_rx", 4096, NULL, 6, NULL, 0);
|
|
|
xTaskCreatePinnedToCore(control_task, "control", 4096, NULL, 8, NULL, 1);
|
|
|
xTaskCreatePinnedToCore(can_feedback_task, "can_rx", 4096, NULL, 7, NULL, 1);
|
|
|
+ xTaskCreatePinnedToCore(can_diagnostics_task, "can_diag", 4096, NULL, 4, NULL, 1);
|
|
|
xTaskCreatePinnedToCore(sensor_task, "sensors", 4096, NULL, 5, NULL, 0);
|
|
|
xTaskCreatePinnedToCore(telemetry_task, "telemetry", 4096, NULL, 5, NULL, 0);
|
|
|
xTaskCreatePinnedToCore(status_task, "status", 4096, NULL, 4, NULL, 0);
|