Trudne początki C

To forum jest dla wszystkich pasjonatów mikrokontrolerów AVR Atmela. Wymiana doświadczeń i pomoc dla początkujących w pisaniu programów zarówno w C, Asemblerze jak i BASCOM. Zapraszam znawców tematu, aby pomogli wszystkim początkującym!
ODPOWIEDZ
Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Trudne początki C

Post autor: M@ciej » 10 paź 2014, 11:21

Witam,

W końcu zdecydowałem się na naukę C, próbuje swoich sił pod AVRGCC. Napisałem taki prosty program:

Kod: Zaznacz cały

#define F_CPU 1000000
#include <avr/io.h>
#include <util/delay.h>

#define SET_LED1 PORTA |= (1<<PA4) 
#define RESET_LED1 PORTA &= ~(1<<PA4)
#define SET_LED2 PORTA |= (1<<PA5) 
#define RESET_LED2 PORTA &= ~(1<<PA5)
#define SET_LED3 PORTA |= (1<<PA6) 
#define RESET_LED3 PORTA &= ~(1<<PA6)
#define SET_LED4 PORTA |= (1<<PA7) 
#define RESET_LED4 PORTA &= ~(1<<PA7)
#define S1_0 !(PINA & 0x01)
#define S1_1 (PINA & 0x01) 
#define S2_0 !(PINA & 0x02)
#define S2_1 (PINA & 0x02)
#define S3_0 !(PINA & 0x04)
#define S3_1 (PINA & 0x04)
#define S4_0 !(PINA & 0x08)
#define S4_1 (PINA & 0x08)

int main(void)
{
 DDRA = 0xF0; //&B11110000 - 4 najm-odsze jako wejscia, 4 najstarsze wyjscia
 DDRB = 0xFF; //&B11111111 - wszystkie jako wyjscia
 PORTA = 0x0F; //&B00001111 - 4 najmlodsze podciagniete, 4 najstarsze wyzerowane
 //unsigned char opu=10;
 int swt1=0, swt2=0;
 
 while(1)
 {
   
  if((S1_0) & (swt1==0))
  {
   swt1=1;
  }
  
  if((S1_1) & (swt1==1))
  {
   SET_LED1;
   swt1=0;
  }
 
  if((S2_0) & (swt2==0))
  {
   swt2=1;
   SET_LED4;
  }
  
  if((S2_1) & (swt2==1))
  {
   RESET_LED1;  
   swt2=0;
  }

 }

}
Z założenia przyciśnięcie i puszczenie klawisza podpiętego pod Porta.0 ma zaświecić diodę podpiętą pod Porta.4 i to działa.
Natomiast przyciśnięcie i puszczenie klawisza podpiętego pod porta.1 powinno zgasić tą diodę, ale tak się nie dzieje. Sprzętowo wszystko jest dobrze podłączone. Używam Attiny26, bo taki miałem akurat pod ręką.

Dodatkowo zaremowanie wyrażenia "& (swt2==0))" powoduje, że nawet zaświecenie diody LED nie jest już możliwe i moim zdaniem przeczy to logice tego programu.
Inny problem to, że procek nie zeruje się po włączeniu zasilania, tylko pozostaje w tym stanie, w jakim go zostawiłem. Dopiero zwarcie pinu RESET do masy powoduje jego wyzerowanie. Do pinu RESET mam podpięty 47k do +5V i 100nF do masy.

Awatar użytkownika
Ertew
Użytkownik
Posty: 1418
Rejestracja: 03 lip 2005, 10:36
Lokalizacja: Leszno
Kontakt:

Post autor: Ertew » 11 paź 2014, 0:20

Uwaga, pisane na śpiąco. Możliwe że poniższy tekst zawiera błędy logiczne. Jak by coś nie grało, zapraszam do dyskusji o normalniejszej porze.

________________________________



Nigdy nie miałem problemu z pozostaniem procesora w poprzednim stanie mimo odłączenia zasilania, ale też nigdy nie wieszałem kondensatora na pinie reset.
Sytuacja może być o tyle zabawna, że pin reset nie ma wbudowanej diody zabezpieczającej przed podaniem napięcia wyższego od VCC bo na reset podajesz 12V podczas programowania wysokonapięciowego (zależnie od uC, szeregowe HVSP lub równoległe HVPP)

