当前位置: 首页 > news >正文

万维网的代表网站企业展厅的作用

万维网的代表网站,企业展厅的作用,wordpress profile,哪些品牌网站做的好注#xff1a;本文为 “PID 算法 | stm32 直流电机控制” 相关合辑。 图片清晰度受引文原图所限。 略作重排#xff0c;未全校去重。 如有内容异常#xff0c;请看原文。 STM32—PID 控制在直流电机中的应用 Aspirant-GQ 于 2020-04-28 23:23:39 发布 一、PID 控制算法 1…注本文为 “PID 算法 | stm32 直流电机控制” 相关合辑。 图片清晰度受引文原图所限。 略作重排未全校去重。 如有内容异常请看原文。 STM32—PID 控制在直流电机中的应用 Aspirant-GQ 于 2020-04-28 23:23:39 发布 一、PID 控制算法 1. PIDProportion-Integral-Differential 在过程控制领域PID 算法是一种广泛应用的控制算法。PID 控制算法通过对偏差进行比例、积分、微分运算实现控制使偏差趋近于某一固定值。PID 控制器由三个核心单元构成比例单元P、积分单元I和微分单元D。PID 控制本质上是一种基于误差的控制方式。 PID 控制系统模拟的框图如下 2. PID 系数的理解 理解 PID 的三个系数时可结合 PID 调节过程中的响应曲线。若要使实际响应曲线趋近于理想响应曲线需关注三个关键指标快速性P、准确性I和稳定性D。要使控制量保持在理想状态这三个参数必须调整至合理范围。 以下是三个系数对整体控制效果的影响分析 Ⅰ. 比例P部分 当输入值与目标值产生偏差 eee 时需减小偏差。比例系数 PPP 用于缩小偏差使控制量向减小偏差的方向调整。比例系数 PPP 越大偏差缩小的速度越快因此 PPP 的作用是使控制量更快地接近目标值。 然而当 PPP 过大时系统易超出目标值引发反向调整进而在目标值附近产生振荡。这是 PPP 过大的局限性导致系统稳定性下降。 因此虽然较大的 PPP 可实现快速响应但也易引发振荡破坏系统稳定性。可通过适当增大微分系数 DDD 以提高系统稳定性。 Ⅱ. 积分I部分 积分环节的表达式如下 KpTi∫0te(τ)dτ\frac{K_p}{T_i} \int_{0}^{t} e(\tau) \, d\tau Ti​Kp​​∫0t​e(τ)dτ 由表达式可知只要存在偏差积分结果就会持续增大即控制作用会不断增强当偏差为 0 时积分结果为一常数此时控制作用才可能处于稳定状态。 积分环节的作用是消除系统的静态偏差。一旦存在误差积分结果便会增加系统随之做出反应直至偏差为 0。因此积分环节保证了控制的准确性。 积分环节虽能消除静态偏差但会降低系统的响应速度即积分环节对比例环节 PPP 存在抑制作用。 Ⅲ. 微分D部分 当比例系数 PPP 过大时会引发系统振荡降低系统稳定性。可通过增大微分系数 DDD 来减弱振荡。 微分环节的作用是根据偏差的变化趋势预先进行纠正。由于微分可看作求导运算而求导能反映函数的变化趋势因此通过微分环节可对偏差的变化进行预判并抑制避免出现矫枉过正的现象。 微分环节的引入有助于减小振荡使系统趋于稳定。微分系数 DDD 越大对比例环节 PPP 的抑制效果越强。 3. PID 的数字化处理 由于计算机控制属于采样控制仅能依据采样时刻的偏差值计算控制量因此需通过软件实现控制算法。对 PID 进行离散化处理即数字化处理也就是根据离散采样点的偏差值实施控制。离散化处理后的变化如下 用差分代替微分用累加代替积分 数字化处理后的 PID 系统如下 采样周期越小系统越接近模拟控制控制效果也就越优。 二、位置闭环控制 位置闭环控制又称位置式 PID 控制是传统的 PID 控制方式。其通过使控制偏差逐渐趋近于 0使控制量趋于目标值。 在电机控制中位置闭环控制用于调控电机的转动位置。通过编码器的脉冲累加测量电机的位置信息并与目标位置进行比较得到偏差值。随后通过比例、积分、微分的 PID 算法进行控制使偏差趋于 0。 在电机控制中输出量为电机控制模块输出的占空比作用于电机转速。此时离散 PID 的公式如下 根据位置式离散 PID 公式 PwmKp⋅e(k)Ki⋅∑e(k)Kd⋅[e(k)−e(k−1)]Pwm K_p \cdot e(k) K_i \cdot \sum e(k) K_d \cdot [e(k) - e(k-1)]PwmKp​⋅e(k)Ki​⋅∑e(k)Kd​⋅[e(k)−e(k−1)] 其中 e(k)e(k)e(k)本次偏差e(k−1)e(k-1)e(k−1)上一次的偏差∑e(k)\sum e(k)∑e(k)e(k)e(k)e(k) 及之前偏差的累积和kkk 为 1, 2, …, kkkPwmPwmPwm 代表输出 其控制框图如下 控制实现代码如下 /* 返回输出 * Encoder 为输入编码器测量的信号即电机转速 * Target 为目标值 */ // KP、KI、KD 为 PID 的系数 int Position_PID(int Encoder, int Target) {static float Bias, Pwm, Integral_bias, Last_bias; Bias Encoder - Target; // 计算偏差Integral_bias Bias; // 计算偏差积分累加Pwm KP * Bias KI * Integral_bias KD * (Bias - Last_bias); // 计算输出根据 PIDLast_bias Bias; // 保存为上一次偏差return Pwm; // 返回输出 }在定时中断中实现 PID 控制定时中断相当于离散的采样点。中断服务函数中可调用控制函数 // 中断中如下调用 Moto Position_PID(Encoder, Target); // 最终的控制落回到控制电机转速上 Set_Pwm(Moto);位置控制的调节经验先仅采用 PPP 控制逐渐增大 PPP 的值待系统出现振荡后加入微分控制 DDD 以抑制振荡调整 KDK_DKD​ 直至振荡消除之后再根据系统对响应速度和静差的要求调节 P、IP、IP、I 参数。 三、速度闭环控制 速度闭环控制又称增量式 PID 控制与位置式 PID 控制的区别在于位置式 PID 输出的是控制量的新状态而增量式 PID 输出的是控制量的增量。 增量式 PID 的离散公式如下 ΔPwmKP⋅[e(k)−e(k−1)]KI⋅e(k)KD⋅[e(k)−2e(k−1)e(k−2)]\Delta \text{Pwm} K_P \cdot [e(k) - e(k-1)] K_I \cdot e(k) K_D \cdot [e(k) - 2e(k-1) e(k-2)] ΔPwmKP​⋅[e(k)−e(k−1)]KI​⋅e(k)KD​⋅[e(k)−2e(k−1)e(k−2)] 其中 ΔPwm\Delta \text{Pwm}ΔPwm 为控制量的增量如 PWM 占空比的变化量e(k)e(k)e(k) 为当前时刻的偏差设定值 - 反馈值e(k−1)e(k-1)e(k−1)、e(k−2)e(k-2)e(k−2) 分别为上一时刻、上两时刻的偏差KPK_PKP​、KIK_IKI​、KDK_DKD​ 分别为比例、积分、微分系数 位置闭环控制用于使电机准确转动到某一位置而速度闭环控制用于使电机以某一速度转动。在速度闭环控制中仅需采用 PI 控制 即可因此简化后的公式如下 PwmKp⋅[e(k)−e(k−1)]Ki⋅e(k)\text{Pwm} K_p \cdot [e(k) - e(k-1)] K_i \cdot e(k) PwmKp​⋅[e(k)−e(k−1)]Ki​⋅e(k) 注意公式直接将控制量的增量叠加在上一次控制量的状态上 控制框图如下 控制代码如下 /* 返回输出 * Encoder 为输入编码器测量的信号即电机转速 * Target 为目标值 */ // KP、KI 为 PID 的系数 int Incremental_PI(int Encoder, int Target) {static float Bias, Pwm, Last_bias; Bias Encoder - Target; // 计算偏差Pwm KP * (Bias - Last_bias) KI * Bias; // 增量式 PILast_bias Bias; // 保存为上一次偏差return Pwm; // 返回输出 }在定时中断中实现 PID 控制定时中断相当于离散的采样点。中断服务函数中可调用控制函数 // 中断中如下调用 Moto Incremental_PI(Encoder, Target); // 最终的控制落回到控制电机转速上 Set_Pwm(Moto);需要注意的是速度闭环控制采用 PI 控制其公式与 PID 有所不同输出的是控制量的增量。此处将增量直接叠加在上一次的状态量上因此输出的仍是控制量的新状态。 最后附上常用的 PID 参数调整口诀 常用口诀 参数整定找最佳从小到大顺序查 先是比例后积分最后再把微分加 曲线振荡很频繁比例度盘要放大 曲线漂浮绕大湾比例度盘往小扳 曲线偏离回复慢积分时间往下降 曲线波动周期长积分时间再加长 曲线振荡频率快先把微分降下来 动差大来波动慢微分时间应加长 理想曲线两个波前高后低 4 比 1 一看二调多分析调节质量不会低 STM32 直流电机控制—PID 算法篇 “星云 - 视界” 于 2020-10-05 00:58:28 发布 一、常用的控制算法 1. 控制系统的基本结构 控制系统的基本结构如图 控制目的 控制的根本目的是使控制对象当前的状态值与用户的设定值相同最大限度接近。 基本思想 用户设定值 SvS_vSv​ 与被控制对象当前的值 PvP_vPv​ 同时送入由特定硬件电路模型或特定软件算法组成的控制算法逻辑中利用不同的控制算法对 SvS_vSv​ 和 PvP_vPv​ 进行分析、判断、处理从而产生当前应输出的控制信号 OUTOUTOUT。控制信号经过执行机构施加到控制对象上以产生预期的控制效果。 2. 常用控制算法位式控制 ① 二位式控制算法 二位式控制算法具体控制逻辑如图所示 特点 二位式控制算法输出的控制量只有高、低 2 种状态。执行机构使控制对象要么全额工作要么停止工作。当 PvP_vPv​ 低于 SvS_vSv​ 时全额工作PvP_vPv​ 大于或等于 SvS_vSv​ 时彻底停止工作。若控制对象是一个 1000 W 的加热器温度未达设定值时以 1000 W 全功率运行温度达到设定值时停止工作。由于环境因素、控制系统传输延时或控制对象本身的惯性等因素控制效果往往是 PvP_vPv​ 在 SvS_vSv​ 的上下有较大波动。在 PvP_vPv​ 接近 SvS_vSv​ 的临界点时控制输出信号 OUTOUTOUT 往往在 HHH 和 LLL 之间频繁转换导致执行部件的触点频繁开关动作易产生干扰及缩短执行部件的寿命。 ② 具有回差的二位式控制算法 具有回差的二位式控制算法具体控制逻辑如图所示 特点 取 SvS_vSv​ 的正负 10% 左右作为回差调节上下限高于上限才开始输出 LLL低于下限才开始输出 HHH避免了一般二位式控制算法在临界点时执行部件频繁动作因控制对象只有全额运行或不运行两种状态仍然存在一般二位式控制算法的缺点PvP_vPv​ 总是在 SvS_vSv​ 附近波动。 ③ 三位式控制算法 三位式控制算法具体控制逻辑如图所示 特点 在二位式控制的基础上对控制对象的功率分为 0 功率停止工作、半功率、全功率三种情况即三位。当前值低于设定值一定比例一般 10%时 OUT1OUT1OUT1 和 OUT2OUT2OUT2 同时起控制作用控制对象全功率运行当前值在设定值的正负 10% 范围时OUT1OUT1OUT1 单独作用工作于半功率状态当前值达到或超过设定值时 OUT1OUT1OUT1 和 OUT2OUT2OUT2 都停止输出控制对象停止工作相对一般二位式控制算法三位式算法对控制对象的当前状态值做了简单分析并根据不同的当前状态值输出不同的控制信号能够较好地对输出产生控制效果。 ④ 小结 位式控制的主要特征 控制算法只关注控制当前的状态值PvP_vPv​与设定值之间的差值——二者当前有差值就输出控制信号二者当前无差值就不输出控制信号。位式控制算法的输出信号状态单一只输出高、低两种状态这两种状态对应着控制对象的工作与不工作——如果是温度控制系统就是加热器加热与不加热。由于控制系统自身的延时或者控制对象自身的惯性位式控制算法只能使控制对象当前的状态值在设定值附近波动不能很好地跟踪在设定值附近甚至与之相等。 二、PID 控制算法 1. PID 控制算法的基本思想 PID 控制算法具体控制逻辑如图所示 PID 算法是一种具有预见性的控制算法其核心思想是 PID 算法不但考虑控制对象的当前状态值现在状态而且还考虑控制对象过去一段时间的状态值历史状态和最近一段时间的状态值变化预期由这 3 方面共同决定当前的输出控制信号PID 控制算法的运算结果是一个数利用这个数来控制被控对象在多种工作状态比如加热器的多种功率阀门的多种开度等工作一般输出形式为 PWM基本满足了按需输出控制信号、根据情况随时改变输出的目的。 2. PID 算法分析 设某控制系统用户设定值为 SvS_vSv​即希望通过 PID 控制算法使被控制对象的状态值保持在 SvS_vSv​ 附近。 ① 从系统投入运行开始控制算法每隔一段时间对被控制对象的状态值进行采样。由此可得到开机以来由各个采样时间点被控制对象的状态值所形成的数据序列 X1,X2,X3,X4,…,Xk−2,Xk−1,XkX_1, X_2, X_3, X_4, \ldots, X_{k-2}, X_{k-1}, X_kX1​,X2​,X3​,X4​,…,Xk−2​,Xk−1​,Xk​ 说明 X1X_1X1​开机以来的第一次采样值 XkX_kXk​目前的采样值最近一次的采样值 ② 从这个采样值序列中提取出三方面信息 当前采样值 XkX_kXk​ 与用户设定值 SvS_vSv​ 之间的差值EkE_kEk​ EkSv−XkE_k S_v - X_kEk​Sv​−Xk​ 分析 EkE_kEk​ Ek0E_k 0Ek​0说明当前状态值未达标 Ek0E_k 0Ek​0说明当前控制状态值正好满足要求 Ek0E_k 0Ek​0说明当前状态值已经超标 结论EkE_kEk​ 反应了控制对象当前值与设定值的偏差程度可根据 EkE_kEk​ 的大小对输出信号 OUTOUTOUT 进行调整偏差程度大则 OUTOUTOUT 增大偏差程度小则 OUTOUTOUT 减小。即输出信号的强弱与当前偏差程度的大小成比例因此根据 EkE_kEk​ 的大小来给出控制信号 OUTOUTOUT 的当前值的算法称为比例控制Proportion。其数学模型可表示为 POUT(Kp⋅Ek)Out0P_{\text{OUT}} (K_p \cdot E_k) \text{Out}_0POUT​(Kp​⋅Ek​)Out0​ 其中KpK_pKp​ 一般称之为比例系数可理解为硬件上的放大器或衰减器适当选取 KpK_pKp​ 可将当前误差值 EkE_kEk​ 按一定的增益放大或缩小以提高控制算法的响应速度Out0\text{Out}_0Out0​ 是一个常数目的是当 EkE_kEk​ 为 0 时确保输出信号不为 0以避免在当前值与设定值相等时控制器输出信号 OUTOUTOUT 为 0导致系统处于无控制信号的失控状态。 将投入运行以来的各个采样值都与设定值相减可得到开机以来每个采样时刻的偏差序列数据 E1,E2,E3,…,Ek−2,Ek−1,EkE_1, E_2, E_3, \ldots, E_{k-2}, E_{k-1}, E_kE1​,E2​,E3​,…,Ek−2​,Ek−1​,Ek​ 说明 E1E_1E1​开机的第一个采样点与设定值的偏差E1Sv−X1E_1 S_v - X_1E1​Sv​−X1​ E2Sv−X2E_2 S_v - X_2E2​Sv​−X2​ …\ldots… Ek−2Sv−Xk−2E_{k-2} S_v - X_{k-2}Ek−2​Sv​−Xk−2​ Ek−1Sv−Xk−1E_{k-1} S_v - X_{k-1}Ek−1​Sv​−Xk−1​ EkE_kEk​当前的采样值与设定值的偏差EkSv−XkE_k S_v - X_kEk​Sv​−Xk​。 分析开机以来的误差序列 每个偏差值可能有 000、000、000 这三种可能的值。从开机到现在控制算法不断输出控制信号对被控对象进行控制导致过去这段时间有时超标Ex0E_x 0Ex​0有时未达标Ex0E_x 0Ex​0有时正好满足要求Ex0E_x 0Ex​0。若将这些偏差值进行累加求代数和得到 SkS_kSk​即 SkE1E2E3…Ek−2Ek−1EkS_k E_1 E_2 E_3 \ldots E_{k-2} E_{k-1} E_kSk​E1​E2​E3​…Ek−2​Ek−1​Ek​ 分析 SkS_kSk​ Sk0S_k 0Sk​0过去大多数时候未达标 Sk0S_k 0Sk​0过去控制效果较理想 Sk0S_k 0Sk​0过去大多数时候已经超标 结论 通过对 SkS_kSk​ 的分析可对控制算法过去的控制效果进行综合评估。这体现了控制算法按照原来的方式输出的控制信号导致了现在的控制结果因此应利用这个值对当前要输出的控制信号 OUTOUTOUT 进行修正以确保控制对象在将来的一小段时间尽快达到用户设定的值。 SkS_kSk​ 实际上是过去每个时间点的误差相加与数学上的定积分运算类似因此根据 SkS_kSk​ 对输出信号进行调节的算法称为积分Integral算法。积分控制的数学模型为 IOUT(Kp⋅(1Ti∫Exdt))Out0I_{\text{OUT}} \left( K_p \cdot \left( \frac{1}{T_i} \int E_x \text{d}t \right) \right) \text{Out}_0IOUT​(Kp​⋅(Ti​1​∫Ex​dt))Out0​ 其中KpK_pKp​ 是一常数其目的类似硬件上的放大器用于将 SkS_kSk​ 放大或衰减Out0\text{Out}_0Out0​ 是一常数用于在历史积分偏差值为 0 时确保系统有一个输出值避免失控TiT_iTi​ 是积分时间常数取值越大输出量 OUTOUTOUT 越小可理解为历史上较早的误差值对当前输出信号的影响取值越小输出 OUTOUTOUT 越强烈可理解为积分只考虑了最近一段时间的误差。实际中若系统已运行“很长”一段时间早期采样的偏差值可忽略其对当前控制的影响因此应根据情况选择合理的 TiT_iTi​ 值以获得良好的控制效果。 最近两次的偏差之差 DkD_kDk​ DkEk−Ek−1D_k E_k - E_{k-1}Dk​Ek​−Ek−1​ 说明 EkE_kEk​当前的偏差 Ek−1E_{k-1}Ek−1​当前的前一个采样时刻的偏差值即上一次的偏差值 分析 DkD_kDk​ Dk0D_k 0Dk​0说明从上一采样时刻到当前误差有增大趋势 Dk0D_k 0Dk​0说明从上一采样时刻到当前误差平稳 Dk0D_k 0Dk​0说明从上一采样时刻到当前误差有减小趋势 结论 DkD_kDk​ 能够说明从上次采样到当前采样的这段时间被控制对象的状态变化趋势这种变化趋势很可能在一定程度上延续到下一个采样时间点因此可根据这个变化趋势DkD_kDk​ 的值对输出信号 OUTOUTOUT 进行调整达到提前控制的目的。 DkD_kDk​ 形如数学上的微分运算反应了控制对象在一段时间内的变化趋势及变化量因此利用 DkD_kDk​ 对控制器输出信号进行调节的算法称为微分Differential算法。其数学模型可表达为 DOUTKp⋅(Td⋅dedt)Out0D_{\text{OUT}} K_p \cdot \left( T_d \cdot \frac{\text{d}e}{\text{d}t} \right) \text{Out}_0DOUT​Kp​⋅(Td​⋅dtde​)Out0​ 其中KpK_pKp​ 为一常数可理解为硬件上的放大器或衰减器用于对输出信号 OUTOUTOUT 的增益进行调整Out0\text{Out}_0Out0​ 为一常数用于在 DkD_kDk​ 为 0 时确保 OUTOUTOUT 有一个稳定的控制值避免失控TdT_dTd​ 叫微分时间常数犹如硬件上电感器的自感系数TdT_dTd​ 越大OUTOUTOUT 增大对输出信号产生强烈影响。 ③ PID 算法的形成 比例、积分、微分三种算法的优缺点分析 POUT(Kp⋅Ek)Out0P_{\text{OUT}} (K_p \cdot E_k) \text{Out}_0POUT​(Kp​⋅Ek​)Out0​ ——比例算法 IOUT(Kp⋅(1Ti∫Exdt))Out0I_{\text{OUT}} \left( K_p \cdot \left( \frac{1}{T_i} \int E_x \text{d}t \right) \right) \text{Out}_0IOUT​(Kp​⋅(Ti​1​∫Ex​dt))Out0​ ——积分算法 DOUTKp⋅(Td⋅dedt)Out0D_{\text{OUT}} K_p \cdot \left( T_d \cdot \frac{\text{d}e}{\text{d}t} \right) \text{Out}_0DOUT​Kp​⋅(Td​⋅dtde​)Out0​ ——微分算法 比例算法只考虑控制对象当前误差当前有误差才输出控制信号当前没有误差就不输出控制信号。即只要偏差已产生比例算法才采取措施进行调整因此单独的比例算法不可能将控制对象的状态值控制在设定值上始终在设定值上下波动但比例控制反应灵敏有误差马上就反应到输出。 积分算法考虑了被控制对象的历史误差情况过去的误差状况参与当前的输出控制。但在系统未达到目标期间历史误差往往会对当前控制产生干扰即拖后腿使用不当反而会干扰当前输出。不过在系统进入稳定状态后特别是当前值与设定值没有偏差时积分算法可根据过去的偏差值输出一个相对稳定的控制信号以防止产生偏离目标起到预防作用。 微分算法单纯考虑近期的变化率当系统的偏差趋近于某一固定值时变化率为 0微分算法不输出信号对其偏差进行调整因此微分算法不能单独使用它只关心偏差的变化速度不考虑是否有偏差偏差变化率为 0 时偏差不一定是 0。但微分算法能获得控制对象近期的变化趋势可协助输出信号尽早抑制控制对象的变化。可理解为当即将有剧烈变化时就大幅度调整输出信号进行抑制避免控制对象的大幅度变化。 以上三种算法综合起来产生一个当前的控制量对控制对象进行控制它们的优缺点互补即形成经典的 PID 算法。 3. PID 算法数学模型 OUTPOUTIOUTDOUTOUT P_{\text{OUT}} I_{\text{OUT}} D_{\text{OUT}}OUTPOUT​IOUT​DOUT​ 即 OUT((Kp⋅Ek)Out0)(Kp⋅(1Ti∫Exdt)Out0)(Kp⋅(Td⋅dedt)Out0)OUT \left( (K_p \cdot E_k) \text{Out}_0 \right) \left( K_p \cdot \left( \frac{1}{T_i} \int E_x \text{d}t \right) \text{Out}_0 \right) \left( K_p \cdot \left( T_d \cdot \frac{\text{d}e}{\text{d}t} \right) \text{Out}_0 \right)OUT((Kp​⋅Ek​)Out0​)(Kp​⋅(Ti​1​∫Ex​dt)Out0​)(Kp​⋅(Td​⋅dtde​)Out0​) 整理该式将各项的 Out0\text{Out}_0Out0​ 归并为 OUT0OUT_0OUT0​得到 OUTKp(Ek(1Ti∫Exdt)(Td⋅dedt))OUT0OUT K_p \left( E_k \left( \frac{1}{T_i} \int E_x \text{d}t \right) \left( T_d \cdot \frac{\text{d}e}{\text{d}t} \right) \right) OUT_0OUTKp​(Ek​(Ti​1​∫Ex​dt)(Td​⋅dtde​))OUT0​ PID 算法在单片机中的应用 1PID 算法在单片机中应用时对积分和微分项可作近似变换 对于积分项可改写成 I1Ti∑k0nEk⋅TI \frac{1}{T_i} \sum_{k0}^{n} E_k \cdot TITi​1​∑k0n​Ek​⋅T 即用过去一段时间的采样点的偏差值的代数和代替积分。其中TTT 是采样周期也叫控制周期每隔 TTT 时间段进行一次 PID 计算。 对于微分项可改写成 DTd⋅Ek−Ek−1TD T_d \cdot \frac{E_k - E_{k-1}}{T}DTd​⋅TEk​−Ek−1​​ 其中EkE_kEk​ 为本次偏差Ek−1E_{k-1}Ek−1​ 为上次的偏差值。 2位置式 PID 算法数学模型 由此可得到单片机中 PID 算法的表达式 OUTKp(Ek(1Ti∫Exdt)(Td⋅dedt))OUT0OUT K_p \left( E_k \left( \frac{1}{T_i} \int E_x \text{d}t \right) \left( T_d \cdot \frac{\text{d}e}{\text{d}t} \right) \right) OUT_0OUTKp​(Ek​(Ti​1​∫Ex​dt)(Td​⋅dtde​))OUT0​ 即 OUTKp(En(1Ti∑k0nEk⋅T)(Td⋅Ek−Ek−1T))OUT0OUT K_p \left( E_n \left( \frac{1}{T_i} \sum_{k0}^{n} E_k \cdot T \right) \left( T_d \cdot \frac{E_k - E_{k-1}}{T} \right) \right) OUT_0OUTKp​(En​(Ti​1​∑k0n​Ek​⋅T)(Td​⋅TEk​−Ek−1​​))OUT0​ 进一步展开得 OUT(Kp⋅Ek)(Kp⋅TTi∑k0nEk)(Kp⋅TdT(Ek−Ek−1))OUT0OUT (K_p \cdot E_k) \left( K_p \cdot \frac{T}{T_i} \sum_{k0}^{n} E_k \right) \left( K_p \cdot \frac{T_d}{T} (E_k - E_{k-1}) \right) OUT_0OUT(Kp​⋅Ek​)(Kp​⋅Ti​T​∑k0n​Ek​)(Kp​⋅TTd​​(Ek​−Ek−1​))OUT0​ 令 KiKp⋅TTiK_i K_p \cdot \frac{T}{T_i}Ki​Kp​⋅Ti​T​KdKp⋅TdTK_d K_p \cdot \frac{T_d}{T}Kd​Kp​⋅TTd​​则 OUT(Kp⋅Ek)(Ki∑k0nEk)(Kd(Ek−Ek−1))OUT0OUT (K_p \cdot E_k) \left( K_i \sum_{k0}^{n} E_k \right) \left( K_d (E_k - E_{k-1}) \right) OUT_0OUT(Kp​⋅Ek​)(Ki​∑k0n​Ek​)(Kd​(Ek​−Ek−1​))OUT0​ 程序设计时利用 C 语言或汇编语言可方便实现这个计算公式。OUTOUTOUT 即为本次运算的结果利用 OUTOUTOUT 可驱动执行机构输出对应的控制信号例如温度控制中可控制 PWM 的宽度电磁阀中可改变电磁线圈电流以改变阀门开度或可控硅的导通角度等。这种 PID 算法计算出的结果OUTOUTOUT 值表示当前控制器应该输出的控制量因此称为位置式直接输出了执行机构应该达到的状态值。 3增量式 PID 算法 位置式 PID 算法计算量较大较消耗处理器资源。在有些控制系统中执行机构本身没有记忆功能如 MOS 管是否导通完全取决于控制极电压可控硅是否导通取决于触发信号继电器是否接通取决于线圈电流等只要控制信号丢失执行机构就停止在这些应用中应采用位置式 PID。 也有一些执行机构本身具有记忆功能如步进电机即使控制信号丢失由于其自身的机械结构会保持在原来的位置等在这些控制系统中PID 算法无需输出本次应该到达的真实位置只需说明应在上次的基础上对输出信号做多大的修正可正可负即可这就是增量式 PID 算法。 增量式 PID 计算出的是应在当前控制信号上的调整值若计算结果为正则增强输出信号若为负则减弱输出信号。 增量式 PID 算法数学模型 若用 OUTk−1OUT_{k-1}OUTk−1​ 表示上次的输出控制信号值当前的输出值应为 OUTkOUT_kOUTk​则两者关系为 OUTkOUTk−1ΔOUTOUT_k OUT_{k-1} \Delta OUTOUTk​OUTk−1​ΔOUT 其中ΔOUT\Delta OUTΔOUT 即为应输出的增量值。上式变形得 ΔOUTOUTk−OUTk−1\Delta OUT OUT_k - OUT_{k-1}ΔOUTOUTk​−OUTk−1​ 本次的位置式算法输出 OUTk(Kp⋅Ek)(Ki∑k0nEk)(Kd(Ek−Ek−1))OUT0OUT_k (K_p \cdot E_k) \left( K_i \sum_{k0}^{n} E_k \right) \left( K_d (E_k - E_{k-1}) \right) OUT_0OUTk​(Kp​⋅Ek​)(Ki​∑k0n​Ek​)(Kd​(Ek​−Ek−1​))OUT0​ ——1 式 上次的位置式算法输出 OUTk−1(Kp⋅Ek−1)(Ki∑k0n−1Ek)(Kd(Ek−1−Ek−2))OUT0OUT_{k-1} (K_p \cdot E_{k-1}) \left( K_i \sum_{k0}^{n-1} E_k \right) \left( K_d (E_{k-1} - E_{k-2}) \right) OUT_0OUTk−1​(Kp​⋅Ek−1​)(Ki​∑k0n−1​Ek​)(Kd​(Ek−1​−Ek−2​))OUT0​ ——2 式 上述 1 式减 2 式即得到相邻两次的增量 如前所述KiKp⋅TTiK_i K_p \cdot \frac{T}{T_i}Ki​Kp​⋅Ti​T​KdKp⋅TdTK_d K_p \cdot \frac{T_d}{T}Kd​Kp​⋅TTd​​则 ΔOUTOUTk−OUTk−1Kp(Ek−Ek−1)(Kp⋅TTi)Ek(Kp⋅TdT)(Ek−2Ek−1Ek−2)\Delta OUT OUT_k - OUT_{k-1} K_p (E_k - E_{k-1}) \left( K_p \cdot \frac{T}{T_i} \right) E_k \left( K_p \cdot \frac{T_d}{T} \right) (E_k - 2E_{k-1} E_{k-2})ΔOUTOUTk​−OUTk−1​Kp​(Ek​−Ek−1​)(Kp​⋅Ti​T​)Ek​(Kp​⋅TTd​​)(Ek​−2Ek−1​Ek−2​) 其中EkE_kEk​ 为本次的偏差Ek−1E_{k-1}Ek−1​ 为上次的偏差Ek−2E_{k-2}Ek−2​ 为上上次的偏差KpK_pKp​ 为算法增益调节TiT_iTi​ 为积分时间TdT_dTd​ 为微分时间常数。 结论 增量式 PID 的计算只需最近 3 次的偏差本次偏差、上次偏差、上上次偏差不需要处理器存储大量的历史偏差值计算量也相对较少容易实现。 4关于 TiT_iTi​ 和 TdT_dTd​ 的理解 在 PID 控制算法中当前的输出信号由比例项、积分项、微分项共同作用形成。当比例项输出不为 0 时若积分项对运算输出的贡献作用与比例项对运算输出的贡献相同即同为正或同为负积分项相当于重复了一次比例项产生作用的时间这个时间可理解为积分时间。 当比例项不为 0 时若微分项在一段时间里计算的结果与比例项对输出的贡献相同即同为正或同为负微分项相当于在一段时间里重复了比例项的作用这段时间可理解为微分时间。 实际应用中应合理选择 KpK_pKp​、TiT_iTi​、TdT_dTd​ 以确保三者对输出的贡献平衡从而使控制对象稳定在设定值附近。 4. 小结 PID 是比例、积分、微分的简称PID 控制的难点并非编程而是控制器的参数整定。参数整定的关键是正确理解各参数的物理意义PID 控制的原理可用人对炉温的手动控制来理解。阅读本文不需要高深的数学知识。 比例控制 有经验的操作人员手动控制电加热炉的炉温可获得非常好的控制品质PID 控制与人工控制的控制策略有很多相似之处。 下面介绍操作人员怎样用比例控制的思想来手动控制电加热炉的炉温。假设用热电偶检测炉温用数字仪表显示温度值。在控制过程中操作人员用眼睛读取炉温并与炉温给定值比较得到温度的误差值。然后用手操作电位器调节加热的电流使炉温保持在给定值附近。 操作人员知道炉温稳定在给定值时电位器的大致位置我们将它称为位置 LLL并根据当时的温度误差值调整控制加热电流的电位器的转角。炉温小于给定值时误差为正在位置 LLL 的基础上顺时针增大电位器的转角以增大加热的电流。炉温大于给定值时误差为负在位置 LLL 的基础上反时针减小电位器的转角并令转角与位置 LLL 的差值与误差成正比。上述控制策略就是比例控制即 PID 控制器输出中的比例部分与误差成正比。 闭环中存在着各种各样的延迟作用。例如调节电位器转角后到温度上升到新的转角对应的稳态值时有较大的时间延迟。由于延迟因素的存在调节电位器转角后不能马上看到调节的效果因此闭环控制系统调节困难的主要原因是系统中的延迟作用。 比例控制的比例系数如果太小即调节后的电位器转角与位置 LLL 的差值太小调节的力度不够使系统输出量变化缓慢调节所需的总时间过长。比例系数如果过大即调节后电位器转角与位置 LLL 的差值过大调节力度太强将造成调节过头甚至使温度忽高忽低来回震荡。 增大比例系数使系统反应灵敏调节速度加快并且可以减小稳态误差。但是比例系数过大会使超调量增大振荡次数增加调节时间加长动态性能变坏比例系数太大甚至会使闭环系统不稳定。 单纯的比例控制很难保证调节得恰到好处完全消除误差。 积分控制 PID 控制器中的积分对应于图 1 中误差曲线与坐标轴包围的面积图中的灰色部分。PID 控制程序是周期性执行的执行的周期称为采样周期。计算机的程序用图 1 中各矩形面积之和来近似精确的积分图中的 TST_STS​ 就是采样周期。 图 1 积分运算示意图 每次 PID 运算时在原来的积分值的基础上增加一个与当前的误差值 ev(n)ev(n)ev(n) 成正比的微小部分。误差为负值时积分的增量为负。 手动调节温度时积分控制相当于根据当时的误差值周期性地微调电位器的角度每次调节的角度增量值与当时的误差值成正比。温度低于设定值时误差为正积分项增大使加热电流逐渐增大反之积分项减小。因此只要误差不为零控制器的输出就会因为积分作用而不断变化。积分调节的“大方向”是正确的积分项有减小误差的作用。一直要到系统处于稳定状态这时误差恒为零比例部分和微分部分均为零积分部分才不再变化并且刚好等于稳态时需要的控制器的输出值对应于上述温度控制系统中电位器转角的位置 LLL。因此积分部分的作用是消除稳态误差提高控制精度积分作用一般是必须的。 PID 控制器输出中的积分部分与误差的积分成正比。因为积分时间 TIT_ITI​ 在积分项的分母中TIT_ITI​ 越小积分项变化的速度越快积分作用越强。 PI 控制 控制器输出中的积分项与当前的误差值和过去历次误差值的累加值成正比因此积分作用本身具有严重的滞后特性对系统的稳定性不利。如果积分项的系数设置得不好其负面作用很难通过积分作用本身迅速地修正。而比例项没有延迟只要误差一出现比例部分就会立即起作用。因此积分作用很少单独使用它一般与比例和微分联合使用组成 PI 或 PID 控制器。 PI 和 PID 控制器既克服了单纯的比例调节有稳态误差的缺点又避免了单纯的积分调节响应慢、动态性能不好的缺点因此被广泛使用。 如果控制器有积分作用例如采用 PI 或 PID 控制积分能消除阶跃输入的稳态误差这时可以将比例系数调得小一些。 如果积分作用太强即积分时间太小相当于每次微调电位器的角度值过大其累积的作用会使系统输出的动态性能变差超调量增大甚至使系统不稳定。积分作用太弱即积分时间太大则消除稳态误差的速度太慢积分时间的值应取得适中。 微分作用 误差的微分就是误差的变化速率误差变化越快其微分绝对值越大。误差增大时其微分为正误差减小时其微分为负。控制器输出量的微分部分与误差的微分成正比反映了被控量变化的趋势。 有经验的操作人员在温度上升过快但是尚未达到设定值时根据温度变化的趋势预感到温度将会超过设定值出现超调。于是调节电位器的转角提前减小加热的电流。这相当于士兵射击远方的移动目标时考虑到子弹运动的时间需要一定的提前量一样。 图 2 阶跃响应曲线 图 2 中的 c(∞)c(\infty)c(∞) 为被控量 c(t)c(t)c(t) 的稳态值或被控量的期望值误差 e(t)c(∞)−c(t)e(t) c(\infty) - c(t)e(t)c(∞)−c(t)。在图 2 中启动过程的上升阶段当 c(t)c(∞)c(t) c(\infty)c(t)c(∞) 时被控量尚未超过其稳态值。但是因为误差 e(t)e(t)e(t) 不断减小误差的微分和控制器输出的微分部分为负值减小了控制器的输出量相当于提前给出了制动作用以阻碍被控量的上升所以可以减少超调量。因此微分控制具有超前和预测的特性在超调尚未出现之前就能提前给出控制作用。 闭环控制系统的振荡甚至不稳定的根本原因在于有较大的滞后因素。因为微分项能预测误差变化的趋势这种“超前”的作用可以抵消滞后因素的影响。适当的微分控制作用可以使超调量减小增加系统的稳定性。 对于有较大的滞后特性的被控对象如果 PI 控制的效果不理想可以考虑增加微分控制以改善系统在调节过程中的动态特性。如果将微分时间设置为 0微分部分将不起作用。 微分时间与微分作用的强弱成正比微分时间越大微分作用越强。如果微分时间太大在误差快速变化时响应曲线上可能会出现“毛刺”。 微分控制的缺点是对干扰噪声敏感使系统抑制干扰的能力降低。为此可在微分部分增加惯性滤波环节。 采样周期 PID 控制程序是周期性执行的执行的周期称为采样周期。采样周期越小采样值越能反映模拟量的变化情况。但是太小会增加 CPU 的运算工作量相邻两次采样的差值几乎没有什么变化将使 PID 控制器输出的微分部分接近为零所以也不宜将采样周期取得过小。 应保证在被控量迅速变化时例如启动过程中的上升阶段能有足够多的采样点数不致因为采样点数过少而丢失被采集的模拟量中的重要信息。 PID 参数的调整方法 在整定 PID 控制器参数时可以根据控制器的参数与系统动态性能和稳态性能之间的定性关系用实验的方法来调节控制器的参数。有经验的调试人员一般可以较快地得到较为满意的调试结果。在调试中最重要的问题是在系统性能不能令人满意时知道应该调节哪一个参数该参数应该增大还是减小。 为了减少需要整定的参数首先可以采用 PI 控制器。为了保证系统的安全在调试开始时应设置比较保守的参数例如比例系数不要太大积分时间不要太小以避免出现系统不稳定或超调量过大的异常情况。给出一个阶跃给定信号根据被控量的输出波形可以获得系统性能的信息例如超调量和调节时间。应根据 PID 参数与系统性能的关系反复调节 PID 的参数。 如果阶跃响应的超调量太大经过多次振荡才能稳定或者根本不稳定应减小比例系数、增大积分时间。如果阶跃响应没有超调量但是被控量上升过于缓慢过渡过程时间太长应按相反的方向调整参数。 如果消除误差的速度较慢可以适当减小积分时间增强积分作用。 反复调节比例系数和积分时间如果超调量仍然较大可以加入微分控制微分时间从 0 逐渐增大反复调节控制器的比例、积分和微分部分的参数。 总之PID 参数的调试是一个综合的、各参数互相影响的过程实际调试过程中的多次尝试是非常重要的也是必须的。 三、STM32 代码控制说明 1. 硬件电路搭建 本实验使用司南电气控制 STM32 单片机开发板来实现使用 PID 算法对直流电机的速度控制。 实验材料 司南电气控制 STM32 单片机开发板带编码器的直流电机OLED 显示屏 接线如图 2. 软件实现 PID 算法速度控制代码如下 PID pid; //存放 PID 算法所需要的数据 void PID_Calc() //pid 计算 {u8 strr[2],str[2]; //检测脉冲变量float DelEk;float ti,ki; // float Iout; // float Pout; // float Dout;float td;float kd;float out;extern float pulse; //检测脉冲变量if(pid.C200ms (pid.T)) //计算时间未到200ms{return ;}LED4!LED4;//为什么是 2.84/*因为电机转一圈黄色线向单片机输出 105.6 个脉冲pulse/105.6 200ms 的圈数(pulse/105.6)*5 1s 的圈数(pulse/105.6)*5*60 1min 的圈数 pulse*2.48 1min 的圈数*/pid.Pv pulse * 2.84; //速度采样pid.Ek pid.Sv - pid.Pv; //得到当前的偏差值pid.Pout pid.Kp * pid.Ek; //比例输出pid.SEk pid.Ek; //历史偏差总和DelEk pid.Ek - pid.Ek_1; //最近两次偏差之差ti pid.T / pid.Ti;ki ti * pid.Kp;pid.Iout ki * pid.SEk * pid.Kp; //积分输出td pid.Td / pid.T;kd pid.Kp * td;pid.Dout kd * DelEk; //微分输出out pid.Pout pid.Iout pid.Dout;//////////////////////////////////////////////////////////if(out pid.pwmcycle){pid.OUT pid.pwmcycle;}else if(out 0){pid.OUT pid.OUT0; }else {pid.OUT out;}//pid.OUT ; //本次的计算结果pid.Ek_1 pid.Ek; //更新偏差TIM2-CCR1 (u16)pid.OUT; TIM2-CCR4 0; //PWM 输出TIM2-CCR2 0; TIM2-CCR3 (u16)pid.OUT; //PWM 输出sprintf(strr,%0.1f\0,pid.OUT); //打印 PWM 输出值OLED_ShowString(48,3,strr); //打印 PWM 输出值sprintf(str,%0.1f\0,pid.Pv); //打印当前速度输出值OLED_ShowString(56,6,str); //打印当前速度输出值pid.C200ms 0;pulse 0; //检测脉冲变量清零 }基于 STM32 的减速直流电机 PID 算法控制 混分巨兽龙某某 已于 2022-06-11 21:52:53 修改 本例程采用 HAL 库进行项目开发主要使用软件 CubeMX 和 Keil5文章末尾附有代码开源欢迎各位对文章进行指正和探讨。 一、硬件模块与原理图 1. 硬件组成 硬件组成包括STM32F103C8T6 最小系统板0.96 英寸 LED12864I2C 通讯模式智能小车 12V 移动电源25GA370 直流减速电机带霍尔编码器JDY-31 蓝牙模块L298N 电机驱动模块若干杜邦线1 个面包板。 图片如下 2. 模块分析 1. L298N 电机驱动模块 该模块可驱动两路直流电机输出 A 和 B 各接一直流电机即可。若使用 12V 供电将 12V 供电端口及 GND 接上电源正负即可同时 5V 供电端可作为最小系统板的输入电源。若不需要使用 PWM 调速仅需控制电机正反转则逻辑 A 与 B 跳线帽插上即可相当于始终使能。若需要使用 PWM 调速需将跳线帽拔起将使能端接上单片机 IO 口定时器 IO 口PWM 输出模式。逻辑输入四个端口 IN1、IN2、IN3、IN4 接单片机四个 IO 口每两个端口控制一路电机。 温馨提示 特别不建议新手或资金有限的情况下使用电机驱动模块直连成品开发板很容易烧坏。 原因如下 由于电机的特性在堵转或高负载下电流会增大可能影响单片机。新手操作单片机可能出现短路等情况容易导致开发板损坏。 L298N 的转动逻辑图 2. 0.96 英寸 OLEDI2C 通讯 目前市面上主要分为 OLED 与 LCD 两种屏幕。OLED 具有自发光特性而 LCD 需要背光OLED 的显示效果更好。OLED 支持多种接口方式6800、8080 两种并行接口方式4 线的 SPI 接口以及 IIC 接口2 线。OLED 的工作电压不宜过高3.3V 即可正常工作。OLED 的不足之处在于做大尺寸屏幕时成本较高。 本实验采用 0.96 英寸 OLED 屏幕通讯方式为 IIC4 个接线柱SCL、SDA、GND、VCC。 IIC 通讯实现方式 IICInter-Integrated Circuit总线是一种由 PHILIPS 公司开发的两线式串行总线用于连接微控制器及其外围设备。它由数据线 SDA 和时钟线 SCL 构成可发送和接收数据。高速 IIC 总线一般可达 400kbps 以上。 模拟 IIC 通讯 I2C 支持多从机即一个 I2C 控制器下可以挂载多个 I2C 从设备。这些不同的 I2C 从设备具有不同的器件地址I2C 主控制器可以通过 I2C 设备的器件地址访问指定的 I2C 设备。SDA 和 SCL 这两根线必须接一个上拉电阻一般为 4.7kΩ。其余的 I2C 从器件都挂接到 SDA 和 SCL 这两根线上通过 SDA 和 SCL 这两根线访问多个 I2C 设备。 I2C 协议 包括起始位、停止位、数据传输、应答信号、I2C 写时序、I2C 读时序。 I2C 写时序 发送起始信号。发送 I2C 设备地址每个 I2C 器件都有一个设备地址通过发送具体的设备地址决定访问哪个 I2C 器件。这是一个 8 位的数据其中高 7 位是设备地址最后 1 位是读写位。若为 1则表示这是一个读操作若为 0则表示这是一个写操作。I2C 器件地址后面跟着一个读写位为 0 表示写操作为 1 表示读操作。从机发送的 ACK 应答信号。重新发送起始信号。发送要写入数据的寄存器地址。从机发送的 ACK 应答信号。发送要写入寄存器的数据。从机发送的 ACK 应答信号。发送停止信号。 I2C 读时序 I2C 单字节读时序比写时序复杂分为四大步第一步是发送设备地址第二步是发送要读取的寄存器地址第三步重新发送设备地址最后一步是从 I2C 从器件中读取寄存器值。具体步骤如下 主机发送起始信号。主机发送要读取的 I2C 从设备地址。读写控制位由于是向 I2C 从设备发送数据因此是写信号。从机发送的 ACK 应答信号。重新发送 START 信号。主机发送要读取的寄存器地址。从机发送的 ACK 应答信号。重新发送 START 信号。重新发送要读取的 I2C 从设备地址。读写控制位这里是读信号表示接下来是从 I2C 从设备中读取数据。从机发送的 ACK 应答信号。从 I2C 器件中读取到的数据。主机发出 NO ACK 信号表示读取完成不需要从机再发送 ACK 信号。主机发出 STOP 信号停止 I2C 通信。 3. JDY-31 蓝牙模块 市场上蓝牙模块种类繁多常见的有 JDY-xx 和 HC-xx 系列。这些看似高级的蓝牙功能背后实际上是简单的串口通讯。 USART 的全称是 Universal Synchronous/Asynchronous Receiver/Transmitter也就是同步/异步串行收发器。相比 UART它多了一条时钟线具备同步功能。一般 USART 可以作为 UART 使用即不使用其同步功能。 串口通讯协议 数据包由发送设备通过自身的 TXD 接口传输到接收设备的 RXD 接口。在协议层中数据包的内容包括起始位、主体数据8 位或 9 位、校验位以及停止位。通讯双方必须将数据包的格式约定一致才能正常收发数据。 具体如图所示 波特率 由于异步通信中没有时钟信号接收双方需约定好波特率即每秒传输的码元个数以便对信号进行解码。常见波特率有 4800、9600、115200 等。STM32 中波特率的设置通过串口初始化结构体实现。 注意MCU 设置的波特率大小要与蓝牙 APP 设置的大小一致 6 线减速电机带编码器模块 市面上电机种类繁多常用的有步进电机、直流减速电机、伺服电机等。编码器是一种用于测量电机转速的仪器元件常见的有霍尔编码器、光电编码器等。电机的驱动原理很简单给电压差即可使电机转动调速则利用 PWM 调节实现。 编码器原理 编码器是一种将角位移或角速度转换成一串电数字脉冲的旋转式传感器。霍尔编码器由霍尔码盘和霍尔元件组成。霍尔码盘在一定直径的圆板上等分布置不同磁极。霍尔码盘与电动机同轴电动机旋转时霍尔元件检测输出若干脉冲信号。为判断转向一般输出两组存在一定相位差的方波信号。 注意通过判断 A 与 B 相哪一位在前即可判断正转还是反转。 二、CubexMX 设置 使用的 MCU 为 STM32F103C8T6 RCC SYS 注意Debug 一定要设置为 Serial Wire否则可能出现芯片自锁。 GPIO 设置 定时 TIM2 用于测速与测量正转反转计数器模式 定时 3PWM 调节 I2C USART1 之后按照个人习惯生成初始化文件。 三、代码 自动生成的 需要自己编写的 I2C 代码 #include oled.h #include asc.h #include main.hvoid WriteCmd(unsigned char I2C_Command) {HAL_I2C_Mem_Write(hi2c2, OLED0561_ADD, COM, I2C_MEMADD_SIZE_8BIT, I2C_Command, 1, 100); }void WriteDat(unsigned char I2C_Data) {HAL_I2C_Mem_Write(hi2c2, OLED0561_ADD, DAT, I2C_MEMADD_SIZE_8BIT, I2C_Data, 1, 100); }void OLED_Init(void) {HAL_Delay(100);WriteCmd(0xAE); // display offWriteCmd(0x20); // Set Memory Addressing ModeWriteCmd(0x10); // 00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,InvalidWriteCmd(0xb0); // Set Page Start Address for Page Addressing Mode,0-7WriteCmd(0xc8); // Set COM Output Scan DirectionWriteCmd(0x00); // ---set low column addressWriteCmd(0x10); // ---set high column addressWriteCmd(0x40); // --set start line addressWriteCmd(0x81); // --set contrast control registerWriteCmd(0xff); // 0x00~0xffWriteCmd(0xa1); // --set segment re-map 0 to 127WriteCmd(0xa6); // --set normal displayWriteCmd(0xa8); // --set multiplex ratio(1 to 64)WriteCmd(0x3F);WriteCmd(0xa4); // 0xa4,Output follows RAM content;0xa5,Output ignores RAM contentWriteCmd(0xd3); // -set display offsetWriteCmd(0x00); // -not offsetWriteCmd(0xd5); // --set display clock divide ratio/oscillator frequencyWriteCmd(0xf0); // --set divide ratioWriteCmd(0xd9); // --set pre-charge periodWriteCmd(0x22);WriteCmd(0xda); // --set com pins hardware configurationWriteCmd(0x12);WriteCmd(0xdb); // --set vcomhWriteCmd(0x20); // 0x20,0.77xVccWriteCmd(0x8d); // --set DC-DC enableWriteCmd(0x14);WriteCmd(0xaf); // --turn on oled panel }void OLED_SetPos(unsigned char x, unsigned char y) {WriteCmd(0xb0 y);WriteCmd(((x 0xf0) 4) | 0x10);WriteCmd((x 0x0f) | 0x01); }void OLED_Fill(unsigned char fill_Data) {unsigned char m, n;for (m 0; m 8; m){WriteCmd(0xb0 m); // page0-page1WriteCmd(0x00); // low column start addressWriteCmd(0x10); // high column start addressfor (n 0; n 128; n){WriteDat(fill_Data);}} }void OLED_CLS(void) {OLED_Fill(0x00); }void OLED_ON(void) {WriteCmd(0X8D); // ???WriteCmd(0X14); // ???WriteCmd(0XAF); // OLED??? }void OLED_OFF(void) {WriteCmd(0X8D); // ???WriteCmd(0X10); // ???WriteCmd(0XAE); // OLED??? }// Parameters : x,y -- ???(x:0~127, y:0~7); ch[] -- ???; TextSize -- ???(1:6*8 ; 2:8*16) // Description : ??codetab.h??ASCII??,?6*8?8*16??? void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize) {unsigned char c 0, i 0, j 0;switch (TextSize){case 1:{while (ch[j] ! \0){c ch[j] - 32;if (x 126){x 0;y;}OLED_SetPos(x, y);for (i 0; i 6; i)WriteDat(F6x8[c][i]);x 6;j;}}break;case 2:{while (ch[j] ! \0){c ch[j] - 32;if (x 120){x 0;y;}OLED_SetPos(x, y);for (i 0; i 8; i)WriteDat(F8X16[c * 16 i]);OLED_SetPos(x, y 1);for (i 0; i 8; i)WriteDat(F8X16[c * 16 i 8]);x 8;j;}}break;} }// Parameters : x,y -- ???(x:0~127, y:0~7); N:???.h???? // Description : ??ASCII_8x16.h????,16*16??? void OLED_ShowCN(unsigned char x, unsigned char y, unsigned char N) {unsigned char wm 0;unsigned int adder 32 * N;OLED_SetPos(x, y);for (wm 0; wm 16; wm){WriteDat(F16x16[adder]);adder 1;}OLED_SetPos(x, y 1);for (wm 0; wm 16; wm){WriteDat(F16x16[adder]);adder 1;} }// ????????????????,????????“??——???——????”??????ascll.h?????(????) // ??????:x:????? // ??????y:???(??0-7) // ??????begin:????????????????ascll.c???????? // ??????num:???????? // ??????“??”,??????????????????0,1,???0,??????,??:x:0,y:2,begin:0,num:2 void OLED_ShowCN_STR(u8 x, u8 y, u8 begin, u8 num) {u8 i;for (i 0; i num; i){OLED_ShowCN(i * 16 x, y, i begin);} // OLED???? }// Parameters : x0,y0 -- ???(x0:0~127, y0:0~7); x1,y1 -- ???(???)???(x1:1~128,y1:1~8) // Description : ??BMP??? void OLED_DrawBMP(unsigned char x0, unsigned char y0, unsigned char x1, unsigned char y1, unsigned char BMP[]) {unsigned int j 0;unsigned char x, y;if (y1 % 8 0)y y1 / 8;elsey y1 / 8 1;for (y y0; y y1; y){OLED_SetPos(x0, y);for (x x0; x x1; x){WriteDat(BMP[j]);}} }void OLED_ShowChar(u8 x, u8 y, u8 chr, u8 Char_Size) {unsigned char c 0, i 0;c chr - ; // ???????if (x 128 - 1){x 0;y y 2;}if (Char_Size 16){OLED_SetPos(x, y);for (i 0; i 8; i)WriteDat(F8X16[c * 16 i]);OLED_SetPos(x, y 1);for (i 0; i 8; i)WriteDat(F8X16[c * 16 i 8]);}else{OLED_SetPos(x, y);for (i 0; i 6; i)WriteDat(F6x8[c][i]);} }u32 oled_pow(u8 m, u8 n) {u32 result 1;while (n--)result * m;return result; }// ??2??? // x,y :??? // len :???? // size:???? // mode:?? 0,????;1,???? // num:??(0~4294967295); void OLED_ShowNum(u8 x, u8 y, u32 num, u8 len, u8 size2) {u8 t, temp;u8 enshow 0;for (t 0; t len; t){temp (num / oled_pow(10, len - t - 1)) % 10;if (enshow 0 t (len - 1)){if (temp 0){OLED_ShowChar(x (size2 / 2) * t, y, , size2);continue;}elseenshow 1;}OLED_ShowChar(x (size2 / 2) * t, y, temp 0, size2);} }UART 代码 #include uart.h uint8_t USART1_RX_BUF[USART1_REC_LEN]; // ???,??USART_REC_LEN???. uint16_t USART1_RX_STA 0; // ??????//bit15:??????,bit14~0:?????????? uint8_t USART1_NewData; // ??????????1???????? extern int flag;void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) // ???????? {if (huart huart1){if ((USART1_RX_STA 0x8000) 0) // ??????{if (USART1_NewData 0x5A) // ?????0x5A{USART1_RX_STA | 0x8000; // ??????,?USART2_RX_STA??bit15(15?)?1}else{USART1_RX_BUF[USART1_RX_STA 0X7FFF] USART1_NewData;if (USART1_RX_BUF[1] 0x01){flag 2;}USART1_RX_STA; // ??????1if (USART1_RX_STA (USART1_REC_LEN - 1))USART1_RX_STA 0; // ???????,??????}}HAL_UART_Receive_IT(huart1, (uint8_t *)USART1_NewData, 1);} }常规的编写如上但本人的 MCU 存在问题单片机未能接收到预设的数据。因此本人项目中采用了下方代码 #include uart.h uint8_t USART1_RX_BUF[USART1_REC_LEN]; // ???,??USART_REC_LEN???. uint16_t USART1_RX_STA 0; // ??????//bit15:??????,bit14~0:?????????? uint8_t USART1_NewData; // ??????????1???????? extern int flag;void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) // ???????? {if (huart huart1){USART1_RX_BUF[USART1_RX_STA 0X7FFF] USART1_NewData;USART1_RX_STA; // ??????1if (USART1_RX_STA (USART1_REC_LEN - 1))USART1_RX_STA 0; // ???????,??????if (USART1_RX_BUF[USART1_RX_STA - 4] 0xA0){flag 1;}if (USART1_RX_BUF[USART1_RX_STA - 4] 0x90){flag 2;}if (USART1_RX_BUF[USART1_RX_STA - 4] 0xD0){flag 3;}if (USART1_RX_BUF[USART1_RX_STA - 4] 0x88){flag 4;}if (USART1_RX_BUF[USART1_RX_STA - 4] 0x48){flag 5;}HAL_UART_Receive_IT(huart1, (uint8_t *)USART1_NewData, 1);} }如果大家自己使用可以根据自己的蓝牙 APP 修改这段程序有问题欢迎留言。 Motor 代码 #include motor.hvoid MOTOR_GO() {__HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 3000);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); }void MOTOR_BACK() {HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); }void MOTOR_STOP() {HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); }void MOTOR_UP() {__HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 1);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); }void MOTOR_DOWN() {__HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, 400);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); }PID PID 算法 PID 分为位置型和增量型。增量型通过 u(k)−u(k−1)u(k) - u(k-1)u(k)−u(k−1) 得出公式 公式的第一部分是比例项用于让值按一定比例达到目标值 第二部分是积分项正值在计算过程中往往会受到环境等其他因素的影响导致值不能到达目标值 第三部分是微分项通常是负值后一次偏差值往往小于前一次偏差值目的是防止值增加过大起到阻碍作用。 PID 代码 #include pid.h #include tim.h #include main.h #include math.h #include i2c.h #include oled.hunsigned int MotorSpeed; // 全局变量电机当前转速 int SpeedTarget 750; // 目标转速 int MotorOutput; // 电机输出// 1. 使用 TIM2 计算电机转速 void GetMotorSpeed(void) {int CaptureNumber (short)__HAL_TIM_GET_COUNTER(htim2); // 获取计数值__HAL_TIM_GET_COUNTER(htim2) 0; // 清零计数器int Direction __HAL_TIM_IS_TIM_COUNTING_DOWN(htim2); // 判断计数方向if (Direction 1){CaptureNumber - 65535;}MotorSpeed CaptureNumber;OLED_ShowNum(40, 0, MotorSpeed, 4, 16); // 显示转速HAL_Delay(100);OLED_CLS(); }// 2. 增量式 PID 控制器PID 常见的有位置 PID 和增量 PID int Error_Last, Error_Prev; // 上次误差上上次误差 int Pwm_add, Pwm; // PWM 增量PWM 值 int Kp 5, Ki 3, Kd 1; // PID 参数比例、积分、微分系数int SpeedInnerControl(int Speed, int Target) // 速度内环控制 {int Error Target - Speed; // 误差 目标速度 - 实际速度Pwm_add Kp * (Error - Error_Last) // 比例项Ki * Error // 积分项Kd * (Error - 2.0f * Error_Last Error_Prev); // 微分项Pwm Pwm_add; // 输出 初始值 增量Error_Prev Error_Last; // 更新上上次误差Error_Last Error; // 更新上次误差if (Pwm 4999)Pwm 3000; // 限制 PWM 上下限if (Pwm -4999)Pwm -3000;return Pwm; // 返回输出值 }// 3. 设置电机电压和方向PID 控制 void SetMotorVoltageAndDirection(int Pwm) {if (Pwm 0) // 如果 PWM 小于 0{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);Pwm (-Pwm); // PWM 取绝对值__HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, Pwm); // 设置 PWM 占空比}else{HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);HAL_GPIO_WritePin(GPIO_PIN_5, GPIO_PIN_SET);__HAL_TIM_SET_COMPARE(htim3, TIM_CHANNEL_1, Pwm); // 设置 PWM 占空比} }void ModePID() {GetMotorSpeed();MotorOutput SpeedInnerControl(MotorSpeed, SpeedTarget);SetMotorVoltageAndDirection(MotorOutput); }主函数代码 while (1) {switch (flag){case (1):MOTOR_GO();break;case (2):MOTOR_BACK();break;case (3):MOTOR_STOP();break;case (4):MOTOR_UP();break;case (5):ModePID();break;default:break;}if (flag ! 5){int CaptureNumber (short)__HAL_TIM_GET_COUNTER(htim2); // 获取计数值__HAL_TIM_GET_COUNTER(htim2) 0;int Direction __HAL_TIM_IS_TIM_COUNTING_DOWN(htim2);if (Direction 1){CaptureNumber - 65535;}int Speed CaptureNumber;OLED_ShowNum(40, 0, Speed, 5, 16);HAL_Delay(100);OLED_CLS();}int Direction __HAL_TIM_IS_TIM_COUNTING_DOWN(htim2);OLED_ShowCN_STR(0, 0, 0, 3);OLED_ShowNum(90, 0, Speed, 4, 16);OLED_ShowStr(90, 0, cm/s, 2);OLED_ShowCN_STR(0, 3, 3, 2);if (Direction 0){OLED_ShowCN_STR(40, 3, 5, 2);}if (Direction 1){OLED_ShowCN_STR(40, 3, 7, 2);} }蓝牙 APP 源代码以及技术论文 链接https://pan.baidu.com/s/1-rbicxuyLVCq6rglCWcJTg 提取码huzm 使用 STM32 实现电机的 PID 控制 倩倩草原晖太郎 已于 2024-06-13 19:14:02 修改 PID 控制是一种非常古老且应用广泛的控制算法小到热水壶温度控制大到无人机的飞行姿态和飞行速度控制等。在电机控制中PID 算法尤为常见。本文将由浅入深介绍使用 STM32 实现电机的 PID 控制希望能帮助到有需要的人。 直接上代码仓库链接gitee-基于 STM32 的 PID 电机控制源码 一、电机基本控制 直流电机的内部结构和工作原理在高中的物理教材中已经讲过这里主要讨论如何使用单片机和电机驱动模块驱动一个直流电机。 1.1 器件准备和接线 本文介绍使用的单片机型号是 STM32F103RCT6也可以使用 C8T6。 电机驱动选择 TB6612。 直流电机选择这种带霍尔传感器的编码电机12V减速比 1/30速度 330rpm。 为了方便观察和操作使用了一块 0.96 英寸的 OLED。 接线表 模块引脚单片机引脚OLED_SCLPB8OLED_SDAPB9按键 K1PC9按键 K2PC8TB6612_AIN1PB12TB6612_AIN2PB13编码器 A 相PB6编码器 B 相PB7 1.2 代码展示 TB6612 的驱动非常简单使用两个普通的 GPIO 输出高低电平控制电机正反转再使用一个复用定时器的 IO 生成一个 PWM 控制电机转速即可。 motor.c 部分代码如下 #include motor.h/*** brief 电机方向控制引脚设置* param None* retval None*/ static void motor_gpio_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 使能 PB 端口时钟GPIO_InitStructure.GPIO_Pin GPIO_Pin_12 | GPIO_Pin_13; // 端口配置GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; // 推挽输出GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; // 50MGPIO_Init(GPIOB, GPIO_InitStructure); // 根据设定参数初始化 GPIOB }/*** brief 定时器初始化* param arr自动重装值,设为一个时钟频率的最大值* param psc预分频值* retval None*/ void Motor_PWM_Init(u16 arr, u16 psc) {GPIO_InitTypeDef GPIO_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;motor_gpio_Init();RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); // 使能 TIM1 时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能 GPIOA 时钟// 设置该引脚为复用输出功能, 输出 TIM1 CH1 CH4 的 PWM 脉冲波形GPIO_InitStructure.GPIO_Pin GPIO_Pin_11; // TIM_CH1 // TIM_CH4GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽输出GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOA, GPIO_InitStructure);TIM_TimeBaseStructure.TIM_Period arr; // 设置在下一个更新事件装入活动的自动重装载寄存器周期的值TIM_TimeBaseStructure.TIM_Prescaler psc; // 设置用来作为 TIMx 时钟频率除数的预分频值TIM_TimeBaseStructure.TIM_ClockDivision 0; // 设置时钟分割: TDTS Tck_timTIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; // TIM 向上计数模式TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); // 根据 TIM_TimeBaseInitStruct 中指定的参数初始化 TIMx 的时间基数单位TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; // 选择定时器模式: TIM 脉冲宽度调制模式 1TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; // 比较输出使能TIM_OCInitStructure.TIM_Pulse 0; // 设置待装入捕获比较寄存器的脉冲值TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; // 输出极性: TIM 输出比较极性高TIM_OC4Init(TIM1, TIM_OCInitStructure); // 根据 TIM_OCInitStruct 中指定的参数初始化外设 TIMxTIM_CtrlPWMOutputs(TIM1, ENABLE); // MOE 主输出使能TIM_OC4PreloadConfig(TIM1, TIM_OCPreload_Enable); // CH4 预装载使能TIM_ARRPreloadConfig(TIM1, ENABLE); // 使能 TIMx 在 ARR 上的预装载寄存器TIM_Cmd(TIM1, ENABLE); // 使能 TIM1 }使用一个函数即可输入的是带符号的整型变量正负号代表选择方向绝对值代表占空比。 /*** brief 电机输出控制* param motor_pwm: 占空比 0-7200* retval None*/ void Set_Pwm(int motor_pwm) {if (motor_pwm 0){BIN1 1;BIN2 0; // 前进}else if (motor_pwm 0){BIN1 0;BIN2 1; // 后退}else // 停止{BIN1 0;BIN2 0;}PWMB myabs(motor_pwm);TIM_SetCompare4(TIM1, PWMB); }1.3 效果展示 OLED 的第一行显示运行状态ON 或 OFF第二行显示电机 PWM 数值 表示正转- 表示反转。按下 K1 切换电机旋转方向按下 K2电机启动/关闭。 gitee-基于 STM32 的 PID 电机控制源码 二、电机速度读取 所用的编码器是一个霍尔传感器两个霍尔元件相差 90 度放置。编码电机速度的读取方式从单片机读取方式上分有定时器输入捕获法和外部中断法从编码器原理上又分为二倍频和四倍频从速度计算方式上又分为 M 法测速和 T 法测速。 下面简单了解 M 法测速和 T 法测速。 M 法测速在一个固定的计时周期内统计这段时间内的编码器脉冲数从而计算速度值。 转速 nnn 的计算公式为 nM0C×T0n \frac{M_0}{C \times T_0} nC×T0​M0​​ 其中 CCC编码器单圈总脉冲数T0T_0T0​计数周期单位为秒sM0M_0M0​在计数周期内统计到的编码器脉冲数 假设已知编码器转过一圈需要 100 个脉冲在 100ms 内测得产生了 20 个脉冲。通过公式计算 n20100×0.12n \frac{20}{100 \times 0.1} 2n100×0.120​2圈/秒。 T 法测速这种方法是建立一个已知频率的高频脉冲并对其计数。 转速 nnn 的计算公式为 nF0C×M1n \frac{F_0}{C \times M_1} nC×M1​F0​​ 其中 CCC编码器单圈总脉冲数F0F_0F0​高频脉冲的频率M1M_1M1​一个脉冲转的圈数 T 法是利用一个已知脉冲来测量编码器两个脉冲之间的时间从而计算出速度。 假设编码器转过一圈需要 100 个脉冲C100C 100C100则 1 个脉冲转了 1100\frac{1}{100}1001​ 圈用时为 20ms即 50Hz转速为 0.5 圈/秒。 M 法测速和 T 法测速是解决速度计算层面的问题下面以定时器输入捕获法和外部中断法展开讨论。 2.1 定时器输入捕获法 2.1.1 定时器输入捕获内部结构 首先是两个正交信号从 GPIO 输入到滤波器然后再进行边沿检测和极性选择接到定时器内部的编码器接口。 使用定时器输入捕获法测速其实是利用了 STM32 定时器自带的编码器接口功能 编码器接口可接收增量正交编码器的信号根据编码器旋转产生的正交信号脉冲自动控制 CNT 自增或自减从而指示编码器的位置、旋转方向和旋转速度。 每个高级定时器和通用定时器都拥有 1 个编码器接口。两个输入引脚借用了输入捕获的通道 1 和通道 2。 下面是计数方向与编码信号的关系 2.1.2 代码展示 encoder.c 部分代码如下 /*** brief 编码器初始化, 使用定时器输入捕获法* param None* retval None*/ void Encoder_Init(void) {RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7;GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz;GPIO_Init(GPIOB, GPIO_InitStructure);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;TIM_TimeBaseInitStructure.TIM_ClockDivision TIM_CKD_DIV1;TIM_TimeBaseInitStructure.TIM_CounterMode TIM_CounterMode_Up;TIM_TimeBaseInitStructure.TIM_Period 65536 - 1; // ARRTIM_TimeBaseInitStructure.TIM_Prescaler 1 - 1; // PSCTIM_TimeBaseInitStructure.TIM_RepetitionCounter 0;TIM_TimeBaseInit(TIM4, TIM_TimeBaseInitStructure);TIM_ICInitTypeDef TIM_ICInitStructure;TIM_ICStructInit(TIM_ICInitStructure);TIM_ICInitStructure.TIM_Channel TIM_Channel_1;TIM_ICInitStructure.TIM_ICFilter 0xF;TIM_ICInit(TIM4, TIM_ICInitStructure);TIM_ICInitStructure.TIM_Channel TIM_Channel_2;TIM_ICInitStructure.TIM_ICFilter 0xF;TIM_ICInit(TIM4, TIM_ICInitStructure);/* TI1 和 TI2 都计数上升沿计数 */TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);TIM_Cmd(TIM4, ENABLE); }/*** brief 获取定时器的计数值* param None* retval None*/ int16_t Encoder_Get(void) {int16_t Temp;Temp TIM_GetCounter(TIM4);TIM_SetCounter(TIM4, 0);return Temp; }此外还需要再使用一个定时器定时采集速度 /*** brief 定时器中断每 100ms 更新一次速度* param None* retval None*/ void TIM2_IRQHandler(void) {if (TIM_GetITStatus(TIM2, TIM_IT_Update) SET){Speed Encoder_Get();TIM_ClearITPendingBit(TIM2, TIM_IT_Update);} }2.2 外部中断法 2.2.1 外部中断法简介 使用单片机引脚的跳变信号触发中断然后在中断里判断两个编码器引脚的电平让计数值增加或者减少。我们规定正转计数值 111反转计数值 −1-1−1。 采用 4 倍频计数即 A 相的上升沿、下降沿和 B 相的上升沿、下降沿都触发中断。 A 相边沿B 相电平对应区域计数值上升沿L2Encoder_EXTI下降沿H4Encoder_EXTI B 相边沿A 相电平对应区域计数值上升沿H3Encoder_EXTI下降沿L1Encoder_EXTI 当电机反转时A 相、B 相的信号与正转时的相位相差 90 度。换句话说正转时 A 相先触发上升沿反转时 B 相先触发上升沿。 A 相边沿B 相电平对应区域计数值上升沿H3Encoder_EXTI–下降沿L1Encoder_EXTI– B 相边沿A 相电平对应区域计数值上升沿L2Encoder_EXTI–下降沿H4Encoder_EXTI– 2.2.2 代码展示 部分代码如下 /*** brief 编码器初始化, 使用外部中断法* param None* retval None*/ void Encoder_Init(void) {GPIO_InitTypeDef GPIO_InitStruct;EXTI_InitTypeDef EXTI_InitStruct;NVIC_InitTypeDef NVIC_InitStruct;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitStruct.GPIO_Mode GPIO_Mode_IPD;GPIO_InitStruct.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7;GPIO_Init(GPIOB, GPIO_InitStruct);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);// 设置 IO 口与中断线的映射关系GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource6);GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource7);// 初始化线上中断EXTI_InitStruct.EXTI_Line EXTI_Line6 | EXTI_Line7;EXTI_InitStruct.EXTI_LineCmd ENABLE;EXTI_InitStruct.EXTI_Mode EXTI_Mode_Interrupt;EXTI_InitStruct.EXTI_Trigger EXTI_Trigger_Rising_Falling; // 跳变沿触发EXTI_Init(EXTI_InitStruct);// 配置中断分组NVIC_InitStruct.NVIC_IRQChannel EXTI9_5_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority 1;NVIC_InitStruct.NVIC_IRQChannelSubPriority 0;NVIC_Init(NVIC_InitStruct); }/*** brief 中断服务函数采用 4 倍频测速* param None* retval None*/ int Encoder_EXTI 0; void EXTI9_5_IRQHandler(void) {if (EXTI_GetITStatus(EXTI_Line6) ! RESET) // 右轮 A 相 PB6{EXTI_ClearITPendingBit(EXTI_Line6); // 清除 LINE 上的中断标志位if (PBin(6) 0) // 这里判断检测到的是否是下降沿{if (PBin(7) 0) Encoder_EXTI; // B 相的电平如果是低电机就是正转加 1else Encoder_EXTI--; // 否则就是反转减 1}else // 上升沿{if (PBin(7) 1) Encoder_EXTI; // B 相电平如果为高电机就是正转加 1else Encoder_EXTI--; // 否则就是反转减 1}}if (EXTI_GetITStatus(EXTI_Line7) ! RESET) // 右轮 B 相 PB7{EXTI_ClearITPendingBit(EXTI_Line7); // 清除 LINE 上的中断标志位if (PBin(7) 0) // 这里判断检测到的是否是下降沿{if (PBin(6) 1) Encoder_EXTI; // A 相的电平如果是高电机就是正转加 1else Encoder_EXTI--; // 否则就是反转减 1}else // 上升沿{if (PBin(6) 0) Encoder_EXTI; // A 相电平如果为低电机就是正转加 1else Encoder_EXTI--; // 否则就是反转减 1}} }/*** brief 获取中断的计数值* param None* retval None*/ int16_t Encoder_Get(void) {int16_t Temp;Temp Encoder_EXTI;Encoder_EXTI 0;return Temp; }/*** brief 定时器中断每 100ms 更新一次速度* param None* retval None*/ void TIM2_IRQHandler(void) {if (TIM_GetITStatus(TIM2, TIM_IT_Update) SET){Speed Encoder_Get();TIM_ClearITPendingBit(TIM2, TIM_IT_Update);} }2.2.3 效果展示 关于电机轮子实际转速的计算 首先根据速度的计算公式本质 vstv \frac{s}{t} vts​ 其中 ttt已知的编码器计数周期100mssss轮子在这个计数周期内走过的路程 下面计算 sss 我们知道编码器采集的是单位时间内的脉冲个数而且编码器的码盘安装在电机轴的末端测的是电机输出轴的速度。电机轴还需要经过减速器才能和轮子相连。 因此我们可以先算出单位时间内一个脉冲轮子走过的距离 l2×π×R4×n×il \frac{2 \times \pi \times R}{4 \times n \times i} l4×n×i2×π×R​ 其中 2×π×R2 \times \pi \times R2×π×R 是轮子周长RRR 是轮子半径34mm使用 4 倍频计数nnn 是编码器的码盘转一圈的脉冲数11iii 是电机的减速比30 一个脉冲轮子走过的距离可以直接手算出来那么我们再乘以编码器测量的脉冲个数就可以知道轮子在这个计数周期内走过的路程 sss sm×ls m \times l sm×l 其中 mmm 是编码器测量的脉冲个数。 上述计算过程使用 C 代码表示如下 /*** brief 编码器读数转换为轮子速度mm/s* param encoder编码器计数* retval : Velocity 轮子速度*/ int Get_Velocity_Form_Encoder(int encoder) {float Distance, Velocity;Distance 2 * 3.14159 * 34 / (4 * 11 * 30); // 单位是 mmVelocity encoder * Distance / 0.1; // 单位是 mm/s。0.1 就是编码器计数周期 100ms0.1sreturn Velocity; }三、位置式 PID 3.1 计算公式 在电机控制中我们给电机输出的是一个 PWM 占空比的数值。 位置式 PID 控制的输出公式为 PwmKp⋅e(k)Ki⋅∑j0ke(j)Kd[e(k)−e(k−1)]\text{Pwm} K_p \cdot e(k) K_i \cdot \sum_{j0}^{k} e(j) K_d \left[ e(k) - e(k-1) \right]PwmKp​⋅e(k)Ki​⋅j0∑k​e(j)Kd​[e(k)−e(k−1)] 其中 e(k)e(k)e(k)本次偏差e(k−1)e(k-1)e(k−1)上一次的偏差∑j0ke(j)\sum_{j0}^{k} e(j)∑j0k​e(j)e(k)e(k)e(k) 及之前偏差的累积和其中 kkk 为 1, 2, …Pwm\text{Pwm}Pwm输出KpK_pKp​比例项参数KiK_iKi​积分项参数KdK_dKd​微分项参数 控制流程图如下 上图中的目标位置一般可以通过按键或者开关等方式编程实现改变目标值测量位置就是通过 STM32 去采集编码器的数据。目标位置和测量位置之间做差这个就是目前系统的偏差。送入 PID 控制器进行计算输出然后再经过电机驱动的功率放大控制电机的转动去减小偏差最终达到目标位置的过程。 3.2 C 语言实现 位置式 PID 具体通过 C 语言实现的代码如下 先定义一个 PID 参数的结构体 typedef struct {float target_val; // 目标值float Error; // 第 $k$ 次偏差float LastError; // $\text{Error}[-1]$第 $k-1$ 次偏差float PrevError; // $\text{Error}[-2]$第 $k-2$ 次偏差float Kp, Ki, Kd; // 比例、积分、微分系数float integral; // 积分值float output_val; // 输出值 } PID;然后定义一个 PID 参数初始化的函数 /*** brief PID 参数初始化* note 无* retval 无*/ void PID_param_init(void) {PosionPID.target_val 3600;PosionPID.output_val 0.0;PosionPID.Error 0.0;PosionPID.LastError 0.0;PosionPID.integral 0.0;PosionPID.Kp 10;PosionPID.Ki 0.5;PosionPID.Kd 0.8; }最后根据公式写出位置式 PID 的实现函数 /*** brief 位置 PID 算法实现* param actual_val: 实际测量值* note 无* retval 通过 PID 计算后的输出*/ float PosionPID_realize(PID *pid, float actual_val) {/* 计算目标值与实际值的误差 */pid-Error pid-target_val - actual_val;/* 积分项 */pid-integral pid-Error;/* PID 算法实现 */pid-output_val pid-Kp * pid-Error pid-Ki * pid-integral pid-Kd * (pid-Error - pid-LastError);/* 误差传递 */pid-LastError pid-Error;/* 返回当前实际值 */return pid-output_val; }函数入口参数为编码器的速度测量值和 PID 参数的结构体返回值为电机控制 PWM。 四、增量式 PID 4.1 计算公式 增量式 PID 也称速度环 PID速度闭环控制就是根据单位时间获取的脉冲数这里使用了 M 法测速测量电机的速度信息并与目标值进行比较得到控制偏差然后通过对偏差的比例、积分、微分进行控制使偏差趋向于零的过程。 增量式 PID 控制的输出公式为 PwmKp[e(k)−e(k−1)]Ki⋅e(k)Kd[e(k)−2e(k−1)e(k−2)]\text{Pwm} K_p \left[ e(k) - e(k-1) \right] K_i \cdot e(k) K_d \left[ e(k) - 2e(k-1) e(k-2) \right]PwmKp​[e(k)−e(k−1)]Ki​⋅e(k)Kd​[e(k)−2e(k−1)e(k−2)] 其中 e(k)e(k)e(k)本次偏差e(k−1)e(k-1)e(k−1)上一次的偏差e(k−2)e(k-2)e(k−2)上上次的偏差KpK_pKp​比例项参数KiK_iKi​积分项参数KdK_dKd​微分项参数Pwm\text{Pwm}Pwm代表增量输出 在速度控制闭环系统中只使用 PI 控制因此对 PID 控制器可简化为以下公式 PwmKp[e(k)−e(k−1)]Ki⋅e(k)Pwm K_p [e(k) - e(k-1)] K_i \cdot e(k)PwmKp​[e(k)−e(k−1)]Ki​⋅e(k) 控制框图和位置式的一样。 上图中的目标速度一般可以通过按键或者开关等方式编程实现改变目标值测量速度前面在编码器的章节已经有提到即通过单片机定时去采集编码器的数据并清零。目标速度和测量速度之间做差这个就是目前系统的偏差。送入 PID 控制器进行计算输出然后再经过电机驱动的功率放大控制电机的转动去减小偏差最终达到目标速度的过程。 4.2 C 语言实现 增量式 PID 的结构体定义和成员初始化与位置式相同通过 C 语言实现的代码如下 /*** brief 速度 PID 算法实现* param actual_val: 实际值* note 无* retval 通过 PID 计算后的输出*/ float addPID_realize(PID *pid, float actual_val) {/* 计算目标值与实际值的误差 */pid-Error pid-target_val - actual_val;/* PID 算法实现照搬公式 */pid-output_val pid-Kp * (pid-Error - pid-LastError) pid-Ki * pid-Error pid-Kd * (pid-Error - 2 * pid-LastError pid-PrevError);/* 误差传递 */pid-PrevError pid-LastError;pid-LastError pid-Error;/* 返回当前实际值 */return pid-output_val; }函数入口参数为编码器的速度测量值和 PID 参数的结构体返回值为电机控制 PWM。可以看出增量式 PID 只与最近三次的测量值有关。 五、串级 PID 串级 PID 就是先输入位置 PID再经过速度 PID最后输出。 六、P、I、D 各个参数的作用 自动控制系统的性能指标主要有三个方面稳定性、快速性和准确性。 稳定性系统在受到外作用后若控制系统使其被控变量随时间的增长而最终与给定期望值一致则称系统是稳定的我们一般称为系统收敛。 如果被控量随时间的增长越来越偏离给定值则称系统是不稳定的我们一般称为系统发散。 稳定的系统才能完成自动控制的任务所以系统稳定是保证控制系统正常工作的必要条件。一个稳定的控制系统其被控量偏离给定值的初始偏差应随时间的增长逐渐减小并趋于零。 快速性快速性是指系统的动态过程进行的时间长短。过程时间越短说明系统快速性越好过程时间持续越长说明系统响应迟钝难以实现快速变化的指令信号。稳定性和快速性反映了系统在控制过程中的性能。系统在跟踪过程中被控量偏离给定值越小偏离的时间越短说明系统的动态精度偏高。 准确性是指系统在动态过程结束后其被控变量或反馈量对给定值的偏差而言这一偏差即为稳态误差它是衡量系统稳态精度的指标反映了动态过程后期的性能。 在实践生产工程中不同的控制系统对控制器效果的要求不一样。比如平衡车、倒立摆对系统的快速性要求很高响应太慢会导致系统失控。智能家居里面的门窗自动开合系统对快速性要求就不高但是对稳定性和准确性的要求就很高所以需要严格控制系统的超调量和静差。 总结 本文主要介绍了在电机的 PID 控制中常用的位置式 PID 和增量式 PID。 上述基于 STM32 的 PID 电机控制源码库链接 stm32_Motor_PID: 个人学习 PID 时做的基于 stm32 的 PID 电机控制源码本实验包含 6 个电机控制实验电机的 PWM 控制编码器测速_定时器捕获法和外部中断法位置式 PID增量式 PID, 串级 PID代码简洁易懂十分适合 PID 入门。 - Gitee.com https://gitee.com/tang176084/stm32_Motor_PID/tree/master via STM32—PID 控制在直流电机中的应用_分析 pid 控制参数对直流电机运行的影响 - CSDN 博客 https://blog.csdn.net/qq_43743762/article/details/105827410 stm32 直流电机控制 —PID 算法篇_直流电机速度控制的 pid 算法 - CSDN 博客 https://blog.csdn.net/weixin_43281206/article/details/108916349 基于 stm32 的减速直流电机 PID 算法控制_直流减速电机的控制 - CSDN 博客 https://blog.csdn.net/black_sneak/article/details/125237524 使用 stm32 实现电机的 PID 控制_stm32pid 控制电机 - CSDN 博客 https://blog.csdn.net/weixin_43811044/article/details/127956227
http://www.pierceye.com/news/460819/

