(Arduino Uno) ШИМ-выход (и подключенный к нему сервопривод) становится нестабильным, если я использую 1 провод на другом выводе

Ненужный контекст но эй я предполагаю что вам может быть интересно

Привет, я хочу построить ПИД-регулятор температуры с сервоприводом, который нажимает на кремниевую трубу, чтобы регулировать поток жидкости, проходящей через нее. Жидкость представляет собой горячее пиво, которое должно быть очень быстро охлаждено от 100 °C до 25 °C, и оно проходит через теплообменник, где также проходит в другом направлении постоянный поток холодной воды. Чем меньше поток пива, тем холоднее оно становится. Но регулировать его вручную громоздко, и я хотел бы автоматизировать его, потому что я (думаю, что могу).

Обзор системы

Моя система в основном состоит из Arduino Uno, серводвигателя (ШИМ на выводе 11), датчика температуры BS18B20 1Wire (на выводе 2) и нескольких кнопок. В "ручном" режиме я могу выбрать положение сервопривода с помощью кнопок, и он очень стабилен.

У меня также есть зуммер на выводе 13, и я добавлю экран I2C, когда ядро будет работать.

Моя проблема

Как только я начинаю запрашивать температуру через регулярные промежутки времени (например, каждые 200 мс), сервопривод все время мерцает, что скучно, выглядит плохо и, вероятно, сократит его продолжительность жизни. Я думаю, что каждый раз, когда я запрашиваю температуру, что-то происходит с ШИМ, например, короткий сброс или что-то в этом роде, и сервопривод реагирует, а затем возвращается в исходное положение.

Единственное питание, которое я тестировал до сих пор, - это USB-кабель от компьютера. На случай, если вам интересно, код здесь:

#include <AceButton.h> // https://github.com/bxparks/AceButton
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Servo.h>

Servo myservo; // создать объект servo для управления сервоприводом
// двенадцать сервообъектов могут быть созданы на большинстве плат

#define ONE_WIRE_BUS 2 // контакт для термометра

using namespace ace_button;

// контакт, прикрепленный к кнопке.
const int BUTTON_SET_PIN = 12;
const int BUTTON_MORE_PIN = 4;
const int BUTTON_LESS_PIN = 0;

const int BUTTON_MODE2_PIN = 5;
const int BUTTON_MODE3_PIN = 7;

const int LED_PIN = LED_BUILTIN;
const int SERVO_PIN = 11;

const int MODE_THERMOMETER = 1;
const int MODE_REGULATOR = 2;
const int MODE_MANUAL = 3;
int mode;

// Светодиодные состояния. Некоторые микроконтроллеры подключают свой встроенный светодиод наоборот.
const int LED_ON = HIGH;
const int LED_OFF = LOW;

// Обе кнопки автоматически используют системный ButtonConfig по умолчанию. Альтернативой
// является вызов метода AceButton::init() в setup() ниже.
AceButton buttonSet(BUTTON_SET_PIN);
AceButton buttonMore(BUTTON_MORE_PIN);
AceButton buttonLess(BUTTON_LESS_PIN);

ButtonConfig modeConfig;
AceButton buttonMode2(&modeConfig);
AceButton buttonMode3(&modeConfig);

// Настройка экземпляра OneWire для связи с любыми устройствами OneWire
// (не только Maxim / Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
// Передайте нашу ссылку OneWire на температуру Далласа.
DallasTemperature sensors(&oneWire);

// переменные для настройки
float value = 50;
unsigned long tuneStart;
float max = 175;
float min = 10;

void handleModeEvent(AceButton *, uint8_t, uint8_t);

void handleModeEvent(AceButton *button, uint8_t eventType, uint8_t /* buttonState */) {
  switch (eventType) {
  case AceButton::kEventPressed:
    if (button->getPin() == BUTTON_MODE2_PIN) {
      mode = MODE_REGULATOR;
      printMode();
    }

    if (button->getPin() == BUTTON_MODE3_PIN) {
      mode = MODE_MANUAL;
      printMode();
    }
    break;
  case AceButton::kEventReleased:
    if (button->getPin() == BUTTON_MODE2_PIN) {
      mode = MODE_THERMOMETER;
      printMode();
    }
    if (button->getPin() == BUTTON_MODE3_PIN) {
      mode = MODE_REGULATOR;
      printMode();
    }
    break;
  }
}

