class Button { // a simple class for buttons based on the "Debounce" example
const byte buttonPin; // the GPIO / pin for the button
static constexpr byte debounceDelay = 30; // the debounce time; increase if the output flickers. Static because we only need one value for all buttons
const bool active; // is the pin active HIGH or active LOW (will also activate the pullups!)
bool lastButtonState = HIGH; // the previous reading from the input pin
byte lastDebounceTime = 0; // the last time the output pin was toggled - we check only ONE byte, so I didn't mess around with unsigned long
public:
/**
\brief constructor for a button
The constructor takes the GPIO as parameter.
If you omit the second parameter, the library will activate the internal pullup resistor
and the button should connect to GND.
If you set the second parameter to HIGH, the button is active HIGH.
The button should connect to VCC.
The internal pullups will not be used but you will need an external pulldown resistor.
\param attachTo the GPIO for the button
\param active LOW (default) - if button connects to GND, HIGH if button connects to VCC
*/
Button(byte attachTo, bool active = LOW) : buttonPin(attachTo), active(active) {}
/**
\brief set the pin to the proper state
Call this function in your setup().
The pinMode will be set according to your constructor.
*/
void begin() {
if (active == LOW)
pinMode(buttonPin, INPUT_PULLUP);
else
pinMode(buttonPin, INPUT);
}
/**
\brief indicate if button was pressed since last call
@return HIGH if button was pressed since last call - debounce
*/
bool isClicked() {
bool buttonState = LOW; // the current reading from the input pin
byte reading = LOW; // "translated" state of button LOW = released, HIGH = pressed, despite the electrical state of the input pint
if (digitalRead(buttonPin) == active) reading = HIGH; // if we are using INPUT_PULLUP we are checking invers to LOW Pin
if (((millis() & 0xFF ) - lastDebounceTime) > debounceDelay) // If the switch changed, AFTER any pressing or noise
{
if (reading != lastButtonState && lastButtonState == LOW) // If there was a change and and last state was LOW (= released)
{
buttonState = HIGH;
}
lastDebounceTime = millis() & 0xFF;
lastButtonState = reading;
}
return buttonState;
}
bool isPressed() {
return digitalRead(buttonPin) == active;
}
};
class LedController {
int pin = 0;
public:
LedController(int pin) : pin(pin) {}
void begin() {
pinMode(pin, OUTPUT);
}
void turnOn() {
digitalWrite(pin, HIGH);
}
void turnOff() {
digitalWrite(pin, LOW);
}
};
const int ONE_TURN = 400;
//const int ONE_TURN = 40; // DEBUG ONLY TODO
// Нужное количество оборотов для одного ряда
const int ONE_ROW_TURNS_COUNT = 10 * ONE_TURN;
enum Speed {
Default, Fast
};
class StepMotor {
int current_direction = HIGH;
int current_counter = 0;
int stepPin = 0;
int dirPin = 0;
int enablePin = 0;
int output_direction = 0;
Speed speed = Default;
int getDelay() {
return speed == Default ? 500 : 250;
}
bool switch_direction_if_needed() {
// Если количество шагов меньше, чем нужное количество оборотов, то ничего не делаем
if (current_counter < ONE_ROW_TURNS_COUNT) {
return false;
}
// Если количество шагов больше или равно максимальному количеству поворотов, то...
// Поменять направление вращения
if (current_direction == HIGH) {
current_direction = LOW;
}
else {
current_direction = HIGH;
}
digitalWrite(dirPin, current_direction); // Записать новое направление вращения в пин
current_counter = 0; // Сбросить счетчик шагов
return true;
}
public:
StepMotor(int stepPin, int dirPin, int enablePin)
: stepPin(stepPin)
, dirPin(dirPin)
, enablePin(enablePin)
{
}
void begin() {
pinMode(stepPin, OUTPUT);
pinMode(dirPin, OUTPUT);
pinMode(enablePin, OUTPUT);
digitalWrite(enablePin, LOW);
digitalWrite(dirPin, current_direction);
}
void new_step() {
output_direction = 0;
}
bool do_step() {
const int del = getDelay();
digitalWrite(stepPin, HIGH);
delayMicroseconds(del);
digitalWrite(stepPin, LOW);
delayMicroseconds(del);
current_counter += 1;
const bool switched = switch_direction_if_needed();
output_direction = current_direction == HIGH ? 1 : -1;
return switched;
}
bool isGoingLeft() {
return output_direction < 0;
}
bool isGoingRight() {
return output_direction > 0;
}
void setSpeed(Speed speed) {
this->speed = speed;
}
Speed getSpeed() {
return speed;
}
void switchSpeed() {
setSpeed(speed == Default ? Fast : Default);
}
};
enum CarretteMode {
Manual,
Automatic
};
class Carrette {
CarretteMode mode = Manual;
int row = 0;
public:
void do_automatic_step(StepMotor &motor) {
if (mode == Automatic) {
if (row > 0) {
if (motor.do_step()) {
row -= 1;
}
}
if (row <= 0) {
mode = Manual;
row = 0;
motor.new_step();
}
}
}
bool isMovingInAutomaticMode() {
return mode == Automatic && row > 0;
}
void setRowsAndStartAutomatically(int rows) {
row = rows;
mode = Automatic;
}
void stop() {
row = 0;
mode = Manual;
}
void moveManually(StepMotor &motor) {
stop();
motor.do_step();
}
};
// Как много рядов делать (пока не настраивается, можно в коде поменять)
const int ROWS_COUNT = 10;
// Пины мотора
const int STEP_PIN = 27;
const int DIR_PIN = 26;
const int ENABLE_PIN = 25;
// Пины кнопки
const int BUTTON_LEFT_RIGHT_PRESS_PIN = 33; // Кнопка влево-вправо (пока держишь)
const int BUTTON_AUTOMATIC_PIN = 22; // Кнопка влево-вправо (автоматически)
const int BUTTON_SPEED_PIN = 34; // Кнопка переключения режима скорости
// Пины Диодов
const int LED_GO_LEFT_PIN = 17; // Диод когда едет влево
const int LED_GO_RIGHT_PIN = 5; // Диод когда едет вправо
// Диод для индикации высокой скорости (я его добавил для себя)
const int LED_FAST_SPEED_PIN = 18;
// Управление
Button buttonStart{ BUTTON_LEFT_RIGHT_PRESS_PIN };
Button buttonAutomatic{ BUTTON_AUTOMATIC_PIN };
Button buttonChangeSpeed{ BUTTON_SPEED_PIN };
StepMotor motor{ STEP_PIN, DIR_PIN, ENABLE_PIN };
LedController ledLeft{ LED_GO_LEFT_PIN };
LedController ledRight{ LED_GO_RIGHT_PIN };
LedController ledFastSpeed{ LED_FAST_SPEED_PIN };
Carrette carrette;
// Логика
void setup() {
Serial.begin(9600);
ledLeft.begin();
ledRight.begin();
ledFastSpeed.begin();
buttonStart.begin();
buttonAutomatic.begin();
buttonChangeSpeed.begin();
motor.begin();
}
void loop() {
motor.new_step();
if (buttonChangeSpeed.isClicked()) {
motor.switchSpeed();
}
if (buttonAutomatic.isClicked()) {
if (carrette.isMovingInAutomaticMode()) {
carrette.stop();
}
else {
carrette.setRowsAndStartAutomatically(ROWS_COUNT);
}
}
if (buttonStart.isPressed()) {
carrette.moveManually(motor);
}
else {
carrette.do_automatic_step(motor);
}
show_moving_direction();
show_fast_speed();
}
void show_moving_direction() {
if (motor.isGoingLeft()) {
ledLeft.turnOn();
ledRight.turnOff();
}
else if (motor.isGoingRight()) {
ledLeft.turnOff();
ledRight.turnOn();
}
else {
ledLeft.turnOff();
ledRight.turnOff();
}
}
void show_fast_speed() {
static Speed previousSpeed = motor.getSpeed();
const Speed speed = motor.getSpeed();
if (speed == Fast && previousSpeed == Default) {
ledFastSpeed.turnOn();
}
else if (speed == Default && previousSpeed == Fast) {
ledFastSpeed.turnOff();
}
previousSpeed = speed;
}