Радиоуправляемая машинка

Автор Deny, 10:17:06

« предыдущая - следующая »

0 Пользователей и 1 гость просматривают эту тему.

Deny

Радиоуправляемая машинка



В этой статье рассказывается о том, как сделать радиоуправляемую платформу. Роль пульта дистанционного управления будет выполнять передатчик HobbyKing HK6S Transmitter. Приемником в данном случае может быть только HobbyKing HK6DF 6 Channel 2.4GHz Receiver. Однако, есть основания полагать, что подойдет любая RC аппаратура (с соответствующим приемником разумеется). Распознавание сигналов приемника и управление драйвером двигателей, в данном проекте, осуществляется ардуино. В статье приведено подробное описание всего железа и скетч.


Начну с краткого описания аппаратуры.



Продается передатчик вместе с приемником. Но если приемник сломается, то сильно горевать не стоит. Приемник можно купить отдельно.

В продаже есть два типа таких передатчиков: Mode1 и Mode2. Аппаратура Mode2 более распространена и я купил её. В дальнейшем я не буду специально указывать тип аппаратуры, но будет подразумеваться, что речь идет именно о Mode2.

В этом проекте я использовал Arduino Nano с USB UART преобразователем на микросхеме CH340.



Компактная, недорогая и живучая :)

Драйвер двигателей необходимо подбирать исходя из характеристик моторов. Основными параметрами влияющими на выбор драйвера являются напряжение и максимальный ток.

Напряжение подаваемое на электрические двигатели будет в районе 5 вольт. Этот параметр зависит скорее от источника тока (аккумулятор).

Максимальный ток использованных мной моторов составляет около 0.6 ампера. Этот параметр измеряется амперметром при заблокированном вале моторчика. Подключаете амперметр последовательно с мотором. Подаете питание. И руками осторожно останавливаете вал. Смотрите показания прибора. Долго блокировать вал моторчика нельзя.

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

Сразу скажу, что аккумулятор должен быть способен отдать необходимый моторам ток. Иначе просядет напряжение и ардуино перезагрузится.

И ещё. В реальной эксплуатации до блокировки моторы лучше не доводить. Это может привести к их выходу из строя.

Подходящий под мои требования драйвер двигателей изображен на картинке ниже.



Этот драйвер изготовлен на двух микросхемах L9110S. Работает в пределах от 2.5 вольт до 12 вольт. Максимальный допустимый ток для этой микросхемы составляет 0.8 ампера.

Источником тока у меня служат четыре последовательно соединенных NiMH аккумулятора AAA. В полностью заряженном состоянии они выдают немного больше 5 вольт. Поэтому в проекте не должно быть деталей не способных пережить это. Для ATmega328, CH340 и L9110S проблем быть не должно.

Сама платформа та же, что и в статье http://justforduino.blogspot.ru/2014/02/blog-post_23.html

В собранном виде это все выглядит как то так.



Подключается все просто.

Левый электродвигатель платформы подключаются к клеммам драйвера MOTOR-A. Правый электродвигатель платформы подключается к клеммам драйвера MOTOR-B.

Arduino --- Motor Driver

GND --- GND
VCC --- VCC
D8 --- A-IA
D9 --- A-IB
D10 --- B-IA
D11 --- B-IB

Arduino --- Receiver

GND --- GND
VCC --- VCC
D6 --- CH1
D7 --- CH2

Для управления платформой достаточно двух каналов. Я выбрал aileron и elevator. Это правый стик моей аппаратуры. У приемника aileron соответствует первый канал (CH1), а elevator соответствует второй канал (CH2).

Значение управляющего импульса на первом канале (aileron) изменяется при перемещении стика по горизонтали. В крайнем левом положении управляющий импульс составляет 1914 миллисекунд. В среднем положении управляющий импульс составляет 1525 миллисекунд. В крайнем правом положении управляющий импульс составляет 1119 миллисекунд.

Значение управляющего импульса на втором канале (elevator) изменяется при перемещении стика по вертикали. В крайнем верхнем положении управляющий импульс составляет 1905 миллисекунд. В среднем положении управляющий импульс составляет 1507 миллисекунд. В крайнем нижнем положении управляющий импульс составляет 1114 миллисекунд.


Информация с сайта http://justforduino.blogspot.ru/2015/02/blog-post.html     Автор: mihail shevchenko
​Не говорите, если это не изменит тишину к лучшему.

