Może się powtórzę, ale zestawy od ST są świetne. Do każdej płytki dostajemy w pełni funkcjonalny programator ST-Link. No prawie w pełni, ale w za to 100% pokrywający potrzeby montowanych w zestawy mikrokontrolera. Czy znasz wszystkie jego funkcjonalności? Mógłbym założyć się, że nie, ale zakładam też, że zaawansowani użytkownicy również to czytają, więc nie będę ryzykował 😉
Czym jest ST-Link
W skrócie jest to programator zgodny z MCU firmy ST. ST-Link default’owo jest integralną częścią zestawów Nucleo, lecz można je wyłamać i używać jako odrębnego tworu. Niestety Nucleo-32 jest wyjątkiem i nie oddzielimy fizycznie ST-Linka od części zasadniczej. Podobnie jest z płytami Discovery – też nie wyłuskamy sobie programatora. Posiadając kilka Nucleo mam zapas programatorów dla siebie i moich dzieci do końca życia.
Co może ten mały potworek? Wiele feature’ów opisanych zostało w dokumentacji dotyczącej programatorów zintegrowanych z zestawami ewaluacyjnymi. Dodatkowo w dokumentacji ogólnej zestawów Nucleco jest co nie co opisane. Polecam Ci zapoznanie się z dokumentami.
TN1235 - Overview of the ST-LINK embedded in STM32 MCU Nucleo
Podchodząc poraz pierwszy do swojego egzemplarza wypadałoby zaktualizować oprogramowanie w programatorze. Zrobisz to za pomocą ST-Link Utility. Najnowszą wersję pobierzesz ze strony ST. Niestety trzeba mieć konto, aby coś od nich pobrać. Całą operację wykonuje się bardzo prosto. Uruchamiasz narzędzie i podłączasz Nucleo lub inną płytkę z ST-Link’iem. Następnie z górnego menu wybierz ST-Link >> Firmware Update.
Po klinknięciu w Device Connect program powinien wykryć podłączony do komputera programator. Firmware Version to aktualne oprogramowanie, które siedzi w programatorze. Poniżej znajduje się najnowsza wersja, która jest dostępna. W moim wypadku jest taka sama jak w sprzęcie. Klikając Yes, uruchamiasz procedurę aktualizacji.
Programowanie
Jeśli przeczytałeś uważnie dokumentację to znasz już niektóre cechy ST-Linka. Jego najważniejszą i podstawową funkcją jest niewątpliwie programowanie mikrokontrolera. Podłączając Nucleo w całości lub inne płytki ze zintegrowanym ST-Linkiem, mamy możliwość wgrania swojego programu do MCU. Można zrobić to poprzez ST-Link Utility wskazując skompilowany plik binarny z firmware’m lub bezpośrednio w środowisku SW4STM32 które jest zalecane przez ST oraz z którego ja korzystam.
Fajnym “bajerem” jest programowanie poprzez wrzucenie binarki do pamięci masowej pod którą montuje się ST-Link w systemie operacyjnym. Sposób ten jest podyktowany założeniami projektu mbed. Wyobraź sobie, że jesteś w terenie u klienta, nie masz swojego zestawu oprogramowania bo siadł Ci komputer, ale ktoś podesłał Ci binarkę z poprawionym błędem. Dzięki temu, że programator montuje się jak pendrive, przerzucasz na niego binarkę z porzyczonego laptopa czy nawet z telefonu obsługującego OTG i… gotowe. Układ jest zaprogramowany. Świetne, co nie? Ja z tego sposobu korzystam baardzo rzadko, ale widzę w nim potencjał.
Wspominałem wcześniej o wyłamywaniu ST-Linka z płytek Nucleo. Po takim zabiegu, możemy programować dowolne układy podłączając się poprzez złącze CN4. Aby było to możliwe należy jeszcze ściągnąć zworki z CN2. Ściągając te zworki można również programować układy zewnętrzne bez wyłamywania ST-Linka z Nucleo. Złącze CN4 posiada standardowy pinout:
Uwaga. Pin 1 nie służy do zasilania programowanego układu! Pin ten podłączamy do zasilania programowanego MCU po to aby ST-Link dopasował się do niego. Dzięki temu możemy programować układy o bardzo niskich napięciach roboczych. Mimo tego, że nota ST-Linka dołączanego do zestawów Nucleo mówi, że mniej niż 3 V nie da rady – mi udało się z powodzeniem programować i debugować układy zasilane 1,8 V.
Pin 6 ja zawsze pomijam 😉
Debugowanie
Oprócz programowania mamy możliwość prawdziwego debugowania kodu. Pamiętam jak na AVR mordowałem się zawsze z debugową diodą, która była ustawiana gdzieś w kodzie jako pułapka. Miało to swój urok i lubię to do dzisiaj czasem robić. Problem pojawia się kiedy np. procesor chodzi, komunikacja wygląda na poprawną, ale nadal za nic w świecie program nie działa poprawnie. Diodą nie podglądniemy wartości zmiennych w RAMie.
Tak samo diodą nie zatrzymasz programu w dowolnym momencie. Natomiast z poziomu debugowania już tak. Postawisz tzw. breakpoint’y które zatrzymają wykonywanie programu kiedy MCU dojdzie do nich. Jest to niesamowicie potężne narzędzie do rozwiązywania problemów, a problemy to nawet 80% pisania kodu. Chyba, że ktoś preferuje metodę gumowej kaczuszki (link). Ja oczywiście swoją kaczkę na biurku mam 😉
Komunikacja UART
ST-Link jest jednocześnie konwerterem UART <=> USB. Tworzy on wirtualny port COM w systemie operacyjnym naszego komputera. Dzięki temu możemy printować co chcemy na terminalu naszego PC. Nie tylko printować, bo odbierać z PC również możemy. Świetna sprawa. Do tego celu wykorzystywany jest port UART2 znajdujący się na pinach PA2 i PA3.
Tak samo jak programowanie, używanie konwersji UART <=> USB jest możliwe na zewnątrz płytek ewaluacyjnych. Możliwe jest to za pomocą złącza CN3 do którego dostarczamy UART mikrokontrolera. Z tym, że nie ma wygodnej zworki do rozłączania jak w przypadku programowania. Aby użyć UART na zewnątrz bez wyłamywania ST-Linka należy wylutować zworki SB13 i SB14 znajdującej się na spodzie PCB.
Semihosting
Prawdopodobnie nie słyszałeś o tym. Ja sam dowiedziałem się o tym całkiem niedawno. Co jeśli chciałbyś coś printować do komputera, ale nie masz wolnego żadnego interfejsu UART lub USB bo wszystko jest zajęte przez układy peryferyjne w urządzeniu? Dupa, nie da się co nie? A jednak! Z pomocą przychodzi tzw. semihosting. Jest to ficzer polegający na printowaniu do gdb także używa się go jedynie w trybie debugowania. Niestety działa to tylko w jedną stronę, ale przy debugowaniu głównie tę właśnie stronę używamy – wysyłamy do PC. Kolejną wadą jest znaczne spowolnienie wykonywania programu przez MCU, dlatego trzeba wyłączać tą metodę w docelowym programie na mikrokontroler. Brak uruchomienia MCU w trybie debugowania również nie pozwoli na prawidłową pracę jeśli używany jest semihosting. Jak to skonfigurować i jak używać? Do dzieła!
Potrzeba jest w kodzie specjalna funkcja asemblerowa piszącza na interfejs ST-Link.
void send_command(int command, void *message) { #ifdef DEBUG __asm("mov r0, %[cmd];" "mov r1, %[msg];" "bkpt #0xAB" : : [cmd] "r" (command), [msg] "r" (message) : "r0", "r1", "memory"); #endif }
Jak widzisz funkcję tę opakowałem w #ifdefa. Dzięki temu mogę szybko wyłączyć semihosting jeśli nie chcę go użyć. Do tego dochodzi jeszcze cała otoczka w postaci funkcji printujących.
#define INT_DIGITS 19 /* enough for 64 bit integer */ char *itoa(int i) { static char buf[INT_DIGITS + 2]; char *p = buf + INT_DIGITS + 1; /* '\0' */ if (i >= 0) { do { *--p = '0' + (i % 10); i /= 10; } while (i != 0); return p; } else { /* i < 0 */ do { *--p = '0' - (i % 10); i /= 10; } while (i != 0); *--p = '-'; } return p; } void prints( const char* str ) { uint32_t len = strlen(str); uint32_t i = 0; for( i = 0; i < len; i+=4) { uint32_t buflen = 4; if( i+4 >= len ) buflen = len-i; size_t m[] = { 2/*stderr*/, (size_t)str, buflen/sizeof(char) }; send_command(0x05/* some interrupt ID */, m); str+=4; } } void printc( char c ) { char buf[2]; buf[0] = c; buf[1] = 0; prints(buf); } void printi( int32_t i ) { prints( itoa(i) ); } char *convert(unsigned int num, int base) { static char Representation[]= "0123456789ABCDEF"; static char buffer[50]; char *ptr; ptr = &buffer[49]; *ptr = '\0'; do { *--ptr = Representation[num%base]; num /= base; }while(num != 0); return(ptr); } void print(char* format, ...) { char *traverse; unsigned int i; char *s; double d; va_list arg; va_start(arg, format); /// Pack buf per 4 characters (last is always NULL) char buf[5]; memset(buf, 0, 5); int t = 0; for(traverse = format; *traverse != '\0'; traverse++) { if (*traverse != '%') { if( t == 5 ) { prints(buf); memset(buf, 0, 5); t = 0; } buf[t%5] = *traverse; t++; continue; } prints(buf); memset(buf, 0, 5); t = 0; traverse++; /// Module 2: Fetching and executing arguments switch(*traverse) { /// Fetch char argument case 'c' : i = va_arg(arg,int); printc(i); break; /// Fetch Decimal/Integer argument case 'd' : i = va_arg(arg,int); if(i<0) { i = -i; printc('-'); } prints(convert(i,10)); break; /// Fetch floating point argument case 'f' : d = va_arg(arg, double); if(d<0) { d = -d; prints("-"); } i = (unsigned int)d; prints(convert(i, 10)); printc('.'); d = d - i; if (d*10.0 < 1.0) printc('0'); if (d*100.0 < 1.0) printc('0'); /// Round to 3 decimal places prints(convert((int)round(d*1000.0), 10)); break; /// Fetch Octal representation case 'o': i = va_arg(arg,unsigned int); prints(convert(i,8)); break; /// Fetch string case 's': s = va_arg(arg,char *); prints(s); break; /// Fetch Hexadecimal representation case 'x': i = va_arg(arg,unsigned int); prints(convert(i,16)); break; } } prints(buf); memset(buf, 0, 5); va_end(arg); } [/cc]
Gdy mamy już gotowy kod, trzeba skonfigurować SW4STM32, aby odbierał on komunikaty w gdb. Otwórz konfiguracje profili debugowania.
W zakładce Startup używanego
monitor arm semihosting enable
Pamiętaj o tym, aby komentować kod do semihostingu jeśli go nie używasz. Możesz to wykonać np. za pomocą prostego define’a jak jak wspominałem wyżej.
Po wykonaniu tych kroków możemy printować w kodzie np. testowo w głównej pętli programu między wysyłaniek komunikatu przez UART, a cyklicznym mignięciem diody:
while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ uint8_t message[] = "UART over ST-Link \n\r"; HAL_UART_Transmit(&huart2, message, sizeof(message)-1, 100); print("Semihosting test \n"); HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); HAL_Delay(500); } /* USER CODE END 3 */
Uruchomienie programu w trybie debugowania generuje cykliczne wypisywania na konsole gdb testowego komunikatu.
Przyznaj, że jest to świetne. Nie potrzebujesz wolnego UARTa do prostego debugowania – wystarczy ST-Link, którego masz zawsze!
Jeśli podobał Ci się ten wpis polub lub udostępnij go dalej. Będę bardzo wdzięczny za każdą aktywność.
Jak zwykle kod źródłowy użyty we wpisie znajdziesz na GitHubie: link
Jeśli zauważyłeś jakiś błąd, nie zgadzasz się z czymś lub po prostu uważasz, że chciałbyś podystkutować w tym temacie, napisz komentarz. Pamiętaj, że dyskusja ma być kulturalna i zgodna z zasadami języka polskiego.
8 komentarzy
Tomasz · 06/11/2020 o 22:36
Witaj, czy pin “SWO” jest potrzebny przy debugowaniu? Czy można go bezwzględnie pominąć?
Mateusz Salamon · 07/11/2020 o 10:01
Można pominąć.
Dariusz · 01/03/2020 o 14:43
“…Aby użyć UART na zewnątrz bez wyłamywania ST-Linka należy wylutować zworki SB13 i SB14 znajdującej się na spodzie PCB…”, albo ustawić wejścia na PA2,PA3
Mateusz Salamon · 10/03/2020 o 09:45
Ale PA2, PA3 na mikrokontrolerze na Nucleo, tak? Można w sumie i tak, nie pomyślałem o tym, ale musisz na szybko robić i wrzucać projekt na Nucleowy MCU 😀 Ma to plus, że jest łatwo odwracalne, a lutowanie faktycznie może być mocno angażujące.
SaS · 12/12/2019 o 20:12
Można już kupić ST-Link V3 https://kamami.pl/programatory-stm32/575809-stlink-v3-mini-kompaktowy-programatordebuger-dla-stm32.html?search_query=st-link+v3&results=40
Jest tańszy niż ST-Link V2-1 https://kamami.pl/programatory-stm32/559992-zl30prgv2-1-programator-debugger-swd-dla-mikrokontrolerow-stm32.html?search_query=zl-30&results=2
SaS · 03/12/2019 o 13:40
Z ST-Link V2-1 9ale -12 więc chińskie klony odpadają) można bezpłatnie, legalnie, zrobić J-LINK EDU! Jak? Tak: https://mikrokontroler.pl/2016/05/06/stlinkreflash-interfejs-j-link-w-zestawach-stm32-nucleo-i-discovery/
Zalety? 8192 pułapki anie zależnie od rdzenia 3 do 9. Co istotne, proces jest odwracalny! Jak J-LINK się nie podoba można zrobić go ponownie ST-Linkiem.
Mateusz Salamon · 08/12/2019 o 15:06
Słyszałem kiedyś o tym. Chętnie wypróbuję i opiszę na blogu. Dzięki!
SaS · 12/12/2019 o 20:10
Wypróbowałem, działa. Różnica w stosunku do ST-Link duża, głównie w sensie liczby półłapek.