Po pierwsze więc, wywal kondensator z pinu reset. W tym miejscu powinien być tylko rezystor (możesz zmniejszyć go do wartości 22k albo 10k). Kondensator powinien być na zasilaniu.
To powinno rozwiązać problem pamiętania poprzedniego stanu. Jeśli nie, to po odłączeniu zasilania zewrzyj główny kondensator i spróbuj ponownie.



Drugi błąd jaki mi się rzucił w oczy to użycie operacji bitowej & zamiast logicznej &&:

Kod: Zaznacz cały

#define S2_1 (PINA & 0x02) 
...
  if((S2_1) & (swt2==1)) 
Dla przykładu:

Kod: Zaznacz cały

(PINA & 0x02) 
wyzeruje wszystkie bity poza 0x02 a więc da wynik 0x02 gdy klawisz jest puszczony (pull-up) i 0x00 gdy wciśnięty.

Kod: Zaznacz cały

(swt2==1)
zwróci TRUE czyli dowolną wartość różną od zera (załóżmy 0x01) gdy warunek jest spełniony lub 0x00 gdy w zmiennej swt2 jest coś innego niż 1.
** w praktyce nie ma to większego sensu skoro używasz tylko stanu 1 i 0, wystarczyło by wstawić swt2 lub !swt2 w ten nawias i efekt byłby taki sam **

Kod: Zaznacz cały

  if(((PINA & 0x02)) & (swt2==1)) 
oznacza natomiast bitową operację AND na dwóch bajtach zwracanych przez powyższe fragmenty kodu. 0x02 & 0x01 zawsze będzie oznaczać 0x00 ponieważ żadne bity się nie pokrywają.
Dla porównania logiczna operacja AND czyli 0x02 && 0x01 powinna zwrócić 0x01 (w teorii TRUE czyli dowolną wartość różną od 0x00, ale umówmy się że będzie to klasyczne 0x01).

Jak widzisz, ten warunek IF nigdy nie zostanie spełniony, więc ten fragment kodu nigdy nie zostanie ruszony.




Teraz ostatniego problemu z wyrzuceniem fragment & (swt2==0)) już nie ogarniam.
Zgodnie z moja wiedzą, wciśnięcie S2 powinno zaświecić LEDa 4, brak takiej reakcji oznacza uszkodzenie sprzętu albo poważny błąd kompilatora.
Tak czy inaczej, może rozwiążmy poprzednie dwa tematy i do tego wrócimy później.

Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Post autor: M@ciej » 11 paź 2014, 9:32

Dzięki za wyjaśnienia.

Z tym resetem, to zawsze kładłem 47k do zasilania i 100nF do masy, gdy programowałem w Bascomie. Teraz przesiadka na C i kilka niespodzianek :) Popróbuje, jak pisałeś, wywalę kondziora. Być może, w C procek szybciej startuje.

W ogóle jestem pozytywnie zaskoczony ilością generowanego kodu. W Bascomie programik (akurat nie ten) zajął mi prawie kilobajt miejsca, a tutaj niecałe 200 bajtów (ilość danych wpisywanych do procka).

A z problemem instrukcji warunkowych poradziłem sobie w ten sposób, że potworzyłem zagnieżdżone warunki i nie było kłopotów z operatorami.

Powinienem wiedzieć, że w instrukcjach warunkowych trzeba stosować przyrównanie a nie przypisanie.

Mogę mieć jeszcze kilka pytań w przyszłości, wiec mam nadzieję, że będę mógł pisać w tym wątku i otrzymywać odpowiedzi. Ten C bardzo mnie wciągnął.

Wojtek
Moderator
Posty: 2604
Rejestracja: 04 sie 2002, 19:00
Lokalizacja: --
Kontakt:

Post autor: Wojtek » 11 paź 2014, 9:41

