Skocz do zawartości
slon

NanoPad mini projekt do ćwiczenia refleksu

Pomocna odpowiedź

Sam tytuł mówi prawie wszystko na temat tej zabawki. Co do genezy to od jakiegoś czasu miałem chęć zrobić pada opartego o arduino. Sam pad nie miał służyć do grania a bardziej do sterowania czymś innym. Całość najpierw powstała w SimulIDE 

pad.thumb.jpg.ac87968a4ad926315d04d029e13c58c7.jpg

Tak jak widać do każdego przycisku są przypisane diody czyli 4 po lewej jedna po prawej (plus led na pinie13) i cztery diody sygnalizacyjne na środku. Po dostarczeniu zasilania diody zaświecają się jedna po drugiej a naszym celem jest wcisnąć odpowiadający danej diodzie przycisk co powoduje jej zgaszenie. Jeśli nie wciśniemy przycisku lub wciśniemy go po czasie zapala się dioda sygnalizacyjna. Możemy pominąć trzy diody a przy czwartej jest game over 🙂 . W raz z postępem rozgrywki diody gasną i zapalają się coraz szybciej. Cel był taki żeby rozgrywka była możliwie krótka a stopień trudności narastał dając nam odczuć wyraźną zmianę. 

1134314119_20190921_0048581.thumb.jpg.fce7e8eee217abafd55bdd4f9273518a.jpg

tak wygląda efekt końcowy. Chciałem kupić diody w kształcie kwadrat 12x12mm ale nie było ich w sklepie więc trudno. Za brakujące elementy zapłaciłem 7 zł 🙂 resztę miałem.  W załączniku przesyłam plik do simulIDE wraz z kodem (całym). Można skompilować i wgrać bezpośrednio z simulIDE i zobaczyć jak to działa w symulacji. 

void setup() {
Serial.begin(9600);
DDRC = 15; // A0..A3 Output
DDRB = 48;   //D8...D11  INPUT, D12 and D13 Output
PORTB = 15; //D8...D11 High impedance
DDRD = 60;  //D2...D5 OUTPUT
PORTD = 192; //D6 D7 High impedance
PCICR = 5;  // enable pin change interrupt bank 0 and 2
PCMSK0 = 15; //enable pin change interrupt PCINT0...PCINT3/D8...D11
PCMSK2 =192;  // enable pin change interrupt D6 D7
}
void loop() {
start(randomPin,ledOn,ledOff);
}

ISR(PCINT0_vect) {
for (byte i=0; i<4; i++) {
if (bitRead(PINB,i)==0 && bitRead(PIND,2+i)==1) {score++; bitClear(PORTD, 2+i);}
  }
 }

ISR(PCINT2_vect) {
for (byte i=0; i<2; i++) {
if (bitRead(PIND,i+6)==0 && bitRead(PINB,i+4)==1) {score++; bitClear(PORTB,i+4);}
  }
 }

Polecam zrobienie podglądu w rejestry (jeśli ktoś wcześniej tego nie robił)  z poziomu simulIDE naprawdę bardzo przydatna sprawa. I to by było na tyle. A co do samego refleksu to udało mi się zgasić ponad 100 diod.

Pin_interruptPad.zip

  • Lubię! 2

Udostępnij ten post


Link to post
Share on other sites

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.

@slon gratuluję ciekawego projektu, fajna zabawka! Dla lepszej czytelności warto byłoby tylko zapisać wartości przypisywane do rejestrów w formie liczb binarnych. Wtedy łatwiej się w tym odnaleźć 😉 Jak ktoś nie słyszał o SimulIDE to polecam ten wpis: Prosty program do symulacji układów z Arduino (i nie tylko).

Udostępnij ten post


Link to post
Share on other sites