相关文章:

  • 微网站建设哪家优惠h5小游戏在线玩
  • 娄底高端网站建设网站建设费计入 科目
  • 免费企业网站程序上传wordpress 卸载
  • 大庆市建设局网站上不去linux删除WordPress
  • 宣城市建设监督管理局网站下载怎么上wordpress
  • 福州做网站fjfzwl编写软件开发文档
  • 平台设计网站公司电话号码建站哪家好用兴田德润
  • 宝安网站建设信科免费网站开发 自动填写表单
  • 网站怎么更新文章动漫网站在线免费观看
  • 织梦 网站迁移网页制作三剑客通常指
  • 南京本地网站建站武安百度seo
  • 特定ip段访问网站代码西安免费建网站设计
  • 个人网站备案取消wordpress可以做大吗
  • 如何做网站管理网站服务器基本配置
  • 做网站需要参考书目书龙岩营销型网站建设
  • 南通网站建设解决方案求助如何做网站推广
  • 揭阳企业做网站淮安做网站
  • 怎么给餐饮店做网站用织梦做企业网站
  • 技术支持 创思佳网站建设如何制作自己的网站
  • 济南网站建设公司晟创未来wordpress xml插件
  • 前端做商城网站需要多久实训课网站开发个人小结
  • 南宁网站seo排名优化手机网站制作架构
  • 亿唐网不做网站做品牌案例分析seo 推广服务
  • 深圳网站建设服务器如何编写一份网站开发需求文档
  • 营销网站策划wordpress主题在线汉化插件下载
  • 深圳市网站开发个人养老保险金怎么交
  • 超炫html5网站模板新手做网站怎么上传系统
  • 一个网站的二级目录在另一台服务器上_怎么做网络营销工程师前景
  • 个人网站推广软件企业网站建设变相收取等级保护费6
  • 有没有一些网站可以做问卷个人网站 服务器