Skocz do zawartości

[Kurs] Kurs programowania ARM cz.10 - RS-232 c.d.


Elvis

Pomocna odpowiedź

Poprzednia lekcja

RS-232, c.d. - biblioteka standardowa

W przypadku Bascom-a używanie RS-232 jest bajecznie proste. Polecenie „print” wysyła tekst, właściwie nic nie trzeba robić.

W tej części spróbujemy uzyskać podobny efekt w języku C.

Funkcja sprintf

Najpierw przyjrzymy się funkcji „sprintf”. Należy ona do biblioteki standardowej C, więc jest dostępna na właściwie każdej platformie i kompilatorze.

Aby ją wykorzystać wystarczy zaimportować plik stdio.h:

#include <stdio.h>

Funkcja sprintf jest bardzo podobna do znanej funkcji printf. Każdy kurs C zaczyna się wyświetleniem „Hello World” za pomocą printf. Sprintf jako pierwszy parametr przyjmuje bufor (tablicę znaków) i zamiast na ekran, wypisuje wynik do bufora.

Obejrzyjmy kod:

	char buf[32];

sprintf(buf, "Hello world!\n");

Program nie wypisze nic na ekran. W jego wyniku zmieni się zawartość tablicy buf, ale nic nie będzie widać. Po co więc nam funkcja sprintf? Za jej pomocą możemy właściwie dowolnie formatować napisy, wykorzystywać formatowania w rodzaju %d, %f, \n itd. Następnie bufor możemy za pomocą naszej funkcji uart_send_string() wysłać przez RS-232.

Zmieńmy nieco poprzedni program. Tym razem, gdy otrzymamy nieznane polecenie (znak), wypiszemy kod tego znaku. Fragment:

			default:
			uart1_send("Nieznane polecenie\r\n");
			break;				

Zastąpimy następującym:

			default:
			sprintf(buf, "#%d - Nieznane polecenie\r\n", c);
			uart1_send(buf);
			break;				

Kompilujemy program i... czeka nas przykra niespodzianka.

Dostajemy całe mnóstwo błędów:

c:/winarm/bin/../lib/gcc/arm-elf/4.1.1/../../../../arm-elf/lib\libc.a(freer.o): In function `_malloc_trim_r':
mallocr.c:(.text+0x48): undefined reference to `_sbrk_r'
mallocr.c:(.text+0x64): undefined reference to `_sbrk_r'
mallocr.c:(.text+0x84): undefined reference to `_sbrk_r'
c:/winarm/bin/../lib/gcc/arm-elf/4.1.1/../../../../arm-elf/lib\libc.a(makebuf.o): In function `__smakebuf':
makebuf.c:(.text+0x3c): undefined reference to `_fstat_r'
makebuf.c:(.text+0x110): undefined reference to `isatty'
c:/winarm/bin/../lib/gcc/arm-elf/4.1.1/../../../../arm-elf/lib\libc.a(mallocr.o): In function `_malloc_r':
mallocr.c:(.text+0x424): undefined reference to `_sbrk_r'
mallocr.c:(.text+0x4cc): undefined reference to `_sbrk_r'
c:/winarm/bin/../lib/gcc/arm-elf/4.1.1/../../../../arm-elf/lib\libc.a(stdio.o): In function

Okazuje się, że musimy sami zdefiniować brakujące funkcje. Najprościej będzie skopiować gotowy plik z funkcjami z przykładu w katalogu: C:\WinARM\examples\lpc2129_adc_stdio\syscalls.c

Kopiujemy plik do naszego projektu, dodajemy go w oknie edytora. Niestety błędy podczas kompilacji pozostają bez zmian.

Musimy dodać nowy plik do kompilacji, czyli poleceń w pliku Makefile.

Odnajdujemy fragment:

# List C source files here. (C dependencies are automatically generated.)
# use file-extension c for "c-only"-files
SRC = $(TARGET).c

I do zmiennej SRC dodajmy nowy plik:

SRC = $(TARGET).c syscalls.c

Jest to bardzo ważny moment. Od tej chwili nasz program będzie się składał z dwóch plików źródłowych. Część kodu jest w program19.c, część w syscalls.c. Jest to bardzo częsty i użyteczny mechanizm.

Nie musimy trzymać wszystkich funkcji w jednym, z czasem bardzo dużym pliku. Zamiast tego dzielimy projekt na mniejsze części, funkcje umieszczamy w różnych plikach.