Proszę bardzo wartości binarne 🙂

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Fajnie, że na forum pojawił się projekt w innym środowisku niż Arduino IDE. Od jakiegoś czasu myślę, żeby zrobić choćby coś małego w jakimś egzotycznym środowisku, niekoniecznie ma to być niewiadomo jak wydajne, ale sprawdzić czy się da, także dobrze widzieć, że faktycznie da się 😀

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

Fajnie , że komuś się to spodobało 🙂 Dzięki @Treker i @Gieneq.  W między czasie napisałem funkcję start(); od nowa 

void start(uint16_t wait, uint32_t onTime, uint32_t offTime, uint8_t counter) {
   static byte index=0;
   byte pin[30] = {3,4,12,2,4,13,3,2,2,4,5,13,3,5,3,5,3,4,4,3,5,3,2,3,4,2,12,12,3,2};
   static byte power=1;

 if (timer0_millis > wait && timer0_millis < (onTime+wait) && power==1 && ledCounter<=(counter-1)) {
   if (pin[index] >= 12 && pin[index] <= 13) {bitSet(PORTB,pin[index]-8);}
   else if (pin[index] >= 2 && pin[index] <= 5) {bitSet(PORTD,pin[index]);} 
   power=0;
  }

 if (timer0_millis >= (onTime+wait) && power==0) {
   if (pin[index] >= 12 && pin[index] <= 13) {bitClear(PORTB,pin[index]-8);}
   else if (pin[index] >= 2 && pin[index] <= 5) {bitClear(PORTD,pin[index]);}
   ledCounter++;
   power=1;
  }

 if (timer0_millis >= (onTime+offTime+wait)) {timer0_millis =wait; index++; if (index==30) index=0;}
}

Nie jest specjalnie długa więc wklejam w całości. Niema numeru pin bo i tak zmienia się automatycznie. Na pierwszym miejscu jest czas opóźnienia. Czas na jaki włączamy led , Czas na jaki wyłączamy led, i ile razy ma się zaświecić led (docelowo dopuszczalna ilość błędów ). Losowości tutaj niema ale wcześniej też nie było wiec zamiast random() jest tablica z numerami pinów.

Edytowano przez slon
błąd w kodzie
  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Wklejam ostatnią pełną wersję dla całego projektu z drobnymi poprawkami. Funkcja start w w/w formie raz na kilkanaście czy kilkadziesiąt prób  działa niepoprawnie a więc została w niewielki stopniu zmodyfikowana. Dla nowej wersji zrobiłem kilka tysięcy prób z różnymi wartościami i żaden błąd się nie pojawił. Dodałem też komentarze


extern volatile uint32_t timer0_millis; //zmienna zdefiniowana w wiring.c
volatile uint8_t score =0; //przyznane punkty
uint16_t ledCounter=0; //licznik diod

uint32_t wait_ = 2000; //czas na rozpoczęcie rozgrywki w milisekundach
uint32_t ledOff = 1000; //początkowy czas wyłączenia diody w milisekundach
uint32_t ledOn = 1000; //początkowy czas włączenia diody w milisekundach
uint8_t mistakes = 4; //dopuszczalna ilość pominiętych diod

void setup() {
 Serial.begin(9600);
DDRC = 15; // A0..A3 Output
DDRB = 48;   //D8...D11  INPUT, D12 and D13 Output
PORTB = 15; //D8...D11 High impedance
DDRD = 60;  //D2...D5 OUTPUT
PORTD = 192; //D6 D7 High impedance
PCICR = 5;  // enable pin change interrupt bank 0 and 2
PCMSK0 = 15; //enable pin change interrupt PCINT0...PCINT3/D8...D11
PCMSK2 =192;  // enable pin change interrupt PCINT22/D6 D7
}
void loop() {
start(wait_,ledOn,ledOff,mistakes);
}
//sprawdzamy od którego z czterech przycisków (D8...D11) pochodzi przerwanie
//jeżeli wcisnęliśmy przycisk odpowiadający zapalonej diodzie to przyznawany jest punkt i dioda gaśnie
ISR(PCINT0_vect) {
for (byte i=0; i<4; i++) {
if (bitRead(PINB,i)==0 && bitRead(PIND,2+i)==1) {score++; bitClear(PORTD, 2+i);}
  }
 }
