Witam!
Dzisiaj zaprezentuję mój najnowszy projekt – zdalnie sterowany samochodzik oparty na mikrokontrolerze Atmega 328p i module komunikacyjnym nRF24L01+ PA LNA.
Generalnie założenia projektowe są takie :
- Zasięg 100 metrów.
- Dobry czas pracy na akumulatorze.
- Przesyłanie danych telemetrycznych do pilota : dane o stanie akumulatora i temperaturze powietrza ( czemu nie (: )
- Program napisany w Arduino IDE.
- Napęd na 4 koła z możliwością kontrolowania każdego koła z osobna.
- Zabezpieczenie akumulatora przed nadmiernym rozładowaniem.
- Możliwość odzyskania wszystkich części składowych po skończonej eksploatacji (brak lutowanych elementów).
Założenia raczej proste. W teorii bo w praktyce okazały się dość skomplikowane w realizacji.
Generalnie projekt ten jest umieszczony w dziale “elektronika” bo mechaniczne aspekty całości zostały przez mnie spartaczone i nie miałem sumienia umieścić go w dziale “Druk 3D” :P
To tyle ze wstępu.
Część mechaniczna i konstrukcyjna
Pod względem mechanicznym całość jest bardzo prosta.
Układ jezdny składa się z 4 zestawów : Silnik DC 5V z przekładnią kątową + koło. (Konkretnie chodzi o ten zestaw ).
Silniki te są przymocowane do ramy wydrukowanej w 3D. Na ramie znajdują się płytki prototypowe z elektroniką a po bokach przymocowane są 2 zestawy akumulatorów Li-ion.
Oto montaż tej konstrukcji krok po kroku.
Zacząłem od przylutowania do styków silnika dwóch przewodów oraz zabezpieczeniem połączenia klejem na gorąco (styki wyglądały tak słabo, że prawdopodobnie bez tego by się urwały ).
Następnym krokiem było zaprojektowanie i wydrukowanie ramy samochodzika.
Całość została wydrukowana z PLA, przy wypełnieniu 50 % i wysokości warstwy 0.3 mm. Drukowanie zajęło koło 2 godziny.
Cała rama ma długość 161mm i szerokość 143 mm. Wysokość wraz z uchwytami silników to 17mm.
Model STL jest dostępny do pobrania na końcu artykułu.
Teraz przyszła pora na montaż silników.
Najpierw przełożyłem kable silnika przez otwór w uchwycie.
A następnie umieściłem silnik w uchwycie i przykleiłem go tam z użyciem kleju na gorąco.
Na końcu owinąłem uchwyt taśmą izolacyjną żeby trochę lepiej to wyglądało.
Procedurę powtórzyłem z pozostałymi 3 silnikami i uzyskałem coś takiego :
No i to tyle jeśli chodzi o mechanikę i konstrukcję.
Teraz pora na elektronikę.
Elektronika sterująca
Oto schemat całej elektroniki sterującej :
Można w nim wyróżnić 3 grupy elementów :
- Zasilanie
- Sterowanie silnikami
- Układ kontrolny wraz z modułem komunikacyjnym
Zasilanie
Cały samochodzik jest zasilany z 4 cel litowo-jonowych. Są to standardowe cele 18650 o pojemności 2500 mAh każda, połączone w konfiguracji 2S2.
Oznacza to, że równolegle połączone są dwa zestawy cel. Każdy zestaw składa się z kolejnych dwóch cel połączonych szeregowo.
Daje to sumaryczne napięcie nominalne akumulatora równe 7,2V i pojemność równą 5000 mAh.
Środkowe cele są również połączone szeregowo tak aby wyrównywać napięcie.
Od akumulatora odchodzą dwa punkty pomiarowe.
Jeden punkt służy do pomiaru napięcia na celi 1, drugi punkt mierzy napięcie na wyjściu z akumulatora. Dzięki temu mikrokontroler wie dokładnie jaki jest stan akumulatora.
Oba punkty pomiarowe są podłączone do wejść analogowych mikrokontrolera przez dzielniki napięcia.
Wyjście akumulatora jest podłączone do układu zasilającego przez bezpiecznik polimerowy 1.5A. Ma on zapobiec uszkodzeniu układu po zwarciu o które w tym spaghetti nie trudno.
Napięcie z akumulatora trafia do przetwornicy step-down D24V25F5 (tej) która obniża napięcie do 5V.
Jej maksymalne natężenie wyjściowe to 2.5A ponieważ musi ona uciągnąć zarówno elektronikę sterującą jak i silniki napędowe.
Przetwornica ta zasila główną szynę zasilającą samochodzika.
Jeśli chodzi o kontrolę zasilania to do pinu EN ( włączającego przetwornicę) jest podłączony prosty układ kontrolujący jej pracę.
Domyślnie pin ten jest podciągnięty do masy (przez rezystor 20K) powodując wyłączenie zasilania.
Aby włączyć zasilanie należy nacisnąć przycisk który na moment zewrze ten pin do dodatniego wyjścia akumulatora.
Spowoduje to włączenie zasilania i uruchomienie komputera.
Komputer sterujący natychmiast poda na ten pin (przez diodę 1N4148) stan wysoki co zapobiegnie wyłączeniu całości po zwolnieniu przycisku. Kiedy komputer zdecyduje o wyłączeniu zasilania (na przykład kiedy napięcie na którejś celi spadnie poniżej 3V) to wtedy poda na ten pin stan niski. Spowoduje to wyłączenie całości. Zużycie energii kiedy całość jest wyłączona jest pomijalne.
Aby całość nie wyłączyła się w trakcie resetu, do pinu EN dodany jest kondensator 47uF którego celem jest dodanie opóźnienia pomiędzy podaniem stanu niskiego na pin sterujący i wyłączeniem całości. Dioda pomiędzy pinem sterującym mikrokontrolera i pinem EN zapobiega natychmiastowemu rozładowaniu kondensatora przez pin sterujący.
Kolejnym elementem zasilania są stabilizatory 3.3V (LD1117V33)
Zarówno moduł komunikacyjny jak i mikrokontroler są zasilane napięciem 3.3V.
Moduł komunikacyjny ma osobny stabilizator ponieważ powoduje on sporo zakłóceń zasilania co mogłoby się odbić na stabilności pracy mikrokontrolera.
Wyjścia stabilizatorów są podłączone do szyn 3.3V które zasilają układy sterujące.
Wejścia stabilizatorów są podłączone do szyny 5V.
Sterowanie silnikami
Sterowanie silnikami odbywa się za pomocą 4 tranzystorów MOSFET BUZ11.
Każdy tranzystor steruje jednym silnikiem.
Niestety ten model posiada dość wysokie napięcie progowe ( ok 3V) więc pomiędzy bramką tranzystora a pinem generującym sygnał sterujący ( PWM) zastosowałem konwerter poziomów logicznych. Zwiększa on napięcie sterujące z 3.3V do napięcia wyjściowego baterii (min 6V).
Zmniejsza to straty energii.
Dodatni biegun silnika jest podłączony do głównej szyny 5V natomiast biegun ujemny do drenu tranzystora. Źródło tranzystora jest podłączone do masy układu. Kiedy na bramkę jest podawane napięcie, wtedy MOSFET zaczyna przewodzić prąd i silnik się uruchamia. Regulując wypełnienie sygnału PWM można regulować prędkość obrotu.
Równolegle z każdym silnikiem podłączony jest kondensator 100 nF (zmniejsza zakłócenia powodowane przez silniki) i dioda 1N4148 która chroni przed powstawaniem szpilek napięcia.
Układ kontrolny wraz z modułem komunikacyjnym
Cały samochodzik jest kontrolowany przez mikrokontroler Atmega328p taktowany za pomocą zewnętrznego oscylatora 8 MHz.
Generalnie cały osprzęt mikrokontrolera jest standardowy.
Do mikrokontrolera podłączony jest również :
- Moduł komunikacyjny nRF24L01+ PA LNA
- Sterownik silników
- Kontrola zasilania (Punkty pomiaru baterii i pin sterujący pracą przetwornicy)
- Czujnik temperatury DS18B20
- Dioda sygnalizacji załadowania programu
Moduł komunikacyjny jest podłączony za pomocą magistrali SPI oraz dwóch dodatkowych pinów sterujących (CE i CNS).
Każdy pin sterujący silnikiem jest podłączony do sprzętowego pinu PWM dzięki czemu łatwo można wygenerować sygnał sterujący silnikiem.
Punkty pomiarowe baterii są podłączone przez dzielniki napięcia do pinów A0 i A1.
Czujnik temperatury korzysta z magistrali One Wire i jest podłączony do zwykłego pinu cyfrowego.
Dioda sygnalizacyjna również jest podłączona do zwykłego pinu cyfrowego.
Generalnie to wszystko jeśli chodzi o komputer sterujący.
Elektronika Pilota
Ponieważ skończyły mi się płytki prototypowe (wersja z atmegą zajęłaby dwie płytki) więc poszedłem trochę na łatwiznę i użyłem arduino uno.
Choć ten schemat powinien zadziałać także dla innych płytek arduino.
Generalnie całość jest bardzo prosta.
Jest to po prostu wyświetlacz LCD 2×16 znaków, moduł komunikacyjny (ten sam co w samochodziku) i 4 przyciski podłączone do arduino.
Warto jednak zwrócić uwagę na zasilanie modułu komunikacyjnego. Jest on zasilany napięciem 3.3V (na pinach sterujących tolerowane jest napięcie 5V ale musi on być zasilany z 3.3V) filtrowanym aż 3 kondensatorami. 47uF, 1000 uF i 2200 uF. Wartości dobrałem eksperymentalnie, mierząc zakłócenia za pomocą oscyloskopu.
Bez nich spadki napięcia podczas przesyłania danych wynoszą nawet 300 mV (!). Po dodaniu kondensatorów wynoszą one już ok 90mV.
Stabilność zasilania modułu komunikacyjnego jest krytyczna dla uzyskania dobrego zasięgu.
Przygotowałem jednak również wersję na mikrokontrolerze Atmega 328p :
Piny do których podłączone są peryferia są te same co w przypadku Arduino ale tym razem zastępuje je mikrokontroler wraz z niezbędnym osprzętem.
Ponownie jest to Atmega 328p jednak tym razem zasilana napięciem 5V i taktowana zewnętrznym oscylatorem 16 MHz.
Zasilanie stanowią dwa regulatory napięcie. LD1117V33 dla modułu komunikacyjnego i L7805CV dla mikrokontrolera.
Warto zauważyć, że w tej wersji brakuje kondensatora 2200 uF przy zasilaniu modułu komunikacyjnego. Usunąłem go bo LD1117V33 ma wyższą wydolność prądową niż regulator wbudowany w arduino.
Oprogramowanie
Całe oprogramowanie samochodzika zostało napisane w Arduino IDE.
Jeśli chodzi o zewnętrzne biblioteki wymagane do skompilowania kodu to użyłem :
- RF24 (https://github.com/nRF24/RF24)
- One wire (https://github.com/PaulStoffregen/OneWire)
- DallasTmperature (https://github.com/milesburton/Arduino-Temperature-Control-Library)
A oto kod źródłowy do samochodzika :
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 226 227 228 229 230 231 232 233 234 235 236 237 |
#include <OneWire.h> #include <DallasTemperature.h> #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> // definicje pinów // silniki #define mc1 3 // silnik 1 #define mc2 5 // silnik 2 #define mc3 6 // silnik 3 #define mc4 9 // silnik 4 // moduł komunikacyjny #define CE 8 // pin CE modułu komunikacyjnego #define CNS 10 // pin CNS modułu komunikacyjnego // peryferia #define TEMP 4 // czujnik temperatury #define LED 2 // dioda kontrolna //kontrola zasilania #define PWC 7 // pin kontroli zasilania #define P1 A0 // punkt pomiarowy 1 #define P2 A1 // punkt pomiarowy 2 #define MOTOR_DELAY 50 //czas pracy motorów po wysłaniu jednej komendy ruchu const byte write_addr[6] = "00001"; // adres rury wysyłającej dane const byte read_addr[6] = "00010"; // adres rury odbierającej dane unsigned long timer=0;// timer od ochrony baterii RF24 radio(CE,CNS);//utworzenie obiektu radio OneWire oneWire(TEMP);//obiekt dla magistrali One wire DallasTemperature sensors(&oneWire);// obiekt czujnika temperatury void clear_motors()// funkcja wyłączająca motory { // wszystkie piny sterujące motorami na LOW digitalWrite(mc1,LOW); digitalWrite(mc2,LOW); digitalWrite(mc3,LOW); digitalWrite(mc4,LOW); } float get_bat_voltage(char cell)// funkcja zwracająca napięcie na danej celi akumulatora { float voltage=0; if(cell==1)// pierwsza cela { short data=analogRead(P2);// zmierz napięcie na pinie analogowym voltage=data*0.00645;// pomnóż razy napięcie na jedną jednostkę voltage=voltage+0.02;//kalibracja if(data==0) { voltage=0; } } if(cell==2)// druga cela { float v1; float v2; // zmierz napięcie na celi 1 short data=analogRead(P2); v1=data*0.00645; v1=v1+0.02;//kalibracja if(data==0) { v1=0; } // napięcie akumulatora data=analogRead(P1); v2=data*0.00911; v2=v2+0.02;//kalibracja if(data==0) { v2=0; } voltage=v2-v1;// oblicz napięcie na celi 2 } if(cell==3)// napięcie wyjściowe akumulatora { short data=analogRead(P1); voltage=data*0.00911; voltage=voltage+0.02;//kalibracja if(data==0) { voltage=0; } } return voltage;// zwróć napięcie } void send_temp()// wyślij temperaturę { // pierwszy odczyt czasami siada więc go odrzucamy sensors.requestTemperatures(); float temp; sensors.getTempCByIndex(0); // drugi odczyt powinien być ok więc go zapisujemy sensors.requestTemperatures(); temp=sensors.getTempCByIndex(0); delay(100);// daj nadajnikowi czas na przygotowanie się do odbioru danych radio.write(&temp, sizeof(float));// wysyłamy temperaturę delay(10);// upewnij się, że dane zostały wysłane } void send_voltage()// wyślij napięcie { float volt[3];// 1 cela druga cela i cała bateria volt[0]=get_bat_voltage(1);// cela 1 volt[1]=get_bat_voltage(2);// cela 2 volt[2]=get_bat_voltage(3);// napięcie akumulatora delay(100); radio.write(&volt, sizeof(volt));// wyślij całą tablicę w jednej wiadomości delay(10); } void send_ok()// wyślij wiadomość ok ( w celu sprawdzenia komunikacji { delay(100); char data[2];// dwa bajty data[0]='o'; data[1]='k'; radio.write(&data, sizeof(data));// wyślij ok delay(10); } void bat_security()// funkcja wyłączająca samochodzik jeśli napięcie w baterii spadnie zbyt nisko { float voltage; voltage=get_bat_voltage(1);// zmierz napięcie na celi 1 if(voltage<=3)// minimalne napięcie 3V na celę { digitalWrite(PWC,LOW);// jeśli napięcie za niskie, wyłącz samochodzik while(1); } voltage=get_bat_voltage(2);// zmierz napięcie na celi 2 if(voltage<=3)// minimalne napięcie3V na celę { digitalWrite(PWC,LOW); while(1); } } void setup() { // ustawienia pinów pinMode(PWC,OUTPUT); digitalWrite(PWC,HIGH);//zapobiegnij wyłączeniu pinMode(mc1,OUTPUT); pinMode(mc2,OUTPUT); pinMode(mc3,OUTPUT); pinMode(mc4,OUTPUT); clear_motors(); pinMode(LED,OUTPUT); pinMode(P1,INPUT); pinMode(P2,INPUT); sensors.begin();// inicjalizacja czujnika temperatury // radio // inicjalizacja radia if(!radio.begin()) { while(1); } //konfiguracja radia radio.setPALevel(RF24_PA_MAX);// maksymalna moc radio.setCRCLength(RF24_CRC_8);//8 bitowa suma CRC radio.setDataRate(RF24_250KBPS);// minimalna prędkość danych radio.setRetries(5,15);//ustaw liczbę prób wysłania danych i opóźnienie między próbami radio.openWritingPipe(write_addr);// otwórz rurę wysyłającą dane radio.openReadingPipe(1,read_addr);// otwórz rurę odbierającą dane // włącz LED na znak rozpoczęcia pracy digitalWrite(LED,HIGH); radio.startListening();// rozpocznij nasłuch na radiu } void loop() { if(millis()>timer){// sprawdź czy od wyłączenia silników minęła 1 sekunda // napięcie jest mierzone przy obciążeniu ok 50 mA. bat_security(); } if(radio.available())// Jeśli coś odbieramy { char command[5];// 5 bajtów komendy, pierwszy bajt to komenda pozostałę 4 to dane niezbędne do wykonania komendy radio.read(&command, sizeof(command));// odbierz dane // wykonaj komendę switch(command[0])// odczytaj pierwszy bajt komendy { case 1:// komenda jazdy // kolejne bajty danych to wypełnienie sygnałów PWM dla kolejnych silników analogWrite(mc1,command[1]); analogWrite(mc2,command[2]); analogWrite(mc3,command[3]); analogWrite(mc4,command[4]); delay(MOTOR_DELAY);// poczekaj chwilę clear_motors();// wyłącz silniki timer=millis()+1000;// wyłącz sprawdzanie akumulatora na 1 sekundę aby spadek napięcia nie spowodował wyłączenia break; case 2://wyłącz digitalWrite(PWC,LOW);// pin kontrolny na LOW while(1);// nic już nie rób, samochodzik za chwilę się wyłączy break; case 3:// wyślij temperaturę radio.stopListening();// przełącz radio w tryb nadawania send_temp();// wyślij temperaturę radio.startListening();// przełącz radio ponownie w tryb odbioru break; case 4:// wyślij dane o baterii radio.stopListening(); send_voltage();// wyślij napięcie radio.startListening(); break; case 5:// wyślij ok radio.stopListening(); send_ok();// wyślij ok radio.startListening(); break; } } } |
I kod źródłowy pilota :
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 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
#include <LiquidCrystal.h> #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> //definicje pinów // radio #define CE 8 #define CNS 9 // wyświetlacz #define RS 2 #define E 3 #define D1 4 #define D2 5 #define D3 6 #define D4 7 // przyciski #define P1 A0 //dużo do przodu, góra #define P2 A1 //mało do przodu, dół #define P3 A2 // lewo , ok #define P4 A3 // prawo, cancel //adresy const byte write_addr[6] = "00010"; // adres rury wysyłającej dane const byte read_addr[6] = "00001"; // adres rury odbierającej dane RF24 radio(CE,CNS);// obiekt radia LiquidCrystal lcd(RS, E, D1, D2, D3, D4);// obiekt wyświetlacza short menu=1;// zmienna globalna przechowująca dane o pozycji w menu void intro()// funkcja wyświetlająca into { lcd.clear(); lcd.print("Samochodzik V1.0"); delay(2000); lcd.clear(); lcd.print("(C) Bob"); lcd.setCursor(0,1); lcd.print("Majsterkowo 2017"); delay(2000); lcd.clear(); } void drive()// tryb jazdy { lcd.clear(); lcd.print("tryb jazdy"); delay(500); while(1)// pętla trybu jazdy { // odczytaj stan przycisków bool p1=digitalRead(P1); bool p2=digitalRead(P2); bool p3=digitalRead(P3); bool p4=digitalRead(P4); if(p1==false||p2==false||p3==false||p4==false)// jeśli któryś przycisk jest wciśnięty { char command[5];// 5 bajtów komendy command[0]=1;// ustaw bajt komendy na 1 (komenda jazdy ) // pozostałe 4 bajty ustawiane są w zależności od stanu przycisków if(p1==false) { //szybko do przodu command[1]=255; command[2]=255; command[3]=255; command[4]=255; } if(p1==false&&p3==false) { //lekko w lewo command[1]=255; command[2]=255; command[3]=40; command[4]=40; } if(p1==false&p4==false) { //lekko w prawo command[1]=40; command[2]=40; command[3]=255; command[4]=255; } if(p2==false) { // wolno do przodu command[1]=100; command[2]=100; command[3]=100; command[4]=100; } if(p3==false&&p1==true) { // ostro w lewo command[1]=255; command[2]=255; command[3]=0; command[4]=0; } if(p4==false&&p1==true) { // ostro w prawo command[1]=0; command[2]=0; command[3]=255; command[4]=255; } if(p3==false&&p4==false)// wyjście z trybu jazdy { lcd.clear(); delay(500); break; } radio.write(&command, sizeof(command));// wyślij komendę delay(40);// poczekaj 40 sekund aby nie wysyłać niepotrzebnie danych } } delay(1000); } void print_bat()// wyświetlanie napięcia akumulatora { while(digitalRead(P4)!=LOW){// pętla wyświetlania (do czasu naciśnięcie P4) char command[5];// 5 bajtów komendy command[0]=4;//komenda numer 4 czyli polecenie wysłania stanu baterii radio.write(&command, sizeof(command)); delay(10);// upewnij się, że dane zostały wysłane radio.startListening();// rozpocznij nasłuch unsigned long timer=millis()+500;// maksymalny czas oczekiwania na dane to 500 ms float voltage[3];// tablica przechowująca odebrane dane while(timer>millis())// pętla oczekiwania { if(radio.available()) { radio.read(&voltage, sizeof(voltage));// odbierz dane break;// zakończ pętlę } } radio.stopListening();// skończ nasłuchiwać // wyświetl dane lcd.clear(); lcd.print("1~"); lcd.print(voltage[0]); lcd.print("V "); lcd.print("2~"); lcd.print(voltage[1]); lcd.print("V "); lcd.setCursor(0,1); lcd.print("Batt: "); lcd.print(voltage[2]); lcd.print("V"); delay(1000); } } void print_temp()// wyświetlanie temperatury { while(digitalRead(P4)!=LOW){// pętla wyświetlania temperatury char command[5]; command[0]=3;// komenda 3 - polecenie wysłania temperatury radio.write(&command, sizeof(command));// wyślij komendę delay(10); radio.startListening();// radio w tryb odbioru unsigned long timer=millis()+500; float temp; while(timer>millis())// pętla oczekiwania na dane { if(radio.available()) { radio.read(&temp, sizeof(float));// odbierz dane break; } } radio.stopListening(); // wyświetl dane lcd.clear(); lcd.print(temp); lcd.print(" *C"); delay(1000); } } void poweroff()// wyłączenie samochodzika { char command[5]; command[0]=2;// komenda 2, wyłączneie samochodzika radio.write(&command, sizeof(command));// wyślij komendę // wyświetl komunikat o wyłączeniu lcd.clear(); lcd.print("samochodzik"); lcd.setCursor(0,1); lcd.print("wylonczony"); wait_keypad();// poczekaj na wciśnięcie dowolnego klawisza } void rf_test()// test połączenia { char command[5]; command[0]=5;// komenda 5 - test połączenia radio.write(&command, sizeof(command));// wyślij komendę delay(10); radio.startListening(); unsigned long timer=millis()+500; char data[2]; while(timer>millis())// pętla oczekiwania na dane { if(radio.available()) { radio.read(&data, sizeof(data));// odbierz 2 bajty break; } } radio.stopListening(); lcd.clear(); if(data[0]=='o'&&data[1]=='k')// sprawdź czy odebrano 'ok' { lcd.print("RF OK");// wyświetl komunikat o sukcesie } else { lcd.print("RF ERR");// komunikat o błędzie } wait_keypad();// poczekaj na wciśnięcie dowolnego przycisku } void print_menu(short index)// wyświetl menu { lcd.clear(); // kolejne pozycje meny są wyświetlane w zależności od zmiennej index switch(index) { case 1: lcd.print("Jazda"); break; case 2: lcd.print("Bateria"); break; case 3: lcd.print("temperatura"); break; case 4: lcd.print("Wylonczenie"); break; case 5: lcd.print("RF test"); break; } } short wait_keypad()// poczekaj aż któryś przycisk został wciśnięty { short key=0; while(1)// poczekaj na wciśnięcie przycisków { // warunki na kolejne przyciski if(digitalRead(P1)==LOW) { key=1; break; } if(digitalRead(P2)==LOW) { key=2; break; } if(digitalRead(P3)==LOW) { key=3; break; } if(digitalRead(P4)==LOW) { key=4; break; } } return key;// zwróć który przycisk został wciśniety } void setup() { lcd.begin(16, 2);// uruchom wyświetlacz intro();// wyświetl intro if(!radio.begin())// uruchom moduł komunikacyjny { lcd.clear(); lcd.print("RF fail"); while(1); } // ustaw piny przycisków jako wejścia pinMode(P1,INPUT_PULLUP); pinMode(P2,INPUT_PULLUP); pinMode(P3,INPUT_PULLUP); pinMode(P4,INPUT_PULLUP); //konfiguracja radia (ta sama co w samochodziku tylko adresy zamienione) radio.setPALevel(RF24_PA_MAX); radio.setCRCLength(RF24_CRC_8); radio.setDataRate(RF24_250KBPS); radio.setRetries(5, 15); radio.openWritingPipe(write_addr); radio.openReadingPipe(1, read_addr); radio.stopListening();// włąc tryb nadawania print_menu(menu);// wyświetl menu } void loop() { short key=wait_keypad();// poczekaj na wciśnięcie przycisku bool upd=false;// flaga odświerzenia menu if(key==1)// zwiększ pozycję o 1 { menu++; upd=true; delay(300); } if(key==2)// zmniejsz pozycję o 1 { upd=true; menu--; delay(300); } if(key==3)// uruchom daną pozycję { switch(menu) { case 1: drive(); break; case 2: print_bat(); break; case 3: print_temp(); break; case 4: poweroff(); break; case 5: rf_test(); break; } upd=true; } // zapobiegnij wyjściu zmiennej poza zakres menu if(menu<=0) { menu=5; } if(menu>=6) { menu=1; } if(upd)// odświerz wyświetlacz { print_menu(menu); } } |
Oba programy są bogate w komentarze więc nie powinno być problemów z ich zrozumieniem.
W razie pytań proszę o komentarz.
Oba programy zostały wgrane przy użyciu programatora USBASP i arduino IDE.
W przypadku programu pilota należy wybrać płytkę Arduino / Genuino Uno.
Natomiast w przypadku samochodzika Arduino Mini / pro mini i procesor Atmega328 3.3V 8 MHz.
Kilka zdjęć z działania
Mała galeria z tego jak to wszystko działa.
Ogólnie efekt jest taki :
Niezłe spaghetti (:
Zdjęcia z jazdy próbnej :
Podczas jazdy samochodzik sprawuje się całkiem nieźle choć porusza się dość powoli.
Zasięg okazał się zaskakująco dobry.
Udało się dojechać do końca ulicy widocznej na zdjęciu (ok 200 metrów) zanim zasięg zaczął się rwać.
Czas pracy też jest całkiem niezły. Po godzinie jazdy akumulator nadal był naładowany w 60%.
Choć co prawda była to dość wolna jazda bo samochodzik nie porusza się szybko.
Jeszcze kilka zdjęć :
Sterowanie odbywa się za pomocą 4 przycisków.
Górny daje całą naprzód, dolny daje pół na przód.
Po prawej znajdują się przyciski do sterowania kierunkiem.
Prawy przycisk powoduje ostry skręt w prawo.
Lewy ostry skręt w lewo.
Jednoczesne naciśnięcie przycisku “cała na przód” i przycisku skrętu powoduje łagodny skręt połączony z jazdą do przodu.
Po włączeniu pilota najpierw pokarze się intro a potem uruchomi się “menu”.
Nawigując przyciskiem górnym i dolnym można wybrać daną opcję. Np :
Pełna lista opcji to :
- Jazda – Wejście w tryb jazdy
- Bateria – Wyświetlenie danych o stanie akumulatra
- Temperatura – Wyświetlenie danych o temperaturze
- Wylonczenie – Wyłączenie samochodzika
- RF Test – Test komunikacji
Wybraną opcję należy zatwierdzić wciskając przycisk skrętu w lewo.
Generalnie w opcji Jazdy po prostu sterujemy samochodzikiem za pomocą przycisków. Wciśnięcie przycisku skrętu w lewo i skrętu w prawo jednocześnie powoduje powrót do menu.
W trybie wyświetlania danych o baterii pokazuje się nam taki ekran:
Są na nim dane o napięciu poszczególnych cel oraz o napięciu zbiorczym akumulatora.
Dane są odświeżane co ok 1 sekundę.
W trybie pomiaru temperatury, na ekranie wyświetla się temperatura, odświeżana co sekundę.
Aby wyjść z tych trybów należy nacisnąć i przytrzymać przycisk skrętu w prawo.
W przypadku wyłączenia, po wybraniu tego trybu, samochodzik się wyłączy a na ekranie pokarze się stosowny komunikat.
Opcja RF test, przetestuje komunikację i wyświetli na ekranie “RF OK ” lub “RF ERR” zależnie od tego czy komunikacja działa czy nie.
Kilka słów na koniec
Cóż, to by było na tyle jeśli chodzi o ten projekt.
Osobiście sądzę, że część strukturalna jest nieco spartaczona choć nic na razie się nie rozwaliło :).
W załączniku znajdują się oba programy w plikach .ino, schematy w plikach SVG ( można powiększyć) oraz pliki z edytora EasyEDA i plik STL do ramy.
Komentarze pozytywne jak i negatywne jak zwykle mile widziane (:
Jeżdżąca centrala telefoniczna :)
Może i centrala ale jeździ i własnoręcznie zrobiona, na to trzeba czasu, wiedzy i cierpliwości:)
Projekcik świetny, zasięg i wytrzymałość aku też , wygląd dopracujesz. Nie umywa się do chińczyków sprzedawanych w sklepach. które po 15 minutach trzeba ładować , zasięg w nich też do dupy. :)
Fakt projekt świetny, i faktycznie aby wykonać coś takiego potrzeba dużo cierpliwości i czasu, tylko dlaczego mając tyle czasu nie można było wykonać jakieś obudowy, wszystko połączone ze sobą na “gluta” i taśmę izolacyjną – oczekiwałem lepszego efektu finalnego.
Jestem pod wrażeniem, że ktoś buduje pojazd jeżdżący na płytce prototypowej. Tyle włożonej pracy, a i tak zaraz wszystko szlag trafi.
Generalnie cały projekt ma działać tydzień a potem części zostaną wykorzystane w kolejnej konstrukcji.
Nie opłacało się wytrawiać płytek.
A co do tej włożonej pracy to najwięcej wysiłku kosztuje opracowanie elektroniki i programu.
Montaż zajmuje jakieś 2-3 godziny.
A że mam to już opracowane to w razie czego mogę odbudować projekt w mniej niż jeden dzień.
Wygląda strasznie ale przygotowanie schematów, programu no i pomysł to na 6+
Dlatego wstawiłem go do działu Eletronika :D
Podoba mi się to zdjęcie na drodze, gdyby nie zieleń w tle to samochodzik wyglądałby jak na marsie :D
Niezłe :)
Mam też pomysł na wersję V2.
Była by ona oparta na Raspberry pi a dzięki kamerze mogła by przesyłać panoramy do pilota. Zupełnie jak łazik marsjański.
No ale zanim go zrealizuję to czeka mnie jeszcze sporo nauki programowania w pythonie (:
Witam. Ciekawy projekt. Mam prośbę. Czy możesz wrzucić schemat połączeń samych ogniw18650 do arduino (chodzi mi o to sprawdzanie napiecie) ? Chciałbym tą część sprawdzania poziomu naładowania baterii wykorzystać u siebie
Generalnie to zależy od tego ile cel i w jakiej konfiguracji ma twój układ.
Ogólna koncepcja jest prosta :
Do każdej celi jest podłączony dzielnik napięcia (wartości dobrane tak żeby nie przekroczyć VCC mikrokontrolera).
Wyjście z dzielnika jest natomiast podłączone bezpośrednio do pinu analogowego mikrokontrolera.
No i to jest cały schemat. Reszta to już program.
a jak mniej więcej wygląda sprawa kosztów zrobienia takiego samochodzika? pomijając koszta druku 3D ale samych części mi chodzi :)
Około ~200 zł jakby kupować wszystko nowe. Najdroższe są akumulatory i silniki z kołami.