Работа с битами

mahatm
mahatm аватар
Offline
Зарегистрирован: 15.10.2015

День добрый!

 

Есть такая задача, нужно из данных в виде четырёх шестибитных цифр, сделать шесть четырёхбитных, при этом не сильно нагрузив контроллер, ибо это всё делается внутри цикла.

То есть к примеру у нас есть B111111  B100000 B100000 B010101, числа доступны из заданных переменных, и каждый раз разные. 

Нужно взять четыре старших бита из первого числа в первую переменную, во вторую переменную два младших бита из первого числа, и два старших из второго, в третью переменную четыре младших из второго числа, ну и со второй парой проделать то же самое.

 

Собственно как взять определённые биты, - вполне понятно, непонятно как без танцев с бубнами взять  в переменную два младших из одного и два старших бита из другого числа, так что бы это превратилось в ззначение переменной,(если с указанными выше числами) типа - B1110.

Плиз хелп!

BoBo4kA
Offline
Зарегистрирован: 15.01.2016

Добрый!

Первое, что пришло в голову, bitRead(), bitWrite(), сложно сказать на сколько это нагрузит системе, я новенький ))

Buldakov
Offline
Зарегистрирован: 17.01.2016
MacSim
Offline
Зарегистрирован: 28.11.2012

Интересно. А как вы храните в памяти 4 шестибитные и 6 четырехбитные числа?

Переменную какого типа для этого используете?

Сколько пространства отводится в памяти на каждую переменную?

<< , >>

 

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

mahatm пишет:
Есть такая задача, нужно из данных в виде четырёх шестибитных цифр, сделать шесть четырёхбитных, при этом не сильно нагрузив контроллер, ибо это всё делается внутри цикла.

То есть к примеру у нас есть B111111  B100000 B100000 B010101, числа доступны из заданных переменных, и каждый раз разные. 

Нужно взять четыре старших бита из первого числа в первую переменную, во вторую переменную два младших бита из первого числа, и два старших из второго, в третью переменную четыре младших из второго числа, ну и со второй парой проделать то же самое.

Собственно как взять определённые биты, - вполне понятно, непонятно как без танцев с бубнами взять  в переменную два младших из одного и два старших бита из другого числа, так что бы это превратилось в ззначение переменной,(если с указанными выше числами) типа - B1110.

Как-то так:

d1=(c1>>2)&0x0F;

d2=((c1<<2)&0x0C) | ((c2>>4)&0x03);

и т.д.

mahatm
mahatm аватар
Offline
Зарегистрирован: 15.10.2015

Andy А вот в последней строке присвоение d2, если я напишу вместо 0x0C - B001100 и вместо 0x03 - B000011 это сработает?

Andy
Andy аватар
Offline
Зарегистрирован: 01.01.2016

Конечно сработает.

MacSim
Offline
Зарегистрирован: 28.11.2012

mahatm пишет:

 при этом не сильно нагрузив контроллер, ибо это всё делается внутри цикла.

Судя по этой фразе задача практическая

mahatm пишет:

То есть к примеру у нас есть B111111  B100000 B100000 B010101, числа доступны из заданных переменных, и каждый раз разные. 

поскольку задача практическая, то самая маленькая переменная будет, как ни крути, занимать 8 бит. следовательно надо читать из 0b00111111 (0b-arduino все-ж : -), 0b00100000, 0b00100000, 0b00010101 и соответственно получить:    0b00001111, 0b00001110, 0b00000000, 0b00001000, 0b00000001, 0b00000101

Исходя из есть и надо напишите используя сдвиги влево и в право, сумирование сначала все по действиям. если цикл использовать очень надо перепешите в цикл.

 

если задача теоретическая, кто его знает может препод требует (я сказал и все!), шестибитное и четырехбитное объявил, придумал препод так, то двигайте без учетов первых двух нулей.

 

 

mahatm
mahatm аватар
Offline
Зарегистрирован: 15.10.2015

Да, это сугубо практическая задача. И нет, если дополнять до полного бита, выходит неверно.

Чтоб не было разночтений, - речь идёт о генерации IR пакета, который имеет вид:

111111 000000 111111 0000000 11 00 1111

первые четыре поля по 6 бит, это соответственно скорость моторов, Вправо/Влево, Вперёд/назад и трим(соотношение скорости между двумя моторами). Далее два бита указывающие канал и ещё два это вкл/вкл Лед подсветки.

