/* vesc_can.c — VESC CAN protocol driver (Issue #674) * * Registers vesc_can_on_frame as the extended-frame callback with can_driver. * VESC uses 29-bit arb IDs: (pkt_type << 8) | vesc_node_id. * All wire values are big-endian (VESC FW 6.x). */ #include "vesc_can.h" #include "can_driver.h" #include "jlink.h" #include "stm32f7xx_hal.h" #include #include static uint8_t s_id_left; static uint8_t s_id_right; static vesc_state_t s_state[2]; /* [0] = left, [1] = right */ static uint32_t s_tlm_tick; static vesc_state_t *state_for_id(uint8_t vesc_id) { if (vesc_id == s_id_left) return &s_state[0]; if (vesc_id == s_id_right) return &s_state[1]; return NULL; } void vesc_can_init(uint8_t id_left, uint8_t id_right) { s_id_left = id_left; s_id_right = id_right; memset(s_state, 0, sizeof(s_state)); /* Pre-wind so first send_tlm call fires immediately */ s_tlm_tick = (uint32_t)(-(uint32_t)(1000u / VESC_TLM_HZ)); can_driver_set_ext_cb(vesc_can_on_frame); } void vesc_can_send_rpm(uint8_t vesc_id, int32_t rpm) { /* arb_id = (VESC_PKT_SET_RPM << 8) | vesc_id */ uint32_t ext_id = ((uint32_t)VESC_PKT_SET_RPM << 8u) | (uint32_t)vesc_id; /* Payload: int32 RPM, big-endian */ uint32_t urpm = (uint32_t)rpm; uint8_t data[4]; data[0] = (uint8_t)(urpm >> 24u); data[1] = (uint8_t)(urpm >> 16u); data[2] = (uint8_t)(urpm >> 8u); data[3] = (uint8_t)(urpm); can_driver_send_ext(ext_id, data, 4u); } void vesc_can_on_frame(uint32_t ext_id, const uint8_t *data, uint8_t len) { uint8_t pkt_type = (uint8_t)(ext_id >> 8u); uint8_t vesc_id = (uint8_t)(ext_id & 0xFFu); vesc_state_t *s = state_for_id(vesc_id); if (s == NULL) { return; } switch (pkt_type) { case VESC_PKT_STATUS: /* 9: int32 RPM, int16 I×10, int16 duty×1000 (8 bytes) */ if (len < 8u) { break; } s->rpm = (int32_t)(((uint32_t)data[0] << 24u) | ((uint32_t)data[1] << 16u) | ((uint32_t)data[2] << 8u) | (uint32_t)data[3]); s->current_x10 = (int16_t)(((uint16_t)data[4] << 8u) | (uint16_t)data[5]); s->duty_x1000 = (int16_t)(((uint16_t)data[6] << 8u) | (uint16_t)data[7]); s->last_rx_ms = HAL_GetTick(); break; case VESC_PKT_STATUS_4: /* 16: int16 T_fet×10, T_mot×10, I_in×10 (6 bytes) */ if (len < 6u) { break; } s->temp_fet_x10 = (int16_t)(((uint16_t)data[0] << 8u) | (uint16_t)data[1]); s->temp_motor_x10 = (int16_t)(((uint16_t)data[2] << 8u) | (uint16_t)data[3]); s->current_in_x10 = (int16_t)(((uint16_t)data[4] << 8u) | (uint16_t)data[5]); break; case VESC_PKT_STATUS_5: /* 27: int32 tacho (ignored), int16 V_in×10 (6 bytes) */ if (len < 6u) { break; } /* bytes [0..3] = odometer tachometer — not stored */ s->voltage_x10 = (int16_t)(((uint16_t)data[4] << 8u) | (uint16_t)data[5]); break; default: break; } } bool vesc_can_get_state(uint8_t vesc_id, vesc_state_t *out) { if (out == NULL) { return false; } vesc_state_t *s = state_for_id(vesc_id); if (s == NULL || s->last_rx_ms == 0u) { return false; } memcpy(out, s, sizeof(vesc_state_t)); return true; } bool vesc_can_is_alive(uint8_t vesc_id, uint32_t now_ms) { vesc_state_t *s = state_for_id(vesc_id); if (s == NULL || s->last_rx_ms == 0u) { return false; } return (now_ms - s->last_rx_ms) < VESC_ALIVE_TIMEOUT_MS; } void vesc_can_send_tlm(uint32_t now_ms) { if ((now_ms - s_tlm_tick) < (1000u / VESC_TLM_HZ)) { return; } s_tlm_tick = now_ms; jlink_tlm_vesc_state_t tlm; memset(&tlm, 0, sizeof(tlm)); tlm.left_rpm = s_state[0].rpm; tlm.right_rpm = s_state[1].rpm; tlm.left_current_x10 = s_state[0].current_x10; tlm.right_current_x10 = s_state[1].current_x10; tlm.left_temp_x10 = s_state[0].temp_fet_x10; tlm.right_temp_x10 = s_state[1].temp_fet_x10; /* Use left voltage; fall back to right if left not yet received */ tlm.voltage_x10 = s_state[0].voltage_x10 ? s_state[0].voltage_x10 : s_state[1].voltage_x10; tlm.left_fault = s_state[0].fault_code; tlm.right_fault = s_state[1].fault_code; tlm.left_alive = vesc_can_is_alive(s_id_left, now_ms) ? 1u : 0u; tlm.right_alive = vesc_can_is_alive(s_id_right, now_ms) ? 1u : 0u; jlink_send_vesc_state_tlm(&tlm); }