Хочу критики моего кода

Код управляет тремя жалюзи. Для каждого жалюзи предусмотрены две кнопки для управления в каждом направлении, а также две кнопки для управления всеми жалюзи одновременно. motorSerial — это способ управления всеми жалюзи через последовательный монитор: отправка команды 1 посылает команду в одну сторону, а 2 — в другую.

Больше всего меня беспокоит, что при нажатии кнопки мотор включается, ждёт 0,2 секунды и выключается. Но если удерживать кнопку, он будет постоянно включаться и выключаться. Есть ли у кого-нибудь способ получше?

const int PWMA = 13;
const int AI2_left = 12;
const int AI1_left = 11;
const int PWMB = 18;
const int BI1_middle = 10;
const int BI2_middle = 9;

const int PWMA2 = 8;
const int AI2_right = 7;
const int AI1_right = 6;

const int greenLeft1 = 5;
const int greenLeft2 = 4;
const int redMiddle1 = 3;
const int redMiddle2 = 2;
const int yellowRight1 = 14;
const int yellowRight2 = 15;
const int blueAll1 = 16;
const int blueAll2 = 17;

int motorSpeed_left = 0;
int motorSpeed_middle = 0;
int motorSpeed_right = 0;
int motorSpeed_all = 0;
int motorSerial = 0;

void setup() {
  pinMode(PWMA, OUTPUT);
  pinMode(AI2_left, OUTPUT);
  pinMode(AI1_left, OUTPUT);
  pinMode(BI1_middle, OUTPUT);
  pinMode(BI2_middle, OUTPUT);
  pinMode(PWMA2, OUTPUT);
  pinMode(AI2_right, OUTPUT);  
  pinMode(AI1_right, OUTPUT);

  pinMode(greenLeft1, INPUT_PULLUP);
  pinMode(greenLeft2, INPUT_PULLUP);
  pinMode(redMiddle1, INPUT_PULLUP);
  pinMode(redMiddle2, INPUT_PULLUP);
  pinMode(yellowRight1, INPUT_PULLUP);
  pinMode(yellowRight2, INPUT_PULLUP);
  pinMode(blueAll1, INPUT_PULLUP);
  pinMode(blueAll2, INPUT_PULLUP);

  Serial.begin(9600);
}

