side.cpp

  • 构造函数
    • 源代码
  • run_side - 核心
  • read_data()
    • 源代码
    • FSR压力传感器读取与赋值
    • 步态事件检测:落地(ground_strike)
    • 步态周期自适应:期望步长更新
    • Toe-Off/Toe-On事件检测与站立/摆动窗口更新
    • 步态百分比进度估算
    • FSR阈值动态读取(可能受标定/漂移影响)
    • 坡度/倾角检测
    • 关节级传感器/数据读取
  • check_calibration()
    • 源代码
    • 整体结构:只对启用侧做标定
    • FSR(压力传感器)脚趾标定与精细化标定
    • FSR脚跟标定与精细化标定
    • 关节(Hip/Knee/Ankle/Elbow)逐个检查并执行标定
  • _check_ground_strike()
    • 源代码
    • 保存上一次heel/toe接触状态
    • 读取当前heel/toe的触地状态
    • 同步当前heel/toe状态到side全局数据
    • 步态事件标志初始化
    • 实时记录toe strike事件
    • 只在摆动相(悬空)检测ground strike
    • 检测heel或toe的上升沿(rising edge)
  • 更新历史状态
    • 返回ground strike事件标志
  • _check_toe_on()
    • 源代码
    • 变量初始化
    • 检测 toe on 上升沿
    • 保存本次 toe 状态
    • 返回结果
  • _calc_percent_gait()
    • 源码
    • 获取当前时间戳
    • 默认值初始化
    • 只有有期望步态周期时才进行计算
    • 返回结果
  • _update_expected_duration()
    • 源码
    • 步态周期测量
    • 初始化与边界检查
    • 检查滑动窗口内未初始化的数量
    • 获取滑动窗口的最大最小值(为“异常剔除窗口”做准备)
    • 初始化期:填满滑动窗口
    • 正常采样期:窗口剔除+滑动平均
    • 返回

构造函数

源代码

Side::Side(bool is_left, ExoData* exo_data)
: _hip((config_defs::joint_id)((uint8_t)(is_left ? config_defs::joint_id::left : config_defs::joint_id::right) | (uint8_t)config_defs::joint_id::hip), exo_data)        //We need to cast to uint8_t to do bitwise or, then we have to cast it back to joint_id
, _knee((config_defs::joint_id)((uint8_t)(is_left ? config_defs::joint_id::left : config_defs::joint_id::right) | (uint8_t)config_defs::joint_id::knee), exo_data)
, _ankle((config_defs::joint_id)((uint8_t)(is_left ? config_defs::joint_id::left : config_defs::joint_id::right) | (uint8_t)config_defs::joint_id::ankle), exo_data)
, _elbow((config_defs::joint_id)((uint8_t)(is_left ? config_defs::joint_id::left : config_defs::joint_id::right) | (uint8_t)config_defs::joint_id::elbow), exo_data)
, _heel_fsr(is_left ? logic_micro_pins::fsr_sense_left_heel_pin : logic_micro_pins::fsr_sense_right_heel_pin) //Check if it is the left and use the appropriate pin for the side.
, _toe_fsr(is_left ? logic_micro_pins::fsr_sense_left_toe_pin : logic_micro_pins::fsr_sense_right_toe_pin)
{_data = exo_data;_is_left = is_left;//This data object is set for the specific side so we don't have to keep checking._side_data = _is_left ? &(_data->left_side) : &(_data->right_side);#ifdef SIDE_DEBUGlogger::print(_is_left ? "Left " : "Right ");logger::println("Side :: Constructor : _data set");#endif_prev_heel_contact_state = true;        //Initialized to true so we don't get a strike the first time we read_prev_toe_contact_state = true;_prev_toe_contact_state_toe_off = true;_prev_toe_contact_state_toe_on = true;for (int i = 0; i<_num_steps_avg; i++){_step_times[i] = 0;}_ground_strike_timestamp = 0;_prev_ground_strike_timestamp = 0;//_expected_step_duration = 0;#ifdef SIDE_DEBUGlogger::print(_is_left ? "Left " : "Right ");logger::println("Side :: Constructor : Exit");#endif_heel_fsr.get_contact_thresholds(_side_data->heel_fsr_lower_threshold, _side_data->heel_fsr_upper_threshold);_toe_fsr.get_contact_thresholds(_side_data->toe_fsr_lower_threshold, _side_data->toe_fsr_upper_threshold);inclination_detector = new InclinationDetector();
};

功能总结:

  • 分配关节对象,自动绑定侧别(左/右),不需要在后续逻辑反复判断左/右。

  • FSR传感器(heel/toe)自动选pin。

  • 初始化状态机相关变量、步态窗口数组,防止“误触发”。

  • 关联倾角检测器,用于动态地形或坡度环境下的自适应。

