- can_driver: add filter bank 15 (all ext IDs → FIFO1) and widen bank 14 to accept all standard IDs; add can_driver_send_ext/std and ext/std frame callbacks (can_driver_set_ext_cb / can_driver_set_std_cb) - vesc_can: VESC 29-bit extended CAN protocol driver — send RPM to IDs 56 and 68 (FSESC 6.7 Pro Mini Dual), parse STATUS/STATUS_4/STATUS_5 big-endian payloads, alive timeout, JLINK_TLM_VESC_STATE at 1 Hz - orin_can: Orin↔FC standard CAN protocol — HEARTBEAT/DRIVE/MODE/ESTOP commands in, FC_STATUS + FC_VESC broadcast at 10 Hz - jlink: add JLINK_TLM_VESC_STATE (0x8E), jlink_tlm_vesc_state_t (22 bytes), jlink_send_vesc_state_tlm() - main: wire vesc_can_init/orin_can_init; replace can_driver_send_cmd with vesc_can_send_rpm; inject Orin CAN speed/steer into balance PID; add Orin CAN estop/clear handling; add orin_can_broadcast at 10 Hz - test: 56-test host-side suite for vesc_can; test/stubs/stm32f7xx_hal.h minimal HAL stub for all future host-side tests Safety: balance PID runs independently on Mamba — if Orin CAN link drops (orin_can_is_alive() == false) the robot continues balancing in-place. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
142 lines
4.6 KiB
C
142 lines
4.6 KiB
C
/* 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 <string.h>
|
||
#include <stddef.h>
|
||
|
||
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);
|
||
}
|