Co do pinu reset i jego podłączenia nie wiem czemu po prostu nie skorzystać z zaleceń Atmel które są w nocie aplikacyjnej AVR042 http://www.google.pl/url?sa=t&rct=j&q=& ... 1500,d.bGQ na stronie 5 gdzie jest dokładnie wyjaśnione kiedy i w jakich sytuacjach stosować rezystor, kondensator i diode. Ludzie tworzą różne teorie na ten temat a wszystko w źródle jest wyjaśnione.

Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Post autor: M@ciej » 11 paź 2014, 9:50

Dzięki za linka.
Teraz się nieco rozjaśniło. Stosowałem zbyt duży rezystor. Używając STK200 powinien być on 10k, nie podali, jaka ma być wartość kodziora, lecz chyba 100nF to za dużo, tak z 10nF powinno wystarczyć, może mniej.

Awatar użytkownika
Artyliusz
Użytkownik
Posty: 306
Rejestracja: 06 sty 2013, 14:10
Lokalizacja: Z Polski
Kontakt:

Post autor: Artyliusz » 11 paź 2014, 11:15

Jeśli pracujesz w środowisku o dużych zakłóceniach to zaleca się stosowanie kondensatora, który chroni przed niekontrolowanym resetem. Tak samo będzie dobrze 100nF lub 10nF. Jednak warto pamiętać, aby w szereg z przyciskiem dać rezystor 330 Ohmów, aby ograniczyć prąd, ponieważ naciskając przycisk de facto rozładowujesz kondensator. To sama mogłeś zresztą przeczytać w dokumencie podanym przez Wojtek.

Druga sprawa to drgania styków. W programie nie widzę nic, co by im zapobiegało. Podejrzewam, że to sprzętowo też nie rozwiązałeś. Przez taką błahostkę może Tobie program nie działać lub działać kiedy chce. Dodaj _delay_ms(25) zaraz po naciśnięciu przycisku. To najprostszy sposób. ;)

Jeszcze powiem, że jeśli chodzi o zmianę stanu diody możesz stosować PORTA ^= 1<<PA4

Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Post autor: M@ciej » 11 paź 2014, 11:46

Co do układu resetu, to mam jasność sytuacji.

Przyciski w programie. Właśnie dlatego tak skonstruowałem procedurę obsługi przycisku, żeby:
1. Wyeliminować drgania styków. Wiem, że przy puszczeniu klawisza styki też drgają. W Bascomie nie było z tym nigdy problemu, ale w przypadku C, możesz mieć rację, gdyż jest szybszy.
2, Żeby się obsługa przycisku wykonała tylko raz. Tutaj akurat tak trzeba, by dłuższe przyciśnięcie nie spowodowało kilkukrotnego zadziałania obsługi przycisku.

Jednak będę miał na uwadze Twoją uwagę, Antyliusz. W programie nie chcę stosować opóźnień, gdyż nierzadko zakłóca to jego działanie. W Bascomie tak miałem, że instrukcja WaitMS stopowała przerwania. Najbardziej było to odczuć gdy procek pracował z 1MHz.

Awatar użytkownika
Artyliusz
Użytkownik
Posty: 306
Rejestracja: 06 sty 2013, 14:10
Lokalizacja: Z Polski
Kontakt:

Post autor: Artyliusz » 11 paź 2014, 14:59

Co do punktu drugiego to przecież wykrywasz naciśnięcie lub puszczenie przycisku. Przeważnie wykrywa się naciśnięcie tak jak Ty, co powoduje na przykład ustawienie portu w stan wysoki. Nie zrobi to drugi raz skoro już stan wysoki jest. Ale być może akurat nie rozumiem idei. :P

Jest taka zasada, że w obsłudze przerwania nie można używać _delay_ i w sumie innych rzeczy opóźniających. Zamiast tego można stosować tzw. flagi.

Wojtek
Moderator
Posty: 2604
Rejestracja: 04 sie 2002, 19:00
Lokalizacja: --
Kontakt:

Post autor: Wojtek » 11 paź 2014, 16:15

Namawiam do stosowania sprzętowego układu eliminującego drgania, maleństwo 4 pinowe, nie trzeba pisać funkcji programowej a działa bez pudła np. taki http://www.maximintegrated.com/en/produ ... X6816.html to też warto przeglądnąć http://www.maximintegrated.com/en/app-n ... mvp/id/287

Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Post autor: M@ciej » 11 paź 2014, 18:17