//analogicznie jak wyżej z tym , że dla dwóch przycisków D6 i D7
ISR(PCINT2_vect) {
for (byte i=0; i<2; i++) {
if (bitRead(PIND,i+6)==0 && bitRead(PINB,i+4)==1) {score++; bitClear(PORTB,i+4);}
  }
 }
//sprawdzamy czy liczba zapalonych diod jest różna od ilości zdobytych punktów.
//jeśli tak to zapalamy kolejno diody na pinach A0 A1 A2 A3
void results() {
  if (ledCounter - score>0 && ledCounter - score<5 ) bitSet(PORTC,(ledCounter - score)-1);
}
//skracamy czas trwania stanu niskiego i wysokiego 
void difficulty() {
  if (ledOn>500) {ledOff -= 15; ledOn -= 15;}
  else if (ledOff>200) {ledOff -= 3; ledOn-=2;}
  else {ledOn=1003; ledOff=1003;}
}

void start(uint32_t wait, uint32_t onTime, uint32_t offTime, uint8_t counter) {
    static byte index = 0; 
    byte pin[30] = {3,4,12,2,4,13,3,2,2,4,5,13,3,5,3,5,3,4,4,3,5,3,2,3,4,2,12,12,3,2};
    static byte power = 1;
    //zapalamy diodę na pinie określonym w tablicy
 if (timer0_millis > wait && timer0_millis < (onTime+wait) && power==1 && (ledCounter - score)<mistakes) { 
   if (pin[index] > 11 && pin[index] < 14) {bitSet(PORTB,pin[index]-8);}
   else if (pin[index] > 1 && pin[index] < 6) {bitSet(PORTD,pin[index]);} 
   difficulty(); Serial.print(" missed "); Serial.print(ledCounter - score);
   power=2; 
  }
   //wyłączamy diodę na pinie określonym w tablicy
  if (timer0_millis >= (onTime+wait) && power==2) {
   if (pin[index] > 11 && pin[index] < 14) {bitClear(PORTB,pin[index]-8);}
   else if (pin[index] > 1 && pin[index] < 6) {bitClear(PORTD,pin[index]);}
   ledCounter++;
   results(); Serial.print(" score "); Serial.print(score);
   power=3; 
  }
  //ustawiamy czas do wartości równej początkowemu opóźnieniu
 if (timer0_millis >= (onTime+offTime+wait) && power==3) {
  index++; if (index==30) index=0;
  power=1; Serial.print(" Blink "); Serial.println(ledCounter);
  timer0_millis =wait;
  }
}

 

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Drobna uwaga dla osób które chciały by modyfikować w/w kod w sposób w , którym funkcja start() miała by się wykonać po czasie większym niż: onTime+wait. Czyli np: onTime=1000;  wait=1000; czyli mamy 2s na rozpoczęcie rozgrywki ale chcemy aby rozgrywka rozpoczęła się dopiero na nasz sygnał (mniejsza o to jaki to będzie sygnał) a ten może być po czasie większym niż 2s. W takim wypadku funkcja start() nie wykona się. Aczkolwiek wystarczy prosta modyfikacja.

static byte power = 0; // to już jest tylko trzeba nadać wartość zero dla zmiennej power

if (power==0) {power=1; timer0_millis=0;} // i jako pierwszy dodać ten warunek

w ten sposób działa to bardziej uniwersalnie. Jeśli ktoś nie lubi cyferek to można: 

#define RESET 0
#define ON 1
#define OFF 2
#define SET 3

lub przypisać inne wartości np. 20,21,22,23 tak , żeby zostawić "miejsce" dla pinów.

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Rozmiar całego szkicu udało się zmniejszyć z 9% do 6% zastępując Serial.begin() wpisami do rejestru oraz Serial.print() trzema funkcjami.

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