Bardzo ułatwia to orientowanie się w projekcie oraz pozwala na wykorzystywanie plików z innych projektów w nowych programach.

Niestety problemy nadal występują.

syscalls.c:15:18: error: uart.h: No such file or directory
syscalls.c: In function '_read_r':
syscalls.c:31: warning: implicit declaration of function 'uart0GetchW'
syscalls.c:31: warning: nested extern declaration of 'uart0GetchW'
syscalls.c:37: warning: implicit declaration of function 'uart0Putch'
syscalls.c:37: warning: nested extern declaration of 'uart0Putch'

Na początek po prostu pozbędziemy się problemów - zakomentujemy wszystkie linie, które sprawiają problemy.

W języku C wystarczy na początku linii napisać // aby kompilator resztę traktował jako komentarz. Dzięki temu linie te zostaną pominięte podczas kompilacji, ale gdy będziemy chcieli do nich wrócić, wystarczy usunąć napis //.

Kompilujemy program i wgrywamy do procesora. Poniżej wynik działania:

W archiwum program19.zip znajdziemy kompletny kod przykładu.

Wgrywanie zajmuje teraz dużo więcej czasu niż w poprzedniej wersji. Niestety dołączenie funkcji sprintf znacznie zwiększyło objętość programu. Zajmuje teraz aż 45KB! Czyli o ponad 40KB więcej niż poprzednio...

Moduł obsługi UART-a

Zanim przejdziemy dalej warto nieco uporządkować program. W pliku głównym poza samym programem napisaliśmy kod odpowiedzialny za komunikację przez UART1.

Kod ten na pewno przyda się w kolejnych programach, jest dość długi i utrudnia analizę programu głównego.

Przygotujemy własny moduł, czyli utworzymy dwa pliki: uart.h, uart.c

Do pliku uart.c przeniesiemy funkcje związane z komunikacją, czyli: uart1_init, uart1_recv, uart1_send_byte, uart1_send.

Plik nagłówkowy jest potrzebny kompilatorowi, aby w programie głównym rozpoznawane były nazwy naszych funkcji.

Wpisujemy do niego nagłówki funkcji poprzedzone słowem kluczowym extern, które informuje kompilator, że treść funkcji zdefiniowana jest gdzie indziej (w pliku uart.c).

Następnie w programie głównym dodajemy instrukcję włączającą nasz nowy moduł:

#include "uart.h"

Musimy jeszcze w pliku Makefile dodać informację o nowym pliku z programem: uart.c. Wykonujemy to podobnie jak poprzednio dla pliku syscalls.c.

Kompilujemy nowy projekt i sprawdzamy działanie.

Wielkość kodu, ani działanie programu nie ulega zmianie.

Natomiast program jest nieco "czytelniejszy", a nasz nowy moduł uart będziemy mogli wykorzystywać w kolejnych programach.

Archiwum Program20.zip zawiera omawiany przykład.

Funkcja printf i scanf

Teraz możemy przystąpić do prawdziwego ułatwienia pracy. W standardowych programach pisanych na PC do komunikacji z użytkownikiem można wykorzystać standardowe funkcje printf oraz scanf. Ich użycie jest bardzo proste i każdy kurs C szczegółowo je omawia.

Nauczymy się wykorzystywać je na mikrokontrolerze.

Otwieramy plik syscalls.c i sprawdzamy, które linie musieliśmy zakomentować, aby program się kompilował.

Na początek:

#include "uart.h"

Tutaj jest łatwo - wystarczy usunąć komentarz, mamy moduł uart.h, więc wszystko powinno być dobrze.

Następnie zobaczmy zakomentowane odwołania do funkcji uart0Putch. Jak można się domyślić, ta funkcja ma wysyłać znaki. Nasza wersja nazywa się uart1_send_byte.

Wystarczy więc zmienić nazwę wywoływanej funkcji i oczywiście usunąć komentarze.

Zobaczmy jeszcze funkcję uart0GetchW. Odpowiada ona za odczyt znaku. Nasza wersja nazywa się uart1_recv i niestety działa nieco inaczej - zamiast czekać na znak, zwraca 0 jeśli nic nie zostało odebrane.

Musimy więc dodać pętlę, w której będziemy czekać, aż coś odbierzemy.

        do {
           c = uart1_recv();
       } while (c==0);

Czas teraz wrócić do programu głównego i wykorzystać nowo przygotowane funkcje.

