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

STM32 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.

1
2
3
4
5
6
7
8
9
10
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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#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 &gt;= 0) {
do {
*--p = '0' + (i % 10);
i /= 10;
} while (i != 0);
return p;
}
else { /* i &lt; 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 &lt; len; i+=4) {

uint32_t buflen = 4;
if( i+4 &gt;= 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 = &amp;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&lt;0)
{
i = -i;
printc('-');
}
prints(convert(i,10));
break;

/// Fetch floating point argument
case 'f' : d = va_arg(arg, double);
if(d&lt;0)
{
d = -d;
prints("-");
}
i = (unsigned int)d;
prints(convert(i, 10));
printc('.');

d = d - i;
if (d*10.0 &lt; 1.0)
printc('0');
if (d*100.0 &lt; 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);
}

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 przez Ciebie profilu dodaj

1
monitor arm semihosting enable

w polu Run Commands.

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
while (1)
{
/* USER CODE END WHILE */

/* USER CODE BEGIN 3 */
uint8_t message[] = "UART over ST-Link \n\r";
HAL_UART_Transmit(&amp;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.


Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Serwis wykorzystuje pliki cookies. Korzystając ze strony wyrażasz zgodę na wykorzystywanie plików cookies. więcej informacji

Wrażenie zgody na pliki Cookies jest konieczne, aby uzyskać najlepsze wrażenia z przeglądania strony. Jeżeli nadal nie wyraziłeś zgody na używanie plików Cookies, zaakceptuj poniżej klikając w przycisk "Akceptuj" na banerze.

Close