Wow, nawet nie wiedziałem, że są sprzętowe układy eliminujące drgania styków (debounce). Jednak kondek 100nF równolegle do switcha to najprostsze rozwiązanie (nie zawsze najlepsze).

Co do flag, co mam rozumieć jako 1-bitową zmienną, nie mogłem znaleźć, czy i gdzie się coś takiego stosuje w C. Na intuicję powinno być. Chętnie bym coś takiego zastosował w programie w C, właśnie w wersji Bascomowej były tam flagi.

Awatar użytkownika
joon
Użytkownik
Posty: 2078
Rejestracja: 30 cze 2007, 22:56
Lokalizacja: Kraków, Przemyśl, Warszawa
Kontakt:

Post autor: joon » 12 paź 2014, 0:15

Flagi jedno bitowej w c raczej nie zrobisz, chyba że za pomocą maskowania bitowego jak używasz wiecej niż jednej flagi- zaoszczędzisz ramu ale kod sie powiększy. Moje próby na A13 skończyły sie przepełnieniem stosu :evil:

Wojtek
Moderator
Posty: 2604
Rejestracja: 04 sie 2002, 19:00
Lokalizacja: --
Kontakt:

Post autor: Wojtek » 12 paź 2014, 6:34

M@ciej pisze:Wow, nawet nie wiedziałem, że są sprzętowe układy eliminujące drgania styków (debounce). Jednak kondek 100nF równolegle do switcha to najprostsze rozwiązanie (nie zawsze najlepsze).
Masz o tym napisane w nocie którą wcześniej podlinkowałem, wprawdzie tam jest to opisane na przykładzie pinu reset ale zjawisko jest takie samo, więc dobrze przy stosowaniu kondensatora dołożyć w szereg niewielki rezystor.

Awatar użytkownika
Ertew
Użytkownik
Posty: 1418
Rejestracja: 03 lip 2005, 10:36
Lokalizacja: Leszno
Kontakt:

Post autor: Ertew » 12 paź 2014, 15:12

M@ciej pisze: W ogóle jestem pozytywnie zaskoczony ilością generowanego kodu. W Bascomie programik (akurat nie ten) zajął mi prawie kilobajt miejsca, a tutaj niecałe 200 bajtów (ilość danych wpisywanych do procka).
W temacie ilości kodu możesz się nie raz przejechać na C. Ja pamiętam dwa elementy na które trzeba uważać, ale może ktoś jeszcze coś dorzuci.

- Biblioteka matematyczna i wykorzystanie zmiennych typu float. Jedna taka zmienna i do flesza ładuje się wersja okrojona (około 1kB kodu gratis). Jeśli potrzebujesz funkcji matematycznych typu sinus, potęga czy pierwiastek, dostajesz pełną wersję biblioteki matematycznej (2kB). W ten sposób błędne ustawienie makefile powoduje że do klasycznego '2313 nie zmieścisz testowego programu migającego LEDem.

- Zbędne funkcje (tutaj walczyłem z Eclipse, WinAVR ma inna domyśle ustawienia). O ile skompilowanie wszystkich funkcji z każdej użytej biblioteki (nawet gdy wrzuciłeś w kod kilka bibliotek na zapas) ma sens, to wrzucenie ich wszystkich do flesza stosuje się tylko gdy tworzysz coś na kształt biosu czy systemu operacyjnego (co domyślnie tworzy eclipse). Dopisanie kilku dodatkowych instrukcji do makefile powoduje, że do flesza trafią tylko funkcje z których korzystasz, a biblioteka-kombajn do obsługi 10 różnych wyświetlaczy graficznych zamiast zajmować 10kB, wypluje z siebie tylko kod dla twojego LCD i cały program schudnie nawet o połowę.