void printMode()
{
  Serial.print("Mode ");
  Serial.println(mode);
}

void handleButtonEvent(AceButton *, uint8_t, uint8_t);

void setup() {
  Serial.begin(9600);
  while (!Serial); // Подождите, пока Serial не будет готов - Leonardo/ Micro
  Serial.println(F("Test 3 boutons - la suite en 115200 bps"));
  delay(300);

  // servo
  myservo.attach(11);

  // thermal probe
  sensors.begin();
  sensors.setWaitForConversion(false);
  sensors.setResolution(10);

  Serial.begin(115200);
  while (!Serial); // Подождите, пока Serial не будет готов - Leonardo/ Micro
  Serial.println(F("setup(): begin"));

  // Инициализировать встроенный светодиод в качестве выхода.
  pinMode(LED_PIN, OUTPUT);
  pinMode(SERVO_PIN, OUTPUT);

  // Buttons use the built-in pull up register.
  pinMode(BUTTON_SET_PIN, INPUT_PULLUP);
  pinMode(BUTTON_MORE_PIN, INPUT_PULLUP);
  pinMode(BUTTON_LESS_PIN, INPUT_PULLUP);

  pinMode(BUTTON_MODE2_PIN, INPUT_PULLUP);
  pinMode(BUTTON_MODE3_PIN, INPUT_PULLUP);

  buttonMode2.init(BUTTON_MODE2_PIN);
  buttonMode3.init(BUTTON_MODE3_PIN);

  // Настройте ButtonConfig с помощью обработчика событий и включите все более высокие
  // события уровня.
  ButtonConfig *buttonConfig = ButtonConfig::getSystemButtonConfig();
  buttonConfig->setEventHandler(handleButtonEvent);
  buttonConfig->setFeature(ButtonConfig::kFeatureClick);
  buttonConfig->setFeature(ButtonConfig::kFeatureLongPress);
  buttonConfig->setFeature(ButtonConfig::kFeatureRepeatPress);

  // Configs для кнопок tune-up и tune-down. Нужно RepeatPress вместо
  // ЛонгПресс.
  modeConfig.setEventHandler(handleModeEvent);
  modeConfig.setFeature(ButtonConfig::kFeatureClick);
  // Эти подавления на самом деле не нужны, но чище.
  modeConfig.setFeature(ButtonConfig::kFeatureSuppressAfterClick);

  // Проверьте, была ли нажата кнопка при загрузке
  if (buttonMode3.isPressedRaw()) {
    mode = MODE_MANUAL;
    printMode();
  } else if (buttonMode2.isPressedRaw()) {
    mode = MODE_THERMOMETER;
    printMode();
  } else {
    mode = MODE_REGULATOR;
    printMode();
  }
  Serial.println(F("setup(): ready"));
}

long timer_200 = 0;
long timer_1000 = 0;

void loop() {
  // Должен вызываться каждые 4-5 мс или быстрее, для времени деблокирования по умолчанию ~ 20 мс.
  buttonSet.check();
  buttonMore.check();
  buttonLess.check();
  buttonMode2.check();
  buttonMode3.check();

  // каждые 200 мс
  long now = millis();
  if (now > timer_200 + 200) {
    timer_200 = now; // для повторного запуска таймера
    if (mode == MODE_MANUAL) {
      myservo.write(value);
    }
  }

  // каждый 1s
  if (now > timer_1000 + 1000) {
    timer_1000 = now; // для повторного запуска таймера
    // получить температуру, запрошенную в последний раз
    Serial.print("Temperature for Device is: ");
    Serial.println(sensors.getTempCByIndex(0));
    // и запросить новый
    sensors.requestTemperatures(); // Отправить команду для получения температуры
    if (mode == MODE_THERMOMETER) {}
  }
}

