Відповісти

Введіть повідомлення і натисніть Надіслати
Параметри

Назад

Огляд теми (нові повідомленні вгорі)

3611621
2026-05-07 15:20:12
nickjust пише:

Я рахую,тре всьо спробувати...та вчитися!
І краще розмовляти з ШІ ніж з їбаньками з форумів,таких як Радіокот (правда там є і нормальні)
Мені ще подобається Чорний форум - також норм допомогають.
І ще замічаю ,що інет став нууу дуже грошовий ....усі кинулися заробляти...за прошивку 2017 року ,яка нещо давно вільно була на оффсайті Acer хотят гроші....гроші....гроші....


шановний nickjust ви знаєте когось хто може виконати роботу за гроші? ключове це виконати, а не консультувати або подивитися скетч. Я в Києві. Дякую

3611621
2026-05-07 15:15:00

На сьогодні єнкодер видає 10 імпульсів на оборот. Стоїть резистор 10кОм + конденсатори 0.1 мкФ на A (A → GND)
Є скетч для керування мотором та підтримкою швидкості Але двигун не підтримує швидкість. Постійно крутиться з максимальною швидкістю

/*
*  Мотор + энкодер 10 PPR
*  Стабильный расчёт RPM (среднее за 1 секунду + сглаживание)
*  Arduino Uno
*  Энкодер: A→2, B→4, Vcc→5V, GND→GND
*  Мотор (L298N): IN1→10, IN2→11, ENA→9
*/

// ====================== ПИНЫ ======================
#define ENC_A   2
#define ENC_B   4
#define MOTOR_1 10
#define MOTOR_2 11
#define MOTOR_PWM 9

// ====================== КОНСТАНТЫ ЭНКОДЕРА ======================
const int PULSES_PER_REV = 10;        // импульсов за один оборот

// ====================== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ======================
volatile long totalPulses = 0;        // суммарное количество импульсов
volatile int lastEncA = HIGH;         // предыдущее состояние канала A
volatile unsigned long lastIntTime_us = 0;
const unsigned long DEBOUNCE_us = 5000;   // 5 мс антидребезг

long revolutions = 0;                 // totalPulses / 10 (целые обороты)

// ---- Переменные для RPM (стабильный расчёт) ----
unsigned long lastRpmTime_ms = 0;
long prevTotalPulses = 0;
float smoothRPM = 0.0;
const float RPM_SMOOTH = 0.1;         // сильное сглаживание (0..1)
const unsigned long RPM_INTERVAL_ms = 1000;   // интервал измерения 1 секунда

// ---- Управление мотором ----
int targetRevolutions = 0;            // 0 – бесконечно, иначе остановить по достижении
bool motorEnabled = false;
int currentSpeed = 200;               // текущая скорость (0-255)
bool rpmVisible = true;               // показывать RPM в логе

// ====================== SETUP ======================
void setup() {
  Serial.begin(9600);
 
  // Энкодер
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(ENC_A), encoderISR, CHANGE);
 
/*
*  Мотор + энкодер 10 PPR + П-регулятор скорости
*  Поддерживает заданные обороты в минуту (RPM)
*/

#define ENC_A   2
#define ENC_B   4
#define MOTOR_1 10
#define MOTOR_2 11
#define MOTOR_PWM 9

const int PULSES_PER_REV = 10;

volatile long totalPulses = 0;
volatile int lastEncA = HIGH;
volatile unsigned long lastIntTime_us = 0;
const unsigned long DEBOUNCE_us = 5000;

long revolutions = 0;
unsigned long lastRpmTime_ms = 0;
long prevTotalPulses = 0;
float smoothRPM = 0.0;
const float RPM_SMOOTH = 0.1;
const unsigned long RPM_INTERVAL_ms = 1000;

bool motorEnabled = false;
int currentSpeed = 0;          // текущая ШИМ (0-255)
float targetRPM = 0.0;         // целевая скорость (0 = выкл)
float Kp = 2.0;                // коэффициент П-регулятора (настраивается)
const int minPWM = 0;
const int maxPWM = 255;