M@ciej pisze: A z problemem instrukcji warunkowych poradziłem sobie w ten sposób, że potworzyłem zagnieżdżone warunki i nie było kłopotów z operatorami.
To jest zły nawyk, choć podejście przydaje się przy debugowaniu i operacjach na peryferiach gdzie trzeba zachować kolejność operacji/odczytów.
Jeśli zastosujesz zagnieżdżenie kilku IFów jeden w drugim, kompilator może pominąć zbędne IFy (takie w których wynik zawsze będzie TRUE albo FLASE) ale reszta zostanie wykonana dokładnie krok po kroku bez optymalizacji pomiędzy poszczególnymi IFami. Gdy upchniesz wszystko w jeden większy warunek, kompilator ma większe pole do optymalizacji co zazwyczaj jest elementem pożądanym.

Odwrotnie sprawa miała się w przypadku Bscoma (przynajmniej ~10 lat temu gdy jeszcze z niego korzystałem). Tutaj wyrażenia matematyczne i logiczne powinny być możliwie proste i krótkie, gdyż kompilator nie chciał przyjąć i przetrawić dłuższych. Ale taka jest natura języka Basic.

M@ciej pisze: Powinienem wiedzieć, że w instrukcjach warunkowych trzeba stosować przyrównanie a nie przypisanie.
Znowu nie ogarniam co autor miał na myśli.
W kodzie z pierwszego postu, wszędzie w instrukcjach warunkowych masz użyte porównania, nie przypisania. O co więc chodzi?
Oddzielną kwestią jest fakt, że masz rację. Wpisanie = zamiast == wewnątrz warunku powoduje że program za nic nie chce działać zgodnie z założeniami. No a znalezienie tego błędu wymaga uważnego prześledzenia całego kodu.

Wojtek pisze:
M@ciej pisze:Wow, nawet nie wiedziałem, że są sprzętowe układy eliminujące drgania styków (debounce). Jednak kondek 100nF równolegle do switcha to najprostsze rozwiązanie (nie zawsze najlepsze).
Masz o tym napisane w nocie którą wcześniej podlinkowałem, wprawdzie tam jest to opisane na przykładzie pinu reset ale zjawisko jest takie samo, więc dobrze przy stosowaniu kondensatora dołożyć w szereg niewielki rezystor.
Z kondensatorem się zgadzam. Sam zazwyczaj nie stosuję kondensatorów na płytkach stykowych z powodu lenistwa (wstyd się przyznać, dokładam go dopiero jak coś nie działa), jednak przy rysowaniu schematów czy projektowaniu płytek zawsze daję, najwyżej się nie wlutuje.

Mam natomiast pytanie o zasadność stosowania rezystora.
- Jedni zalecają rezystor by nie wypalać styków. Nota zaleca rezystor by ograniczyć szpilki napięcia które mogą się zaindukować w ścieżkach, lecz dotyczy to głównie pinu reset.
- Odmienna sytuacja (z którą i ja się zetknąłem) to utlenienie styków co prowadzi do pogorszenia styku. Dodanie kondensatora daje impulsy prądu które wypalają zanieczyszczenia, co znacząco przedłuża żywotność przycisków.
Pytanie czy dodanie rezystora 330Ω nie spowoduje osłabienia tego dobroczynnego zjawiska.

Wojtek
Moderator
Posty: 2604
Rejestracja: 04 sie 2002, 19:00
Lokalizacja: --
Kontakt:

Post autor: Wojtek » 12 paź 2014, 17:36

Ertew pisze:Mam natomiast pytanie o zasadność stosowania rezystora.

- Jedni zalecają rezystor by nie wypalać styków. Nota zaleca rezystor by ograniczyć szpilki napięcia które mogą się zaindukować w ścieżkach, lecz dotyczy to głównie pinu reset.
Te szpilki mogą znacznie przekraczać napięcie zasilające i po to jest ten rezystor aby "złagodzić" takie zjawisko, ale ja osobiście preferuję stosowanie układów do tego przeznaczonych ("debuncerów")o czym wyżej napisałem.

Awatar użytkownika
M@ciej
Użytkownik
Posty: 736
Rejestracja: 13 sie 2005, 21:38
Lokalizacja: Szczecin

Post autor: M@ciej » 13 paź 2014, 6:22

