第一版代码,为了在EEPROM保存参数的时候走STM32的CRC,让Codex修改了一下,现在的效果是无法存储,codex表示原因是CRC方法不同,修改到一半今天的额度使用完了,有待后续解决CRC的bug
This commit is contained in:
329
Application/app_CiA402.c
Normal file
329
Application/app_CiA402.c
Normal file
@@ -0,0 +1,329 @@
|
||||
#include "app_CiA402.h"
|
||||
|
||||
/**
|
||||
* @brief 辅助函数:CiA402 状态机管理
|
||||
* @note 使用 cia402_defs.h 标准宏定义重构,确保状态流转逻辑与原版本一致
|
||||
*/
|
||||
void Process_StateMachine(void)
|
||||
{
|
||||
// 1. 获取对象字典条目句柄
|
||||
OD_entry_t *entry_ctrl = OD_find(OD, CIA402_INDEX_CONTROLWORD);
|
||||
OD_entry_t *entry_status = OD_find(OD, CIA402_INDEX_STATUSWORD);
|
||||
|
||||
uint16_t cw = 0;
|
||||
uint16_t sw = 0;
|
||||
|
||||
// 2. 读取当前的控制字与状态字
|
||||
OD_get_u16(entry_ctrl, 0, &cw, true);
|
||||
OD_get_u16(entry_status, 0, &sw, true);
|
||||
|
||||
// 3. 屏蔽状态字低 7 位 (Bit 0-6),准备根据当前状态重新填充反馈位
|
||||
sw &= 0xFF80;
|
||||
|
||||
// 4. 标准 CiA402 状态流转逻辑处理
|
||||
switch (internal_state)
|
||||
{
|
||||
case STATE_SWITCH_ON_DISABLED:
|
||||
// 检查是否收到 Shutdown 命令 (对应 0x0006)
|
||||
if ((cw & CIA402_CMD_SHUTDOWN) == CIA402_CMD_SHUTDOWN)
|
||||
internal_state = STATE_READY_TO_SWITCH_ON;
|
||||
// 反馈状态:Switch on disabled
|
||||
sw |= CIA402_STATUS_SWITCH_ON_DISABLED;
|
||||
break;
|
||||
|
||||
case STATE_READY_TO_SWITCH_ON:
|
||||
// 检查是否收到 Switch On 命令 (对应 0x0007)
|
||||
if ((cw & CIA402_CMD_SWITCH_ON) == CIA402_CMD_SWITCH_ON)
|
||||
internal_state = STATE_SWITCHED_ON;
|
||||
// 反馈状态:Ready to switch on + Quick stop (标志正常运行位)
|
||||
sw |= (CIA402_STATUS_READY_TO_SWITCH_ON | CIA402_STATUS_QUICK_STOP);
|
||||
break;
|
||||
|
||||
case STATE_SWITCHED_ON:
|
||||
// 检查是否收到 Enable Operation 命令 (对应 0x000F)
|
||||
if ((cw & CIA402_CMD_ENABLE_OP) == CIA402_CMD_ENABLE_OP)
|
||||
internal_state = STATE_OPERATION_ENABLED;
|
||||
else if ((cw & CIA402_CMD_SWITCH_ON) != CIA402_CMD_SWITCH_ON)
|
||||
internal_state = STATE_READY_TO_SWITCH_ON;
|
||||
// 反馈状态:Switched on 等组合
|
||||
sw |= (CIA402_STATUS_READY_TO_SWITCH_ON | CIA402_STATUS_SWITCHED_ON | CIA402_STATUS_QUICK_STOP);
|
||||
break;
|
||||
|
||||
case STATE_OPERATION_ENABLED:
|
||||
// 如果控制字不再满足使能条件,退回上一状态
|
||||
if ((cw & CIA402_CMD_ENABLE_OP) != CIA402_CMD_ENABLE_OP)
|
||||
internal_state = STATE_SWITCHED_ON;
|
||||
// 反馈状态:使能运行全标志 (对应掩码 0x0027)
|
||||
sw |= CIA402_STATUS_MASK_OP_ENABLE;
|
||||
break;
|
||||
|
||||
default:
|
||||
internal_state = STATE_SWITCH_ON_DISABLED;
|
||||
break;
|
||||
}
|
||||
|
||||
// 5. 更新状态字到对象字典
|
||||
OD_set_u16(entry_status, 0, sw, true);
|
||||
}
|
||||
|
||||
// 根据脉冲更新绝对位置并写入 OD
|
||||
void Update_Motion_State_To_OD(void)
|
||||
{
|
||||
// 1. 获取 OD 条目句柄
|
||||
OD_entry_t *entry_pos = OD_find(OD, CIA402_INDEX_POS_ACTUAL);
|
||||
OD_entry_t *entry_vel = OD_find(OD, CIA402_INDEX_VEL_ACTUAL);
|
||||
OD_entry_t *entry_sw = OD_find(OD, CIA402_INDEX_STATUSWORD);
|
||||
|
||||
if (!entry_pos || !entry_vel || !entry_sw)
|
||||
return;
|
||||
|
||||
// 2. 物理位置逻辑计算
|
||||
float dist_moved_mm = (float)stepper_1.step_count / STEPS_PER_MM;
|
||||
|
||||
if (stepper_1.dir == GPIO_PIN_RESET)
|
||||
current_absolute_pos_mm = last_move_start_pos_mm + dist_moved_mm;
|
||||
else
|
||||
current_absolute_pos_mm = last_move_start_pos_mm - dist_moved_mm;
|
||||
|
||||
// 3. 计算并四舍五入位置值
|
||||
int32_t pos_to_set = 0;
|
||||
if (current_absolute_pos_mm >= 0)
|
||||
pos_to_set = (int32_t)(current_absolute_pos_mm + 0.5f);
|
||||
else
|
||||
pos_to_set = (int32_t)(current_absolute_pos_mm - 0.5f);
|
||||
|
||||
// 4. 获取当前速度值
|
||||
int32_t vel_to_set = (stepper_1.state != STOP) ? (int32_t)stepper_1.current_speed : 0;
|
||||
|
||||
// 5. 写入 OD (使用锁保护原子性)
|
||||
// 注意:电机板作为从机,通常直接使用 CO 指针访问
|
||||
CO_LOCK_OD(CO->CANmodule);
|
||||
|
||||
OD_set_i32(entry_pos, 0, pos_to_set, true);
|
||||
OD_set_i32(entry_vel, 0, vel_to_set, true);
|
||||
|
||||
// 6. 更新状态字 (Bit 10: Target Reached)
|
||||
uint16_t sw = 0;
|
||||
OD_get_u16(entry_sw, 0, &sw, true);
|
||||
|
||||
if (stepper_1.state == STOP)
|
||||
{
|
||||
// 仅在非回零或回零完成且 Operation Enabled 时置位
|
||||
if (homing_state == HOMING_IDLE || homing_state == HOMING_DONE)
|
||||
{
|
||||
// 使用掩码检查 Ready+SwOn+OpEn+NoQuickStop (0x0027)
|
||||
if ((sw & CIA402_STATUS_MASK_OP_ENABLE) == CIA402_STATUS_MASK_OP_ENABLE)
|
||||
{
|
||||
sw |= CIA402_STATUS_TARGET_REACHED; // 置位目标到达 (Bit 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 运动中清除已到达标志
|
||||
sw &= ~CIA402_STATUS_TARGET_REACHED;
|
||||
}
|
||||
|
||||
OD_set_u16(entry_sw, 0, sw, true);
|
||||
CO_UNLOCK_OD(CO->CANmodule);
|
||||
}
|
||||
|
||||
// @brief 处理 Mode 6 (回零模式) 的逻辑
|
||||
void Handle_Homing_Mode(void)
|
||||
{
|
||||
// 1. 获取 OD 条目句柄
|
||||
OD_entry_t *entry_ctrl = OD_find(OD, CIA402_INDEX_CONTROLWORD);
|
||||
OD_entry_t *entry_status = OD_find(OD, CIA402_INDEX_STATUSWORD);
|
||||
OD_entry_t *entry_pos = OD_find(OD, CIA402_INDEX_POS_ACTUAL);
|
||||
|
||||
if (!entry_ctrl || !entry_status || !entry_pos)
|
||||
return;
|
||||
|
||||
uint16_t cw = 0;
|
||||
OD_get_u16(entry_ctrl, 0, &cw, true); // 读取控制字
|
||||
|
||||
// A. 启动回零 (Bit 4 上升沿)
|
||||
if ((cw & CIA402_CONTROL_HM_START) && homing_state == HOMING_IDLE)
|
||||
{
|
||||
debug_printf("[App] Homing Start...");
|
||||
last_move_start_pos_mm = current_absolute_pos_mm;
|
||||
|
||||
stepper_1.dir = GPIO_PIN_SET; // 物理向右 (找开关)
|
||||
stepper_1.distance = 230.0f; // 设大距离
|
||||
stepper_1.speed = 5.0f; // 慢速
|
||||
stepper_1.acc = 10.0f;
|
||||
stepper_1.start_speed = 1.0f;
|
||||
|
||||
stepper_1.x_zero = 0; // 清除标志
|
||||
Int_TMC2209_start(&stepper_1, &encoder_1);
|
||||
|
||||
homing_state = HOMING_MOVING;
|
||||
|
||||
// 原子化更新状态字:清除 Bit 10 (Target Reached) 和 Bit 12 (Homing Attained)
|
||||
CO_LOCK_OD(CO->CANmodule);
|
||||
uint16_t sw = 0;
|
||||
OD_get_u16(entry_status, 0, &sw, true);
|
||||
sw &= ~(CIA402_STATUS_TARGET_REACHED | CIA402_STATUS_OMS_12);
|
||||
OD_set_u16(entry_status, 0, sw, true);
|
||||
CO_UNLOCK_OD(CO->CANmodule);
|
||||
}
|
||||
|
||||
// B. 正在回零 (检测开关)
|
||||
if (homing_state == HOMING_MOVING)
|
||||
{
|
||||
if (stepper_1.x_zero == 1) // 撞到开关,到达零点
|
||||
{
|
||||
Int_TMC2209_stop();
|
||||
stepper_1.state = STOP; // 强制停止状态
|
||||
stepper_1.step_count = 0; // 清除底层脉冲
|
||||
|
||||
/* --- 核心修复:原子化重置坐标系,防止 TPDO 跳变 --- */
|
||||
CO_LOCK_OD(CO->CANmodule);
|
||||
|
||||
// 1. 内部浮点坐标重置
|
||||
current_absolute_pos_mm = 0.0f;
|
||||
last_move_start_pos_mm = 0.0f;
|
||||
|
||||
// 2. 通过接口更新对象字典位置为 0
|
||||
// 这会自动标记 TPDO 为待发送状态
|
||||
OD_set_i32(entry_pos, 0, 0, true);
|
||||
|
||||
// 3. 更新状态字 (Bit 10: Reached, Bit 12: Attained)
|
||||
uint16_t sw = 0;
|
||||
OD_get_u16(entry_status, 0, &sw, true);
|
||||
sw |= (CIA402_STATUS_TARGET_REACHED | CIA402_STATUS_OMS_12);
|
||||
OD_set_u16(entry_status, 0, sw, true);
|
||||
|
||||
CO_UNLOCK_OD(CO->CANmodule);
|
||||
|
||||
homing_state = HOMING_DONE;
|
||||
debug_printf("[App] Homing Success, Position Reset to 0.");
|
||||
}
|
||||
else if (stepper_1.state == STOP) // 没撞开关就停了
|
||||
{
|
||||
homing_state = HOMING_IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// @brief 处理 Mode 1 (位置模式) 的逻辑
|
||||
void Handle_Position_Mode(void)
|
||||
{
|
||||
homing_state = HOMING_IDLE; // 复位回零状态
|
||||
static uint16_t last_cw = 0;
|
||||
|
||||
// 1. 获取 OD 条目句柄
|
||||
OD_entry_t *entry_cw = OD_find(OD, CIA402_INDEX_CONTROLWORD);
|
||||
OD_entry_t *entry_sw = OD_find(OD, CIA402_INDEX_STATUSWORD);
|
||||
OD_entry_t *entry_tpos = OD_find(OD, CIA402_INDEX_TARGET_POS);
|
||||
OD_entry_t *entry_tvel = OD_find(OD, CIA402_INDEX_TARGET_VEL);
|
||||
OD_entry_t *entry_acc = OD_find(OD, CIA402_INDEX_PROFILE_ACC);
|
||||
OD_entry_t *entry_dec = OD_find(OD, CIA402_INDEX_PROFILE_DEC);
|
||||
|
||||
if (!entry_cw || !entry_sw || !entry_tpos || !entry_tvel || !entry_acc || !entry_dec)
|
||||
return;
|
||||
|
||||
uint16_t cw = 0;
|
||||
OD_get_u16(entry_cw, 0, &cw, true); // 读取当前控制字
|
||||
|
||||
// A. 触发新运动 (Bit 4 上升沿:New set-point)
|
||||
if ((cw & CIA402_CONTROL_PP_NEW_SET_POINT) && !(last_cw & CIA402_CONTROL_PP_NEW_SET_POINT))
|
||||
{
|
||||
int32_t target_pos_raw = 0;
|
||||
int32_t target_vel_raw = 0;
|
||||
uint32_t target_acc_raw = 0;
|
||||
uint32_t target_dec_raw = 0;
|
||||
|
||||
// 通过接口获取目标参数
|
||||
OD_get_i32(entry_tpos, 0, &target_pos_raw, true);
|
||||
OD_get_i32(entry_tvel, 0, &target_vel_raw, true);
|
||||
OD_get_u32(entry_acc, 0, &target_acc_raw, true);
|
||||
OD_get_u32(entry_dec, 0, &target_dec_raw, true);
|
||||
|
||||
float target_pos = (float)target_pos_raw;
|
||||
float target_vel = (float)target_vel_raw;
|
||||
float target_acc = (float)target_acc_raw;
|
||||
float target_dec = (float)target_dec_raw;
|
||||
|
||||
if (target_pos > SOFT_LIMIT_MAX_MM || target_pos < SOFT_LIMIT_MIN_MM)
|
||||
{
|
||||
// 如果超出 230mm 或小于 0mm,拦截指令并报错
|
||||
debug_printf("[App] ERROR: Target %.1f out of Range [0, 230]! Command Ignored.\r\n", target_pos);
|
||||
|
||||
// 为了完成 CiA402 握手,即便不移动也需要置位 Bit 12 (Ack)
|
||||
CO_LOCK_OD(CO->CANmodule);
|
||||
uint16_t sw = 0;
|
||||
OD_get_u16(entry_sw, 0, &sw, true); // 读取当前状态字
|
||||
sw |= CIA402_STATUS_OMS_12;
|
||||
OD_set_u16(entry_sw, 0, sw, true);
|
||||
CO_UNLOCK_OD(CO->CANmodule);
|
||||
last_cw = cw;
|
||||
return;
|
||||
}
|
||||
|
||||
// 默认参数保护
|
||||
if (target_vel < 0.1f)
|
||||
target_vel = 5.0f;
|
||||
if (target_acc < 0.1f)
|
||||
target_acc = 50.0f;
|
||||
if (target_dec < 0.1f)
|
||||
target_dec = 50.0f;
|
||||
|
||||
float delta = target_pos - current_absolute_pos_mm;
|
||||
debug_printf("[App] PP Move: Tgt=%.1f, Delta=%.1f", target_pos, delta);
|
||||
|
||||
// 设置方向与行程
|
||||
if (delta >= 0)
|
||||
{
|
||||
stepper_1.dir = GPIO_PIN_RESET;
|
||||
stepper_1.distance = delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
stepper_1.dir = GPIO_PIN_SET;
|
||||
stepper_1.distance = -delta;
|
||||
}
|
||||
|
||||
stepper_1.speed = target_vel;
|
||||
stepper_1.acc = target_acc;
|
||||
stepper_1.dec = target_dec;
|
||||
stepper_1.start_speed = 5.0f;
|
||||
|
||||
// 启动运动或直接置位到达标志
|
||||
CO_LOCK_OD(CO->CANmodule); // 进入保护区修改状态字
|
||||
uint16_t sw = 0;
|
||||
OD_get_u16(entry_sw, 0, &sw, true);
|
||||
|
||||
/* 清除丢步标志位 */
|
||||
sw &= (uint16_t)(~CIA402_STATUS_OMS_13);
|
||||
|
||||
if (stepper_1.distance > 0.05f)
|
||||
{
|
||||
last_move_start_pos_mm = current_absolute_pos_mm; // 记录起点
|
||||
Int_TMC2209_start(&stepper_1, &encoder_1);
|
||||
|
||||
sw &= ~CIA402_STATUS_TARGET_REACHED; // 清除 Target Reached (Bit 10)
|
||||
sw |= CIA402_STATUS_OMS_12; // 置位 Set-point Acknowledge (Bit 12)
|
||||
}
|
||||
else
|
||||
{
|
||||
sw |= (CIA402_STATUS_TARGET_REACHED | CIA402_STATUS_OMS_12); // 已在目标位置
|
||||
}
|
||||
|
||||
OD_set_u16(entry_sw, 0, sw, true);
|
||||
CO_UNLOCK_OD(CO->CANmodule);
|
||||
}
|
||||
|
||||
// B. 握手信号处理 (Bit 4 下降沿清除 Ack 反馈)
|
||||
if (!(cw & 0x0010) && (last_cw & 0x0010))
|
||||
{
|
||||
CO_LOCK_OD(CO->CANmodule);
|
||||
uint16_t sw = 0;
|
||||
OD_get_u16(entry_sw, 0, &sw, true);
|
||||
sw &= ~CIA402_STATUS_OMS_12; // 清除 Set-point Acknowledge
|
||||
OD_set_u16(entry_sw, 0, sw, true);
|
||||
CO_UNLOCK_OD(CO->CANmodule);
|
||||
}
|
||||
|
||||
last_cw = cw;
|
||||
}
|
||||
27
Application/app_CiA402.h
Normal file
27
Application/app_CiA402.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef __APP_CIA402_H__
|
||||
#define __APP_CIA402_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h> // for abs()
|
||||
#include "CiA402_defs.h"
|
||||
#include "CO_ODinterface.h"
|
||||
#include "Int_TMC2209.h"
|
||||
#include "CANopen.h"
|
||||
#include "app_main.h"
|
||||
#include "app_motor.h"
|
||||
#include "OD.h"
|
||||
|
||||
/* 物理参数定义 */
|
||||
#define SOFT_LIMIT_MAX_MM 230.0f /* 丝杆有效量程上限 (mm) */
|
||||
#define SOFT_LIMIT_MIN_MM 0.0f /* 丝杆有效量程下限 (mm) */
|
||||
|
||||
extern Stepper_t stepper_1; // 引用在 app_main.c 中定义的电机对象
|
||||
extern CO_t *CO; // 引用在 CO_app_STM32.c 中定义的 CANopen 对象
|
||||
|
||||
void Process_StateMachine(void);
|
||||
void Update_Motion_State_To_OD(void);
|
||||
void Handle_Homing_Mode(void);
|
||||
void Handle_Position_Mode(void);
|
||||
|
||||
#endif /* __APP_CIA402_H__ */
|
||||
236
Application/app_key.c
Normal file
236
Application/app_key.c
Normal file
@@ -0,0 +1,236 @@
|
||||
#include "app_key.h"
|
||||
#include <stddef.h>
|
||||
|
||||
/* 按键行为说明:
|
||||
* K1 单击 -> 回零(朝光电开关方向,低速)
|
||||
* K2 长按/松开 -> JOG+ 启动 / 减速停止
|
||||
* K3 长按/松开 -> JOG- 启动 / 减速停止
|
||||
* K4 单击 -> 速度档位循环
|
||||
* K5 单击 -> 立即停止
|
||||
*/
|
||||
#define SPEED_LEVEL_COUNT (3u)
|
||||
#define JOG_SPEED_LOW_MM_S (5.0f)
|
||||
#define JOG_SPEED_MID_MM_S (25.0f)
|
||||
#define JOG_SPEED_HIGH_MM_S (60.0f)
|
||||
#define HOME_SPEED_MM_S (5.0f)
|
||||
#define HOME_TRAVEL_MM (500.0f)
|
||||
|
||||
static const float s_speed_levels[SPEED_LEVEL_COUNT] = {
|
||||
JOG_SPEED_LOW_MM_S,
|
||||
JOG_SPEED_MID_MM_S,
|
||||
JOG_SPEED_HIGH_MM_S,
|
||||
};
|
||||
|
||||
static uint8_t s_speed_idx = 0u;
|
||||
static uint8_t s_jog_key = KEY_NONE;
|
||||
static uint8_t s_home_active = 0u;
|
||||
|
||||
/* 光电零位开关:低电平有效。 */
|
||||
static uint8_t key_home_switch_active(void)
|
||||
{
|
||||
return (HAL_GPIO_ReadPin(X_ZERO_GPIO_Port, X_ZERO_Pin) == GPIO_PIN_RESET) ? 1u : 0u;
|
||||
}
|
||||
|
||||
/* 根据方向和已走步数估算当前位置(mm)。 */
|
||||
static float key_estimate_pos_mm(const Stepper_t *stepper)
|
||||
{
|
||||
float moved_mm = (float)stepper->step_count / STEPS_PER_MM;
|
||||
if (stepper->dir == GPIO_PIN_RESET)
|
||||
{
|
||||
return last_move_start_pos_mm + moved_mm;
|
||||
}
|
||||
return last_move_start_pos_mm - moved_mm;
|
||||
}
|
||||
|
||||
static void local_motion_start(Stepper_t *stepper, GPIO_PinState dir, float speed_mm_s, float travel_mm)
|
||||
{
|
||||
if (stepper == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (speed_mm_s < 0.1f)
|
||||
{
|
||||
speed_mm_s = 0.1f;
|
||||
}
|
||||
|
||||
if (stepper->state != STOP)
|
||||
{
|
||||
Int_TMC2209_stop();
|
||||
stepper->state = STOP;
|
||||
}
|
||||
|
||||
stepper->dir = dir;
|
||||
stepper->distance = travel_mm;
|
||||
stepper->speed = (uint16_t)speed_mm_s;
|
||||
stepper->acc = 50;
|
||||
stepper->dec = 50;
|
||||
stepper->start_speed = 1.0f;
|
||||
stepper->run_flag = 1u;
|
||||
last_move_start_pos_mm = current_absolute_pos_mm;
|
||||
|
||||
Int_TMC2209_start(stepper, &encoder_1);
|
||||
}
|
||||
|
||||
/* 将当前运动切换为减速停止。 */
|
||||
static void local_motion_stop_decel(Stepper_t *stepper)
|
||||
{
|
||||
if (stepper == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (stepper->state == STOP)
|
||||
{
|
||||
stepper->run_flag = 0u;
|
||||
return;
|
||||
}
|
||||
|
||||
stepper->total_step = stepper->step_count + stepper->acc_step;
|
||||
if (stepper->state == ACCELERATE || stepper->state == CONSTANT)
|
||||
{
|
||||
stepper->state = DECELERATE;
|
||||
}
|
||||
stepper->run_flag = 0u;
|
||||
}
|
||||
|
||||
/* 立即停止通道:用于 STOP 按键和 HOME 完成后的收尾。 */
|
||||
static void local_motion_estop(Stepper_t *stepper)
|
||||
{
|
||||
if (stepper == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Int_TMC2209_stop();
|
||||
stepper->state = STOP;
|
||||
stepper->run_flag = 0u;
|
||||
s_jog_key = KEY_NONE;
|
||||
s_home_active = 0u;
|
||||
}
|
||||
|
||||
/* 运行时守卫:运动中执行限位判断与 HOME 触发判断。 */
|
||||
static void key_guard_check(Stepper_t *stepper)
|
||||
{
|
||||
float est_pos_mm = 0.0f;
|
||||
uint8_t xzero_active = 0u;
|
||||
|
||||
if (stepper == NULL || stepper->state == STOP)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
est_pos_mm = key_estimate_pos_mm(stepper);
|
||||
current_absolute_pos_mm = est_pos_mm;
|
||||
xzero_active = key_home_switch_active();
|
||||
if ((stepper->x_zero == 1u) && (xzero_active == 0u))
|
||||
{
|
||||
/* 若引脚当前未触发,清除可能因抖动造成的 EXTI 残留置位。 */
|
||||
stepper->x_zero = 0u;
|
||||
}
|
||||
|
||||
if (s_home_active)
|
||||
{
|
||||
if ((stepper->x_zero == 1u) && (xzero_active == 1u))
|
||||
{
|
||||
local_motion_estop(stepper);
|
||||
stepper->x_zero = 0u;
|
||||
debug_printf("[KEY] HOME done");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if ((s_jog_key == KEY_2) && (est_pos_mm >= SOFT_LIMIT_MAX_MM))
|
||||
{
|
||||
local_motion_stop_decel(stepper);
|
||||
current_absolute_pos_mm = SOFT_LIMIT_MAX_MM;
|
||||
debug_printf("[KEY] JOG+ reach soft limit %.1f mm", SOFT_LIMIT_MAX_MM);
|
||||
}
|
||||
else if ((s_jog_key == KEY_3) &&
|
||||
((stepper->x_zero == 1u) && (xzero_active == 1u)))
|
||||
{
|
||||
local_motion_stop_decel(stepper);
|
||||
stepper->x_zero = 0u;
|
||||
debug_printf("[KEY] JOG- reach home boundary");
|
||||
}
|
||||
}
|
||||
|
||||
void App_key_run(Stepper_t *stepper)
|
||||
{
|
||||
Int_Key_Task();
|
||||
key_guard_check(stepper);
|
||||
|
||||
KeyAction_t action;
|
||||
while (Int_Key_PopAction(&action))
|
||||
{
|
||||
switch (action.key)
|
||||
{
|
||||
case KEY_1:
|
||||
if (action.evt == KEY_EVT_CLICK)
|
||||
{
|
||||
s_jog_key = KEY_NONE;
|
||||
s_home_active = 1u;
|
||||
stepper->x_zero = 0u;
|
||||
local_motion_start(stepper, GPIO_PIN_SET, HOME_SPEED_MM_S, HOME_TRAVEL_MM);
|
||||
debug_printf("[KEY] HOME start @ %.1f mm/s", HOME_SPEED_MM_S);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY_2:
|
||||
if (action.evt == KEY_EVT_LONG_START)
|
||||
{
|
||||
s_home_active = 0u;
|
||||
s_jog_key = KEY_2;
|
||||
local_motion_start(stepper, GPIO_PIN_RESET, s_speed_levels[s_speed_idx], HOME_TRAVEL_MM);
|
||||
debug_printf("[KEY] JOG+ start @ %.1f mm/s", s_speed_levels[s_speed_idx]);
|
||||
}
|
||||
else if (action.evt == KEY_EVT_RELEASE && s_jog_key == KEY_2)
|
||||
{
|
||||
local_motion_stop_decel(stepper);
|
||||
s_jog_key = KEY_NONE;
|
||||
debug_printf("[KEY] JOG+ release -> decel stop");
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY_3:
|
||||
if (action.evt == KEY_EVT_LONG_START)
|
||||
{
|
||||
s_home_active = 0u;
|
||||
s_jog_key = KEY_3;
|
||||
stepper->x_zero = 0u;
|
||||
local_motion_start(stepper, GPIO_PIN_SET, s_speed_levels[s_speed_idx], HOME_TRAVEL_MM);
|
||||
debug_printf("[KEY] JOG- start @ %.1f mm/s", s_speed_levels[s_speed_idx]);
|
||||
}
|
||||
else if (action.evt == KEY_EVT_RELEASE && s_jog_key == KEY_3)
|
||||
{
|
||||
local_motion_stop_decel(stepper);
|
||||
s_jog_key = KEY_NONE;
|
||||
debug_printf("[KEY] JOG- release -> decel stop");
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY_4:
|
||||
if (action.evt == KEY_EVT_CLICK)
|
||||
{
|
||||
s_speed_idx++;
|
||||
if (s_speed_idx >= SPEED_LEVEL_COUNT)
|
||||
{
|
||||
s_speed_idx = 0u;
|
||||
}
|
||||
debug_printf("[KEY] SPEED -> %.1f mm/s", s_speed_levels[s_speed_idx]);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY_5:
|
||||
if (action.evt == KEY_EVT_CLICK)
|
||||
{
|
||||
local_motion_estop(stepper);
|
||||
debug_printf("[KEY] STOP");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Application/app_key.h
Normal file
11
Application/app_key.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#ifndef __APP_KEY_H__
|
||||
#define __APP_KEY_H__
|
||||
|
||||
#include "Int_Key.h"
|
||||
#include "tim.h"
|
||||
#include "Int_TMC2209.h"
|
||||
#include "app_motor.h"
|
||||
|
||||
void App_key_run(Stepper_t *stepper);
|
||||
|
||||
#endif /* __APP_KEY_H__ */
|
||||
89
Application/app_main.c
Normal file
89
Application/app_main.c
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "main.h"
|
||||
#include "tim.h"
|
||||
#include "usart.h"
|
||||
#include "gpio.h"
|
||||
|
||||
#include "app_main.h"
|
||||
#include "Com_type.h"
|
||||
#include "Com_debug.h"
|
||||
#include "Int_Encoder.h"
|
||||
#include "Int_TMC2209.h"
|
||||
#include "app_key.h"
|
||||
#include "app_motor.h"
|
||||
#include "app_param_store.h"
|
||||
#include "app_test.h"
|
||||
#include "CANopen.h"
|
||||
#include "CO_app_STM32.h"
|
||||
#include "can.h"
|
||||
|
||||
Encoder_t encoder_1 = {0};
|
||||
|
||||
// 静音最大速度:65mm/s
|
||||
// 有效运动最大速度:350mm/s
|
||||
// 最远运行距离:230mm
|
||||
Stepper_t stepper_1 = {
|
||||
.dir = GPIO_PIN_RESET, // 方向(1为手轮方向,0为电机方向)
|
||||
.distance = 100.0f, // 距离mm
|
||||
.speed = 50.0f, // 速度mm/s
|
||||
.acc = 40.0f, // 加速度 mm/s^2
|
||||
.dec = 40.0f, // 减速度 mm/s^2
|
||||
.start_speed = 5.0f, // 启动速度
|
||||
};
|
||||
|
||||
CANopenNodeSTM32 CANopenNode;
|
||||
static void CANopenNode_init(void)
|
||||
{
|
||||
/* 初始化CANopen */
|
||||
CANopenNode.CANHandle = &hcan1; /* 使用CAN接口 */
|
||||
CANopenNode.HWInitFunction = MX_CAN1_Init; /* 初始化CAN */
|
||||
CANopenNode.timerHandle = &htim11; /* 使用的定时器句柄 */
|
||||
CANopenNode.desiredNodeID = 3; /* Node-ID */
|
||||
CANopenNode.baudrate = 500; /* 波特率,单位KHz */
|
||||
canopen_app_init(&CANopenNode);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
debug_printf("hello three-axis steppers");
|
||||
CANopenNode_init();
|
||||
App_Motor_Init();
|
||||
App_ParamStore_Init();
|
||||
(void)App_ParamStore_LoadAndApply();
|
||||
while (1)
|
||||
{
|
||||
App_key_run(&stepper_1);
|
||||
canopen_app_process();
|
||||
App_Motor_Process();
|
||||
App_ParamStore_Process();
|
||||
}
|
||||
}
|
||||
|
||||
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
|
||||
{
|
||||
switch (GPIO_Pin)
|
||||
{
|
||||
// PE2对应X_ZERO引脚
|
||||
case X_ZERO_Pin:
|
||||
stepper_1.x_zero = 1;
|
||||
break;
|
||||
// PC9对应编码器1的Z引脚
|
||||
case ENCODER1_Z_Pin:
|
||||
encoder_1.z++;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
|
||||
{
|
||||
if (htim == CANopenNode.timerHandle)
|
||||
{
|
||||
canopen_app_interrupt();
|
||||
}
|
||||
|
||||
if (htim->Instance == TIM12)
|
||||
{
|
||||
App_Motor_StepLossCheck();
|
||||
}
|
||||
}
|
||||
10
Application/app_main.h
Normal file
10
Application/app_main.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#ifndef __APP_MAIN_H__
|
||||
#define __APP_MAIN_H__
|
||||
|
||||
void app_main(void);
|
||||
|
||||
#endif /* __APP_MAIN_H__ */
|
||||
|
||||
|
||||
|
||||
|
||||
292
Application/app_motor.c
Normal file
292
Application/app_motor.c
Normal file
@@ -0,0 +1,292 @@
|
||||
#include "app_motor.h"
|
||||
|
||||
/* 当前位置相关:用于CiA402位置反馈 */
|
||||
float current_absolute_pos_mm = 0.0f;
|
||||
float last_move_start_pos_mm = 0.0f;
|
||||
|
||||
/* 电机内部状态机 */
|
||||
Motor_State_t internal_state = STATE_SWITCH_ON_DISABLED;
|
||||
Homing_State_t homing_state = HOMING_IDLE;
|
||||
|
||||
/* 兼容旧调试变量:值由StepLossMonitor同步更新 */
|
||||
float error = 0.0f;
|
||||
float distance = 0.0f;
|
||||
float encoder_distance = 0.0f;
|
||||
uint32_t steps = 0;
|
||||
|
||||
#define STEPLOSS_SETTLE_MS 200U
|
||||
#define STEPLOSS_THRESHOLD_MM_DEFAULT 0.1f
|
||||
|
||||
/* 丢步检测运行态:集中管理,避免状态分散在多个全局变量 */
|
||||
typedef struct
|
||||
{
|
||||
/* 实时观测量 */
|
||||
float abs_error_mm;
|
||||
float cmd_distance_mm;
|
||||
float enc_distance_mm;
|
||||
uint32_t cmd_steps;
|
||||
|
||||
/* 编码器16位计数扩展 */
|
||||
int32_t accum_pulses;
|
||||
uint16_t prev_raw_cnt;
|
||||
uint8_t prev_raw_valid;
|
||||
uint8_t motion_active;
|
||||
|
||||
/* 停止后延时上报 */
|
||||
uint8_t wait_stop_settle;
|
||||
uint32_t stop_tick_ms;
|
||||
uint8_t report_ready;
|
||||
float report_error_mm;
|
||||
float threshold_mm;
|
||||
Stepper_state_t prev_state;
|
||||
} StepLossMonitor_t;
|
||||
|
||||
static StepLossMonitor_t s_step_loss = {
|
||||
.threshold_mm = STEPLOSS_THRESHOLD_MM_DEFAULT,
|
||||
.prev_state = STOP,
|
||||
};
|
||||
|
||||
static float Normalize_FollowingErrorThreshold(float threshold_mm, float fallback_mm)
|
||||
{
|
||||
if (!(threshold_mm == threshold_mm))
|
||||
{
|
||||
return fallback_mm;
|
||||
}
|
||||
|
||||
if (threshold_mm <= 0.0f)
|
||||
{
|
||||
return fallback_mm;
|
||||
}
|
||||
|
||||
if (threshold_mm > SOFT_LIMIT_MAX_MM)
|
||||
{
|
||||
return SOFT_LIMIT_MAX_MM;
|
||||
}
|
||||
|
||||
return threshold_mm;
|
||||
}
|
||||
|
||||
static void Sync_FollowingErrorThreshold_From_OD(void)
|
||||
{
|
||||
OD_entry_t *entry_ferr = OD_find(OD, CIA402_INDEX_FOLLOWING_ERROR_WINDOW);
|
||||
|
||||
if (entry_ferr == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float32_t threshold_mm = 0.0f;
|
||||
if (OD_get_f32(entry_ferr, 0, &threshold_mm, true) == ODR_OK)
|
||||
{
|
||||
s_step_loss.threshold_mm =
|
||||
Normalize_FollowingErrorThreshold((float)threshold_mm, s_step_loss.threshold_mm);
|
||||
}
|
||||
}
|
||||
|
||||
static void Update_PP_FollowingError_StatusBit(float stop_error_mm)
|
||||
{
|
||||
OD_entry_t *entry_mode = OD_find(OD, CIA402_INDEX_OP_MODE);
|
||||
OD_entry_t *entry_sw = OD_find(OD, CIA402_INDEX_STATUSWORD);
|
||||
|
||||
if (entry_mode == NULL || entry_sw == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int8_t mode = 0;
|
||||
OD_get_i8(entry_mode, 0, &mode, true);
|
||||
if (mode != 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CO_LOCK_OD(CO->CANmodule);
|
||||
uint16_t sw = 0;
|
||||
OD_get_u16(entry_sw, 0, &sw, true);
|
||||
|
||||
if (stop_error_mm > s_step_loss.threshold_mm)
|
||||
{
|
||||
sw |= CIA402_STATUS_OMS_13;
|
||||
}
|
||||
else
|
||||
{
|
||||
sw &= (uint16_t)(~CIA402_STATUS_OMS_13);
|
||||
}
|
||||
|
||||
OD_set_u16(entry_sw, 0, sw, true);
|
||||
CO_UNLOCK_OD(CO->CANmodule);
|
||||
}
|
||||
|
||||
/* 核心运动分发:先做安全判断,再按模式进入回零/位置模式处理 */
|
||||
static void Process_Motion_Logic(void)
|
||||
{
|
||||
/* 未使能运行时,强制停机并维持状态反馈 */
|
||||
if ((internal_state != STATE_OPERATION_ENABLED) && (stepper_1.run_flag == 0u))
|
||||
{
|
||||
if (stepper_1.state != STOP)
|
||||
{
|
||||
Int_TMC2209_stop();
|
||||
stepper_1.state = STOP;
|
||||
}
|
||||
homing_state = HOMING_IDLE;
|
||||
Update_Motion_State_To_OD();
|
||||
return;
|
||||
}
|
||||
|
||||
Update_Motion_State_To_OD();
|
||||
|
||||
OD_entry_t *entry_mode = OD_find(OD, CIA402_INDEX_OP_MODE);
|
||||
OD_entry_t *entry_mode_disp = OD_find(OD, CIA402_INDEX_OP_MODE_DISPLAY);
|
||||
|
||||
if (entry_mode != NULL)
|
||||
{
|
||||
int8_t mode = 0;
|
||||
OD_get_i8(entry_mode, 0, &mode, true);
|
||||
|
||||
if (entry_mode_disp != NULL)
|
||||
{
|
||||
OD_set_i8(entry_mode_disp, 0, mode, true);
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case 6:
|
||||
Handle_Homing_Mode();
|
||||
break;
|
||||
case 1:
|
||||
Handle_Position_Mode();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void App_Motor_Init(void)
|
||||
{
|
||||
/* 统一初始化运行态与位置基准 */
|
||||
current_absolute_pos_mm = 0.0f;
|
||||
last_move_start_pos_mm = 0.0f;
|
||||
homing_state = HOMING_IDLE;
|
||||
internal_state = STATE_SWITCH_ON_DISABLED;
|
||||
Int_Encoder_Init();
|
||||
debug_printf("App Motor Initialized.");
|
||||
}
|
||||
|
||||
void App_Motor_Process(void)
|
||||
{
|
||||
Sync_FollowingErrorThreshold_From_OD();
|
||||
|
||||
/* 先跑CiA402状态机,再执行业务运动逻辑 */
|
||||
Process_StateMachine();
|
||||
Process_Motion_Logic();
|
||||
|
||||
float stop_error_mm = 0.0f;
|
||||
if (App_Motor_TryGetStopError(&stop_error_mm))
|
||||
{
|
||||
Update_PP_FollowingError_StatusBit(stop_error_mm);
|
||||
|
||||
/* 回零流程不输出StepLoss误差日志,避免与“回零成功/失败”语义混淆 */
|
||||
if (homing_state == HOMING_IDLE)
|
||||
{
|
||||
debug_printf("[StepLoss] stop+200ms error = %.6f mm", stop_error_mm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void App_Motor_StepLossCheck(void)
|
||||
{
|
||||
/* 周期读取硬件计数与已发步数(TIM12中断调用) */
|
||||
uint16_t raw_cnt = (uint16_t)__HAL_TIM_GET_COUNTER(&htim1);
|
||||
s_step_loss.cmd_steps = stepper_1.step_count;
|
||||
|
||||
/* 新动作开始时重置累计基准,避免跨动作串扰 */
|
||||
if (stepper_1.state != STOP)
|
||||
{
|
||||
if (!s_step_loss.motion_active)
|
||||
{
|
||||
s_step_loss.motion_active = 1U;
|
||||
s_step_loss.accum_pulses = 0;
|
||||
s_step_loss.prev_raw_cnt = raw_cnt;
|
||||
s_step_loss.prev_raw_valid = 1U;
|
||||
}
|
||||
}
|
||||
else if (s_step_loss.motion_active)
|
||||
{
|
||||
s_step_loss.motion_active = 0U;
|
||||
}
|
||||
|
||||
/* 首次采样仅建立基准,不计算位移增量 */
|
||||
if (!s_step_loss.prev_raw_valid)
|
||||
{
|
||||
s_step_loss.prev_raw_cnt = raw_cnt;
|
||||
s_step_loss.prev_raw_valid = 1U;
|
||||
}
|
||||
|
||||
/* int16差分可自然处理16位计数器回绕(溢出/下溢) */
|
||||
int16_t delta = (int16_t)(raw_cnt - s_step_loss.prev_raw_cnt);
|
||||
s_step_loss.accum_pulses += (int32_t)delta;
|
||||
s_step_loss.prev_raw_cnt = raw_cnt;
|
||||
|
||||
/* 输出扩展后的总脉冲给上层使用 */
|
||||
encoder_1.pulses = s_step_loss.accum_pulses;
|
||||
encoder_1.overflow = (uint16_t)(abs(encoder_1.pulses) / 65536);
|
||||
|
||||
/* 统一换算为mm并计算绝对误差 */
|
||||
s_step_loss.cmd_distance_mm = (float)s_step_loss.cmd_steps / STEPS_PER_MM;
|
||||
s_step_loss.enc_distance_mm = (float)abs(encoder_1.pulses) * STICK_LEAD / (float)ENCODER_PULSES_PER_CIRCLE;
|
||||
s_step_loss.abs_error_mm = s_step_loss.enc_distance_mm - s_step_loss.cmd_distance_mm;
|
||||
if (s_step_loss.abs_error_mm < 0.0f)
|
||||
{
|
||||
s_step_loss.abs_error_mm = -s_step_loss.abs_error_mm;
|
||||
}
|
||||
|
||||
/* 同步旧调试变量,便于观察窗口/旧日志继续使用 */
|
||||
steps = s_step_loss.cmd_steps;
|
||||
distance = s_step_loss.cmd_distance_mm;
|
||||
encoder_distance = s_step_loss.enc_distance_mm;
|
||||
error = s_step_loss.abs_error_mm;
|
||||
|
||||
/* 运动中不出最终结论,仅标记“停稳后需要上报一次” */
|
||||
if (stepper_1.state != STOP)
|
||||
{
|
||||
s_step_loss.wait_stop_settle = 1U;
|
||||
s_step_loss.report_ready = 0U;
|
||||
s_step_loss.prev_state = stepper_1.state;
|
||||
return;
|
||||
}
|
||||
|
||||
/* 从运动态切换到STOP时,开始计时稳定窗口 */
|
||||
if (s_step_loss.prev_state != STOP)
|
||||
{
|
||||
s_step_loss.prev_state = STOP;
|
||||
s_step_loss.stop_tick_ms = HAL_GetTick();
|
||||
return;
|
||||
}
|
||||
|
||||
/* STOP持续到阈值后,锁存一次最终误差 */
|
||||
if (s_step_loss.wait_stop_settle &&
|
||||
((uint32_t)(HAL_GetTick() - s_step_loss.stop_tick_ms) >= STEPLOSS_SETTLE_MS))
|
||||
{
|
||||
s_step_loss.report_error_mm = s_step_loss.abs_error_mm;
|
||||
s_step_loss.report_ready = 1U;
|
||||
s_step_loss.wait_stop_settle = 0U;
|
||||
}
|
||||
}
|
||||
|
||||
/* 一次性读取“停止后最终误差”;读取成功后自动清ready标志 */
|
||||
bool App_Motor_TryGetStopError(float *err_mm)
|
||||
{
|
||||
if (!s_step_loss.report_ready)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (err_mm != NULL)
|
||||
{
|
||||
*err_mm = s_step_loss.report_error_mm;
|
||||
}
|
||||
|
||||
s_step_loss.report_ready = 0U;
|
||||
return true;
|
||||
}
|
||||
51
Application/app_motor.h
Normal file
51
Application/app_motor.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#ifndef __APP_MOTOR_H__
|
||||
#define __APP_MOTOR_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "CANopen.h"
|
||||
#include "OD.h"
|
||||
#include "Int_TMC2209.h"
|
||||
#include "Com_debug.h"
|
||||
#include "CiA402_defs.h"
|
||||
#include "app_CiA402.h"
|
||||
#include <stdlib.h>
|
||||
#include "Int_Encoder.h"
|
||||
|
||||
// --- 全局变量引用 ---
|
||||
|
||||
extern Stepper_t stepper_1; // 引用在 app_main.c 中定义的电机对象
|
||||
extern Encoder_t encoder_1; // 引用在 app_main.c 中定义的编码器对象
|
||||
extern CO_t *CO; // 引用在 CO_app_STM32.c 中定义的 CANopen 对象
|
||||
|
||||
// 记录当前的绝对位置 (mm)
|
||||
extern float current_absolute_pos_mm; // 当前绝对位置
|
||||
extern float last_move_start_pos_mm; // 每次动作开始时的绝对位置
|
||||
|
||||
// 两个状态机,分别对应位置模式和回零模式,枚举类型的定义在CiA402_defs.h 中定义
|
||||
extern Motor_State_t internal_state;
|
||||
extern Homing_State_t homing_state;
|
||||
|
||||
// --- 函数声明 ---
|
||||
/**
|
||||
* @brief 电机应用层初始化
|
||||
*/
|
||||
void App_Motor_Init(void);
|
||||
|
||||
/**
|
||||
* @brief 电机应用层周期性处理函数
|
||||
*/
|
||||
void App_Motor_Process(void);
|
||||
|
||||
/**
|
||||
* @brief 在定时器终端中周期判断电机是否丢步
|
||||
*
|
||||
*/
|
||||
void App_Motor_StepLossCheck(void);
|
||||
|
||||
/**
|
||||
* @brief 获取“停止后延时窗口”评估出的误差(mm),读取成功返回 true
|
||||
*/
|
||||
bool App_Motor_TryGetStopError(float *err_mm);
|
||||
|
||||
#endif /* __APP_MOTOR_H__ */
|
||||
402
Application/app_param_store.c
Normal file
402
Application/app_param_store.c
Normal file
@@ -0,0 +1,402 @@
|
||||
#include "app_param_store.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "Com_debug.h"
|
||||
#include "app_CiA402.h"
|
||||
#include "Int_EEPROM24xx.h"
|
||||
#include "crc.h"
|
||||
|
||||
/* 参数块格式:
|
||||
* 固定头(magic/version/len) + 业务参数 + CRC32
|
||||
* 用于上电校验,防止读取到脏数据。
|
||||
*/
|
||||
#define APP_PARAM_MAGIC 0x5250414DU /* "MPAR" */
|
||||
#define APP_PARAM_VERSION 0x0001u
|
||||
#define APP_PARAM_EEPROM_ADDR 0x0000u
|
||||
#define APP_PARAM_AUTOSAVE_DEBOUNCE_MS 500u
|
||||
|
||||
/* Log level: 0=off, 1=error, 2=info */
|
||||
#define APP_PARAM_STORE_LOG_LEVEL 2
|
||||
#if APP_PARAM_STORE_LOG_LEVEL >= 2
|
||||
#define APP_PS_LOGI(...) debug_printf(__VA_ARGS__)
|
||||
#else
|
||||
#define APP_PS_LOGI(...) ((void)0)
|
||||
#endif
|
||||
#if APP_PARAM_STORE_LOG_LEVEL >= 1
|
||||
#define APP_PS_LOGE(...) debug_printf(__VA_ARGS__)
|
||||
#else
|
||||
#define APP_PS_LOGE(...) ((void)0)
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* blob = 二进制数据块(Binary Large Object):
|
||||
* 这里表示“整块写入/读出 EEPROM 的参数记录”。
|
||||
*/
|
||||
uint32_t magic;
|
||||
uint16_t version;
|
||||
uint16_t payload_len;
|
||||
uint16_t acc_mm_s2;
|
||||
uint16_t dec_mm_s2;
|
||||
float step_loss_threshold_mm;
|
||||
uint32_t crc32;
|
||||
} App_ParamBlob_t;
|
||||
|
||||
static uint32_t g_eeprom_i2c_last_err = 0u;
|
||||
static App_RunParams_t g_last_seen_params = {0};
|
||||
static uint8_t g_last_seen_valid = 0u;
|
||||
static uint8_t g_autosave_dirty = 0u;
|
||||
static uint32_t g_autosave_due_ms = 0u;
|
||||
|
||||
/* 使用STM32硬件CRC外设计算CRC32。 */
|
||||
static uint32_t App_ParamStore_Crc32(const uint8_t *data, uint32_t len)
|
||||
{
|
||||
uint32_t idx = 0u;
|
||||
uint32_t word = 0u;
|
||||
uint32_t crc = 0u;
|
||||
uint8_t first_word = 1u;
|
||||
|
||||
if ((data == 0) || (len == 0u))
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
__HAL_CRC_DR_RESET(&hcrc);
|
||||
|
||||
while (idx < len)
|
||||
{
|
||||
uint8_t b0 = data[idx++];
|
||||
uint8_t b1 = (idx < len) ? data[idx++] : 0u;
|
||||
uint8_t b2 = (idx < len) ? data[idx++] : 0u;
|
||||
uint8_t b3 = (idx < len) ? data[idx++] : 0u;
|
||||
|
||||
word = ((uint32_t)b0) |
|
||||
((uint32_t)b1 << 8) |
|
||||
((uint32_t)b2 << 16) |
|
||||
((uint32_t)b3 << 24);
|
||||
|
||||
if (first_word != 0u)
|
||||
{
|
||||
crc = HAL_CRC_Calculate(&hcrc, &word, 1u);
|
||||
first_word = 0u;
|
||||
}
|
||||
else
|
||||
{
|
||||
crc = HAL_CRC_Accumulate(&hcrc, &word, 1u);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
|
||||
}
|
||||
|
||||
static bool App_ParamStore_Validate(const App_ParamBlob_t *blob)
|
||||
{
|
||||
uint32_t calc_crc;
|
||||
|
||||
/* 先做结构字段检查,再做CRC校验。 */
|
||||
if (blob->magic != APP_PARAM_MAGIC)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (blob->version != APP_PARAM_VERSION)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (blob->payload_len != (uint16_t)(sizeof(App_ParamBlob_t) - sizeof(uint32_t)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((blob->acc_mm_s2 == 0u) || (blob->dec_mm_s2 == 0u))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((blob->step_loss_threshold_mm <= 0.0f) || (blob->step_loss_threshold_mm > SOFT_LIMIT_MAX_MM))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
calc_crc = App_ParamStore_Crc32((const uint8_t *)blob, (uint32_t)(sizeof(App_ParamBlob_t) - sizeof(uint32_t)));
|
||||
return (calc_crc == blob->crc32);
|
||||
}
|
||||
|
||||
static uint32_t App_ParamStore_CalcBlobCrc(const App_ParamBlob_t *blob)
|
||||
{
|
||||
return App_ParamStore_Crc32((const uint8_t *)blob, (uint32_t)(sizeof(App_ParamBlob_t) - sizeof(uint32_t)));
|
||||
}
|
||||
|
||||
static void App_ParamStore_ApplyThresholdToOD(float threshold_mm)
|
||||
{
|
||||
/* 将丢步阈值同步到OD,CANopen运行时需加锁访问。 */
|
||||
OD_entry_t *entry = OD_find(OD, CIA402_INDEX_FOLLOWING_ERROR_WINDOW);
|
||||
if (entry == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (CO != 0)
|
||||
{
|
||||
CO_LOCK_OD(CO->CANmodule);
|
||||
(void)OD_set_f32(entry, 0, threshold_mm, true);
|
||||
CO_UNLOCK_OD(CO->CANmodule);
|
||||
}
|
||||
else
|
||||
{
|
||||
(void)OD_set_f32(entry, 0, threshold_mm, true);
|
||||
}
|
||||
}
|
||||
|
||||
static void App_ParamStore_ApplyAccDecToOD(uint16_t acc_mm_s2, uint16_t dec_mm_s2)
|
||||
{
|
||||
/* 将加减速度同步到OD,保持网关与本地参数一致。 */
|
||||
OD_entry_t *entry_acc = OD_find(OD, CIA402_INDEX_PROFILE_ACC);
|
||||
OD_entry_t *entry_dec = OD_find(OD, CIA402_INDEX_PROFILE_DEC);
|
||||
if ((entry_acc == 0) || (entry_dec == 0))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (CO != 0)
|
||||
{
|
||||
CO_LOCK_OD(CO->CANmodule);
|
||||
(void)OD_set_u32(entry_acc, 0, (uint32_t)acc_mm_s2, true);
|
||||
(void)OD_set_u32(entry_dec, 0, (uint32_t)dec_mm_s2, true);
|
||||
CO_UNLOCK_OD(CO->CANmodule);
|
||||
}
|
||||
else
|
||||
{
|
||||
(void)OD_set_u32(entry_acc, 0, (uint32_t)acc_mm_s2, true);
|
||||
(void)OD_set_u32(entry_dec, 0, (uint32_t)dec_mm_s2, true);
|
||||
}
|
||||
}
|
||||
|
||||
static bool App_ParamStore_ReadCurrentFromOD(App_RunParams_t *params)
|
||||
{
|
||||
OD_entry_t *entry_acc = OD_find(OD, CIA402_INDEX_PROFILE_ACC);
|
||||
OD_entry_t *entry_dec = OD_find(OD, CIA402_INDEX_PROFILE_DEC);
|
||||
OD_entry_t *entry_ferr = OD_find(OD, CIA402_INDEX_FOLLOWING_ERROR_WINDOW);
|
||||
uint32_t acc_raw = 0u;
|
||||
uint32_t dec_raw = 0u;
|
||||
float32_t ferr_raw = 0.0f;
|
||||
|
||||
if ((params == 0) || (entry_acc == 0) || (entry_dec == 0) || (entry_ferr == 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CO != 0)
|
||||
{
|
||||
CO_LOCK_OD(CO->CANmodule);
|
||||
(void)OD_get_u32(entry_acc, 0, &acc_raw, true);
|
||||
(void)OD_get_u32(entry_dec, 0, &dec_raw, true);
|
||||
(void)OD_get_f32(entry_ferr, 0, &ferr_raw, true);
|
||||
CO_UNLOCK_OD(CO->CANmodule);
|
||||
}
|
||||
else
|
||||
{
|
||||
(void)OD_get_u32(entry_acc, 0, &acc_raw, true);
|
||||
(void)OD_get_u32(entry_dec, 0, &dec_raw, true);
|
||||
(void)OD_get_f32(entry_ferr, 0, &ferr_raw, true);
|
||||
}
|
||||
|
||||
if ((acc_raw == 0u) || (acc_raw > 65535u) || (dec_raw == 0u) || (dec_raw > 65535u))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (((float)ferr_raw <= 0.0f) || ((float)ferr_raw > SOFT_LIMIT_MAX_MM))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
params->acc_mm_s2 = (uint16_t)acc_raw;
|
||||
params->dec_mm_s2 = (uint16_t)dec_raw;
|
||||
params->step_loss_threshold_mm = (float)ferr_raw;
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t App_ParamStore_ParamsEqual(const App_RunParams_t *a, const App_RunParams_t *b)
|
||||
{
|
||||
float diff;
|
||||
|
||||
if ((a == 0) || (b == 0))
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
if ((a->acc_mm_s2 != b->acc_mm_s2) || (a->dec_mm_s2 != b->dec_mm_s2))
|
||||
{
|
||||
return 0u;
|
||||
}
|
||||
|
||||
diff = a->step_loss_threshold_mm - b->step_loss_threshold_mm;
|
||||
if (diff < 0.0f)
|
||||
{
|
||||
diff = -diff;
|
||||
}
|
||||
return (diff <= 0.0001f) ? 1u : 0u;
|
||||
}
|
||||
|
||||
void App_ParamStore_Init(void)
|
||||
{
|
||||
/* EEPROM驱动初始化:参数由Int_EEPROM24xx内部宏配置。 */
|
||||
if (Int_EEPROM24xx_Init() != INT_EEPROM_OK)
|
||||
{
|
||||
g_eeprom_i2c_last_err = Int_EEPROM24xx_GetLastHalError();
|
||||
APP_PS_LOGE("[EEPROM] init failed");
|
||||
}
|
||||
|
||||
g_last_seen_valid = 0u;
|
||||
g_autosave_dirty = 0u;
|
||||
g_autosave_due_ms = 0u;
|
||||
}
|
||||
|
||||
bool App_ParamStore_Read(App_RunParams_t *params)
|
||||
{
|
||||
App_ParamBlob_t blob;
|
||||
uint32_t calc_crc;
|
||||
|
||||
if (params == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 从固定地址读出完整参数块并校验。 */
|
||||
if (Int_EEPROM24xx_Read(APP_PARAM_EEPROM_ADDR, (uint8_t *)&blob, (uint16_t)sizeof(blob)) != INT_EEPROM_OK)
|
||||
{
|
||||
g_eeprom_i2c_last_err = Int_EEPROM24xx_GetLastHalError();
|
||||
APP_PS_LOGE("[EEPROM] read failed, i2c_err=0x%08lX", (unsigned long)g_eeprom_i2c_last_err);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!App_ParamStore_Validate(&blob))
|
||||
{
|
||||
calc_crc = App_ParamStore_CalcBlobCrc(&blob);
|
||||
APP_PS_LOGI("[EEPROM] invalid blob: magic=0x%08lX ver=%u len=%u acc=%u dec=%u ferr=%.4f crc=0x%08lX calc=0x%08lX",
|
||||
(unsigned long)blob.magic,
|
||||
(unsigned int)blob.version,
|
||||
(unsigned int)blob.payload_len,
|
||||
(unsigned int)blob.acc_mm_s2,
|
||||
(unsigned int)blob.dec_mm_s2,
|
||||
(double)blob.step_loss_threshold_mm,
|
||||
(unsigned long)blob.crc32,
|
||||
(unsigned long)calc_crc);
|
||||
return false;
|
||||
}
|
||||
|
||||
params->acc_mm_s2 = blob.acc_mm_s2;
|
||||
params->dec_mm_s2 = blob.dec_mm_s2;
|
||||
params->step_loss_threshold_mm = blob.step_loss_threshold_mm;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool App_ParamStore_LoadAndApply(void)
|
||||
{
|
||||
App_RunParams_t p;
|
||||
|
||||
/* 上电读取成功后,应用到运行参数与OD对象。 */
|
||||
if (!App_ParamStore_Read(&p))
|
||||
{
|
||||
APP_PS_LOGI("[EEPROM] no valid params in EEPROM, keep defaults");
|
||||
return false;
|
||||
}
|
||||
|
||||
stepper_1.acc = p.acc_mm_s2;
|
||||
stepper_1.dec = p.dec_mm_s2;
|
||||
App_ParamStore_ApplyAccDecToOD(p.acc_mm_s2, p.dec_mm_s2);
|
||||
App_ParamStore_ApplyThresholdToOD(p.step_loss_threshold_mm);
|
||||
|
||||
APP_PS_LOGI("[EEPROM] loaded acc=%u dec=%u ferr=%.4f",
|
||||
(unsigned int)p.acc_mm_s2,
|
||||
(unsigned int)p.dec_mm_s2,
|
||||
(double)p.step_loss_threshold_mm);
|
||||
g_last_seen_params = p;
|
||||
g_last_seen_valid = 1u;
|
||||
g_autosave_dirty = 0u;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool App_ParamStore_Save(const App_RunParams_t *params)
|
||||
{
|
||||
App_ParamBlob_t blob;
|
||||
|
||||
/* 写入前先做业务范围检查。 */
|
||||
if (params == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((params->acc_mm_s2 == 0u) || (params->dec_mm_s2 == 0u))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ((params->step_loss_threshold_mm <= 0.0f) || (params->step_loss_threshold_mm > SOFT_LIMIT_MAX_MM))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
memset(&blob, 0, sizeof(blob));
|
||||
blob.magic = APP_PARAM_MAGIC;
|
||||
blob.version = APP_PARAM_VERSION;
|
||||
blob.payload_len = (uint16_t)(sizeof(App_ParamBlob_t) - sizeof(uint32_t));
|
||||
blob.acc_mm_s2 = params->acc_mm_s2;
|
||||
blob.dec_mm_s2 = params->dec_mm_s2;
|
||||
blob.step_loss_threshold_mm = params->step_loss_threshold_mm;
|
||||
blob.crc32 = App_ParamStore_Crc32((const uint8_t *)&blob, (uint32_t)(sizeof(App_ParamBlob_t) - sizeof(uint32_t)));
|
||||
|
||||
/* 写入固定地址,供下次上电加载。 */
|
||||
if (Int_EEPROM24xx_Write(APP_PARAM_EEPROM_ADDR, (const uint8_t *)&blob, (uint16_t)sizeof(blob)) != INT_EEPROM_OK)
|
||||
{
|
||||
g_eeprom_i2c_last_err = Int_EEPROM24xx_GetLastHalError();
|
||||
APP_PS_LOGE("[EEPROM] save failed, i2c_err=0x%08lX", (unsigned long)g_eeprom_i2c_last_err);
|
||||
return false;
|
||||
}
|
||||
|
||||
APP_PS_LOGI("[EEPROM] saved acc=%u dec=%u ferr=%.4f",
|
||||
(unsigned int)params->acc_mm_s2,
|
||||
(unsigned int)params->dec_mm_s2,
|
||||
(double)params->step_loss_threshold_mm);
|
||||
return true;
|
||||
}
|
||||
|
||||
void App_ParamStore_Process(void)
|
||||
{
|
||||
App_RunParams_t current;
|
||||
uint32_t now_ms;
|
||||
|
||||
if (!App_ParamStore_ReadCurrentFromOD(¤t))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
now_ms = HAL_GetTick();
|
||||
|
||||
if (g_last_seen_valid == 0u)
|
||||
{
|
||||
g_last_seen_params = current;
|
||||
g_last_seen_valid = 1u;
|
||||
return;
|
||||
}
|
||||
|
||||
if (App_ParamStore_ParamsEqual(¤t, &g_last_seen_params) == 0u)
|
||||
{
|
||||
g_last_seen_params = current;
|
||||
g_autosave_dirty = 1u;
|
||||
g_autosave_due_ms = now_ms + APP_PARAM_AUTOSAVE_DEBOUNCE_MS;
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_autosave_dirty == 0u)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ((int32_t)(now_ms - g_autosave_due_ms) < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (App_ParamStore_Save(&g_last_seen_params))
|
||||
{
|
||||
g_autosave_dirty = 0u;
|
||||
}
|
||||
}
|
||||
13
Application/app_param_store.h
Normal file
13
Application/app_param_store.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef APP_PARAM_STORE_H
|
||||
#define APP_PARAM_STORE_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "Com_type.h"
|
||||
|
||||
void App_ParamStore_Init(void);
|
||||
bool App_ParamStore_LoadAndApply(void);
|
||||
bool App_ParamStore_Save(const App_RunParams_t *params);
|
||||
bool App_ParamStore_Read(App_RunParams_t *params);
|
||||
void App_ParamStore_Process(void);
|
||||
|
||||
#endif
|
||||
68
Application/app_test.c
Normal file
68
Application/app_test.c
Normal file
@@ -0,0 +1,68 @@
|
||||
#include "app_test.h"
|
||||
|
||||
uint8_t rx_buf[32]; // RS485 receive buffer
|
||||
uint8_t rs_rx_flag = 0; // RS485 receive flag
|
||||
uint8_t rs_rx_len; // RS485 receive length
|
||||
|
||||
void app_RS485_test(void)
|
||||
{
|
||||
HAL_UARTEx_ReceiveToIdle_IT(&huart3, rx_buf, 32);
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (rs_rx_flag)
|
||||
{
|
||||
debug_printf("RS485 RX Recv (%d bytes): %c %c %c", rs_rx_len, rx_buf[0], rx_buf[1], rx_buf[2]);
|
||||
HAL_UART_Transmit(&huart3, rx_buf, rs_rx_len, 1000);
|
||||
rs_rx_flag = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void app_big_laser_test(void)
|
||||
{
|
||||
HAL_GPIO_WritePin(POWER_12V_EN_GPIO_Port, POWER_12V_EN_Pin, GPIO_PIN_SET);
|
||||
HAL_TIM_PWM_Start(&htim10, TIM_CHANNEL_1);
|
||||
while (1)
|
||||
{
|
||||
for (uint8_t pulse = 0; pulse < 100; pulse += 10)
|
||||
{
|
||||
__HAL_TIM_SetCompare(&htim10, TIM_CHANNEL_1, pulse);
|
||||
HAL_Delay(10);
|
||||
}
|
||||
for (uint8_t pulse = 100; pulse > 0; pulse -= 10)
|
||||
{
|
||||
__HAL_TIM_SetCompare(&htim10, TIM_CHANNEL_1, pulse);
|
||||
HAL_Delay(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void app_small_laser_test(void)
|
||||
{
|
||||
HAL_GPIO_WritePin(POWER_5V_EN_GPIO_Port, POWER_5V_EN_Pin, GPIO_PIN_SET);
|
||||
HAL_TIM_PWM_Start(&htim10, TIM_CHANNEL_1);
|
||||
while (1)
|
||||
{
|
||||
for (uint8_t pulse = 0; pulse < 100; pulse += 10)
|
||||
{
|
||||
__HAL_TIM_SetCompare(&htim10, TIM_CHANNEL_1, pulse);
|
||||
HAL_Delay(10);
|
||||
}
|
||||
for (uint8_t pulse = 100; pulse > 0; pulse -= 10)
|
||||
{
|
||||
__HAL_TIM_SetCompare(&htim10, TIM_CHANNEL_1, pulse);
|
||||
HAL_Delay(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
|
||||
{
|
||||
if (huart->Instance == USART3)
|
||||
{
|
||||
rs_rx_flag = 1;
|
||||
rs_rx_len = Size;
|
||||
HAL_UARTEx_ReceiveToIdle_IT(&huart3, rx_buf, 32);
|
||||
}
|
||||
}
|
||||
17
Application/app_test.h
Normal file
17
Application/app_test.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#ifndef __APP_TEST_H__
|
||||
#define __APP_TEST_H__
|
||||
|
||||
#include "usart.h"
|
||||
#include "gpio.h"
|
||||
#include "tim.h"
|
||||
#include "Com_debug.h"
|
||||
#include "Com_type.h"
|
||||
|
||||
//本文件放置模块测试代码
|
||||
void app_RS485_test(void);
|
||||
|
||||
void app_big_laser_test(void);
|
||||
|
||||
void app_small_laser_test(void);
|
||||
|
||||
#endif /* __APP_TEST_H__ */
|
||||
Reference in New Issue
Block a user