Собственно известно что последние  четыре бита, это CRC. И я проверил по десятку пакетов, расчитывается она однозначно верно, если разделить всё до CRC на группы по четыре бита, сложить их, из суммы взять младшие четыре бита, и следать реверс, - поменять все единицы на нули, а нули на единицы(просто бывает ещё с лево на право реверс).

Пакет нужно отсылать непрерывно, а считается он сложно, в двух вложенных циклах.

Вот я и думаю как бы мне самым минимальным выражением, подсчитывать CRC. Пока я сделал функцию которую вызываю сразу за получением в цикл параметров, и которая возвращает переменную с этими четырьмя битами. Но что-то пока не очень..

Так что если есть предложения - велком. :)

 

mahatm
mahatm аватар
Offline
Зарегистрирован: 15.10.2015

Кстати, есть ли простой способ проверять выражения в коде, не мучая каждый раз ардуинку и вписывая кучу лишнего(типа вывода в сериал, всех промежуточных значений)?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

mahatm,

Вас уже спрашивали в каких переменных Вы храните свои шестибитовые числа. Почему Вы не отвечаете? От этого многое зависит.

Пока, я вижу наиболее кошерное решение безо всяких сдвигов и прочих танцев с бубнами, путём накладывания одних чисел на другие в памяти. Т.е. просто сложить все шестибитовые подряд в памяти, а потом из той же самой памяти прочитать их как 4-битовые, и всего делов.

Вот смотрите:

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

// из четырёх шестибитных цифр -> сделать шесть четырёхбитных

typedef union {
	struct {
		unsigned n6_0 : 6;
		unsigned n6_1 : 6;
		unsigned n6_2 : 6;
		unsigned n6_3 : 6;
	};
	struct {
		unsigned n4_0 : 4;
		unsigned n4_1 : 4;
		unsigned n4_2 : 4;
		unsigned n4_3 : 4;
		unsigned n4_4 : 4;
		unsigned n4_5 : 4;
	};
} SixFourMix;

void setup() {
	Serial.begin(115200);
	SixFourMix mix;
	//	Присвоим 6-битовые значения
	mix.n6_0 = 1;
	mix.n6_1 = 1;
	mix.n6_2 = 1;
	mix.n6_3 = 1;
	//	Напечатаем 6-битовые и получившиеся 4-битовые для контроля
	Serial << "6-bit values: (" << mix.n6_0 << ", " << mix.n6_1 << ", " << mix.n6_2 << ", " << mix.n6_3 << ")\n"; 
	Serial << "4-bit values: (" << mix.n4_0 << ", " << mix.n4_1 << ", " << mix.n4_2 << ", " << mix.n4_3 << ", " << mix.n4_4 << ", " << mix.n4_5 << ")\n"; 
}

void loop () {}

Но если Вам надо что-то ещё, то не знаю.

mahatm
mahatm аватар
Offline
Зарегистрирован: 15.10.2015

Переменные принимаются в функцию непосредственно передачи IR , в виде byte name1...

Дальше они складываются в массив и оттуда уже всячески достаются и мучаются.

А беруться они просто из аналогового порта, - переменника(резистора). Правда щас я собираю новый шилд, на нём переменники в виде джойстика, но это не суть важно. В общем на данный момент мне наконец удалось выиграть если не войну то сражение с битовыми операциями, и мой вертолётик наконец полетел.. Правда пока "нызенко, нызенко".. :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

mahatm пишет:

Переменные принимаются в функцию непосредственно передачи IR , в виде byte name1...

Дальше они складываются в массив и оттуда уже всячески достаются и мучаются.

Ну, тогда складывайте их как я написал, а потом вытаскивайте - никакиях явных преобразований. Если чего ещё надо - говорите, обсудим.

mahatm
mahatm аватар
Offline
Зарегистрирован: 15.10.2015

Вот окончательно работающий код для моего вертолётика, может пригодиться кому. Код сурово закоментирован, но(сорри) не вычищен от остатков вылизывания. Ещё раз  подчёркиваю он полностью рабочий. С удовольствием приму каменты по возможной правке в сторону красоты и главное эфективности.

Вертолётик серии 8185, такие продают в сети "Детский мир". Они там средние по цене.