Nowy program ma na celu jedynie prezentację, jak łatwo teraz programować komunikację przez RS-232:

	while (1) {
	printf("Podaj prędkość robota: ");
	scanf("%d", &speed);
	printf("\n");

	if (speed>150) speed = 0;
	if (speed<0) printf("Za mala wartosc\n");
	else if (speed>100) printf("Za duza wartosc\n");
	else printf("Predkosc wynosi %d%%\n", speed);		
}

Okazało się, że kod w pliku syscalls.c wymagał nieco poprawek - wersja z przykładu nie działała w 100% poprawnie.

W załączniku znajdziemy plik program21.zip z poprawioną i przetestowaną wersją.

Wnioski

Wykorzystanie funkcji printf do komunikacji jest bardzo wygodnym rozwiązaniem. Niestety w przypadku kompilatora gcc oraz biblioteki standardowej, cena jaką trzeba zapłacić za wygodę jest wysoka. Dla dużych programów 40kB nie stanowi problemu, natomiast jeśli nasz program jest mały, lepiej poszukać innego rozwiązania.

Program21.zip

Program20.zip

Program19.zip

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

To znowu ja 🙂 Mam problem - nie wychodzi mi ustawianie stanu na P0. Robię wszystko analogicznei do P1. Na P0 już uruchomiłem SPI, UART, PWM i ADC, ale zwykłą funkcja GPIO nie działa :/ Zamieszczam kod (przy okazji może się komuś SPI i ADC przyda).

#include "lpc2114.h"

#define _BV(x) 	(1<<(x))
#define LED_0        _BV(16) 
#define LED_1        _BV(17) 
#define LED_2        _BV(18) 
#define LED_3        _BV(19) 
#define LED_4        _BV(20) 
#define LED_5        _BV(21) 
#define LED_6        _BV(22) 
#define LED_7        _BV(23)
#define LED_MASK (LED_0|LED_1|LED_2|LED_3|LED_4|LED_5|LED_6|LED_7)
#define DELAY		5000

