Activity

Исправляем время на VBar Control при синхронизации под MacOS

Починил время на 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-адаптер для симулятора по протоколу SBUS

У меня получилось сделать беспроводной 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) как квадрик ведет себя при увеличении Д:

И вот на втором видео про “Д” Джошуа предупреждает что при большом увеличении Д может быть нагрев моторов и они могут сгореть или сгорят регуляторы. Поэтому он предупреждает что нужно быть аккуратным. Когда Д увеличиваете то частота осцилляций увеличиваете и на глаз их можно не заметить, но на слух слышно как на разворотах (при резком изменении курса) идут высокочастотные осцилляции по звуку пропеллеров. И вот в конце он снова повышает П - пробует и немного увиливает Д чтобы снова уменьшить перерегулирование из-за увеличения П.
Теперь ждем третье видео от Джошуа по П-компненте ПИД-регулятора.
Чуть позже добавлю полезных картинок и подправлю текст.