//* Connect (+) of IR LED to 5Vcc
//* connect (-) to pin 4 with a 100 Ohm resistor in line.  For best results make a transistor circuit with external power source.

//*Potentiometers connected to analog pins 0, 1, 2, 3  (Throttle, Rudder, Elevator, Trim)

#define RED 4    //* the output pin of the IR LED
byte lb = 0; // тут будет лежать последние 8 бит. Так как нужно что бы в переменной хранилось значение доступное из нескольких функций, обьявляем ее тут
byte Sst = B1001; // Статичная часть последнего байта пакета.
byte LastByte = 0; // возвращает значение из одной функции в другую
void setup() {
pinMode(RED, OUTPUT);    //* set IR LED (Pin 4) to Output
int ThrottlePin = A1;    //* select the input pin for the potentiometer для скорости вращения главного винта.
int LrPin = A5;      //* select the input pin for the potentiometer Для скорости и направления вращения хвостового винта
int FbPin = A4;    //* select the input pin for the potentiometer Для скорости вращения нижнего(не несущего) винта, - медленнее чем середина диапазона для вращения вертолёта в в право, и быстрее для вращения влево
int TrimmPin = A3;        //* select the input pin for the potentiometer Устанавливаем середину для предыдущего параметра(НЕ связанно с расчитываемой серединой, влияет на внутренние расчёты железа вертолёта), говоря иначе задаёт соотношение скорости главного винта и нижнего, для того что бы вертолёт не вращался в плоскости вращения винтов. И меняя в параметре Lr значение от 32 в большую или меньшую сторону, мы как раз таки нарушаем установленное тут значение, для того что бы повернуть вертолёт вокруг оси вращения лопостей в право или лево.
int Channel = 0;          //*Channel A = 0, Channel B = 128 - оставлен на всякий случай, в коде фактически не используется, но может если будет надо.
int Lr =32;
int Fb = 32;


Serial.begin(9600);
}


void loop() {
  //*Do you magic here.  The code directly below just throttles up to 60 and back down to 0.
  //*You can get data from the serial port, a joystick, or whatever.  You just need to translate your input into values between 0 and 127.
  //*Pass those values to Transmit( ) as an integer between 0 and 127 and that's it!
  // С одной стороны делить на 16, быстрее чем делать map или приводить к заданному диапазону, с другой было бы правильнее всё же использовать map
  // делим на 16, в итоге у нас получается значение в пределах 0-63. Дробная часть будет отброшена из за того что переменная обьявленна как int.
  int Throttle, Lr, Fb, Trimm;
  int Channel;
  Throttle = analogRead(A1) / 16 + Channel; //*Divide by 2 for difference between CHA and B, Divide by 4 to convert from 10Bit to 8Bit
  Throttle=map(Throttle,0,63,63,0);
    Serial.print("Throttle:" );
  Serial.print(Throttle);
  Serial.print("|");
  Lr = analogRead(A5) / 16 + 1;
  
  Fb = analogRead(A4) / 16 + 1;
  
  if(Lr<=31){
  Lr = map(Lr, 0, 30, 30, 0);
  }
    
  if(Fb<=31){
  Fb = map(Fb, 0, 30, 30, 0);
  }
  //AllMap(Lr,Fb) ; // делаем мапинг Fb и Lr
  
  
  Trimm = analogRead(A3) / 16 + Channel;
  Serial.print("Lr:" );
  Serial.print(Lr);
  Serial.print(" |");

  Serial.print("Fb:");
  Serial.print(Fb);
  Serial.print("|");

  Serial.print("Trimm:");
  Serial.println(Trimm);



  Transmit(Throttle, Lr, Fb, Trimm);
 


} //*End loop()

