• Witaj na Forum Arduino Polska! Zapraszamy do rejestracji!
  • Znajdziesz tutaj wiele informacji na temat hardware / software.
Witaj! Logowanie Rejestracja


Ocena wątku:
  • 0 głosów - średnia: 0
  • 1
  • 2
  • 3
  • 4
  • 5
VU metr na ATTiny85, dziwne zachowanie ADC.
#1
Witam

Walczę od kilku dni z "analogowym" wskaźnikiem wysterowania. Jest to projekt niejakiego Daniela C.:
https://sites.google.com/view/arduino-co...d_attiny85

Kod źródłowy nie zawiera instrukcji sterujących wyświetlaczem, ale nie to jest problemem.

Do oryginalnego kodu próbuję dorobić funkcję regulacji kontrastu za pomocą potencjometru oraz zmiany trybu wyświetlania negatyw/pozytyw poprzez zwarcie pinu do masy.
Ponieważ PB0 i PB2 używane są do obsługi magistrali I2C zaś PB3 jest wejściem sygnału, wybór padł na PB1 do przełączania pozytyw/negatyw oraz PB4 (ADC3) do obsługi potencjometru.

Przełączanie negatyw/pozytyw zostawiam na koniec, bo to nie powinno sprawiać problemów. Uznałem, że zacznę od regulacji kontrastu, ponieważ ta z wielu powodów może sprawiać problemy. Niestety wykrakałem, choć nie takich problemów się spodziewałem.
Oryginalny kod odpowiedzialny za obsługę wejścia analogowego wygląda tak:

Kod:
while(1){
sig_Read=map(analogRead(A3),0,1023,14,120);  //14 start 120 end
if (sig_Read>sig_Mem) {Refresh=0;}
}


Analogicznie dopisałem obsługę potencjometru:

Kod:
ReadContrast = map(analogRead(A2), 0, 1023, 0, 255);
if (ReadContrast != Contrast){
  Contrast = ReadContrast;
  TinyOLED_send_command(0x81);
  TinyOLED_send_command(Contrast);
}

Oczywiście zmienna "Contrast" jest wcześniej zadeklarowana jako zmienna globalna i zainicjowana.

Całość pętli głównej wygląda tak:

Kod:
while(1){
ReadContrast = map(analogRead(A2), 0, 1023, 0, 255);
if (ReadContrast != Contrast){             
  Contrast = ReadContrast;                 
  TinyOLED_send_command(0x81);           
  TinyOLED_send_command(Contrast);       
 
}

sig_Read=map(analogRead(A3),0,1023,14,120);  //14 start 120 end
if (sig_Read>sig_Mem) {Refresh=0;}

FIRST_RUN:;
switch (Refresh) {
  case 0:sig_Send=VuDelay();Tiny_Flip_VU();Refresh++;break;
  case 1 ... 4 :Refresh=(Refresh<4)?Refresh+1:0;break;
  default:break;
    }

  }
}

Taki kod działa, ale regulacja kontrastu zmienia także poziom wskazywany przez wskaźnik i na odwrót - sygnał wejściowy dla wskaźnika zmienia kontrast. Dzieje się to w pełnym zakresie tzn. min kontrast = min wskazanie VU, zaś max kontrast = max wskazanie VU.
Zachowuje się to tak jakby wyprowadzenia portów PB3 i PB4 były fizycznie zwarte ale nie są.

Wiele wskazuje na to, że przetwornik ADC pracuje w trybie "Differential", a nie "Single End".
Częściowo dowodzi temu modyfikacja kodu wymuszająca w rejestrze ADMUX odpowiednie ustawienie bitów:

Kod:
while(1){
ADMUX = 00000010;
ReadContrast = map(analogRead(A2), 0, 1023, 0, 255);
if (ReadContrast != Contrast){             
  Contrast = ReadContrast;                 
  TinyOLED_send_command(0x81);             
  TinyOLED_send_command(Contrast);         

}

ADMUX = 00000011;
sig_Read=map(analogRead(A3),0,1023,14,120);  //14 start 120 end
if (sig_Read>sig_Mem) {Refresh=0;}

FIRST_RUN:;
switch (Refresh) {
  case 0:sig_Send=VuDelay();Tiny_Flip_VU();Refresh++;break;
  case 1 ... 4 :Refresh=(Refresh<4)?Refresh+1:0;break;
  default:break;
    }

  }
}

Taki kod działa zdecydowanie lepiej, jednak nadal widoczny jest niewielki wpływ regulacji kontrastu na wskazania VU i na odwrót choć to może wynikać z jakiegoś przesłuchu. Poza tym wydaje mi się, że samo analogRead() powinno ustawić odpowiednie bity w rejestrze ADMUX.

Elektronikiem podobno jestem niezłym, ale programistą raczej marnym i skończyły mi się pomysły. Gotów jestem uznać, że jest to jakiś błąd Arduino. Nie mam pojęcia jak sprawdzić co dokładnie robi funkcja analogRead() i w jaki sposób ustawiane są bity konfiguracyjne ADC w rejestrze ADMUX w zależności od parametru przekazywanego do funkcji. 
Nigdzie nie znalazłem informacji o tym że wejścia ADC domyślnie pracują w trybie różnicowym ani też jak to zmienić. Znalezione w sieci przykłady dotyczą obsługi jednego wejścia analogowego, a nie dwóch.
Jednak z jakiegoś powodu autor kodu w innych swoich projektach korzysta z ADC0 i ADC3, a nie ADC3 i ADC2 gdy potrzebuje dwóch wejść analogowych, co może sugerować że również natknął się na ten problem.

Czy ktoś ma pomysł co można zrobić aby to działało prawidłowo bez żadnych kombinacji?
 
Odpowiedź
#2
Niektórych rzeczy się w Arduino nie przeskoczy. Jak chcesz mieć sam pełną kontrolę na pinami i rejestrami to zostaje C i Atmel Studio czy tam Eclipse i C. Tu się może wszystko dziać, bo uC nie ma I2C ani za bardzo RAM.
Miło być decenianym https://buycoffee.to/kaczakat
 
Odpowiedź
#3
Dzięki za odpowiedź.

Właśnie o to chodzi że nie chcę mieć 100% kontroli nad rejestrami itp. Po to się sięga po języki takie jak Arduino żeby nie trzeba było się w takie rzeczy bawić. Równie dobrze można zejść jeszcze niżej i zamiast w C pisać w assemblerze.
Jak wspomniałem, programistą jestem raczej kiepskim, coś tam wiem ale jestem za cienki aby napisać własny tego typu kod od początku do końca. Gdybym umiał to bym napisał, a nie przerabiał czyjś. Smile

Spodziewałem się, że wywołanie funkcji analogRead() samoczynnie ustawi odpowiednie rejestry uC. W tym przypadku wiele wskazuje na to że tak się nie dzieje. Może trzeba coś więcej niż wywołać funkcję? Próbowałem podejrzeć jak to robią inni ale niestety nie znalazłem niczego sensownego.

Jeśli chodzi o sam kod to chyba tragedii nie ma. Wprawdzie kompilator podaje tylko bardzo ogólne informacje, ale nie wygląda to źle:
Kod:
Sketch uses 3972 bytes (48%) of program storage space. Maximum is 8192 bytes.
Global variables use 15 bytes (2%) of dynamic memory, leaving 497 bytes for local variables. Maximum is 512 bytes.
Ramu faktycznie nie ma wiele ale jeśli chodzi o I2C to w tym uC sam Atmel poleca wykorzystać USI do tego celu. Jest nawet dokument temu poświęcony - "AVR310: Using the USI module as a TWI Master"
Można tam trafić na taką informację:
Cytat:The USI Two-wire mode is compliant to the TWI bus protocol, but without slew rate limiting on outputs
and input noise filtering.
Czyli w zasadzie można mówić o sprzętowym I2C choć z pewnymi ograniczeniami. Czy Arduino z tej możliwości korzysta nie wiem.

Ogólnie oryginalny kod działa dobrze, nie zauważyłem aby się coś zacinało itd.. Zresztą sądząc po zawartości strony autora tego kodu, można dojść do wniosku że zna się na tym, tworzy np. mini konsole do gier z wykorzystaniem tego uC.
Oryginalnego kodu i zasobów uC bym się nie czepiał, błąd jest albo po mojej stronie, albo w jakiejś bibliotece Arduino. Chciałbym się dowiedzieć co tu jest nie tak i albo to poprawić jeśli to moja wina, albo ew. zgłosić błąd do autora wadliwej biblioteki.
Rozwiązanie z ręcznym ustawianiem zawartości rejestru traktuje jako działanie diagnostyczne, a nie docelowe, szczególnie że i tak nie działa w 100% poprawnie.
 
Odpowiedź
#4
Wiem jak to zrobić w C, wiem jak to zrobić w Arduino, ale po prostu widzę, że tu ktoś pisząc kod miesza zabawę na rejestrach z gotowymi funkcjami Arduino i to właśnie może prowadzić do nieoczekiwanych zachowań. Może to, może ilość zasobów. Wg mnie to po prostu zupełnie niepotrzebna gimnastyka, jak bym walczył o każdy bajt pamięci RAM i flash to nie pisałbym programu w Arduino, w tworzeniu i testowaniu jakiegoś pojedynczego prototypu, gdy cena uC Atmega88 czy 168 czy 328 to są jakieś grosze różnicy względem Attiny85. Wręcz jak kupowałem tackę uC Atmega88 na Ali to były taniej niż Attiny85. Widzę, że tacka dalej jest tańsza, tylko teraz trzeba wydać 40zł by przesyłka była darmowa. W sumie to teraz nawet 328p jest taniej niż attiny85, jakieś chore te ceny.
Miło być decenianym https://buycoffee.to/kaczakat
 
Odpowiedź
#5
Attiny85 ma tutaj taki sens, że jest mały. Da się do tego układu zrobić malutką płytkę wymiarami identyczną jak płytka OLED i całość złożyć "na kanapkę" i to w wersji DIL8.
Nie znam Atmegi w obudowie 8 nóżkowej, trzeba byłoby użyć jakiejś w TQFP, ale to powoduje że ciężko jest się zmieścić na jednostronnej płytce o takich wymiarach jak OLED, a dwustronną ciężko jest wytrawić w warunkach domowych, zaś zamawiać w Chinach w tym konkretnym przypadku (potrzebuję póki co 2 sztuki) nie ma sensu.
Projektów takich VU jest sporo, wybrałem właśnie ten dlatego że można go ładnie zminiaturyzować. Dałoby się nawet zrobić nową identyczną wymiarami płytkę do OLED i od razu na niej upchać uC wówczas oczywiście w SMD. Może nawet coś takiego zrobię, zobaczę jak ten VU będzie się sprawdzał w praktyce. Z atmegą to nie będzie możliwe ze względu na rozmiar obudowy.
Ceny powariowały już dawno, zaczęło się od koronawirusa. Teraz w ogóle nie opłaca się bawić ani w Mega, ani w Tiny bo ze 2-3 lata temu wyszła nowa rodzina uC - AVR Dx, która funkcjonalnie bije na głowę każdego wcześniejszego, a cenowo jest tańsza.

Zobacz np. AVR16EA32, technicznie można porównać ten uC do Mega168, tyle że w Mouserze kosztuje niecałe 4.6zł, za Mega 168 trzeba zapłacić min. 1zł więcej.
Mega i Tiny to po prostu już stare procesory, trzeba pamiętać że ich historia sięga końca lat 90 ubiegłego wieku. W technice to są ze dwie epoki.
Za chwilę pewnie wszystkie stare (poatmelowe) Mega i Tiny pojawią się jako "not recommended for new design", a za jakieś 5 lat większość nie będzie już produkowana.


Ale wracając do tematu - program już działa, zgodnie z oczekiwaniami błąd był po mojej stronie. Okazuje się, że trzeba skonfigurować wyprowadzenie uC jako wejście. Jeśli ma to być wyprowadzenie "cyfrowe" to oczywiste że trzeba określić czym ma być a jeśli wejściem to jakim, ale dla mnie dziwne jest że analogowe także wymaga takiej konfiguracji, przecież wyjściem być nie może a podciąganie do V+ nie ma sensu, a wręcz byłby to skandaliczny błąd. Nie rozumiem dlaczego funkcja analogRead() tego nie robi z automatu, przecież i tak na którymś etapie te nieszczęsne bity w taki czy inny sposób trzeba ustawić.
Być może dlatego że nie wszystko jest do końca przemyślane i zrobione zgodnie z logiką to zmusza bardziej świadomych programistów do operowania bezpośrednio na rejestrach.
Z drugiej strony zauważyłem że zwykle jak ktoś jest dobrym programistą, to najczęściej takim sobie elektronikiem i na odwrót.

W każdym razie:

Kod:
void setup() {
pinMode(1, INPUT_PULLUP);
pinMode(A2,INPUT);
pinMode(A3,INPUT);
TinyOLED_init();

rozwiązuje problem.
W oryginalnym kodzie ta konfiguracja jest w innym pliku - ELECTROLIB.h i wygląda tak:

Kod:
void TINYJOYPAD_INIT(void){
pinMode(1,INPUT);
digitalWrite(4,LOW);
pinMode(4,OUTPUT);
pinMode(A0,INPUT);
pinMode(A3,INPUT);

zakomentowałem już wcześniej konfigurację związaną z PB4 aby nie kolidowała ponieważ zamierzałem go użyć jako ADC2, ale jakoś mi umknęło że wejścia analogowe też są tutaj skonfigurowane.
Finalnie cała ta funkcja wyleciała, a konfigurację przeniosłem do funkcji setup bo tak jest to o wiele bardziej czytelne.
Zresztą oryginalny kod odchudziłem o kilka innych rzeczy, bo to jest ta sama "biblioteka" którą autor kodu używa do tworzenia gier. Zawiera kilka funkcji oraz zmiennych potrzebnych do obsługi np. joysticka czy generowania dźwięku które są kompletnie zbędne w tym przypadku.

Jeśli chodzi o zasoby sprzętowe, nie umiem tego dokładnie policzyć, ale szacując mniej więcej wychodzi mi że wcale nie trzeba dużo RAM. Jeśli dobrze rozumiem działanie programu, to potrzeba cały 1 bajt na grafikę, kolejny na maskę określającą położenie wskazówki i kolejny na wskaźnik "peak". Razem 3 bajty, do tego dochodzi kilkanaście zmiennych które są typu uint8_t oraz dwie zmienne 2 bajtowe dla wartości ADC. Mieścimy się w 128 bajtach, a do dyspozycji jest 512. Pamięć flash zgodnie z tym co wyświetla kompilator, zajęta w ok. 51%, wychodzi więc na to że taki Tiny85 jest aż nadto do takiego zastosowania. Gdyby dobrze zoptymalizować kod, to pewnie dałoby się to zamknąć w Tiny45, bo bez moich dodatków w postaci regulacji kontrastu i zmiany trybu wyświetlania negatyw/pozytyw, a po pobieżnym usunięciu zbędnych funkcji udało mi się zejść do 48% zajętości flash. Z ciekawości możne sprawdzę czy będzie działało na Tiny45. Smile
Przerabiałem jeszcze sterownik grafiki bo ja używam wyświetlacza 1.3" który jest na SH1106, a nie SSD1306 co w sumie zajęło kolejny 1% pamięci programu, za to pewnie dałoby się samą bibliotekę wyświetlacza napisać tylko dla 1106 i wówczas zwolnilibyśmy kolejne % pamięci flash.
 
Odpowiedź
#6
Bo nie musisz ustawić żadnego pinu na INPUT, każdy jest w tym trybie po resecie, jeśli z jakiegoś powodu w czasie działania programu przestawisz na OUTPUT, to tylko wtedy jak potem z niego chcesz odczytać sygnał cyfrowy lub analogowy przestawiasz na INPUT.
Miło być decenianym https://buycoffee.to/kaczakat
 
Odpowiedź
#7
Zatem dlaczego bez ustawienia pinu jako INPUT dzieją się dziwne rzeczy, a po ustawieniu zaczęło działać prawidłowo?

Coś mi się kojarzy, że już kiedyś się na to naciąłem pisząc w asemblerze. Trzeba było ustawić rejestry bo piny uC są trójstanowe i po resecie są w stanie wysokiej impedancji.

W datasheet jest tak:

Cytat:The port pins are tri-stated when reset condition becomes active, even if no clocks are running.

Według tabelki datasheet gdy rejestr DDxn oraz PORTxn, są wyzerowane to wówczas bit PUD w MCUCR nie ma znaczenia, a wyprowadzenie uC jest w stanie wysokiej impedancji.
Wartości początkowe (Initial Value) dla tych rejestrów to właśnie 0.


Załączone pliki Miniatury
       
 
Odpowiedź
#8
Wystarczy, że wgrasz prosty program napisany w C i sobie to sprawdzisz. Piny są wejściami po resecie, te klasyczne AVR inaczej nie umieją. Arduino nie było wymyślone pod wyczynowe pisanie kodu na Attiny i mogą tu się dziać różne rzeczy, bo jest za mała ich fanatyków na tej platformie i nie wszystko zostało przetestowane w każdych warunkach.
Faktycznie te tanie Atmega88 są w wersji SMD, ale ich lutowanie jest łatwiejsze niż się wydaje, topnik i napięcie powierzchniowe cyny sprawia, przy odrobinie wprawy, że przejeżdżasz grotem i po robocie.
Miło być decenianym https://buycoffee.to/kaczakat
 
Odpowiedź
#9
W wolnej chwili sprawdzę jak to się zachowuje, bo kojarzę że już kiedyś się na ten problem naciąłem.

Lutowanie SMD to żaden problem, ja ze 30 lat trzymam lutownicę w ręku. Smile Dla mnie do lutowania to wszystko jedno czy będzie obudowa DIL, SO, TQFP czy MLF. Problemem jest wykonanie dwustronnej PCB w warunkach domowych, oczywiście to nie jest niemożliwe bo już raz robiłem ale właśnie dlatego że robiłem to drugi raz nie mam ochoty. W Chinach zamawiać póki co nie chcę, bo nie wiem czy zrobię kiedykolwiek więcej niż 2 sztuki tego urządzenia.
Na jednostronnej płytce będzie ciężko, bo taki Mega88 zajmuje jakieś 30% powierzchni PCB, Ponadto tutaj potrzeba pracy uC z taktowaniem 16MHz, Mega88 czy inne z tej rodziny tego nie umieją na wewnętrznym taktowaniu. Trzeba więc dodać kwarc albo generator który zajmie kolejną przestrzeń. Tiny85 ma PLL i dzięki temu osiąga 16MHz z wewnętrznego oscylatora.

Tiny85 to wręcz idealny uC do tego zadania, tylko faktycznie chyba lepiej byłoby przepisać kod na klasyczne C. Może kiedyś podejmę próbę, choć obawiam się że jestem na to za cienki.

Póki co muszę trochę popracować nad tą regulacją kontrastu, bo wykonywanie tego na przemian z normalną pętlą powoduje widoczne spowolnienie działania programu.
Muszę pomyśleć gdzie to wkleić aby zminimalizować problem, albo zrezygnować z możliwości bieżącej regulacji (która w sumie i tak jest zbędna bo ustawia się to raz) na rzecz dobranego dzielnika napięcia i sprawdzania jego stanu tylko po resecie. Nie chcę tego robić na stałe w kodzie, chcę mieć możliwość zmiany kontrastu bez używania programatora.
 
Odpowiedź
#10
Może potrzeba 16MHz, może nie, czasami to co robisz w Attiny programowo Atmega zrobi sprzętowo mając UART, I2C, itp.
Miło być decenianym https://buycoffee.to/kaczakat
 
Odpowiedź
  


Skocz do:


Przeglądający: 1 gości