Skocz do zawartości

"Elektroniczny patefon" na bazie czujnika odległości HC-SR04


Adder

Pomocna odpowiedź

3 godziny temu, Adder napisał:

jako wkład własny zamierzam ominąć komendę digitalWrite dobierając się do rejestrów portów...

Polecam zamiast kolejnego wyważania otwartych drzwi świeżo wynalezionym kołem zapoznać się z dziełami poprzedników - np. https://github.com/NicksonYap/digitalWriteFast

  • Lubię! 1
  • Pomogłeś! 1
Link do komentarza
Share on other sites

No właśnie sugeruję, żeby w ogóle portami nie machać tylko zostawić to sprzętowi. Program ma tylko nadzorować proces: dowiadywać się kiedy poszedł impuls sondujący (wskazówka: generujesz okresową szpilkę w trybie PWM na pinie OC2x lub OC1x) i odczytywać wyniki z rejestru (wskazówka: doprowadzasz wyjście czujnika do pinu ICP1). Żadnej bezpośredniej aktywności programu na pinach. Nawet przerwania nie są tu potrzebne. Przeczytaj wyłącznie rozdział o Timerze 1 (w uproszczonej wersji także o Timerze 2) i to wystarczy do zrobienia tego dobrze.

  • Pomogłeś! 1
Link do komentarza
Share on other sites

Dzień dobry!

Walka z problemem "bouncing button" zajęła mi cały wczorajszy dzień (nie znałem tego problemu, a zapragnąłem mieć Rec i Stop na jednym przycisku... Rozgrzebałem cały program szukając błędu, potem godzinami składałem wszystko w całość...🤦‍♂️)

No nic... Załączam aktualny kod Arduino, a potem garść pytań. 

W kodzie umieściłem komentarze, więc się będzie łatwo zorientować (mam nadzieję). 

#include <digitalWriteFast.h>

#define trigPin 12
#define echoPin 11
#define recordPin 2
#define playPin 9
#define redLed 8
#define greenLed 4


boolean RecordingON=0;                                                                // Zmienna przechowująca informację, czy trwa nagranie
boolean Trigger;                                                                      // Zmienna przechowująca informację o stanie na trigPin    
  
volatile boolean recordingButtonPressed=0;                                            // Zmienna przechowująca informację, czy ostatnio naciśnięto i puszczono przycisk Record

void RButtonPressed() {                                                               // Funkcja wykonywana w sytuacji naciśnięcia i puszczenia przycisku Record. Obsługiwana przez przerwanie
  
  recordingButtonPressed=1; 
}


ISR(TIMER1_COMPA_vect){                                                               //Funkcja wysyłająca trigger na trigPin z częstotliością 10 000Hz sterowana Timerem 1 
  if (Trigger){
    digitalWriteFast(trigPin,HIGH);
    Trigger = 0;
  }
  else{
    digitalWriteFast(trigPin,LOW);
    Trigger = 1;
  }
}


unsigned long GetTime() {                                                             // Funkcja wyzwalająca impulsy na trigPin i odczytująca czas trwania impulsów zwrotnych na echoPIn

  return pulseIn(echoPin, HIGH);
 
}

boolean RecordingConfirmed(){                                                         // Funkcja nawiązująca połączenie z Processing i przygotowująca go odbioru danych z Arduino. "Shake-hand".

  boolean Connection=false;
  String Signal;

  while (!Connection) {
  if (Serial.available()<=0) {
  Serial.println("R");
  digitalWrite(redLed,HIGH);
  delay(50);  
  digitalWrite(redLed,LOW);
} else {
  Signal=Serial.readString();
  Signal.trim();
  if (Signal=="C") {
  digitalWriteFast(redLed,HIGH);
  Connection=true;
}  
}   
}
  return Connection;
}


void Record(){                                                                      // Funkcja wysyłająca czasy impulsów z echoPin do Processing    

  recordingButtonPressed=0;                                                         // Reset zmiennej wskazującej na naciśnięcie przycisku
  
  while (!recordingButtonPressed) {
  Serial.print(GetTime());
  Serial.print(",");
}                                                                                   // Wysyłanie danych na port szeregowy dopóki trwa nagranie

  Serial.println();
  digitalWriteFast(redLed,LOW);
  RecordingON=false;
  recordingButtonPressed=0;                                                         // Reset zmiennej wskazującej na naciśnięcie przycisku
  
}