void Transmit(byte Throttle, byte Lr, byte Fb, byte Trimm) {
  static byte Code[4];  // масив считанных с переменников значений
  byte mask = 32;     //*bitmask маска 32, поскольку у нас каждый парамерт 6 бит.
  int i;
  Code[0] = Throttle; //* 0 -> 127; 63 is the midpoint. Увеличивает скорость вращения лопастей, линейно от 0(лопасти не вращаются) до 63(моторы работают на полную мощиность)
  Code[1] = Lr; //* 0 -> 127; 63 is the midpoint. В мидпоинте задний мотор=стоп, меньше - против часовой, больше по часовой. В результате вертолёт наклоняется либо в перёд(хвост поднят выше плоскости вращения основных лопастей), либо назад, хвост опущен ниже плоскости вращения.
  Code[2] = Fb; //* 0 -> 127; в мидпоинте никак не влияет на соотношение скорости работы двух главных моторов. При значении ниже мидпоинта, нижние лопости вращаются медленее вплоть до 0 - останов двигателя, выше - нижние лопасти начинают вращаться быстрее верхних, вплоть до 63.
  // на самом деле неизвестно, при значениях выше мидпоинта, только ли ускоряется нижний мотор.. Возможно что наряду с ускорением нижнего, верхний замедляется. Просто в ином случае получается довольно расточительно с точки зрения КПД подьёмной силы.
  Code[3] = Trimm;    //* Haven't messed with this Тут устанавливается трим - статичное соотношение между скорость работы моторов. от мидпоинта - никак не влияем на этот параметр, от мидпоинта до 0 - прибавляем к скорости вращения нижних лопастей.
  // Нужно помнить что трим, никак не связан с параметром Lr напрямую, это разные кофициенты, Lr - это фактически команда нарушить равновесие, а трим это кофициент необходимый для его поддержания.
  lb = Crc(Throttle, Lr, Fb, Trimm); // Вызываем функцию расчёта последних 8 бит пакета. Складываем ответ в lb
  OutPulse(988);  //* Start 38Khz pulse for 988us (988us is evenly divided by 26) Это стартовый длинный пульс означающий начало пакета, 998 а не тысяча, потому что всё что отправляется в OutPulse должно делиться на 26(что бы было 38Гц)
  //delayMicroseconds(2000);  //* 2000us off.Это пульс сделующий за первым марком. В моём случае не используется, поскольку первая пауза уже значимая.

  for (i = 0; i < 4; i++) {      //* Loops through the Code[] цикл через 4 полученных с переменников значения
    for (mask = 32; mask > 0; mask >>= 1) {   //* See Arduino reference for bit masking (really cool stuff!) сдвинули значение mask(буквально mask=mask>>1)в право на битпозицию(2 в десятичной)

      if (i != 0 || mask != 32) {
        OutPulse(312);         //* Sends 312 pulse each loop, but not first. Так как в моём протоколе за первым длинным марком, сразу идёт первый значимый бит, пришлось добавить это условие, - в первом тике цикла, пульс не посылаем. А то получалось бы длиннй марк, сразу за ним опять марк, и значимая пауза.
      }

      if (Code[i] & mask) {         //*If both bit positions are 1 you get 1   бит по маске,которая каждый тик цикла, уменьшается на один бит от старшего к младшему.(Кстати мне не понятно почему берётся только старший бит для каждого тика цикла, но по факту, берётся именно он)
        delayMicroseconds(650);     //* send 1 (700 off) Длинная пауза после марка, - это единица.
      }
      else {
        delayMicroseconds(338);     //* send 0 (300 off) короткая пауза ,это ноль.
      }
    } //*End mask loop
  }  //*End i loop

  for (mask = 128; mask > 0; mask >>= 1) {  // отправляем последние 8 бит из которых первые четыре статичны(первые два это канал 10 - для А, вторая пара это подсветка, - 11 для ВЫКЛ). И ещё четыре, это CRC. Так как у нас 8 бит а не 6 как в предыдущем цикле, маска должна начинаться со 128

    OutPulse(312); // это марк перед каждой паузой

    if ( lb & mask) {         // накладываем уменьшающуюся вправо маску, на расчитанные 8 бит, если в левом бите(кстати непонятно почему именно в старшем бите), стоит 1 и  в иаске 1, то делаем длинную паузу, в ином случае короткую.
      delayMicroseconds(650);     //* send 1 (700 off)
    }
    else {
      delayMicroseconds(338);     //* send 0 (300 off)
    }
  } //*End mask loop
  OutPulse(312);  //*Send 312 microsecond Closing Pulse всё что посылается в функцию пульс, должно делиться на 26, - (1000/26=38герц)
  delay(60); // не уверен что эта пауза вообще нужна. это пауза между пакетами.

} //* End Transmit


