В данной статье мы рассмотрим создание на основе платы Arduino и модуля NRF24L01 пульта дистанционного управления (радиопередатчика, RC controller), который будет работать на частоте 2,4 ГГц. Данный пульт дистанционного управления (ДУ) можно использовать для управления практически любыми устройствами, в которых приемник построен на основе модуля NRF24L01.
Для взаимодействия с данным передатчиком вы можете использовать приемник на Arduino, который работает практически на этой же самой элементной базе, либо же использовать свои собственные совместимые схемы приемников.
Также ранее на нашем сайте мы рассматривали аналогичный пульт дистанционного управления на Arduino, но попроще. Рассматриваемый в данной статье пульт более функциональный.
Обзор проекта
С помощью данного пульта ДУ можно управлять любым проектом на основе платы Arduino по беспроводной сети, сделав лишь небольшие изменения на стороне приемника. Этот передатчик также можно использовать как любой коммерческий радиоуправляемый передатчик для управления радиоуправляемыми игрушками, автомобилями, дронами и т. д. Для этой цели ему просто нужен простой приемник Arduino, который затем генерирует соответствующие сигналы для управления этими устройствами.
Радиосвязь этого контроллера основана на модуле приемопередатчика NRF24L01, который при использовании с усиленной антенной может иметь стабильную дальность действия до 700 метров на открытом пространстве. Он имеет 14 каналов, 6 из которых являются аналоговыми входами, а 8 - цифровыми входами.
Он имеет два джойстика, два потенциометра, два тумблера, шесть кнопок и, кроме того, внутренний измерительный блок, состоящий из акселерометра и гироскопа, который также можно использовать для управления объектами, просто перемещая или наклоняя контроллер.
Принципиальная схема радиопередатчика на Arduino
Для начала давайте взглянем на принципиальную схему. «Мозгом» этого радиоуправляемого контроллера является плата Arduino Pro Mini, который питается от двух LiPo-батареек напряжением около 7,4 В. Мы можем подключить их непосредственно к контакту RAW Pro Mini, который имеет регулятор напряжения, снижающий напряжение до 5 В. Обратите внимание, что существует две версии Arduino Pro Mini: одна у меня работает с напряжением 5 В, а другая — с напряжением 3,3 В.
С другой стороны, модулю NRF24L01 строго требуется напряжение 3,3 В, и рекомендуется получать его из специального источника. Поэтому нам нужно использовать стабилизатор напряжения 3,3 В, который подключается к батареям и преобразует 7,4 В в 3,3 В. Также нам необходимо использовать развязывающий конденсатор рядом с модулем, чтобы поддерживать более стабильное напряжение и, таким образом, радиосвязь также будет более стабильной. Модуль NRF24L01 обменивается данными с Arduino по протоколу SPI, а модуль акселерометра и гироскопа MPU6050 использует протокол I2C.
Необходимые компоненты
- Плата Arduino Pro Mini (купить на AliExpress).
- Модуль nRF24L01 + PA + LNA (купить на AliExpress).
- Модуль акселерометра и гироскопа MPU6050 (купить на AliExpress).
- Потенциометр.
- Серводвигатель.
- Тумблер.
- Джойстик.
- Джойстик без коммутационной панели.
- HT7333 (стабилизатор напряжения 3,3в).
- Регулятор напряжения AMS1117 3,3 В (купить на AliExpress).
Реклама: ООО "АЛИБАБА.КОМ (РУ)" ИНН: 7703380158
Проектирование печатной платы для проекта
В итоге я использовал все аналоговые и цифровые контакты Arduino Pro Mini. Так что теперь, если я попытаюсь соединить все вместе с помощью соединительных проводов, это будет полный беспорядок. Поэтому я разработал специальную печатную плату, используя бесплатное онлайн-программное обеспечение для проектирования схем EasyEDA.
Здесь я учел эргономику контроллера и спроектировал его так, чтобы его можно было легко держать двумя руками, а все элементы управления находились в пределах досягаемости пальцев. Я закруглил края и добавил несколько отверстий диаметром 3 мм, чтобы позже можно было прикрепить печатную плату к чему-нибудь. Я разместил контакты для программирования Arduino Pro Mini на верхней стороне контроллера, чтобы к ним можно было легко получить доступ, если мы захотим перепрограммировать Arduino. Здесь мы также можем заметить, что я использовал контакты RX и TX Arduino для кнопок джойстика. Однако эти две линии необходимо отключить от чего-либо, пока мы загружаем скетч в Arduino. Поэтому они прерываются двумя контактами, которые затем можно легко соединить с помощью простых перемычек.
Обратите внимание: убедитесь, что у вас есть правильная версия Arduino Pro Mini для обработки печатной платы или изменения конструкции печатной платы в соответствии с ней. Вот сравнение фотографий трех разных версий, в зависимости от вашего Arduino и регулятора напряжения.
Вот ссылка на файлы проекта этой печатной платы. Откроется три разные версии на отдельных вкладках, и вы сможете выбрать ту, которая вам нужна.
Итак, закончив проектирование, я создал файл Gerber, необходимый для изготовления печатной платы.
Гербер-файлы для изготовления печатной платы:
Внешний вид изготовленной печатной платы представлен на следующем рисунке. Качество печатной платы отличное, все точно так же, как и в дизайне.
Сборка электронной части пульта ДУ
Хорошо, теперь мы можем перейти к сборке печатной платы. Я начал с пайки разъемов контактов Arduino Pro Mini. Самый простой и хороший способ сделать это — разместить их на макетной плате, чтобы они прочно оставались на месте во время пайки.
Pro Mini также имеет контакты по бокам, но учтите, что расположение этих контактов может различаться в зависимости от производителя.
Для моей конкретной модели мне нужно по 5 контактов для каждой стороны, оставив при этом один контакт GND пустым, поскольку я использовал его область ниже на печатной плате для проведения некоторых трассировок. Я припаял Arduino Pro Mini непосредственно к печатной плате и обрезал разъемы по длине. Рядом с ним находится модуль акселерометра и гироскопа MPU6050.
Затем припаял стабилизатор напряжения 3,3В с конденсатором рядом и еще один конденсатор возле модуля NRF24L01. У этого модуля есть три разные версии, и мы можем использовать здесь любую из них.
Я продолжил работу с контактами для программирования Arduino, контактами RX и TX, контактами источника питания и выключателем питания.
Затем, чтобы припаять потенциометры к печатной плате, мне пришлось удлинить их контакты с помощью разъемов для контактов.
Здесь можно отметить, что я заранее обрезал ручки, чтобы можно было правильно надеть на них колпачки. Однако потенциометры к плате мы припаяем чуть позже.
Затем я вставил и припаял на место два тумблера и два джойстика.
Наконец, осталось припаять четыре кнопки. Однако у них нет подходящей высоты, поэтому я снова использовал разъемы, чтобы немного расширить их контакты.
Вот и все, наша печатная плата готова, и мы можем продолжить изготовление крышки. Поскольку мне нравится, как выглядит печатная плата, и я хочу, чтобы ее было видно, я решил использовать для покрытия прозрачный акрил.
Здесь у меня есть прозрачный акрил толщиной 4 мм, который в настоящее время имеет защитную фольгу и кажется синим. Идея крышки состоит в том, чтобы сделать две пластины по форме печатной платы и закрепить одну из них на верхней стороне, а другую на нижней стороне печатной платы.
Поэтому я отметил форму печатной платы и с помощью ручной пилы по металлу вырезал акрил в соответствии с ней.
Затем с помощью простого рашпиля я доработал форму акрила. Две пластины получились великолепными и идеально сочетаются с печатной платой.
Затем я отметил места, где мне нужно сделать отверстия для прохождения компонентов. С помощью сверла диаметром 3 мм я сначала проделал 4 отверстия для крепления пластин к печатной плате. Для этих отверстий я также сделал зенковки, чтобы болты можно было соединить с пластинами.
Для отверстий под тумблеры и потенциометры я использовал сверло диаметром 6 мм, а для отверстий джойстика — сверло Форстнера диаметром 25 мм. Опять же, используя рашпиль, я подправил все отверстия.
Прежде чем собирать крышку, обратите внимание: на самом деле я припаял контактный разъем блока питания в перевернутом положении, чтобы к нему можно было добраться с задней стороны, где будет расположен аккумулятор.
Хорошо, теперь можно приступить к сборке чехла. Я начал с снятия защитной фольги с акрила, что, должен признаться, меня очень удовлетворило, потому что акрил теперь был таким чистым. Поэтому сначала я закрепил два потенциометра на верхней пластине, вставил крепежные болты диаметром 3 мм и установил дистанционные кольца диаметром 11 мм.
Затем я аккуратно соединил и закрепил верхнюю пластину и печатную плату с помощью болтов. На этом этапе я наконец припаял потенциометры к плате, поскольку раньше я не знал точно, на какой высоте они будут размещены.
Затем на задней панели я прикрепил держатель аккумулятора с помощью 2 болтов. Я закончил сборку крышки, прикрепив заднюю пластину к задней стороне печатной платы с помощью четырех крепежных болтов.
Наконец, мы можем прикрепить линии батареи к контактам источника питания, вставить и закрепить ручки на потенциометрах, вставить ручки джойстиков и прикрепить антенну к модулю NRF24l01. Вот и все, мы наконец закончили сборку RC-передатчика Arduino своими руками.
Теперь осталось запрограммировать плату Arduino. Для программирования платы Pro Mini нам понадобится интерфейс USB-последовательный UART, который можно подключить к разъему для программирования, расположенному на верхней стороне нашего контроллера.
Затем в меню инструментов Arduino IDE нам нужно выбрать плату Arduino Pro или Pro Mini, выбрать подходящую версию процессора, выбрать порт и выбрать метод программирования «USBasp».
Итак, теперь мы можем загружать код в плату Arduino.
Код пульта ДУ на основе платы Arduino
Давайте рассмотрим как работает код нашего передатчика. Итак, сначала нам нужно подключить библиотеку SPI и RF24 для беспроводной связи, а также библиотеку I2C для модуля акселерометра. Затем нам нужно определить цифровые входы, некоторые переменные, необходимые для приведенной ниже программы, создать радиообъект и задать адрес связи.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <SPI.h> #include <nRF24L01.h> #include <RF24.h> #include <Wire.h> // Define the digital inputs #define jB1 1 // Joystick button 1 #define jB2 0 // Joystick button 2 #define t1 7 // Toggle switch 1 #define t2 4 // Toggle switch 1 #define b1 8 // Button 1 #define b2 9 // Button 2 #define b3 2 // Button 3 #define b4 3 // Button 4 const int MPU = 0x68; // MPU6050 I2C address float AccX, AccY, AccZ; float GyroX, GyroY, GyroZ; float accAngleX, accAngleY, gyroAngleX, gyroAngleY; float angleX, angleY; float AccErrorX, AccErrorY, GyroErrorX, GyroErrorY; float elapsedTime, currentTime, previousTime; int c = 0; RF24 radio(5, 6); // nRF24L01 (CE, CSN) const byte address[6] = "00001"; // Address |
Затем нам нужно задать структуру, в которой мы будем хранить 14 входных значений контроллера. Максимальный размер этой структуры может составлять 32 байта, поскольку это ограничение буфера NRF24L01 или объем данных, которые модуль может отправить одновременно.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
/ Max size of this struct is 32 bytes - NRF24L01 buffer limit struct Data_Package { byte j1PotX; byte j1PotY; byte j1Button; byte j2PotX; byte j2PotY; byte j2Button; byte pot1; byte pot2; byte tSwitch1; byte tSwitch2; byte button1; byte button2; byte button3; byte button4; }; Data_Package data; //Create a variable with the above structure |
В разделе настройки нам необходимо инициализировать модуль MPU6050, а также мы можем рассчитать ошибку IMU, которая представляет собой значения, которые позже используются при расчете правильных углов модуля.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
void initialize_MPU6050() { Wire.begin(); // Initialize comunication Wire.beginTransmission(MPU); // Start communication with MPU6050 // MPU=0x68 Wire.write(0x6B); // Talk to the register 6B Wire.write(0x00); // Make reset - place a 0 into the 6B register Wire.endTransmission(true); //end the transmission // Configure Accelerometer Wire.beginTransmission(MPU); Wire.write(0x1C); //Talk to the ACCEL_CONFIG register Wire.write(0x10); //Set the register bits as 00010000 (+/- 8g full scale range) Wire.endTransmission(true); // Configure Gyro Wire.beginTransmission(MPU); Wire.write(0x1B); // Talk to the GYRO_CONFIG register (1B hex) Wire.write(0x10); // Set the register bits as 00010000 (1000dps full scale) Wire.endTransmission(true); } |
Также на нашем сайте вы можете найти более подробную информацию о работе MEMS-акселерометра и гироскопа.
Затем нам нужно инициализировать радиосвязь, активировать внутренние подтягивающие резисторы Arduino для всех цифровых входов и установить начальные значения по умолчанию для всех переменных.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
// Define the radio communication radio.begin(); radio.openWritingPipe(address); radio.setAutoAck(false); radio.setDataRate(RF24_250KBPS); radio.setPALevel(RF24_PA_LOW); // Activate the Arduino internal pull-up resistors pinMode(jB1, INPUT_PULLUP); pinMode(jB2, INPUT_PULLUP); pinMode(t1, INPUT_PULLUP); pinMode(t2, INPUT_PULLUP); pinMode(b1, INPUT_PULLUP); pinMode(b2, INPUT_PULLUP); pinMode(b3, INPUT_PULLUP); pinMode(b4, INPUT_PULLUP); |
В разделе цикла начните с чтения всех аналоговых входов, сопоставьте их значения от 0 до 1023 с байтовыми значениями от 0 до 255, поскольку мы уже определили переменные в нашей структуре как байты. Каждый ввод сохраняется в конкретной переменной данных из нашей структуры.
1 2 3 4 5 6 7 |
// Read all analog inputs and map them to one Byte value data.j1PotX = map(analogRead(A1), 0, 1023, 0, 255); // Convert the analog read value from 0 to 1023 into a BYTE value from 0 to 255 data.j1PotY = map(analogRead(A0), 0, 1023, 0, 255); data.j2PotX = map(analogRead(A2), 0, 1023, 0, 255); data.j2PotY = map(analogRead(A3), 0, 1023, 0, 255); data.pot1 = map(analogRead(A7), 0, 1023, 0, 255); data.pot2 = map(analogRead(A6), 0, 1023, 0, 255); |
Следует лишь отметить, что, поскольку мы используем подтягивающие резисторы, показания цифровых выводов равны 0 при нажатии кнопок.
1 2 3 4 5 6 7 8 |
// Read all digital inputs data.j1Button = digitalRead(jB1); data.j2Button = digitalRead(jB2); data.tSwitch2 = digitalRead(t2); data.button1 = digitalRead(b1); data.button2 = digitalRead(b2); data.button3 = digitalRead(b3); data.button4 = digitalRead(b4); |
Таким образом, используя функцию radio.write(), мы просто отправляем значения со всех 14 каналов на приемник.
1 2 |
// Send the whole data from the structure to the receiver radio.write(&data, sizeof(Data_Package)); |
В случае, если тумблер 1 включен, то для управления мы используем данные акселерометра и гироскопа.
1 2 3 |
if (digitalRead(t1) == 0) { read_IMU(); // Use MPU6050 instead of Joystick 1 for controling left, right, forward and backward movements } |
Таким образом, вместо значений X и Y джойстика 1 мы используем значения углов, которые мы получаем от IMU, которые мы предварительно конвертируем из значений от -90 до +90 градусов в байтовые значения от 0 до 255 соответственно.
1 2 3 |
// Map the angle values from -90deg to +90 deg into values from 0 to 255, like the values we are getting from the Joystick data.j1PotX = map(angleX, -90, +90, 255, 0); data.j1PotY = map(angleY, -90, +90, 0, 255); |
Полный код для нашего пульта ДУ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 |
/* DIY Arduino based RC Transmitter by Dejan Nedelkovski, www.HowToMechatronics.com Library: TMRh20/RF24, https://github.com/tmrh20/RF24/ */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> #include <Wire.h> // Define the digital inputs #define jB1 1 // Joystick button 1 #define jB2 0 // Joystick button 2 #define t1 7 // Toggle switch 1 #define t2 4 // Toggle switch 1 #define b1 8 // Button 1 #define b2 9 // Button 2 #define b3 2 // Button 3 #define b4 3 // Button 4 const int MPU = 0x68; // MPU6050 I2C address float AccX, AccY, AccZ; float GyroX, GyroY, GyroZ; float accAngleX, accAngleY, gyroAngleX, gyroAngleY; float angleX, angleY; float AccErrorX, AccErrorY, GyroErrorX, GyroErrorY; float elapsedTime, currentTime, previousTime; int c = 0; RF24 radio(5, 6); // nRF24L01 (CE, CSN) const byte address[6] = "00001"; // Address // Max size of this struct is 32 bytes - NRF24L01 buffer limit struct Data_Package { byte j1PotX; byte j1PotY; byte j1Button; byte j2PotX; byte j2PotY; byte j2Button; byte pot1; byte pot2; byte tSwitch1; byte tSwitch2; byte button1; byte button2; byte button3; byte button4; }; Data_Package data; //Create a variable with the above structure void setup() { Serial.begin(9600); // Initialize interface to the MPU6050 initialize_MPU6050(); // Call this function if you need to get the IMU error values for your module //calculate_IMU_error(); // Define the radio communication radio.begin(); radio.openWritingPipe(address); radio.setAutoAck(false); radio.setDataRate(RF24_250KBPS); radio.setPALevel(RF24_PA_LOW); // Activate the Arduino internal pull-up resistors pinMode(jB1, INPUT_PULLUP); pinMode(jB2, INPUT_PULLUP); pinMode(t1, INPUT_PULLUP); pinMode(t2, INPUT_PULLUP); pinMode(b1, INPUT_PULLUP); pinMode(b2, INPUT_PULLUP); pinMode(b3, INPUT_PULLUP); pinMode(b4, INPUT_PULLUP); // Set initial default values data.j1PotX = 127; // Values from 0 to 255. When Joystick is in resting position, the value is in the middle, or 127. We actually map the pot value from 0 to 1023 to 0 to 255 because that's one BYTE value data.j1PotY = 127; data.j2PotX = 127; data.j2PotY = 127; data.j1Button = 1; data.j2Button = 1; data.pot1 = 1; data.pot2 = 1; data.tSwitch1 = 1; data.tSwitch2 = 1; data.button1 = 1; data.button2 = 1; data.button3 = 1; data.button4 = 1; } void loop() { // Read all analog inputs and map them to one Byte value data.j1PotX = map(analogRead(A1), 0, 1023, 0, 255); // Convert the analog read value from 0 to 1023 into a BYTE value from 0 to 255 data.j1PotY = map(analogRead(A0), 0, 1023, 0, 255); data.j2PotX = map(analogRead(A2), 0, 1023, 0, 255); data.j2PotY = map(analogRead(A3), 0, 1023, 0, 255); data.pot1 = map(analogRead(A7), 0, 1023, 0, 255); data.pot2 = map(analogRead(A6), 0, 1023, 0, 255); // Read all digital inputs data.j1Button = digitalRead(jB1); data.j2Button = digitalRead(jB2); data.tSwitch2 = digitalRead(t2); data.button1 = digitalRead(b1); data.button2 = digitalRead(b2); data.button3 = digitalRead(b3); data.button4 = digitalRead(b4); // If toggle switch 1 is switched on if (digitalRead(t1) == 0) { read_IMU(); // Use MPU6050 instead of Joystick 1 for controling left, right, forward and backward movements } // Send the whole data from the structure to the receiver radio.write(&data, sizeof(Data_Package)); } void initialize_MPU6050() { Wire.begin(); // Initialize comunication Wire.beginTransmission(MPU); // Start communication with MPU6050 // MPU=0x68 Wire.write(0x6B); // Talk to the register 6B Wire.write(0x00); // Make reset - place a 0 into the 6B register Wire.endTransmission(true); //end the transmission // Configure Accelerometer Wire.beginTransmission(MPU); Wire.write(0x1C); //Talk to the ACCEL_CONFIG register Wire.write(0x10); //Set the register bits as 00010000 (+/- 8g full scale range) Wire.endTransmission(true); // Configure Gyro Wire.beginTransmission(MPU); Wire.write(0x1B); // Talk to the GYRO_CONFIG register (1B hex) Wire.write(0x10); // Set the register bits as 00010000 (1000dps full scale) Wire.endTransmission(true); } void calculate_IMU_error() { // We can call this funtion in the setup section to calculate the accelerometer and gury data error. From here we will get the error values used in the above equations printed on the Serial Monitor. // Note that we should place the IMU flat in order to get the proper values, so that we then can the correct values // Read accelerometer values 200 times while (c < 200) { Wire.beginTransmission(MPU); Wire.write(0x3B); Wire.endTransmission(false); Wire.requestFrom(MPU, 6, true); AccX = (Wire.read() << 8 | Wire.read()) / 4096.0 ; AccY = (Wire.read() << 8 | Wire.read()) / 4096.0 ; AccZ = (Wire.read() << 8 | Wire.read()) / 4096.0 ; // Sum all readings AccErrorX = AccErrorX + ((atan((AccY) / sqrt(pow((AccX), 2) + pow((AccZ), 2))) * 180 / PI)); AccErrorY = AccErrorY + ((atan(-1 * (AccX) / sqrt(pow((AccY), 2) + pow((AccZ), 2))) * 180 / PI)); c++; } //Divide the sum by 200 to get the error value AccErrorX = AccErrorX / 200; AccErrorY = AccErrorY / 200; c = 0; // Read gyro values 200 times while (c < 200) { Wire.beginTransmission(MPU); Wire.write(0x43); Wire.endTransmission(false); Wire.requestFrom(MPU, 4, true); GyroX = Wire.read() << 8 | Wire.read(); GyroY = Wire.read() << 8 | Wire.read(); // Sum all readings GyroErrorX = GyroErrorX + (GyroX / 32.8); GyroErrorY = GyroErrorY + (GyroY / 32.8); c++; } //Divide the sum by 200 to get the error value GyroErrorX = GyroErrorX / 200; GyroErrorY = GyroErrorY / 200; // Print the error values on the Serial Monitor Serial.print("AccErrorX: "); Serial.println(AccErrorX); Serial.print("AccErrorY: "); Serial.println(AccErrorY); Serial.print("GyroErrorX: "); Serial.println(GyroErrorX); Serial.print("GyroErrorY: "); Serial.println(GyroErrorY); } void read_IMU() { // === Read acceleromter data === // Wire.beginTransmission(MPU); Wire.write(0x3B); // Start with register 0x3B (ACCEL_XOUT_H) Wire.endTransmission(false); Wire.requestFrom(MPU, 6, true); // Read 6 registers total, each axis value is stored in 2 registers //For a range of +-8g, we need to divide the raw values by 4096, according to the datasheet AccX = (Wire.read() << 8 | Wire.read()) / 4096.0; // X-axis value AccY = (Wire.read() << 8 | Wire.read()) / 4096.0; // Y-axis value AccZ = (Wire.read() << 8 | Wire.read()) / 4096.0; // Z-axis value // Calculating angle values using accAngleX = (atan(AccY / sqrt(pow(AccX, 2) + pow(AccZ, 2))) * 180 / PI) + 1.15; // AccErrorX ~(-1.15) See the calculate_IMU_error()custom function for more details accAngleY = (atan(-1 * AccX / sqrt(pow(AccY, 2) + pow(AccZ, 2))) * 180 / PI) - 0.52; // AccErrorX ~(0.5) // === Read gyro data === // previousTime = currentTime; // Previous time is stored before the actual time read currentTime = millis(); // Current time actual time read elapsedTime = (currentTime - previousTime) / 1000; // Divide by 1000 to get seconds Wire.beginTransmission(MPU); Wire.write(0x43); // Gyro data first register address 0x43 Wire.endTransmission(false); Wire.requestFrom(MPU, 4, true); // Read 4 registers total, each axis value is stored in 2 registers GyroX = (Wire.read() << 8 | Wire.read()) / 32.8; // For a 1000dps range we have to divide first the raw value by 32.8, according to the datasheet GyroY = (Wire.read() << 8 | Wire.read()) / 32.8; GyroX = GyroX + 1.85; //// GyroErrorX ~(-1.85) GyroY = GyroY - 0.15; // GyroErrorY ~(0.15) // Currently the raw values are in degrees per seconds, deg/s, so we need to multiply by sendonds (s) to get the angle in degrees gyroAngleX = GyroX * elapsedTime; gyroAngleY = GyroY * elapsedTime; // Complementary filter - combine acceleromter and gyro angle values angleX = 0.98 * (angleX + gyroAngleX) + 0.02 * accAngleX; angleY = 0.98 * (angleY + gyroAngleY) + 0.02 * accAngleY; // Map the angle values from -90deg to +90 deg into values from 0 to 255, like the values we are getting from the Joystick data.j1PotX = map(angleX, -90, +90, 255, 0); data.j1PotY = map(angleY, -90, +90, 0, 255); } |
Код приемника (получателя)
Теперь давайте посмотрим, как мы можем получить эти данные. Вот простая схема приемника на Arduino и модуле NRF24L01. Конечно, вы можете использовать любую другую плату Arduino.
А вот простой код получателя, в котором мы будем получать данные и просто распечатывать их на последовательном мониторе, чтобы знать, что связь работает правильно. Нам снова нужно подключить библиотеку RF24 и определить объекты и структуру так же, как и в коде передатчика. В разделе настройки при определении радиосвязи нам необходимо использовать те же настройки, что и для передатчика, и установить модуль в качестве приемника с помощью функции radio.startListening().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
/* DIY Arduino based RC Transmitter Project == Receiver Code == by Dejan Nedelkovski, www.HowToMechatronics.com Library: TMRh20/RF24, https://github.com/tmrh20/RF24/ */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> RF24 radio(10, 9); // nRF24L01 (CE, CSN) const byte address[6] = "00001"; unsigned long lastReceiveTime = 0; unsigned long currentTime = 0; // Max size of this struct is 32 bytes - NRF24L01 buffer limit struct Data_Package { byte j1PotX; byte j1PotY; byte j1Button; byte j2PotX; byte j2PotY; byte j2Button; byte pot1; byte pot2; byte tSwitch1; byte tSwitch2; byte button1; byte button2; byte button3; byte button4; }; Data_Package data; //Create a variable with the above structure void setup() { Serial.begin(9600); radio.begin(); radio.openReadingPipe(0, address); radio.setAutoAck(false); radio.setDataRate(RF24_250KBPS); radio.setPALevel(RF24_PA_LOW); radio.startListening(); // Set the module as receiver resetData(); } void loop() { // Check whether there is data to be received if (radio.available()) { radio.read(&data, sizeof(Data_Package)); // Read the whole data and store it into the 'data' structure lastReceiveTime = millis(); // At this moment we have received the data } // Check whether we keep receving data, or we have a connection between the two modules currentTime = millis(); if ( currentTime - lastReceiveTime > 1000 ) { // If current time is more then 1 second since we have recived the last data, that means we have lost connection resetData(); // If connection is lost, reset the data. It prevents unwanted behavior, for example if a drone has a throttle up and we lose connection, it can keep flying unless we reset the values } // Print the data in the Serial Monitor Serial.print("j1PotX: "); Serial.print(data.j1PotX); Serial.print("; j1PotY: "); Serial.print(data.j1PotY); Serial.print("; button1: "); Serial.print(data.button1); Serial.print("; j2PotX: "); Serial.println(data.j2PotX); } void resetData() { // Reset the values when there is no radio connection - Set initial default values data.j1PotX = 127; data.j1PotY = 127; data.j2PotX = 127; data.j2PotY = 127; data.j1Button = 1; data.j2Button = 1; data.pot1 = 1; data.pot2 = 1; data.tSwitch1 = 1; data.tSwitch2 = 1; data.button1 = 1; data.button2 = 1; data.button3 = 1; data.button4 = 1; } |
В основном цикле с помощью функции Available() мы проверяем, есть ли входящие данные. Если это правда, мы просто читаем данные и сохраняем их в переменных структуры. Теперь мы можем распечатать данные на последовательном мониторе, чтобы проверить, правильно ли работает передача. Также с помощью функции millis() и оператора if мы проверяем, продолжаем ли мы получать данные или если мы не получаем данные в течение периода более 1 секунды, затем мы сбрасываем переменные к значениям по умолчанию. Мы используем это для предотвращения нежелательного поведения, например, если у дрона есть газ, и мы теряем соединение, он может продолжать улетать, пока мы не сбросим значения.
Итак, это все. Теперь мы можем реализовать этот метод получения данных для любого проекта Arduino.
Управление роботом-муравьем на Arduino с помощью нашего пульта ДУ
Вы можете использовать представленный передатчик (пульт ДУ) для управления роботом-муравьем на Arduino из предыдущей статьи на нашем сайте. Нам просто нужно прочитать данные и в соответствии с ними выполнить соответствующие функции, такие как движение вперед, влево, вправо, укус, атака и так далее.
ESC и сервоуправление с помощью RC-передатчика
Наконец, давайте посмотрим, как можно использовать этот передатчик для управления коммерческими радиоуправляемыми устройствами.
Обычно для этих устройств нам необходимо управлять сервоприводами или бесщеточными двигателями. Поэтому после получения данных от передатчика для управления сервоприводом мы просто используем библиотеку Arduino Servo и используем значения от 0 до 180 градусов. Для управления бесщеточным двигателем с помощью ESC мы снова можем использовать сервобиблиотеку для генерации сигнала ШИМ частотой 50 Гц, используемого для управления ESC. Изменяя рабочий цикл от 1000 до 2000 микросекунд, мы контролируем частоту вращения двигателя от нуля до максимума.
Обратите внимание, что на самом деле мы не можем связать стандартную систему RC-приемника с этой системой NRF24L01 2,4 ГГц. Вместо этого нам нужно модифицировать или создать собственный приемник, состоящий из Arduino и модуля NRF24L01. Отсюда мы можем генерировать соответствующие сигналы PWM (ШИМ) или PPM для управления RC-устройством.
Код программы при этом также претерпит некоторые изменения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
/* DIY Arduino based RC Transmitter Project == Receiver Code - ESC and Servo Control == by Dejan Nedelkovski, www.HowToMechatronics.com Library: TMRh20/RF24, https://github.com/tmrh20/RF24/ */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> #include <Servo.h> RF24 radio(10, 9); // nRF24L01 (CE, CSN) const byte address[6] = "00001"; unsigned long lastReceiveTime = 0; unsigned long currentTime = 0; Servo esc; // create servo object to control the ESC Servo servo1; Servo servo2; int escValue, servo1Value, servo2Value; // Max size of this struct is 32 bytes - NRF24L01 buffer limit struct Data_Package { byte j1PotX; byte j1PotY; byte j1Button; byte j2PotX; byte j2PotY; byte j2Button; byte pot1; byte pot2; byte tSwitch1; byte tSwitch2; byte button1; byte button2; byte button3; byte button4; }; Data_Package data; //Create a variable with the above structure void setup() { Serial.begin(9600); radio.begin(); radio.openReadingPipe(0, address); radio.setAutoAck(false); radio.setDataRate(RF24_250KBPS); radio.setPALevel(RF24_PA_LOW); radio.startListening(); // Set the module as receiver resetData(); esc.attach(9); servo1.attach(3); servo2.attach(4); } void loop() { // Check whether we keep receving data, or we have a connection between the two modules currentTime = millis(); if ( currentTime - lastReceiveTime > 1000 ) { // If current time is more then 1 second since we have recived the last data, that means we have lost connection resetData(); // If connection is lost, reset the data. It prevents unwanted behavior, for example if a drone jas a throttle up, if we lose connection it can keep flying away if we dont reset the function } // Check whether there is data to be received if (radio.available()) { radio.read(&data, sizeof(Data_Package)); // Read the whole data and store it into the 'data' structure lastReceiveTime = millis(); // At this moment we have received the data } // Controlling servos servo1Value = map(data.j2PotX, 0, 255, 0, 180); servo2Value = map(data.j2PotY, 0, 255, 0, 180); servo1.write(servo1Value); servo2.write(servo2Value); // Controlling brushless motor with ESC escValue = map(data.pot1, 0, 255, 1000, 2000); // Map the receiving value form 0 to 255 to 0 1000 to 2000, values used for controlling ESCs esc.writeMicroseconds(escValue); // Send the PWM control singal to the ESC } void resetData() { // Reset the values when there is no radio connection - Set initial default values data.j1PotX = 127; data.j1PotY = 127; data.j2PotX = 127; data.j2PotY = 127; data.j1Button = 1; data.j2Button = 1; data.pot1 = 1; data.pot2 = 1; data.tSwitch1 = 1; data.tSwitch2 = 1; data.button1 = 1; data.button2 = 1; data.button3 = 1; data.button4 = 1; } |
Здравствуйте. Пользуемся с сыном
RC моделями, устраиваем заезды на выходных. Используя аппаратуру собранную по вашим материалам.
Всё хорошо всё чётко работает.
Заметил всё таки что используя джойстики от ардуино всё таки тяжело поймать момент плавного старта машинки.
Может быть подскажите какие джойстики подойдут? Просто в платах приёмника разведено так что по размеру подходят только от ардуино. Плату переделывать не хочется. Это опять заказывать , ждать.
Может вы знаете от чего можно подобрать джойстики по размеру? PS2, PS3, PS4, PS5?
Добрый вечер. К сожалению, не могу по этому моменту подсказать что то конструктивное, не являюсь специалистом по джойстикам
Ок
Спасибо
Буду разбираться
Или менять адрес нужно только на стороне приёмника.? Передатчик остаётся мастером. Запутался я.
Нет, адрес нужно менять и на передатчике, и на приемнике если вы хотите чтобы этот комплект работал совершенно независимо от первого комплекта.
Спасибо вам. Всё получилось.
Это хорошо. Удачи вам в дальнейших проектах и заходите к нам еще.
Меняю адрес и на передатчике и приёмнике. Всё хорошо работает. Но при включении второго комплекта в работу первый перестаёт работать. Подскажите а скорость передачи данных тоже разная нужна?
Нет, скорость на это не влияет. Попробуйте поизменять значение адреса на другом комплекте, возможно, на тех двух адресах, которые вы выбрали, несущие частоты находятся вблизи друг друга, поэтому и появляется интерференция
Здравствуйте. Подскажите пожалуйста собрал я rc модель используя ваш передатчик и приёмник. Подскажите что нужно поменять в скетчах передатчика и приёмника чтобы работала вторая модель и друг другу не мешали.?
Добрый вечер, нужно изменить адрес в строке const byte address[6] = "00001";
Более подробно про эти аспекты можете прочитать в статье - как работает модуль nRF24L01 и как его подключить к Arduino
Здравствуйте. Изменил я строчку
esc.attach(9) на esc.attach(9, 1000, 2000).
Знаете управление мотором с джойстика пульта адекватное. Меня смущает что очень резко стартует мотор. Стартует плавно но очень как будто чувствительный
Джойстик. Тяжело понимать момент плавного старта. Ладно получу карту программирования ESC попробую попрагрлмировать сам ESC.
Может он так резко стартует из-за того что управление со джойстика и вы просто не успеваете передать сигнал на снижение скорости вращения до комфортной для вас? Попробуйте вместо джойстика использовать обычный потенциометр - на нем можно еще до включения питания установить маленькую скорость для двигателя
Т.е на передаичике я записываю
escValue = map(data.pot1, 0, 255, 1000, 2000);
А на приёмнике
escValue = map(data.j1PotY, 127, 255, 1000, 2000);
Правельно?
Но как я понял pot1 это потенциометр первый(левый на плате передатчика.
А j1PotY это джойстик левый.
Если в передатчике pot1 а в приёмнике
j1PotY то джойстик левый по Y так и останется для управления двигателем?
Вы меня немного запутали. В статье несколько скетчей приемников. Если хотите управлять с джойстика, которому соответствует переменная j1PotY, то в передатчике делаете data.j1PotY = map(analogRead(A0), 0, 1023, 0, 255) (если он у вас к A0 подключен), а в приемнике escValue = map(data.j1PotY, 0, 255, 1000, 2000). В данном случае имена переменных на стороне передатчика и приемника должны совпадать.
Я и сам запутался. Извините ради Бога.
Я собрал передатчик, собрал приёмник. Всё по вашим материалам.
Напечатал на 3d принтере rc модель багги.
Задача такая. Управление rc моделью
Использование одного сервопривода для рулежки, пару сервоприводов для передвижение fpv камеры. Ну и канал для управления бесколлекторным двигателем для передвижения вперёд назад.
С сервоприводами разобрался с использованием каналов на приёмнике
A0, A1, A2,A3, A6, A7. Тоже разобрался благодаря вам.
Канал 1-рулежка, канал 2,3,4,5. Управление ещё 4-мя сервоприводами. Канал 9 на esc.
Ну сейчас, когда имена переменным правильные дали, снова не регулируется скорость?
Имена переменных и были правельно
Джойстик и на передаче и на приёме.
Не пойму что менять
1000,2000 или
0,255?
Ни 0,255, ни 1000,2000 менять не нужно. Там все корректно.
Попробуйте изменить esc.attach(9) на esc.attach(9, 1000, 2000). Возможно, в этом проблема и после этого у вас команда ESC.writeMicroseconds заработает корректно
Я попробую ещё программировать ESC контролёр. Может в нём что то. Карту для программирования уже заказал.
Потаму что в принципе обороты пусть будут максимальными нужно чтобы плавнее стартовал ну и помягче тормозил
Ато как уже говорил у меня нейлоновые шестерни напечатанные на принтере со 100℅ заполнением разлетаются на куски.
Ну может поможет. Странно что вы застряли вроде бы на таком простом моменте. Управление скоростью или интенсивностью чего либо с помощью ШИМ такое простое и понятное. Проверьте, вдруг вы подключили ESC не к ШИМ контакту платы Ардуино, а к обычному - тогда механизм ШИМ работать не будет.
К 10-му пину ардуино. Канал номер 9.в приемнике
Ардуино какая, Уно или Нано? Там значок тильды должен быть нарисован если контакт поддерживает ШИМ
Ардуино pro mini
Да, на нем есть возможность использования ШИМ
Ладно буду разбираться.
Ещё раз прошу прощения за глупые вопросы. Спасибо вам большое.
Ок. Попробую. Спасибо вам за помощь
Удачи вам. Проверьте еще раз соответствие имен переменных, не закралась ли у вас там где-нибудь опечатка. В скетчах в статье на стороне передатчика и на стороне приемника используются немного отличающиеся названия переменных.
На приёмнике в принципе и запускаю функцию строчкой
esc.writeMicroseconds(escValue)
Правельно?
Просто не могу понять
На приёмнике меняю в строчке число 255 на 128 и всё равно двигатель вращается на максимальных оборотах
Вот в этой строчке
escValue = map(data.j1PotY, 127, 255, 1000, 2000);
В этой строчке я поменял ещё 127 на 0
Джойстик вперёд вращение вперёд
Джойстик назад вращение назад.
а 255 меняю на 128 всё равно двигатель на максимальных оборотах вращается.
Ну escValue вы правильное через радиоканал принимаете? Проверяли? И почему вы используете esc.writeMicroseconds, а не ESC.write?
escValue = map(data.j1PotY, 127, 255, 1000, 2000) - здесь неправильно поменяли. Во первых, порядок аргументов неправильный, во-вторых - значение с выхода АЦП может быть максимум 1023
Это ведь строчки из скетча по вашим проектам. Передатчик и приёмник на ардуино.
esc.writeMicroseconds
escValue = map(data.j1PotY, 127, 255, 1000, 2000)
Я повторил полностью ваш проект.
Даже платы заказывал из Китая.
Ну вы когда в функции esc.writeMicroseconds или ESC.write вручную подставляете различные значения аргументов у вас разве не изменяется скорость вращения двигателя? Вы пробовали просто управлять вашим BLDC двигателем с платы Ардуино?
Неа меняю значения скорость не меняется.
Вобщем на передатчике строчку
data.j1PotY = map(analogRead(A0), 0, 1023, 0, 255);
Ничего не меняем. ?
В строчке приёмника:
escValue = map(data.j1PotY, 127, 255, 1000, 2000);
esc.writeMicroseconds(escValue);
Нужно поменять число 255?
Ну тогда дело не в радиоинтерфейсе, а что то не так с управлением двигателем. Может ESC у вас бракованный. Попробуйте посмотреть в сети другие статьи как подключить BLDC двигатель к Ардуино. Может какие-нибудь другие библиотеки управления этим двигателям вам помогут. Если не помогут, то сокрее всего проблема в оборудовании.
data.j1PotY = map(analogRead(A0), 0, 1023, 0, 255) - ничего не меняем
escValue = map(data.j1PotY, 127, 255, 1000, 2000) - если используете именно esc.writeMicroseconds, то лучше оставить тогда как в коде в статье, то есть map(data.pot1, 0, 255, 1000, 2000). Проверьте что у вас data.pot1 должно быть (именно эта переменная используется в коде приемника, она отличается от переменной в передатчике), а не data.j1PotY. Если будете использовать ESC.write или другую библиотеку, тогда и аргументы функции map нужно будет изменить
Извините. Пишу в другой ветке.
Хочу управлять оборотами двигателя
Джойстиком как у вас в проекте.
По сути это тот же самый переменный резистор? Правельно?
Да, по сути, джойстик - это два переменных резистора. В приведенной программе передатчика есть же команды, которые считывают показания с джойстика, например, data.j1PotX = map(analogRead(A1), 0, 1023, 0, 255) - потом вы всю эту структуру data передаете через радиоканал, а на приеме вам просто нужно это принятое значение data.j1PotX передать в качестве аргумента в функцию ESC.write
Здравствуйте. Спасибо вам огромное.
Внёс изменения в скетчах. Активировал всё кнопки и переменные резисторы.
Извините ещё за один вопрос? А можно ли как то в скетче уменьшить обороты бесколлекторного мотора? У меня мотор с esc регулятором.
Чтобы уменьшить обороты какие изменения нужно в скетч внести?
Спасибо.
Добрый день. Вот статья про подключение BLDC двигателя к Arduino. Достаточно легко это сделать - просто регулируя коэффициент заполнения ШИМ
Менял я число 255 на число меньшее
И в приёмнике и в передатчике.
По работе ничего не меняется. У двигателя так же чересчур высокие обороты.
Буду или менять джойстики или покупать мотор послабее или закажу карту программирования для своего esc регулятора и настраивать в нём старт плавнее. Да именно старт потаму что обороты пусть будут. У меня шестерни из нейлона в модели разлетаются в куски. Потаму что очень резко стартует двигатель. Спасибо за помощь.
Или нужно менять числа 1000,2000?
Что то не пойму
Вы когда даете команду ESC.write(throttle), то в ней число throttle может быть от 0 до 1023. Чем больше это число - тем быстрее скорость вращения двигателя. Если вы значение throttle передаете со стороны передатчика, то проверьте с помощью монитора последовательной связи правильно ли его значение устанавливается на стороне передатчика
data.j1PotY = map(analogRead(A0), 0, 1023, 0, 255);
В этой строчке в передатчике нужно что то поменять или добавить?
От админа: нет, в этой строчке ничего менять не нужно если у вас на A0 подается корректное значение аналогового напряжения. Если не уверены в этом, то проверьте, добавив вывод analogRead(A0) в окно монитора последовательной связи. Смысл команды map здесь состоит в том, чтобы преобразовать значение, считанное с A0 (а оно может быть в диапазоне от 0 до 1023) в число в диапазоне от 0 до 255 (а именно такой диапазон для ШИМ в Ардуино). Если вы считаете с A0, к примеру, значение 512, то с помощью приведенной команды map оно преобразуется в значение 128 - и в вашем случае это будет соответствовать половинной скорости вращения двигателя
А как же тогда в скетче уменьшить коэффициент заполнения шим? Как уменьшить обороты двигателя? Где создавать команду ESC.write? После какой строчки?
От админа: ну вы каким образом хотите регулировать частоту вращения двигателя? Обычно это делается с помощью потенциометра. Вы подключаете потенциометр на стороне передатчика к аналоговому контакту, считываете с него аналоговое значение - оно будет в диапазоне 0-1023, преобразуете его указанной командой map в диапазон 0-255 и передаете это число через радиолинию. А в приемнике вы это принятое число помещаете в аргумент вызова функции ESC.write и в результате двигатель вращается у вас с необходимой скоростью. И если хотите еще что то спросить, то создайте плз новую ветку комментариев, в этой уже неудобно отвечать
Здравствуйте. Собрал по вашим материалам передатчик и приёмник. Разобрался как добавить в скетч приёмника контроль и управления сервоприводами с пульта при помощи джойстиков и двух переменных резисторов.
Подскажите а как в приемнике прописать работу с кнопок? И какими каналами можно управлять на приемнике с пульта.
Добрый вечер. Так вот же статья про приемник для данного передатчика. Можете использовать на приемнике любые каналы (как аналоговые, так и цифровые), модуль NRF24L01 имеет 14 каналов, 6 из которых являются аналоговыми, а 8 - цифровыми
Суть в том что я собрал и ваш приёмник.
В скетче активировал работу обоих джойстиков по всём осям. Активировал оба переменных резистора. В итоге подключил сервоприводы с канала C1 по канал C5 т.е могу управлять пятью сервоприводами с джойстиков и двух переменных резисторов. ( джойстик 1 по оси Y управляет ESS двигателя.)
Вопрос как прописать в скетче приёмника работу кнопок их на пульте 4+ 2 кнопки джойстиков. Хочу использовать всё возможности пульта на этом приемнике.
На приеме из принятых данных считываете соответствующие поля объекта data и в зависимости от их значения предпринимаете необходимые действия. А на передатчике нужно сделать так, чтобы при нажатии кнопок в объекте data эти поля устанавливались в необходимые значения.
Извините пожалуйста.
Не будет нагло с моей стороны если я попросил бы хоть примерно показать мне как и что записывать в скетчи передатчика и приёмника.?
К примеру, в коде передатчика вы считываете значение с кнопки с помощью data.button1 = digitalRead(b1). Потом вы передаете в эфир всю структуру с данными с помощью radio.write(&data, sizeof(Data_Package)). В приемнике вы принимаете всю эту структуру с помощью radio.read(&data, sizeof(Data_Package)). Потом из этой структуры вы просто достаете нужное значение, соответствующее нажатой кнопке на стороне передатчика, например, valuebutton1 = data.button1. А потом в зависимости от значения valuebutton1 выполняете нужное вам значение.
Если нужно считать значение кнопки джойстика, а не просто кнопки в схеме, то здесь на стороне передатчика изменится только оператор считывания этого значения, а затем вы помещаете этого значение, к примеру, в то же самое поле data.button1 и аналогичным образом передаете его через эфир и принимаете в приемнике
Добрый день. Подскажите пожалуйста, подойдёт этот пульт для управления моделью танка?
Требуется осуществить его передвижение, поворот башни, вертикальное перемещение пушки и стрельба. Хотелось бы подключить дымогенератор.
Добрый вечер. Да, я думаю 14 каналов вам должно хватить на все это