【问题标题】:Issue with servo motor and potentiometer: not moving when program is inserted into board伺服电机和电位器问题:程序插入板时不移动
【发布时间】:2020-09-07 14:56:03
【问题描述】:

此代码旨在利用电位计来转动伺服电机。当我试图将它插入程序时,伺服器根本没有移动,我不知道这是我的电路板、我的接线还是我的代码的结果。如果有人可以帮助或提供一些帮助,将不胜感激。我使用的开发板是 Nucleo STM L476RG 开发板,电机是微型 SG90。

#include "mbed.h"
#include "Servo.h"
#include "iostream"

Servo myservo(D6);
AnalogOut MyPot(A1);


int main() {
    float PotReading;
    PotReading = MyPot.read();

    while(1) {
        for(int i=0; i<100; i++) {
            myservo.SetPosition(PotReading);
            wait(0.01);
        }
    }
}

另外,我使用的代码在已发布的库伺服中将此代码列为 Servo.h

#ifndef MBED_SERVO_H
#define MBED_SERVO_H

#include "mbed.h"

/** Class to control a servo on any pin, without using pwm
 *
 * Example:
 * @code
 * // Keep sweeping servo from left to right
 * #include "mbed.h"
 * #include "Servo.h"
 * 
 * Servo Servo1(p20);
 *
 * Servo1.Enable(1500,20000);
 *
 * while(1) {
 *     for (int pos = 1000; pos < 2000; pos += 25) {
 *         Servo1.SetPosition(pos);  
 *         wait_ms(20);
 *     }
 *     for (int pos = 2000; pos > 1000; pos -= 25) {
 *         Servo1.SetPosition(pos); 
 *         wait_ms(20); 
 *     }
 * }
 * @endcode
 */

class Servo {

public:
    /** Create a new Servo object on any mbed pin
     *
     * @param Pin Pin on mbed to connect servo to
     */
    Servo(PinName Pin);

    /** Change the position of the servo. Position in us
     *
     * @param NewPos The new value of the servos position (us)
     */
    void SetPosition(int NewPos);

    /** Enable the servo. Without enabling the servo won't be running. Startposition and period both in us.
     *
     * @param StartPos The position of the servo to start (us) 
     * @param Period The time between every pulse. 20000 us = 50 Hz(standard) (us)
     */
    void Enable(int StartPos, int Period);

    /** Disable the servo. After disabling the servo won't get any signal anymore
     *
     */
    void Disable();

private:
    void StartPulse();
    void EndPulse();

    int Position;
    DigitalOut ServoPin;
    Ticker Pulse;
    Timeout PulseStop;
};

#endif

它还有一个 .cpp 文件与它在同一个地方,所以如果有人需要它作为参考,我会将它作为编辑发布。我也会把接线以防万一

伺服是SG90

板子的接线:

【问题讨论】:

  • 你的电位器应该是AnalogIn吗?您可能还想在每次循环迭代时读取底池输入。现在你只是在程序开始时阅读它一次,再也不会。你在启动程序后立即把它变成一个无用的旋钮。
  • @JohnFilleau 刚刚改了,测试了一下,结果和之前一样
  • 第四个问题:Servo::SetPosition 期望在0 和(可能)20000 之间有一个int(取决于您特定伺服的规格表)。 AnalogIn::read01 之间返回一个float。您需要将该浮点数转换为 int。
  • @GB "刚刚改了并测试了" 改成 what?测试它如何?这里有几个错误,都需要解决。
  • 现在你有几个移动的部分。你有一个产生电压的电位器。这是由您的 ADC 读取的。然后由您的 ADC 驱动程序读取。然后将其写入您的伺服器。你有万用表吗?从头开始。确保扭转电位器实际上会导致电压偏离。如果是,则转到下一个。这些芯片是否有一些调试接口可以让您单步执行代码?

标签: c++ embedded stm32 mbed


【解决方案1】:

首先要了解的是伺服电机的工作原理。根据数据表,它需要一个脉冲宽度为 1 到 2ms 的 50Hz PWM。脉冲宽度决定位置,因此 1ms 的宽度将伺服定位在其行程的一端,2ms 的脉冲将设置在另一端的位置,1.5ms 将设置中心位置。

其次,您需要阅读您正在使用的 Servo 类的文档(在 cmets 中)。它甚至有示例代码。首先您需要实例化一个Servo 对象(您已经完成),然后您需要通过设置其脉冲间隔和初始脉冲宽度(或位置)来启用它:

Servo output( D6 ) ;
output.Enable( 1500, 20000 ) ; // Centre position, 50Hz

那么如果你想读取模拟输入,显然你需要一个AnalogIn 对象:

AnalogOut input(A1);

那么您需要了解闭环控制系统必须不断读取其输入以调整输出。在这里,您只需在控制回路之前读取一次电位计,因此在回路中它永远不会改变值,因此位置不会改变。此外,您还有一个完全不必要的内部循环,它来自完全不同的Servo 实现here 的示例代码-该示例不是闭环控制系统-它只是不断地来回循环伺服它的全部范围 - 在这种情况下,这不是您想要实现的目标,它只是 cargo cult programming

闭环控制中连续

         _______
        |       |
        V       |
    get-input   | <repeat>
    set-output  |
        |_______|

最后,您需要了解,在这种情况下,输入测量的单位与输出设置的单位不同 - 它们需要缩放,以便满量程输入范围映射到满量程输出范围。 mbed AnalogIn 类有两个读取函数; AnalogIn::read() 返回一个在 0.0 到 1.0 范围内的 floatAnalogIn::read_u16 返回一个 uint16_t 值 0 到 65535。我个人会使用整数版本,但 STM32F4 部件具有单精度 FPU,因此缺少在这种情况下,硬件浮点数不是问题 - 尽管还有其他原因可以避免使用浮点数。然后Servo::setPosition() 函数采用脉冲宽度方面的位置参数,如上所述,这与给定建议初始化的位置标度 0 到 20000 相关。所以你需要:

float set_point = input.read() ;
output.SetPosition( (int)( (set_point * 1000) + 1000 ) ; // Scale to 1 to 2ms

uint16_t set_point = input.read_u16() ;
output.SetPosition( ((set_point * 1000) >> 16) + 1000 ) ;

将所有这些放在一起(以及其他一些改进):

#include "mbed.h"
#include "Servo.h"

int main() 
{
    // Hardware parameters
    static const int SERVO_FREQ = 50 ;                        // Hz
    static const int SERVO_PERIOD = 1000000 / SERVO_FREQ ;    // microseconds
    static const int SERVO_MIN = 1000 ;                       // 1ms in microseconds
    static const int SERVO_MAX = 2000 ;                       // 2ms in microseconds
    static const int SERVO_RANGE = SERVO_MAX  - SERVO_MIN ;

    // I/O configuration
    AnalogIn input( A1 ) ;
    Servo output( D6 ) ;
    output.Enable( SERVO_MIN, SERVO_PERIOD ) ;

    // Control loop
    for(;;)
    {
        float set_point = input.read() ;                            // 0.0 to 1.0
        output.SetPosition( set_point * SERVO_RANGE + SERVO_MIN ) ; // 1 to 2ms

        wait_us( SERVO_PERIOD ) ; // wait for one complete PWM cycle.
    }
}

定点版本会:

        uint16_t set_point = input.read_u16() ;                 // 0 to 2^16
        output.SetPosition( ((set_point * SERVO_RANGE) >> 16)   // 1 to 2ms
                            + SERVO_MIN ) ;

在循环中。

请注意,这不是最优雅的Servo 类实现。它只不过是一个 PWM 类。如果将最小/最大脉冲宽度与周期一起传递给构造函数会更好,这样您就可以给它一个零到 n 设定点而不是绝对脉冲宽度。这样一来,有些晦涩难懂的输出值计算就会被简化,因为Servo 类会通过适当的范围检查为您做到这一点。事实上,如果位置参数是 uint16_t 并且范围是 0 到 65535,那么所有可能的输入值都是有效的,你可以将 AnalogIn::read_u16() 的输出直接传递给它,这样你的循环就可以只包含:

output.SetPosition_u16( input.read_u16() ) ;
wait_us( SERVO_PERIOD ) ;

换句话说 - 获得更好的Servo 课程或编写自己的课程 - 在封装伺服控制专业知识方面对您几乎没有什么作用。

【讨论】:

    【解决方案2】:

    立即观察

    我现在看到五个问题,从“可能是问题”到“可能是问题”。我能够辨认出你照片上的别针标签,你的别针分配似乎是正确的。假设没有奇怪的电线或电压问题:

    1. 您的模拟引脚应该是AnalogIn,而不是AnalogOut。虽然AnalogOut 具有read 的能力,但它用于反馈以确保您的输出符合您的预期。现在,作为AnalogOut,您实际上是充当此引脚上的电压源并设置电压,而不是测量电压。

    2. 你没有打电话给Servo::Enable。文档告诉你如何调用Servo::Enable。确保调用它。您甚至需要指定伺服的起始位置,这将允许您对输出和伺服进行故障排除(请参阅稍后的故障排除)。

    3. AnalogIn::read[0.0, 1.0] 之间返回一个float,表示输入线上读取的电压与系统电压(通常为 5V 或 3.3V)之间的比率。但是,Servo::SetPosition 需要一个整数来表示脉冲信号正部分的长度(以 int 为单位,以微秒为单位 - 在您的情况下,介于 0 和 20,000 之间)。如果您尝试将AnalogIn::read 的结果传递给Servo::SetPosition,那么您的float 将被转换为0(除了一个罕见的情况,它是1)。您需要将模拟输入转换为整数输出。

    4. 现在在您的代码中,您只是在程序开始时读取模拟输入引脚的状态。您进入一个无限循环,并且再也不会读取此模拟输入。你可以随心所欲地转动那个旋钮,但它永远不会影响程序。您需要多次读取模拟输入。将其移动到循环内。

    5. 只是一个风格的东西,但你不需要那个内部 for 循环。除了使您的代码混乱之外,它不会做任何事情。如果您希望在将来的某个时候使用 i 的值,那么请保留它,否则就放弃它。

    疑难解答

    幸运的是,许多系统可以被认为是多个盒子(子系统),它们之间画有箭头。如果所有的盒子都在正确地完成它们的工作并且它们被插入到正确的下一个盒子中,那么整个系统作为一个整体工作。您的系统如下所示:

    +-----+   +-----+   +----------------+   +-------------+   +--------------------+   +-----+   +-------+
    | Pot |-->| ADC |-->| AnalogIn::read |-->| Float-to-us |-->| Servo::SetPosition |-->| PWM |-->| Servo |
    +-----+   +-----+   +----------------+   +-------------+   +--------------------+   +-----+   +-------+
    

    所有这些子系统共同构成了您的整个系统。如果其中一个链接不能正常工作,整个事情就不能正常工作。通常(或者至少我们喜欢想象我们有时间时会这样做),我们对系统和子系统进行测试,以确保它们根据输入产生预期的输出。如果您将输入应用于其中一个框,并且输出是您所期望的,那么该框就很好。给它一个绿色的复选标记并移动到下一个。

    对每一个的测试可能类似于:

    1. 电位器: 输入:转动旋钮,输出:中间引脚电压接近系统电压(5V或3.3V) 一路转单向时,转至另一端时接近0,两端之间呈近似线性递进。你需要一个万用表来测量。

    2. ADC(模数转换器): 输入:输入引脚上的一些电压。一旦你确认它工作正常,你可以通过扭转电位器来改变它。 输出: 这有点困难,因为输出是微控制器中的一个寄存器。我不知道你的硬件的调试环境是什么样的,所以我不能告诉你如何测量这个输出。如果您不知道如何使用调试器,您可以假设这可行,然后转到下一个(但您确实应该学习如何使用硬件的调试器)

    3. AnalogIn::read: input: ADC 的寄存器值。 输出: 在 0.0 和 1.0 之间浮动。我们可以同时测试 ADC 和AnalogIn::read 功能,将它们视为一个子系统,将引脚电压作为输入,将浮点值作为输出。再次为此,您将需要一些调试功能。打印语句或串行连接或开发环境或其他东西。

    4. 浮点数到我们的转换: 输入: 0.0 到 1.0 之间的浮点数。 输出: 一个介于 0 和 20,000 之间的整数,与浮点输入成比例(或成反比,取决于所需的功能)。同样,由于我们正在查看变量,因此您需要使用调试环境。

    5. Servo::SetPosition 输入: 一个介于 0 和 20,000 之间的整数,表示输出脉冲宽度调制 (PWM) 信号的占空比(高电平周期)。 输出: 增加这个数字会增加观察到的占空比。减少会减少它。我们中占空比的长度大约等于代码中的设定长度。你需要一个示波器来观察占空比。或者,如果您的伺服器正在工作,那么您应该会在这种情况发生变化时看到它移动。

    6. 伺服: 输入: PWM 信号。 输出: 角度位置。 0% 的占空比应该一直到一个极端,而 100% 的占空比应该轮流到另一个极端。

    结论

    将您的系统视为一系列子系统。独立测试每一项。你不能指望下一个子系统会“弥补”前一个子系统的不足。他们都需要工作。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-04-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多