void setup() {
  Serial.begin(9600);
  pinMode(ENC_A, INPUT_PULLUP);
  pinMode(ENC_B, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(ENC_A), encoderISR, CHANGE);
  pinMode(MOTOR_1, OUTPUT);
  pinMode(MOTOR_2, OUTPUT);
  pinMode(MOTOR_PWM, OUTPUT);
  stopMotor();
 
  Serial.println(F("=== ПИД-регулятор скорости (П-регулятор) ==="));
  Serial.println(F("Команды:"));
  Serial.println(F("  r <об/мин>   - установить целевую скорость, например r100"));
  Serial.println(F("  s            - стоп"));
  Serial.println(F("  k <коэфф>    - изменить Kp (по умолч. 2.0)"));
  Serial.println(F("  status       - показать параметры"));
  Serial.println();
}

void loop() {
  // Обновление счётчика оборотов
  long newRevs = totalPulses / PULSES_PER_REV;
  if (newRevs != revolutions) revolutions = newRevs;
 
  // Расчёт RPM (раз в секунду)
  unsigned long now = millis();
  if (now - lastRpmTime_ms >= RPM_INTERVAL_ms) {
    long delta = totalPulses - prevTotalPulses;
    float revs = (float)delta / PULSES_PER_REV;
    float sec = (now - lastRpmTime_ms) / 1000.0;
    float instant = (revs / sec) * 60.0;
    if (instant < 0) instant = -instant;
    if (smoothRPM < 0.1) smoothRPM = instant;
    else smoothRPM = RPM_SMOOTH * instant + (1 - RPM_SMOOTH) * smoothRPM;
   
    prevTotalPulses = totalPulses;
    lastRpmTime_ms = now;
  }
 
  // П-регулятор: подстраиваем ШИМ, чтобы smoothRPM стремился к targetRPM
  if (targetRPM > 0 && motorEnabled) {
    float error = targetRPM - smoothRPM;
    int newPWM = constrain(currentSpeed + Kp * error, minPWM, maxPWM);
    if (newPWM != currentSpeed) {
      // Сохраняем направление (определяем по текущим выходам)
      bool forward = (digitalRead(MOTOR_1) == HIGH && digitalRead(MOTOR_2) == LOW);
      setMotor(newPWM, forward);
    }
  }
 
  // Вывод состояния (каждые 200 мс)
  static unsigned long lastPrint = 0;
  if (now - lastPrint >= 200) {
    Serial.print(F("Об.: ")); Serial.print(revolutions);
    Serial.print(F(" имп.: ")); Serial.print(totalPulses % PULSES_PER_REV);
    Serial.print(F(" RPM: ")); Serial.print(smoothRPM, 1);
    if (targetRPM > 0) {
      Serial.print(F(" / целевая: ")); Serial.print(targetRPM, 1);
      Serial.print(F(" ШИМ: ")); Serial.print(currentSpeed);
    }
    Serial.println();
    lastPrint = now;
  }
 
  // Обработка команд
  if (Serial.available()) {
    String cmd = Serial.readStringUntil('n');
    cmd.trim();
    parseCommand(cmd);
  }
 
  delay(1);
}

void encoderISR() {
  unsigned long now = micros();
  if (now - lastIntTime_us < DEBOUNCE_us) return;
  lastIntTime_us = now;
  int a = digitalRead(ENC_A);
  int b = digitalRead(ENC_B);
  if (a != lastEncA) {
    if (a == b) totalPulses++;
    else totalPulses--;
    lastEncA = a;
  }
}

void setMotor(int speed, bool forward) {
  speed = constrain(speed, 0, 255);
  currentSpeed = speed;
  if (speed == 0) { stopMotor(); return; }
  digitalWrite(MOTOR_1, forward ? HIGH : LOW);
  digitalWrite(MOTOR_2, forward ? LOW : HIGH);
  analogWrite(MOTOR_PWM, speed);
  motorEnabled = true;
}

void stopMotor() {
  digitalWrite(MOTOR_1, LOW);
  digitalWrite(MOTOR_2, LOW);
  analogWrite(MOTOR_PWM, 0);
  motorEnabled = false;
  targetRPM = 0;
  currentSpeed = 0;
}

