伺服电机和电位器的问题:程序插入电路板时不移动
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"。我能辨认出你照片上的别针标签,你的别针分配似乎是正确的。假设没有奇怪的电线或电压问题:
您的模拟引脚应该是 AnalogIn
,而不是 AnalogOut
。虽然 AnalogOut
具有 read
的能力,但它用于反馈以确保您的输出符合您的预期。现在,作为 AnalogOut
,您实际上是在充当此引脚上的电压源,并且 设置 电压而不是 测量 电压。
您没有打电话给 Servo::Enable
。文档告诉你如何调用 Servo::Enable
。确保调用它。您甚至需要指定伺服的起始位置,这将允许您对输出和伺服进行故障排除(请参阅稍后的故障排除)。
AnalogIn::read
returns [0.0, 1.0]
之间的float
表示输入线上读取的电压与系统电压(5V或通常为 3.3V)。但是,Servo::SetPosition
需要一个整数来表示脉冲信号正部分的长度(以微秒为单位的 int
- 在您的情况下介于 0 和 20,000 之间)。如果您尝试将 AnalogIn::read
的结果传递给 Servo::SetPosition
,那么您的 float
将被转换为 0(除了 1
这种罕见的情况)。您需要将模拟输入转换为整数输出。
现在在您的代码中,您只是在程序开始时读取模拟输入引脚的状态。您进入了一个无限循环,再也不会读取这个模拟输入。你可以随心所欲地转动那个旋钮,但它永远不会影响程序。您需要多次读取模拟输入。把它移到你的循环中。
只是一种样式,但您不需要内部 for
循环。除了使您的代码混乱之外,它什么都不做。如果您希望在将来的某个时候使用 i
的值,那么保留它,否则就放弃它。
疑难解答
幸运的是,许多系统可以被认为是许多框(子系统),它们之间画有箭头。如果所有的盒子都正确地完成了它们的工作,并且它们被插入了正确的下一个盒子,那么整个系统就可以正常工作了。您的系统如下所示:
+-----+ +-----+ +----------------+ +-------------+ +--------------------+ +-----+ +-------+
| Pot |-->| ADC |-->| AnalogIn::read |-->| Float-to-us |-->| Servo::SetPosition |-->| PWM |-->| Servo |
+-----+ +-----+ +----------------+ +-------------+ +--------------------+ +-----+ +-------+
所有这些子系统共同构成了您的整个系统。如果这些链接之一不能正常工作,整个事情将无法正常工作。通常(或者至少我们喜欢想象我们有空的时候会这样做),我们对系统和子系统进行测试,以确保它们根据输入产生预期的输出。如果您将输入应用到这些框之一,并且输出是您所期望的,那么该框是好的。给它一个绿色的复选标记并移动到下一个。
对其中每一项的测试可能类似于:
电位器: 输入:旋转旋钮,输出:中间引脚的电压在顺时针顺时针接近系统电压(5V或3.3V),逆时针顺时针接近0,两端近似线性递增。你需要一个万用表来测量这个。
ADC(模数转换器): 输入: 输入引脚上的一些电压。你可以通过扭转电位器来改变它,一旦你确认它正在工作。 输出:这有点难,因为输出是微控制器中的一个寄存器。我不知道你的硬件调试环境是什么样的,所以我不能告诉你如何测量这个输出。如果您不知道如何使用调试器(但您确实应该学习如何使用硬件调试器),您可以假设这有效并转到下一个
AnalogIn::read
: input: ADC的寄存器值。 输出: 一些在 0.0 和 1.0 之间浮动。我们可以同时测试 ADC 和 AnalogIn::read
功能,方法是将它们视为一个子系统,引脚电压作为输入,浮点值作为输出。同样,为此您需要一些调试功能。打印语句或串行连接或开发环境或其他东西。
向我们转换的浮点数: 输入: 0.0 和 1.0 之间的浮点数。 输出: 0 到 20,000 之间的整数,与浮点输入成正比(或反比,取决于所需的功能)。同样,由于我们正在查看变量,因此您需要使用调试环境。
Servo::SetPosition
input:一个0到20000之间的整数代表占空比(高电平周期) 的输出脉冲宽度调制 (PWM) 信号。 输出: 增加这个数字会增加观察到的占空比。减少减少它。我们中占空比的长度大约等于代码中设置的长度。您需要一个示波器来观察占空比。或者,如果您的伺服系统正在工作,那么您应该会看到它在发生变化时移动。
伺服: 输入:一个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 范围内的 float
和 AnalogIn::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 或编写你自己的 - 这在封装伺服控制专业知识方面对你帮助不大。
此代码旨在利用电位器转动伺服电机。当我试图将它插入程序时,伺服器根本没有移动,我不知道这是我的电路板、我的接线还是我的代码的结果。如果有人可以为此事提供帮助或提供一些帮助,将不胜感激。我使用的电路板是 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"。我能辨认出你照片上的别针标签,你的别针分配似乎是正确的。假设没有奇怪的电线或电压问题:
您的模拟引脚应该是
AnalogIn
,而不是AnalogOut
。虽然AnalogOut
具有read
的能力,但它用于反馈以确保您的输出符合您的预期。现在,作为AnalogOut
,您实际上是在充当此引脚上的电压源,并且 设置 电压而不是 测量 电压。您没有打电话给
Servo::Enable
。文档告诉你如何调用Servo::Enable
。确保调用它。您甚至需要指定伺服的起始位置,这将允许您对输出和伺服进行故障排除(请参阅稍后的故障排除)。AnalogIn::read
returns[0.0, 1.0]
之间的float
表示输入线上读取的电压与系统电压(5V或通常为 3.3V)。但是,Servo::SetPosition
需要一个整数来表示脉冲信号正部分的长度(以微秒为单位的int
- 在您的情况下介于 0 和 20,000 之间)。如果您尝试将AnalogIn::read
的结果传递给Servo::SetPosition
,那么您的float
将被转换为 0(除了1
这种罕见的情况)。您需要将模拟输入转换为整数输出。现在在您的代码中,您只是在程序开始时读取模拟输入引脚的状态。您进入了一个无限循环,再也不会读取这个模拟输入。你可以随心所欲地转动那个旋钮,但它永远不会影响程序。您需要多次读取模拟输入。把它移到你的循环中。
只是一种样式,但您不需要内部
for
循环。除了使您的代码混乱之外,它什么都不做。如果您希望在将来的某个时候使用i
的值,那么保留它,否则就放弃它。
疑难解答
幸运的是,许多系统可以被认为是许多框(子系统),它们之间画有箭头。如果所有的盒子都正确地完成了它们的工作,并且它们被插入了正确的下一个盒子,那么整个系统就可以正常工作了。您的系统如下所示:
+-----+ +-----+ +----------------+ +-------------+ +--------------------+ +-----+ +-------+
| Pot |-->| ADC |-->| AnalogIn::read |-->| Float-to-us |-->| Servo::SetPosition |-->| PWM |-->| Servo |
+-----+ +-----+ +----------------+ +-------------+ +--------------------+ +-----+ +-------+
所有这些子系统共同构成了您的整个系统。如果这些链接之一不能正常工作,整个事情将无法正常工作。通常(或者至少我们喜欢想象我们有空的时候会这样做),我们对系统和子系统进行测试,以确保它们根据输入产生预期的输出。如果您将输入应用到这些框之一,并且输出是您所期望的,那么该框是好的。给它一个绿色的复选标记并移动到下一个。
对其中每一项的测试可能类似于:
电位器: 输入:旋转旋钮,输出:中间引脚的电压在顺时针顺时针接近系统电压(5V或3.3V),逆时针顺时针接近0,两端近似线性递增。你需要一个万用表来测量这个。
ADC(模数转换器): 输入: 输入引脚上的一些电压。你可以通过扭转电位器来改变它,一旦你确认它正在工作。 输出:这有点难,因为输出是微控制器中的一个寄存器。我不知道你的硬件调试环境是什么样的,所以我不能告诉你如何测量这个输出。如果您不知道如何使用调试器(但您确实应该学习如何使用硬件调试器),您可以假设这有效并转到下一个
AnalogIn::read
: input: ADC的寄存器值。 输出: 一些在 0.0 和 1.0 之间浮动。我们可以同时测试 ADC 和AnalogIn::read
功能,方法是将它们视为一个子系统,引脚电压作为输入,浮点值作为输出。同样,为此您需要一些调试功能。打印语句或串行连接或开发环境或其他东西。向我们转换的浮点数: 输入: 0.0 和 1.0 之间的浮点数。 输出: 0 到 20,000 之间的整数,与浮点输入成正比(或反比,取决于所需的功能)。同样,由于我们正在查看变量,因此您需要使用调试环境。
Servo::SetPosition
input:一个0到20000之间的整数代表占空比(高电平周期) 的输出脉冲宽度调制 (PWM) 信号。 输出: 增加这个数字会增加观察到的占空比。减少减少它。我们中占空比的长度大约等于代码中设置的长度。您需要一个示波器来观察占空比。或者,如果您的伺服系统正在工作,那么您应该会看到它在发生变化时移动。伺服: 输入:一个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 范围内的 float
和 AnalogIn::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 或编写你自己的 - 这在封装伺服控制专业知识方面对你帮助不大。