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.