Przeglądając budżetowe mikrokontrolery z rdzeniem ARM Cortex M0/M0+ autor natrafił na sygnowany przez firmę Twen układ typu TW32F003. Mikrokontroler ten jest oferowany na internetowych portalach sprzedażowych głównie w postaci płytki uruchomieniowej o symbolu TWen32F003. Jej wygląd prezentuje fotografia 1.
Płytka uruchomieniowa
Płytka TWen32F003 jest jednym z wielu modułów opracowanych na potrzeby projektów z internetowej platformy edukacyjno-szkoleniowej http://haohaodada.com. Platforma ta koncepcyjnie zbliżona jest do znanego wśród programistów mikrokontrolerów ARM środowiska Mbed Online Compiler (obecnie już nie rozwijanego i zastąpionego przez Keil Studio Cloud). Programy pisze się tu w oknie przeglądarki www, kompiluje w chmurze, a program wynikowy jest następnie przesyłany do płytki użytkownika w celu wykonania. Platforma http://haohaodada.com obsługuje wiele różnych dalekowschodnich mikrokontrolerów, zarówno 8- jak i 32-bitowych, do których oferuje specjalne płytki uruchomieniowe. O ile zazwyczaj zainstalowane w nich mikrokontrolery są dość dobrze udokumentowane, o tyle w przypadku płytki TWen32F003 większość dostawców ogranicza się do stwierdzenia, że użyty w niej mikrokontroler jest wyposażony w 32-bitowy rdzeń ARM, nie określając nawet dokładnego typu rdzenia. Poszukiwania producenta TW32F003 w Internecie również nie przynoszą żadnych rezultatów. Można jedynie natrafić na schemat blokowy tego układu, obrazujący zaimplementowane w mikrokontrolerze peryferia. Schemat ten widnieje na rysunku 1.
Jak widać, mikrokontroler TW32F003 wyposażony został w rdzeń ARM Cortex M0+, który może być taktowany z maksymalną częstotliwością 32 MHz – oraz w stosunkowo bogaty, jak na budżetowy układ, zestaw peryferiów. Wbudowane pamięci Flash i SRAM mają odpowiednio rozmiar 64 kB i 8 kB. Znalazły się tu też typowe układy czasowo-licznikowe, interfejsy komunikacyjne SPI, I²C i UART, 2 watchdogi, 12-bitowy przetwornik analogowo-cyfrowy, 2 komparatory, sprzętowy blok obliczeniowy CRC, a nawet 3-kanałowy kontroler DMA, który rzadko występuje w mikrokontrolerach z tej półki cenowej. Na uwagę zasługuje także bardzo szeroki zakres napięć zasilania układu, rozciągający się od 1,7 V aż do 5,5 V.
Schemat ideowy płytki uruchomieniowej, na której zainstalowano omawiany mikrokontroler, prezentuje rysunek 2. Trudno o prostszy układ. Poza mikrokontrolerem i dwiema diodami LED, z których jedna sygnalizuje obecność napięcia zasilania, a druga jest przeznaczona do sterowania przez program użytkownika, na płytce nie znajduje się w zasadzie nic więcej. Nie ma tu nawet stabilizatora napięcia zasilania +3,3 V, typowo spotykanego w podobnych modułach.
Zgodnie z aktualnym trendem płytka jest wyposażona w złącze standardu USB C. Pewne zdziwienie może budzić fakt podłączenia portów PA13 i PA14 mikrokontrolera do linii danych DN i DP w gnieździe USB, ponieważ mikrokontroler TW32F003 nie ma wbudowanego interfejsu USB. Jak jednak wynika z diagramu na stronie platformy szkoleniowej, płytki uruchomieniowe nie są podłączane bezpośrednio do portu USB komputera PC, lecz do gniazda USB w programatorze – debuggerze typu STC Link. Kabel USB C łączący płytkę uruchomieniową z programatorem – debuggerem zastępuje w tym przypadku zwykły przewód taśmowy interfejsów SWD i RS. Drugą opcją jest podłączenie płytki TWen32F003 do komputera PC za pośrednictwem modułu LILYGO T-U2T. Jest to – wykonany w formie „pendriva”, zakończonego z obu stron gniazdami USB C – konwerter USB/UART-TTL. Używa się go w płytkach systemu LILYGO. Bezpośrednie podłączenie płytki uruchomieniowej TWen32F003 do portu USB w komputerze PC pozwala jedynie na jej zasilanie – układ nie jest wykrywany przez system operacyjny MS Windows jako urządzenie peryferyjne.
TW32F003 vel PY32F003F18P6
Analiza listy zasobów sprzętowych mikrokontrolera TW32F003, jego zakresu napięcia zasilania oraz rozkładu wyprowadzeń pozwoliła na identyfikację układu. Okazuje się, że mikrokontroler TW32F003 to rebrandowany układ typu PY32F003F18P6 produkcji Puya Semiconductor, do którego pamięci Flash został wgrany dodatkowy bootloader obsługujący platformę http://haohaodada.com, natomiast wyprowadzenie nRST/PF2 zostało skonfigurowane jako port I/O. Potwierdza to kod identyfikacyjny, znajdujący się w pamięci systemowej mikrokontrolera pod adresem 0x1FFF 0D80 – 0x1FFF 0D8F (rysunek 3).
Przestawienie znaków w kodzie identyfikacyjnym spowodowane jest zapisaniem ich w pamięci w postaci 32-bitowych słów w notacji Little Endian.
Mikrokontrolery PY32F0
Mikrokontrolery firmy Puya są dość znane. Zdobyły one rozgłos jako najtańsze na rynku procesory z rdzeniem ARM, dostępne w cenie już od 8 centów za sztukę [1]. Rodzina układów PY32F0 obejmuje trzy modele: PY32F002A, PY32F003 oraz PY32F030, przy czym układ typu PY32F002 okazuje się najprostszy spośród nich. Jest on oferowany tylko z jedną opcją rozmiaru pamięci Flash i SRAM, obniżoną częstotliwością taktowania rdzenia wynoszącą 24 MHz oraz zredukowanym zestawem peryferiów. Z kolei układ typu PY32F030 jest najbardziej rozbudowany: jego rdzeń może być taktowany z częstotliwością równą 48 MHz, obecny w nim generator sygnału zegarowego wyposażono w pętlę PLL, a sam układ ma dodatkowy interfejs SPI oraz sterownik wyświetlacza LED o organizacji 4 znaki po 8 segmentów. Wbrew temu, co sugeruje oznaczenie, układ PY32F030 nie stanowi klona mikrokontrolera STM32F030. Mimo wielu podobieństw, nie jest z nim zgodny programowo. Na przykład rejestry kontrolujące porty wejścia/wyjścia w obu układach znajdują się pod innymi adresami. Krótkie porównanie poszczególnych członków rodziny PY32F0 zawiera tabela 1. Pełna dokumentacja mikrokontrolerów PY32F0 – obejmująca karty katalogowe, podręczniki programowania, erraty oraz noty aplikacyjne – jest dostępna zarówno na stronie producenta [2], jak i na portalach prowadzonych przez entuzjastów mikrokontrolerów firmy Puya [3]. Dokumentacja ta dostarczona została w większości w języku chińskim, jednak podstawowe dokumenty (takie jak karty katalogowe i podręczniki programowania) są też dostępne w języku angielskim.
Mikrokontrolery PY32F0 można nabyć w wielu typach obudów, poczynając od najmniejszych z 8 wyprowadzeniami, aż do największych o 32 wyprowadzeniach. Są to standardowe obudowy typu SOP, MSOP, TSSOP, DFN, QFN i LQFP. Pojawiła się też jedna nietypowa obudowa ESSOP10 – o 10 wyprowadzeniach w rastrze 1 mm. Kolejną rzadko spotykaną cechą tych układów okazuje się fakt, iż mikrokontroler w ramach danej obudowy jest oferowany z różnymi wariantami układu wyprowadzeń. Przykładowe zestawienie dostępnych odmian mikrokontrolera typu PY32F003 w obudowie TSSOP20 pokazuje rysunek 4.
Wywołanie programu z linii komend:
pyStlink [-h] [-q | -i | -v | -d] [-V] [-c CPU] [-r] [-u] [-s SERIAL] [-n INDEX] [-H] [action [action…]]
Opcjonalne parametry:
-h, --help : wyświetlenie listy dostępnych parametrów i komend
-q, --quiet : wyłączenie komunikatów
-i, --info : podstawowy zestaw komunikatów (opcja domyślna)
-v, --verbose : rozszerzony zestaw komunikatów
-d, --debug : komunikaty debug pracy programu
-V, --version : wyświetlenie wersji programu
-c CPU, --cpu CPU : oczekiwany typ mikrokontrolera [np.: STM32F051, STM32L4]
-r, --no-run : pozostawienie rdzenia mikrokontrolera w stanie zatrzymania po zakończeniu działania programu
-u, --no-unmount : brak odmontowania funkcji DISCOVERY dla programatora ST-Link/V2-1 w systemie OS/X
-s SERIAL, --serial SERIAL : wybór programatora STLink o podanym numerze seryjnym
-n INDEX, --num-index INDEX : wybór programatora STLink o podanym indeksie
-H, --hard : sprzętowy reset mikrokontrolera linią NRST
Akcje:
dump:core : wyświetlenie zawartości wszystkich rejestrów roboczych rdzenia
dump:{reg} : wyświetlenie zawartości wybranego rejestru roboczego rdzenia
dump:{addr}:{size} : wyświetlenie zawartości pamięci poczynając od podanego adresu
dump:sram[:{size}] : wyświetlenie zawartości pamięci SRAM
dump:flash[:{size}] : wyświetlenie zawartości pamięci Flash
dump:{addr} : wyświetlenie zawartości 32-bitowego rejestru o podanym adresie
dump16:{addr} : wyświetlenie zawartości 16-bitowego rejestru o podanym adresie
dump8:{addr} : wyświetlenie zawartości 8-bitowego rejestru o podanym adresie
dump:ob : wyświetlenie zawartości Option Bytes
set:{reg}:{data} : zapis podanej wartości do podanego rejestru roboczego rdzenia
set:{addr}:{data} : zapis podanej wartości do 32-bitowego rejestru o podanym adresie
read:{addr}:{size}:{file} : zapis do pliku zawartości pamięci poczynając od podanego adresu
read:sram[:{size}]:{file} : zapis do pliku zawartości pamięci SRAM
read:flash[:{size}]:{file} : zapis do pliku zawartości pamięci Flash
fill:{addr}:{size}:{pattern} : wypełnienie pamięci SRAM podanym wzorem poczynając od podanego adresu
fill:sram[:{size}]:{pattern} : wypełnienie pamięci SRAM podanym wzorem
write:{file.srec} : zapis danych z pliku w formacie SREC do pamięci SRAM
write:{addr}:{file} : zapis danych z pliku w formacie binarnym do pamięci SRAM poczynając od podanego adresu
write:sram:{file} : zapis danych z pliku w formacie binarnym do pamięci SRAM
flash:erase : skasowanie zawartości całej pamięci Flash
flash[:erase][:verify]:{file.srec} : skasowanie wymaganej liczby sektorów pamięci Flash + zapis danych z pliku w formacie SREC do pamięci Flash + weryfikacja poprawności zapisu
flash[:erase][:verify][:{addr}]:{file} : skasowanie wymaganej liczby sektorów pamięci Flash + zapis danych z pliku w formacie binarnym do pamięci Flash poczynając od podanego adresu + weryfikacja poprawności zapisu
flash:check:{file.srec} : weryfikacja zawartości pamięci Flash z danymi z pliku w formacie SREC
flash:check[:{addr}]:{file} : weryfikacja zawartości pamięci Flash z danymi z pliku w formacie binarnym poczynając od podanego adresu
ob:name:data : zapis podanej wartości do Option Byte o podanej nazwie
reset : reset rdzenia mikrokontrolera
reset:halt : reset i zatrzymanie rdzenia mikrokontrolera
halt : zatrzymanie rdzenia mikrokontrolera
step : wykonanie przez rdzeń jednego rozkazu
run : wznowienie wykonywania programu
sleep:{seconds} : wstawienie opóźnienia pomiędzy akcjami
Metody programowania
Programy na mikrokontrolery PY32F0 (a więc i układ TW32F003) mogą być pisane w dowolnym środowisku programistycznym obsługującym układy ARM z rdzeniem Cortex M0/M0+. Firma Puya dostarcza biblioteki programistyczne oraz przykładowe programy. Biblioteki obsługi peryferiów obejmują zarówno wysokopoziomową wersję bibliotek HAL, jak i wersję niskopoziomową LL. Jak ma to zazwyczaj miejsce w przypadku układów produkcji chińskiej, dostarczane biblioteki i przykładowe programy są dostosowane do kompilatorów firm Keil oraz IAR. W przypadku kompilatora gcc konieczna jest modyfikacja kodów startowych w celu ich dostosowania do wymagań tego narzędzia. Nie ma jednak potrzeby modyfikowania bibliotek osobiście, ponieważ w Internecie można znaleźć biblioteki przygotowane do zastosowania z kompilatorem gcc, np. autorstwa Jay’a Carlsona [1].
Zapis programu wynikowego do pamięci Flash mikrokontrolera TW32F003 można wykonać za pomocą:
- bootloadera platformy http://haohaodada.com,
- fabrycznego bootloadera,
- programatora SWD.
Pierwszy sposób byłby w naszych warunkach mało przydatny. Poza tym bootloader ten zajmuje początkowe 2 kB pamięci Flash, ograniczając tym samym maksymalny dopuszczalny rozmiar aplikacji użytkownika. Najlepiej więc skasować go, zwalniając całą pamięć Flash na potrzeby własnego programu.
Programowanie pamięci Flash za pomocą fabrycznego bootloadera wymaga użycia programu PuyaISP (rysunek 5).
Bootloader systemowy jest aktywowany po podaniu na linię BOOT0 (PF4) stanu wysokiego i zrestartowaniu mikrokontrolera. Programowanie odbywa się przez interfejs szeregowy UART przy użyciu linii PA2 (sygnał TxD) i PA3 (sygnał RxD). Do podłączenia płytki TWen32F003 do komputera PC wymagany jest konwerter UART-TTL/USB. Program sterujący, oprócz kasowania i zapisu pamięci Flash, pozwala też na odczyt i zapis ustawień konfiguracyjnych mikrokontrolera (tzw. Option Bytes).
Ponieważ program PuyaISP pracuje tylko w wersji okienkowej w 64-bitowym systemie MS Windows, problematyczna okazuje się jego integracja z używanym środowiskiem IDE. Poza tym nie pozwala on na debugowanie wykonywanego przez mikrokontroler programu.
Z tego też powodu autor do programowania mikrokontrolera TW32F003 zastosował programator-debugger typu ST-Link oraz program sterujący pyStlink, pozwalające na programowanie układu przez interfejs SWD. Schemat połączenia płytki TWen32F003 z programatorem STLink pokazuje rysunek 6, natomiast na fotografii tytułowej pokazano rzeczywisty układ używający programatora-debuggera ST-Link wchodzącego w skład płytki STM32F0 Discovery.
Programator-debugger jest podłączony do portów PA13 (linia SWDIO) i PA14 (linia SWCLK) mikrokontrolera. Dodatkowo port PF4 (linia BOOT0) mikrokontrolera dołączono do szyny zasilania, co wymusza wykonywanie przez mikrokontroler kodu fabrycznego bootloadera. Okazało się to konieczne, ponieważ bootloader platformy http://haohaodada.com, z którym dostarczana jest płytka TWen32F003, zmienia przy starcie konfigurację portów PA13 i PA14, odłączając je od układu SWD i blokując tym samym możliwość połączenia z programatorem-debuggerem.
Obsługa układów PY32F0 i TW32F003 w programie pyStlink
Użycie programatora-debuggera ST-Link wraz z programem pyStlink do programowania innych mikrokontrolerów niż układy STM32 było już szczegółowo opisywane na łamach „Elektroniki Praktycznej” [4], dlatego też informacje o samym pyStlink, jego obsłudze, jak również sposobie dodawania nowych mikrokontrolerów nie będą tutaj powtarzane – zainteresowanych Czytelników zachęcamy do lektury wspomnianego artykułu. Niniejszy opis skupia się na implementacji w programie pyStlink obsługi układu TW32F003, która okazała się nieco bardziej złożona, niż w przypadku omówionych we wspomnianym artykule układów typu HC32F003/HC32F005. Gwoli przypomnienia, dodanie w programie pyStlink obsługi nowego mikrokontrolera wymaga przygotowania opisu tego układu oraz napisania sterownika obsługi jego pamięci Flash.
Pierwszym problemem przy tworzeniu opisu mikrokontrolera TW32F003 okazała się automatyczna detekcja przez program pyStlink typu podłączonego układu. W dokumentacji mikrokontrolerów PY32F0 podawana jest co prawda lokalizacja rejestru IDCODE zawierającego kod identyfikacyjny układu (oraz skojarzonej z nim komórki w pamięci systemowej zlokalizowanej pod adresem 0x1FFF 0FF8), jednak bez podania identyfikatorów przypisanych poszczególnym typom mikrokontrolerów rodziny PY32F0. Z tego też powodu rozpoznawanie układu oparto na wspomnianym wcześniej tekstowym identyfikatorze typu mikrokontrolera, zlokalizowanym w pamięci systemowej pod adresem 0x1FFF 0D80 – 0x1FFF 0D8F (rysunek 3). Pierwsze cztery bajty tego identyfikatora zostały potraktowane tak, jakby były one rejestrem IDCODE w mikrokontrolerze STM32. Przy takim podejściu kod identyfikacyjny mikrokontrolera TW32F003 odczytywany przez program pyStlink jest równy 0x332.
Drugi problem stanowiła identyfikacja rozmiaru pamięci Flash wbudowanej w mikrokontroler. Dokumentacja układu PY32F003 nie wymienia rejestru zawierającego informację o rozmiarze zaimplementowanej w układzie pamięci. Co prawda w dostarczanych przez producenta plikach nagłówkowych py32f003xx.h, definiujących zasoby mikrokontrolerów PY32F003, znajduje się deklaracja:
określająca lokalizację rozmiaru pamięci Flash w pamięci systemowej, jednak zawartość tej komórki pamięci nie odpowiada rzeczywistemu rozmiarowi pamięci Flash w mikrokontrolerze. Problem ten rozwiązano przez modyfikację w programie pyStlink algorytmu wyszukiwania typu podłączonego układu. W programie dodano warunek, że w przypadku braku odczytu kodu identyfikacyjnego mikrokontrolera, lub też braku odczytu rozmiaru pamięci Flash, program pyStlink posiłkuje się podanym w linii komend typem układu (parametr -c wywołania programu), wyszukując w słowniku obsługiwanych układów mikrokontroler wskazanego typu. W przypadku znalezienia zgodnego układu, rozmiar pamięci i nazwa sterownika obsługującego pamięć Flash w mikrokontrolerze są pobierane ze znalezionego opisu. Dodany w słowniku obsługiwanych przez program pyStlink układów opis mikrokontrolerów PY32F0 i TW32F003 pokazuje listing 1.
‘core’: ‘CortexM0+’,
‘idcode_reg’: [0x40015800, 0x00100C60, 0x1FFF0D80],
‘devices’: [
.............................................
{
‘dev_id’: 0x332, # Puya Semiconductor
‘flash_size_reg’: 0,
‘flash_driver’: ‘PY32F0’,
‘erase_sizes’: (128, ),
‘devices’: [
{‘type’: ‘PY32F002A’, ‘flash_size’: 20, ‘sram_size’: 3, ‘eeprom_size’: 0,
‘freq’: 24},
{‘type’: ‘PY32F003XX4’, ‘flash_size’: 16, ‘sram_size’: 2, ‘eeprom_size’: 0,
‘freq’: 32},
{‘type’: ‘PY32F003XX6’, ‘flash_size’: 32, ‘sram_size’: 4, ‘eeprom_size’: 0,
‘freq’: 32},
{‘type’: ‘PY32F003XX8’, ‘flash_size’: 64, ‘sram_size’: 8, ‘eeprom_size’: 0,
‘freq’: 32},
{‘type’: ‘PY32F030XX3’, ‘flash_size’: 8, ‘sram_size’: 2, ‘eeprom_size’: 0,
‘freq’: 48},
{‘type’: ‘PY32F030XX4’, ‘flash_size’: 16, ‘sram_size’: 2, ‘eeprom_size’: 0,
‘freq’: 48},
{‘type’: ‘PY32F030XX6’, ‘flash_size’: 32, ‘sram_size’: 4, ‘eeprom_size’: 0,
‘freq’: 48},
{‘type’: ‘PY32F030XX7’, ‘flash_size’: 48, ‘sram_size’: 6, ‘eeprom_size’: 0,
‘freq’: 48},
{‘type’: ‘PY32F030XX8’, ‘flash_size’: 64, ‘sram_size’: 8, ‘eeprom_size’: 0,
‘freq’: 48},
{‘type’: ‘TW32F003’, ‘flash_size’: 64, ‘sram_size’: 8, ‘eeprom_size’: 0,
‘freq’: 32},
],
},
],
Listing 1. Opis mikrokontrolerów PY32F0 i TW32F003 w programie pyStlink (plik devices.py)
Wynik identyfikacji przez program pyStlink tak zdefiniowanego układu TW32F003 pokazuje natomiast rysunek 7.
Zgodnie z konwencją przyjętą w programie pyStlink, sterownik obsługi pamięci Flash mikrokontrolerów PY32F0 i TW32F003 zlokalizowano w oddzielnym pliku o nazwie py32f0.py. Sterownik ten jest tworzony jako klasa pochodna klasy stm32. Dzięki temu wymaga on jedynie napisania metody inicjalizacji mikrokontrolera, metod kasowania całej pamięci Flash i pojedynczych sektorów oraz metody zapisu pamięci Flash. Pozostałe funkcje sterownika są dziedziczone z klasy stm32 i nie wymagają modyfikacji.
Uproszczona funkcja inicjalizacji mikrokontrolerów PY32F0
i TW32F003, pozbawiona nadmiarowego kodu odpowiedzialnego za debugowanie, zaprezentowana została na listingu 2.
TRIM_HSI_24MHZ = 0x1FFF0F10
# Flash controller timers settings at SYSCLK=24MHz (reg. addr, size, trim data addr)
FLASH_TIM_CFG_24MHZ = (
{‘reg’: FLASH_TS0, ‘size’: ‘Byte’, ‘trim_data’: 0x1FFF0F6C},
{‘reg’: FLASH_TS1, ‘size’: ‘Word’, ‘trim_data’: 0x1FFF0F6E},
{‘reg’: FLASH_TS2P, ‘size’: ‘Byte’, ‘trim_data’: 0x1FFF0F70},
{‘reg’: FLASH_TPS3, ‘size’: ‘Word’, ‘trim_data’: 0x1FFF0F72},
{‘reg’: FLASH_TS3, ‘size’: ‘Byte’, ‘trim_data’: 0x1FFF0F6D},
{‘reg’: FLASH_PERTPE, ‘size’: ‘Long’, ‘trim_data’: 0x1FFF0F74},
{‘reg’: FLASH_SMERTPE,’size’: ‘Long’, ‘trim_data’: 0x1FFF0F78},
{‘reg’: FLASH_PRGTPE, ‘size’: ‘Word’, ‘trim_data’: 0x1FFF0F7C},
{‘reg’: FLASH_PRETPE, ‘size’: ‘Word’, ‘trim_data’: 0x1FFF0F7E},
)
def nvm_init(self):
self._driver.core_reset_halt()
# set SYSCLK to HSI=24MHz
clk = self.RCC_CR_HSIDIV_1 | self.RCC_CR_HSION
self._stlink.set_debugreg32(self.RCC_CR, clk)
clk_trim = self._stlink.get_debugreg16(self.TRIM_HSI_24MHZ)
clk_trim |= self.RCC_HSI_FS_24MHZ
self._stlink.set_mem16(self.RCC_ICSCR,
list(clk_trim.to_bytes(2, byteorder=’little’)))
clk = self.RCC_CFGR_PPRE_1 | self.RCC_CFGR_HPRE_1 | self.RCC_CFGR_SW_HSISYS
self._stlink.set_debugreg32(self.RCC_CFGR, clk)
# init Flash controller for SYSCLK=24MHz
self.unlock_nvm()
self._stlink.set_debugreg32(self.FLASH_ACR, self.FLASH_LATENCY_0)
# init Flash controller timers
for tim_cfg in self.FLASH_TIM_CFG_24MHZ:
reg = tim_cfg[‘reg’]
if tim_cfg[‘size’] == ‘Byte’:
data = self._stlink.get_debugreg8(tim_cfg[‘trim_data’])
elif tim_cfg[‘size’] == ‘Word’:
data = self._stlink.get_debugreg16(tim_cfg[‘trim_data’])
else:
data = self._stlink.get_debugreg32(tim_cfg[‘trim_data’])
self._stlink.set_debugreg32(reg, data)
self.lock_nvm()
Listing 2. Uproszczona funkcja inicjalizacji mikrokontrolerów rodziny PY32F0
Inicjalizacja układu polega na włączeniu w rejestrze RCC_CR oscylatora HSI (tj. wewnętrznego generatora RC), ustawieniu w rejestrze RCC_ICSCR w polu HSI_FS częstotliwości pracy bloku HSI i przepisaniu do pola HSI_TRIM tego rejestru ustawień kalibracyjnych układu HSI (dla wybranej częstotliwości pracy) pobranych z pamięci systemowej. Na koniec trzeba jeszcze ustawić w rejestrze kontrolnym RCC_CFGR oscylator HSI jako źródło sygnału zegarowego mikrokontrolera. W układzie TW32F003 blok HSI może generować sygnał zegarowy o częstotliwości 4 MHz, 8 MHz, 16 MHz, 22,12 MHz i 24 MHz.
Jednak w przypadku modelu PY32F002A dane kalibracyjne są zdefiniowane tylko dla częstotliwości 8 MHz i 24 MHz. Aby więc sterownik obsługiwał prawidłowo wszystkie układy z rodziny PY32F0, jako częstotliwość sygnału zegarowego HSI została wybrana wartość 24 MHz. Następnie do rejestrów kontrolera pamięci Flash przepisywane zostają z pamięci systemowej skalibrowane ustawienia timerów, ustalające cykle programowania i kasowania pamięci Flash przy zaprogramowanej częstotliwości sygnału zegarowego (tj. 24 MHz). Dostęp do rejestrów kontrolera Flash jest chroniony – z tego też względu przed ich modyfikacją konieczne jest odblokowanie dostępu przez wpisanie do rejestru FLASH_KEYR sekwencji kodów 0x4567 0123 i 0xCDEF 89AB, co realizuje funkcja unlock_nvm(). Ponowne włączenie ochrony rejestrów kontrolera pamięci Flash odbywa się przez ustawienie bitu LOCK w rejestrze kontrolnym FLASH_CR. Zadanie to wykonuje funkcja lock_nvm(), która kończy inicjalizację układu.
Wszystkie operacje na pamięci programu mikrokontrolera (tj. zapis pamięci, jej kasowanie, jak również kasowanie sektorów i kasowanie stron) są wykonywane przez kontroler pamięci Flash. Mapę jego rejestrów zawiera tabela 2.
Poszczególne operacje kontrolera Flash są wywoływane przez ustawienie odpowiedniego bitu w rejestrze kontrolnym FLASH_CR, którego strukturę pokazuje rysunek 8.
Pamięć Flash w mikrokontrolerach PY32F0 może być kasowana całkowicie, pojedynczymi sektorami o rozmiarze 4 kB oraz pojedynczymi stronami o rozmiarze 128 B. Kasowanie zawartości pamięci Flash polega na ustawieniu bitu operacji w rejestrze kontrolnym FLASH_CR (odpowiednio bitu MER – w przypadku kasowania pamięci, bitu SER – w przypadku kasowania sektora i bitu PER – w przypadku kasowania strony), a następnie zapisie 32 bitowego słowa pod adres znajdujący się odpowiednio: w obszarze pamięci Flash, kasowanego sektora lub kasowanej strony. Uproszczony kod funkcji kasowania całej pamięci Flash i wybranych stron pokazuje listing 3. W sterowniku nie implementowano obsługi kasowania sektorów, ponieważ nie było takiej potrzeby.
self.unlock_nvm()
self.set_operation(self.FLASH_CR_EOPIE | self.FLASH_CR_MER)
self._stlink.set_mem32(self._driver.FLASH_START, [0xFF, 0xFF, 0xFF, 0xFF])
self.wait_busy(1, ‘Erasing FLASH’)
if self.get_operation_result() == self.OPR_ERROR:
raise lib.stlinkex.StlinkException(‘Error erasing chip.’
‘Check write protection!’)
self.lock_nvm()
def erase_pages(self, addr, size):
self.unlock_nvm()
page_addr = addr & ~(self._driver.FLASH_PAGE_SIZE – 1)
end_addr = addr + size
while page_addr < end_addr:
self.set_operation(self.FLASH_CR_EOPIE | self.FLASH_CR_PER)
self._stlink.set_mem32(page_addr, [0xFF, 0xFF, 0xFF, 0xFF])
self.wait_busy(1, ‘Erasing FLASH page No %d’
%((page_addr – self._driver.FLASH_START) // self._driver.FLASH_PAGE_SIZE))
if self.get_operation_result() == self.OPR_ERROR :
raise lib.stlinkex.StlinkException(‘Error erasing FLASH page No %d.’
‘Check write protection!’
%((page_addr – self._driver.FLASH_START) // self._driver.FLASH_PAGE_SIZE))
page_addr += self._driver.FLASH_PAGE_SIZE
self.lock_nvm()
Listing 3. Uproszczone funkcje kasowania całej pamięci Flash oraz kasowania wybranych stron
Programowanie pamięci Flash w układach PY32F0 odbywa się całymi stronami o rozmiarze 128 B. W związku z tym adres początkowy zapisywanych danych musi ulec wyrównaniu do adresu strony. Warunek ten kontroluje funkcja zapisu pamięci Flash. Następnie rozmiar zapisywanych danych jest dopełniany do pełnej wielokrotności rozmiaru strony pamięci Flash.
Zapis pamięci jest wykonywany tylko słowami o rozmiarze 32 bitów. Polega on na ustawieniu bitu operacji PG w rejestrze kontrolnym FLASH_CR, następnie zapisie pod kolejnymi adresami na programowanej stronie pamięci Flash 31 słów danych, każde o rozmiarze 32 bitów, inicjalizacji operacji zapisu przez ustawienie bitu PGSTART w rejestrze kontrolnym FLASH_CR i zapisie ostatniego 32-bitowego słowa danych pod końcowy adres na programowanej stronie. Wysłanie trzydziestego drugiego 32-bitowego słowa uruchamia zapis strony pamięci Flash. Zakończenie procesu jest sygnalizowane przez bit końca operacji (EOP) w rejestrze stanu FLASH_SR. W przypadku, gdy zapis strony się nie powiódł, bit ten pozostaje wyzerowany. Jeżeli błąd zapisu jest spowodowany ustawionym zabezpieczeniem sektora pamięci Flash przed zapisem, w rejestrze stanu dodatkowo ustawiony zostaje bit WRPERR. Uproszczony kod funkcji zapisu pamięci Flash opisuje listing 4.
if addr is None:
addr = self.FLASH_START
if addr % self.FLASH_PAGE_SIZE:
# Flash is programmed in full pages only
raise lib.stlinkex.StlinkException(‘Start address is not aligned ‘
‘to FLASH page!’)
if len(data) % self.FLASH_PAGE_SIZE:
# Pad data to page size
data.extend([0xff] * (self.FLASH_PAGE_SIZE – len(data) % self.FLASH_PAGE_SIZE))
flash = Flash(self, self._stlink, self._dbg)
if erase:
if erase_sizes:
flash.erase_pages(addr, len(data))
else:
flash.erase_all()
flash.unlock_nvm()
flash.set_operation(flash.FLASH_CR_EOPIE | flash.FLASH_CR_PG)
while data:
data_block = data[:self.FLASH_PAGE_SIZE]
data_addr = addr
self._stlink.set_mem32(data_addr, data_block[:self.FLASH_PAGE_SIZE-4])
flash.set_operation(flash.FLASH_CR_PGSTRT)
self._stlink.set_mem32(data_addr + self.FLASH_PAGE_SIZE-4,
data_block[self.FLASH_PAGE_SIZE-4:])
flash.wait_busy(0.005)
if flash.get_operation_result() == flash.OPR_ERROR:
raise lib.stlinkex.StlinkException(‘Writing FLASH page addr 0x%08x failed. ‘
‘Check page write protection!’ %(data_addr))
data = data[self.FLASH_PAGE_SIZE:]
addr += self.FLASH_PAGE_SIZE
flash.clear_operation(flash.FLASH_CR_PG)
flash.lock_nvm()
Listing 4. Uproszczona funkcja zapisu pamięci Flash
Rysunek 9 pokazuje natomiast wynik procesu programowania pamięci Flash w mikrokontrolerze TW32F003.
Ustawienia konfiguracyjne Option Bytes
Niezależne od realizowanego programu ustawienia konfiguracyjne mikrokontrolera są w układach PY32F0 i TW32F003 zgrupowane w tzw. Option Bytes. Są to:
- ustawienia użytkownika (User option),
- adres obszaru chronionego SDK (SDK option),
- blokady sektorów pamięci Flash przed zapisem (WRP address).
Parametry te przechowuje się na odpowiedniej stronie systemowej pamięci Flash w postaci trzech 32-bitowych słów, przy czym poszczególne ustawienia są przypisane do bitów b15...b0 w danym słowie, natomiast bity b31..b16 pełnią rolę kontroli błędów. W nieuszkodzonym słowie są one równe zanegowanym wartościom bitów b15...b0. Przy starcie mikrokontrolera bity b15...b0 są kopiowane do specjalnych rejestrów kontrolera pamięci Flash, odpowiednio FLASH_OPTR, FLASH_SDKR i FLASH_WRPR. Tabela 3 zawiera zestawienie adresów słów konfiguracyjnych w systemowej pamięci Flash oraz odpowiadających im rejestrów kontrolera pamięci Flash.
Strukturę rejestru z ustawieniami użytkownika prezentuje rysunek 10.
Ustawienia te obejmują konfigurację sygnału BOOT1, funkcję wejścia nRST, automatyczne uruchamianie timerów watchdog, próg zadziałania układu kontroli napięcia zasilania (BOR) oraz ochronę pamięci Flash przed odczytem.
Strukturę rejestru z konfiguracją obszaru chronionego SDK w pamięci Flash pokazuje rysunek 11.
Ustawienia te definiują obszar pamięci, z której kod może być wykonywany, ale nie może być odczytywany przez instrukcje wykonywane poza tym obszarem (tj. zlokalizowane w pozostałej części pamięci Flash lub w pamięci SRAM), przez kontroler DMA, ani też przez interfejs SWD. Obszar ten jest przeznaczony do przechowywania kodu (np. bibliotek), którego dostawca nie chce udostępniać użytkownikowi nawet w postaci binarnej. Po wgraniu go do obszaru chronionego SDK przez dostawcę i włączeniu ochrony, użytkownik końcowy może rozwijać swoją aplikację, traktując zapisany kod w obszarze SDK jako czarną skrzynkę o zdefiniowanych przez dostawcę wejściach i wyjściach. Ochrona obszaru SDK jest aktywna, gdy adres początkowy SDK_STRT pozostaje mniejszy lub równy od adresu końcowego SDK_END.
W przeciwnym przypadku ochrona jest wyłączona. Adresy: początkowy i końcowy obszaru SDK są definiowane z krokiem 2 kB.
Strukturę rejestru z ustawieniami blokad sektorów pamięci Flash przed zapisem pokazuje rysunek 12. Bity b15...b0 odpowiadają stanowi ochrony przed zapisem sektorów pamięci Flash (każdy o rozmiarze 4 kB), odpowiednio o numerach S15...S0.
Oryginalny program pyStlink nie obsługuje programowania ustawień konfiguracyjnych mikrokontrolera. Aby uzyskać tę funkcjonalność dla układów PY32F003 i TW32F003, w klasie stm32 zdefiniowano dwie dodatkowe metody: ob_get_all() i ob_write(), realizujące odpowiednio odczyt ustawień konfiguracyjnych i zapis wybranego słowa konfiguracyjnego. Implementację tych metod w sterowniku obsługi pamięci Flash PY32F0 w programie pyStlink zawiera listing 5.
OPT_BYTES = (
{‘name’: ‘OPTR’, ‘reg’: Flash.FLASH_OPTR},
{‘name’: ‘SDKR’, ‘reg’: Flash.FLASH_SDKR},
{‘name’: ‘WRPR’, ‘reg’: Flash.FLASH_WRPR},
)
def ob_get_all(self):
self._dbg.debug(‘PY32F0.ob_get_all’)
opt_data = []
for opt_byte in self.OPT_BYTES:
opt_data += [[opt_byte[‘name’]]+[self._stlink.get_debugreg32(opt_byte[‘reg’])]]
return opt_data
def ob_write(self, name, data):
self._dbg.debug(‘PY32F0.ob_write()’)
for opt_byte in self.OPT_BYTES:
if opt_byte[‘name’] == name:
reg = opt_byte[‘reg’]
self._dbg.debug(‘Programming Option Byte %s (addr:0x%08x, data:0x%04x)’
%(name, reg, data))
flash = Flash(self, self._stlink, self._dbg)
flash.unlock_nvm()
flash.unlock_ob()
self._stlink.set_debugreg32(reg, data)
flash.set_operation(flash.FLASH_CR_EOPIE | flash.FLASH_CR_OPTSTRT)
self._stlink.set_mem32(flash.FLASH_OPT_WRITE, [0xFF, 0xFF, 0xFF, 0xFF])
flash.wait_busy(0.005)
if flash.get_operation_result() == flash.OPR_ERROR:
raise lib.stlinkex.StlinkException(‘Writing Option Byte %s failed’
%(name))
flash.lock_nvm()
print(‘Option Byte %s programmed successfully. Reseting MCU’ %(name))
flash.set_operation(flash.FLASH_CR_OBL_LAUNCH)
return
raise lib.stlinkex.StlinkException(‘Programming Option Byte %s is not implemented ‘
‘for this MCU’ %(name))
Listing 5. Funkcje odczytu i zapisu ustawień konfiguracyjnych Option Bytes
Odczyt ustawień konfiguracyjnych układu ogranicza się do pobrania zawartości rejestrów kontrolera pamięci Flash (zawierających kopie danych konfiguracyjnych) – i ich wyświetlenia. Funkcja ta w programie pyStlink została podpięta pod parametr wywołania programu:
Wynik jej działania pokazuje rysunek 13.
Zapis nowych ustawień konfiguracyjnych mikrokontrolera polega na ich wpisaniu do odpowiedniego rejestru Option Bytes w kontrolerze pamięci Flash (tj. rejestru FLASH_OPTR – w przypadku modyfikacji ustawień użytkownika, rejestru FLASH_SDKR – w przypadku modyfikacji obszaru chronionego SKD i rejestru FLASH_WRPR – w przypadku modyfikacji ochrony sektorów przed zapisem), a następnie ustawieniu bitu operacji OPTSTRT w rejestrze kontrolnym FLASH_CR i zapisie 32-bitowego słowa pod adres 0x4002 2080. Operacja ta inicjuje skopiowanie bitów b15...b0 z rejestrów Option Bytes w kontrolerze Flash do powiązanych z nimi komórek pamięci systemowej Flash oraz ustawienie w tych komórkach zanegowanych wartości bitów na pozycjach b31...b16. Zakończenie procesu jest sygnalizowane przez bit końca operacji (EOP) w rejestrze stanu FLASH_SR. Nowe ustawienia zostają zapisane w komórkach pamięci systemowej Flash, ale pozostają nieaktywne – są uwzględniane w pracy mikrokontrolera dopiero po ponownym włączeniu zasilania układu (tj. resecie typu POR lub BOR). Drugim sposobem aktywacji wprowadzonych zmian w ustawieniach konfiguracyjnych układu jest ustawienie bitu OBL_LAUNCH w rejestrze kontrolnym FLASH_CR. Wymusza to restart mikrokontrolera z załadowaniem nowej konfiguracji.
Rejestry Option Bytes w kontrolerze pamięci Flash są dodatkowo chronione przed modyfikacją. Dlatego też przed ich zapisem konieczne jest najpierw odblokowanie dostępu do modyfikacji rejestrów kontrolera pamięci Flash, jak zostało to zaprezentowane przy zapisie i kasowaniu pamięci, a następnie odblokowanie dostępu do modyfikacji rejestrów Option Bytes przez wpisanie do rejestru FLASH_OPTKEYR sekwencji kodów 0x0819 2A3B i 0x4C5D 6E7F, co w sterowniku realizuje funkcja unlock_ob().
Do programowania ustawień konfiguracyjnych mikrokontrolera lista opcji wywołania programu pyStlink została rozszerzona o dodatkowy parametr:
Wynik operacji zmiany ochrony sektorów pamięci Flash przed zapisem pokazuje rysunek 14, natomiast w ramce zebrano pełną listę opcji wywołania programu pyStlink, uwzględniającą opisane w artykule modyfikacje.
Podsumowanie
Egzotyczny na pierwszy rzut oka mikrokontroler TW32F003 okazał się dość dobrze znanym i udokumentowanym układem typu PY32F003F1P6 firmy Puya Semiconductor. A dzięki programatorowi-debuggerowi STLink, obsługiwanemu przez zmodyfikowany program pyStlink, można przetestować ten interesujący układ (oraz inne mikrokontrolery z rodziny PY32F0), bez konieczności inwestowania w dodatkowe narzędzia.
Aleksander Borysiuk
alex_priv@wp.pl
Źródła:
- Jay Carlson, „The Cheapest Flash Microcontroller You Can Buy Is Actually an ARM Cortex M0+”, https://jaycarlson.net
- Mikrokontrolery PY32F002/PY32F003/PY32F030: https://www.puyasemi.com/py32_xilie.html#common
- Mikrokontrolery PY32: https://py32.org
- Aleksander Borysiuk, „Programowanie mikrokontrolerów HC32F003/HC32F005 za pomocą programatora ST-Link”, „Elektronika Praktyczna” 9/2022
- Program pyStlink: https://github.com/pavelrevak/pyStlink