Ja ten przycisk RESET wstawiłem właśnie dlatego, że procek nie chciał startować od nowa. W ogóle zrobiłem sobie małą płytkę ewaluacyjną na płytce uniwersalnej do nauki tego C. 4 przyciski (+ 5. RESET), 4 diody i filtr RC na PWM, gdyż moim głównym celem jest napisanie prostego DDSa i porównanie osiągów z wersją Bascomową.

Wziąłem się więc za przerwania, dopisując do kodu ich obsługę:

Kod: Zaznacz cały

#define F_CPU 8000000L
#include <avr/io.h>
#include <avr/interrupt.h>
//#include <util/delay.h>

#define SET_LED1 PORTA |= (1<<PA4) 
#define RESET_LED1 PORTA &= ~(1<<PA4)
#define SET_LED2 PORTA |= (1<<PA5) 
#define RESET_LED2 PORTA &= ~(1<<PA5)
#define SET_LED3 PORTA |= (1<<PA6) 
#define RESET_LED3 PORTA &= ~(1<<PA6)
#define SET_LED4 PORTA |= (1<<PA7) 
#define RESET_LED4 PORTA &= ~(1<<PA7)
#define S1_F0 !(PINA & 0x01)
#define S1_F1 (PINA & 0x01) 
#define S2_F0 !(PINA & 0x02)
#define S2_F1 (PINA & 0x02)
#define S3_F0 !(PINA & 0x04)
#define S3_F1 (PINA & 0x04)
#define S4_F0 !(PINA & 0x08)
#define S4_F1 (PINA & 0x08)

int main(void)
{
 DDRA = 0xF0; //&B11110000 - 4 najmLodsze jako wejscia, 4 najstarsze wyjscia
 DDRB = 0xFF; //&B11111111 - wszystkie jako wyjscia
 PORTA = 0x0F; //&B00001111 - 4 najmlodsze podciagniete, 4 najstarsze wyzerowane
 char swt1=0, swt2=0;
 char func=1;
 TCCR0 |= (1<<CS02) | (0<<CS01) | (1<<CS00);
 TIMSK |= (1<<TOIE0);
 sei(); //Globalne uruchomienie przerwań 
 

 
 while(1)
 {
   
  if((S1_F0) && (swt1==0))
  {
    swt1=1; 
  }
  
  if ((S1_F1) && (swt1==1) && (func>0))
  {
 	 func -=1; 
 	 swt1=0;
  }
 
  if ((S2_F0) && (swt2==0))
  {
    swt2=1;
  }
  
  if ((S2_F1) && (swt2==1) && (func<16))
  {
     func +=1;
	 swt2=0;
  }
  switch(func)
  {
   case 0:
    RESET_LED1;
	RESET_LED2;
	RESET_LED3;
	RESET_LED4;
   break;
   case 1:
    SET_LED1;
	RESET_LED2;
	RESET_LED3;
	RESET_LED4;
   break;
   case 2:
    RESET_LED1;
	SET_LED2;
	RESET_LED3;
	RESET_LED4;
   break;
   case 3:
    SET_LED1;
	SET_LED2;
	RESET_LED3;
	RESET_LED4;
   break;
   case 4:
    RESET_LED1;
	RESET_LED2;
	SET_LED3;
	RESET_LED4;
   break;
   case 5:
    SET_LED1;
	RESET_LED2;
	SET_LED3;
	RESET_LED4;
   break;
   case 6:
    RESET_LED1;
	SET_LED2;
	SET_LED3;
	RESET_LED4;
   break;
   case 7:
    SET_LED1;
	SET_LED2;
	SET_LED3;
	RESET_LED4;
   break;
  
  } 
 }

}
ISR(TIMER0_OVF_vect)   
{  
 //TCNT0 = timer_start;          //Początkowa wartość licznika 

 
}  
lecz tutaj dziwne zjawisko. Zmienna FUNC zadeklarowana od razu z wartością, jest odświeżana po wykonaniu przerwania (tak ja to widzę), i nie mogę przełączać diod LED, bo co nacisnę guzik, to na chwile zaświeca się następna dioda, by już za chwilę zaświecić się ta pierwsza (druga gaśnie). Tego zjawiska nie było przed wpisaniem obsługi przerwań. Chyba czegoś tu nie rozumiem.

ODPOWIEDZ