run_side - 核心

void Side::run_side()
{#ifdef SIDE_DEBUGlogger::print("\nmicros : ");logger::println(micros());logger::print(_is_left ? "Left " : "Right ");logger::println("Side :: run_side : checking calibration");#endifcheck_calibration();#ifdef SIDE_DEBUGlogger::print(_is_left ? "Left " : "Right ");logger::println("Side :: run_side : reading data");#endif_check_thresholds();//Read all the data before we calculate and send the new motor commandsread_data();#ifdef SIDE_DEBUGlogger::print(_is_left ? "Left " : "Right ");logger::println("Side :: run_side : updating motor commands");#endif//Calculates the new motor commands and sends them.update_motor_cmds();}; 
  • 检查&触发FSR或关节标定流程
  • FSR的阈值设定(防止标定变化)
  • 读取传感器/关节/步态状态
  • 计算并下发新的电机指令

read_data()

源代码

void Side::read_data()
{//Check the FSRs_side_data->heel_fsr = _heel_fsr.read();_side_data->toe_fsr = _toe_fsr.read();//Check if a ground strike is detected_side_data->ground_strike = _check_ground_strike();//If a strike is detected, update the expected duration of the step if (_side_data->ground_strike){_side_data->expected_step_duration = _update_expected_duration();}//Check if the toe on or toe off is occuring_side_data->toe_off = _check_toe_off();_side_data->toe_on = _check_toe_on();//If toe off is detected, updated expected stance durationif (_side_data->toe_off == true){_side_data->expected_stance_duration = _update_expected_stance_duration();}//If toe on is detected, update expected swing durationif (_side_data->toe_on == true){_side_data->expected_swing_duration = _update_expected_swing_duration();}//Calculate Percent Gait, Percent Stance, and Percent Swing_side_data->percent_gait = _calc_percent_gait();_side_data->percent_stance = _calc_percent_stance();_side_data->percent_swing = _calc_percent_swing();//Get the contact thesholds for the Heel and Toe FSRs_heel_fsr.get_contact_thresholds(_side_data->heel_fsr_lower_threshold, _side_data->heel_fsr_upper_threshold);_toe_fsr.get_contact_thresholds(_side_data->toe_fsr_lower_threshold, _side_data->toe_fsr_upper_threshold);//Check the inclination_side_data->inclination = inclination_detector->check(_side_data->toe_stance, _side_data->do_calibration_refinement_toe_fsr, _side_data->ankle.joint_position);//Check the joint sensors if the joint is used.if (_side_data->hip.is_used){_hip.read_data();}if (_side_data->knee.is_used){_knee.read_data();}if (_side_data->ankle.is_used){_ankle.read_data();}if (_side_data->elbow.is_used){_elbow.read_data();}};

FSR压力传感器读取与赋值

_side_data->heel_fsr = _heel_fsr.read();
_side_data->toe_fsr  = _toe_fsr.read();
  • 这里调用了heel/toe FSR(脚跟/脚趾压力传感器)的read()方法,获得当前压力或传感器值,并存入对应的SideData成员。

  • 这是所有步态事件检测(落地、抬脚、踢地等)的基础感知信号。

步态事件检测:落地(ground_strike)

_side_data->ground_strike = _check_ground_strike();
  • 用FSR的“接触/离地”状态,判断本步是否发生了“落地”。

  • 这种事件通常只在脚由“空中”转为“地面”时才判定为真,有效避免重复误判。

  • 检测到ground_strike之后(即完成一次步态周期),会进入下个步骤。

步态周期自适应:期望步长更新

if (_side_data->ground_strike)
{_side_data->expected_step_duration = _update_expected_duration();
}
  • 如果检测到新的一次落地事件,则用当前步长(两次落地时间差),更新一个滑动窗口(见前文分析)。

  • 用窗口平均自适应地估计本侧当前的步长(步态周期),用于百分比进度估算和节奏自调。

  • 这样能让外骨骼“跟随使用者节奏”,而不是死板设定。

Toe-Off/Toe-On事件检测与站立/摆动窗口更新

_side_data->toe_off = _check_toe_off();
_side_data->toe_on  = _check_toe_on();if (_side_data->toe_off)_side_data->expected_stance_duration = _update_expected_stance_duration();
if (_side_data->toe_on)_side_data->expected_swing_duration  = _update_expected_swing_duration();
  • 分别检测“脚趾离地”(摆动开始)和“脚趾着地”(摆动结束/站立开始)。

  • 每检测到一次事件,就用最新时长更新“期望站立/摆动时长”,同样采用滑动窗口方式。

  • 这样做的好处:

    • 能区分不同步态阶段(stance/swing)。

    • 自动适应不同用户、速度、模式下的站立/摆动比例(如老人慢步和跑步的占比差别)。

步态百分比进度估算

_side_data->percent_gait   = _calc_percent_gait();
_side_data->percent_stance = _calc_percent_stance();
_side_data->percent_swing  = _calc_percent_swing();
  • 利用已知的周期时长、事件时间戳等,实时计算本侧“已走过的步态百分比”、“已站立的比例”、“已摆动的比例”。

  • 这些百分比是时序同步、控制器触发、力矩规划、数据记录的核心指标,也是下游“高级控制算法/自适应助力”必备输入。

FSR阈值动态读取(可能受标定/漂移影响)

_heel_fsr.get_contact_thresholds(_side_data->heel_fsr_lower_threshold, _side_data->heel_fsr_upper_threshold);
_toe_fsr.get_contact_thresholds(_side_data->toe_fsr_lower_threshold, _side_data->toe_fsr_upper_threshold);
  • 获取heel/toe当前的“接触判定阈值”——可能在动态标定或补偿漂移后被修改。

  • 这样后续所有事件检测判据都能“自适应传感器特性/环境变化”。

坡度/倾角检测

_side_data->inclination = inclination_detector->check(_side_data->toe_stance, _side_data->do_calibration_refinement_toe_fsr, _side_data->ankle.joint_position);
  • 这里利用关节角、FSR、标定标志等信息,调用InclinationDetector检测当前步态的“地面坡度/使用者踝关节姿态”。

  • 这对于动态坡度补偿、上下坡时的力矩修正、平地与障碍判别等场景非常有用。

关节级传感器/数据读取

if (_side_data->hip.is_used)    { _hip.read_data(); }
if (_side_data->knee.is_used)   { _knee.read_data(); }
if (_side_data->ankle.is_used)  { _ankle.read_data(); }
if (_side_data->elbow.is_used)  { _elbow.read_data(); }
  • 调用各关节对象的 read_data() 方法,采集角度/力矩/速度/电流等状态。

  • 是否“参与”由 is_used 决定,可灵活屏蔽部分关节,适应不同外骨骼拓扑。

  • 关节本身负责数据融合和内部状态更新,不影响Side的主流程。

check_calibration()

源代码

void Side::check_calibration()
{if (_side_data->is_used){//Make sure FSR calibration is done before refinement.if (_side_data->do_calibration_toe_fsr){_side_data->do_calibration_toe_fsr = _toe_fsr.calibrate(_side_data->do_calibration_toe_fsr);_data->set_status(status_defs::messages::fsr_calibration);}else if (_side_data->do_calibration_refinement_toe_fsr) {_side_data->do_calibration_refinement_toe_fsr = _toe_fsr.refine_calibration(_side_data->do_calibration_refinement_toe_fsr);_data->set_status(status_defs::messages::fsr_refinement);}if (_side_data->do_calibration_heel_fsr){_side_data->do_calibration_heel_fsr = _heel_fsr.calibrate(_side_data->do_calibration_heel_fsr);_data->set_status(status_defs::messages::fsr_calibration);}else if (_side_data->do_calibration_refinement_heel_fsr) {_side_data->do_calibration_refinement_heel_fsr = _heel_fsr.refine_calibration(_side_data->do_calibration_refinement_heel_fsr);_data->set_status(status_defs::messages::fsr_refinement);}//Check the joint sensors if the joint is used.if (_side_data->hip.is_used){_hip.check_calibration();}if (_side_data->knee.is_used){_knee.check_calibration();}if (_side_data->ankle.is_used){_ankle.check_calibration();}if (_side_data->elbow.is_used){_elbow.check_calibration();}}        
};
  • 此函数是整个“单侧传感与控制链”的标定调度枢纽,上层能清晰了解当前哪个部分正在标定。

  • 典型用法场景:UI/蓝牙/上位机发出一次标定指令→ 相关flag置位 → 控制循环自动推进流程 → 标定完成自动退回。

  • 便于加装其他类型传感器或关节——只要新增对应的do_calibration_xxx和check_calibration()接口就可以。

  • 整个流程不阻塞主控循环,适合穿戴场景的高鲁棒、快速上电自检。

整体结构:只对启用侧做标定

if (_side_data->is_used)
{// ...
}
  • 只对被启用的side(即实际接入的左/右腿)执行标定流程,未启用的侧完全跳过,提高效率且防止误操作。

FSR(压力传感器)脚趾标定与精细化标定

if (_side_data->do_calibration_toe_fsr)
{_side_data->do_calibration_toe_fsr = _toe_fsr.calibrate(_side_data->do_calibration_toe_fsr);_data->set_status(status_defs::messages::fsr_calibration);
}
else if (_side_data->do_calibration_refinement_toe_fsr) 
{_side_data->do_calibration_refinement_toe_fsr = _toe_fsr.refine_calibration(_side_data->do_calibration_refinement_toe_fsr);_data->set_status(status_defs::messages::fsr_refinement);
}
  • 先判断do_calibration_toe_fsr(脚趾FSR是否需要标定)。若为真,执行FSR初始标定流程(如采集多帧信号平均、设阈值等)。

  • 标定完成后,calibrate返回false,flag被置0,防止重复执行。

  • 标定时同步全局状态,让上位机/LED/UI等实时反映“当前正处于FSR标定中”。

  • 只有初始标定完成后,才允许进入精细化(refine_calibration)。这个流程多用于二次补偿、更高精度调节。

  • 这种分阶段、自动推进的设计,保障了标定流程不会混乱,也方便用户交互(比如按钮点一次就是一轮完整流程)。

FSR脚跟标定与精细化标定

if (_side_data->do_calibration_heel_fsr)
{_side_data->do_calibration_heel_fsr = _heel_fsr.calibrate(_side_data->do_calibration_heel_fsr);_data->set_status(status_defs::messages::fsr_calibration);
}
else if (_side_data->do_calibration_refinement_heel_fsr) 
{_side_data->do_calibration_refinement_heel_fsr = _heel_fsr.refine_calibration(_side_data->do_calibration_refinement_heel_fsr);_data->set_status(status_defs::messages::fsr_refinement);
}
  • 脚跟FSR与脚趾FSR标定流程完全一样,各自独立,互不干扰。

  • 支持先后/多轮分阶段标定,适合应对传感器偏移、环境变化等。

关节(Hip/Knee/Ankle/Elbow)逐个检查并执行标定

if (_side_data->hip.is_used)   { _hip.check_calibration(); }
if (_side_data->knee.is_used)  { _knee.check_calibration(); }
if (_side_data->ankle.is_used) { _ankle.check_calibration(); }
if (_side_data->elbow.is_used) { _elbow.check_calibration(); }
  • 对每个关节(只要在本侧被启用)依次下发check_calibration(),这会递归调用到关节/电机/角度传感器等子系统的标定。

  • 这种“递归式”设计保证了上层Side类只需关注高层流程,不用管每种关节的细节,强解耦。

  • 新增关节只需实现自己的check_calibration即可,对系统无侵入。

_check_ground_strike()

源代码

bool Side::_check_ground_strike()
{_side_data->prev_heel_stance = _prev_heel_contact_state;  _side_data->prev_toe_stance = _prev_toe_contact_state;bool heel_contact_state = _heel_fsr.get_ground_contact();bool toe_contact_state = _toe_fsr.get_ground_contact();_side_data->heel_stance = heel_contact_state;_side_data->toe_stance = toe_contact_state;bool ground_strike = false;// logger::print("Side::_check_ground_strike : _prev_heel_contact_state - ");// logger::print(_prev_heel_contact_state);// logger::print("\n");// logger::print("\t_prev_toe_contact_state - ");// logger::print(_prev_toe_contact_state);// logger::print("\n");//Only check if in swing_side_data->toe_strike = toe_contact_state > _prev_toe_contact_state;if(!_prev_heel_contact_state & !_prev_toe_contact_state) //If we were previously in swing{//Check for rising edge on heel and toe, toe is to account for flat foot landingsif ((heel_contact_state > _prev_heel_contact_state) | (toe_contact_state > _prev_toe_contact_state))    //If either the heel or toe FSR is on the ground and it previously wasn't on the ground{ground_strike = true;_prev_ground_strike_timestamp = _ground_strike_timestamp;_ground_strike_timestamp = millis();}}_prev_heel_contact_state = heel_contact_state;_prev_toe_contact_state = toe_contact_state;return ground_strike;
};

_check_ground_strike() 是步态周期检测的核心,用于识别足底由“空中(摆动)”到“着地”这一关键事件(即“ground strike”/落地瞬间),它是外骨骼助力/数据记录的基本触发点。

保存上一次heel/toe接触状态

_side_data->prev_heel_stance = _prev_heel_contact_state;  
_side_data->prev_toe_stance = _prev_toe_contact_state;

把上一次采样时的“脚跟/脚趾是否触地”状态,存到side数据结构中,方便全局分析和后续计算(比如UI、数据同步、事件溯源)。

读取当前heel/toe的触地状态

bool heel_contact_state = _heel_fsr.get_ground_contact();
bool toe_contact_state = _toe_fsr.get_ground_contact();
  • 实时读取脚跟、脚趾FSR(力敏电阻)当前是否“有地面压力”。

  • 这通常是一个bool(0/1),传感器阈值判决由FSR类内部实现。

同步当前heel/toe状态到side全局数据

_side_data->heel_stance = heel_contact_state;
_side_data->toe_stance = toe_contact_state;
  • 方便整个系统(UI、控制、记录)直接用side数据就能拿到“当前这一帧”的足底状态。

步态事件标志初始化

bool ground_strike = false;
  • 初始化一个flag,用于记录“这一帧是否检测到地面冲击事件(足部落地)”。

实时记录toe strike事件

_side_data->toe_strike = toe_contact_state > _prev_toe_contact_state;
  • 如果当前脚趾由“未触地”变成“触地”(即从0变1),则toe_strike为真,代表检测到脚趾首次着地。

  • 用于某些步态模型(如平足或脚尖先着地的人群)。

只在摆动相(悬空)检测ground strike

if(!_prev_heel_contact_state & !_prev_toe_contact_state) //If we were previously in swing
{//...
}
  • 只有上一次采样时,脚跟和脚趾都没触地(即这只脚在摆动),才判定接下来有无落地事件。

  • 避免反复触发、只在真实“从空中到落地”那一瞬间识别。

检测heel或toe的上升沿(rising edge)

if ((heel_contact_state > _prev_heel_contact_state) | (toe_contact_state > _prev_toe_contact_state))
{ground_strike = true;_prev_ground_strike_timestamp = _ground_strike_timestamp;_ground_strike_timestamp = millis();
}
  • 如果“脚跟”或“脚趾”任何一个传感器状态从0变1(刚好踩到地面),判定为ground strike。

  • 为什么要“或”而不是“与”?——有人落地先脚跟,有人先脚趾(甚至平足),都能检测到,更健壮。

  • 一旦检测到,更新落地时间戳,为步态周期估算等后续分析做准备。

更新历史状态

_prev_heel_contact_state = heel_contact_state;
_prev_toe_contact_state = toe_contact_state;
  • 把本帧的触地状态存下来,供下次采样时做“上升沿”判断。

返回ground strike事件标志

return ground_strike;
  • 外部主循环会用它来决定是否触发步态事件、调整助力、记录数据等。

_check_toe_on()

源代码

bool Side::_check_toe_on()
{bool toe_on = false;if (_side_data->toe_stance > _prev_toe_contact_state_toe_on){toe_on = true;_prev_toe_strike_timestamp = _toe_strike_timestamp;_toe_strike_timestamp = millis();}_prev_toe_contact_state_toe_on = _side_data->toe_stance;return toe_on;
};
  • 步态检测:toe on常用于区分“摆动相结束、站立相开始”,也就是脚尖第一次踩到地面。

  • 实时性强:通过对比上/下采样周期,只会在真正状态变化瞬间触发,避免长时间触地反复触发。

  • 事件驱动:便于同步下游模块(比如:助力控制、数据采集、相位切换、计时等)。

  • 时间戳为分析和自适应奠定基础:累计周期时间可用来估算步频、步幅、异常检测等。

变量初始化

bool toe_on = false;
  • 初始设置返回值为false,即“默认本周期没有检测到toe on事件”。

检测 toe on 上升沿

if (_side_data->toe_stance > _prev_toe_contact_state_toe_on)
{toe_on = true;_prev_toe_strike_timestamp = _toe_strike_timestamp;_toe_strike_timestamp = millis();
}
  • _side_data->toe_stance 是当前采样帧脚趾的触地状态(一般是bool,0/1)。

  • _prev_toe_contact_state_toe_on 是上一帧的脚趾触地状态。

  • 如果 当前 > 上一帧,说明刚刚由“悬空”变为“落地”,也就是toe on事件发生了。

  • 一旦检测到:

    • toe_on = true,主循环可根据它做响应(如记录步态相位切换、辅助控制等)。

    • 时间戳管理:

      • _prev_toe_strike_timestamp = _toe_strike_timestamp; 把“上次toe on”的时间存为“前一次”,方便统计周期、滤除异常等。
    • _toe_strike_timestamp = millis(); 记录本次toe on发生的时刻。

保存本次 toe 状态

_prev_toe_contact_state_toe_on = _side_data->toe_stance;
  • 更新 toe 状态,为下次采样时做“上升沿检测”准备。

返回结果

return toe_on;
  • 返回是否检测到 toe on 事件。

_calc_percent_gait()

实时计算当前步态周期内已经走了多少百分比,也就是gait phase progress,通常用于步态相位同步、控制器参数插值、实时分析等场景。

源码

float Side::_calc_percent_gait()
{int timestamp = millis();int percent_gait = -1;//Only calulate if the expected step duration has been established.if (_side_data->expected_step_duration > 0){percent_gait = 100 * ((float)timestamp - _ground_strike_timestamp) / _side_data->expected_step_duration;percent_gait = min(percent_gait, 100); //Set saturation.// logger::print("Side::_calc_percent_gait : percent_gait_x10 = ");// logger::print(percent_gait_x10);// logger::print("\n");}return percent_gait;
};

获取当前时间戳

int timestamp = millis();
  • millis() 返回系统运行到现在的毫秒数。

  • 这个时间戳用来计算距离本周期步态开始(上一次ground strike)已经过去多久。

默认值初始化

int percent_gait = -1;
  • 默认值为-1,表明当前还无法判断百分比(比如未初始化或步态未检测到)。

只有有期望步态周期时才进行计算

if (_side_data->expected_step_duration > 0)
{percent_gait = 100 * ((float)timestamp - _ground_strike_timestamp) / _side_data->expected_step_duration;percent_gait = min(percent_gait, 100); //Set saturation.
}
  • 只有expected_step_duration(期望步态周期,单位ms)大于0才进行计算,防止未初始化导致错误。

  • 计算公式为:

    • ((float)timestamp - _ground_strike_timestamp):距离上次落地(ground strike)已经过去的时间。

    • 除以期望步态周期,得到本周期已完成的比例。

    • 乘100,得到百分数。

    • 最后min(percent_gait, 100):保证最大不会超过100%(即0-100之间),防止走慢或滞后造成溢出。

返回结果

return percent_gait;
  • 返回步态周期百分比,供后续模块(如相位插值、控制曲线索引、步态进度显示)使用。

_update_expected_duration()

  • Side::_update_expected_duration() 的作用是自适应地估算当前步态周期(一步所需的平均时长),用来动态调整后续的步态判别、步态百分比计算、控制器相位插值等参数。
  • 它的核心是“滑动平均+窗口判断”,有点像低通滤波器+异常剔除,适应用户步频的变化。

源码

float Side::_update_expected_duration()
{unsigned int step_time = _ground_strike_timestamp - _prev_ground_strike_timestamp;float expected_step_duration = _side_data->expected_step_duration;if (0 == _prev_ground_strike_timestamp) //If the prev time isn't set just return.{return expected_step_duration;}uint8_t num_uninitialized = 0;//Check that everything is set.for (int i = 0; i < _num_steps_avg; i++){num_uninitialized += (_step_times[i] == 0);}//Get the max and min values of the array for determining the window for expected values.unsigned int* max_val = std::max_element(_step_times, _step_times + _num_steps_avg);unsigned int* min_val = std::min_element(_step_times, _step_times + _num_steps_avg);if  (num_uninitialized > 0)  //If all the values haven't been replaced{//Shift all the values and insert the new onefor (int i = (_num_steps_avg - 1); i>0; i--){_step_times[i] = _step_times[i-1];}_step_times[0] = step_time;// logger::print("Side::_update_expected_duration : _step_times not fully initialized- [\t");// for (int i = 0; i < _num_steps_avg; i++)// {// logger::print(_step_times[i]);// logger::print("\t");// }// logger::print("\t]\n");    }//Consider it a good step if the ground strike falls within a window around the expected duration. Then shift the step times and put in the new value.else if ((step_time <= (_side_data->expected_duration_window_upper_coeff * *max_val)) & (step_time >= (_side_data->expected_duration_window_lower_coeff * *min_val))) // and (armed_time > ARMED_DURATION_PERCENT * self.expected_duration)): # a better check can be used.  If the person hasn't stopped or the step is good update the vector.  {int sum_step_times = step_time;for (int i = (_num_steps_avg - 1); i>0; i--){sum_step_times += _step_times[i-1];_step_times[i] = _step_times[i-1];}_step_times[0] = step_time;expected_step_duration = sum_step_times / _num_steps_avg;  //Average to the nearest ms// logger::print("Side::_update_expected_duration : _expected_step_duration - ");// logger::print(_expected_step_duration);// logger::print("\n");}return expected_step_duration;
};

步态周期测量

unsigned int step_time = _ground_strike_timestamp - _prev_ground_strike_timestamp;

用当前一次落地(ground strike)和上一次的时间戳差,得到本次步态周期的实际时长。

初始化与边界检查

if (0 == _prev_ground_strike_timestamp) //If the prev time isn't set just return.
{return expected_step_duration;
}
  • 如果是第一次运行,_prev_ground_strike_timestamp还没有设置,直接返回之前的expected_step_duration,避免出错。

检查滑动窗口内未初始化的数量

uint8_t num_uninitialized = 0;
for (int i = 0; i < _num_steps_avg; i++)
{num_uninitialized += (_step_times[i] == 0);
}
  • _step_times[]保存最近N步的周期(比如N=5或N=10)。

  • 只要有一个没初始化(==0),num_uninitialized就大于0。

获取滑动窗口的最大最小值(为“异常剔除窗口”做准备)

unsigned int* max_val = std::max_element(_step_times, _step_times + _num_steps_avg);
unsigned int* min_val = std::min_element(_step_times, _step_times + _num_steps_avg);
  • 记录最近N步最大和最小的步态周期。

初始化期:填满滑动窗口

if  (num_uninitialized > 0)
{//Shift all the values and insert the new onefor (int i = (_num_steps_avg - 1); i>0; i--){_step_times[i] = _step_times[i-1];}_step_times[0] = step_time;
}
  • 只要有没填满的数据,采用“先进先出”方式:把新的step_time插到第0个,其他依次后移。

  • 填满滑动窗口后才进行下一步的“正常采样和异常剔除”。

正常采样期:窗口剔除+滑动平均

else if ((step_time <= (_side_data->expected_duration_window_upper_coeff * *max_val)) & (step_time >= (_side_data->expected_duration_window_lower_coeff * *min_val)))
{int sum_step_times = step_time;for (int i = (_num_steps_avg - 1); i>0; i--){sum_step_times += _step_times[i-1];_step_times[i] = _step_times[i-1];}_step_times[0] = step_time;expected_step_duration = sum_step_times / _num_steps_avg;  //Average to the nearest ms
}
  • 如果新采样的step_time在合理的窗口范围内(上下限由expected_duration_window_upper_coeff和expected_duration_window_lower_coeff修正的最大/最小步长):

    • 将其加入滑动窗口(同上)。

    • 用全部N步求均值,得到新的expected_step_duration。

    • 这样可以“剔除异常值”(如偶发停顿、抖动导致的极大极小步长),只吸收正常步态变化。

    • 如果不在窗口内,则忽略本次采样(不更新均值,防止异常值污染估算)。

返回

return expected_step_duration;
  • 得到最新的滑动平均步态周期,用于后续所有需要步态相位的模块。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/916152.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/916152.shtml
英文地址,请注明出处:http://en.pswp.cn/news/916152.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Java+MySQL实现(Web)文件共享管理系统(仿照百度文库)

文件共享管理系统的设计与实现摘要&#xff1a;本文件共享管理系统解决了用户在搜索文件不需要下载文件到本地硬盘后才能查看文件的详细内容的弊端&#xff1b;解决用户在搜索关键字不明确条件下无法搜索到自己需要的文件弊端&#xff1b;解决了系统用户并发量增加后服务器宕机…

go语言基础教程:1. Go 下载安装和设置

1. Go 下载安装和设置1. 安装Go 官网下载安装即可&#xff0c;注意要记住安装的位置&#xff0c;例如D:\Go cmd输入go 或者go env 会输出各种信息&#xff0c;代表安装成功 2. hello go &#xff08;1&#xff09;编写 hello.go go是以文件夹为最小单位管理程序的&#xff0c…

使用相机不同曝光时间测试灯光闪烁频率及Ai解释

1.背景坐地铁上&#xff0c;拨弄着手机拍照中的专业模式&#xff0c;偶然发现拍出了条纹&#xff0c;怀疑是灯光的缘故&#xff0c;但是随后在家里的LED等下就拍不出类似的效果了。好奇心❤让我又尝试多了解了一点和不断尝试&#xff0c;发现不同的曝光时间可以拍出不同明显程度…

力扣-416.分割等和子集

题目链接 416.分割等和子集 class Solution {public boolean canPartition(int[] nums) {int sum 0;for (int i 0; i < nums.length; i) {sum nums[i];}if (sum % 2 1)return false;int target sum / 2;// dp[i]表示&#xff1a;背包容量为i时&#xff0c;能装的最大…

http协议学习-body各种类型

1、概述使用postman工具和nc命令分析http协议中body各种类型的格式。2、分析环境准备虚拟机中用nc命令模仿服务器&#xff0c;启动监听状态。 windows机器安装postmannc -k -l 192.168.202.223 80821、params参数postman中params添加俩个key为m、n&#xff1b;value为1、2&…

C++中的塔尖算法(Tarjan算法)详解

C中的塔尖算法&#xff08;Tarjan算法&#xff09;详解——目录C中的塔尖算法&#xff08;Tarjan算法&#xff09;详解一、什么是Tarjan算法&#xff1f;二、算法原理与实现步骤1. 核心概念2. 主要逻辑3. C代码示例三、应用场景与扩展1. 典型应用2. 注意事项四、为什么选择Tarj…

Qt 数据库事务处理与数据安全

在 Qt 应用程序中&#xff0c;数据库事务处理是确保数据完整性和一致性的关键技术。通过事务&#xff0c;可以将多个数据库操作作为一个不可分割的单元执行&#xff0c;保证数据在并发访问和异常情况下的安全性。本文将详细介绍 Qt 中数据库事务的处理方法和数据安全策略。 一、…

Redis的事务和Lua之间的区别

Redis的事务和Lua之间的区别 Redis 提供了事务和 Lua 脚本两种实现原子性操作的方式。当需要以原子方式执行多个命令时,我们可以选择其中一种方案。 原子性保证 两者都确保操作的不可分割性 需要注意:不管是事务还是 Lua 脚本都不支持回滚机制 区别: 事务:某个命令失败不会…

腾讯云SDK

SDK的用途&#xff0c;现在显然是想更系统地了解它的产品定位和核心能力。 用户可能是开发者或者技术决策者&#xff0c;正在评估腾讯云的开发工具链。从ta连续追问云服务相关技术细节的习惯看&#xff0c;应该具备相当的技术背景&#xff0c;但需要避免过度使用术语。 需要突出…

大数据集分页优化:LIMIT OFFSET的替代方案

针对大数据集分页场景中 LIMIT OFFSET 的性能瓶颈&#xff0c;以下是已验证的高效替代方案及实施要点&#xff1a;⚠️ 一、LIMIT OFFSET 的核心问题当偏移量&#xff08;OFFSET&#xff09;增大时&#xff0c;数据库需‌物理扫描并丢弃前 N 条记录‌&#xff0c;导致资源浪费和…

Linux网络框架分析

在 Linux 内核架构中,/net 和 /drivers/net 是网络子系统的两个核心组成部分,它们之间的关系体现了 Linux 经典的 “抽象层分离” 设计哲学。以下是深入分析: 一、核心关系图解 #mermaid-svg-esFw9i3LN65SYumi {font-family:"trebuchet ms",verdana,arial,sans-se…

ISIS高级特性GR

一、概述IS-IS GR是一种支持GR能力的高可靠性技术&#xff0c;可以实现数据的不间断转发。与我们之前介绍的OSPF的GR功能几乎一致,但实现方法并不相同。1、GR支持GR的ISIS的设备,IIH报文中一定会携带TLV211(GR),TLV211包含的字段(1)RR:restart request 请求重启,默认是3秒发送1…

电厂液压执行器自动化升级:Modbus TCP与DeviceNet的协议贯通实践

一、项目背景在我们电厂的汽轮机控制区&#xff0c;液压执行器是实打实的“关键选手”——从调节蒸汽阀门开度到控制闸板起落&#xff0c;全靠它在高压环境下精准动作。但这套系统一直有个“沟通障碍”&#xff1a;负责统筹控制的施耐德PLC走Modbus TCP协议&#xff0c;而液压执…

ucharts 搭配uniapp 自定义x轴文字 实现截取显示

formatter格式化问题因为组件不能传递 function&#xff0c;所有的 formatter 均需要变成别名 format 来定义&#xff0c;并在 config-ucharts.js 或 config-echarts.js 配置对应的 formatter 方法&#xff0c;组件会根据 format 的值自动替换配置文件中的 formatter 方法。uCh…

Logstash 多表增量同步 MySQL 到 Elasticsearch:支持逻辑删除与热加载,Docker 快速部署实战

​ 1. 项目结构 install-elk/ ├── start-elastic.sh ├── es-data/ # Elasticsearch 持久化目录&#xff08;自动创建&#xff09; ├── logstash/├── logstash.yml├── pipeline/│ ├── user.conf│ ├── articles.conf│ …

服务器托管:网站经常被攻击该怎么办?

“木马”对于孩子来说是个玩具&#xff0c;但是对于网络行业来说是一个病毒威胁&#xff0c;站长在进行建站的过程中&#xff0c;通常都会面临一个问题网站被挂马&#xff0c;有些网站服务器托管在进行多次处理木马之后得不到根治&#xff0c;后续会受到频繁的攻击该怎么办&…

判断子序列-leetcode

给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是"abcde"的一个子序列&#x…

如何提高微信小程序的应用速度

1、如何提高微信小程序的应用速度&#xff1f;加载时1、上传代码时&#xff0c;对代码进行压缩。2、清理点代码中无效的代码和资源文件。3、减少本地代码中图片等资源的数量和大小。如将多个图片合成一张图片。还有将图片资源放在静态资源库存储。渲染1、在加载页面时&#xff…

华为高频算法题:最长连续递增子序列(Longest Continuous Increasing Subsequence)

文章目录前言题目描述&#xff08;华为校招真题&#xff09;解题思路分析Java 实现代码单元测试代码结语前言 在各大互联网公司的算法面试中&#xff0c;数组类题目一直是考察的重点&#xff0c;尤其是对于应届生和初级工程师的面试来说更是常见题型。华为作为国内顶尖的科技企…

JavaSE-图书信息管理系统

目录 前置知识点 项目部署说明 项目运行截图 项目结构展示 项目编写构思 book包 Book类 Booklist类 ioperations包 IOPeration接口 AddOperation类 BorrowOperation类 DelOperation类 FindOperation类 ReturnOperation类 ShowOperation类 ExitOperation类 use…