void setup() {
  
  Serial.begin (9600);
  while (!Serial);
  
  pinMode(trigPin, OUTPUT); 
  pinMode(echoPin, INPUT); 
  pinMode(recordPin, INPUT_PULLUP);
  pinMode(playPin, INPUT_PULLUP);
  pinMode(redLed, OUTPUT);
  pinMode(greenLed, OUTPUT);

  digitalWriteFast(redLed, LOW);
  digitalWriteFast(greenLed,LOW); 

  attachInterrupt(digitalPinToInterrupt(recordPin),RButtonPressed, RISING);         // Przerwanie obsługujące przycisk Record

                                                                                    // Ustawienie przerwania na Timer1 na 10us
  cli();                                                                            // Wyłączenie wszystkich przerwań
  TCCR1A = 0;                                                                       // Zerowanie rejestrów
  TCCR1B = 0;                                                                       // Zerowanie rejestrów
  TCNT1  = 0;                                                                       // Inicjalizacja licznika
  OCR1A = 1599;                                                                     // Ustawienie Compare Match Register na (16*10^6) / (prescaler*10000Hz) - 1 = 1599 
  TCCR1B |= (1 << WGM12);                                                           // Włączenie trybu CTC
  TCCR1B |= (1 << CS10);                                                            // Ustawienie prescalera na 1     
  TIMSK1 |= (1 << OCIE1A);                                                          // Właczenie przerwań na Timer 1
  sei();                                                                            // Włączenie wszystkich przerwań
  
}

void loop() {
  
  TIMSK1 &= ~(1 << OCIE1A);                                                         // Wyłączenie przerwań na Timer 1 / Wyłączenie pulsów na trigPin
  while (recordingButtonPressed) {                                                  // "Anti-bouncing" dla przycisku Record
  RecordingON=(!RecordingON);
  recordingButtonPressed=0;
}
  
  while (RecordingON) {
  TIMSK1 |= (1 << OCIE1A);                                                        // Włączenie przerwań na Timer 1 / Włączenie pulsów na trigPin
  if (RecordingConfirmed()) Record();                                             // Realizacja nagrania
}
    
}

Pytania: 

1. Czy jestem na "kursie i ścieżce" wprost na jakąś katastrofę?

2. Czy dobrze policzyłem i ustawiłem prescaler i compare match register? Zrobiłem sobie do tych obliczeń całego excela, ale... jeśli założenia były złe, to i cała reszta...

3. Czy dobrze włączam i wyłączam przerwania na Timer1? Niestety poruszam się w tym temacie jakbym chodził na szczudłach po lodzie.

4. Gdzie można znaleźć składnię programowania rejestrów? Gdzie można znaleźć opisy samych rejestrów? Fascynujące jest to "nowo wynalezione koło" 🙂

5. Teraz trzeba pozbyć się Pulsein z programu. Czyli, Timer Capture Interrupt, tak? Czy może jakaś inna metoda?

Ogólnie program obecnie działa. Wyciągam >300Hz

 

 

 

Kalkulator prescalerów i CMR jaki zrobiłem:

 

115296183_Zrzutekranu2021-02-28o09_44_36.thumb.png.74f34d1eddc133206ae21503d8cd8407.png

Link do komentarza
Share on other sites

Zamiast publikować programiki, napisz po prostu w kilku prostych punktach, własnymi słowami co i jak chcesz zrobić. Wymień funkcje jakie program ma wykonywać i sposoby jak będzie to realizował oraz jakie są zasoby sprzętowe i jak ustawione do tego przeznaczasz. Wtedy od razu wyjdzie czy myślisz dobrze, czy głupoty zmyślasz. Nie ma sensu rozkminiać kolejnych kodów.

