Activity
Починил время на VBar Control при обновлении через VBar Control Manager на Mac OS!
Кому интересно, то виноваты не разработчики Mikado, а Дмитрий Анатольевич Медведев, когда он ввел временную зону для Москвы +4 часа, а потом вернули +3 часа. И вот здесь у меня закралось подозрение. Выяснил что VBar Control Manager v1.3 написан на Java и соответственно дело скорее всего в ней и нужно проапдейтить настройки временных зон для Java машины.
Скачал утилиту Java SE Timezone Updater 2.1.1. Запустил, но сразу не удалось: ошибка. Погуглил и нашел решение. Для MacOS используется своя Java машина JRE 1.6.0_65 и есть нюанс. Для нормального патча таймзон нужно в терминале запустить такую команду:
sudo /Library/Java/Home/bin/java -Djava.vendor="Sun Microsystems Inc." -jar tzupdater.jar -v -l
конечно из папки куда разархивировали утилиту tzupdater.jar, либо в команде прописать полный путь к файлу tzupdater.jar. Для отработки команды понадобиться пароль администратора.
В итоге должно пройти все удачно:
bash-3.2$ sudo /Library/Java/Home/bin/java -Djava.vendor="Sun Microsystems Inc." -jar tzupdater.jar -v -l
Using as source for tzdata bundle.
java.home: /Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
java.vendor: Sun Microsystems Inc.
java.version: 1.6.0_65
tzupdater version 2.1.1-b01
JRE tzdata version: tzdata2013d
Downloaded file to /var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/tz.tmp/tzdata.tar.gz
tzupdater tool would update with tzdata version: tzdata2017c
Downloaded file to /var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/tz.tmp/sha512hash
Extracting files... done.
Renaming /Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/zi to /Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/zi.tzdata2013d
Renaming /Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/zi.tzdata2017c to /Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/lib/zi
Validating for : tzdata2017c
Validation complete
JRE updated to version : tzdata2017c
bash-3.2$
После этого запускаем программу VBar Control Manager и подключаем по USB аппаратуру, после обновления времени на пульте будет правильное время! Вуаля!
У меня получилось сделать беспроводной USB адаптер к FrSky по шине SBUS
Теперь можно во FPV FreeRider с Taranisа летать и никаких проводов! Спасибо за идею Михаилу и его статье Самодельный беспроводной USB-адаптер для симулятора повышенной точности.
Сначала сделал как в статье по PPM протоколу, а потом переделал под протокол SBUS!
Вот исходный текст для Arduino:
#include "Arduino.h"
#include <avr/interrupt.h>
#include <Joystick.h>
// Use to enable output of PPM values to serial
//#define SERIALOUT
// Minimal and maximal PPM-pulse * 2 for more precission, because grab 2xPPM-pulse. For real values divide half.
// Example: Minimal PPM-value 1110, 1110 * 2 = 2220
#define MIN_PULSE_WIDTH 204 //2000 // Minimal pulse
#define CENTER_PULSE_WIDTH 1020 //3000 // Middle pulse
#define MAX_PULSE_WIDTH 1836 //4000 // Maximal pulse
#define CENTER_PULSE_JITTER 0 // Dead zone. If possible, do not use it.
// Min and Max joystick value
#define USB_STICK_MIN -32767
#define USB_STICK_MAX 32767
// Number of channels. Between 4-8.
#define RC_CHANNELS_COUNT 8
// Create the Joystick
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_JOYSTICK, 2, 0, true, true, true, true, true, true, false, false, false, false, false);
// Enum defines the order of channels
enum {
ROLL,
PITCH,
THROTTLE,
YAW,
AUX1,
AUX2,
AUX3,
AUX4
};
// ********** SBUS ************
#define port Serial1
//#define SBUS_BAUDRATE 98000
#define SBUS_BAUDRATE 100000
//#define SBUS_PORT_OPTIONS (SERIAL_STOPBITS_2 | SERIAL_PARITY_EVEN)
#define SBUS_PORT_OPTIONS SERIAL_8E2
//#define ALL_CHANNELS
#define SBUS_MAX_CHANNELS 18
#define SBUS_FRAME_SIZE 25
//#define SBUS_FRAME_BEGIN_BYTE 0x0F
#define SBUS_START_BYTE 0x0F
#define SBUS_END_BYTE 0x00
#define SBUS_DIGITAL_CHANNEL_MIN MIN_PULSE_WIDTH //173
#define SBUS_DIGITAL_CHANNEL_MAX MAX_PULSE_WIDTH //1812
#define SBUS_SIGNAL_OK 0x00
#define SBUS_SIGNAL_LOST 0x01
#define SBUS_SIGNAL_FAILSAFE 0x03
#define SBUS_STATE_FAILSAFE (1 << 0)
#define SBUS_STATE_SIGNALLOSS (1 << 1)
#define SBUS_FLAG_CHANNEL_17 (1 << 0)
#define SBUS_FLAG_CHANNEL_18 (1 << 1)
#define SBUS_FLAG_SIGNAL_LOSS (1 << 2)
#define SBUS_FLAG_FAILSAFE_ACTIVE (1 << 3)
// 16 channel (11 bit) + 2 digital channel
int16_t channels[SBUS_MAX_CHANNELS] = {1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,1023,0,0};
uint8_t failsafeStatus = SBUS_SIGNAL_FAILSAFE; // ?SBUS_SIGNAL_OK
int toChannels = 0;
// private variables
uint8_t inBuffer[SBUS_FRAME_SIZE];
int bufferIndex = 0;
uint8_t inData;
int feedState = 0;
struct sbusFrame_s {
uint8_t syncByte;
// 176 bits of data (11 bits per channel * 16 channels) = 22 bytes.
unsigned int chan0 : 11;
unsigned int chan1 : 11;
unsigned int chan2 : 11;
unsigned int chan3 : 11;
unsigned int chan4 : 11;
unsigned int chan5 : 11;
unsigned int chan6 : 11;
unsigned int chan7 : 11;
unsigned int chan8 : 11;
unsigned int chan9 : 11;
unsigned int chan10 : 11;
unsigned int chan11 : 11;
unsigned int chan12 : 11;
unsigned int chan13 : 11;
unsigned int chan14 : 11;
unsigned int chan15 : 11;
uint8_t flags;
/**
* The endByte is 0x00 on FrSky and some futaba RX's, on Some SBUS2 RX's the value indicates the telemetry byte that is sent after every 4th sbus frame.
*
* See
* and
*
*/
uint8_t endByte;
} __attribute__ ((__packed__));
typedef union {
uint8_t bytes[SBUS_FRAME_SIZE];
struct sbusFrame_s frame;
} sbusFrame_t;
static sbusFrame_t sbusFrame;
// SETUP
void setup() {
sbusBegin();
Joystick.setXAxisRange(USB_STICK_MIN, USB_STICK_MAX);
Joystick.setYAxisRange(USB_STICK_MIN, USB_STICK_MAX);
Joystick.setZAxisRange(USB_STICK_MIN, USB_STICK_MAX);
Joystick.setRxAxisRange(USB_STICK_MIN, USB_STICK_MAX);
Joystick.setRyAxisRange(USB_STICK_MIN, USB_STICK_MAX);
Joystick.setRzAxisRange(USB_STICK_MIN, USB_STICK_MAX);
Joystick.begin(false);
#ifdef SERIALOUT
Serial.begin(115200);
#endif
}
// LOOP
void loop(){
feedLine();
if (toChannels == 1) {
updateChannels();
// updateServos();
toChannels = 0;
}
setControllerDataJoystick();
Joystick.sendState();
#ifdef SERIALOUT
Serial.print(channels[ROLL]);
Serial.print("\t");
Serial.print(channels[PITCH]);
Serial.print("\t");
Serial.print(channels[THROTTLE]);
Serial.print("\t");
Serial.print(channels[YAW]);
Serial.print("\t");
Serial.print(channels[AUX1]);
Serial.print("\t");
Serial.print(channels[AUX2]);
Serial.print("\r\n");
#endif
}
// ********** Joystick ************
// Set joystick data in HID descriptor. Use functions: setXAxis, setYAxis, setZAxis, setRxAxis, setRyAxis, setRzAxis, setRudder, setThrottle.
void setControllerDataJoystick(){
Joystick.setXAxis(stickValue(channels[ROLL]));
Joystick.setYAxis(stickValue(channels[PITCH]));
Joystick.setZAxis(stickValue(channels[THROTTLE]));
Joystick.setRxAxis(stickValue(channels[YAW]));
Joystick.setRyAxis(stickValue(channels[AUX1]));
Joystick.setRzAxis(stickValue(channels[AUX2]));
Joystick.setButton(0, channels[AUX3] > CENTER_PULSE_WIDTH);
Joystick.setButton(1, channels[AUX4] > CENTER_PULSE_WIDTH);
}
// Convert a value in the range of [Min Pulse - Max Pulse] to [USB_STICK_MIN/USB_STICK_MAX]
uint16_t stickValue(uint16_t rcVal) {
if(rcVal > (CENTER_PULSE_WIDTH + CENTER_PULSE_JITTER)) {return constrain( map(rcVal, CENTER_PULSE_WIDTH, MAX_PULSE_WIDTH, (USB_STICK_MAX + USB_STICK_MIN) / 2, USB_STICK_MAX ), (USB_STICK_MAX + USB_STICK_MIN) / 2, USB_STICK_MAX);}
if(rcVal < (CENTER_PULSE_WIDTH - CENTER_PULSE_JITTER)) {return constrain( map(rcVal, MIN_PULSE_WIDTH, CENTER_PULSE_WIDTH, USB_STICK_MIN, (USB_STICK_MAX + USB_STICK_MIN) / 2 ), USB_STICK_MIN, (USB_STICK_MAX + USB_STICK_MIN) / 2);}
return (USB_STICK_MAX + USB_STICK_MIN) / 2;
}
// ********** SBUS ************
void sbusBegin() {
/*, SP_2_STOP_BIT | SP_EVEN_PARITY | SP_8_BIT_CHAR */
port.begin(SBUS_BAUDRATE, SBUS_PORT_OPTIONS);
failsafeStatus = SBUS_SIGNAL_OK;
toChannels = 0;
bufferIndex = 0;
feedState = 0;
}
// Read channel data
int16_t channel(uint8_t ch) {
if ((ch > 0) && (ch <= 16)) {
return channels[ch-1];
} else {
return 1023;
}
}
// Read digital channel data
uint8_t digiChannel(uint8_t ch) {
if ((ch > 0) && (ch <= 2)) {
return channels[15+ch];
} else {
return 0;
}
}
uint8_t failsafe(void) {
return failsafeStatus;
}
void updateChannels(void) {
// using structure
channels[0] = sbusFrame.frame.chan0;
channels[1] = sbusFrame.frame.chan1;
channels[2] = sbusFrame.frame.chan2;
channels[3] = sbusFrame.frame.chan3;
channels[4] = sbusFrame.frame.chan4;
channels[5] = sbusFrame.frame.chan5;
channels[6] = sbusFrame.frame.chan6;
channels[7] = sbusFrame.frame.chan7;
#ifdef ALL_CHANNELS
// & the other 8 + 2 channels if you need them
channels[8] = sbusFrame.frame.chan8;
channels[9] = sbusFrame.frame.chan9;
channels[10] = sbusFrame.frame.chan10;
channels[11] = sbusFrame.frame.chan11;
channels[12] = sbusFrame.frame.chan12;
channels[13] = sbusFrame.frame.chan13;
channels[14] = sbusFrame.frame.chan14;
channels[15] = sbusFrame.frame.chan15;
// DigiChannel 1
if (sbusFrame.frame.flags & SBUS_FLAG_CHANNEL_17) {
channels[16] = SBUS_DIGITAL_CHANNEL_MAX;
} else {
channels[16] = SBUS_DIGITAL_CHANNEL_MIN;
}
// DigiChannel 2
if (sbusFrame.frame.flags & SBUS_FLAG_CHANNEL_18) {
channels[17] = SBUS_DIGITAL_CHANNEL_MAX;
} else {
channels[17] = SBUS_DIGITAL_CHANNEL_MIN;
}
#endif
// Failsafe
failsafeStatus = SBUS_SIGNAL_OK;
if (sbusFrame.frame.flags & SBUS_FLAG_SIGNAL_LOSS) {
failsafeStatus = SBUS_SIGNAL_LOST;
}
if (sbusFrame.frame.flags & SBUS_FLAG_FAILSAFE_ACTIVE) {
// internal failsafe enabled and rx failsafe flag set
failsafeStatus = SBUS_SIGNAL_FAILSAFE;
}
}
void feedLine() {
if (port.available() >= SBUS_FRAME_SIZE) {
while (port.available() > 0) {
inData = port.read();
if (0 == feedState) {
// feedState == 0
if (inData != SBUS_START_BYTE){
//read the contents of in buffer this should resync the transmission
while (port.available() > 0){
inData = port.read();
}
return;
} else {
bufferIndex = 0;
inBuffer[bufferIndex] = inData;
inBuffer[SBUS_FRAME_SIZE-1] = 0xff;
feedState = 1;
}
} else {
// feedState == 1
bufferIndex ++;
inBuffer[bufferIndex] = inData;
if (bufferIndex < (SBUS_FRAME_SIZE-1)
&& port.available() == 0) {
feedState = 0;
}
if (bufferIndex == (SBUS_FRAME_SIZE-1)) {
feedState = 0;
if (inBuffer[0] == SBUS_START_BYTE
&& inBuffer[SBUS_FRAME_SIZE-1] == SBUS_END_BYTE) {
memcpy(sbusFrame.bytes, inBuffer, SBUS_FRAME_SIZE);
toChannels = 1;
}
}
}
}
}
}
А вот фото куда нужно подпаивать неинвертированный SBUS ко входу RX1 Ардуинки Леонардо:
Решил написать немного о ПИДах. Так как многие не правильно трактуют их 😃
Не забываем что в современных прошивках несколько ПИД-регуляторов (контуры управления): один работает с гироскопом и держит заданный угол (режим Акро, про него я и написал ниже), другой работает с акселерометром и держит уровень квадрика чтобы висел в горизонте (это в режиме стабилизации Angle и Horizon) и еще один работает с GPS для удержании позиции и высоты (этих режимов нет в прошивке BetaFlight, а вот в iNav есть).
Характерная ошибка трактовки параметра Д в ПИД регуляторе - многие считают что Д отвечает за скорость поворота квадрика и все из за слова “Дифференциальная”.
Д - компенсирует большое П. Д и П связаны между собой.
Общая формула управления:
U = P*E - D*dE/dt + I*SUM(E)
где E - ошибка управления - разница между требуемым углом отклонения и реальным
dE - разница ошибки между предыдущим измерение и текущим за время dt (дифференциал)
SUM - это функция суммирования ошибки (интеграл)
P - коэффициент пропорциональной составляющей регулятора
D - коэффициент дифференциальной составляющей регулятора
I - коэффициент интегральной составляющей регулятора
U - управляющее напряжение подаваемое на мотор (привод)
Объясню Д: П - прямо зависит от ошибки. Например нам надо отклониться на 20 градусов. П = 10, то по формуле (если Д и И равны 0) сигнал на моторы будет равный U=P*20 - и это напряжение подается на мотор (условно, так как моторы у нас трехфазные и регулятор это за нас это обеспечивает). Поэтому мотор старается повернуть наш квадрик на 20 градусов как можно быстрее. Когда квадрик у нас повернется на 10 градусов то сигнал на мотор будет уже U = P*10. И так приближаясь к нужному углу управляющее напряжение падает. Но квадрик обладает инерцией и поэтому когда управляющее напряжение будет равно 0 (U = P * 0 - нет ошибки управления), то квадрик по инерции продолжит свое движение и тогда угол ошибки станет -5 градусов например и тогда управляющее напряжение будет U = P * -5 и квадрик начнет обратное движение к нужному углу. И вот как раз возникает так называемое перерегулирование (на просторах форума - осцилляции). Это возникает когда большое P. Если P маленькое то перерегулирования не будет, но время выхода на нужный угол будет большое. Для того чтобы квадрик был не “вялым” а “бодрым” нам нужно большое P, но в этом случае у нас будет перерегулирование и осцилляции. Это можно видеть на многих квадриках - когда резко изменяете угол то квадрик делает пару колебательных движений и успокаивается - это значит большое P.
Чтобы убрать осцилляции или перерегулирование решили ввести дифференциальный контур регулирования, который будет измерять скорость изменения ошибки управления (еще раз напомню что это разница между нужным углом и текущим положением квадрика) и будет уменьшать P управление когда объект будет приближаться к заданному углу. Т.е. когда у нас большое P то квадрик из-за инерции начнет медленно компенсировать ошибку, но потом наберет скорость и начнет быстро приближаться к нужному углу и вот тут в дело вступает Д компонента регулятора, чем быстрее уменьшается ошибка то больше будет Д составляющая и тем меньше будет управляющий сигнал на мотор: U = P*E - D*dE/dt - т.е. включается торможение квадрика заранее когда он приближается к нужному углу. В итоге P составляющая уменьшается за счет Д составляющей и не происходит перерегулирования - т.е. отсутсвие осцилляций. Поэтому когда нужно большое П чтобы квадрик был “бодрым” то нужно Д. Поэтому П и Д неразлучные друзья. Но при большом П все равно возникают осцилляции но их компенсирует Д, но не до конца и возникают высокочастотные осцилляции которые на глаз никогда не увидишь. Поэтому определить высокочастотные осцилляции можно по температуре моторов - если горячие (т.е. они трудились чтобы убрать перерегулирование, но П такое большое что им пришлось сильно трудиться) то нужно уменьшать Д и соотвественно уменьшать и П
Теперь что такое И: вроде бы все нормально П и Д справились, но у системы есть ошибки: ошибка измерения угла, смещенный центр тяжести, разная производительность моторов и пропов. Поэтому вроде бы система справилась с ошибкой - уменьшила ее до нуля, но на самом деле есть ошибка. Поэтому вводят И составляющая котора складывает все ошибки и добавляют ее в управляющий сигнал для компенсации конструктивных особенностей системы. Вот и получается формула:
U = P*E - D*dE/dt + I*SUM(E)
Теперь как все это настраивается. В идеальном случае в начале убирают Д и И составляющие регулятора, т.е. делают коэффициенты Д и И равными 0 (ноль). И ставят какое то маленькое значение П (чисто имперически - практически). И начинают настройку П составляющей. Увеличивают П до тех пор пока не начнется перерегулирование (не будет осцилляций). Как только началась осцилляция, то останавливаются и начинают увеличивать Д до тех пор пока осцилляции не уйдут. Т.е. Д справилось со своей задачей и при высоком П убрала перерегулирование. Дальше снова увеличивают П, чтобы повысить скорость реакции системы, чтобы квадрик стал “бодрым” а не “вялым” и увеличивают до тех пор пока не будут осцилляций, потом увеличивают Д чтобы их убрать. И так делают до тех пор пока не будет реакции при увеличении П и Д. После того как достигли приемлемого управления, немного уменьшают значения П и Д примерно на 5%. Чтобы не было работы системы на грани устойчивости. После всего этого начинают крутить И. Вот здесь сложнее. Здесь нужно смотреть реакцию системы в дальней перспективе. Нужно смотреть как система себя ведет как она вышла на заданный угол и как долго она исправляет ошибку конструкции. Настройку И не смогу объяснить По видео которые я смотрел, говорят что И проявляется при спусках коптера. Если при спуске коптер раскачивает, то нужно увеличивать И. В принципе верно, если посмотреть на формулу то И работает на сумму ошибки управления. И если сумма ошибки есть то на нее нужно реагировать. Если коэффициент И маленький то реакция на сумму ошибки будет “вялой” если большой, то “резкой” и возможны опять колебания (осцилляции). Для меня пока И очень тяжела в настройке. Так как могут влиять внешние факторы - например порывы ветра и вся настройка И на смарку
Надеюсь объяснил понятно.
А вот картинка которая очень ясно и понятно показывает влияние коэффициентов ПИД-регулятора на выходной сигнал:
Лучше один раз увидеть чем сто раз прочитать 😃
В подтверждение выше сказанного Джошуа только что выложил два видео объясняющие ПИДы.
Первое видео показывает что бывает когда увеличиваете П:
Второе видео показывает как при высоком П (около 70) как квадрик ведет себя при увеличении Д:
И вот на втором видео про “Д” Джошуа предупреждает что при большом увеличении Д может быть нагрев моторов и они могут сгореть или сгорят регуляторы. Поэтому он предупреждает что нужно быть аккуратным. Когда Д увеличиваете то частота осцилляций увеличиваете и на глаз их можно не заметить, но на слух слышно как на разворотах (при резком изменении курса) идут высокочастотные осцилляции по звуку пропеллеров. И вот в конце он снова повышает П - пробует и немного увиливает Д чтобы снова уменьшить перерегулирование из-за увеличения П.
Теперь ждем третье видео от Джошуа по П-компненте ПИД-регулятора.
Чуть позже добавлю полезных картинок и подправлю текст.