Контроллер для кордовых электричек
ATMEGA328p и MPU6050 отдельными камнями,
стоят на Али дороже чем готовые платы Ардуино Мини и GY521
Верно, поэтому использование ардуино подобных платок с периферией считаю экономически обоснованными и наиболее простым в реализации руками пионерофф.
а плюс еще обвес и платы где то заказать, одну две штуки делать никто не будет.
JLCPCB изготовит и отправит от пяти штук в двухстороннем исполнении.
обвес стоит совсем копейки, рублей на 15 в том виде как на картинке.
Да все понятно, это так, от нечего делать )
Решил изменить немного логику переменных параметров.
Те параметры которые определяют обороты двигателя сделаю в процентах от максимальных.
Временные параметры сделаю минуты и десятки секунд.
Тогда не придется устраивать “лишние трудности” в виде разбиения на тысячи, стони, десятки и изменение каждого числа.
Если такой вариант не устроит, напишите.
Сегодня, позднее, продемонстрирую первую версию, осталось немного “причесать”.
Вот как раз вопрос стоит ухода от процентов, милисекунд и прочего, и привидение в читаемый вид. Так как не понято 10 000 оборотов, это сколько в процентах… . Если есть возможность использовать точные велечины, то лучше оставить их. Т. Е. Если я знаю, что мой винт работает в диапазоне от 10 до 12 тысяч, то на таймере выставляю 11 и дальше либо добавляю, либо убираю. И по времени так же, на полет у меня 6минут 15 секунд, их и выставляю, потом меняю в нужную сторону. Выщитывать проценты и милисекунды , ну очень не удобно.
Виталий, у нас нет данных о реальных оборотах двигателя.
На данном устройстве отсутствует датчик оборотов.
Может быть, потом смогу придумать, как его реализовать, но пока не придумал.
Моторы разные, количество витков тоже, можно измерить частоту фазы на выходе регулятора,
но это даст не много. Потому как, на разных моторах будут получаться разные обороты,
даже если у них одинаковый кВ
Пока речь идет только о длительности сигнала PWM либо о процентах от длительности.
Сейчас в скетче за базу взяты 2200 мкс для максимальных оборотов и 800 мкс для минимальных,
от этих значений калибруется регулятор.
Вот проценты от тех самых 2200 и будут меняться
Примерно обороты можно посчитать
3S 12В 1200кВ 80%
1200 х 12 х 0.8 = 10560, но это число совсем не точное, батарейки разные, реальное напряжение тоже
А дальше, в руках, дождались запуска, измерили обороты тахометром получилось например 11000
внесли поправку 0,9 и опять измеряем.
Но все равно должно быть меньше 1, потому как при увеличении тангажа таймер будет увеличивать обороты до максимальных
Пока речь идет только о длительности сигнала PWM либо о процентах от длительности.
Сейчас в скетче за базу взяты 2200 мкс для максимальных оборотов и 800 мкс для минимальных,
от этих значений калибруется регулятор.
Вот проценты от тех самых 2200 и будут меняться
Стандарт сигнала PPM от 1000 до 2000 мкс. от этого и надо плясать. Не все регуляторы понимают расширенный диапазон. В ардуиновской библиотеке “servo” диапазон ещё шире, по этому при подключении ESC вместо сервомашинки иногда возникает вопрос: “А почему не жужжит”. Об этом я писал в 16 сообщении этой темы.
Это не сильно принципиально, границы максимум-минимум задаются в скетче Таймера
Собственно говоря, отсюда и возникло желание уйти от абсолютных значений в сторону относительных )
“Зеленый змей” победил желание поработать.
в 16 сообщении этой темы.
16 сообщение было в те времена когда мой аккаунт был забанен
вопрос остался открытым, против относительных значений возражений нет ?
О том и речь, высчитывать параметр ниже 1, не совсем удобно. Как и проценты. Да и сами обороты понятно что относительны, просто в этих оборотах шкала регулировки получается удобнее. Например, я выставил 11000, померил тахометром , 10500, я взял в программаторе, еще 500 добавил. Мне не надо высчитывать 0.80 или 0.85, или 0.90. Я докинул еще 500 и плюс/минус попал в свой диапазон.
Например, я выставил 11000
так сейчас и нет никаких 11000, есть диапазон 800-2200
в любом случае, все что не длительность PWM будет оставаться попугаями,
пока не будет датчика оборотов
может стоит добавить в постоянные кВ мотора и количество банок АК и тогда высчитывать обороты
и от них плясать, но вопрос - а оно надо ?
тут же опять подводные камни, на модели в руках будут одни максимальные обороты,
в горизонтальном полете они будут совсем другими, а на горке третьи
На двс так же, в полете обороты разные, вот гироскоп и должен их выравнивать. Поэтому и закладывается порог регулирования вверх и вниз по оборотам.
И не всегда надо выкрутить мотор в максимальные его обороты, мотор может выдать и 13000, а надо 12000, и это максимум для гироскопа.
я не могу оперировать абсолютными значениями оборотов, мне их взять просто не где.
а с точки зрения программы, я могу нарисовать любые цифры.
но в любом случае,
10 процентов длительности PWM не будут соответствовать 10 процентам оборотов двигателя.
Зеленый змий победил, что то последнее время слаб я стал, но мы не привыкли отступать,
по крайней мере нашел интересное решение тахометра для таймера
Самодельный цифровой тахометр для измерения оборотов бесколлекторных моторов
Так что, вполне можно реализовать регулировку оборотов ) и тогда гироскоп может оказаться лишней деталью.
Ставим те самые 12000 оборотов ардуинка их старается удержать, обороты уменьшаются,
PWM увеличивается и наоборот.
Примерно так я и предлогал, когда писал, что для контроля оборотов брать сигнал с одной из фаз на выходе регулятора.
Ещё один вариант реализации тахометра , но с датчиком Холла.
alexgyver.ru/tahometer/
ещё из разряда “хотелок” добавить возможность управления 2-4 моторами и получим таймер для моделей-копий.
На копиях нет смысла в таймере, там отдельный пульт у них, на все демонстрации. Управление по проводам, газом, сбросом, шасси и т. Д., они глушат мотор когда им надо и управляют всем чем только можно.
Один таймер на 2 мотора, если только захотите сделать, двухмоторную пилотажку, в Америке народ так развлекается.
Ок, сегодня к вечеру постараюсь доковырять программатор и надо будет решать вопрос с дальнейшим развитием )
если только захотите сделать, двухмоторную пилотажку
Есть такая задумка… но пока только задумка.
процесс идет, только медленнее чем хотелось бы.
уже прикрутил разложение целочисленных значений на разряды и прикрутил изменение значащих цифр.
но что то с этим надо делать, целочисленные в тысячах, время - минуты секунды, надо что то придумать, как стандартизовать.
для обработки значащих значений, разные алгоритмы, так еще надо учитывать, какой параметр, каким алгоритмом менять (((
вот кусок кода для разложения тысяч на четыре числа
data[i][0] = Stack[i]/1000;
data[i][1] = (Stack[i]-data[i][0]*1000)/100;
data[i][2] = (Stack[i]-data[i][0]*1000-data[i][1]*100)/10;
data[i][3] = (Stack[i]-data[i][0]*1000-data[i][1]*100-data[i][2]*10);
а потом все опять в кучу при записи 😄
ладно, поплакался … полегчало … продолжу )
завтра на полетушки, к вечеру попробую “причесать” но без обмена,
пока не понимаю, как его реализовать, есть одна задумка, но … дъявол в деталях.
я не могу программатор сделать ведущим i2c, а будучи ведомым он не может давать команды таймеру,
думаю передавит таймеру байт команды, типа готов принимать/передавать,
таймер приняв байт - смотрит и либо отправляет пакет с параметрами либо принимает,
на макетке между двумя UNO вроде работает, а дальше… посмотрим
Поразрядную корректировку целочисленных значений до 9999 сделал.
И сделал изменение булевых параметров False/True.
Следующим аккордом будут временные параметры, минуты/десятки секунд,
Точность до секунд делать не вижу никакого смысла.
т.е. параметр в программаторе будет выглядеть, ММ:С0,
сделаю возможность установки от 00:00 до 30:00, думаю этого будет достаточно
Вроде допилил, только без обмена с таймером.
Если есть связка Uno+LCD+Key, можно потестить.
Programmer.zip
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
#define serialPort
#define BTN_UP 1
#define BTN_DOWN 2
#define BTN_LEFT 3
#define BTN_RIGHT 4
#define BTN_SELECT 5
#define BTN_NONE 10
// стоковые параметры для таймера
#define Motor_ESC_Max_THR 2200 // максимальные обороты двигателя
#define Motor_ESC_Normall_THR 2000 // номинальные (полетные) обороты
#define Motor_ESC_Landing_THR 1400 // посадочный режим
#define Motor_ESC_Min_THR 800 // двигатель стоп
#define Time_Of_Calibrate 2 // время между Макс газ и мин газ при необходимости калибровке регулятора
#define Takeoff 120 // время отведенное на взлет и уборку шасси, в течении этого времени процедура старта может быть отменена (т.е. время до уборки шасси) - 10 секунд
#define GearUp 40 // время время после старта, начало уборки шасси - 5 секунд
#define InFly 330 // время полета в секундах с момента завершения процедуры старта - сейчас стоит 60 секунд
#define Landing 40 // время отводимое на посадку, в течении этого времени будут выпущены шасси и снижены обороты мотора до посадочных - 60 секунд
#define delay_g 100 // коэффициент торможения ))
#define free1 0
#define free2 0
#define free3 0
#define free4 0
#define free5 0
#define free6 0
#define free7 0
#define free8 0
uint8_t stateButtonKey = 0;
int countMenu = 15;
boolean flagButtonState = true; // изменилось состояние кнопок
uint8_t lastStateButton = 10; // нет нажатия кнопок
uint64_t timeOfChange;
uint8_t position = 0;
int8_t data [14] [4]; // массив для отображения побайтно
char* menu [17] {
"THR Norm Level ", // нормальные обороты мотора
"THR Land Level ", // обороты мотора при посадке
"FREE-1 uint16_t ", // 1 резервный пункт меню числовой параметр
"FREE-2 uint16_t ", // 2 резервный пункт меню числовой параметр
"FREE-3 uint16_t ", // 3 резервный пункт меню числовой параметр
"Flight time ", // общее время полета с момента уборки шасси
"Time for start ", // время на “убежать к ручке”
"Time for GUp ", // момент уборки шасси с момента старта
“Time for landing”, // время на посадку.
"FREE-4 uint16_t ", // 4 резервный пункт меню временной параметр
"FREE-5 uint16_t ", // 5 резервный пункт меню временной параметр
"FREE-6 uint16_t ", // 6 резервный пункт меню временной параметр
"FREE-1 bollean ", // 1 резервный пункт меню логический параметр
"FREE-2 bollean ", // 2 резервный пункт меню логический параметр
"Write data ", // записать данные в таймер
"Read data " // прочитать данные с таймера
};
uint16_t Stack [] { // программирумые параметры
Motor_ESC_Normall_THR, // нормальные обороты мотора
Motor_ESC_Landing_THR, // обороты мотора при посадке
free1,
free2,
free3,
InFly, // общее время полета с момента уборки шасси
Takeoff, // время на “убежать к ручке”
GearUp, // момент уборки шасси с момента старта
Landing, // время на посадку.
free4,
free5,
free6,
free7,
free8
};
int readButton()
{
int keyAnalog = analogRead(A0);
if (keyAnalog < 50) return BTN_RIGHT;
else if (keyAnalog < 150) return BTN_UP;
else if (keyAnalog < 300) return BTN_DOWN;
else if (keyAnalog < 550) return BTN_LEFT;
else if (keyAnalog < 750) return BTN_SELECT;
else return BTN_NONE;
}
/*
void clearLine(int line)
{
lcd.setCursor(line, 1);
lcd.print(" ");
}
/
void printDisplay(uint8_t pos, uint8_t line, String message)
{
lcd.setCursor(pos, line);
lcd.print(message);
}
//******************************************************************************
void readData()
{
uint8_t pos = 0;
uint8_t i = 0;
printDisplay(6, 1, “Read OK!”);
Serial.println(“Read OK!”);
for (i = 0; i<13; i++)
{
if (i >= 0 && i <= 4) pos = 0;
else if (i >= 5 && i <= 11) pos = 1;
else pos = 2;
Serial.println(i);
Serial.println(Stack[i]);
switch (pos)
{
case 0:
data[i][0] = Stack[i]/1000;
data[i][1] = (Stack[i]-data[i][0]*1000)/100;
data[i][2] = (Stack[i]-data[i][0]*1000-data[i][1]*100)/10;
data[i][3] = (Stack[i]-data[i][0]*1000-data[i][1]*100-data[i][2]*10);
break;
case 1:
data[i][0] = Stack[i]/60; // минуты
data[i][3] = (Stack[i]-data[i][0]*60)/10; // десятки секунд
break;
}
}
}
void writeData()
{
printDisplay(0, 1, “write timer data”);
}
//*******************************************************************************
void setup()
{
#ifdef serialPort
Serial.begin(9600);
#endif
lcd.begin(16, 2);
lcd.print(“Programmer ready”);
lcd.cursor();
// lcd.blink();
#ifdef serialPort
Serial.println(“Programmer ready”);
#endif
printDisplay(0,0,menu[countMenu]);
}
void loop()
{
stateButtonKey = readButton();
if ((stateButtonKey != lastStateButton) && flagButtonState)
{
Serial.println(“Button”);
Serial.println(stateButtonKey);
flagButtonState = false; // изменилось состояние и флаг поднят
timeOfChange = millis();
}
if (millis() - timeOfChange > 120 && (stateButtonKey != lastStateButton))
{
Serial.println(“Wait”);
lastStateButton = stateButtonKey;
Serial.println(stateButtonKey);
flagButtonState = true;
switch (stateButtonKey)
{
//***********************************************************************
case BTN_UP:
if (countMenu >= 0 && countMenu <= 4)
{
data [countMenu][position]++;
if (data [countMenu][position] > 9) data [countMenu][position] = 0;
lcd.setCursor(position, 1);
lcd.print(data [countMenu][position]);
lcd.setCursor(position, 1);
}
if (countMenu >= 5 && countMenu <= 11)
{
data [countMenu][position]++;
if (position == 0 && data [countMenu][position] > 30) data [countMenu][position] = 0;
if (position == 3 && data [countMenu][position] > 5) data [countMenu][position] = 0;
printDisplay(0,1," ");
lcd.setCursor(0, 1);
lcd.print(data[countMenu][0]);
lcd.setCursor(2, 1);
lcd.print("😊;
lcd.print(data[countMenu][3]);
lcd.print(“0 min:sec”);
lcd.setCursor(position, 1);
}
if (countMenu >= 12 && countMenu <= 13)
{
if (data [countMenu][0] == 0) data [countMenu][0] = 1; else data [countMenu][0] = 0;
lcd.setCursor(0, 1);
if (data [countMenu][0] == 0) lcd.print("False "); else lcd.print("True “);
lcd.setCursor(2, 1);
}
break;
//*********************************************************************
case BTN_DOWN:
if (countMenu >= 0 && countMenu <= 4)
{
data [countMenu][position]–;
if (data [countMenu][position] < 0) data [countMenu][position] = 9;
lcd.setCursor(position, 1);
lcd.print(data [countMenu][position]);
lcd.setCursor(position, 1);
}
if (countMenu >= 5 && countMenu <= 11)
{
data [countMenu][position]–;
if (position == 0 && data [countMenu][position] < 0) data [countMenu][position] = 30;
if (position == 3 && data [countMenu][position] < 0) data [countMenu][position] = 5;
printDisplay(0,1,” ");
lcd.setCursor(0, 1);
lcd.print(data[countMenu][0]);
lcd.setCursor(2, 1);
lcd.print("😊;
lcd.print(data[countMenu][3]);
lcd.print(“0 min:sec”);
lcd.setCursor(position, 1);
}
if (countMenu >= 12 && countMenu <= 13)
{
if (data [countMenu][0] == 0) data [countMenu][0] = 1; else data [countMenu][0] = 0;
lcd.setCursor(0, 1);
if (data [countMenu][0] == 0) lcd.print("False "); else lcd.print("True “);
lcd.setCursor(2, 1);
}
break;
//*********************************************************************
case BTN_LEFT:
countMenu–;
if (countMenu < 0) countMenu = 15;
printDisplay(0,0,menu[countMenu]);
if (countMenu >= 0 && countMenu <= 4)
{
printDisplay(0,1,” “);
lcd.setCursor(0, 1);
lcd.print(data[countMenu][0]);
lcd.print(data[countMenu][1]);
lcd.print(data[countMenu][2]);
lcd.print(data[countMenu][3]);
lcd.setCursor(0, 1);
}
if (countMenu >= 5 && countMenu <= 11)
{
printDisplay(0,1,” ");
lcd.setCursor(0, 1);
lcd.print(data[countMenu][0]);
lcd.setCursor(2, 1);
lcd.print(“😊;
lcd.print(data[countMenu][3]);
lcd.print(“0 min:sec”);
lcd.setCursor(2, 1);
}
if (countMenu >= 12 && countMenu <= 13)
{
if (data [countMenu][0] == 0)
{
printDisplay(0,1,” ");
printDisplay(0,1,"False “);
}
else
{
printDisplay(0,1,” ");
printDisplay(0,1,"True “);
}
}
if (countMenu >= 14 && countMenu <= 15)
{
printDisplay(0,1,” “);
}
position = 0;
lcd.setCursor(position, 1);
break;
//**************************************************************
case BTN_RIGHT:
countMenu++;
if (countMenu > 15) countMenu = 0;
printDisplay(0,0,menu[countMenu]);
if (countMenu >= 0 && countMenu <= 4)
{
printDisplay(0,1,” “);
lcd.setCursor(0, 1);
lcd.print(data[countMenu][0]);
lcd.print(data[countMenu][1]);
lcd.print(data[countMenu][2]);
lcd.print(data[countMenu][3]);
lcd.setCursor(0, 1);
}
if (countMenu >= 5 && countMenu <= 11)
{
printDisplay(0,1,” ");
lcd.setCursor(0, 1);
lcd.print(data[countMenu][0]);
lcd.setCursor(2, 1);
lcd.print(“😊;
lcd.print(data[countMenu][3]);
lcd.print(“0 min:sec”);
lcd.setCursor(2, 1);
}
if (countMenu >= 12 && countMenu <= 13)
{
if (data [countMenu][0] == 0)
{
printDisplay(0,1,” ");
printDisplay(0,1,"False “);
}
else
{
printDisplay(0,1,” ");
printDisplay(0,1,"True “);
}
}
if (countMenu >= 14 && countMenu <= 15)
{
printDisplay(0,1,” ");
}
position = 0;
lcd.setCursor(position, 1);
break;
//****************************************************************************
case BTN_SELECT:
if (countMenu == 14) {writeData(); break;}
if (countMenu == 15) {readData(); break;}
if (countMenu >= 0 && countMenu <=4)
{
position++;
if (position > 3) position = 0;
lcd.setCursor(position, 1);
}
if (countMenu >= 5 && countMenu <= 11)
{
if (position == 0) position = 3; else position = 0;
lcd.setCursor(position, 1);
}
if (countMenu >= 12 && countMenu <= 13)
{
position = 0;
lcd.setCursor(position, 1);
}
break;
}
}
}
Массив Stack[] предназначен для обмена с таймером
массив data[][] нужен для отображения и изменения значений, что бы менять поразрядно )
Алгоритм обмена в принципе “нарисован”, осталось “причесать” и “прикрутить” в программатор и таймер.
Пришлось его делать через … в общем надо было придумать, как заставить ведущего i2c выполнять команды ведомого )
На нобелевку конечно не потянет, а так … самому понравилось.
Алгоритм выглядит примерно так
- при включении тайера он проверяет наличие на i2c устройства с адресом 0x0f, с этим адресом будет инициализироваться программатор.
- если устройство есть, таймер переходит в режим программирования.
- каждую секунду таймер запрашивает один байт информации от программатора.
- программатор может передать таймеру только три кода 0x00 - никаких действий, 0xCC - передать данные и 0xAA - принять данные,
- получив команду на передачу данных, таймер отправит программатору массив,
- получив от программатора команду на прием данных, таймер запросит передачу от программатора массива.
В будни буду дома, попробую