Skocz do zawartości

Pomocna odpowiedź

Podoba Ci się ten projekt? Zostaw pozytywny komentarz i daj znać autorowi, że zbudował coś fajnego!

Masz uwagi? Napisz kulturalnie co warto zmienić. Doceń pracę autora nad konstrukcją oraz opisem.

@spook, właśnie zaakceptowałem opis. Dziękuję za przedstawienie ciekawego projektu, zachęcam do prezentowania kolejnych DIY oraz aktywności na naszym forum 😉

  • 6 lat(a) później...
(edytowany)
Dnia 27.03.2019 o 20:08, spook napisał:

Trudne początki

Tak naprawdę to jest chyba mój pierwszy projekt w świecie Arduino! Zamarzyłem sobie zbudowanie własnego, terenowego pojazdu zdalnie sterowanego - takiego, na którym można potem zamontować jakiś chwytak, ramię albo kamerkę z przekaźnikiem FPV.

Kontroler

Tu akurat nie miałem większego wyboru, bo wtedy pod ręką miałem akurat Arduino Leonardo. Zaletą tej płytki jest kompatybilność z popularnymi shieldami do Uno a także złącze microUSB typu B (zamiast mało popularnego złącza "drukarkowego" w Uno). Na potrzeby tego niezbyt skomplikowanego projektu moc obliczeniowa tej płytki jest również całkowicie wystarczająca.

Podwozie

Moim planem było zbudowanie definitywnego i niepokonanego łazika marsjańskiego, więc zwykłe podwozie nie wchodziło w grę - koniecznie musiało być terenowe. Przegrzebałem naprawdę połowę Internetu w poszukiwaniu tego idealnego podwozia (ale - nie ukrywajmy - mieszczącego się również w moim budżecie) i w końcu mój wybór padł na podwozie Dagu DG012-ATV z napędem na cztery koła. Nieco podwyższony prześwit w stosunku do innych podwozi (dający nadzieję na pokonywanie niewielkich przeszkód), napęd na cztery koła - wszystko to brzmiało bardzo zachęcająco.

Czterema silnikami coś oczywiście musi obracać, razem z podwoziem nabyłem więc również czterokanałowy sterownik silników DFRobota (w postaci shieldu dla Arduino).

Serwo i czujnik

Żeby urozmaicić nieco projekt, dodałem do niego serwo modelarskie, na którym zamontowałem ultradźwiękowy czujnik odległości z założeniem, że spróbuję kiedyś napisać program do autonomicznego poruszania się.

RC

Od długiego czasu używam do zdalnego sterowania aparatury FrSky Taranis, więc oczywiście musiałem skorzystać z kompatybilnego odbiornika - w tym przypadku X8R.

Zasilanie

Projekt oczywiście musiał być mobilny, więc zdecydowałem się na zasilenie go dwucelowym akumulatorem Lipo; konieczne okazało się też zastosowanie układu BEC, który obniżył napięcie akumulatora do 5V.

Teraz pozostało już tylko zmontować wszystko w całość.

Montaż podwozia

Tu obyło się bez niespodzianek i problemów, po prostu skręciłem wszystko zgodnie z instrukcją i wyprowadziłem na zewnątrz przewody, którymi zasilane miały być silniki. Potem sprawy nieco się skomplikowały.

Wszystko rozbiło się generalnie o to, że jak bym nie ułożył elementów na podwoziu, po prostu nie było takiego ułożenia, żeby wszystko się zmieściło. Sprawy utrudniał również fakt zastosowania Leonardo, które - umówmy się - jest raczej kobylaste i znacznie lepiej sprawdziłoby się tu kompatybilne z tą płytką Arduino Micro. Do tego dochodził BEC, odbiornik, serwo (którego nota bene nie miałem jak zamocować, bo w wersji 4WD podwozia miejsce na serwo zajmowane było przez dwa dodatkowe silniki) no i oczywiście akumulator. Dlatego zdecydowałem się na umieszczenie wszystkiego ponad podwoziem, pozostawiając na dole sam akumulator.

Przykleiłem więc na dolnym pokładzie rzep, na którym mocowany jest akumulator - przeciążenia podczas poruszania robota są tak znikome, że jest to naprawdę pewny i sprawdzony sposób montażu (pozwalający też szybko zamontować albo zdemontować akumulator w razie potrzeby). Oprócz tego przykręciłem w narożnikach podwozia długie dystanse (kupione kiedyś w Chinach na potrzeby projektu quadrokoptera) i zabrałem się za przygotowywanie górnej części pojazdu.

20170514_141306.thumb.jpg.87191ed41e7c8d197ff13df93c4720e8.jpg20170515_190342.thumb.jpg.e1a918566dd7c91d8800bdc6bab3c7ab.jpg

Górne podwozie wykonałem z fragmentu płytki aluminiowej, którą dociąłem tak, by znalazła się dokładnie ponad górną częścią podwozia - i jednocześnie dzięki temu przykryła koła, z których pył mógł się dostawać do elektroniki.

20170513_194057.thumb.jpg.1fd2ef4d0f7e0f4ec5bd89af76f0ca9b.jpg20170513_230829.thumb.jpg.69df0e42e99a42861bf652e895a85766.jpg

W płytce wyciąłem otwór na serwo; ponieważ nie dysponuję żadnym sprzętem CNC, który pomógłby mi wyciąć równy, parametryczny otwór, rad nierad wziąłem do ręki wiertarkę, najpierw nawierciłem otwory, potem zamontowałem w niej frez i zacząłem ręcznie wycinać aluminium, a na końcu doszlifowałem wszystko pilnikami o zmniejszającej się ziarnistości. Cały proces poniżej:

20170514_125711.thumb.jpg.96224b4b4e55edc8390a936fcda34f19.jpg20170514_131207.thumb.jpg.b924ca4bf39b5850cef71f762e06e85f.jpg20170514_133446.thumb.jpg.fd3b535897656114a4c890199e650a4c.jpg

Teraz można było powoli przystąpić do montażu. Na pierwszy ogień poszło serwo, które na szczęście wpasowało się w wycięty przeze mnie otwór po prostu idealnie.

20170514_141709.thumb.jpg.91eff54f630230f00912a99bc60b919d.jpg20170514_141722.thumb.jpg.b5b74380f0e084629af7ce1e1774d572.jpg

Następnym krokiem było zamontowanie BECa, którego umieściłem pod górnym pokładem.

20170514_204857.thumb.jpg.4162ada83ecde14ace859d35ed68d00e.jpg

Przewód zasilający (zakończony złączem T-Dean) musiałem rozgałęzić, ponieważ jedna para przewodów musiała zostać połączona z BECem, który obniży napięcie do 5V dla części elektroniki, zaś druga para - do sterownika silników, który będzie potem je zasilał. Szczęśliwie silniki akceptują napięcie dwucelowego akumulatora LiPo - trzeba to koniecznie sprawdzić przed zakupem/montażem!

Arduino Leonardo można zasilić bezpośrednio z akumulatora, natomiast konieczne było przylutowanie odpowiedniej wtyczki (na zdjęciu po prawej stronie).

20170514_220438.thumb.jpg.3a368e38a4b66c61b0d06fe9798eb12b.jpg20170515_190437.thumb.jpg.357bb26166eec2114753d1fccc3d0ea8.jpg

Na koniec pozostało podłączenie wszystkich komponentów i otrzymujemy następujący efekt:

20170515_210046.thumb.jpg.f3efad08ff299ef491a748541e55c9c4.jpg

Programowanie

Pierwszym programikiem, który napisałem, był tester silników, który uruchamiał każdy z nich na pół sekundy. Kod wygląda następująco:

const int E1 = 3;  // Motor1 Speed
const int E2 = 11; // Motor2 Speed
const int E3 = 5;  // Motor3 Speed
const int E4 = 6;  // Motor4 Speed
 
const int M1 = 4;  // Motor1 Direction
const int M2 = 12; // Motor2 Direction
const int M3 = 8;  // Motor3 Direction
const int M4 = 7;  // Motor4 Direction
 
void M1_advance(char Speed)
{
    digitalWrite(M1, HIGH);
    analogWrite(E1, Speed);
}
 
void M2_advance(char Speed)
{
    digitalWrite(M2, LOW);
    analogWrite(E2, Speed);
}
 
void M3_advance(char Speed)
{
    digitalWrite(M3, LOW);
    analogWrite(E3, Speed);
}
 
void M4_advance(char Speed)
{
    digitalWrite(M4, HIGH);
    analogWrite(E4, Speed);
}
 
void M1_back(char Speed) 
{
    digitalWrite(M1, LOW);
    analogWrite(E1, Speed);
}
 
void M2_back(char Speed) 
 
{
    digitalWrite(M2, HIGH);
    analogWrite(E2, Speed);
}
 
void M3_back(char Speed) 
{
    digitalWrite(M3, HIGH);
    analogWrite(E3, Speed);
}
 
void M4_back(char Speed) 
{
    digitalWrite(M4, LOW);
    analogWrite(E4, Speed);
}
 
void setup() 
{
    for (int i = 3; i < 9; i++)
        pinMode(i, OUTPUT);
 
    for (int i = 11; i < 13; i++)
        pinMode(i, OUTPUT);
 
    pinMode(LED_BUILTIN, OUTPUT);
}
 
void loop() 
{
    digitalWrite(LED_BUILTIN, HIGH);
 
    M1_advance(100);
    delay(500);
    M1_advance(0);
 
    M2_advance(100);
    delay(500);
    M2_advance(0);
 
    M3_advance(100);
    delay(500);
    M3_advance(0);
 
    M4_advance(100);
    delay(500);
    M4_advance(0);
 
    digitalWrite(LED_BUILTIN, LOW);
    delay(2000); // Delay 2S
}

Jak widać, sterowanie silnikami odbywa się poprzez podawanie kierunku poprzez piny cyfrowe i prędkości poprzez piny analogowe - proste, jak konstrukcja cepa.

Teraz przyszła kolej na odbiornik RC i tu zaczęły się schody. Odbiorniki klasycznie podają stan każdego kanału poprzez sygnał PWM. W praktyce jest to seria naprzemiennych zer i jedynek, z których każda para 1+0 trwa 1/55 sekundy. Wartość możemy odczytać mierząc czas trwania sygnału 1: jeżeli jest to 1 milisekunda, przyjmujemy wartość minimalną ("-1"), jeżeli 1.5 milisekundy, to wartość środkową ("0"), zaś jeśli 2 milisekundy, to wartość maksymalną ("1"). Niektóre odbiorniki pozwalają na nieco szerszy zakres - od 0.5ms do 2.5ms, X8R domyślnie podaje wartości z tego pierwszego zakresu.

Ponieważ musiałem mierzyć wartości na dwóch różnych kanałach, zdecydowałem się skorzystać z mechanizmu przerwań. Działa on mniej więcej następująco: gdy zajdzie wybrane zdarzenie (na przykład zmiana stanu danego pinu z niskiego na wysoki), wykonanie programu jest przerywane, zostaje wykonana funkcja oznaczona jako tzw. handler przerwania, a po jej zakończeniu program wznawia wykonanie w miejscu, w którym został przerwany. Na Arduino można znaleźć bardzo wygodną bibliotekę EnableInterrupt, która uogólnia sposób dodawania i usuwania handlerów przerwań pomiędzy różnymi wersjami Arduino, a korzysta się z niej w następujący sposób:

#include "EnableInterrupt.h"
 
volatile int microsStart = 0;
 
void pin0Rising() 
{
    microsStart = micros();
    disableInterrupt(0);
    enableInterrupt(0, pin0Falling, FALLING);
}
 
void pin0Falling() 
{
    int microsEnd = micros();
    int diff = microsEnd - microsStart;
 
    if (diff > 1500) 
    {
        digitalWrite(LED_BUILTIN, HIGH);
    } 
    else 
    {
        digitalWrite(LED_BUILTIN, LOW);
    }
 
    disableInterrupt(0);
 
    enableInterrupt(0, pin0Rising, RISING);
}
 
void setup() 
{
    enableInterrupt(0, pin0Rising, RISING);
    pinMode(LED_BUILTIN, OUTPUT);
}
 
void loop() 
{
 
}

Po wpięciu przewodu sygnałowego do pinu 0 i uruchomieniu programiku na kontrolerze, wbudowana dioda powinna się zapalić w momencie, gdy ustawimy drążek w położeniu większym niż połowa.

Zwrócę jeszcze uwagę na magiczne słówko "volatile" przy deklaracji zmiennej - informuje ono kompilator, że zmienna ta nie może zostać zoptymalizowana (kompilator, a dokładniej optymalizator w niektórych przypadkach może w locie usunąć zmienną, na przykład jeżeli nie jest ona używana lub przez cały czas trwania programu ma zawsze tę samą wartość).

Dodam tu jeszcze jedną ważną informację: program obsługi przerwania powinien być tak krótki, jak to tylko możliwe! Ma to sporo sensu jeżeli się nad tym nieco dłużej zastanowić, ale ja na to nie wpadłem i w pierwotnej wersji programu cała obsługa silników umieszczona była właśnie w programie obsługi przerwania. I ku mojemu zdziwieniu, po uruchomieniu programu, pomimo tego, że drążki były w położeniu zerowym, dwa silniki zaczęły się obracać. Okazało się, że obsługa przerwania obliczającego czas trwania PWM na pierwszym kanale trwała tak długo, że sztucznie opóźniała wywołanie drugiego przerwania wykonującego te same obliczenia dla drugiego kanału, przez co podawało ono zawyżone wartości. Wystarczyło przebudować program w taki sposób, by obsługa silników znalazła się poza programami obsługi przerwań i wszystko wróciło do normy.

Pamiętajmy - to może oczywiste, ale mimo wszystko warto to powiedzieć - że mikrokontrolery nie są wielowątkowe, a przerwania nie są wątkami: wykonują się tylko jedno na raz. Drugie przerwanie musi czekać, aż pierwsze zostanie do końca obsłużone.

Przed napisaniem końcowego programu pozostało mi już tylko przetestować obsługę czujnika ruchu, programik wygląda następująco:

#include <NewPing.h>
 
#define ULTRASONIC_TRIGGER_PIN 9
#define ULTRASONIC_ECHO_PIN 10
#define MAX_DISTANCE 400
 
NewPing sonar(ULTRASONIC_TRIGGER_PIN, ULTRASONIC_ECHO_PIN, MAX_DISTANCE);
 
void setup() 
{
    pinMode(LED_BUILTIN, OUTPUT);
}
 
void loop() 
{
    digitalWrite(LED_BUILTIN, HIGH);
    delay(50);
    digitalWrite(LED_BUILTIN, LOW);
    delay(50);
 
    int ping = sonar.ping_cm();
 
    if (ping < 30)
        digitalWrite(LED_BUILTIN, HIGH);
    else
        digitalWrite(LED_BUILTIN, LOW);
 
    delay(200);
}

Również i tu korzystam z wbudowanej diody, która powinna zapalić się, gdy zmierzona przez czujnik odległość będzie mniejsza niż 30 cm.

Wreszcie program obsługujący całego robota:

#include <NewPing.h>
 
#include "EnableInterrupt.h"
#include "NewPing.h"
 
// Motor constants
 
// Motor1 Speed
#define MOTOR_1_SPEED_PIN 3
// Motor2 Speed
#define MOTOR_2_SPEED_PIN 11
// Motor3 Speed
#define MOTOR_3_SPEED_PIN 5
// Motor4 Speed
#define MOTOR_4_SPEED_PIN 6
 
// Motor1 Direction
#define MOTOR_1_DIR_PIN 4
// Motor2 Direction
#define MOTOR_2_DIR_PIN 12
// Motor3 Direction
#define MOTOR_3_DIR_PIN 8
// Motor4 Direction
#define MOTOR_4_DIR_PIN 7
 
// Ultrasonic constants
 
#define ULTRASONIC_TRIGGER_PIN 9
#define ULTRASONIC_ECHO_PIN 10
 
#define MAX_DISTANCE 400
 
// Servo constants
 
#define SERVO_PIN 13
 
// AI constants
 
#define DISTANCE_THRESHOLD_2 80
#define DISTANCE_THRESHOLD_1 40
 
// Ultrasonic control
 
NewPing sonar(ULTRASONIC_TRIGGER_PIN, ULTRASONIC_ECHO_PIN, MAX_DISTANCE);
 
// Motor control functions
 
void M1_advance(byte Speed) ///<Motor1 Advance
{
    digitalWrite(MOTOR_1_DIR_PIN, HIGH);
    analogWrite(MOTOR_1_SPEED_PIN, Speed);
}
 
void M2_advance(byte Speed) ///<Motor2 Advance
{
    digitalWrite(MOTOR_2_DIR_PIN, LOW);
    analogWrite(MOTOR_2_SPEED_PIN, Speed);
}
 
void M3_advance(byte Speed) ///<Motor3 Advance
{
    digitalWrite(MOTOR_3_DIR_PIN, LOW);
    analogWrite(MOTOR_3_SPEED_PIN, Speed);
}
 
void M4_advance(byte Speed) ///<Motor4 Advance
{
    digitalWrite(MOTOR_4_DIR_PIN, HIGH);
    analogWrite(MOTOR_4_SPEED_PIN, Speed);
}
 
void M1_back(byte Speed) ///<Motor1 Back off
{
    digitalWrite(MOTOR_1_DIR_PIN, LOW);
    analogWrite(MOTOR_1_SPEED_PIN, Speed);
}
 
void M2_back(byte Speed) ///<Motor2 Back off
{
    digitalWrite(MOTOR_2_DIR_PIN, HIGH);
    analogWrite(MOTOR_2_SPEED_PIN, Speed);
}
 
void M3_back(byte Speed) ///<Motor3 Back off
{
    digitalWrite(MOTOR_3_DIR_PIN, HIGH);
    analogWrite(MOTOR_3_SPEED_PIN, Speed);
}
 
void M4_back(byte Speed) ///<Motor4 Back off
{
    digitalWrite(MOTOR_4_DIR_PIN, LOW);
    analogWrite(MOTOR_4_SPEED_PIN, Speed);
}
 
// PWM control
 
volatile int microsYStart = 0;
volatile int microsXStart = 0;
volatile int microsZStart = 0;
 
volatile int yMicros = 0;
volatile int xMicros = 0;
volatile int zMicros = 0;
 
int UScounter = 0;
int USlock = 0;
 
// Pin 0 interrupt handling
 
void pin0Rising()
{
    microsYStart = micros();
    disableInterrupt(0);
    enableInterrupt(0, pin0Falling, FALLING);
}
 
void pin0Falling()
{
    yMicros = micros() - microsYStart;
    disableInterrupt(0);
    enableInterrupt(0, pin0Rising, RISING);
}
 
// Pin 1 interrupt handling
 
void pin1Rising()
{
    microsXStart = micros();
    disableInterrupt(1);
    enableInterrupt(1, pin1Falling, FALLING);
}
 
void pin1Falling()
{
    xMicros = micros() - microsXStart;
    disableInterrupt(1);
    enableInterrupt(1, pin1Rising, RISING);
}
 
// Pin2 interrupt handling
 
void pin2Rising()
{
    microsZStart = micros();
    disableInterrupt(2);
    enableInterrupt(2, pin2Falling, FALLING);
}
 
void pin2Falling()
{
    zMicros = micros() - microsZStart;
    disableInterrupt(2);
    enableInterrupt(2, pin2Rising, RISING);
}
 
void setup()
{
    for (int i = 3; i < 9; i++)
        pinMode(i, OUTPUT);
    for (int i = 11; i < 13; i++)
        pinMode(i, OUTPUT);
 
    pinMode(ULTRASONIC_TRIGGER_PIN, OUTPUT);
    pinMode(ULTRASONIC_ECHO_PIN, INPUT);
    pinMode(SERVO_PIN, OUTPUT);
 
    enableInterrupt(0, pin0Rising, RISING);
    enableInterrupt(1, pin1Rising, RISING);
    enableInterrupt(2, pin2Rising, RISING);
}
 
void loop()
{
    // Eval motor signals
 
    int yValue = (yMicros - 1500) / 2;
    int xValue = (xMicros - 1500) / 2;
 
    if (yValue > 260 || yValue < -260)
        yValue = 0;
 
    if (xValue > 260 || xValue < -260)
        xValue = 0;
 
    int leftEngines = constrain(xValue + yValue, -250, 250);
    int rightEngines = constrain(yValue - xValue, -250, 250);
 
    // Check for obstacles every 10th iteration
 
    UScounter++;
 
    if (UScounter >= 10)
    {
        UScounter = 0;
 
        int frontDistance = sonar.convert_cm(sonar.ping_median(5));
        if (frontDistance != 0 && frontDistance < DISTANCE_THRESHOLD_2)
        {
            if (frontDistance > DISTANCE_THRESHOLD_1)
                USlock = 1;
            else
                USlock = 2;
        }
        else
            USlock = 0;
    }
 
    if (USlock == 1) 
    {
        leftEngines = constrain(leftEngines, -250, 128);
        rightEngines = constrain(rightEngines, -250, 128);
    }
 
    if (USlock == 2) 
    {
        leftEngines = constrain(leftEngines, -250, 0);
        rightEngines = constrain(rightEngines, -250, 0);
    }
 
    if (abs(leftEngines) < 20)
    {
        M3_advance(0);
        M4_advance(0);
    }
    else if (leftEngines > 0)
    {
        M3_advance(leftEngines);
        M4_advance(leftEngines);
    }
    else
    {
        M3_back(-leftEngines);
        M4_back(-leftEngines);
    }
 
    if (abs(rightEngines) < 20)
    {
        M1_advance(0);
        M2_advance(0);
    }
    else if (rightEngines > 0)
    {
        M1_advance(rightEngines);
        M2_advance(rightEngines);
    }
    else
    {
        M1_back(-rightEngines);
        M2_back(-rightEngines);
    }
}

Pozwala on na kontrolowanie robota przy pomocy jednego drążka aparatury (dwóch kanałów). Oprócz tego program cyklicznie sprawdza odległość przed robotem i zabezpiecza przed wjechaniem w ścianę: w przypadku przeszkody znajdującej się bliżej niż 80 cm od robota, zostanie ograniczona jego maksymalna prędkość, natomiast jeżeli odległość ta będzie mniejsza niż 40cm, robot zatrzyma się całkowicie i niemożliwe będzie ruszenie nim do przodu.

Wnioski

To była prawdziwa frajda zobaczyć, jak robot rusza i jeździ zgodnie z instrukcjami z aparatury! Pierwszy skończony projekt. Nauczyłem się na nim dużo, bo okazało się, że w trakcie pracy podjąłem bardzo dużo nietrafnych decyzji.

  • Robot tak naprawdę nigdy nie wyjechał z domu, ma bardzo otwartą konstrukcję, która sprzyja dostawaniu się do obudowy pyłu i piachu. W domowych warunkach wystarczyłby natomiast napęd na dwa koła - w ten sposób miałbym też trochę miejsca wewnątrz obudowy.
  • Arduino Leonardo jest świetne, ale wielkie. Znacznie lepiej sprawdziłoby się Arduino Micro albo Teensy. To drugie nawet bardziej, bo shield do sterowania silnikami pożera dużo pinów i niewiele zostaje na odbiór sygnału z odbiornika RC. Udało mi się jeszcze podłączyć czujnik odległości, ale serwo wpiąłem już bezpośrednio w odbiornik, bo po prostu zabrakło mi pinów.
  • Nie ma większego sensu robić żadnych projektów (innych niż wstępne prototypy) na przewodzikach połączeniowych. Lepiej kupić sobie płytkę prototypową, złącza goldpinowe, przewodziki kydexowe i polutować wszystko na płytce - układ zajmuje znacznie mniej miejsce i jest znacznie mniej podatny np. na przypadkowe wyjęcie przewodu.

Chodzi mi po głowie wskrzeszenie projektu - właśnie przy pomocy Teensy oraz drukarki 3D, przy pomocy której mogę wydrukować całe nadwozie szyte na miarę. Ale podejrzewam, że zajmę się tym dopiero za jakiś czas...

Hej. Fajny pomysł przypomina mi to mój projekt pojazdu gąsienicowego.

IMG20250502174030.thumb.jpg.6286588d0992c21d1422578c84623448.jpg

Walcze z kodem do dzisiaj. Tylko ja mam arduino uno, MODUŁ STEROWNIKA SILNIKA L298N i aparature flysky i6x. Jak rusze drążkiem to albo włączają sie obroty i niechce zatrzymac albo zero reakcji. Czy Twoj kod by posowal do mojego pojazdu oczywiscie po przerobieniu. Pozdrawiam.

Edytowano przez Spidi-Andi
  • Lubię! 1
1 godzinę temu, Spidi-Andi napisał:

Czy Twoj kod by posowal do mojego pojazdu oczywiscie po przerobieniu. Pozdrawiam.

Pokaż swój kod...a najlepiej żeby nie śmiecić tutaj otwórz wątek, i znajdziemy przyczynę...

@farmaceuta Witam. Niewiem czy dobrze wstawiłem. 

#define CH1 3   // skręt
#define CH2 5   // przód/tył

#define ENA 6   // PWM - lewy silnik
#define ENB 11  // PWM - prawy silnik

#define IN1 7
#define IN2 4
#define IN3 9
#define IN4 10

const int DEAD_ZONE = 10;    // próg strefy martwej
const int PWM_MIN_US = 1000; // minimalny czas impulsu w us
const int PWM_MAX_US = 2000; // maksymalny czas impulsu w us

// Odczyt i filtrowanie sygnału PWM
int readChannel(int pin, int minLim, int maxLim, int defaultVal) {
  long pulse = pulseIn(pin, HIGH, 30000);
  // jeśli poza zakresem 1000–2000 us uznajemy brak sygnału
  if (pulse < PWM_MIN_US || pulse > PWM_MAX_US) {
    return defaultVal;
  }
  int val = map(pulse, PWM_MIN_US, PWM_MAX_US, minLim, maxLim);
  // dead-zone
  if (abs(val) < DEAD_ZONE) return 0;
  return val;
}

void setup() {
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
  pinMode(ENA, OUTPUT);
  pinMode(ENB, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  // surowe i zmapowane odczyty
  long raw1 = pulseIn(CH1, HIGH, 30000);
  long raw2 = pulseIn(CH2, HIGH, 30000);
  int ch1 = readChannel(CH1, -100, 100, 0);
  int ch2 = readChannel(CH2, -100, 100, 0);

  Serial.print("RAW1="); Serial.print(raw1);
  Serial.print("  CH1="); Serial.print(ch1);
  Serial.print("    RAW2="); Serial.print(raw2);
  Serial.print("  CH2="); Serial.println(ch2);

  // miks prędkości i skrętu
  int speedL = constrain(ch2 + ch1, -100, 100);
  int speedR = constrain(ch2 - ch1, -100, 100);

  // ustawienie kierunku i PWM
  // lewy silnik
  if (speedL > 0) {
    digitalWrite(IN1, HIGH);
    digitalWrite(IN2, LOW);
  } else if (speedL < 0) {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, HIGH);
  } else {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, LOW);
  }
  analogWrite(ENA, map(abs(speedL), 0, 100, 0, 255));

  // prawy silnik
  if (speedR > 0) {
    digitalWrite(IN3, HIGH);
    digitalWrite(IN4, LOW);
  } else if (speedR < 0) {
    digitalWrite(IN3, LOW);
    digitalWrite(IN4, HIGH);
  } else {
    digitalWrite(IN3, LOW);
    digitalWrite(IN4, LOW);
  }
  analogWrite(ENB, map(abs(speedR), 0, 100, 0, 255));

  delay(20);
}

 

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...