void OutPulse(int Pulse) {  //*sends 38Khz pulses over the Pulse Length
  int p;

  for (p = 0; p < (Pulse / 26) - 1; p++) { //*Takes about 26 microseconds per loop
    digitalWrite(RED, HIGH);            //по две мс на работу порта, и 20 мс паузы = 24+2мс на отработку цикла.
    delayMicroseconds(10);
    digitalWrite(RED, LOW);
    delayMicroseconds(10);
  }
}
// Функция расчёта CRC. Для расчёта нам нужно сформировать полный пакет без последних 4 бит(собственно CRC).
// Далее нужно разделить получившийся пакет(28 бит=6бит throtle+6бит Вперед/назад+6бит Лево/право+6бит трим и плюс 4 бита,которые всегда 1011 - А канал и ВЫКЛ подсветка) разделить на 7 груп по 4 бита начиная со старшего.
// после чего надо их сложить и сделать реверс результата - поменять все единицы на нули, а нули на единицы(бывает ещё просто реверс - с лева на право) и взять младшие 4 бита. Это и есть искомый CRC.
byte Crc(byte Throttle, byte Lr, byte Fb, byte Trimm) {
  static byte Param[4]; // создаём линейный массив на наши четыре параметра
  Param[0] = Throttle;
  Param[1] = Lr;
  Param[2] = Fb;
  Param[3] = Trimm;

  byte x1 = (Param[0] >> 2) & 15; // берем первый параметр из масива, сдвигаем все биты на 2 вправо, и берем последние четыре
  byte x2 = ((Param[0] << 2) & 15) | ((Param[1] >> 4 ) & 15); // берем первый параметр из массива, сдвигаем на два в лево, и берём таки последние 4, потом берём 2 параметр, сдвигаем все биты на 4 вправо, и снова берём 4 младших. обьединяем первое со вторым.
  // Так как у нас первый результат всегда имеет 00 в двух младших битах, получается что обьединяя результаты, мы получим два бита из первого результата(**00) и два из второго(00**)
  byte x3 = Param[1] & 15; // просто накладываем маску 001111 на второй параметр и забираем последние четыре бита
  byte x4 = (Param[2] >> 2) & 15; // Повторяем фактически три предыдущие операции.
  byte x5 = ((Param[2] << 2) & 15) | ((Param[3] >> 4 ) & 15);
  byte x6 = Param[3] & 15;
  byte x7 = Sst; // Sst  всегда равно 1011
  LastByte = (x1 + x2 + x3 + x4 + x5 + x6 + x7) & 15; // 15 - маска. Всё складываем и берём четыре младших бита

  LastByte = ~LastByte; //делаем реверс
  LastByte = ((Sst << 4) | (LastByte & 15)); // Берем Sst, и двигаем на 4 бита влево(нужно помнить что byte это всегда 8 бит, даже если значимых всего 4), обьединяем с нашим CRC(младшие 4 бита), получаем готовый для отправки пакет в 8 бит,- 24 бита параметров + 8 бит наш пакет= полный пакет отправки.

  // Для отладки. Должно выводить 4 параметра+последние 8 бит.
  // Serial.Print(Param[0], BYTE);
  // Serial.Print(" ");
  // Serial.Print(Param[1], BYTE);
  // Serial.Print(" ");
  // Serial.Print(Param[2], BYTE);
  // Serial.Print(" ");
  // Serial.Print(Param[3], BYTE);
  // Serial.Print(" ");
  // Serial.Println(Lastbyte, BYTE);
  return LastByte; //возвращаем наши 8 бит.
}



//*End OutPulse

 

mahatm
mahatm аватар
Offline
Зарегистрирован: 15.10.2015

Для собственно управления был сделан шилд, 4х6 см, из стандартной готовой для пайки платы с дырочками, двухсторонней. На нём размещены: три последовательно соединённых IR LED, Потенциометр в виде джойстика для Throttle, Два потенциометра в виде второго джойстика для управления Left/Right и Forward/Back, отдельный маленький потенциометр для trim.

С двух сторон были впаяны  штырьки, что бы оно всё втыкалось в плату (Leonardo) в одну секунду, и так же снималось.

Стоит добавить что леды подключены через транзистор NPN, соответственно установленный в нижнем плече, и управляемый через малоомный резюк с пина ардуины(база транзистора, на пин ардуины).