void parseCommand(String cmd) {
  if (cmd.length() == 0) return;
  char first = cmd.charAt(0);
 
  if (first == 'r') {
    float rpm = cmd.substring(1).toFloat();
    if (rpm > 0) {
      targetRPM = rpm;
      // Запускаем мотор с текущим ШИМ (можно начать с любого, например 100)
      if (!motorEnabled) setMotor(100, true);
      Serial.print(F("Целевая скорость установлена: "));
      Serial.print(targetRPM);
      Serial.println(F(" об/мин"));
    } else {
      targetRPM = 0;
      stopMotor();
      Serial.println(F("Регулятор отключён"));
    }
  }
  else if (first == 's') {
    stopMotor();
    Serial.println(F("Стоп"));
  }
  else if (first == 'k') {
    float newK = cmd.substring(1).toFloat();
    if (newK >= 0) {
      Kp = newK;
      Serial.print(F("Kp = "));
      Serial.println(Kp);
    }
  }
  else if (cmd.equalsIgnoreCase("status")) {
    Serial.print(F("Оборотов: ")); Serial.print(revolutions);
    Serial.print(F(", RPM: ")); Serial.print(smoothRPM, 1);
    Serial.print(F(", целевая: ")); Serial.print(targetRPM, 1);
    Serial.print(F(", ШИМ: ")); Serial.print(currentSpeed);
    Serial.print(F(", Kp: ")); Serial.println(Kp);
  }
  else {
    Serial.println(F("Команды: r<об/мин>, s, k<коэфф>, status"));
  }
}


В результаті

Целевая скорость установлена: 100.00 об/мин
Об.: 0 имп.: 1 RPM: 0.0 / целевая: 100.0 ШИМ: 255
Об.: 0 имп.: 1 RPM: 0.0 / целевая: 100.0 ШИМ: 255
Об.: 0 имп.: 1 RPM: 0.0 / целевая: 100.0 ШИМ: 255

nickjust
2026-04-29 10:29:12

https://forum.gsmhosting.com/vbb/index.php

jokeer
2026-04-29 08:21:39

Чорний форум - wtf??

nickjust
2026-04-29 07:24:59

Я рахую,тре всьо спробувати...та вчитися!
І краще розмовляти з ШІ ніж з їбаньками з форумів,таких як Радіокот (правда там є і нормальні)
Мені ще подобається Чорний форум - також норм допомогають.
І ще замічаю ,що інет став нууу дуже грошовий ....усі кинулися заробляти...за прошивку 2017 року ,яка нещо давно вільно була на оффсайті Acer хотят гроші....гроші....гроші....

dimich
2026-04-28 21:54:41
nickjust пише:

А так?

Так уже краще. ChatGPT вчиться? smile

nickjust пише:

додати кондюк 0.1 мкФ на землю (A → GND)

З підтяжкою 10 кОм такий конденсатор утворюватиме ФНЧ з частотою зрізу 160 Гц. А з вбудованою - десь 60-70 Гц. На 300 об/хв може ще не впливатиме, але якщо більше - вже не годиться.
Щоб позбутись "брудних" фронтів (якщо вони там дійсно є і заважають), можна просто рахувати по обом фронтам фази A скільки раз фаза B змінила значення на протилежне від попереднього.

nickjust пише:

либа от Paul Stoffregen
нюанс-либа рахує x4-10 P/R → 40 імпульсів/оберт

То вже якесь збочення.

jokeR
2026-04-28 20:58:34

нюанс-либа рахує x4-10 P/R → 40 імпульсів/оберт

Ну так на то і енкодер.
Взагалі для вимірювання швидкості та ліба (разом з енкодером) - це мікроскоп для забивання шурупів wink Можна взяти timer-counter і просто рахувати імпульси.

nickjust
2026-04-28 20:16:31

А так?

const byte pinA = 2;
volatile uint16_t pulses = 0;

void isrA() {
  pulses++;  // ISR короткий — это хорошо
}

void setup() {
  Serial.begin(9600);
  pinMode(pinA, INPUT); // или INPUT_PULLUP при необходимости
  attachInterrupt(digitalPinToInterrupt(pinA), isrA, RISING);
}

void loop() {
  static uint16_t last = 0;

  uint16_t snapshot;

  noInterrupts();        // критическая секция
  snapshot = pulses;     // атомарный снимок
  interrupts();

  if (snapshot != last) {
    Serial.print("Pulses: ");
    Serial.println(snapshot);
    last = snapshot;
  }
}

і знову jokeR правий,можна простіше



відкриваємо Arduino IDE
Sketch → Include Library → Manage Libraries
В пошуку пишемо:
Encoder
либа от Paul Stoffregen

