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

Wersja alternatywna z wykorzystaniem timer1 do odmierzania czasu. Rozmiar szkicu zmniejszony do 5%.


volatile uint8_t count;
volatile uint8_t countBuf;

uint8_t timerSeconds;
uint8_t timerSecondsBuf;
uint8_t score;

uint16_t timer1;
uint16_t ledCounter;


#define BAUD_RATE 9600
#define BAUD_RATE_DIVISOR (F_CPU / 16 / BAUD_RATE - 1)

void setup() {

  UCSR0A = 0 << TXC0 | 0 << U2X0 | 0 << MPCM0;
  UCSR0B = 0 << RXCIE0 | 0 << TXCIE0 | 0 << UDRIE0 | 0 << RXEN0 | 1 << TXEN0 | 0 << UCSZ02 | 0 << TXB80;
  UCSR0C = 0 << UMSEL01 | 0 << UMSEL00 | 0 << UPM01 | 0 << UPM00 | 0 << USBS0 | 1 << UCSZ01 | 1 << UCSZ00 | 0 << UCPOL0;
  UBRR0 = BAUD_RATE_DIVISOR;

  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

  TCCR1A = 0;
  TCCR1B = 1 << WGM13 | 1 << WGM12 | 1 << CS12 | 1 << CS10; //prescaler set to 1024
  TIMSK1 = 32;  // enable capture event interrupt
  ICR1 = 15625; //input capture register 1s
}

void loop() {
  start(timeSet(2, 0, 0), timeSet(1, 0, 0), timeSet(1, 0, 0), 170);
}

ISR(TIMER1_CAPT_vect) {
  if (count == 255) {
    countBuf = 0;
    count = 0;
  }
  count++;
  timerSeconds++;
  if (!timerSeconds) timerSecondsBuf++;
}

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

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

void results() {
  if (ledCounter - score > 0 && ledCounter - score < 5 ) bitSet(PORTC, (ledCounter - score) - 1);
}

void start(uint16_t wait, uint16_t onTime, uint16_t offTime, uint16_t counter) {
  static uint8_t index = 0;
  uint8_t 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};
  for (countBuf; countBuf < count; countBuf++) {
    timer1 = timerSeconds + (timerSecondsBuf * 256);
    //printi(timer1); printc('\n');

    if (timer1 == wait + 1 && ledCounter <= (counter - 1)) {
      (pin[index] == 12 || pin[index] == 13) ? PORTB |= 1UL << (pin[index] - 8) : PORTD |= 1UL << pin[index];
      ledCounter++; //printi(ledCounter); printc('\n');
    }
    else if (timer1 == wait + 1 + onTime) {
      (pin[index] == 12 || pin[index] == 13) ? PORTB &= ~(1UL << (pin[index] - 8)) : PORTD &= ~(1UL << (pin[index]));
      results(); printi(score); printc('\n');
    }
    if (timer1 == wait + onTime + offTime) {
      index++; if (index == 30) index = 0;
      timerSeconds = wait;
      //if (ledCounter == 5) ICR1 = 3999;
      ledCounter < 34 ? ICR1 -= 233 : ICR1 -= 46;
    }
    else if (timer1 > wait + onTime + offTime) timerSeconds = 0;
  }
}

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

void printc(char c) {
  loop_until_bit_is_set(UCSR0A, UDRE0); // wait for transmit buffer to be empty
  UDR0 = c; // transmit character out the serial port
}

void prints(char *s) {
  while (*s) {
    printc(*s);
    s++;
  }
}

void printi(long i) {
  char s[25]; // character buffer to build string in
  itoa(i, s, 10); // convert integer to ASCII string, base 10
  prints(s); // now print the string on the serial port
}

Rozgrywka nie kończy się po zapaleniu 4 diod a trwa dalej aż do 170 mignięć. Wartości poniżej jednej sekundy można uzyskać modyfikując rejestr ICR1 np: dajemy w komentarz tą linijkę

//ledCounter < 34 ? ICR1 -= 233 : ICR1 -= 46;

i tą

//printi(score); printc('\n');

odznaczamy komentarz w tej linijce

if (ledCounter == 5) ICR1 = 3999;

i tej

printi(timer1); printc('\n');

wartości dla funkcji start ustawiamy np:

start(timeSet(15, 0, 0), timeSet(6, 0, 0), timeSet(2, 0, 0), 20);

z takimi zmianami szkic można wgrać do arduino i otworzyć serial monitor. Po piętnastu sekundach diody zaczynają się zmieniać i po 5 mignięciach zostaje ustawiona wartość dla rejestru ICR1 na 3999 czyli 256 ms i co tyle jest aktualizowana zmienna timer1. Natomiast czas włączenia będzie wynosił 256ms * 6 =  1536ms i czas wyłączenia 2*256=512ms. Czas można też spowolnić ustawiając wartość dla rejestru ICR1 np na 62499 czyli 4s. W takim wypadku zmienna timer1 będzie aktualizowana co 4s  , włączenie 6*4 =24s i wyłączenie 6*2=12s. Co do samych wartości dla rejestru ICR1 to można pobrać darmową aplikację z google play AVR Calculator w, której wystarczy podać kilka parametrów  (w tym wypadku procesor 16MHz,  timer 16 bit, Prescaler 1024 i podać czas jaki nas interesuje lub częstotliwość i kliknąć calculate). Ten kod nie działa w simulIDE  trzeba wgrywać do prawdziwego arduino :).

  • 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 i zacznij 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...