Проблема со счетчиком энкодеров на руле сегвея.
Я пытаюсь реализовать рулевой механизм для проекта сегвея. Идея состоит в том, чтобы определять наклон руля сегвея с помощью инкрементного поворотного энкодера (omron e6j-cwz 600ppr). Таким образом, когда я поворачиваю планку влево или вправо, сегвей тоже поворачивается. Я использую Teensy 3.2 с IDE Arduino.
Для управления двигателями я использую ШИМ. Пределы импульсов — это калиброванные значения для входного напряжения +/- 100 % (+/- 24 В). Диапазон импульсов составляет от 1,0 до 2,0 мс, при этом 1,5 мс является нулевым значением.
Вот изображение поворотного механизма.
Проблема в том, что движение рукоятки не очень плавное, и иногда после наклона рукоятки и возврата ее в прямое положение счетчик никогда не возвращается к 0, но считается некоторое значение от -6 до 6. а иногда даже от -15 до 15. Таким образом, сегвей пытается повернуть, хотя планка прямая.
Код, который я использую для определения наклона и настройки двигателей:
//Кодер поворота
#define c_TurnEncoderPinA 15
#define c_TurnEncoderPinB 14
#define TurnEncoderIsReversed
volatile bool _TurnEncoderBSet;
volatile int _TurnEncoderTicks = 0;
/** Turn position */
int TurnAngular = 0;
/** Proportional gain */
int kp = 55;
/** voltage to the motor*/
float vol = 0;
float aux;
/** calulated pwm pulse*/
float uy = 0;
/**Left motor control*/
int motorL=9;
/**Right motor control*/
int motorR=10;
//Сопоставление напряжения с ШИМ (контроллер Mc 160)
float p1 = -25.44, p2 = 0.1894, p3 = 146.6, p4 = -0.2874, p5 = -480, p6 = 1500; // ШИМ3
//Дискретные значения времени
const int samplingTime = 1000*10; // 10000 микросекунд = 10 мс
unsigned long previousTime, currentTime, timeDifference;
int estimationTimeCount = 1;
void setup() {
delay(3000);
Serial.begin(115200);
delay(2000);
previousTime = micros();
// Энкодер поворота
pinMode(c_TurnEncoderPinA, INPUT_PULLUP); // устанавливаем контакт A как вход
pinMode(c_TurnEncoderPinB, INPUT_PULLUP); // устанавливаем контакт B как вход
attachInterrupt(digitalPinToInterrupt(c_TurnEncoderPinA), HandleTurnMotorInterruptA, RISING);
/*Initiate Output*/
analogWriteResolution(11);
pinMode(motorR,OUTPUT);
pinMode(motorL,OUTPUT);
analogWrite(motorR, 1500);
analogWrite(motorL, 1500);
Serial.println("Start...");
}
void loop() {
currentTime = micros();
timeDifference = currentTime - previousTime;
if (timeDifference > samplingTime)
{
previousTime = currentTime;
sampleEncoders();
setMotors();
printData();
estimationTimeCount++;
}
}
void sampleEncoders() {
TurnAngular = _TurnEncoderTicks; //обновляем показания кодировщика каждые 10 мс.
}
float turn(){
if(TurnAngular<=6 && TurnAngular>=-6)
{
return 0;
}
else
{
//напряжение рассчитывается с пропорциональным коэффициентом усиления
//умножить на угол энкодера в радианах.
return (kp*TurnAngular*(2*pi/(pprT)));
}
}
void setMotors() {
vol = turn();
// эти две строки отображают рассчитанное напряжение на ШИМ
// в диапазоне от 1000 до 2000.
aux = (vol+0.01143)/13.13;
uy = (int) p1*pow(aux,5) + p2*pow(aux,4) + p3*pow(aux,3) + p4*pow(aux,2) + p5*(aux) + p6;
uy = constrain(uy, 1000, 2000);
// это просто идея, которую я должен был решить, чтобы решить эту проблему.
// если управление в пределах +/-50 импульсов от 1500
// устанавливаем импульс на 1500, а последнее сохраненное значение счетчика на 0.
// каждые 2,5 секунды, если условие выполнено, то также сбрасываем фактическое значение
// счетчик кодера
if (uy < 1550 && uy > 1450)
{
uy = 1500;
TurnAngular = 0;
if ( estimationTimeCount%250 == 0 && _TurnEncoderTicks != 0)
{
_TurnEncoderTicks = 0;
}
}
}
/**
Left wheel (L): forward from 1500 to 1000, backward from 1500 to 2000.
Right wheel (R): forward from 1500 to 2000, backward from 1500 to 1000.
**/
//устанавливаем u для каждого колеса, чтобы сегвей поворачивал.
analogWrite(motorR,uy);
analogWrite(motorL,uy);
}
void printOutput()
{
Serial.print("tout(");
Serial.print(estimationTimeCount);
Serial.print(", :) = [");
Serial.print(TurnAngular);
Serial.print(", ");
Serial.print(_TurnEncoderTicks);
Serial.print(", ");
Serial.print(currentTime*pow(10,-6));
Serial.println("];");
}
void HandleTurnMotorInterruptA()
{
// Тестовый переход; поскольку прерывание срабатывает только при «нарастании», нам не нужно читать вывод A.
_TurnEncoderBSet = digitalRead(c_TurnEncoderPinB); // читаем входной контакт
// и корректируем счетчик +, если A опережает B
#ifdef TurnEncoderIsReversed
_TurnEncoderTicks -= _TurnEncoderBSet ? -1 : +1;
#else
_TurnEncoderTicks += _TurnEncoderBSet ? -1 : +1;
#endif
}
Исходный код счетчика кодировщиков можно найти здесь.
В код я добавил свой собственный, но иногда он дает сбой, я устанавливаю на двигатели нулевое напряжение, если счет находится в пределах +/- 6 отсчетов. Второстепенная проблема заключается в том, что когда наклон шкалы, например, составляет +7, контроль становится слишком агрессивным, поэтому я продолжаю пытаться найти идеальное пропорциональное усиление.
Наконец, я не добавил код для балансировки сегвея, главным образом потому, что это не проблема, а усилие управления просто установлено на 0, пока я пытаюсь решить проблему. Кроме того, это было бы слишком долго.
Какое решение вы могли бы придумать?
Я отредактировал функцию поворота следующим образом:
float turn(){
int8_t TurnVal = 0;
//размермертвой зоны = 6
if(TurnAngular <= deadZoneSize && TurnAngular >= -deadZoneSize)
{
return 0;
}
else
{
if (TurnAngular<0)
{
TurnVal = TurnAngular + deadZoneSize;
}
else
{
TurnVal = TurnAngular - deadZoneSize;
}
Serial.println(TurnVal);
return (kp*TurnVal*(2*pi/(float)(pprT)));
}
Я не могу найти способ исправить ошибку между фактическим прямым положением и измеренным прямым положением (0 отсчетов). Иногда после некоторого перемещения планки фактическое прямое положение оказывается за пределами мертвой зоны, и сигвей все равно хочет повернуть.
Вот иллюстрация.
@BobaFetty91, 👍1
Обсуждение1 ответ
То, что вы придумали самостоятельно, — это почти то, что называется «мертвой зоной», где угол между -6 и 6 игнорируется.
Все, что вам теперь нужно сделать, это не ссылаться на фактический угол при расчете угла поворота, а ссылаться на расстояние от края мертвой зоны:
const uint16_t deadZoneSize = 6;
[...]
float turn(){
unint8_t TurnVal = 0;
if(TurnAngular<=deadZoneSize && TurnAngular>=-deadZoneSize)
{
return 0;
}
else
{
//напряжение рассчитывается с пропорциональным усилением
//умножает угол энкодера в радианах.
if (TurnAngular<0) {
TurnVal = TurnAngular + deadZoneSize)
} else {
TurnVal = TurnAngular - deadZoneSize)
}
return (kp*TurnVal*(2*pi/(pprT)));
}
}
Спасибо, это действительно улучшение, но на самом деле не решает проблему. Теперь вся мертвая зона уходит от прямого положения. Очень небольшой наклон в одну сторону выводит вас из зоны, а относительно большой наклон в другую сторону тоже. Это происходит потому, что каждый раз, когда планка перемещается, а затем возвращается в прямое положение, счетчик (_TurnEncoderTicks) никогда не достигает 0, поэтому ошибка увеличивается с каждым движением планки. Я отвечу на пост иллюстрацией., @BobaFetty91
Что мешает вам установить эту переменную на ноль, когда это необходимо?, @jose can u c
Ну, я думаю, это потому, что у меня нет ссылки, в коде я никогда не могу точно сказать, какое новое число (количество) соответствует прямой позиции., @BobaFetty91
Ах, похоже, вы пропускаете подсчеты, иначе ноль так и остался бы нулем. А что, если увеличить время выборки? Строка uy = (int) p1*pow(aux,5) + p2*pow(aux,4) + p3*pow(aux,3) + p4*pow(aux,2) + p5*(aux) + p6;
занимает целую кучу циклов, чтобы использовать pow()
с типами float
. Есть ли способ упростить этот алгоритм до целочисленной математики?, @jose can u c
Я мог бы увеличить частоту дискретизации, но это испортит весь баланс., @BobaFetty91
Я также мог бы изменить переменную aux на int. Например, int aux = static_cast<int>(vol)
., @BobaFetty91
Определенно не хватает счетчиков, я просто подумал, что это может быть механическая проблема., @BobaFetty91
Попробуйте увеличить время выборки (тем самым уменьшив частоту выборки), чтобы посмотреть, повлияет ли это на количество пропущенных событий?, @jose can u c
- Как настроить выводы ввода-вывода второго квадратурного декодера в Arduino IDE
- C++ против языка Arduino?
- avrdude ser_open() can't set com-state
- Как читать и записывать EEPROM в ESP8266
- Float печатается только 2 десятичных знака после запятой
- устаревшее преобразование из строковой константы в 'char*'
- Запрограммировать ATMega328P и использовать его без платы Arduino.
- Разница между print() и println()
Я бы предложил использовать абсолютный поворотный энкодер или простой потенциометр. Они не пострадают от пропущенных шагов., @Gerben
Я мог бы поискать абсолютный кодер, но мне просто хочется посмотреть, сможет ли он исправить это с помощью кода. Раньше в проекте был потенциометр, но меня попросили использовать энкодер, просто мне повезло., @BobaFetty91
Какую модель Arduino вы используете? Я не думаю, что есть такие, которые поддерживают контакты 14 и 15 для внешних прерываний по фронту «RISING». Не могли бы вы использовать контакт 2 для c_TurnEncoderPinA вместо 14?, @jose can u c
Я использую Teensy 3.2, все цифровые контакты имеют возможность прерывания., @BobaFetty91