mahatm
mahatm аватар
Offline
Зарегистрирован: 15.10.2015

Код был оптимизирован, решил добавить. Плюс теперь большая часть кода с каментами.

//* Цепляем наши Ир светики так: плюс(анод) к VCC, минус(катод), к 4 порту дуины, через стоомный резюк. 
//*  А лучше конечно организовать внешнее питание и цеплять Ир через транзистор работающий как ключ.
//*Цепляем потенциометры(ну или джойстикпи, что то же самое), которые будут давать нам, главные четыре параметра, для формирования пакета  (Throttle, Lr,Fb, Trimm)

#define RED 4    //* Делаем дефайн пина на котором висят ир диоды, - главное для чего вообще всё дальнейшее. :)

byte lb = 0; // тут будет лежать последние 8 бит. Так как нужно что бы в переменной хранилось значение доступное из нескольких функций, обьявляем ее тут
byte Sst = B1001; // Статичная часть последнего байта пакета.Вы можете дописать кусок кода, который будет устанавливать эти два по два байта. В моём случае достаточно один раз их определить
byte LastByte = 0; // возвращает значение из одной функции в другую

void setup() {

pinMode(RED, OUTPUT);    //* Обьясняем рундине политику партии. :) (Что пин 4 должен работать в режиме OUT)
int ThrottlePin = A2;    //*  для скорости вращения главного винта.
int LrPin = A5;      //* Для скорости вращения нижнего(не несущего) винта, - медленнее чем середина диапазона для вращения вертолёта в в право, и быстрее для вращения влево
int FbPin = A4;    //*  Для скорости и направления вращения хвостового винта
int TrimmPin = A3;        //* Устанавливаем середину для предыдущего параметра(НЕ связанно с расчитываемой серединой, влияет на внутренние расчёты железа вертолёта), говоря иначе задаёт соотношение скорости главного винта и нижнего, для того что бы вертолёт не вращался в плоскости вращения винтов. И меняя в параметре Lr значение от 32 в большую или меньшую сторону, мы как раз таки нарушаем установленное тут значение, для того что бы повернуть вертолёт вокруг оси вращения лопостей в право или лево.

int Lr = 32; //* по умолчанию нейтраль джойстиков
int Fb = 32;


Serial.begin(9600);
}


void loop() {
    
   int Throttle, Lr, Fb, Trimm;  //* Обьявляем наши переменные для использования в лупе.

  Throttle = analogRead(A2);   //* Читаем левый Джойстик
  Throttle = map(Throttle, 0, 1023, 63, 0); //* Делаем мапинг в нужный нам интервал. В моём случае из за особенностей потенциометра джойстика, мапинг следан наоборот, вам может это и не надо, тогда меняете местами 0 и 63
  Serial.print("Throttle:" ); //* Выводим в сериал порт
  Serial.print(Throttle);
  Serial.print("|");

  //* Делаем мап диапазона 0-1023 в диапазон 0-63 для Лево-право.  Логика следующая, потенциометр выдаёт нам от 0(крайнее левое положение джойстика) до 1023(крайнее правое положение джойстика), но нам то нужно что бы при движении
  //* джойстика, в ОБЕ стороны цифра увеличивалась. Поэтому мы делим смапленный диапазон на два, - левый правый, и делаем правильные мапинги для обоих диапазонов. Хочу обратить внимание, что 32 - нейтральное положение, одна
  //* цифра, это слишком мало, что бы джойстик вёл себя правильно, поэтому при мапинге, берём по тройке в каждую сторону от нейтрали, и говорим что всё это 32.
  Lr = analogRead(A5);
  if (Lr <= 509) {
    Lr = map(Lr, 0, 509, 31, 0);
  }
  else if (Lr >= 515) {
    Lr = map(Lr, 515, 1023, 33, 63);
  }
  else {
    Lr = 32;
  }
  Serial.print("Lr:" );
  Serial.print(Lr);
  Serial.print(" |");
  //* Лево-право - всё

  //* Делаем мап диапазона 0-1023 в диапазон 0-63 для Вперед-назад. Абсолютно аналогично Lr 
  Fb = analogRead(A4);
  if (Fb <= 509) {
    Fb = map(Fb, 0, 509, 31, 0);
  }
  else if (Fb >= 515) {
    Fb = map(Fb, 515, 1023, 33, 63);
  }
  else {
    Fb = 32;
  }
  Serial.print("Fb:" );
  Serial.print(Fb);
  Serial.print(" |");

  //* Вперёд-назад - всё.

Trimm = analogRead(A3); //* так же для трима. 
  if (Trimm <= 509) {
    Trimm = map(Trimm , 0, 509, 31, 0);
  }
  else if (Trimm >= 515) {
    Trimm = map(Trimm, 515, 1023,33, 63);
  }
  else {
    Trimm = 32;
  }
  
  Serial.print("Trimm:");
  Serial.println(Trimm);

  Transmit(Throttle, Lr, Fb, Trimm);  //* Вызываем функцию которая формирует собственно пакет, в том виде, как нам надо мигать диодами :)

} 

