Rakenna 2WD robotti Arduino -ohjaimella – täydellinen opas
Yksi moottori pyörii penkissä. Kaksi moottoria vie robotin pöydän reunan yli – tai ajaa sen suoraan seinään, jos virransyöttö ja PWM-ramppi on tehty väärin. Jos olet jo pyörittänyt yhtä tasavirtamoottoria H-sillan läpi, seuraava askel on ilmeinen: laita kaksi moottoria pyörille ja anna niiden kuljettaa itseään. Tämä opas on suora jatko-osa artikkelille L298N Arduino -opas: DC-moottorin ohjaus alusta loppuun. Käyn H-sillan teorian, vapaakiertodiodit ja totuustaulun läpi vain lyhyesti, sillä nyt rakennetaan konkreettinen 2WD robotti Arduino -ohjaimella alusta loppuun.
Rakennamme alustan, jossa on kaksi vetopyörää yhteisellä akselilla, yksi vapaasti kääntyvä tukipyörä, L298N-ohjain, erillinen hakkurivirtalähde logiikalle ja gyro suoran linjan pitoon. Lopputulos ajaa suoraan, kaartaa, kääntyy paikallaan ja osaa pysähtyä ennen kuin se ajaa itsensä hajalle.
Sisällysluettelo
- 2WD robotti Arduino: osaluettelo ja hinnat
- Runkolayout ja painopiste
- Moottorien ja pyörien asennus
- Virransyöttö ilman resettejä
- Kytkentätaulukko
- Ensimmäinen ajo-ohjelma
- Differentiaaliohjaus selitettynä
- PWM-nopeussäätö ja käynnistysramppi
- MPU-6050: suoran linjan pito
- Vianetsintä
- Jatkokehitysideat
- Osat kerralla koriin
- Yhteenveto
2WD robotti Arduino -projektin osaluettelo ja hinnat
Tämä on tehokas variantti: 775-moottorit ovat järeitä ja painavia, ja niistä riittää voimaa mattoon, kynnyksiin ja ulkokäyttöön. Alempana kerron rehellisesti, missä ne törmäävät L298N:n rajoihin.