void loop() {

  if(Serial.available() > 0) {
    motorSerial = Serial.parseInt();
  }


  if(digitalRead(greenLeft1) == LOW) {
    motorSpeed_left = 200;
    spinMotor_left(motorSpeed_left);
    delay(200);
    motorSpeed_left = 0;
    spinMotor_left(motorSpeed_left);
  }
  else if(digitalRead(greenLeft2) == LOW) {
    motorSpeed_left = -200;
    spinMotor_left(motorSpeed_left);
    delay(200);
    motorSpeed_left = 0;
    spinMotor_left(motorSpeed_left);
  }
  else if(digitalRead(redMiddle1) == LOW) {
    motorSpeed_middle = 200;
    spinMotor_middle(motorSpeed_middle);
    delay(200);
    motorSpeed_middle = 0;
    spinMotor_middle(motorSpeed_middle);
  }
  else if(digitalRead(redMiddle2) == LOW) {
    motorSpeed_middle = -200;
    spinMotor_middle(motorSpeed_middle);
    delay(200);
    motorSpeed_middle = 0;
    spinMotor_middle(motorSpeed_middle);
  }
  else if(digitalRead(yellowRight1) == LOW) {
    motorSpeed_right = 200;
    spinMotor_right(motorSpeed_right);
    delay(200);
    motorSpeed_right = 0;
    spinMotor_right(motorSpeed_right);
  }
  else if(digitalRead(yellowRight2) == LOW) {
    motorSpeed_right = -200;
    spinMotor_right(motorSpeed_right);
    delay(200);
    motorSpeed_right = 0;
    spinMotor_right(motorSpeed_right);
  }
  else if(digitalRead(blueAll1) == LOW) {
    motorSpeed_left = 200;
    motorSpeed_middle = 200;
    motorSpeed_right = 200;
    spinMotor_left(motorSpeed_left);
    spinMotor_middle(motorSpeed_middle);
    spinMotor_right(motorSpeed_right);
    delay(200);
    motorSpeed_left = 0;
    motorSpeed_middle = 0;
    motorSpeed_right = 0;
    spinMotor_left(motorSpeed_left);
    spinMotor_middle(motorSpeed_middle);
    spinMotor_right(motorSpeed_right);
  }
  else if(digitalRead(blueAll2) == LOW) {
    motorSpeed_left = -200;
    motorSpeed_middle = -200;
    motorSpeed_right = -200;
    spinMotor_left(motorSpeed_left);
    spinMotor_middle(motorSpeed_middle);
    spinMotor_right(motorSpeed_right);
    delay(200);
    motorSpeed_left = 0;
    motorSpeed_middle = 0;
    motorSpeed_right = 0;
    spinMotor_left(motorSpeed_left);
    spinMotor_middle(motorSpeed_middle);
    spinMotor_right(motorSpeed_right);
  }
  else if(motorSerial == 1) {
    motorSpeed_left = 200;
    motorSpeed_middle = 200;
    motorSpeed_right = 200;
    spinMotor_left(motorSpeed_left);
    spinMotor_middle(motorSpeed_middle);
    spinMotor_right(motorSpeed_right);
    delay(2500);
    motorSpeed_left = 0;
    motorSpeed_middle = 0;
    motorSpeed_right = 0;
    spinMotor_left(motorSpeed_left);
    spinMotor_middle(motorSpeed_middle);
    spinMotor_right(motorSpeed_right);
    motorSerial = 0;
  }
  else if(motorSerial == 2) {
    motorSpeed_left = -200;
    motorSpeed_middle = -200;
    motorSpeed_right = -200;
    spinMotor_left(motorSpeed_left);
    spinMotor_middle(motorSpeed_middle);
    spinMotor_right(motorSpeed_right);
    delay(2500);
    motorSpeed_left = 0;
    motorSpeed_middle = 0;
    motorSpeed_right = 0;
    spinMotor_left(motorSpeed_left);
    spinMotor_middle(motorSpeed_middle);
    spinMotor_right(motorSpeed_right);
    motorSerial = 0;
  }


}

/*****************************************************/
void spinMotor_left(int motorSpeed_left)
{
  if (motorSpeed_left > 0)
  {
    digitalWrite(AI1_left, HIGH);
    digitalWrite(AI2_left, LOW);
  }
  else if (motorSpeed_left < 0)
  {
    digitalWrite(AI1_left, LOW);
    digitalWrite(AI2_left, HIGH);
  }
  else
  {
    digitalWrite(AI1_left, LOW);
    digitalWrite(AI2_left, LOW);
  }
  analogWrite(PWMA, abs(motorSpeed_left));
}


void spinMotor_middle(int motorSpeed_middle)
{
  if (motorSpeed_middle > 0)
  {
    digitalWrite(BI1_middle, HIGH);
    digitalWrite(BI2_middle, LOW);
  }
  else if (motorSpeed_middle < 0)
  {
    digitalWrite(BI1_middle, LOW);
    digitalWrite(BI2_middle, HIGH);
  }
  else
  {
    digitalWrite(BI1_middle, LOW);
    digitalWrite(BI2_middle, LOW);
  }
  analogWrite(PWMB, abs(motorSpeed_middle));
}


void spinMotor_right(int motorSpeed_right)
{
  if (motorSpeed_right > 0)
  {
    digitalWrite(AI1_right, HIGH);
    digitalWrite(AI2_right, LOW);
  }
  else if (motorSpeed_right < 0)
  {
    digitalWrite(AI1_right, LOW);
    digitalWrite(AI2_right, HIGH);
  }
  else
  {
    digitalWrite(AI1_right, LOW);
    digitalWrite(AI2_right, LOW);
  }
  analogWrite(PWMA2, abs(motorSpeed_right));
}

, 👍0


3 ответа


Лучший ответ:

0

В длинном ответе Ratchet Freak много хороших моментов. Я тоже согласен. с предложением Code Gorilla добавить конструктор в struct. Однако, как только у вас есть конструктор, он становится похож на обычный класс C++¹, поэтому имело бы смысл добавить связанный с двигателем функции как члены класса. Поэтому я бы начал с построения класс, который абстрагирует детали управления двигателями:

class Motor {
public:
    Motor(uint8_t pin_1, uint8_t pin_2, uint8_t pin_pwm)
    : pin_1(pin_1), pin_2(pin_2), pin_pwm(pin_pwm) {}

    void begin() {
        pinMode(pin_1, OUTPUT);
        pinMode(pin_2, OUTPUT);
        pinMode(pin_pwm, OUTPUT);
    }

    void spin(int speed) {
        digitalWrite(pin_1, speed > 0 ? HIGH : LOW);
        digitalWrite(pin_2, speed < 0 ? HIGH : LOW);
        analogWrite(pin_pwm, abs(speed));
    }

private:
    const uint8_t pin_1, pin_2, pin_pwm;
};

Я исключил кнопки из класса, потому что у вас есть эти синие Кнопки «все двигатели», которые не очень вписываются в концепцию, что каждый кнопка «принадлежит» определенному двигателю.

Затем я бы использовал какую-нибудь другую абстракцию для представления кнопок. Возможно, из библиотеки, которая устраняет дребезг. Если дребезг кнопок не вызывает проблем, в этом приложении «абстракция» будет просто:

// Эта кнопка нажата?
static inline bool pressed(uint8_t pin) {
    return digitalRead(pin) == LOW;
}

Конечно, как абстракция, это не представляет большой ценности. можно рассматривать как небольшой кусочек «синтаксического сахара», который делает письмо (и чтение) остальной части кода проще.

С учетом всего этого программа будет выглядеть так:

// Распиновка.
const uint8_t left_1   = 5;
const uint8_t left_2   = 4;
const uint8_t middle_1 = 3;
const uint8_t middle_2 = 2;
const uint8_t right_1  = 14;
const uint8_t right_2  = 15;
const uint8_t all_1    = 16;
const uint8_t all_2    = 17;
const int button_count = 8;
const uint8_t buttons[button_count] = {
    left_1, left_2, middle_1, middle_2, right_1, right_2, all_1, all_2
};

// Моторы.
const int SPEED = 200;
Motor motor_left(11, 12, 13);
Motor motor_middle(10, 9, 18);
Motor motor_right(6, 7, 8);

void setup() {
    motor_left.begin();
    motor_middle.begin();
    motor_right.begin();
    for (int i = 0; i < button_count; i++)
        pinMode(buttons[i], INPUT_PULLUP);
    Serial.begin(9600);
}

void loop() {
    // Считать последовательную команду, если таковая имеется.
    int serial_command = Serial.read();  // равно -1, если нет ввода
    bool serial_1 = serial_command == '1',  // последовательный запрос для '1'
         serial_2 = serial_command == '2';  // последовательный запрос для '2'

    // Управляйте двигателями.
    if      (pressed(left_1))   motor_left.spin(SPEED);
    else if (pressed(left_2))   motor_left.spin(-SPEED);
    else if (pressed(middle_1)) motor_middle.spin(SPEED);
    else if (pressed(middle_2)) motor_middle.spin(-SPEED);
    else if (pressed(right_1))  motor_right.spin(SPEED);
    else if (pressed(right_2))  motor_right.spin(-SPEED);
    else if (pressed(all_1) || serial_1) {
        motor_left.spin(SPEED);
        motor_middle.spin(SPEED);
        motor_right.spin(SPEED);
        if (serial_1) delay(2500);
    } else if (pressed(all_2) || serial_2) {
        motor_left.spin(-SPEED);
        motor_middle.spin(-SPEED);
        motor_right.spin(-SPEED);
        if (serial_2) delay(2500);
    } else {
        motor_left.spin(0);
        motor_middle.spin(0);
        motor_right.spin(0);
    }
}

¹ Ключевое слово struct в C++ на самом деле объявляет класс.

,

3
if(digitalRead(greenLeft1) == LOW) {
    motorSpeed_left = 200;
    spinMotor_left(motorSpeed_left);
    delay(200);
    motorSpeed_left = 0;
    spinMotor_left(motorSpeed_left);
}
else if(digitalRead(greenLeft2) == LOW) {
    motorSpeed_left = -200;
    spinMotor_left(motorSpeed_left);
    delay(200);
    motorSpeed_left = 0;
    spinMotor_left(motorSpeed_left);
}