volatile unsigned int pwm_led[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
volatile unsigned int t0_counter = 0;
const unsigned int LEDS_PATTERN[8] =         { 
           LED_0, LED_1, LED_2, LED_3, LED_4, LED_5, LED_6, LED_7         };

int leds_index = 0;
unsigned char led_state = 0;
unsigned char spi0_status = 0;

void timer0(void) __attribute__ ((interrupt ("IRQ"))); 
void timer0(void) 
{ 
   if (T0_IR&0x01) { 
       if (t0_counter) t0_counter--; 
   } 
 T0_IR = 0xff;//0x01; 
 VICVectAddr = 0; 
}	


void timer0_init(void) 
{ 
//konfiguracja wektora przerwań
 VICIntSelect &= ~0x10; 
 VICIntEnable = 0x10; 
 VICVectCntl0 = 0x24; 
 VICVectAddr0 = (unsigned long)timer0; 

T0_TCR = 2; //reset timera
T0_PR = 0; 	//preskaler względem zegara PCLK
 T0_MR0 = 1500;
T0_MCR = 3; //po przepełnieniu przerwanie+liczy od nowa
T0_TCR = 1;

//zezwolenie na przerwania
   asm volatile ( 
       "STMDB    SP!, {R0}        \n" 
       "MRS    R0, CPSR        \n"    
       "BIC    R0, R0, #0xC0    \n"    
       "MSR    CPSR, R0        \n"    
       "LDMIA    SP!, {R0}" );            
}

void pll_init()
{
 MAM_MAMTIM = 3;
 MAM_MAMCR = 2;

 SCB_PLLCFG = 0x24;
 SCB_PLLCON = 0x1;
 SCB_PLLFEED = 0xAA;
 SCB_PLLFEED = 0x55;
 while (!(SCB_PLLSTAT&0x0400));
 SCB_PLLCON = 0x3;
 SCB_PLLFEED = 0xAA;
 SCB_PLLFEED = 0x55;
}

void delay_ms(unsigned int ms) 
{ 
   t0_counter = ms*10; 
   while (t0_counter); 
}

int main(void)
{
int i;
unsigned int pomiar_ADC=0;

GPIO1_IODIR |= LED_MASK; 
GPIO0_IODIR = _BV(2)|_BV(30);
PCB_PINSEL0 = 0x0000;
pll_init();
timer0_init();

AD_ADCR = _BV(0)|(3*256)|_BV(16)|_BV(21)|_BV(24);//włączenie ADC, burst mode, preskaler 3+1, kanał 0

//spi
SPI0_SPCR = _BV(5);	//master
SPI0_SPCCR = 32;	//preskaler
PCB_PINSEL0 &= ~(_BV(9)|_BV(11)|_BV(13)|_BV(15));
PCB_PINSEL0 |= _BV(8)|_BV(10)|_BV(12)|_BV(14);

while (1) {
	GPIO0_IOSET = 0xffff;//_BV(2)|_BV(30);
	//delay_ms(500);
	if (AD_ADDR&_BV(31))	//sprawdzenie końca konwersji
	{
	pomiar_ADC = ( (AD_ADDR>>6)&0x03FF );
	SPI0_SPDR = (pomiar_ADC>>2)&0xff;	//start transmisji
	while((SPI0_SPSR&_BV(7))==0);
	spi0_status = SPI0_SPSR;
	pomiar_ADC = SPI0_SPDR*4;
	GPIO1_IOCLR = LED_MASK;
	GPIO1_IOSET = (pomiar_ADC<<14);
	}	
}
}
Link do komentarza
Share on other sites

Dziwne, że nie działa kod:

GPIO0_IOSET =_BV(2)|_BV(30);

Natomiast aktualna wersja:

GPIO0_IOSET = 0xffff;//_BV(2)|_BV(30);

Nie ma prawa działać na P0.30 - powinno być:

GPIO0_IOSET = 0xffffffff;//_BV(2)|_BV(30);

[ Dodano: 16 Paź 10 09:21 ]

Już wiem, czemu nie działa P0.2

W dokumentacji jest mała zmyłka, jak odsyłacz [1]. A w nim:

[1] All functions on this pin are open-drain outputs for I2C-bus compliance.

Więc wyjście P0.2 wymaga pull-upa. Inaczej nie będzie działać.

Link do komentarza
Share on other sites

Ja chyba jestem jakiś pechowy 😉 sprawdziłem pozostałe i chyba jest ok - nie działają tylko od I2C oraz od ADC (czyli m.in. p0.2 i P0.30). Jestem dość mocno zaskoczony, że nie można tych nóżek wykorzystać jako normalnych portów wyjściowych, nawet jeśli są ustawione jako GPIO - trochę kiepsko z tym "general purpose", trochę lipa :/ Będę musiał sprawdzić czy z podciągnięciem są w stanie chociaż "0" podać. Dzięki za nakierunkowanie w czym szukać problemu.

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

Jak chodzi o P0.2 to niestety trzeba dokładnie czytać dokumentację - wyjścia I2C są open-colector. Też się kiedyś na to naciełem. Ale z pullupem działają poprawnie.

Natomiast P0.30 działa poprawnie.

Tylko jest jeszcze jedna sprawa. Na stronie 106 dokumentacji (UM10114.pdf) podane są domyślne ustawienia portów.

I jak na złość P0.30 domyślnie jest jako ADC (AIN3 dokłądniej).

Czyli trzeba dodać:

PINSEL1 &= ~(_BV(28)|_BV(29))

albo:

PINSEL1 = 0
Link do komentarza
Share on other sites

O, dzięki. Czyli tak naprawdę trzeba uważać jedynie z I2C, oraz SSEL od SPI - z tego co zauważyłem musi mieć zewnętrzne podciągnięcie do Vcc jeśli chcemy aby procek działał jako master. Mocno mi to utrudni korzystanie w konstrukcji, którą właśnie buduję na LPC2103 (sterują ukłądem w którym jest podciągnięcie do GND przez rezystor 2k2), ale jest do przeżycia. Te podciągnięcia jak duże powinny być? Stosowne do prądu wyjściowego? Na jednej nóżce od I2C mam diodę LED :/

Link do komentarza
Share on other sites

Te procesorki mają niestety trochę niespodzianek. Trzeba jeszcze uważać na P0.14 i czytać erraty.

Jak chodzi o podciąg, to nie polecam dawać małego rezystora. Wtedy przy wyłączonej diodzie będzie płynąć całkiem spory prąd.

Może lepiej odwrócić logikę - diodą podłączyć katodą z procesorem, anodą (przez rezystor) z zasilaniem. Wtedy zerem będzie można zapalać diodę. Podciąg może być całkiem spory - np. 47k.

Najlepiej byłoby dać tranzystor, który sterowałby diodą - przy najmniej procesor nie byłby obciążany prądem diody.

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.