发布时间:2026/8/29 23:08:31
S加减速,脉冲S加减速 s型加减速在网格图上速度的变化现是缓慢增大然后快速增大再是缓慢增大再到匀速。也就是说加速度是变化的。f(x) 1/(1e^-x)这是y从左到右增加时的S曲线的原始函数用这个生成一个表加速正着用减速倒着用e是自然常数约为2.71828。当-3 ≤ x ≤ 3函数收敛较为明显此时y轴高度基本处于0到1之间更精确点当x -3时 y ≈ 0.04742当x 3时y ≈ 0.95257。如果精度不够就加大x的取值范围。现在假设3 ≤ x ≤ 3时y从0到1。实际使用时x作为加速次数y就是速度。加速次数不可能为负数所以需要对函数进行向右平移也就是1/(1e^(-x 3))这样当 x 0时y ≈ 0这时0 ≤ x ≤ 6。但是加速次数也不可能为小数而且6次加速太少了所以要横向拉长函数。改变x的系数就可以拉长或缩短y ≈ 0时x的范围。这个函数的x的系数越小函数看起来横向越长实际无限长x能取得的整数越多。当系数为0.1时1/(1e^(-0.1x 3))x的有效值就是0到60当系数为2时1/(1e^(-2x 3))x的有效值就是0到3。也就是说x取值的整数范围是系数的倒数即1/0.1 x 6个和1/2 x 6个。如果决定加速次数那么系数就是6/加速次数当加速次数为100次时也就是1/(1e^(-(6/100)x 3))。假如最大脉冲频率为1MHzy乘以最大频率就可以知道当前实际频率了即1000000 x 0.95257。如果将1/(1e^-x)计算后的速度比例存起来下次使用时只需查表再乘以最大频率即可。实现buff_pa是速度表数组。count_va是加速次数。pan_right_va是右移量代表了精度即右移多少后y强制为0一般用7最后一次速度在99.9%。count_va越大S曲线越平缓加速过程越平滑加速时间越长count_va越小S曲线越陡峭加速过程越急剧加速时间越短。void curve_s_init(float *buff_pa, uint16_t count_va, uint8_t pan_right_va) { for (uint16_t x 0; x count_va; x) buff_pa[x] 1.0 / (1.0 exp(((float)pan_right_va * 2 / count_va * -x) pan_right_va)); //buff_pa[x] 1.0 / (1.0 pow(sqrt(m_e_v), (float)pan_right_va * 2 / count_va * -x pan_right_va)); //这是通用计算方法pow函数的第一个参数越接近1该算式计算出的曲线越平缓可以改的和线性加减速无异 } 例 float curve_s_table_v[500]; curve_s_init(curve_s_table_v, 500, 7);以下为c版本#ifndef ivesStepMotorH #define ivesStepMotorH #include main.h namespace xiaguangbo { namespace motorNamespace { namespace ivesStepMotorNamespace { class ivesStepMotorClass { private: static constexpr float pi 3.1415926; //运行状态 static const uint8_t motorStopState 0; static const uint8_t motorAccelerateState 1; static const uint8_t motorMaxState 2; static const uint8_t motorDecelerateState 3; //固定参数 static const uint8_t sCurveExcursion 3; //s曲线偏移的量 static const uint16_t sCurvePointNumber 200; //在s曲线上取的点的数量 static const uint16_t aCircleStep 3200; //一圈的步数 static const uint16_t maxSpeedThreshold 0xffff; //速度的最大值因为速度变量是16位的所以最大就是0xffff //s曲线 static float sCurveTable[sCurvePointNumber]; //放s曲线数值的数组 static bool sCurveTableFlag; //s曲线是否已经计算过了 //算法核心参数 int32_t currentAbsolutePosition; //当前的绝对位置以步数为单位 int32_t currentNeedRunStep; //将要运行的步数。负数就往复位方向移动 uint32_t temporaryStepCounter; //临时的步数计数记录一次算法走了多少步 uint16_t currentSpeed; //当前速度 uint16_t targetSpeed; //要达到的速度 uint8_t runState; //运行状态 uint16_t accelerationInterval; //加减速一次的间隔 uint16_t interruptCounter; //加减速时的定时器中断次数计数 bool interruptCounterFlag; //中断计数的开关 uint32_t accelerationNumberCounter; //加减速次数计数 uint32_t accelerationStep; //加减速阶段的脉冲数 uint32_t speedAccumulator; //速度累加器 bool overflowFlag; //溢出标志 //定制化功能的参数 bool resetTimeDirPinLv; //复位时方向的io电平 uint16_t oneMmStep; //电机走1mm所需的步数 GPIO_TypeDef *stepGpioHandle, *dirGpioHandle, *enGpioHandle, *resetSensorGpioHandle; //IO组脉冲、方向、复位限位 uint16_t stepGpioNumber, dirGpioNumber, enGpioNumber, resetSensorGpioNumber; //IO号脉冲、方向、复位限位 bool resetSensorTriggerLv; //复位限位开关被触发后的电平 bool resetFlag; //是否处于复位 bool stopFlag; //是否被停止了 bool keepMoveFlag; //是否要一直转 bool enTimeEnPinSate; /* 初始化s曲线参数表 */ static void sCurveTableInit(); /* 电机每走一步都会执行的参数。主要用于检测复位开关 */ void runTimeImplement(); void setBeltOneMmStep(uint8_t pitch, uint8_t motorGearTeeth) { oneMmStep aCircleStep / (pitch * motorGearTeeth); } void setGearOneMmStep(uint8_t pitch, uint8_t motorGearTeeth) { setBeltOneMmStep(pitch * pi, motorGearTeeth); } void SetEnPinState(bool state) { HAL_GPIO_WritePin(enGpioHandle, enGpioNumber, GPIO_PinState(state)); } public: ivesStepMotorClass(); ~ivesStepMotorClass(); /* 初始化 */ void init(GPIO_TypeDef *stepGpioHandle, uint16_t stepGpioNumber, GPIO_TypeDef *dirGpioHandle, uint16_t dirGpioNumber, GPIO_TypeDef *enGpioHandle, uint16_t enGpioNumber, GPIO_TypeDef *resetSensorGpioHandle, uint16_t resetSensorGpioNumber, bool resetSensorTriggerLv, bool resetTimeDirPinLv, bool enTimeEnPinSate); /* 移动相应的步数 need_run_step步数。负值是向复位方向移动 acc_dec_interval加速度 */ void move(int32_t currentNeedRunStep, uint16_t targetSpeed, uint16_t accelerationInterval); /* 移动到绝对位置。单位为mm */ void moveToAbsolutePosition(uint16_t absolutePosition, uint16_t targetSpeed, uint16_t accelerationInterval); //在没有使用set_mm_step设置mm_step之前不要用 /* 移动到相对位置 */ void moveToRelativePosition(int16_t relativePosition, uint16_t targetSpeed, uint16_t accelerationInterval); /* 算法的循环。放在一个定时器里进行定时执行调用的速度就是电机的最大速度 */ void loop(); /* 根据参数计算并设置每走1mm需要的步数 beltOrGearfalse同步带。true齿条 pitch同步带的型号或齿条的模数 gear_teeth电机上的齿轮的齿数 */ void setOneMmStep(bool beltOrGear, uint8_t pitch, uint8_t motorGearTeeth) { if (beltOrGear 0) setBeltOneMmStep(pitch, motorGearTeeth); else setGearOneMmStep(pitch, motorGearTeeth); } /* 复位 */ void reset(uint16_t targetSpeed 5000, uint16_t accelerationInterval 100); /* 停止 */ void stop(); /* 一直转 */ void keepMove(bool dir, uint16_t targetSpeed, uint16_t accelerationInterval 100); bool getStopFlag() { return stopFlag; } void resetStopFlag() { stopFlag false; } int32_t getCurrentAbsolutePosition() { return currentAbsolutePosition; } }; } // namespace ivesStepMotorNamespace } // namespace motorNamespace } // namespace xiaguangbo #endif#include ivesStepMotor.hpp #include math.h namespace xiaguangbo { namespace motorNamespace { namespace ivesStepMotorNamespace { float ivesStepMotorClass::sCurveTable[sCurvePointNumber]; bool ivesStepMotorClass::sCurveTableFlag; ivesStepMotorClass::ivesStepMotorClass() { } ivesStepMotorClass::~ivesStepMotorClass() { } void ivesStepMotorClass::sCurveTableInit() { for (uint16_t x 0; x sCurvePointNumber; x) sCurveTable[x] 1.0 / (1.0 exp((sCurveExcursion * 2.0 / sCurvePointNumber * -x) sCurveExcursion)); sCurveTableFlag true; } void ivesStepMotorClass::init(GPIO_TypeDef *stepGpioHandle, uint16_t stepGpioNumber, GPIO_TypeDef *dirGpioHandle, uint16_t dirGpioNumber, GPIO_TypeDef *enGpioHandle, uint16_t enGpioNumber, GPIO_TypeDef *resetSensorGpioHandle, uint16_t resetSensorGpioNumber, bool resetSensorTriggerLv, bool resetTimeDirPinLv, bool enTimeEnPinSate) { if (sCurveTableFlag false) { sCurveTableInit(); sCurveTableFlag true; } this-stepGpioHandle stepGpioHandle; this-stepGpioNumber stepGpioNumber; this-dirGpioHandle dirGpioHandle; this-dirGpioNumber dirGpioNumber; this-enGpioHandle enGpioHandle; this-enGpioNumber enGpioNumber; this-resetSensorGpioHandle resetSensorGpioHandle; this-resetSensorGpioNumber resetSensorGpioNumber; this-resetSensorTriggerLv resetSensorTriggerLv; this-resetTimeDirPinLv resetTimeDirPinLv; stop(); SetEnPinState(enTimeEnPinSate); } void ivesStepMotorClass::move(int32_t currentNeedRunStep, uint16_t targetSpeed, uint16_t accelerationInterval) { if (currentNeedRunStep 0) { stop(); return; } this-currentNeedRunStep currentNeedRunStep; temporaryStepCounter 0; currentSpeed 0; this-targetSpeed targetSpeed maxSpeedThreshold - 100 ? maxSpeedThreshold : targetSpeed 100; //防止速度为0 interruptCounterFlag true; interruptCounter 0; accelerationNumberCounter 0; accelerationStep 0; this-accelerationInterval accelerationInterval; speedAccumulator 0; if (currentNeedRunStep 0) HAL_GPIO_WritePin(dirGpioHandle, dirGpioNumber, (GPIO_PinState)(!resetTimeDirPinLv)); else HAL_GPIO_WritePin(dirGpioHandle, dirGpioNumber, (GPIO_PinState)resetTimeDirPinLv); runState motorAccelerateState; } void ivesStepMotorClass::moveToAbsolutePosition(uint16_t absolutePosition, uint16_t targetSpeed, uint16_t accelerationInterval) { move(absolutePosition * oneMmStep - this-currentAbsolutePosition, targetSpeed, accelerationInterval); } void ivesStepMotorClass::moveToRelativePosition(int16_t relativePosition, uint16_t targetSpeed, uint16_t accelerationInterval) { move(relativePosition * oneMmStep, targetSpeed, accelerationInterval); } void ivesStepMotorClass::runTimeImplement() { if (resetSensorGpioHandle nullptr) return; if (HAL_GPIO_ReadPin(resetSensorGpioHandle, resetSensorGpioNumber) resetSensorTriggerLv) //检查复位开关 { if (resetFlag true) { stop(); currentAbsolutePosition 0; //绝对位置置0 } else if (currentNeedRunStep 0) //不能继续向复位的方向走但可以向复位的反方向走 stop(); } } void ivesStepMotorClass::loop() { if (runState motorStopState) return; if (overflowFlag) //如果产生了高电平 HAL_GPIO_WritePin(stepGpioHandle, stepGpioNumber, GPIO_PIN_RESET); //拉低脉冲信号 overflowFlag false; speedAccumulator currentSpeed; //叠加速度 if (speedAccumulator maxSpeedThreshold) //阈值如果当前速度达到阈值发送脉冲的速度就达到最快了这个阈值就是算法的最大速度 { overflowFlag true; speedAccumulator - maxSpeedThreshold; } if (overflowFlag) //如果溢出 { temporaryStepCounter; HAL_GPIO_WritePin(stepGpioHandle, stepGpioNumber, GPIO_PIN_SET); //拉高脉冲信号产生 if (currentNeedRunStep 0) currentAbsolutePosition 0x7fffffff ? currentAbsolutePosition : currentAbsolutePosition; else currentAbsolutePosition (int32_t)0x80000000 ? currentAbsolutePosition-- : currentAbsolutePosition; runTimeImplement(); //额外的操作比如检测复位开关 } //根据电机的状态进行工作 switch (runState) { case motorAccelerateState: //记录加速的步数 if (overflowFlag) accelerationStep; if (interruptCounterFlag) { interruptCounter; if (interruptCounter accelerationInterval) { interruptCounter 0; accelerationNumberCounter; //计录加速的次数 currentSpeed sCurveTable[accelerationNumberCounter] * targetSpeed; //计算当前速度 //如果加速次数达到最高次数就停止加速并让速度等于最大速度再切换状态 if (accelerationNumberCounter sCurvePointNumber) { interruptCounterFlag false; currentSpeed targetSpeed; runState motorMaxState; } } } //如果总步数大于1步 if ((uint32_t)fabs(currentNeedRunStep) 1) { if (temporaryStepCounter (uint32_t)fabs(currentNeedRunStep) / 2) //如果已走步数大于最大步数的一半 { //加速时中断计数被断所以要调整最后一次加速多长时间减速的第一次的速度就维持多长时间 interruptCounter accelerationInterval - interruptCounter; runState motorDecelerateState; } } else if (temporaryStepCounter 0) //只有1步就至少走1步 runState motorDecelerateState; break; case motorMaxState: if (keepMoveFlag overflowFlag true) //当处于保持转动的状态时 temporaryStepCounter--; //加一次就减一次防止步数达到进入减速的要求 if ((uint32_t)fabs(currentNeedRunStep) - temporaryStepCounter accelerationStep) { interruptCounterFlag true; //进入减速状态就要切换为减速状态时的最大速度 accelerationNumberCounter--; currentSpeed sCurveTable[accelerationNumberCounter] * targetSpeed; //计算当前速度 runState motorDecelerateState; } break; case motorDecelerateState: if (interruptCounterFlag) { interruptCounter; if (interruptCounter accelerationInterval) //如果中断次数达到设定次数 { interruptCounter 0; accelerationNumberCounter--; currentSpeed sCurveTable[accelerationNumberCounter] * targetSpeed; //计算当前速度 if (accelerationNumberCounter 1) interruptCounterFlag false; } } if (temporaryStepCounter (uint32_t)fabs(currentNeedRunStep)) stop(); break; default: break; } } void ivesStepMotorClass::reset(uint16_t targetSpeed, uint16_t accelerationInterval) { resetFlag true; keepMove(false, targetSpeed, accelerationInterval); //向复位方向一直走 } void ivesStepMotorClass::stop() { runState motorStopState; stopFlag true; keepMoveFlag false; resetFlag false; } void ivesStepMotorClass::keepMove(bool dir, uint16_t targetSpeed, uint16_t accelerationInterval) { keepMoveFlag true; move(aCircleStep * 100 * (dir false ? -1 : 1), targetSpeed, accelerationInterval); //步数至少让加速能加满 } } // namespace ivesStepMotorNamespace } // namespace motorNamespace } // namespace xiaguangbo

相关新闻

2026/8/29 23:08:31

C++ String与STL核心解析:从基础概念到工程实践

1. 从“Hello World”到“Hello STL”:为什么C程序员绕不开String和STL刚学C那会儿,觉得这语言真麻烦,连个字符串都得用字符数组char str[],还得操心结尾的\0。后来老师扔过来一句std::string s “Hello World”;,世界…

2026/8/29 23:03:30

前端八股文是什么?高频面试考点与实战答法全解析

“前端八股文”这个词,圈里人听了都会心一笑。说白了,就是面试里翻来覆去问的那批固定知识点:闭包、原型链、事件循环、this 指向、Vue 响应式原理、浏览器从输入 URL 到页面展示发生了什么。背的人觉得枯燥,问的人也觉得套路&…

2026/8/29 23:03:30

4K MV制作全流程:从拍摄参数到编码压制的实用要点

4K 画质的音乐 MV,例如派伟俊《别恋》官方 MV,观众在屏幕上看到的是几分钟的歌曲影像,但创作端面对的是拍摄、剪辑、调色、音频处理、编码压制和平台发布一整条技术链路。很多刚开始接触视频制作的人会把注意力放在镜头构图和转场特效上&…

2026/8/29 23:23:31

低功耗UWB IP核解读:FiRa 2.0规范与厘米级定位技术落地

如果你这两年留意过手机上的“精确查找”功能,或者听身边的人聊过用手机刷开车门,那你其实已经接触过UWB了。但今天要聊的,是这颗UWB芯片背后更上游的一环——Ceva刚刚推出的低功耗UWB IP核,它直接面向FiRa 2.0规范,主…

2026/8/29 23:23:31

Vue项目部署实战:从本地开发到宝塔面板上线全流程指南

1. 项目概述:从本地开发到线上服务的最后一公里作为一名前端开发者,我们花了大量时间在本地用Vue CLI或Vite构建出功能完善、界面炫酷的单页应用。当项目在localhost:8080上跑得顺风顺水时,一个现实问题就摆在了面前:如何让全世界…

2026/8/29 23:23:31

用友前端笔试题解析:JavaScript、CSS与手写代码核心考点

前阵子整理旧电脑,翻到自己当年备战校招时存的一堆笔试题,其中就有一套用友2018年的web前端笔试题。虽然时间过去好几年了,但前端笔试的核心考点其实变化不大——JavaScript基础、CSS布局、浏览器机制、手写代码,这些至今仍然是校…

2026/8/29 23:23:31

字节测开面试全攻略:从八股文到场景题一次讲透

字节测试开发这个岗位,最近几年热度一直很高。钱给到位、做的事情又比纯功能测试有技术含量,比纯开发多一层质量视角,听起来像个两全其美的选择。但也正因为想去的人多,面试难度水涨船高,我见过太多准备不足的同学&…

2026/8/29 23:18:31

四足机器人步态规划与运动控制核心解析

简介:在足式机器人的运动控制研究中,步态是决定其动态性能与稳定性的核心概念。步态规划通过设定各腿的运动相位与支撑顺序,使四足机器人能够在不同地形上实现协调移动。理解步态原理有助于优化机器人的能量效率与负载能力,技术价…

2026/8/29 21:30:11

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/28 16:16:21

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/28 16:16:22

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/29 0:01:10

etc目录下的profile.d文件目录设置环境变量和全局脚本shell

一、设置环境变量etc目录下的profile.d文件目录 /etc/profile.d1、编写 vi test.sh文件内容# jdk变量 export ZHK_HOME/root export PATH$PATH:$ZHK_HOME/test # 可以取出来ZHK_HOME变量给ZZZ_HOME赋值 export ZZZ_HOME${ZHK_HOME}/test2、刷新 执行source /etc/profile 命令使…

2026/8/29 0:01:10

【JavaScript】内存管理-垃圾回收机制-内存泄露

内存管理 C 语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()。 而 JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。释放的过程称为垃圾回收。 整…

2026/8/29 0:01:10

Labgrid-MCP:为嵌入式硬件实验室接入AI Agent操控能力

Labgrid-MCP 的目标是把 MCP(Model Context Protocol)能力延伸到真实嵌入式硬件实验室:AI Agent 通过一个标准化的 MCP Server,就能查看目标板状态、控制上电断电、复位开发板、读取串口日志,甚至执行镜像刷写。对于经…

2026/8/28 16:16:48

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/28 16:16:50

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/28 11:06:45

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…