-
1. Data: 2018-09-12 11:05:52
Temat: Biblioteka standardowa time.h i mikrokontrolery
Od: Atlantis <m...@w...pl>
Może ktoś z was będzie miał jakiś pomysł, bo od paru dni nie mogę dojść
do tego, co robię nie tak. Sytuacja wygląda następująco.
Środowisko: Płytka NUCLEO-L031K6, programowana za pomocą IDE
AtollicSTUDIO. Projekt generowany narzędziem STM32CubeMX.
Software: Biblioteka do obsługi DCF77, przeportowana z Arduino. Do tego
biblioteka sterująca wyświetlaczem od Nokii, przeportowana z AVR.
Obydwie zdają się działać prawidłowo.
DCF odczytuje w przerwaniu impulsy z modułu i sprawdza poprawność ramek.
Kontrolowane jest m.in. to, czy odebrany czas nie różni się za bardzo od
systemowego zegara. Jeśli mamy do czynienia z taką sytuacją, konieczne
jest odebranie dwóch prawidłowych ramek, jedna po drugiej. Oryginał
korzystał w tym celu z jakiejś arduinowej biblioteki, ja przerobiłem to
na standardowe time.h, podkładając wszędzie gdzie trzeba wywołania
time(NULL).
Oczywiście zdefiniowałem w kodzie swoją własną funkcję time(), która
czyta systemowy RTC, wypełnia pobranymi wartościami strukturę struct tm,
a następnie przy pomocy mktime() generuje timestamt time_t.
Sam moduł RTC jest obsługiwany za pomocą bibliotek HAL. Po odebraniu
nowej ramki zegar jest ustawiany wartościami uzyskanymi za pośrednictwem
gmtime().
I teraz przechodząc do sedna sprawy: na początku wszystko zdaje się
działać prawidłowo. Po starcie układu zegar zaczyna odliczać w górę, a
jego aktualną wartość wyświetla się na LCD. Jeśli pojawią się
odpowiednie warunki propagacyjne, DCF zaczyna dobierać ramki i
przestawia zegar. Dodatkowo na LCD wyrzucam też info o czasie ostatniej
poprawnej synchronizacji. Potrafi to działać prawidłowo przez kilka
godzin, aż w końcu coś się wysypuje. Na przykład wczoraj około 20.00 UTC
zegar stwierdził, że jest 4.00 UTC następnego dnia. Żeby było ciekawiej,
kolejne ramki DCF były nadal odbierane, a na ekranie pojawiała się
informacja o udanych synchronizacjach, oznaczonych prawidłowym czasem (!).
Kilka rzeczy nie daje mi spokoju:
- Dokładność tej anomalii - obserwowałem ją kilka razy i OIDP zawsze
było to osiem godzin do przodu. Nie chodzi więc o odebranie jakimś cudem
błędnej ramki.
- Przygotowana przeze mnie funkcje time() najwyraźniej zwraca cały czas
prawidłowego timestampa, bo inaczej kolejne synchronizacje nie
dochodziłyby do skutku. Program stwierdziłby rozjechanie się RTC z
odbieranym czasem, czekając na dwie poprawne ramki. Wtedy ustawiłby
zegar i wszystko wróciłoby do normy. Tak się jednak nie dzieje. Po
pojawieniu się anomalia pozostaje na stałe.
W chwili obecnej do pobierania czasu z RTC używam kombinacji time() i
gmtime(), a uzyskane wartości ze struktury wyrzucam na ekran. Po udanej
synchronizacji odebrany czas z DCF jest zapisywany do zmiennej i również
trafia na ekran za pośrednictwem gmtime().
Ktoś ma jakiś pomysł, co mogę robić nie tak? Może time.h w przypadku
mikrokontrolerów wymaga jakiegoś przygotowania (poza podstawieniem
własnej funkcji time())? W jaki sposób chociażby definiuje się w niej
strefę czasową. Pod Linuksem ustawiało się zmienna środowiskową. A na
małym mikrokontrolerze?
-
2. Data: 2018-09-12 11:49:22
Temat: Re: Biblioteka standardowa time.h i mikrokontrolery
Od: "Grzegorz Niemirowski" <g...@p...onet.pl>
Atlantis <m...@w...pl> napisał(a):
> - Przygotowana przeze mnie funkcje time() najwyraźniej zwraca cały czas
> prawidłowego timestampa, bo inaczej kolejne synchronizacje nie
> dochodziłyby do skutku. Program stwierdziłby rozjechanie się RTC z
> odbieranym czasem, czekając na dwie poprawne ramki. Wtedy ustawiłby
> zegar i wszystko wróciłoby do normy. Tak się jednak nie dzieje. Po
> pojawieniu się anomalia pozostaje na stałe.
> W chwili obecnej do pobierania czasu z RTC używam kombinacji time() i
> gmtime(), a uzyskane wartości ze struktury wyrzucam na ekran. Po udanej
> synchronizacji odebrany czas z DCF jest zapisywany do zmiennej i również
> trafia na ekran za pośrednictwem gmtime().
> Ktoś ma jakiś pomysł, co mogę robić nie tak? Może time.h w przypadku
> mikrokontrolerów wymaga jakiegoś przygotowania (poza podstawieniem
> własnej funkcji time())? W jaki sposób chociażby definiuje się w niej
> strefę czasową. Pod Linuksem ustawiało się zmienna środowiskową. A na
> małym mikrokontrolerze?
Dlaczego pisałeś własną funkcję time()? Ta funkcja już jest gotowa, a Twoim
zadaniem jest napisanie funkcji _gettimeofday(). time() z biblioteki newlib
woła _gettimeofday_r() a ta z kolei _gettimeofday(), którą trzeba
dostarczyć.
Czy monitorowałeś zawartość RTC, np. wypisując zawartość na porcie
szeregowym albo przez semihosting?
Strefę czasową ustawia się tak samo:
setenv("TZ","CET-1CEST,M3.5.0/2,M10.5.0/3",1);
--
Grzegorz Niemirowski
https://www.grzegorz.net/
-
3. Data: 2018-09-12 14:54:19
Temat: Re: Biblioteka standardowa time.h i mikrokontrolery
Od: Atlantis <m...@w...pl>
On 12.09.2018 11:49, Grzegorz Niemirowski wrote:
> Dlaczego pisałeś własną funkcję time()?
Hmm... Tak zawsze robiłem na PIC32. Tam próba odwołania się do time()
bez zdefiniowania własnej wersji tej funkcji powodowała wyświetlenie
warninga:
"Linking code with default time() stub. Hint: Write an app-specific
implementation."
> Ta funkcja już jest gotowa, a Twoim zadaniem jest napisanie funkcji
> _gettimeofday(). time() z biblioteki newlib woła _gettimeofday_r() a
> ta z kolei _gettimeofday(), którą trzeba dostarczyć. Czy
To rozwiązanie charakterystyczne dla STM32, czy stanowi ogólnie przyjęty
standard i mogę się spodziewać, że tak samo będzie się to robiło także w
przypadku innych rodzin MCU, a PIC32 jest tutaj jakimś wyjątkiem?
W ogóle istnieje gdzieś jakiś zasób, który tłumaczyłby w jaki sposób
spiąć niskopoziomowo standardową bibliotekę C z własnym sprzętem?
-
4. Data: 2018-09-12 16:47:51
Temat: Re: Biblioteka standardowa time.h i mikrokontrolery
Od: "Grzegorz Niemirowski" <g...@p...onet.pl>
Atlantis <m...@w...pl> napisał(a):
> To rozwiązanie charakterystyczne dla STM32, czy stanowi ogólnie przyjęty
> standard i mogę się spodziewać, że tak samo będzie się to robiło także w
> przypadku innych rodzin MCU, a PIC32 jest tutaj jakimś wyjątkiem?
ARM, nie STM32, dla ścisłości. Kompilator (gcc-arm-none-eabi) nawet nie wie,
że jest takie coś jak STM32.
Nie wiem jak jest na innych architekturach, z 32-bitowych używam tylko
STM32. Ale w każdym razie z kompilatorem (jako programem) dostarczana jest
też biblioteka języka C. Popularną biblioteką C dla mikrokontrolerów ARM
jest newlib. I ona wymaga własnie _gettimeofday(), inaczej będzie błąd
linkowania. Widocznie w bibliotece rozprowadzanej z kompilatorem dla PIC32
to jest uproszczone, nie ma dodatkowych warstw w postaci _gettimeofday_r() i
_gettimeofday() i trzeba od razu napisać time().
> W ogóle istnieje gdzieś jakiś zasób, który tłumaczyłby w jaki sposób
> spiąć niskopoziomowo standardową bibliotekę C z własnym sprzętem?
Ogólnie jest tak, że w przypadku standardowych funkcji C komunikujących się
z tym, co jest na zewnątrz aplikacji (czas, pliki, standardowe we/wy,
dynamiczna alokacja pamięci), funkcje te wywołują funkcje systemu
operacyjnego (system calls - syscalls). Ponieważ na mikrokontrolerze nie ma
systemu operacyjnego, trzeba właśnie te funkcje systemowe napisać samemu.
Dlatego jeśli chodzi o ARM GCC, możesz napotkać na posty ludzi, którym
linker wywala brak funkcji _write(), _read(), czy _sbrk(). Więc odpowiadając
na Twoje pytanie, to nie znam takiego jednego zasobu, ale googlałbym po
słowach kluczowych newlib syscalls arm. Można znaleźć np.
https://balau82.wordpress.com/2010/12/16/using-newli
b-in-arm-bare-metal-programs/
Przykładowy plik syscalli z minimalnymi funkcjami:
https://github.com/ROBOTIS-GIT/OpenCR/blob/master/ar
duino/opencr_develop/opencr_bootloader/common/bsp/op
encr/cfg/syscalls.c
Dzięki temu, że piszesz własną implementację syscalli możesz np.
przekierować printf() (bo on woła pod spodem _write()) na port szeregowy
albo konsolę semihostingu:
int _write(int file, char *ptr, int len)
{
if (CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk) {
for (int DataIdx = 0; DataIdx < len; DataIdx++)
{
SH_SendChar(*ptr++);
}
}
return len;
}
--
Grzegorz Niemirowski
https://www.grzegorz.net/
-
5. Data: 2018-09-12 20:58:18
Temat: Re: Biblioteka standardowa time.h i mikrokontrolery
Od: Atlantis <m...@w...pl>
On 12.09.2018 16:47, Grzegorz Niemirowski wrote:
> ARM, nie STM32, dla ścisłości. Kompilator (gcc-arm-none-eabi) nawet nie
> wie, że jest takie coś jak STM32.
Czyli rozumiem, że inne ARM-y programuje się w podobny sposób,
korzystając z tego samego kompilatora? Na przykład takie AT91SAM7* będą
miały to rozwiązane w ten sam sposób, a różnice będą wynikały głównie z
nieco innego zestawu peryferiów (np. brak SysTick) oraz innych bibliotek
do ich obsługi?
Tak z ciekawości. Jak to wyglądało w przypadku AVR-ów? W czasach, gdy
uczyłem się tych mikrokontrolerów nigdy nie musiałem podpinać
niskopoziomowych funkcji do obsługi biblioteki standardowej. Zamiast
printf używałem zestawu snprintf + uart_puts, a operacje związane z
czasem załatwiałem za pomocą zestawu własnych funkcji. Jestem jednak
ciekaw na ile dałoby się to zrobić w teorii. Zwłaszcza, że przecież
niektóre z wyższych modeli Atmegi i AtXmegi miały już dość sporo pamięci...
-
6. Data: 2018-09-12 21:53:59
Temat: Re: Biblioteka standardowa time.h i mikrokontrolery
Od: Marek <f...@f...com>
On Wed, 12 Sep 2018 14:54:19 +0200, Atlantis <m...@w...pl>
wrote:
> przypadku innych rodzin MCU, a PIC32 jest tutaj jakimś wyjątkiem?
Nie wiem czy wyjątkiem, libc w pic32 jest niekompletne i zawiera
błędy (przynajmniej w tym co było dostępne przed Harmony). Nie da
się zaplementowac poprawnie stdio mimo, że jest przygotowane
nagłówkowo by to zaimplementować przez własne prymitywy _fopen()
_write(), _read() (mimo wlasnej implementacji powyższych fopen() nie
działalo prawidłowo, nie pamiętam w czym był problem). Nie zawsze
działa prawidłowo sscanf(). To tak w skrócie.
--
Marek
-
7. Data: 2018-09-13 00:07:56
Temat: Re: Biblioteka standardowa time.h i mikrokontrolery
Od: "Grzegorz Niemirowski" <g...@p...onet.pl>
Atlantis <m...@w...pl> napisał(a):
> Czyli rozumiem, że inne ARM-y programuje się w podobny sposób,
> korzystając z tego samego kompilatora? Na przykład takie AT91SAM7* będą
> miały to rozwiązane w ten sam sposób, a różnice będą wynikały głównie z
> nieco innego zestawu peryferiów (np. brak SysTick) oraz innych bibliotek
> do ich obsługi?
Tak, zgadza się. W obrębie danego kompilatora wszystko będzie odbywało się
bardzo podobnie. Standardowe biblioteki C nie zajmują się peryferiami, nie
są aż tak niskopoziomowe i daje to pewną przenośność. Z resztą na
Windowsie/Linuksie Twój program też nie zajmuje się obsługą karty graficznej
czy pakietami TCP/IP. Od tego jest system operacyjny i sterowniki.
Kompilator interesuje przede wszystkim rdzeń mikrokontrolera, lista rozkazów
(np. Thumb2) czy dostępność FPU. Do tego opcje linkera, jak adresacja
RAM/Flash oraz dołączanie bibliotek. Nie ma znaczenia SysTick czy RTC.
Możesz zerknąć na pliki Makefile dla różnych ARMowych projektów i sprawdzić
jakie parametry są przekazywane do kompilatora.
> Tak z ciekawości. Jak to wyglądało w przypadku AVR-ów? W czasach, gdy
> uczyłem się tych mikrokontrolerów nigdy nie musiałem podpinać
> niskopoziomowych funkcji do obsługi biblioteki standardowej. Zamiast
> printf używałem zestawu snprintf + uart_puts, a operacje związane z
> czasem załatwiałem za pomocą zestawu własnych funkcji. Jestem jednak
> ciekaw na ile dałoby się to zrobić w teorii. Zwłaszcza, że przecież
> niektóre z wyższych modeli Atmegi i AtXmegi miały już dość sporo
> pamięci...
W AVR GCC jest taka funkcja:
FILE* fdevopen(int(*)(char, FILE *)put, int(*)(FILE *)get);
Służy ona do definiowania strumienia we/wy. Podajesz jej jako parametry
wskaźniki na funkcje do wysyłania i odbierania znaku. Parametry mogą być
NULami. I jest taki myk, że pierwsze wywołanie fdevopen(), które ma parametr
put niebędący NULem, powoduje zdefiniowanie strumienia stdout (standardowego
wyjścia). Jeśli więc masz funkcję uart_putc(), czyli dla pojedynczego znaku,
to możesz napisać:
int put(char c, FILE * file) {
uart_putc(c);
return 0;
}
I wywołać fdevopen():
fdevopen(&put, NULL);
Od tego momentu możesz używać printf() a wyniki jego działania będą szły na
port szeregowy. Albo np. na LCD jeśli zamiast uart_putc() użyjesz funkcji do
wyświetlania literki na wyświetlaczu. Możesz też zdefiniować drugi strumień
i odwoływać się do niego za pomocą fprintf() jeśli chcesz wysyłać teksty
metodą printfową zarówno na LCD jak i UART.
To wszystko jest opisane w dokumentacji AVR GCC, a dokładniej AVR libc
https://www.nongnu.org/avr-libc/user-manual/group__a
vr__stdio.html
Co do czasu, to AVR libc ma wewnętrzny licznik, który trzeba inkrementować
wywołując co sekundę funkcję system_tick(). A najpierw oczywiście
zainicjalizować wołając set_system_time(). Wtedy funkcja time() będzie mogła
zwrócić właściwy czas. Opis jest na stronie
https://www.nongnu.org/avr-libc/user-manual/group__a
vr__time.html Jak widać
obsługa czasu jest okrojona. Nie można np. ustawić strefy czasowej oraz
czasu letniego za pomocą zmiennej środowiskowej. Zamiast tego ustawia się
strefę funkcją set_zone() a czas letni jest załatwiany poprzez wskazanie
funkcją set_dst() funkcji, która obliczy przesunięcie czasu letniego na
podstawie podanego timestampa (przykładowo dzisiaj zwróci 3600 a w grudniu
0). Czyli jeśli wywołamy funkcję localtime(), to zostanie wzięty czas z
funkcji time(), dodane przesunięcie wynikające ze strefy czasowej (podane
jako parametr set_zone()) oraz dodane przesunięcie z funkcji wskazanej przez
set_dst(). A skąd wziąć funkcję do czasu letniego? W bibliotece
util/eu_dst.h jest taka funkcja dla Unii Europejskiej. Ale nie działa
poprawnie :) Bug+fix: https://savannah.nongnu.org/bugs/?44327
Czyli podsumowując, w AVR GCC zamiast syscalli wymyślili takie obejścia.
--
Grzegorz Niemirowski
https://www.grzegorz.net/
-
8. Data: 2018-09-13 07:46:46
Temat: Re: Biblioteka standardowa time.h i mikrokontrolery
Od: Atlantis <m...@w...pl>
Hmm... Wygląda na to, że problem leży głębiej i dotyczy raczej RTC
(ewentualnie funkcji bibliotecznych odpowiedzialnych za odczytywanie
czasy), niż biblioteki standardowej time.h.
Zgodnie z sugestiami, które tu padły, zastąpiłem usunąłem swoją własną
wersje funkcji time() i napisałem własną wersję _gettimeofday.
int _gettimeofday (struct timeval* tp, struct timezone* tzp) {
RTC_TimeTypeDef timeStruct;
RTC_DateTypeDef dateStruct;
struct tm dstTime;
HAL_RTC_GetTime(&hrtc, &timeStruct, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&hrtc, &dateStruct, RTC_FORMAT_BIN);
dstTime.tm_hour = timeStruct.Hours;
dstTime.tm_min = timeStruct.Minutes;
dstTime.tm_sec = timeStruct.Seconds;
dstTime.tm_year = dateStruct.Year + 100;
dstTime.tm_mon = dateStruct.Month - 1;
dstTime.tm_mday = dateStruct.Date;
if (tp) {
tp->tv_sec = mktime(&dsttime);
tp->tv_usec = 0;
}
if (tzp) {
tzp->tz_minuteswest = 0;
tzp->tz_dsttime = 0;
}
return 0;
}
Następnie w pętli głównej usunąłem gmtime(), zamiast tego wyświetlając
na LCD aktualna wartość zwracaną przez time(). Efekt był dość...
Dziwny... Mianowicie liczba złożona z dwóch ostatnich cyfr faktycznie
zwiększała swoją wartość o jeden co sekundę. Natomiast trzecia, czwarta
i piata cyfra od końca co chwilę zmieniała swoją wartść "tam i z
powrotem" - raz było 500 z czymś, potem ponad 600, potem znów 500 z
czymś i tak dalej...
Postanowiłem więc zrobić eksperyment i stworzyłem zmienną uint32_t _rtc,
która była zwiększana o 1 w przerwaniu alarmu RTC. Podpiąłem ją do
funkcji _gettimeofday i problem zniknął.
Ktoś wie gdzie może leżeć przyczyna takiego zachowania? Co robię nie tak
czytając RTC? Przykład u góry.
-
9. Data: 2018-09-13 08:37:44
Temat: Re: Biblioteka standardowa time.h i mikrokontrolery
Od: Jacek Radzikowski <j...@s...die.die.die.piranet.org>
On 09/13/18 01:46, Atlantis wrote:
[...]
> Następnie w pętli głównej usunąłem gmtime(), zamiast tego wyświetlając
> na LCD aktualna wartość zwracaną przez time(). Efekt był dość...
> Dziwny... Mianowicie liczba złożona z dwóch ostatnich cyfr faktycznie
> zwiększała swoją wartość o jeden co sekundę. Natomiast trzecia, czwarta
> i piata cyfra od końca co chwilę zmieniała swoją wartść "tam i z
> powrotem" - raz było 500 z czymś, potem ponad 600, potem znów 500 z
> czymś i tak dalej...
>
> Postanowiłem więc zrobić eksperyment i stworzyłem zmienną uint32_t _rtc,
> która była zwiększana o 1 w przerwaniu alarmu RTC. Podpiąłem ją do
> funkcji _gettimeofday i problem zniknął.
>
> Ktoś wie gdzie może leżeć przyczyna takiego zachowania? Co robię nie tak
> czytając RTC? Przykład u góry.
To mocno śmierdzi pisaniem po stosie, i problem wcale nie musi być w
twoim kodzie. Uprość maksymalnie program testowy, nie używaj LCD ani
innych wodotrysków, tylko pisz na konsolę szeregową. Jeśli w dalszym
ciągu będą problemy, to błąd najprawdopodobniej siedzi gdzieś w obsłudze
RTC. Jeśli nie, to dodawaj po kolejne elementy i patrz kiedy zacznie
wariować. Wtedy możesz zacząć szukać w którym komponencie jest problem.
Nie bez znaczenia jest też wersja kompilatora. Dwa razy się mocno
przejechałem na arm-gcc: Po raz pierwszy, kiedy próbowałem skompilować
bibliotekę do obsługi launchpadowego LCD od TI. Darmowy kompilator
wszystko pięknie kompilował, przykłady się linkowały i uruchamiały, nie
działał tylko ekran dotykowy. Udało mi się zawęzić region gdzie powstaje
błąd do kilku linijek kodu, i tam utknąłem. Nie było tam żadnych
sztuczek zależnych od wersji kompilatora, nic do czego można by się
przyczepić. Po skompilowaniu gcc od TI wszystko zadziałało poprawnie.
Drugi raz wyłożyłem się na klasach z metodami wirtualnymi. Wystarczyło
dodać słówko "virtual" do deklaracji metody i program szedł w maliny.
Tym razem śledziłem wykonanie instrukcja po instrukcji, i znalazłem że
momencie powrotu z metody wskaźnik stosu wskazywał w jakieś losowe
miejsce w pamięci. I znów, z gcc od TI i ich własnym kompilatorem
wszystko działało poprawnie. Ale wystarczyła sama zmiana wywoływanego
kompilatora w projekcie wygenerowanym przez CCS żeby problem powrócił. W
tym momencie się poddałem, a projekt czeka aż będę miał czas i wenę żeby
znów się nim zająć.
Jacek.
-
10. Data: 2018-09-13 09:03:09
Temat: Re: Biblioteka standardowa time.h i mikrokontrolery
Od: Atlantis <m...@w...pl>
On 13.09.2018 08:37, Jacek Radzikowski wrote:
> To mocno śmierdzi pisaniem po stosie, i problem wcale nie musi być w
> twoim kodzie. Uprość maksymalnie program testowy, nie używaj LCD ani
> innych wodotrysków, tylko pisz na konsolę szeregową. Jeśli w dalszym
> ciągu będą problemy, to błąd najprawdopodobniej siedzi gdzieś w obsłudze
> RTC. Jeśli nie, to dodawaj po kolejne elementy i patrz kiedy zacznie
> wariować. Wtedy możesz zacząć szukać w którym komponencie jest problem.
Płytka prototypowa na której działa ten przykład jest dość skromna - to
fakt. Flash jest w chwili obecnej prawie całkowicie zapchany, jednak
pamięci RAM pozostało jeszcze całkiem sporo. Wątpię, żeby mogło dojść do
napisania stosu. Biblioteka LCD działała prawidłowo na AVR, a po
przeportowaniu na STM32 program z nią również działa poprawnie, pod
warunkiem zastąpienia wbudowanego RTC osobną zmienną przechowującą
timestampa.
Jedyne co mi jeszcze przychodzi do głowy, to próba odczytywania RTC z
parametrem RTC_FORMAT_BCD, a następnie konwertowania do postaci binarnej
za pomocą zestawu własnych funkcji.
W każdym razie sposób w jaki pobieram dane z RTC i wypełniam nimi
strukturę struct tm wygląda w porzadku?