Не совсем то, что нужно.

Вместо этого вам нужно что-то вроде:

if(digitalRead(greenLeft1) == LOW) {
    motorSpeed_left = 200;
    spinMotor_left(motorSpeed_left);

} else if(digitalRead(greenLeft2) == LOW) {
    motorSpeed_left = -200;
    spinMotor_left(motorSpeed_left);
} else {
    motorSpeed_left = 0;
    spinMotor_left(motorSpeed_left);
}

Однако, поскольку вам также нужен главный выключатель и управление через последовательный порт, вы можете вынести тесты каждой кнопки в переменную, а затем заставить главный выключатель переопределять их:

int motor_left = 0;
if(digitalRead(greenLeft1) == LOW) {
    motor_left = 1;
} else if(digitalRead(greenLeft2) == LOW) {
    motor_left = -1;
} else {
    motor_left = 0;
}

//...

if(digitalRead(blueAll1) == LOW){
    motor_left   = 1;
    motor_center = 1;
    motor_right  = 1;
} else if(digitalRead(greenLeft2) == LOW) {
    motor_left   = -1;
    motor_center = -1;
    motor_right  = -1;
} 

if(motor_left > 0) {
    motorSpeed_left = 200;
    spinMotor_left(motorSpeed_left);

} else if(motor_left < 0) {
    motorSpeed_left = -200;
    spinMotor_left(motorSpeed_left);
} else {
    motorSpeed_left = 0;
    spinMotor_left(motorSpeed_left);
}

Кроме того, номер контакта — это просто int, поэтому вы можете вывести его как параметр и иметь только одну копию функции spinMotor.

void spinMotor(int motor_pin1, int motor_pin2, int motorSpeed)
{
  if (motorSpeed > 0)
  {
    digitalWrite(motor_pin1, HIGH);
    digitalWrite(motor_pin2, LOW);
  }
  else if (motorSpeed < 0)
  {
    digitalWrite(motor_pin1, LOW);
    digitalWrite(motor_pin2, HIGH);
  }
  else
  {
    digitalWrite(motor_pin1, LOW);
    digitalWrite(motor_pin2, LOW);
  }
  analogWrite(PWMA, abs(motorSpeed));
}

Чтобы сократить количество глобальных переменных, вы можете сгруппировать состояние каждого двигателя в структуру и инициализировать их в init():

struct motorState {
   int motor_pin1, motor_pin2;
   int pwm_pin;
   int button1, button2;
   int direction;
   int speed;
}

motorState motor_left;
motorState motor_center;
motorState motor_right;

затем передайте глобальные переменные по указателю или ссылке в функции, выполняющие ввод и вывод.

void readMotorButton(motorState *motor){
    if(digitalRead(motor->button1) == LOW) {
        motor->direction = 1;
        motor->speed = 200;
    } else if(digitalRead(motor->button2) == LOW) {
        motor->direction = -1;
        motor->speed = 200;
    } else {
        motor->direction = 0;
        motor->speed = 0;
    }
}

void spinMotor(motorState *motor)
{
  if (motor->direction > 0)
  {
    digitalWrite(motor->motor_pin1, HIGH);
    digitalWrite(motor->motor_pin2, LOW);
  }
  else if (motor->direction < 0)
  {
    digitalWrite(motor->motor_pin1, LOW);
    digitalWrite(motor->motor_pin2, HIGH);
  }
  else
  {
    digitalWrite(motor->motor_pin1, LOW);
    digitalWrite(motor->motor_pin2, LOW);
  }
  analogWrite(motor->pwm_pin, abs(motor->speed));
}
,

1. Если не нажаты ни blueAll1, ни greenLeft2, ваш оператор else предотвратит воздействие другой кнопки на двигатели. 2. Под motor_left > 1 вы, вероятно, имеете в виду motor_left > 0. 3. Вывод ШИМ также должен быть параметром spinMotor(). 4. readMotorButton() и spinMotor() выглядят так, как будто они должны быть методами motorState., @Edgar Bonet

