伺服电机和电位器的问题:程序插入电路板时不移动

Issue with servo motor and potentiometer: not moving when program is inserted into board

此代码旨在利用电位器转动伺服电机。当我试图将它插入程序时,伺服器根本没有移动,我不知道这是我的电路板、我的接线还是我的代码的结果。如果有人可以为此事提供帮助或提供一些帮助,将不胜感激。我使用的电路板是 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 文件,所以如果有人需要它作为参考,我会 post 它作为编辑。为了以防万一

,我也会把接线放好

舵机是SG90

板子的接线:

即时观察

我现在看到五个问题,范围从 "maybe a problem" 到 "likely a problem"。我能辨认出你照片上的别针标签,你的别针分配似乎是正确的。假设没有奇怪的电线或电压问题:

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

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

  3. AnalogIn::read returns [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 input:一个0到20000之间的整数代表占空比(高电平周期) 的输出脉冲宽度调制 (PWM) 信号。 输出: 增加这个数字会增加观察到的占空比。减少减少它。我们中占空比的长度大约等于代码中设置的长度。您需要一个示波器来观察占空比。或者,如果您的伺服系统正在工作,那么您应该会看到它在发生变化时移动。

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

结论

将您的系统视为一系列子系统。独立测试每一个。您不能指望下一个子系统 "make up for" 弥补前一个子系统的不足。他们都需要工作。

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

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

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

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

AnalogOut input(A1);

那么您需要了解 闭环 控制系统必须连续读取其输入以调整输出。在这里,您只在 控制回路之前读取电位器一次,因此在回路中它永远不会改变值,因此位置不会改变。此外,您有一个完全不必要的内部循环,它来自完全不同的 Servo 实现 here - that example was not a closed-loop control system - it simply continuously cycles the servo back-and forth over its full range - that is not what you are trying to achieve in this case and it is just cargo cult programming 的示例代码。

闭环控制连续:

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

最后您需要了解,在这种情况下,输入测量的单位与输出设置的单位不同 - 它们需要缩放,以便满量程输入范围映射到满量程输出范围。 mbed AnalogIn class 有两个读取函数; AnalogIn::read() returns 0.0 到 1.0 范围内的 floatAnalogIn::read_u16 returns 0 到 65535 范围内的 uint16_t 值。我个人会使用整数版本,但 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 class 实现。它只不过是一个 PWM class。如果 min/max 脉冲宽度与周期一起传递给构造函数会更好,这样您就可以给它一个零到 n 设定点而不是绝对值脉冲宽度。这样就可以简化有些神秘的输出值计算,因为 Servo class 会通过适当的范围检查为您完成。事实上,如果位置参数是 uint16_t 并且范围是 0 到 65535,那么所有可能的输入值都是有效的,您可以将 AnalogIn::read_u16() 的输出直接传递给它,这样您的循环就可以只包含:

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

换句话说 - 获得更好的 Servo class 或编写你自己的 - 这在封装伺服控制专业知识方面对你帮助不大。