Deny

Эти цифры получены с помощью приборов описанных в http://justforduino.blogspot.ru/2015/01/blog-post.html и http://justforduino.blogspot.ru/2014/12/blog-post.html

Измерение длительности управляющего импульса в скетче будет осуществляться стандартной функцией ардуино pulseIn(pin, value, timeout) У этой функции три параметра. Первый это ножка ардуино на которой производится измерение. Для ардуино нано значение этого параметра может быть от 0 до 19. Второй параметр определяет измеряемый логический уровень. Возможно всего два варианта. Это HIGH или LOW. Последний параметр не обязателен. Функцию можно вызвать без указания этого параметра. timeout определяет в течении какого времени функция pulseIn пытается измерить длительность импульса. По умолчанию это одна секунда. Функция возвращает значение длительности измеренного импульса в миллисекундах (unsigned long int).

Как именно реализована эта функция можно посмотреть в файле wiring_pulse.c У меня этот файл находится по адресу D:\arduino-1.0.6\hardware\arduino\cores\arduino\wiring_pulse.c

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

Для левого мотора.







D8 (A-IA)D9 (A-IB)
0 (LOW)0 (LOW)мотор не крутится
1 (HIGH)1 (HIGH)мотор не крутится
0 (LOW)1 (HIGH)мотор крутится в одну сторону
1 (HIGH)0 (LOW)мотор крутится в другую сторону

Для правого мотора.







D10 (B-IA)D11 (B-IB)
0 (LOW)0 (LOW)мотор не крутится
1 (HIGH)1 (HIGH)мотор не крутится
0 (LOW)1 (HIGH)мотор крутится в одну сторону
1 (HIGH)0 (LOW)мотор крутится в другую сторону

Теперь нужно выяснить в какую сторону крутится левый и правый мотор в каждом конкретном случае. Вариантов масса. Я решил проблему с помощью специальных скетчей. Перед загрузкой скетчей нужно зафиксировать платформу так, чтобы колесики могли свободно вращаться, но платформа при этом оставалась неподвижной.


////////////////////////
//
// Arduino
//
////////////////////////
//
// Sketch: RC car
//

const unsigned char A_IA = 8;
const unsigned char A_IB = 9;
const unsigned char B_IA = 10;
const unsigned char B_IB = 11;

const unsigned char CH1 = 6; // Aileron
const unsigned char CH2 = 7; // Elevator

unsigned long int ail = 0;
unsigned long int ele = 0;

void forward_forward( void )
{
    digitalWrite(A_IA, HIGH);
    digitalWrite(A_IB, LOW);
    digitalWrite(B_IA, HIGH);
    digitalWrite(B_IB, LOW);
}

void backward_backward( void )
{
    digitalWrite(A_IA, LOW);
    digitalWrite(A_IB, HIGH);
    digitalWrite(B_IA, LOW);
    digitalWrite(B_IB, HIGH);
}

void forward_stop( void )
{
    digitalWrite(A_IA, HIGH);
    digitalWrite(A_IB, LOW);
    digitalWrite(B_IA, LOW);
    digitalWrite(B_IB, LOW);
}

void stop_forward( void )
{
    digitalWrite(A_IA, LOW);
    digitalWrite(A_IB, LOW);
    digitalWrite(B_IA, HIGH);
    digitalWrite(B_IB, LOW);
}

void backward_stop( void )
{
    digitalWrite(A_IA, LOW);
    digitalWrite(A_IB, HIGH);
    digitalWrite(B_IA, LOW);
    digitalWrite(B_IB, LOW);
}

void stop_backward( void )
{
    digitalWrite(A_IA, LOW);
    digitalWrite(A_IB, LOW);
    digitalWrite(B_IA, LOW);
    digitalWrite(B_IB, HIGH);
}

void stop_stop( void )
{
    digitalWrite(A_IA, LOW);
    digitalWrite(A_IB, LOW);
    digitalWrite(B_IA, LOW);
    digitalWrite(B_IB, LOW);
}

void setup()
{
    pinMode(CH1, INPUT);
    pinMode(CH2, INPUT);

    pinMode(A_IA, OUTPUT);
    pinMode(A_IB, OUTPUT);
    pinMode(B_IA, OUTPUT);
    pinMode(B_IB, OUTPUT);

    stop_stop();
}

void loop()
{
    ele = pulseIn(CH2, HIGH);
    ail = pulseIn(CH1, HIGH);

    if ( ail == 0 ) { ail = 1500; ele = 1500; }
    if ( ele == 0 ) { ail = 1500; ele = 1500; }

    if ( ail > 1700 && ele > 1700 )
        forward_stop();
    if ( ail < 1300 && ele > 1700 )
        stop_forward();
    if ( ail > 1700 && ele < 1300 )
        backward_stop();
    if ( ail < 1300 && ele < 1300 )
        stop_backward();
    if ( ail > 1700 && ele < 1700 && ele > 1300 )
        stop_stop();
    if ( ail < 1300 && ele < 1700 && ele > 1300 )
        stop_stop();
    if ( ail < 1700 && ail > 1300 && ele > 1700 )
        forward_forward();
    if ( ail < 1700 && ail > 1300 && ele < 1300 )
        backward_backward();
    if ( ail < 1700 && ail > 1300 && ele < 1700 && ele > 1300 )
        stop_stop();
}

//
// End
//
////////////////////////

Работать все должно так как описано ниже. Когда правый стик находится в центре, платформа неподвижна. При перемещении стика вперед, платформа должна двигаться вперед. При перемещении стика вперед и влево, левый моторчик двигает платформу вперед. При перемещении стика вперед и вправо, правый моторчик двигает платформу вперед. При перемещении стика назад, платформа должна двигаться назад. При перемещении стика назад и влево, левый моторчик двигает платформу назад. При перемещении стика назад и вправо, правый моторчик двигает платформу назад.

При повторении проекта возможно немного иное поведение платформы. Причина в том, что каждый мотор можно подключить к драйверу двумя способами. Тут следует либо поменять местами провода на "неправильном" моторе, либо подредактировать скетч.
Информация с сайта http://justforduino.blogspot.ru/2015/02/blog-post.html Автор: mihail shevchenko
​Не говорите, если это не изменит тишину к лучшему.

Deny

В данной статье связь приемника и ардуино происходит через pulseIn -"ele = pulseIn(CH2, HIGH);
ail = pulseIn(CH1, HIGH);" в принципе вариант рабочий, но долго  занимаясь этой темой пришел к выводу, что лучшее решение будет, если использовать прерывания. Скетч будет приведен чуть позже.


[/COLOR]Данный скетч  написан под ардуино мега 2560, использованы прерывания на аналоговых каналах с А8 по А13

// Иницилизация пинов приемника


const unsigned char CH1 = A8;    // Канал управления ходом (редуктор wheel_fr)
const unsigned char CH2 = A9;    // Канал управления ходом (редуктор wheel_rl)
const unsigned char CH3 = A10;   // Подъем  и опускание ствола (мотор М1 - barrel)
const unsigned char CH4 = A11;   // Поворот башни (мотор М2 - turret_turn)
const unsigned char CH5 = A12;   // Выстрел и серво-откат ствола
const unsigned char CH6 = A13;   // Включение, выключение движения танка
const unsigned char CH7 = A14;   // доп канал
const unsigned char CH8 = A15;   // доп канал


unsigned long timer[5];
byte last_channel[6];
int input[6];




ISR(PCINT2_vect) {
  timer[0] = micros();
  // channel 1 ---------------
  if (last_channel[0] == 0 && PINK & B00000001 ) {
    last_channel[0] = 1;
    timer[1] = timer[0];
  }
  else if (last_channel[0] == 1 && !(PINK & B00000001) ) {
    last_channel[0] = 0;
    input[0] = timer[0] - timer[1];
  }


  // channel 2 ---------------
  if (last_channel[1] == 0 && PINK & B00000010 ) {
    last_channel[1] = 1;
    timer[2] = timer[0];
  }
  else if (last_channel[1] == 1 && !(PINK & B00000010) ) {
    last_channel[1] = 0;
    input[1] = timer[0] - timer[2];
  }


  // channel 3 ---------------
  if (last_channel[2] == 0 && PINK & B00000100 ) {
    last_channel[2] = 1;
    timer[3] = timer[0];
  }
  else if (last_channel[2] == 1 && !(PINK & B00000100) ) {
    last_channel[2] = 0;
    input[2] = timer[0] - timer[3];
  }


  // channel 4 ---------------
  if (last_channel[3] == 0 && PINK & B00001000 ) {
    last_channel[3] = 1;
    timer[4] = timer[0];
  }
  else if (last_channel[3] == 1 && !(PINK & B00001000) ) {
    last_channel[3] = 0;
    input[3] = timer[0] - timer[4];
  }
  // channel 5 ---------------
  if (last_channel[4] == 0 && PINK & B00010000 ) {
    last_channel[4] = 1;
    timer[4] = timer[0];
  }
  else if (last_channel[4] == 1 && !(PINK & B00010000) ) {
    last_channel[4] = 0;
    input[4] = timer[0] - timer[4];
  }
  // channel 6 ---------------
  if (last_channel[5] == 0 && PINK & B00100000 ) {
    last_channel[5] = 1;
    timer[4] = timer[0];
  }
  else if (last_channel[5] == 1 && !(PINK & B00100000) ) {
    last_channel[5] = 0;
    input[5] = timer[0] - timer[4];
  }
}


void print() {
  Serial.print(input[0]);
  Serial.print(" - ");
  Serial.print(input[1]);
  Serial.print(" - ");
  Serial.print(input[2]);
  Serial.print(" - ");
  Serial.print(input[3]);
  Serial.print(" - ");
  Serial.print(input[4]);
  Serial.print(" - ");
  Serial.println(input[5]);
}




void setup() {


  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT16);
  PCMSK2 |= (1 << PCINT17);
  PCMSK2 |= (1 << PCINT18);
  PCMSK2 |= (1 << PCINT19);
  PCMSK2 |= (1 << PCINT20);
  PCMSK2 |= (1 << PCINT21);
  Serial.begin(9600);




  pinMode(CH1, INPUT);
  pinMode(CH2, INPUT);
  pinMode(CH3, INPUT);
  pinMode(CH4, INPUT);
  pinMode(CH5, INPUT);
  pinMode(CH6, INPUT);


}


void loop() {
  // put your main code here, to run repeatedly:
print ();
}
​Не говорите, если это не изменит тишину к лучшему.

Hal

Дени, прекрасная статья и опыт. Спасибо. То, что нам нехватало!
...I.M.

Deny

А вот еще один пример считывания с рс приемника информации, нашел на англоязычном ресурсе


This code uses pin change interrupts and timer 1 to mesure the time between the rise and fall of 3 channels of PPM (Though often called PWM, see this Arduino Forum thread) on a typical RC car receiver. It could be extended to as many channels as you like. It uses thePinChangeInt library to notice when the signal pin goes high and low, and the Timer1 library to record the time between.

#include <PinChangeInt.h> // http://playground.arduino.cc/Main/PinChangeInt


#include <PinChangeIntConfig.h>
#include <TimerOne.h> // http://playground.arduino.cc/Code/Timer1
#define NO_PORTB_PINCHANGES //PinChangeInt setup
#define NO_PORTC_PINCHANGES //only port D pinchanges (see: http://playground.arduino.cc/Learning/Pins)
#define PIN_COUNT 3 //number of channels attached to the receiver
#define MAX_PIN_CHANGE_PINS PIN_COUNT
#define RC_TURN 3 //Arduino pins attached to the receiver
#define RC_FWD 2
#define RC_FIRE 4

byte pin[] = {RC_FWD, RC_TURN, RC_FIRE}; //for maximum efficiency these pins should be attached

unsigned int time[] = {0,0,0}; // to the receiver's channels in the order listed here

byte state=0;

byte burp=0; // a counter to see how many times the int has executed

byte cmd=0; // a place to put our serial data

byte i=0; // global counter for tracking what pin we are on


void setup() {
Serial.begin(115200);
Serial.print(&quot;PinChangeInt ReciverReading test&quot;);
Serial.println(); //warm up the serial port
Timer1.initialize(2200); //longest pulse in PPM is usually 2.1 milliseconds,
//pick a period that gives you a little headroom.
Timer1.stop(); //stop the counter
Timer1.restart(); //set the clock to zero
for (byte i=0; i<3; i++)
{
pinMode(pin[i], INPUT); //set the pin to input
digitalWrite(pin[i], HIGH); //use the internal pullup resistor
}

PCintPort::attachInterrupt(pin[i], rise,RISING); // attach a PinChange Interrupt to our first pin

}

void loop() {
cmd=Serial.read(); //while you got some time gimme a systems report
if (cmd=='p')
{
Serial.print(&quot;time:\t&quot;);
for (byte i=0; i<PIN_COUNT;i++)
{
Serial.print(i,DEC);
Serial.print(&quot;:&quot;);
Serial.print(time[i],DEC);
Serial.print(&quot;\t&quot;);
}
Serial.print(burp, DEC);
Serial.println();
/* Serial.print(&quot;\t&quot;);
Serial.print(clockCyclesToMicroseconds(Timer1.pwmPeriod), DEC);
Serial.print(&quot;\t&quot;);
Serial.print(Timer1.clockSelectBits, BIN);
Serial.print(&quot;\t&quot;);
Serial.println(ICR1, DEC);*/
}

cmd=0;
switch (state)
{
case RISING: //we have just seen a rising edge
PCintPort::detachInterrupt(pin[i]);
PCintPort::attachInterrupt(pin[i], fall, FALLING); //attach the falling end
state=255;
break;
case FALLING: //we just saw a falling edge
PCintPort::detachInterrupt(pin[i]);
i++; //move to the next pin
i = i % PIN_COUNT; //i ranges from 0 to PIN_COUNT
PCintPort::attachInterrupt(pin[i], rise,RISING);
state=255;
break;
/*default:
//do nothing
break;*/
}
}

void rise() //on the rising edge of the currently interesting pin
{
Timer1.restart(); //set our stopwatch to 0
Timer1.start(); //and start it up




state=RISING;




// Serial.print('r');




burp++;




}










void fall() //on the falling edge of the signal




{




state=FALLING;




time[i]=readTimer1(); // read the time since timer1 was restarted




// time[i]=Timer1.read(); // The function below has been ported into the




// the latest TimerOne class, if you have the




// new Timer1 lib you can use this line instead




Timer1.stop();




// Serial.print('f');




}










unsigned long readTimer1() //returns the value of the timer in microseconds




{ //remember! phase and freq correct mode counts




//up to ICR1 then down again




unsigned int tmp=TCNT1;




char scale=0;




switch (Timer1.clockSelectBits)




{




case 1:// no prescale




scale=0;




break;




case 2:// x8 prescale




scale=3;




break;




case 3:// x64




scale=6;




break;




case 4:// x256




scale=8;




break;




case 5:// x1024




scale=10;




break;




}




while (TCNT1==tmp) //if the timer has not ticked yet




{




//do nothing -- max delay here is ~1023 cycles




}




tmp = ( (TCNT1>tmp) ? (tmp) : (ICR1-TCNT1)+ICR1 );//if we are counting down add the top value




//to how far we have counted down




return ((tmp*1000L)/(F_CPU /1000L))<<scale;




}

/*


Copyright 2011 Lex Talionis (Lex.V.Talionis at gmail)


This program is free software: you can redistribute it


and/or modify it under the terms of the GNU General Public


License as published by the Free Software Foundation,


either version 3 of the License, or (at your option) any


later version.


 


This code uses pin change interrupts and timer 1 to measure the


time between the rise and fall of 3 channels of PPM


(Though often called PWM, see http://forum.arduino.cc/index.php/topic,14146.html)


on a typical RC car receiver.  It could be extended to as


many channels as you like.  It uses the PinChangeInt library


to notice when the signal pin goes high and low, and the


Timer1 library to record the time between.
​Не говорите, если это не изменит тишину к лучшему.

Deny

Запись из дневника.

Теперь об актуальном! долго разбираясь с прерываниями http://arduino.ru/Tutorial/Arduino_Interrupts, пришел к выводу что это основа использования ардуино. Кто собирается заняться проектами, уделите этому аспекту огромное внимание.
Первым, что мне удалось сделать, это RC управление для танка на основе Mega2560 http://tankspb.com/showthread.php?t=559 пост №3. Следующим шагом случилось невероятно, но мне удалось перевести плату 27 Мгц от ХЛ на управление 2.4 Ггц, соответственно пульт приходится покупать новый, но зато можно использовать большое количество каналов. На разработку подтолкнула тема одного Московского товарища http://rc-master.ucoz.ru/publ/9-1-0-128, пока есть еще небольшие сбои, поэтому скетч не выкладываю, но танк реально управляется, у меня, к примеру, аппаратурой ФЛАЙ СКАЙ
​Не говорите, если это не изменит тишину к лучшему.