1&2 да, ошибка; 3. Я не знал, что у каждого двигателя есть свой собственный вывод ШИМ; ещё один аргумент в пользу группировки состояний двигателя таким образом; 4. Я не большой поклонник создания функций-членов для таких простых вещей. YYMV, хотя, @ratchet freak


2

Здесь есть группа по обзору кода, где вы можете получить более общий обзор кода, если захотите.

Первое, что я заметил, — отсутствие комментариев. Вы написали абзац, объясняющий, что делает код, но это должно быть в самом коде, где вы сможете его прочитать, когда найдёте его через 12 месяцев.

Rachet Freak рассмотрел множество хороших моментов, но я не согласен с тем, что глобальные переменные передаются в функции через указатели. Я бы вместо этого использовал ссылки. Единственная причина для этого — если вы допустите ошибку, ссылки не могут быть равны NULL, поэтому вам не нужно проверять внутри функции, равны ли они NULL. Конечно, этого никогда не произойдёт, вы никогда не передадите NULL-указатель ни одной из этих функций, но по мере роста размера кода вероятность того, что вы это сделаете, будет увеличиваться. И если вы когда-нибудь напишете библиотеку, вероятность того, что кто-то другой передаст NULL-указатель в вашу библиотеку, очень высока, и тогда у вас возникнет «проблема в вашей библиотеке».

Дальше, если значение параметра не будет изменяться в течение жизненного цикла функции, передайте его как ссылку const. Основная причина этого в том, что это объясняет читателю, что значение не изменится в функции. Это простой способ задокументировать ваше намерение без необходимости писать комментарий. Итак,

void spinMotor(int motor_pin1, int motor_pin2, int motorSpeed){...}

стал бы

void spinMotor(const int& motor_pin1, const int& motor_pin2, const int& motorSpeed) {...}

Лично я бы всегда добавлял конструктор к структурам. Так вы будете уверены, что каждое поле изначально имеет правильное значение. (Я думаю, компилятор Arduino всё инициализирует, но не все компиляторы такие добрые.) Кроме того, это позволяет писать более аккуратный код, например,

struct motorState {
   int motor_pin1;
   int motor_pin2;
   int button1;
   int button2;
   int direction;

  // Параметризованный конструктор можно вызвать либо:
  // motorState mState;
  // motorState mState2 = motorState (2, 3, 4, 5, -1);
  motorState (const int& pin1 = 5, const int& pin2 = 6, const int& b1 = 7, const int& b2 = 8, const int& dir = 1)
  : motor_pin1(pin1)
  , motor_pin2(pin2)
  , button1(b1)
  , button2(b2)
  , direction(dir)
{}

}

Наконец, мне всегда говорили никогда не размещать два заявления на одной строке.

int motor_pin1, motor_pin2;

С точки зрения компилятора разницы между вышеизложенным и этим нет.

int motor_pin1; 
int motor_pin2;

Это сделано исключительно для удобства чтения, вы не осознаете, что делаете это, и чтение первого случая заняло больше времени, чем второго, поскольку ваш мозг более настроен на восприятие типа, имени переменной и необязательного значения.

int 
motor_pin1,
motor_pin2;

Вышеуказанное допустимо, но какой тип использовать в motor_pin2? Это просто широко распространённое соглашение, которое упрощает чтение кода, хотя и затрудняет его написание, подобно тому, как здесь нужно делать отступ в 4 пробела. Без отступа текст будет таким же, но читать его будет практически невозможно.

Наконец, если вы копируете и вставляете строки, вероятно, это должна быть функция или массив, или и то, и другое.

,

Не стоит передавать int как ссылку, если только функция не собирается изменить исходное значение. Целые числа слишком малы, и накладные расходы на передачу ссылки превышают накладные расходы на копирование int. Если же вы хотите изменить исходное значение, то указатель может быть более понятен (хотя и повышает вероятность сбоя)., @piojo

Я понимаю, что вы имеете в виду, но это может зависеть от целевой платформы., @Code Gorilla

Очень верное замечание. Хотя я не думаю, что ссылка может иметь меньшие накладные расходы, чем небольшой примитивный тип., @piojo