用于控制 28BYJ-48 步进电机的 Arduino 库
本文介绍如何创建一个简单的C++类库,使用其控制板来正确控制28BYJ-48步进电机。
引言
我最近发现了Arduino开发板系统。虽然它已经上市一段时间了,但我之前并不知道这个非常有趣的系统。树莓派似乎更受欢迎,但Arduino确实值得被认识。
它的规格远不如树莓派那么令人印象深刻,但它的目的完全不同。那些像我一样学习电子学已经25年的人,应该会觉得它非常熟悉且极其有趣。Arduino系统是一个微控制器原型开发板,可以根据您的想象力进行扩展。
花费38美元,您可以在网上购买一个入门套件,其中包含许多有趣的组件和无焊原型板,可以进行许多实验。我上学时,学会了编程一块带有I/O电路的Motorola CPU,称为Motorola Kit D5。这块CPU板大小相当于一台现代笔记本电脑,由一台6802驱动,配备少量RAM和一个简陋的操作系统,您可以用一个小巧的25键键盘通过6位LED显示屏输入十六进制的汇编指令。
您可以在此处找到一些规格: http://www.old-computers.com/museum/computer.asp?c=504&st=1
Arduino是Kit D5的现代版本,并包含许多有趣的板载功能。
- 12个可配置的数字I/O引脚,其中6个可配置为PWM输出
- 6个10位ADC输入I/O,也可配置为数字输出
- 2个板载定时器
- 32K的NVRAM
- 1K的SRAM
- 2K的EEPROM
- RISC指令集
当然,与树莓派相比,这些规格看起来并不那么令人印象深刻,但这类板的用途与树莓派不同。该板的主要用途是通过I/O引脚与外部世界进行交互。
该板可以通过USB端口连接到PC,并使用一个简单的IDE进行编程。一旦程序安装在板载内存中,它就会以无限循环的方式运行。库可以用C++编写,并用于构建更复杂的应用程序。
控制步进电机
在本文中,我将向您展示如何编写一个简单的类来控制步进电机,也就是我购买的入门套件中提供的28BYJ-48型号。Arduino在其库中自带了一个Stepper
类,但该类无法正确驱动此电机。当该类驱动28BYJ-48时存在两个问题:
- 它需要2048步才能完成360度旋转,而28BYJ-48的规格说明需要4096步。
- 当向
step()
方法发送负值时,电机继续朝同一方向旋转!
我花了很多时间才弄清楚问题所在,并找到了28BYJ-48及其控制板的正确规格,然后我才明白为什么它无法正常工作。您可以在此处找到正确的规格:Geetec步进电机
诀窍在于写入控制板引脚的输入命令时必须遵循的序列。Arduino库的Stepper
类遵循一个由4位组成的4个值的序列,但28BYJ-48需要一个由4位组成的8个值的序列。该序列中的一个值对应电机的一个步进。这8个值必须按此顺序重复以使电机朝一个方向转动,按相反顺序则朝另一个方向转动。
这就是StepperMotor
类的代码所做的。该类旨在与此电机配合使用,如果需要与具有不同控制序列的电机配合使用,则需要对其进行修改。
要发送到28BYJ-48的四位组序列如下:
In 1 | In 2 | In 3 | In 4 |
0 | 0 | 0 | 1 |
0 | 0 | 1 | 1 |
0 | 0 | 1 | 0 |
0 | 1 | 1 | 0 |
0 | 1 | 0 | 0 |
1 | 1 | 0 | 0 |
1 | 0 | 0 | 0 |
1 | 0 | 0 | 1 |
该序列将使电机沿逆时针方向转动,如果反向运行此序列,电机将沿顺时针方向转动。
另一个参数是旋转速度。规格中并不清楚电机及其板的性能如何,但经过一些实验,我发现以1毫秒的周期运行效果很好。这意味着您可以每毫秒更改一次输入引脚的状态。超过此频率,板将无法正常响应,电机也将不会移动。
class StepperMotor { public: /** * Constructor assigning the motor control PIN */ StepperMotor(byte motorPin1, byte motorPin2, byte motorPin3, byte motorPin4); /** * Sets the period to send the command quartet * * @param period Period in ms between to control quartets */ void setPeriod(int period); /** * Moves the motor of a given number of steps. * Positive in a direction, Negative in the opposite * * @param numberOfSteps Number of steps to move */ void move(int numberOfSteps); /** * Stops the motor, setting all bits to LOW and raising the * interrupt flag when interrupt is set to true * * @param interrupt If true it will stop the current sequence. When set to true * this method is meant to be called from an interruption serving * routine (ISR). When false it doesn't raise the interrupt flag, just * set all the PIN to LOW. If not called from an interrupt routine, it * prevents the motor from running. */ void stop(bool interrupt = false); /** * Reset the interrupt flag to false so the motor * can be moved again */ void reset(); }
这个简单的类提供了两个主要方法:setPeriod
和move
。setPeriod
方法用于设置脉冲发送给电机之间的时间(以毫秒为单位),以使其向一个或另一个方向移动。move
方法用于按照参数符号指定的方向移动电机给定的步数。
可以使用stop
和reset
方法从中断服务例程中停止和重新启动电机。stop
方法有一个参数,用于启用从中ISR(中断服务例程)调用它,当为true时,该方法将使用内部标志异步停止电机。
StepperMotor
的代码是一个Arduino库,需要从Arduino IDE以.zip文件的形式安装。
以下是StepperMotor
类代码的摘录,展示了它是如何工作的。
void StepperMotor::stop(bool stop) { if (stop) { interrupt = true; } for (byte idx = 0; idx < PHASE_NUMBER; ++idx) { digitalWrite(motorPins[idx], LOW); } } void StepperMotor::moveOneStep(byte seqIdx) { for (byte idx = 0; idx < PHASE_NUMBER; ++idx) { digitalWrite(motorPins[idx], stepSequence[seqIdx][idx]); } delay(period); } bool StepperMotor::moveOneStepForward() { if (!interrupt) { stepIdx = incrementStep(stepIdx); moveOneStep(stepIdx); } return interrupt; } bool StepperMotor::moveStepsForward(int steps) { for(int n = 0; n < steps; n++) { if (moveOneStepForward()) { break; } } return interrupt; }
stop
方法的行为非常简单,如果在moveStepsForward
方法运行时调用它,它会中断该方法,因为moveOneStepForward
返回true并打破循环,从而停止电机的移动。
在以下示例中,连接如下:引脚1、2、3、4连接到Arduino开发板的引脚19、18、17和16。按钮连接到Arduino的引脚2,该引脚被设置为INPUT_PULLUP,因此无需电阻。
演示应用程序:每次按下按钮时反转旋转
第一个演示应用程序使用按钮启动电机按给定步数旋转,然后第二次点击按钮则使电机朝相反方向旋转,依此类推。
#include <StepperMotor.h> // 4 pins of the stepper motor board #define _PIN1 19 #define _PIN2 18 #define _PIN3 17 #define _PIN4 16 // Interruption on PIN2, push-button connected to pull-up #define ITR_PIN 2 #define ITR_NB 0 volatile boolean forward = false; volatile boolean start = false; volatile boolean first = true; StepperMotor stepper(_PIN1, _PIN2, _PIN3, _PIN4); /** * This method is called on the interruption raised on the falling front of the PIN2 * The start flag is used to avoid rebound front. Interruptions are disabled inside the * interrupt vector method. * start is reset once it has been processed in the main loop() */ void buttonPressed() { if (!first) { if (!start) { forward = !forward; start = true; } } else { first = false; } } void setup() { cli(); stepper.setPeriod(5); pinMode(ITR_PIN, INPUT_PULLUP); attachInterrupt(0, buttonPressed, FALL); sei(); } void loop() { if (start) { if (forward) { stepper.move(2048); stepper.stop(); } else { stepper.move(-2048); stepper.stop(); } start = false; } }
ISR方法在按钮按下脉冲的下降沿被调用。然而,看起来程序在启动时总是检测到中断,即使按钮没有被按下。为了避免处理这个中断,使用了first
标志来忽略这个中断。捕获按钮按下中断还有另一个问题,那就是触点本身的弹跳。在这个简单的例子中,使用了start
标志来避免按钮产生的任何弹跳脉冲。
演示应用程序:在按钮按下中断时停止或启动电机
第二个例子演示了在move
方法运行时停止电机的能力。每次按下按钮时,它会根据电机的当前状态停止或启动电机。中断例程有时会受到按钮弹跳的影响,这会导致按下一次按钮产生多个下降沿,并被芯片的中断线捕获。
我无法通过软件找到控制这个问题的方法,但一个简单的电容器和一些电阻就可以避免这个问题。因此,当前代码在按下按钮时有时可能会出现 erratic 行为。
#include <StepperMotor.h> #define DELAY 2000 #define _PIN1 19 #define _PIN2 18 #define _PIN3 17 #define _PIN4 16 #define ITR_PIN 2 #define ITR_NB 0 volatile bool first = true; volatile bool moveInterrupted = false; StepperMotor stepper(_PIN1, _PIN2, _PIN3, _PIN4); void buttonPressed() { static volatile boolean toggle = false; if (!first) { if (toggle) { stepper.reset(); } else { stepper.stop(true); } toggle = !toggle; } else { first = false; } } void setup() { cli(); stepper.setPeriod(10); pinMode(ITR_PIN, INPUT_PULLUP); attachInterrupt(0, buttonPressed, FALLING); sei(); } void loop() { moveInterrupted = stepper.move(2048); }
结论和兴趣点
这些与Arduino的微小实验就像一台时间机器,带我回到了25年前!网上有很多使用Arduino开发板的代码和电子电路,我只是刚刚触及到这个奇妙的小电路的表面。我一直喜欢用电脑与外部世界互动,可惜我的工作更多地集中在软件方面,尽管我从未忘记我最初学的专业——连接电子元件的CPU板。我希望这篇简单的文章能播下学习如何将计算机与真实世界接口的种子!