нюанс-либа рахує x4-10 P/R → 40 імпульсів/оберт

dimich
2026-04-28 20:07:03
jokeR пише:

Тобто варто взяти 32 розрядний проц - і можна за атомарність не паритись? wink

Якщо атомарність доступа до слова цієї розрядності гарантується апаратно, то для одиничного запису чи читання - так, можна не паритись. Лише впевнитись, що змінна розміщується з необхідним вирівнюванням (зазвичай це також гарантується). Але на деяких архітектурах може довестись паритись із реордерінгом інструкцій та memory барʼєрами, а іноді і з інвалідацією кеша wink

А при read/modify/write все одно потрібна синхронізація.

jokeR
2026-04-28 19:42:00

wink
Тобто варто взяти 32 розрядний проц - і можна за атомарність не паритись? wink

Тут 2 речі незрозуміло.. Чому 9600 а не 115200. І чому не взяти готову лібу, навіщо вайбкодити те що вже зроблено і бездоганно працює? Чи це челлендж такий? wink

dimich
2026-04-28 19:19:30

В цьому скетчі вже баги.
1. 16-бітна змінна pulses змінюється в ISR, а читається неатомарно.
2.

  if (pulses != last) {
...
    Serial.println(pulses);
    last = pulses;

Тут race condition. При всіх трьох зчитуваннях pulses може мати три різні значення.

nickjust
2026-04-28 18:08:44

Починай з каналу А
відкрий Serial Monitor (9600),помалу зроби 1 оберт
повинен побачити

Pulses: 10

якщо 10 - гуд,якщо інше - херовий контакт,живлення енкодера,довгі дроти,підтяжка

включити INPUT_PULLUP   , додати кондюк 0.1 мкФ на землю (A → GND)
У Omron E6B2-CWZ6C: вихід транзисторний -«1» формується підтяжкой

фронт може бути «брудний» → берем тілько RISING

const byte pinA = 2;          // обязательно пин с прерыванием (UNO: 2 или 3)
volatile unsigned int pulses = 0;

void isrA() {
  pulses++;                  // считаем только фронт
}

void setup() {
  Serial.begin(9600);

  pinMode(pinA, INPUT);      // если есть внешняя подтяжка
  // pinMode(pinA, INPUT_PULLUP); // включи это, если сомневаешься

  attachInterrupt(digitalPinToInterrupt(pinA), isrA, RISING);
 //attachInterrupt(digitalPinToInterrupt(pinA), isrA, CHANGE); // 20 имп на оборот

  Serial.println("Start test");
}

void loop() {
  static unsigned int last = 0;

  if (pulses != last) {
    Serial.print("Pulses: ");
    Serial.println(pulses);
    last = pulses;
  }
}
dimich
2026-04-28 14:32:21
3611621 пише:

Можливо, проблема саме в цьому енкодері?

Можливо. Але дуже малоймовірно. Також можливо, що проблема десь у зʼєднаннях, платі чи контролері. Це теж малоймовірно.
Майже впевнений, що проблема в коді, а коду ми не бачили.

Напишіть програму, що пише стан входів у Serial. Повільно обертайте енкодер і перевірте, чи бачите очікуваний квадратурний сигнал.

Якщо все ок, напишіть мінімальну програму, яка обчислює швидкість обертів і видає результат в Serial. Якщо працює не так як очікується, то показуйте код.

3611621 пише:

Мені потрібне точне регулювання обертів

Точне - це яке в цифрах?

При інтервалі в 1 секунду з PPR 10 точність виміру не перевищуватиме 5-6 об/хв. При 30 об/хв це 20%.
А точність регулювання, навіть при ідеально підібраних коефіцієнтах PID, залежатиме як від точності виміру, так і від властивостей керованої системи: потужності двигуна, характеристики навантаження.
Ви можете лише визначити, що відхилення поточної швидкості від цільової перевищує заданий поріг.

jokeer
2026-04-28 13:21:59

До речі pid регулятор не дасть вам точного регулювання. Дасть приблизне. Якщо треба справді точно - беріть bldc.

jokeer
2026-04-28 13:19:04

Візьміть осцилограф і подивіться що він там вертає.
Або візьміть абсолютну будь яку демку від будь якої ліби для енкодера і покрутіть. Демка повинна бути робоча.

Підвал форуму