// Обработчик событий для обеих кнопок.
void handleButtonEvent(AceButton *button, uint8_t eventType, uint8_t buttonState) {
  switch (eventType) {

  case AceButton::kEventClicked:
    if (button->getPin() == BUTTON_MORE_PIN) {
      value = value * 1.01;
      if (value > max) {
        value = max;
      }

      Serial.println(value);
    }

    if (button->getPin() == BUTTON_LESS_PIN) {
      value = value * .99;
      if (value < min) {
        value = min;
      }
      Serial.println(value);
    }

    if (button->getPin() == BUTTON_SET_PIN) {
      Serial.println(F("set"));
    }
    break;

  case AceButton::kEventLongPressed:
    if (button->getPin() == BUTTON_SET_PIN) {
      Serial.println(F("set long press"));
    }

    if (button->getPin() == BUTTON_MORE_PIN || button->getPin() == BUTTON_LESS_PIN) {
      tuneStart = millis();
    }
    break;
  case AceButton::kEventRepeatPressed:

    if (button->getPin() == BUTTON_MORE_PIN) {
      tuneUp();
    }

    if (button->getPin() == BUTTON_LESS_PIN) {
      tuneDown();
    }
    break;
  }
}

void tuneUp() {
  unsigned long duration = millis() - tuneStart;
  float ratio = 1 + (0.00001f * 4 * duration);
  value = value * ratio;
  if (value > max) {
    value = max;
  }
  Serial.println(value);
}

void tuneDown() {
  unsigned long duration = millis() - tuneStart;
  float ratio = 1 - (0.00001f * 4 * duration);
  value = value * ratio;
  if (value < min) {
    value = min;
  }
  Serial.println(value);
}

Это все еще прототип.

Что может быть причиной этого и как я могу это предотвратить ?

, 👍3

Обсуждение

Что вы используете для своего контроллера? Некоторые устройства не имеют аппаратного ШИМ, и даже некоторые из них используют "битный стук", если вы не выбрали правильный вывод., @jwh20

Извините, я забыл главный компонент - arduino Uno. Я отредактировал свой вопрос., @Guillaume Deshors


1 ответ


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

1

Я подозреваю, что 1Wire libary отключает (и включает) прерывания, когда считает нужным. Это вызовет "заминки" сервопривода, потому что библиотека сервоприводов использует прерывания.

Если я прав, то лучше всего использовать сервобиблиотеку, которая не использует прерывания. Я всегда использую аппаратную библиотеку ШИМ для управления сервоприводами, поэтому у меня нет рекомендаций по библиотеке сервоприводов, но, возможно, у кого-то еще есть.

Кроме того: вы делаете Serial.begin() дважды, и переменные, которые вы используете для синхронизации (используя millis()), должны быть беззнаковыми, а не длинными.

,

Спасибо, это очень интересно! Мне действительно не нужна библиотека сервоприводов, вывод обычного ШИМ, скорее всего, будет работать без проблем для моего приложения. Я попробую сделать это завтра и дам вам знать., @Guillaume Deshors

Это проблема использовать Serial.begin дважды? Я делаю это специально, потому что мне нравится знать, что в данный момент загружено на данную плату arduino, поэтому я начинаю с сообщения в 9600 бит/с с именем скетча и скоростью следующих сообщений, а затем повторно запускаю его с указанной скоростью. Кажется, это работает :), @Guillaume Deshors

Это не обязательно проблема, но зачем вам это делать, если принимающее приложение, по-видимому, может справиться с более высоким коэффициентом бодрости в любом случае? Но если вы делаете это специально, то все в порядке., @ocrdu

Идея состояла в том, чтобы всегда начинать с 9600 в качестве стандартного скетча, который я загружаю, а затем свободно выбирать скорость связи. В противном случае я могу позже не вспомнить, какую скорость я должен установить в консоли, чтобы общаться с ней. Это довольно свежая идея, которая пришла мне в голову, и я не знаю, будет ли она настолько полезной., @Guillaume Deshors

Скорее всего, вы были правы. Я сделал пользовательский код для вывода нужного мне сигнала, который * на самом деле не является ШИМ*, см. https://forum.arduino.cc/index.php?topic=530091.0 и это решило проблему. Большое спасибо !, @Guillaume Deshors

Мы вам очень рады. Однако сервосигнал * - это * ШИМ, обычно выровненный по левому краю и с фиксированной частотой, но давайте не будем сейчас это обсуждать., @ocrdu