Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Documents/drivers/wheel.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,26 @@ Kconfig:
- 计算出的 RPM 命令
- 已缓存的静态角

### 麦克纳姆轮示意

俯视单个麦克纳姆轮时,驱动电机提供轮周切向速度,滚子方向提供自由滚动轴。驱动会把底盘请求速度向量投影到自由滚动轴,再折算成电机 RPM。

```text
target vector
^
|
|
wheel face | free rolling axis
+-------------+ | /
| / / / / / | | / free-angle
| / / / / / | | /
|/ / / / / | +----+------> motor tangential axis
+-------------+

target projection on free axis
= target speed * cos(target angle - free axis angle)
```

### 运行时行为

`mecanum_set_speed()` 将请求的底盘运动向量投影到轮子的自由滚动轴,再把投影线速度转换为电机 RPM。
Expand Down
5 changes: 4 additions & 1 deletion Documents/motor/drivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,10 @@ LK 使用标准帧,通过不同命令字区分:

驱动有两条工作路径:

- 周期发送控制命令。
- `motor_set_*()` 更新目标后立即发送当前控制命令。
- `lk_tx_timer` 每 8ms 重新发送当前控制命令;如果当前没有可打包的速度、扭矩或位置命令,
则 fallback 到 `0x9C` 状态读取。提交 handler 前若 3ms 内已收到新的 `0x9C`
状态回复,则跳过本轮发送。
- 独立发送参数更新命令。

这也是 `lk_tx_data_handler()` 和 `lk_tx_params_data_handler()` 分开的原因。
Expand Down
43 changes: 36 additions & 7 deletions Documents/tests/native_sim.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
- `tests/native_sim/pid/`
- `tests/native_sim/sbus/`
- `tests/native_sim/motor_driver_sim/`
- `tests/native_sim/wheel_chassis/`

### 常用命令