void Transmit(byte Throttle, byte Lr, byte Fb, byte Trimm) {  //* принимаем готовые значения четырех параметров для их отправки, создаём аррай и присваиваем в него наши четыре параметра
  static byte Code[4];  //* масив считанных с переменников значений
  byte mask = 32;     //*bitmask маска 32, поскольку у нас каждый парамерт 6 бит.
  int i;
  Code[0] = Throttle; //*  Увеличивает скорость вращения лопастей, линейно от 0(лопасти не вращаются) до 63(моторы работают на полную мощиность)
  Code[1] = Lr; //*  в мидпоинте никак не влияет на соотношение скорости работы двух главных моторов. При значении ниже мидпоинта, нижние лопости вращаются медленее вплоть до 0 - останов двигателя, выше - нижние лопасти начинают вращаться быстрее верхних, вплоть до 63.
  //* на самом деле неизвестно, при значениях выше мидпоинта, только ли ускоряется нижний мотор.. Возможно что наряду с ускорением нижнего, верхний замедляется. Просто в ином случае получается довольно расточительно с точки зрения КПД подьёмной силы.
  Code[2] = Fb; //* В мидпоинте задний мотор=стоп, меньше - против часовой, больше по часовой. В результате вертолёт наклоняется либо в перёд(хвост поднят выше плоскости вращения основных лопастей), либо назад, хвост опущен ниже плоскости вращения.
  Code[3] = Trimm;    //*  Тут устанавливается трим - статичное соотношение между скорость работы моторов. от мидпоинта - никак не влияем на этот параметр, от мидпоинта до 0 - прибавляем к скорости вращения нижних лопастей.
  //* Нужно помнить что трим, никак не связан с параметром Lr напрямую, это разные кофициенты, Lr - это фактически команда нарушить равновесие, а трим это кофициент необходимый для его поддержания.
 
  
  lb = Crc(Throttle, Lr, Fb, Trimm); //* Вызываем функцию расчёта последних 8 бит пакета. Складываем ответ в lb
  OutPulse(988);  //*  Это стартовый длинный пульс означающий начало пакета, 998 а не тысяча, потому что всё что отправляется в OutPulse должно делиться на 26(что бы было 38Гц)
  //* delayMicroseconds(2000);   2000us off.Это пульс сделующий за первым марком. В моём случае не используется, поскольку первая пауза уже значимая.


  for (i = 0; i < 4; i++) {      //* цикл через наши четыре принятых параметра Code[], - цикл через 4 полученных с переменников значения
    for (mask = 32; mask > 0; mask >>= 1) {   //* See Arduino reference for bit masking (really cool stuff!) сдвинули значение mask(буквально mask=mask>>1)в право на битпозицию(2 в десятичной), короче двигаться через наш параметр побитно.
     
      if (i != 0 || mask != 32) {
        OutPulse(312);         //* Так как в моём протоколе за первым длинным марком, сразу идёт первый значимый бит, пришлось добавить это условие, - в первом тике цикла, пульс не посылаем. А то получалось бы длиннй марк, сразу за ним опять марк, и значимая пауза.
      }

      if (Code[i] & mask) {         //*   бит по маске,которая каждый тик цикла, уменьшается на один бит от старшего к младшему.(Кстати мне не понятно почему берётся только старший бит для каждого тика цикла, но по факту, берётся именно он)
        delayMicroseconds(650);     //*  Длинная пауза после марка, - это единица.
      }
      else {
        delayMicroseconds(338);     //*  короткая пауза ,это ноль.
      }
    } 
  }  

  for (mask = 128; mask > 0; mask >>= 1) {  //* отправляем последние 8 бит из которых первые четыре статичны(первые два это канал 10 - для А, вторая пара это подсветка, - 11 для ВЫКЛ). И ещё четыре, это CRC. Так как у нас 8 бит а не 6 как в предыдущем цикле, маска должна начинаться со 128

    OutPulse(312); //* это марк перед каждой паузой

    if ( lb & mask) {         //* накладываем уменьшающуюся вправо маску, на расчитанные 8 бит, если в левом бите(кстати непонятно почему именно в старшем бите), стоит 1 и  в иаске 1, то делаем длинную паузу, в ином случае короткую.
      delayMicroseconds(650);     //* Длинная пауза после марка, - это единица.
    }
    else {
      delayMicroseconds(338);     //* короткая пауза ,это ноль.
    }
  } 
  OutPulse(312);  //* А вот и функция которая делает то, ради чего весь сыр-бор. Она волшебно мигает ир диодом :) всё что посылается в функцию пульс, должно делиться на 26, - (1000/26=38герц)
  delay(60); //* это пауза между пакетами.

} 