kolejna aktualizacja do funkcji start

extern volatile unsigned long timer0_millis;
uint16_t ledCounter;
uint8_t state;
enum State {RESTART_TIME, ON_TIME, OFF_TIME, SET_TIME};

void setup() {
  Serial.begin(9600);

  DDRC = 15; // A0..A3 Output
  DDRB = 48;   //D8...D11  INPUT, D12 and D13 Output
  PORTB = 15; //D8...D11 High impedance
  DDRD = 60;  //D2...D5 OUTPUT
  PORTD = 192; //D6 D7 High impedance
}

void loop() {
  start(timeSet(1, 0, 0), 150, 150, 100);
}

void start(uint32_t wait, uint32_t onTime, uint32_t offTime, uint16_t counter) {
  static byte index = 0;
  byte pin[30] = {3, 4, 12, 2, 4, 13, 3, 2, 2, 4, 5, 13, 3, 5, 3, 5, 3, 4, 4, 3, 5, 3, 2, 3, 4, 2, 12, 12, 3, 2};

  if (state == RESTART_TIME) {
    //Serial.println("start");
    state = ON_TIME;
    timer0_millis = 0;
  }

  if (timer0_millis > wait && timer0_millis <= (onTime + wait) && state == ON_TIME && ledCounter <= (counter - 1)) {
    (pin[index] == 12 || pin[index] == 13) ? PORTB |= 1UL << pin[index] - 8 : PORTD |= 1UL << pin[index];
    state = OFF_TIME;
  }

  if (timer0_millis >= (wait + onTime) && state == OFF_TIME) {
    if (timer0_millis < onTime + wait) return;  Serial.println(timer0_millis);
    (pin[index] == 12 || pin[index] == 13) ? PORTB &= ~(1UL << (pin[index] - 8)) : PORTD &= ~(1UL << (pin[index]));
    ledCounter++;
    state = SET_TIME;
  }

  if (timer0_millis >= (onTime + offTime + wait) && state == SET_TIME) {
    if (timer0_millis < onTime + offTime + wait) return; Serial.println(timer0_millis); Serial.println(ledCounter);
    index++; if (index == 30) index = 0;
    state = ON_TIME;
    timer0_millis = wait;
  }
}

uint32_t timeSet(uint32_t s , uint32_t m , uint32_t h) {
  h = h * 1000 * 3600;
  m = m * 1000 * 60;
  s = s * 1000;
  return  h + m + s;
}

Po n ilości prób zaobserwowałem że diody czasami potrafią zmieniać stany w, krótszym czasie niż zadany. W teorii wygląda to tak , że dioda zaświeca się następnie powinna zgasnąć i zaświecić się kolejna ale przy krótkich czasach np: 250/250ms on/off stanu niskiego prawie nie widać tak jakby odrazu zapalała się kolejna dioda. Aby zaobserwować jak to wygląda w terminalu trzeba w w/w kodzie skomentować lub usunąć return; wgrać szkic otworzyć terminal odczekać chwilę i sprawdzić wyniki. Poza liczbami, które oznaczają mignięcia diod zobaczymy też cyfry 1024 czy 1280 co oznacza , że w tym momencie stan niski był krótszy niż powinien. Debugera niestety nie mam więc jest dodatkowy if z return i w ten sposób działa to prawidłowo. Czasy on off są takie jak być powinny (czasem jest 1ms różnicy ale to mi nie przeszkadza). Jest dodatkowa funkcja do wprowadzania czasu timeSet() kolejno sekundy , minuty, godziny. Sprawdzałem z opóźnieniami 2-5 godzin (jest jakieś 2-5s opóźnienia).

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Gość
Dołącz do dyskusji! Kliknij, aby zacząć pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.


×
×
  • Utwórz nowe...