Expand All @@ -43,6 +44,7 @@ west twister -T tests/native_sim/module_smoke --platform native_sim/native/64 --
west twister -T tests/native_sim/pid --platform native_sim/native/64 --inline-logs
west twister -T tests/native_sim/sbus --platform native_sim/native/64 --inline-logs
west twister -T tests/native_sim/motor_driver_sim --platform native_sim/native/64 --inline-logs
west twister -T tests/native_sim/wheel_chassis --platform native_sim/native/64 --inline-logs
```

单测试构建(快速检查):
Expand Down Expand Up @@ -112,6 +114,25 @@ ares.native_sim.sbus

`prj.conf` 启用 `CONFIG_ARES_SBUS`、UART async API 与 runtime configure,平台限定 `native_sim/native/64`。

## wheel_chassis

测试路径:`tests/native_sim/wheel_chassis/src/main.c` 与 `app.overlay`

测试行为:

- 使用测试专用 motor 设备承载 `ares,mecanum` 与 `ares,steerwheel`,验证轮级 API 到电机 setpoint 的转换。
- 覆盖麦克纳姆轮速度投影、状态读取和静态角锁存。
- 覆盖舵轮短路径反向驱动、零速停止与禁能扭矩清零。
- 使用测试专用 wheel 设备承载 `ares,chassis`,直接验证底盘公共命令映射、运动解析与静态角下发。

testcase:

```text
ares.native_sim.wheel_chassis
```

`prj.conf` 启用 `CONFIG_MOTOR`、`CONFIG_PID`、`CONFIG_WHEEL`、`CONFIG_CHASSIS` 与 `CONFIG_ZTEST`,平台允许 `native_sim` 与 `native_sim/native/64`。

## motor_driver_sim

测试路径:`tests/native_sim/motor_driver_sim/src/main.c` 与 `app.overlay`
Expand All @@ -124,7 +145,7 @@ ares.native_sim.sbus
- `mi0`:`mi,motor`
- `rs0`:`rs,motor`
- `lk0`:`lk,motor`
- `dji0`:`dji,motor`
- `dji0`~`dji6`:`dji,motor`

驱动配置包含 `fake_can`,并通过 `fake_can` 通道复用 `can_channel`。

Expand All @@ -142,15 +163,17 @@ ares.native_sim.sbus

`send` 行为:

- 每帧记录到环形缓冲 `SIM_TX_MAX = 128`
- 按 `bitrate` 和经典 CAN 帧格式占用虚拟总线时间
- 标准帧 8 字节按约 111 bit 计算,扩展帧 8 字节按约 131 bit 计算
- 每帧记录到环形缓冲 `SIM_TX_MAX = 512`
- 记录 `can_frame`、`uptime`、`cycle`
- 同步回调立即触发 tx 回调(`callback` 非空时)
- TX 记录时间为帧完成时间,tx 回调在虚拟帧发送完成后触发

`add_rx_filter` 与回调分发行为:

- 过滤注册按 `can_filter` 匹配
- `sim_emit_frame` 遍历过滤器并触发回调
- 不模拟总线仲裁、mailbox 竞争、bus-off、误码状态
- `sim_emit_frame` 先占用虚拟总线时间,再遍历过滤器并触发回调
- 不模拟 bit stuffing、总线仲裁、mailbox 竞争、bus-off、误码状态

该模型适合协议与驱动逻辑验证,不适合硬件级收发行为还原。

Expand Down Expand Up @@ -195,6 +218,8 @@ ares.native_sim.sbus
- DM/MI/RS:`motor_set_mit` 的连续 setpoint(10、20、30);
- LK:`motor_set_speed` 的连续 setpoint(25、50、75);
- 每次下发前/后都有 `emit_*_feedback` 的配套;
- LK 速度 setpoint 期望直接触发 `0xA2` 控制帧;8ms timer 平常重发当前命令,只有无可用
控制目标时才 fallback 到 `0x9C` 状态读取。
- 使用期望帧匹配函数比较:
- 帧 ID(标准/扩展)
- DLC(固定 8)
Expand All @@ -205,8 +230,8 @@ ares.native_sim.sbus

#### 延迟阈值

- `CONTROL_LATENCY_MS = 1000`
- `DJI_CONTROL_LATENCY_MS = 30`
- `CONTROL_LATENCY_MS = 50`
- `DJI_CONTROL_LATENCY_MS = 2`

延迟由下发时间戳到匹配控制帧到达时间计算。测试使用 `expect_payload_sequence_step` 在窗口内等待;超时给出完整帧转储用于诊断。

Expand All @@ -231,6 +256,10 @@ ares.native_sim.sbus

`test_dji_control_not_starved_by_reply_backlog` 构造 DM backlog 后验证 DJI 控制帧仍能发送,确保请求回复流量不会永久压住周期/关键控制链路。

`test_dji_7_motors_on_one_can_keep_950hz_control` 将 7 个 DJI 电机挂在同一个
fake CAN 上,按 `0x200`/`0x1ff` 聚合帧槽位统计每个电机的控制更新频率,
要求每个电机不低于 950Hz。

## 配置与 testcase 约定

### `testcase.yaml`(当前约定)
Expand Down
2 changes: 1 addition & 1 deletion drivers/chassis/chassis.c
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ void chassis_thread_entry(void *arg1, void *arg2, void *arg3)
int32_t deltaTimeUs = k_cyc_to_us_floor32(data->currTime - data->prevTime);
float error = 0;

if (!isnanf(data->chassis_sensor_data.Yaw) && data->angleControl) {
if (!isnan(data->chassis_sensor_data.Yaw) && data->angleControl) {
float delta_Yaw = data->chassis_sensor_data.Yaw - data->target_status.angle;
delta_Yaw = fmodf(delta_Yaw, 360.0f);
if (delta_Yaw > 180) {
Expand Down
97 changes: 60 additions & 37 deletions drivers/motor/lk/motor_lk.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,25 @@

LOG_MODULE_REGISTER(motor_lk, CONFIG_MOTOR_LOG_LEVEL);

static bool lk_motor_pack(const struct device *dev, struct can_frame *frame);

static void lk_send_control_frame(const struct device *dev)
{
struct can_frame tx_frame = {0};
struct lk_motor_data *data = dev->data;
const struct lk_motor_cfg *cfg = dev->config;

if (!data->common.link.requested_enabled) {
return;
}

if (!lk_motor_pack(dev, &tx_frame)) {
return;
}
motor_can_sched_send_reply(cfg->common.phy, &tx_frame, LK_CMD_ID_BASE + cfg->id,
CAN_STD_ID_MASK, 5U, "lk-control");
}

int float_to_int(float x, float x_min, float x_max, int bits)
{

Expand Down Expand Up @@ -101,11 +120,13 @@ void lk_motor_control(const struct device *dev, enum motor_cmd cmd)
// }
}

static void lk_motor_pack(const struct device *dev, struct can_frame *frame)
static bool lk_motor_pack(const struct device *dev, struct can_frame *frame)
{
struct lk_motor_data *data = (struct lk_motor_data *)(dev->data);
const struct lk_motor_cfg *cfg = (struct lk_motor_cfg *)(dev->config);
bool control_frame = true;

memset(frame, 0, sizeof(*frame));
frame->id = LK_CMD_ID_BASE + cfg->id; // 标准ID: 0x140 + ID
frame->dlc = 8;
frame->flags = 0; // 标准帧
Expand All @@ -132,6 +153,7 @@ static void lk_motor_pack(const struct device *dev, struct can_frame *frame)
}
if (data->common.target != MOTOR_TARGET_TORQUE) {
frame->data[0] = LK_CMD_READ_STAT;
control_frame = false;
break;
}
frame->data[0] = LK_CMD_TORQUE_LOOP;
Expand All @@ -150,6 +172,7 @@ static void lk_motor_pack(const struct device *dev, struct can_frame *frame)
case PV:
if (data->common.target != MOTOR_TARGET_POSITION) {
frame->data[0] = LK_CMD_READ_STAT;
control_frame = false;
break;
}
frame->data[0] = LK_CMD_POS_LOOP_MULTI;
Expand All @@ -171,8 +194,11 @@ static void lk_motor_pack(const struct device *dev, struct can_frame *frame)
default:

frame->data[0] = LK_CMD_READ_STAT;
control_frame = false;
break;
}

return control_frame;
}

int lk_set(const struct device *dev, motor_setpoint_t *status)
Expand Down Expand Up @@ -264,6 +290,8 @@ int lk_set(const struct device *dev, motor_setpoint_t *status)
}
}

lk_send_control_frame(dev);

return 0;
}

Expand Down Expand Up @@ -297,6 +325,9 @@ static void lk_can_rx_handler(const struct device *can_dev, struct can_frame *fr
if (!data->common.link.online) {
data->need_init_frames = true;
}
if (frame->data[0] == LK_CMD_READ_STAT) {
data->last_status_reply_ms = k_uptime_get_32();
}
motor_link_observe_reply(motor_devices[idx], &data->common.link);
// Data[0]: 命令字节
// Data[1]: 温度
Expand Down Expand Up @@ -340,60 +371,52 @@ void lk_rx_data_handler(struct k_work *work)

void lk_tx_isr_handler(struct k_timer *dummy)
{
k_work_submit_to_queue(&lk_work_queue, &lk_tx_data_handle);
uint32_t now = k_uptime_get_32();

ARG_UNUSED(dummy);

for (int i = 0; i < MOTOR_COUNT; i++) {
struct lk_motor_data *data = motor_devices[i]->data;

if (!data->common.link.requested_enabled) {
continue;
}
if (data->last_status_reply_ms != 0U &&
now - data->last_status_reply_ms < LK_STATUS_REPLY_FRESH_MS) {
continue;
}
k_work_submit_to_queue(&lk_work_queue, &lk_tx_data_handle);
return;
}
}

// 发送处理线程
void lk_tx_data_handler(struct k_work *work)
{
struct can_frame tx_frame = {0};
uint32_t now = k_uptime_get_32();

ARG_UNUSED(work);

for (int i = 0; i < MOTOR_COUNT; i++) {
struct lk_motor_data *data = motor_devices[i]->data;
const struct lk_motor_cfg *cfg = motor_devices[i]->config;

if (data->common.link.requested_enabled) {
if (motor_link_note_missed_reply(motor_devices[i], &data->common.link,
10)) {
data->offline_tx_cnt = 0;
}
if (!data->common.link.online) {
if ((data->offline_tx_cnt++ % 3U) == 0U) {
memset(&tx_frame, 0, sizeof(tx_frame));
tx_frame.id = LK_CMD_ID_BASE + cfg->id;
tx_frame.dlc = 8;
tx_frame.flags = 0;
tx_frame.data[0] = LK_CMD_MOTOR_RUN;
motor_can_sched_send_with_priority(
cfg->common.phy, &tx_frame,
MOTOR_CAN_SCHED_PRIO_CRITICAL, "lk-retry-enable");
}
if (data->last_status_reply_ms != 0U &&
now - data->last_status_reply_ms < LK_STATUS_REPLY_FRESH_MS) {
continue;
}
if (data->need_init_frames) {
memset(&tx_frame, 0, sizeof(tx_frame));
tx_frame.id = LK_CMD_ID_BASE + cfg->id;
tx_frame.dlc = 8;
tx_frame.flags = 0;
tx_frame.data[0] = LK_CMD_MOTOR_RUN;
motor_can_sched_send_with_priority(cfg->common.phy, &tx_frame,
MOTOR_CAN_SCHED_PRIO_CRITICAL,
"lk-reenable");
data->params_update[0] = true;
data->params_update[1] = true;
data->params_update[2] = true;
data->need_init_frames = false;
if (motor_link_note_missed_reply(motor_devices[i], &data->common.link,
10)) {
data->offline_tx_cnt = 0;
k_work_submit_to_queue(&lk_work_queue, &lk_tx_params_data_handle);
}
lk_motor_pack(motor_devices[i], &tx_frame);
motor_can_sched_send_reply(cfg->common.phy, &tx_frame,
LK_CMD_ID_BASE + cfg->id, CAN_STD_ID_MASK, 5U,
"lk-control");
tx_frame.data[0] == LK_CMD_READ_STAT
? "lk-read-stat"
: "lk-control");
}
// if (i % 2 == 1) {
// k_usleep(500);
// }
}
}
void lk_tx_params_data_handler(struct k_work *work)
Expand Down Expand Up @@ -537,7 +560,7 @@ void lk_init_handler(struct k_work *work)
}
k_work_submit_to_queue(&lk_work_queue, &lk_tx_params_data_handle);
lk_tx_timer.expiry_fn = lk_tx_isr_handler;
k_timer_start(&lk_tx_timer, K_MSEC(600), K_MSEC(8));
k_timer_start(&lk_tx_timer, K_MSEC(600), K_MSEC(LK_STATUS_POLL_PERIOD_MS));
k_timer_user_data_set(&lk_tx_timer, &lk_tx_data_handle);
}

Expand Down
9 changes: 6 additions & 3 deletions drivers/motor/lk/motor_lk.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@

#define SIZE_OF_ARRAY(x) (sizeof(x) / sizeof(x[0]))

#define CAN_SEND_STACK_SIZE 4096
#define CAN_SEND_PRIORITY -1
#define PI 3.14159265f
#define CAN_SEND_STACK_SIZE 4096
#define CAN_SEND_PRIORITY -1
#define LK_STATUS_POLL_PERIOD_MS 8
#define LK_STATUS_REPLY_FRESH_MS 3
#define PI 3.14159265f
#ifdef RAD2DEG
#undef RAD2DEG
#endif
Expand Down Expand Up @@ -82,6 +84,7 @@ struct lk_motor_data {
bool update;
bool need_init_frames;
uint16_t offline_tx_cnt;
uint32_t last_status_reply_ms;
struct motor_controller_params params[3];
bool params_update[3];
};
Expand Down
3 changes: 2 additions & 1 deletion drivers/wheel/mecanum.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ struct wheel_driver_api mecanum_driver_api = {
DT_STRING_UNQUOTED(DT_DRV_INST(inst), angle_offset) + \
DT_STRING_UNQUOTED(DT_DRV_INST(inst), free_angle) - 90.0f, \
false}, \
.static_angle = NAN, \
}; \
static const mecanum_cfg_t mecanum_cfg_##inst = { \
.common = DT_WHEEL_CONFIG_GET(inst), \
Expand All @@ -156,6 +157,6 @@ struct wheel_driver_api mecanum_driver_api = {
MECANUM_DEVICE_DT_DEFINE(DT_DRV_INST(inst), NULL, NULL, &mecanum_data_##inst, \
&mecanum_cfg_##inst, POST_KERNEL, 90, &mecanum_driver_api);

DT_INST_FOREACH_STATUS_OKAY(mecanum_DEVICE_DEFINE)
DT_INST_FOREACH_STATUS_OKAY(MECANUM_DEVICE_DEFINE)

#endif // MECANUM_C
Loading
Loading