W zakresie obsługi czujnika SR04 masz doz robienia dwie rzeczy: generacja impulsu i pomiar długości odpowiedzi. Jak konkretnie chcesz to zobić, bo ogólny przepis juz dostałeś. Bez lania wody i opisywania jak to strasznie się męczysz, dwa zadania i dwa rozwiązania. Bez żadnego programu, same opisy.

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Kod jest ważny na etapie implementacji. Kiedy masz już obmyśłony sposób rozwiązania i próbujesz go zrealizować przekładając algorytm na któryś język programowania. Jeśli masz kłopoty dużo wcześniej, nie do końca wiesz jak działają timery i nie czujesz w jaki sposób powinieneś ich użyć albo jak podzielić zadania między sprzęt a oprogramowanie, to kod tylko zaciemnia. Nie rozumiejąc ogółu Tego co wymyśłiłeś muszę niepotrzebnie zagłębiać się w szczegóły. Nie wiem co umiesz i prosząc o kod zakładam, że wszystkie etapy pośrednie (od pomysłu i ogólnej koncepcji poprzez alokację zasobów, wydzielenie zadań dla sprzętu, jego obsługę i wreszcie najwyższy poziom programu ten odpowiedzialny za algorytm) masz w małym palcu. A jeśli Ty prosisz o recenzję dot. samej metody, to tę metodę opisz. Nie każ nam jej odcyfrowywać z programu gdzie ustawiasz masę jakichś tajemniczych bitów w rejestrach a my musimy z powrotoem przekładać to na nasze. Także spoko, nie denerwuj się, usiądź spokojnie i napisz po polsku co chciałeś albo co chcesz zrobić i jak. Krok po kroku. Uwierz mi, to naprawdę porządkuje myśli a jeśli tu będą błędy, to nie masz co pisać programu, bo cały ten czas będzie stracony na rozwiązywanie nie tego problemu. Na pewno dojdziemy do jakiegoś szkieletu implementacji zanim jeszcze napiszesz linijkę kodu. A mając przed sobą zrozumiały cel, program pisze się dużo łatwiej.

Link do komentarza
Share on other sites

(edytowany)

@marek1707 Rozumiem, masz rację. 

Zadam pytanie bardziej konkretne. Zmiana wartości preskalera Timera 1 wpływa na prędkość jego taktowania. Zmiany licznika zachodzą wolniej. Czy tym samym do zmierzenia długości pulsu na echu w uS też trzeba użyć prescalera? Czy już bredzę?

Zrobiłem tak: 

- ustawiam Timer 1 na prescaler 64 i Compare Match Register na 2499 - teoretycznie powinien się zerować co 1/100 sekundy

- potem liczę długości nadchodzących impulsów w czasie Timera 1 - wychodzą bzdury. 

Dzisiaj już nie mam siły na eksperymenty. Coś tu jednak nie gra z liczeniem czasu impulsu. Reszta programu działa zgodnie z koncepcją zaproponowaną przez Ciebie (tak mi się zdaje).

Edytowano przez Adder
Link do komentarza
Share on other sites

Masz dwa osobne zadania:

  1. Wygenerować impuls wyzwalający. To ta krótka szpilka do tej pory robiona przez programowe wysłanie jedynki a potem zera na pin portu. To jest kluczowe, bo okres powtarzania tego impulsu i jego precyzja determinują momenty prókowania, a te jak już wiesz muszą być precyzyjne. Przeznacz do tego  np. Timer 2. Ten blok ma tryb PWM (przeczytaj rozdział Fast PWM), w którym możesz sprzętowo generować impulsy na dwóch wyjściach. Ty potrzebujesz jednego, OC2B na pinie PD3. Timer jest 8-bitowy, ala ma prescaler. Ustaw zatem tryb pracy (WGMxx), prescaler (CSxx), bity sterujące wyjściami (COM2Bx) i okres timera tak, by blok pracował w trybie PWM z programowanym okresem i samodzielnie generował krótki impuls o wymaganej długości i okresie powtarzania. Jak podłączysz wejście TRIG czujnika SR04 do wyjścia timera OC2B to masz pomiar wyzwalany w precyzjnie wyznaczanych chwilach. Niech to będzie powiedzmy impuls 16us powtarzany co 4ms. Raz ustawiasz timer, odpalasz go i o sprawie zapominasz - wyzwalanie pomiarów od tej pory robi się samo.
  2. Zmierzyć długość impulsu odpowiedzi czujnika. Podłącz jego wyjście do pinu ICP1 (PB0) i koniecznie przeczytaj rozdział o tzw. Input Capture Timera 1. Mam nadzieję, że to jakoś Cię natchnie. Plan jest prosty: najpierw musisz zasadzić się na sprzętową detekcję zbocza narastającego, odczytać timestamp z rejestru ICR1 gdy już ono przyjdzie, przeprogramować detektor wejścia ICP1 na łapanie zbocza opadającego, poczekać na jego detekcję i odczytać z tego samego rejestru drugą wartość czasu. Różnica między nimi będzie długością impulsu.

Do roboty, Twój program nawet nie może dotykać się bezpośrednio do pinów portów. Wszystko ma zrobić sprzęt w jaki wyposażony jest procek, z dokładnością głównego generatora kwarcowego.

Powyższe można zrobić na jednym timerze, ale nie od razu Kraków.. Na rozgrzewkę zrób jak napisałem. Jeśli nie umiesz od razu napisać kodu, pisz po polsku jak chcesz ustawić timer i co który będzie robił.

  • Lubię! 1
Link do komentarza
Share on other sites

(edytowany)

Dzień dobry!

Dziękuję! Przemyślę to i może zrobię. 

Mój program jest już zupełnie różny od tego co tutaj wkleiłem. Cały algorytm pomiaru odbywa się aktualnie w przerwaniach

1. Ustawiam CTC na puls 10uS - impulsy wysyłają się same

2. W obsłudze przerwania CTC upewniam się, że wysłany jest jeden i tylko jeden puls a następnie wyłączam CTC i włączam Capture RISING na pinie 8

3. W obsłudze Capture czytam czas rosnącego pulsu przełączam tryb na Falling i czytam czas opadającego pulsu - uzyskując długość. Następnie przełączam Timer na CTC (i tak w kółko) . To działa. Całą robotę robią przerwania.  Poniżej kody (tylko obsługa przerwań) - z objaśnieniami 🙂 

Ale, miałem dzisiaj nowy sen. O ciężarówce z piachem. Muszę zrobić jeszcze jeden eksperyment. Jeśli się uda to coś czuję, że będziemy się razem zastanawiali jak sobie poradzić z taką ilością sampli jaką uzyskamy. No, ale nie dzielmy skóry na niedźwiedziu. 👍

ISR(TIMER1_COMPA_vect){                                                               //Funkcja wysyłająca pojedynczy puls o długości 10uS na trigPin a potem ustawiająca Timer1 na odbiór wznoszącego sygnału z echoPin w trybie Capture
  
  if (trigPinState==LOW){
  digitalWriteFast(trigPin,HIGH);
  trigPinState = HIGH;
}
  else {
  digitalWriteFast(trigPin,LOW);
  trigPinState = LOW;
 
  cli();                                                                            // Przełączenie Timera 1 na tryb Interrupt on Capture na Rising Edge    
  TCCR1A = 0;                                                                       // Zerowanie rejestrów
  TCCR1B = 0;                                                                       // Zerowanie rejestrów
  TCNT1  = 0;                                                                       // Inicjalizacja licznika
  OCR1A = 2499;                                                                     // Ustawienie Compare Match Register na (16*10^6) / (prescaler*100Hz) - 1 = 2499 
  TIMSK1 = (1<<ICIE1);                                                              // Włączenie trybu Interrupt on Capture
  TCCR1B |= (1 << CS10) | (1 << CS11);                                              // Ustawienie prescalera na 64    
  TCCR1B |= (1 << ICES1);                                                           // Włączenie oczekiwania na wzrastający sygnał
  TIMSK1 |= (1 << OCIE1A);                                                          // Właczenie przerwań na Timer 1
  sei();
}
ISR (TIMER1_CAPT_vect) {                                                            //Funkcja czytająca wzrastający sygnał na echoPin, włączająca oczekiwanie na opadający sygnał i licząca czas pulsu
  
 static unsigned RisingEdgeTime = 0;                                                // Zmienna przechowująca czas sygnału rosnącego 
 static unsigned FallingEdgeTime = 0;                                               // Zmienna przechowująca czas sygnału opadającego 
   
  
  if (TCCR1B & (1 << ICES1)) {                                                      // Sprawdzenie czy sygnał wzrastający, czy opadający
  RisingEdgeTime = ICR1;                                                            // Sprawdzenie czasu sygnału wzrastającego
  TCCR1B &= ~(1 << ICES1);                                                          // Włączenie oczekiwania na syganł opadający
}
  else {                                                                                
  FallingEdgeTime = ICR1;                                                           // Sprawdzenie czasu sygnału opadającego
  echoPulseTime = FallingEdgeTime - RisingEdgeTime;                                 // Obliczenie pulsu na echoPin
  NewPulse=1;                                                                       // Przekazanie informacji o nowym odczycie pulsu
                                                                                    
  cli();                                                                            // Przełączenie Timera 1 na tryb CTC 
  TCCR1A = 0;                                                                       // Zerowanie rejestrów
  TCCR1B = 0;                                                                       // Zerowanie rejestrów
  TCNT1  = 0;                                                                       // Inicjalizacja licznika
  OCR1A = 1599;                                                                     // Ustawienie Compare Match Register na (16*10^6) / (prescaler*10000Hz) - 1 = 1599 
  TCCR1B |= (1 << WGM12);                                                           // Włączenie trybu CTC
  TCCR1B |= (1 << CS10);                                                            // Ustawienie prescalera na 1     
  TIMSK1 |= (1 << OCIE1A);                                                          // Właczenie przerwań na Timer 1   
  sei();
}
}

 

 

Im dłużej patrzę na obsługę przerwania na przechwyceniu, tym bardziej jestem pewien że coś w niej jest skopane i zamiast pulsu mierzę nie wiadomo co...

Edytowano przez Adder
Link do komentarza
Share on other sites

Mój problem polega obecnie na tym, że otrzymuję długości pulsu w jakichś innych jednostkach niż uS. 

Zmieniające się w zależności od odległości czujnika od głośnika, ale inne. 

Nie mogę sobie z tym poradzić. Nie mogę się skupić. Ciągle myślę o tej idei przebudowy programu, aby robić te tysiące sampli na sekundę.  🤷‍♂️

Link do komentarza
Share on other sites

(edytowany)
1 godzinę temu, Gieneq napisał:

A do czego miałby służyć ta tablica?

Dzięki!

Do zapisywania wartości typu int. 

Od biedy poradzę sobie ze statyczną.

Jestem prawie pewien, że ograniczenia programu (niska ilość zapisanych w pliku sampli) nie wynikają z metody próbkowania. Nie powiem, praca z przerwaniami jest ciekawa i pożyteczna, ale nie zwiększy ilości zapisanych sampli. Nie tu jest pies pogrzebany. Pomimo błędów w obsługach przerwań jakich narobiłem, i które muszę poprawić, jestem już tego prawie na 100% pewien.

Wąskim gardłem jest Serial. Dlatego jak najwięcej danych musi być przechowanych w pamięci płytki. Dopiero potem wysyłanych na Serial. 

Swoją drogą myślę, że możliwych do uzyskania sampli w ciągu sekundy może być nawet kilka tysięcy. Po uporaniu się z próbkowaniem zrobi się wielki problem z dostępną pamięcią. 

Edytowano przez Adder
  • Lubię! 1
Link do komentarza
Share on other sites

Ach, już wiem skąd te dziwne wartości odczytów. Przy preskalerze 64 licznik zmienia się co 4 uS., a nie co jedną uS. Chyba lepiej zmienić preskaler na 8 i dzielić otrzymane wartości przez 2, a w CMR ustawić na jakąś wielką wartość w rodzaju 65535.

Link do komentarza
Share on other sites

Zwiększenie wartości baud rate do 38400 spowodowało zwiększenie ilości zapisanych odczytów sampli w ciągu sekundy do 1,3k

Czyli tak jak myślałem. 

Niestety, obsługa przerwań sprawia mi wielkie problemy. W ogóle nie potrafię się w tym poruszać. 

Link do komentarza
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!

Anonim
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...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.