Интернет-лаборатория роботов ZiZiBOT.RU

Проектирование и разработки в области робототехники и автоматизации технологических процессов. Производство готовых роботов и конструкторов для творчества. Консультации и обучение по электронике и программированию.

г. Юрга,
ул.Ленинградская 38/83

+7 923-503-6074

Подключение аппаратуры удаленного управления FlySky (FS-i6 FS-iA6 FS-iA6B) к роботу на основе контроллера ESP32 (Arduino)

Удаленное управление роботом, актуальный вопрос, которого в то или иной степени касался каждый Конструктор роботов. Когда мой коллега, приобрел систему дистанционного управления FlySky, я отнесся к этому как к шагу назад, рассуждая, что мы и самостоятельно сможем делать подобные системы. Но, как показало время, создание качественных систем дистанционного управления непростая и недешевая задача. После того, как мы сделали и оттестировали один пульт на основе NRF24L01 и сконструировали несколько систем обмена информацией между роботами на основе данных модулей, я сделал вывод, что есть случаи, когда приобрести готовую систему дистанционного управления будет и дешевле и лучше/не хуже в эксплуатации.
 

nrf24l01

Рисунок 1
В свое время также рассматривался вопрос удаленного управления по каналу Bluetooth («Мобильные роботы на базе Arduino»), это удобный и мало затратный способ, но имеет ряд ограничений: 

 

И, в связи с наличием перечисленных мнений и ограничений, попытаюсь рассказать о способах подключения к контроллеру Arduino (на примере ESP32) аппаратуры удаленного управления Flysky, на примере FS-i6 (рис.2).
 

flySky