| Osa | Määrä | Hinta | Yhteensä |
|---|---|---|---|
| 775 DC Moottori 12–24V | 2 | 12,20 € | 24,40 € |
| Rengas 68x26mm | 2 | 2,40 € | 4,80 € |
| L298N duaali H-silta ohjain | 1 | 4,90 € | 4,90 € |
| ATmega328P ’Nano’ ohjain | 1 | 9,90 € | 9,90 € |
| LM2596 step-down -jännitemuunnin | 1 | 3,90 € | 3,90 € |
| MPU-6050 kiihtyvyysanturi- ja gyroskooppimoduuli | 1 | 4,90 € | 4,90 € |
| [Dupont-hyppylanka 20cm (20kpl) naaras-naaras](https://protocache.com/product/dupont-hyppylanka-20cm-20kpl-naaras-naaras/) | 1 | 3,90 € | 3,90 € |
| Yhteensä | ≈ 56,70 € | ||
Summaan ei sisälly akku, runkolevy eikä tukipyörä. Tukipyörän voi tehdä itse pultista ja 608ZZ laakerista (8×22×7 mm). Akselisovituksiin kannattaa varata kiinteä akselinyhdistäjä 5x8mm tai joustava akselinyhdistäjä 5x8mm. Kiinteä yhdistäjä on jäykempi ja pitää pyörän suorassa; joustava sietää pientä akselien linjausvirhettä, mutta antaa hieman periksi vääntömomentissa.
Kaksi rehellistä varoitusta ennen kuin tilaat
- 775 on ylimitoitettu L298N:lle. L298N kestää 2 A kanavaa kohti jatkuvasti (3 A piikki, 25 W). 775:n tyhjäkäyntivirta on jo 0,5–1,7 A ja jumivirta jopa 10 A. Kevyessä pöytärobotissa moottori pyörii kaukana jumista, mutta yksi seinään ajo riittää laukaisemaan L298N:n lämpösuojan tai rikkomaan sen. Aja 12 voltilla (ei 24 V), pidä PWM enintään noin 180/255 ja laita 5 A sulake akkujohtoon. Vakavampaan 775-robottiin oikea vastaus on MOSFET-ohjain (BTS7960/IBT-2 -luokka).
- 4 100 RPM on aivan liikaa 68 mm pyörälle. Suora veto tarkoittaa π × 0,068 m × 4100/60 ≈ 14,6 m/s eli noin 52 km/h. Käyttökelvotonta. Vaihtoehdot: aja hyvin matalalla PWM:llä (30–60/255), mikä antaa huonon vääntömomentin ja nykivän hidasajon, tai tee hihna- tai hammaspyörävälitys. Realistinen tavoite on 10:1–30:1, käytännössä 775-vaihdelaatikko tai hitaampi vaihteistettu moottori. Suora veto toimii, jos robotti on iso ja ajat sitä ulkona.
Runkolayout ja painopiste
Kaksi vetopyörää ja yksi tukipyörä muodostavat kolmipistetuen. Kolme kosketuspistettä on staattisesti määrätty: robotti ei koskaan keiku. Nelipyöräinen alusta epätasaisella lattialla pitää aina yhtä pyörää ilmassa, ellei siihen rakenneta jousitusta.
Ohjaus syntyy pelkästään pyörien nopeuserosta – ei ohjausservoa, ei niveliä, ei Ackermann-geometriaa. Tämä on mekaanisesti yksinkertaisin ajokykyinen robotti, ja se osaa kääntyä paikallaan, mikä on ahtaissa sisätiloissa valtava etu. Haittapuolet: sivuttain ei voi liikkua (ei-holonominen), suuntatarkkuus riippuu pyörien luistosta, ja tukipyörän on käännyttävä vapaasti tai se vastustaa käännöstä.
Nyrkkisäännöt sijoitteluun
- Vetopyörät samalle akselilinjalle koaksiaalisena parina. Kääntökeskipiste on pyörien kosketuspintojen välisen viivan keskellä.
- Tukipyörä toiseen päähän robotin keskilinjalle. Taakse, jos anturit katsovat eteen.
- Painopiste vetoakselin ja tukipyörän väliin, painotettuna vetoakselille: noin 60–70 % painosta vetopyörille. Liikaa painoa tukipyörälle → vetopyörät luistavat. Liian vähän → robotti nousee takakenoon kiihdytyksessä.
- 775-moottorit painavat 300–350 g kappale, eli moottorit ovat suurin osa massasta. Asenna ne matalalle ja lähelle vetoakselia. Akku matalalle ja keskelle, hieman vetoakselia kohti.
- Raideväli L = pyörien kosketuspintojen etäisyys. 42 mm moottorirungot ja kiinnikkeet mahtuvat 150–200 mm levylle, jolloin L ≈ 150–170 mm. Kirjoita L muistiin – tarvitset sen kaikkeen käännösmatematiikkaan.
- Tukipyörän korkeus pitää täsmätä. 68 mm pyörä nostaa vetoakselin 34 mm lattiasta. Jos tukipyörä on eri korkeudella, runko nokkaa ja renkaan kosketuspinta siirtyy.
- Runkomateriaali: 3–5 mm akryyli, vaneri tai 2 mm alumiini. 150 × 200 mm on mukava koko. Kaksi kantta (moottorit ja akku alle, elektroniikka päälle) pitää johdotuksen siistinä.
Moottorien ja pyörien asennus

- Poraa pohjalevy: kaksi moottorikiinnikkeen reikäparia samalle akselilinjalle, tukipyörän reiät ja ruudukko M3-reikiä elektroniikalle.
- Pultaa kiinnikkeet ja kiristä moottorit paikoilleen 42 mm:n rungon kiinnitysreikien kautta. 775:ssä on M4-reiät noin 29 mm reikäkehällä.
- Peilaa vasen ja oikea kiinnike. Koska moottorit osoittavat vastakkaisiin suuntiin, sama sähköinen napaisuus pyörittää niitä vastakkaisiin mekaanisiin suuntiin. Tämä on ykkösyllätys ensimmäisellä ajolla. Korjaa se joko johdotuksessa (vaihda OUT3/OUT4 keskenään) tai ohjelmassa
INVERT_RIGHT-lipulla. - Sovita pyörät. Rengas 68x26mm -navassa on keskellä sylinterimäinen akselireikä, ja 775:ssä on 5 mm D-akseli (toinen sivu tasattu), pituus 16 mm. Käytännössä tarvitset 5 mm napa-adapterin, jonka kiristysruuvi puristuu D:n tasoa vasten – tai 5×8 mm akselinyhdistäjän ja 8 mm tappiakselin 608ZZ-laakerissa. Älä ohita tätä vaihetta: löysä napa kuluu pyöreäksi hetkessä.
- Pyöritä pyöriä kädellä ja tarkista, että ne pyörivät suorassa ilman heittoa.
- Asenna tukipyörä ja varmista tasaisella pöydällä, että kaikki kolme kosketuspistettä koskettavat.
- Asenna L298N (ruuviliittimet moottoreita kohti), LM2596 ja Nano ylätasolle. Kytke teho vasta viimeisenä, kun LM2596:n lähtö on trimmattu 5,00 volttiin.
Virransyöttö ilman resettejä
Yleisin syy siihen, että aloittelijan robotti nytkähtää eteen ja alkaa alusta, on virransyöttö – ei koodi. Kolme mekanismia:
- Jännitekuoppa. Moottorien käynnistys- ja jumivirta romahduttaa yhteisen kiskon jännitteen. Kun ATmega328P:n syöttö painuu alle brown-out-rajan, piiri resetoi ja sketsi alkaa uudelleen
setup()-funktiosta. Kaksi 775:tä yhtä aikaa on monen ampeerin askelkuorma. - Johtuva häiriö. Harjallinen tasavirtamoottori kipinöi kommutaattorilla ja ruiskuttaa suurtaajuista roskaa kiskoon. Se sekoittaa I2C-liikenteen ja AD-muunnokset, vaikka jännite ei koskaan putoaisi resetrajalle.
- Vastasähkömotorinen voima. H-silta katkoo induktiivista kuormaa, ja V = −L·di/dt tuottaa piikkejä. Moduulin vapaakiertodiodit leikkaavat suurimman osan, mutta loppu jää kiskoon.
L298N:n oma 78M05 on noin 1 A:n lineaariregulaattori, joka on kytketty täsmälleen tähän likaiseen syöttöön. Se on huono valinta robotin aivoille. Käytä tätä arkkitehtuuria:

Akku (3S LiPo 11,1 V tai 12 V lyijyakku)
├─► pääkatkaisija ─► 5 A sulake
│ ├─► L298N +12V (moottoriteho, likainen kisko)
│ └─► LM2596 IN+ (oma haara suoraan akulta)
│ └─► OUT = 5,00 V ─► Nano 5V (puhdas kisko)
└─► kaikki GND yhteen pisteeseen (tähtimaa)
Olennaista on kaksi erillistä haaraa akulta, ei yhtä kiskoa joka syöttää toista. LM2596 on hakkuri, jolla on oma tulokondensaattori ja kela, joten se vaimentaa moottoripuolen kuoppia tehokkaasti. Lisätietoa säätämisestä on artikkelissa L298N-oppaassa ja jännitemuuntimen valinnasta postauksessa Jännitemuunnin vertailussa: buck, boost ja buck-boost.
Muistilista virransyötölle
- Poista L298N:n 5V-hyppylankakorkki äläkä kytke moduulin 5V-pinniä Nanoon. Muuten kaksi regulaattoria taistelee samasta solmusta.
- GND on pakko yhdistää. L298N GND, LM2596 OUT− ja Nano GND samaan pisteeseen. Ilman yhteistä maata IN1–IN4- ja ENA/ENB-tasoilla ei ole vertailukohtaa eikä mikään toimi. Käytä tähtimaata: yksi riviliitin, lyhyet paksut johdot, jotta moottorin paluuvirta ei kulje signaalimaan kautta.
- 470–1000 µF:n / 25 V:n elektrolyyttikondensaattori L298N:n +12V- ja GND-napojen väliin (moduulissa on 220 µF, kahdelle moottorille enemmän on parempi).
- 100 nF keraaminen suoraan moottorin napojen väliin, valinnaisesti myös kummastakin navasta runkoon. Tämä tappaa kipinöinnin melun lähteellä.
- Kierrä moottorijohdot pareittain ja pidä ne kaukana MPU-6050:n I2C-johdoista.
- 100 µF + 100 nF Nanon 5V-pinnille.
- 5 A lattasulake akun plussaan ja kunnollinen pääkatkaisija. 10 ampeerin jumivirran kanssa tämä ei ole valinnaista.
LM2596:n säätö oikeassa järjestyksessä
Kytke tulo akulta, älä kytke mitään lähtöön, mittaa yleismittarilla OUT+/OUT− ja kierrä 25-kierroksista trimmeriä, kunnes lukema on tasan 5,00 V. Katkaise virta ja kytke vasta sitten Nano. Muista 1,5 V pudotusvara: 5 V lähtöön tarvitaan vähintään 6,5 V tuloa. 2S LiPo (7,4 V) on rajatapaus, 3S (11,1 V) on mukava.
Syötä Nano 5V-pinnin kautta, koska LM2596 on jo säätänyt jännitteen – näin ohitat Nanon oman regulaattorin, mikä on tehokkaampaa ja viileämpää. Vaihtoehtoisesti säädä LM2596 esimerkiksi 9 volttiin ja syötä VIN-pinniin.
L298N:n jännitehäviö – rehellinen luku
L298N:n lähtöaste on bipolaaritransistoreita, ei MOSFETeja. Kokonaishäviö (ylä- ja alapuoli yhdessä) on tyypillisesti noin 1,8 V yhdellä ampeerilla, pahimmillaan 3,2 V, ja jopa 4,9 V kahdella ampeerilla. Käytännössä:
- 12 V akusta moottori näkee kuormitettuna noin 9–10 V.
- 7,4 V akusta moottori voi nähdä vain 4,9 V – siksi 2S-robotit tuntuvat voimattomilta.
- Piirissä palaa 2 A:lla noin 7,4–9,8 W. Jäähdytyslevy kuumenee aidosti ja lämpösuoja katkaisee ajon.
- Moduulin käytännön minimisyöttö on 6–7 V.
Kytkentätaulukko

| Moduulin napa | Nano-pinni | Huomio |
|---|---|---|
| ENA | D5 (PWM) | poista hyppylankakorkki! vasen nopeus |
| IN1 | D7 | vasen suunta A |
| IN2 | D8 | vasen suunta B |
| IN3 | D9 | oikea suunta A |
| IN4 | D10 | oikea suunta B |
| ENB | D6 (PWM) | poista hyppylankakorkki! oikea nopeus |
| GND | GND | pakollinen yhteinen maa |
| +12V | akku + (sulakkeen kautta) | 5–35 V, käytännössä 12 V |
| 5V | — | EI kytketä, hyppylanka pois |
| OUT1 / OUT2 | vasen moottori | vaihda keskenään jos suunta väärin |
| OUT3 / OUT4 | oikea moottori | vaihda keskenään jos suunta väärin |
| LM2596 IN+ / IN− | akku + / − | oma haara akulta |
| LM2596 OUT+ | Nano 5V | säädä 5,00 V ENNEN kytkentää |
| LM2596 OUT− | Nano GND | tähtimaa |
| MPU-6050 VCC | Nano 5V | moduulissa oma 3,3 V regulaattori |
| MPU-6050 SDA / SCL | A4 / A5 | I2C |
| MPU-6050 AD0 | — (auki) | osoite 0x68 |
Miksi PWM juuri D5 ja D6? Nanon PWM-pinnit ovat D3, D5, D6, D9, D10 ja D11. D5 ja D6 käyvät noin 980 Hz:llä (Timer0), muut noin 490 Hz:llä. Korkeampi taajuus tarkoittaa vähemmän kuuluvaa moottorin vinkunaa. Timer0 pyörittää myös millis()-laskuria, joten älä muuta sen esijakajaa – tavallinen analogWrite() on täysin turvallinen. Bonus: jos lisäät myöhemmin Servo-kirjaston, se varaa Timer1:n eli D9/D10, joten PWM kannattaa pitää D5/D6:ssa.
Ensimmäinen ajo-ohjelma

/* 2WD-robotti Arduinolla – perusajo
* Nano + L298N + 2x DC-moottori | Protocache
*/
// ---- Pinnit ----
const uint8_t ENA = 5; // vasen PWM (~980 Hz, Timer0)
const uint8_t IN1 = 7; // vasen suunta
const uint8_t IN2 = 8;
const uint8_t IN3 = 9; // oikea suunta
const uint8_t IN4 = 10;
const uint8_t ENB = 6; // oikea PWM
// ---- Kalibrointi ----
const bool INVERT_LEFT = false; // true jos puoli pyörii väärään suuntaan
const bool INVERT_RIGHT = true; // moottorit ovat peilikuvia toisistaan
const float LEFT_TRIM = 1.00; // hienosäädä suoraan ajoon
const float RIGHT_TRIM = 0.96;
const int MIN_PWM = 45; // alle tämän moottori ei pyöri (kuollut alue)
const int MAX_PWM = 180; // suojaa L298N:ää: älä aja täysillä
// ---- Pehmeä kiihdytys ----
const int RAMP_STEP = 4; // PWM-yksikköä per askel
const unsigned long RAMP_MS = 15; // askelväli millisekunteina
int targetLeft = 0, targetRight = 0; // -255..255, haluttu nopeus
int currLeft = 0, currRight = 0; // nykyinen, rampitettu arvo
unsigned long lastRamp = 0;
void setup() {
pinMode(ENA, OUTPUT); pinMode(ENB, OUTPUT);
pinMode(IN1, OUTPUT); pinMode(IN2, OUTPUT);
pinMode(IN3, OUTPUT); pinMode(IN4, OUTPUT);
Serial.begin(115200);
stopNow();
delay(1500); // aikaa nostaa robotti pöydältä
}
// Kirjoittaa yhden puolen H-sillalle. speed: -255..255
void driveSide(uint8_t in_a, uint8_t in_b, uint8_t en,
int speed, bool invert, float trim) {
if (invert) speed = -speed;
int mag = (int)(abs(speed) * trim);
if (mag > 0) mag = constrain(mag, MIN_PWM, MAX_PWM); // ohita kuollut alue
if (speed > 0) { digitalWrite(in_a, HIGH); digitalWrite(in_b, LOW); }
else if (speed < 0) { digitalWrite(in_a, LOW); digitalWrite(in_b, HIGH); }
else { digitalWrite(in_a, LOW); digitalWrite(in_b, LOW); } // jarru
analogWrite(en, mag);
}
// Ei-blokkaava ramppi: kutsu tätä joka loopissa
void updateRamp() {
if (millis() - lastRamp < RAMP_MS) return;
lastRamp = millis();
if (currLeft < targetLeft) currLeft = min(currLeft + RAMP_STEP, targetLeft);
if (currLeft > targetLeft) currLeft = max(currLeft - RAMP_STEP, targetLeft);
if (currRight < targetRight) currRight = min(currRight + RAMP_STEP, targetRight);
if (currRight > targetRight) currRight = max(currRight - RAMP_STEP, targetRight);
driveSide(IN1, IN2, ENA, currLeft, INVERT_LEFT, LEFT_TRIM);
driveSide(IN3, IN4, ENB, currRight, INVERT_RIGHT, RIGHT_TRIM);
}
// ---- Liikekomennot: asettavat vain tavoitteen ----
void setSpeeds(int l, int r) { targetLeft = l; targetRight = r; }
void forward (int s) { setSpeeds( s, s); }
void backward(int s) { setSpeeds(-s, -s); }
void arcLeft (int s, int d) { setSpeeds(s - d, s); } // d = nopeusero
void arcRight(int s, int d) { setSpeeds(s, s - d); }
void pivotLeft (int s) { setSpeeds(-s, s); } // tankkikäännös
void pivotRight(int s) { setSpeeds( s, -s); }
void stopSoft() { setSpeeds(0, 0); } // rampittaa nollaan
void stopNow() { // hätäpysäytys, jarru
targetLeft = targetRight = currLeft = currRight = 0;
digitalWrite(IN1, LOW); digitalWrite(IN2, LOW);
digitalWrite(IN3, LOW); digitalWrite(IN4, LOW);
analogWrite(ENA, 0); analogWrite(ENB, 0);
}
// Odota annettu aika mutta pidä ramppi käynnissä
void runFor(unsigned long ms) {
unsigned long t0 = millis();
while (millis() - t0 < ms) updateRamp();
}
void loop() {
forward(150); runFor(2000);
stopSoft(); runFor(600);
pivotRight(140); runFor(700); // ~90° – säädä kokeilemalla
stopSoft(); runFor(400);
forward(150); runFor(2000);
arcLeft(150,60); runFor(1500); // loiva vasen kaarre
stopSoft(); runFor(1000);
backward(120); runFor(1200);
stopSoft(); runFor(2000);
}
Aja ensimmäinen testi robotti pöydän reunalla pyörät ilmassa. Tarkista, että molemmat pyörät pyörivät eteen komennolla forward(). Jos toinen pyörii väärään suuntaan, käännä INVERT_RIGHT.
Testausjärjestys – älä ohita askelia
- Pelkästään Nano USB:llä, moottoriakku irti. Mittaa yleismittarilla ENA/ENB-pinneiltä jännite:
forward(150)antaa keskiarvona noin 150/255 × 5 V ≈ 2,9 V. Näin varmistat, että logiikka toimii ennen kuin tehoa on kytketty. - Moottorit irti L298N:n ruuviliittimistä, akku kiinni. Mittaa OUT1/OUT2- ja OUT3/OUT4-napojen väliltä, että napaisuus vaihtuu suuntakomennoilla.
- Moottorit kiinni, pyörät ilmassa. Tarkista pyörimissuunnat ja etsi
MIN_PWM: laske arvoa, kunnes moottori surisee pyörimättä, ja lisää sitten 5–10 yksikköä. - Lattialla, käsi kytkimellä. Mittaa suoran ajon poikkeama 2 metrillä ja säädä
RIGHT_TRIM. Jos robotti kaartaa 10 cm oikealle 200 cm:n matkalla, oikean puolen pyörä pyörii hieman liian hitaasti – nostaRIGHT_TRIM-arvoa yhdellä sadasosalla kerrallaan ja mittaa uudelleen. - Vasta tämän jälkeen lisää MPU-6050 ja gyrosäätö.
Differentiaaliohjaus selitettynä
Merkitään L = raideväli metreinä, r = pyörän säde (0,034 m 68 mm renkaalle) sekä ω_L ja ω_R = vasemman ja oikean pyörän kulmanopeudet (rad/s). Suora kinematiikka pyörien nopeuksista robotin liikkeeseen:
- Etenemisnopeus: v = r·(ω_R + ω_L) / 2
- Kääntymisnopeus: ω = r·(ω_R − ω_L) / L
- Sijainnin päivitys: ẋ = v·cos θ, ẏ = v·sin θ, θ̇ = ω
Käänteinen kinematiikka halutusta liikkeestä pyörien nopeuksiin:
- ω_L = (v − ω·L/2) / r
- ω_R = (v + ω·L/2) / r
- Hetkellinen kääntösäde: R = v / ω
Viisi tapausta
| ω_L | ω_R | Tulos |
|---|---|---|
| sama, + | sama, + | suoraan eteen, R = ∞, ω = 0 |
| sama, − | sama, − | suoraan taakse |
| hitaampi + | nopeampi + | kaarto vasemmalle; pienempi suhde = tiukempi kaarre |
| 0 | + | kääntyminen pysähtyneen pyörän ympäri, R = L/2 |
| − | + | tankkikäännös paikallaan, R = 0 |
Lasketaan konkreettisesti, kun L = 0,16 m ja r = 0,034 m:
- Molemmat 10 rad/s → v = 0,34 m/s, ω = 0 → suoraan.
- ω_L = 8, ω_R = 12 rad/s → v = 0,34 m/s, ω = 0,85 rad/s (≈49 °/s), R = 0,40 m.
- ω_L = −10, ω_R = +10 → v = 0, ω = 4,25 rad/s (≈244 °/s) → pyörii paikallaan.
Huomaa mittasuhde: 20 % nopeusero pyörien välillä antaa vain 40 cm säteisen kaarteen. Pienet erot merkitsevät paljon – ja juuri siksi kaksi identtiseksi ostettua moottoria ei koskaan aja täysin suoraan. Valmistushajonta, harjojen kuluminen ja erilainen kitka tuottavat muutaman prosentin eron, joka näkyy heti kaartona. Korjaa se joko empiirisellä trimmikertoimella (RIGHT_TRIM = 0.96, mittaa 2 m ajolla poikkeama) tai suljetulla säätösilmukalla gyron avulla.
Nimeä käännöstyypit ja koodaa ne erikseen: kaarto (molemmat eteen, toinen hitaammin – sujuva ja etenee), tankkikäännös (pyörät vastakkain, R = 0) ja yhden pyörän käännös (toinen pysähtyy, R = L/2). Tankkikäännös hankaa renkaita sivuttain ja on epätarkin, mutta sisätiloissa se on korvaamaton. Odometriaa varten: renkaan kehä on π × 68 mm = 213,6 mm, ja täysi 360° tankkikäännös vaatii π × L eli L = 160 mm:llä 502,7 mm pyörämatkaa, eli 2,35 pyörän kierrosta.
PWM-nopeussäätö ja käynnistysramppi
MIN_PWM on kuollut alue. Alle noin 40–50/255 arvoilla tasavirtamoottorin vääntö on pienempi kuin sen oma lepokitka: moottori surisee mutta ei pyöri. Rajaus MIN_PWM-arvoon tarkoittaa yksinkertaisesti “jos pyydetään liikettä, pyydetään ainakin sen verran että se todella liikkuu”. MAX_PWM = 180 puolestaan rajoittaa tarkoituksella virtaa ja lämpöä L298N:ssä ja pitää 775:n pyörimisnopeuden järkevänä.
Ramppi on ei-blokkaava ja perustuu millis()-aikaan, ei delay()-kutsuihin. Tämä on välttämätöntä, koska samassa silmukassa pitää myöhemmin lukea gyroa ja antureita. Arvoilla RAMP_STEP 4 ja RAMP_MS 15 nousu 0 → 150 kestää noin 38 askelta × 15 ms ≈ 560 ms.
Miksi ramppi kannattaa? Äkillinen 0 → 150 hyppy saa moottorit vetämään koko käynnistysvirtansa kerralla, mikä romahduttaa jännitteen ja voi resetoida ohjaimen. Samaan aikaan vääntömomentin askel nostaa korkean painopisteen robotin takakenoon ja pyörät luistavat. Kaksi riviä koodia korjaa sekä sähköisen että mekaanisen ongelman.
Erota vielä jarrutus ja vapaakierto: IN1 = IN2 = LOW ja EN ylhäällä oikosulkee moottorin navat, jolloin moottori toimii generaattorina oikosulkuun – dynaaminen jarrutus pysäyttää nopeasti. EN = LOW jättää lähdöt kellumaan, jolloin robotti liukuu. Jarrutusmatkat ovat erilaiset, ja viivalle pysähtyvä robotti tarvitsee jarrun. Siksi stopSoft() rampittaa pehmeästi ja stopNow() jarruttaa heti.
MPU-6050: suoran linjan pito

Gyro sulkee säätösilmukan: sen sijaan että arvaisit trimmikertoimen, mittaat todellisen kääntymisnopeuden ja korjaat. Kytkentä ja perusteet löytyvät artikkelista MPU-6050 Arduino-opas: kytkentä, koodi ja kallistuskulmat.
/* Suoran ajolinjan pito gyrolla – lisää edelliseen sketsiin */
#include <Wire.h>
const uint8_t MPU_ADDR = 0x68;
const float GYRO_LSB_250DPS = 131.0f; // ±250 °/s -alueella 131 LSB/(°/s)
float gyroZbias = 0.0f; // nollataan käynnistyksessä
float heading = 0.0f; // integroitu kulma, astetta
unsigned long lastUs = 0;
const float KP_STRAIGHT = 3.0f;
const float KD_STRAIGHT = 0.4f;
float prevErr = 0;
void mpuBegin() {
Wire.begin();
Wire.beginTransmission(MPU_ADDR);
Wire.write(0x6B); Wire.write(0x00); // PWR_MGMT_1: herätä
Wire.endTransmission(true);
Wire.beginTransmission(MPU_ADDR);
Wire.write(0x1B); Wire.write(0x00); // GYRO_CONFIG: ±250 °/s
Wire.endTransmission(true);
delay(100);
}
int16_t readGyroZraw() {
Wire.beginTransmission(MPU_ADDR);
Wire.write(0x47); // GYRO_ZOUT_H
Wire.endTransmission(false);
Wire.requestFrom((int)MPU_ADDR, 2, true);
return (int16_t)(Wire.read() << 8 | Wire.read());
}
// Robotti EI SAA liikkua kalibroinnin aikana!
void calibrateGyro() {
long sum = 0;
const int N = 2000;
for (int i = 0; i < N; i++) { sum += readGyroZraw(); delay(2); }
gyroZbias = (float)sum / N;
Serial.print("Gyro Z bias: "); Serial.println(gyroZbias);
}
void updateHeading() {
unsigned long now = micros();
float dt = (now - lastUs) / 1000000.0f;
lastUs = now;
if (dt <= 0 || dt > 0.2f) return; // hylkää oudot askeleet
float rate = (readGyroZraw() - gyroZbias) / GYRO_LSB_250DPS; // °/s
if (fabs(rate) < 0.3f) rate = 0; // pieni kohinaraja
heading += rate * dt;
}
// Aja suoraan: PD-säädin pitää suunnan tavoitteessa
void driveStraightHold(int base, float targetHeading) {
updateHeading();
float err = targetHeading - heading;
float dErr = err - prevErr; prevErr = err;
int corr = (int)(KP_STRAIGHT * err + KD_STRAIGHT * dErr * 100);
corr = constrain(corr, -60, 60);
setSpeeds(base - corr, base + corr); // ero pyörien välillä = käännös
updateRamp();
}
// Tankkikäännös tarkkaan kulmaan gyron avulla
void turnByDegrees(float deg, int speed) {
float target = heading + deg;
unsigned long t0 = millis();
while (millis() - t0 < 4000) { // fail-safe timeout
updateHeading();
float err = target - heading;
if (fabs(err) < 2.0f) break; // 2° toleranssi
if (err > 0) pivotLeft(speed); else pivotRight(speed);
updateRamp();
}
stopNow();
prevErr = 0;
}
Muista kutsua mpuBegin() ja calibrateGyro() setup()-funktiossa sekä alustaa lastUs = micros(); heti kalibroinnin jälkeen – muuten ensimmäinen dt on valtava ja suuntalukema hyppää heti alussa.
Kallistuksen tunnistus on helppo lisä: lue ACCEL_XOUT_H (0x3B), muunna kertoimella 16384 LSB/g (±2 g) ja laske pitch = atan2(-ax, sqrt(ay*ay + az*az)) * 180/PI;. Jos kallistus ylittää noin 30°, robotti on liian jyrkässä rampissa tai kaatumassa – kutsu stopNow(). Jos az lähestyy nollaa tai menee negatiiviseksi, robotti on nostettu ilmaan tai kääntynyt ylösalaisin.
Gyron rehelliset rajat
- Asteikot: 131 LSB/(°/s) alueella ±250 °/s, 65,5 alueella ±500, 32,8 alueella ±1000 ja 16,4 alueella ±2000. Käytä ±250 hitaalle robotille, ±500 jos tankkikäännökset saturoivat mittarin.
- Kalibroi Z-bias käynnistyksessä täysin liikkumattomana. Muutaman LSB:n kalibroimaton bias integroituu kymmeniksi asteiksi minuutissa.
- Kulmanopeuden integrointi antaa vain suhteellisen suunnan, ja se ryömii. Ilman magnetometria ei ole absoluuttista pohjoista. Riittää hyvin komentoon “aja 3 m suoraan” tai “käänny 90°”, ei komentoon “navigoi 20 minuuttia”. Absoluuttista suuntaa varten lisää magnetometri moduulin apu-I2C-väylälle (XDA/XCL).
- Moottorien häiriö sekoittaa I2C:n. Pidä SDA/SCL lyhyinä ja kaukana moottorijohdoista, ja asenna moduuli rungon jäykkään kohtaan mahdollisimman kauas moottoreista. 775:n harjojen ja tuulettimen tärinä näkyy gyrossa – kumitiivisteet auttavat.
- Asenna moduuli vaakaan ja rungon akselien suuntaisesti, muuten Z ei ole kääntymisakseli.
Vianetsintä
| Oire | Syy | Korjaus |
|---|---|---|
| Robotti kaartaa, vaikka PWM-arvot ovat samat | Moottorit eivät ole identtisiä; erilainen kitka | Säädä RIGHT_TRIM kokeellisesti tai sulje silmukka gyrolla |
| Arduino käynnistyy uudelleen moottorien lähtiessä liikkeelle | Brown-out: yhteinen kisko painuu resetrajan alle | Erillinen LM2596-haara, 470–1000 µF moottorikiskoon, tähtimaa |
| Moottorit pyörivät vain täydellä nopeudella | ENA/ENB-hyppylankakorkit paikallaan | Poista korkit ja johda ENA/ENB pinneihin D5/D6 |
| Toinen puoli pyörii väärään suuntaan | Peilatut kiinnikkeet tai vaihtunut napaisuus | Vaihda OUT3/OUT4 tai käännä INVERT_RIGHT |
| Mikään ei liiku, ei ääntä | Ei yhteistä maata L298N:n ja Nanon välillä | Yhdistä kaikki GND-pisteet |
| Moottori surisee mutta ei pyöri pienellä PWM:llä | Kuollut alue alle 40–50/255 | Nosta MIN_PWM, käytä D5/D6 (980 Hz) |
| L298N:n jäähdytyslevy polttavan kuuma, ajo katkeaa | Useita watteja häviötehoa, lämpösuoja laukeaa | Laske MAX_PWM, käytä 12 V, välitä alas tai vaihda MOSFET-ohjaimeen |
| Moottori näkee paljon vähemmän jännitettä kuin akku antaa | Bipolaarilähdön häviö 1,8–4,9 V | Varaa se budjettiin, älä käytä 7,4 V akkua |
| MPU-6050:n lukemat hyppivät ajon aikana | Moottorihäiriö I2C:ssä, tärinä | Lyhennä I2C, 100 nF moottorin napoihin, kumijousita anturi |
| Robotti nousee takakenoon, pyörät luistavat | Äkillinen vääntöaskel, painopiste väärässä | Ota ramppi käyttöön, siirrä akkua vetoakselia kohti |
| Tukipyörä vastustaa käännöstä, robotti nykii | Kääntyvä pyörä jumittaa tai korkeus ei täsmää | Voitele pyörä, säädä korkeus 34 mm akselikorkeuteen |
Jatkokehitysideat
- Viivanseuraaja. 3–5 TCRT5000-heijastusanturia riviin robotin etureunaan, 8–12 mm lattian yläpuolelle, anturien keskinäinen väli hieman pienempi kuin teipin leveys (19 mm). Lue analogisesti A0–A3, kalibroi kynnys pinnalle ja laske painotettu virhe anturien sijainneista. Sitten
setSpeeds(base - Kp*error, base + Kp*error)– täsmälleen sama differentiaaliohjauksen kikka kuindriveStraightHold():ssa, virhe vain tulee infrapuna-antureilta gyron sijaan. Aloita pelkällä P-termillä ja lisää D, kun heiluminen häiritsee. - Esteiden väistö. HC-SR04 eteen. Silmukka: jos etäisyys alle 25 cm →
stopNow(),backward(120)400 ms jaturnByDegrees(±90, 140)gyron tarkkuudella. Servomoottorin päällä anturi voi pyyhkäistä ympäristön ja valita avoimimman suunnan – ja koska Servo-kirjasto varaa Timer1:n, PWM on jo valmiiksi oikeissa pinneissä. - Bluetooth-ohjaus puhelimella. Kaksi tietä: HC-05 Nanon SoftwareSerialiin ja yksimerkkiset komennot (‘F’,‘B’,‘L’,‘R’,‘S’), tai vaihda ohjain ESP32 DevKitC (WROOM-32):een, jossa Bluetooth on sisäänrakennettu. Katso BLE-toteutus artikkelista ESP32 Bluetooth – ohjaa LEDiä puhelimella BLE:n avulla: kutsu vain LED-pinnin kirjoituksen sijaan
setSpeeds()-funktiota. Huomioi ESP32:ssa 3,3 V logiikka,analogWrite()vaihtuuledcWrite()-kutsuihin ja pinninumerot ovat eri. Ohjainvalintaa vertaillaan postauksessa ESP32 vs Arduino Nano – kumpi sopii projektiisi? - Enkooderit ja oikea odometria. Rako- tai magneettienkooderit mahdollistavat pyöräkohtaisen PID-nopeussäädön ja sijainnin laskennan kinematiikalla. Matka per pulssi = 213,6 mm / pulssia per kierros.
- Isompi ohjain. Kun 775:t sammuttavat L298N:n, siirry MOSFET-H-siltaan (BTS7960/IBT-2, noin 43 A). Rajapinta on sama IN/EN/PWM, joten koodi ei muutu.
- Itsestään tasapainottuva robotti. Poista tukipyörä, käytä samaa MPU-6050:tä komplementaarisuodattimella ja PID:llä. Mekaniikka on 90 % samaa.
Osat kerralla koriin
Alla koko 2WD-alustan elektroniikka ja veto yhdessä näkymässä. Nämä kuusi tuotetta riittävät ajokykyiseen robottiin, kun lisäät akun, runkolevyn ja tukipyörän.
Mekaniikan lisäosat
Napasovitukseen, itse tehtyyn tukipyörään ja johdotukseen:
Yhteenveto
Toimiva 2WD-robotti syntyy neljästä oikein tehdystä päätöksestä: kolmipistetuki ja painopiste vetoakselille, kaksi erillistä virtahaaraa akulta yhden yhteisen kiskon sijaan, tähtimaa, ja PWM-ramppi joka ei blokkaa silmukkaa. Kaikki muu on hienosäätöä. Aloita rampilla ja MIN_PWM-arvon etsimisellä, lisää sitten gyro, ja vasta lopuksi anturit ja langaton ohjaus.
Ydinosat: L298N duaali H-silta ohjain, ATmega328P ‘Nano’ ohjain, 775 DC Moottori 12–24V, Rengas 68x26mm, LM2596 step-down -jännitemuunnin, MPU-6050 kiihtyvyysanturi- ja gyroskooppimoduuli ja Dupont-hyppylanka 10cm (20kpl) uros-naaras. Tarkista ajantasainen saatavuus tuotesivuilta.
Tutustu valikoimaamme ja tilaa osat samasta paikasta – kaikki tämän projektin komponentit löytyvät Protocachen verkkokaupasta varastosta ja lähtevät Suomesta. Kun alusta ajaa, jatka viivanseuraajaan tai vaihda ohjain ESP32 DevKitC (WROOM-32):een ja ohjaa robottia puhelimella.
