void OutPulse(int Pulse) {  //* принимаем некоторое число делящееся на 26.
  int p;

  for (p = 0; p < (Pulse / 26) - 1; p++) { //*  26 микросекунд, на один проход цикла
    digitalWrite(RED, HIGH);            //*  по две мс на работу порта, и 20 мс паузы = 24+2мс на отработку цикла.
    delayMicroseconds(10);
    digitalWrite(RED, LOW);
    delayMicroseconds(10);
  }
}
//* Функция расчёта CRC. Для расчёта нам нужно сформировать полный пакет без последних 4 бит(собственно CRC).
//* Далее нужно разделить получившийся пакет(28 бит=6бит throtle+6бит Вперед/назад+6бит Лево/право+6бит трим и плюс 4 бита,которые всегда 1011 - А канал и ВЫКЛ подсветка) разделить на 7 груп по 4 бита начиная со старшего.
//* после чего надо их сложить и сделать реверс результата - поменять все единицы на нули, а нули на единицы(бывает ещё просто реверс - с лева на право) и взять младшие 4 бита. Это и есть искомый CRC.
byte Crc(byte Throttle, byte Lr, byte Fb, byte Trimm) {
  static byte Param[4]; //* создаём аррай на наши четыре параметра
  Param[0] = Throttle;
  Param[1] = Lr;
  Param[2] = Fb;
  Param[3] = Trimm;

  byte x1 = (Param[0] >> 2) & 15; //* берем первый параметр из аррая, сдвигаем все биты на 2 вправо, и берем последние четыре
  byte x2 = ((Param[0] << 2) & 15) | ((Param[1] >> 4 ) & 15); //* берем первый параметр из аррая, сдвигаем на два в лево, и берём таки последние 4, потом берём 2 параметр, сдвигаем все биты на 4 вправо, и снова берём 4 младших. обьединяем первое со вторым.
  //* Так как у нас первый результат всегда имеет 00 в двух младших битах, получается что обьединяя результаты, мы получим два бита из первого результата(**00) и два из второго(00**)
  byte x3 = Param[1] & 15; //* просто накладываем маску 001111 на второй параметр и забираем последние четыре бита
  byte x4 = (Param[2] >> 2) & 15; //* Повторяем фактически три предыдущие операции.
  byte x5 = ((Param[2] << 2) & 15) | ((Param[3] >> 4 ) & 15);
  byte x6 = Param[3] & 15;
  byte x7 = Sst; //* Sst  всегда равно 1011
  LastByte = (x1 + x2 + x3 + x4 + x5 + x6 + x7) & 15; //* 15 - маска. Всё складываем и берём четыре младших бита

  LastByte = ~LastByte; //*делаем реверс
  LastByte = ((Sst << 4) | (LastByte & 15)); //* Берем Sst, и двигаем на 4 бита влево(нужно помнить что byte это всегда 8 бит, даже если значимых всего 4), обьединяем с нашим CRC(младшие 4 бита), получаем готовый для отправки пакет в 8 бит,- 24 бита параметров + 8 бит наш пакет= полный пакет отправки.

  
  return LastByte; //* возвращаем наши 8 бит.
}