Рисунок 2. Аппаратура удаленного управления FS-i6
Аппаратура состоит из передатчика (FS-i6) и приемника (FS-iA6 или FS-iA6B). Передатчик на заводской прошивке может передавать до 6 значений от рычажков управления, какие из 10 рычажков будут задействованы,  настраивается из меню передатчика.  Довольно просто установить альтернативную прошивку, которая дает возможность задействовать одновременно все рычажки управления (я ее установил c https://github.com/qba667/FlySkyI6/releases ), но суть не в этом, и останавливаться на замене прошивки я не буду.
Для управления роботом потребуется как приемник, так и передатчик. Отличия приемников FS-iA6 и FS-iA6B в наличии у FS-iA6B дополнительной шины (bus) о которой поговорим ниже.
Передатчик FS-i6 пока отложим в сторону (как синхронизировать/связать приемник и передатчик ищем в других документах) и подробнее рассмотрим приемники, начнем с FS-iA6, он изображен на следующем рисунке.
 

fS-iA6

Рисунок 3
Питание приемника осуществляется через верхний ряд контактов B/VCC, ряды CH1-CH6 , это выходы, и если питание с приемника брать не нужно, то потребуется использовать только левый столбец контактов, на которых приемник генерирует ШИМ сигнал с шириной импульса соответствующей положению рычажков на передатчике. Замечу, что генерация ШИМ-сигналов приемником, это отсыл к моделям, которые не имеют собственного микроконтроллера, и двигатели которых управляются непосредственно с пульта. Т.е. пульт подключается напрямую к сервомашинкам самолета, которые регулируют положение руля, подкрылков и …, я не специалист в области летательных аппаратов.
Но наши модели являются роботами, они имеют собственные микроконтроллер, который и должен обслуживать сигналы дистанционного управления. Для этого их нужно расшифровать,  а конкретно в случае ШИМ сигналов нужно измерить длительность импульса в такте. Измеряем длину импульса в каждом интересующем нас канале и используем по назначению.
Простейший участок кода, отвечающий за обработку входных ШИМ последовательностей, выглядит следующим образом:
«Все листинги приведены для использования с контроллером ESP32».
 

ESP-WROOM-32

Рисунок 4

ESP-WROOM-32

ESP-WROOM-32

GPIO13 -> CH1
GPIO14 -> CH2
GPIO27 -> CH3
GPIO26 -> CH4
Приемник FS-iA6 и ESP32 от одного источника питания 5 вольт.
 

pulsein

Рисунок 5
Применяя функцию pulseIn(), поочередно измеряем длину импульса, на каждом GPIO к которому подключен ШИМ канал от приемника.
pulseIn() может применяться с двумя или тремя параметрами: первый – номер GPIO, второй –тип ожидаемого сигнала , третий – максимальное время ожидания. pulseIn() измеряет длину в микросекундах импульса на GPIO.
У вышеприведенного кода есть один существенный минус –считывание всех сигналов производится последовательно и возможно в разных циклах, что приводит нас к расчету максимальной задержки, которая равна сумме времени ожидания каждого вызова pulseIn().

 

Давайте попробуем исследовать сигналы не по очереди, а параллельно!
ESP32 быстрый контроллер, его частота 240МГц, и сильно оптимизировать код под него не нужно, хоть это возможно.
Разработаем алгоритм параллельного опроса GPIO в цикле с дискрецией 5мкСек. Сканируем GPIO в цикле, отлавливаем сигналы и измеряем их длину. Основная программа приведена ниже, а функция сканирования расположена в файле intpilt.h , там также описание всех сопутствующих переменных и прочее.
 

flySky PWM

Рисунок 6
 

flySky PWM scan

Рисунок 7
Листинг 1. Модуль intpult.h
#define PWMPOWERMIN 950 // Минимальное состоянние PWM управления от приемника
#define PWMPOWERMAX 2100 // Максимальное состояние PWM управления от приемника

 

#define MAXTIME 21000
#define MAXTIMECANAL 2100
#define pinCanal1 13
#define pinCanal2 14
#define pinCanal3 27
#define pinCanal4 26
#define DELTA 5 //Через сколько микросекунд начинается 

bool Canal1, Canal2, Canal3, Canal4; bool ScanC1, ScanC2, ScanC3, ScanC4; bool flagCanal1, flagCanal2, flagCanal3, flagCanal4;
bool C1Err,  C2Err, C3Err, C4Err, C5Err, CSErr; uint16_t Canal1Timeoff, Canal2Timeoff, Canal3Timeoff, Canal4Timeoff;
uint16_t Canal1Time, Canal2Time, Canal3Time, Canal4Time;
// Перед запуском: // 1. Поднимаем ScanC1=true // 2. Canal1Time = 0; // 3. Canal1Timeoff =0;
// 4. Canal1 = true; //На случай, если начали считывание с 1, нужн ждать пока она кончится
// 5. flagCanal1 = false; //Начало нормального импульса
void setup_CHANEL()
{
  pinMode(pinCanal1, INPUT);
  pinMode(pinCanal2, INPUT);
  pinMode(pinCanal3, INPUT);
  pinMode(pinCanal4, INPUT);
}

void ScanCHANEL()
{
  uint32_t LERRUA;
  ScanC1 = true; ScanC2 = true; ScanC3 = true; ScanC4 = true;  Canal1Time = 0; Canal2Time = 0; Canal3Time = 0; Canal4Time = 0;
  Canal1Timeoff = 0; Canal2Timeoff = 0; Canal3Timeoff = 0; Canal4Timeoff = 0;  Canal1 = true; Canal2 = true; Canal3 = true; Canal4 = true;
  flagCanal1 = false; flagCanal2 = false; flagCanal3 = false; flagCanal4 = false;  C1Err = false; C2Err = false; C3Err = false; C4Err = false;
  unsigned long tt, tectime, end_time;

  tectime = micros();
  end_time = tectime + MAXTIME + DELTA;
  while ((tectime < end_time) && (ScanC1 || ScanC2 || ScanC3 || ScanC4 ))
  {
    //====== C1
    if (ScanC1) //Если поднято считывание с канала
    {
      if (flagCanal1) // Если поднят расчет длины импульса
      {
        Canal1 = digitalRead(pinCanal1);
        if (Canal1) {
          Canal1Time += DELTA;
          if (Canal1Time > MAXTIMECANAL) {
            C1Err = true;
            ScanC1 = false;
          }
        }
        else        {
          ScanC1 = false;
        }
      }
      else //Если не поднят флаг расчет длины импульса
      {
        if (Canal1) //Если в канале 1, то мы в начале
        {
          Canal1 = digitalRead(pinCanal1);
        }
        else
        {
          Canal1 = digitalRead(pinCanal1);
          if (Canal1)
          {
            Canal1Time = DELTA;
            flagCanal1 = true;
          }
          else
          {
            Canal1Timeoff += DELTA; if (Canal1Timeoff > MAXTIME) ScanC1 = false; //Завершили
          }
        }
      }
    }
    //============= C2
    if (ScanC2) //Если поднято считывание с канала
    {
      if (flagCanal2) // Если поднят расчет длины импульса
      {
        Canal2 = digitalRead(pinCanal2);
        if (Canal2) {
          Canal2Time += DELTA;
          if (Canal2Time > MAXTIMECANAL)
          {
            C2Err = true;
            ScanC2 = false;
          }
        }
        else
        {
          ScanC2 = false;
        }
      }
      else //Если не поднят флаг расчет длины импульса
      {
        if (Canal2) //Если в канале 1, то мы в начале
        {
          Canal2 = digitalRead(pinCanal2);
        }
        else
        {
          Canal2 = digitalRead(pinCanal2);
          if (Canal2)
          {
            Canal2Time = DELTA;
            flagCanal2 = true;
          }
          else
          {
            Canal2Timeoff += DELTA; if (Canal2Timeoff > MAXTIME) ScanC2 = false; //Завершили
          }
        }
      }
    }
    //===== C3
    if (ScanC3) //Если поднято считывание с канала
    {
      if (flagCanal3) // Если поднят расчет длины импульса
      {
        Canal3 = digitalRead(pinCanal3);
        if (Canal3) {
          Canal3Time += DELTA;
          if (Canal3Time > MAXTIMECANAL)
          {
            C3Err = true;
            ScanC3 = false;
          }
        }
        else
        {
          ScanC3 = false;
        }
      }
      else //Если не поднят флаг расчет длины импульса
      {
        if (Canal3) //Если в канале 1, то мы в начале
        {
          Canal3 = digitalRead(pinCanal3);
        }
        else
        {
          Canal3 = digitalRead(pinCanal3);
          if (Canal3)
          {
            Canal3Time = DELTA;
            flagCanal3 = true;
          }
          else
          {
            Canal3Timeoff += DELTA; if (Canal3Timeoff > MAXTIME) ScanC3 = false; //Завершили
          }
        }
      }
    }
    //===== C4
    if (ScanC4) //Если поднято считывание с канала
    {
      if (flagCanal4) // Если поднят расчет длины импульса
      {
        Canal4 = digitalRead(pinCanal4);
        if (Canal4) {
          Canal4Time += DELTA;
          if (Canal4Time > MAXTIMECANAL)
          {
            C4Err = true;
            ScanC4 = false;
          }
        }
        else
        {
          ScanC4 = false;
        }
      }
      else //Если не поднят флаг расчет длины импульса
      {
        if (Canal4) //Если в канале 1, то мы в начале
        {
          Canal4 = digitalRead(pinCanal4);
        }
        else
        {
          Canal4 = digitalRead(pinCanal4);
          if (Canal4)
          {
            Canal4Time = DELTA;
            flagCanal4 = true;
          }
          else
          {
            Canal4Timeoff += DELTA; if (Canal4Timeoff > MAXTIME) ScanC4 = false; //Завершили
          }
        }
      }
    }
    tt = micros();
    while (tt < tectime)     tt = micros();
    tectime = tt + DELTA;
  }
}

Теперь блок сканирования будет выполняться за время равное длине одного такта, 21 миллисекунд в нашем случае. Время уменьшено в 4 раза для 4х каналов, для 6 каналов, будет 6 раз соответственно.


Но, по-моему, все равно время велико! 
Тратить 21 миллисекунду впустую тогда, когда наш контроллер мог бы производить полезные вычисления траектории робота или сканировать датчики, поэтому перейдем к рассмотрению приемника FS-iA6B.
 

FS-iA6B

Рисунок 8

ESP-WROOM-32

Все дело в том, что он имеет последовательный порт передачи данных на скорости 115200 
И тут нам на помощь приходит одна замечательная библиотека flyskyIBus (https://github.com/aanon4/FlySkyIBus ).
Скачиваем, подключаем GPIO4 подключаем к iBus Servos контакт S, настраиваем под особенности ESP32 см.ниже:

flySky iBus

 
А именно – добавляем адресные GPIO 
Вот ка выглядит теперь основная программа: 
 

flySky iBus 2

Рисунок 9
Обрабатываем входной буфер порта ввода/вывода IBus.loop() , записываем данные о состоянии рычажков передатчика в переменные и всё. Соединение использует только один GPIO (вывод нами не используется), прием данных в буфер порта проходит на аппаратном уровне и может не задерживать выполнение программы.
Вот  так, затратив  только одну GPIO контроллера ESP32, мы подключили приемник, причем у меня он передает на контроллер 10 значений состояния рычажков управления, а может 14, где взять еще 4 рычажка?

 

Все примеры прикреплены к статье


X

Написать сообщение:

Укажите свой номер телефона или e-mail для обратной связи
- e-mail
ИЛИ
- номер телефона

Текст сообщения: