Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

В część pierwsza Próbowałem powiedzieć hobbystycznym inżynierom elektronikom, którzy wyrośli ze spodni Arduino, jak i dlaczego powinni czytać arkusze danych i inną dokumentację mikrokontrolerów. Tekst okazał się duży, dlatego obiecałem pokazać praktyczne przykłady w osobnym artykule. No cóż, nazwał siebie grzybem mlecznym...

Dzisiaj pokażę Wam jak wykorzystać arkusze danych do rozwiązania dość prostych, ale niezbędnych w wielu projektach zadań na kontrolerach STM32 (Blue Pill) i STM8. Wszystkie projekty demonstracyjne poświęcone są moim ulubionym diodom LED, będziemy je oświetlać w dużych ilościach, do czego będziemy musieli użyć najróżniejszych ciekawych urządzeń peryferyjnych.

Tekst znów okazał się ogromny, więc dla wygody tworzę treść:

STM32 Blue Pill: 16 diod LED ze sterownikiem DM634
STM8: Konfiguracja sześciu pinów PWM
STM8: 8 diod LED RGB na trzech pinach, przerwania

Zastrzeżenie: nie jestem inżynierem, nie udaję, że mam głęboką wiedzę z zakresu elektroniki, artykuł jest przeznaczony dla amatorów takich jak ja. Tak naprawdę dwa lata temu uważałem siebie za grupę docelową. Gdyby ktoś mi wtedy powiedział, że arkusze danych na nieznanym chipie nie są straszne w czytaniu, nie spędziłbym dużo czasu na szukaniu fragmentów kodu w Internecie i wymyślaniu kul z nożyczkami i taśmą klejącą.

W tym artykule skupiono się na arkuszach danych, a nie na projektach, więc kod może nie być zbyt schludny i często ciasny. Same projekty są bardzo proste, choć nadają się do pierwszego zapoznania się z nowym chipem.

Mam nadzieję, że mój artykuł pomoże komuś na podobnym etapie zanurzenia się w hobby.

STM32

16 diod LED z DM634 i SPI

Mały projekt wykorzystujący Blue Pill (STM32F103C8T6) i sterownik LED DM634. Korzystając z arkuszy danych, wymyślimy sterownik, porty STM IO i skonfigurujemy SPI.

DM634

Tajwański chip z 16 16-bitowymi wyjściami PWM, można łączyć w łańcuchy. Low-endowy model 12-bitowy znany jest z krajowego projektu Pakiet świetlny. Swego czasu wybierając pomiędzy DM63x a znanym TLC5940 wybrałem DM z kilku powodów: 1) TLC na Aliexpress jest na pewno podróbką, ale ten nie jest; 2) DM posiada autonomiczny PWM z własnym generatorem częstotliwości; 3) można go było kupić niedrogo w Moskwie, zamiast czekać na paczkę od Alego. I oczywiście ciekawie było nauczyć się samodzielnie sterować chipem, zamiast korzystać z gotowej biblioteki. Chipy są obecnie prezentowane głównie w pakiecie SSOP24, można je łatwo przylutować do adaptera.

Ponieważ producent jest Tajwańczykiem, arkusz danych chip jest napisany w języku chińskim, co oznacza, że ​​będzie fajnie. Najpierw patrzymy na pinout (Połączenie pinowe), aby zrozumieć, do której nóżki co podłączyć, oraz opis pinów (Opis pinów). 16 pinów:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Źródła ujścia prądu stałego (otwarty dren)

Tonąć / Wyjście otwartego drenażu - odpływ; źródło napływającego prądu; w stanie aktywnym wyjście zwarte jest do masy - diody LED połączone są ze sterownikiem za pomocą katod. Elektrycznie nie jest to oczywiście „otwarty odpływ” (otwarty Spływ), ale w arkuszach danych często można znaleźć to oznaczenie pinów w trybie drenażu.

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Zewnętrzne rezystory pomiędzy REXT i GND do ustawiania wartości prądu wyjściowego

Pomiędzy pinem REXT a masą instalowany jest rezystor odniesienia, który kontroluje rezystancję wewnętrzną wyjść, patrz wykres na stronie 9 arkusza danych. W DM634 rezystancją tą można również sterować za pomocą oprogramowania, ustawiając ogólną jasność (globalna jasność); W tym artykule nie będę wchodził w szczegóły, po prostu umieszczę tutaj rezystor 2.2 - 3 kOhm.

Aby zrozumieć, jak sterować chipem, spójrzmy na opis interfejsu urządzenia:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

Tak, oto chiński angielski w całej okazałości. Tłumaczenie tego jest problematyczne, możesz to zrozumieć, jeśli chcesz, ale jest inny sposób - spójrz, jak opisano połączenie z funkcjonalnie podobnym TLC5940 w arkuszu danych:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
... Do wprowadzenia danych do urządzenia potrzebne są tylko trzy piny. Zbocze narastające sygnału SCLK przesuwa dane z pinu SIN do rejestru wewnętrznego. Po załadowaniu wszystkich danych krótki, wysoki sygnał XLAT zatrzaskuje przesyłane sekwencyjnie dane do rejestrów wewnętrznych. Rejestry wewnętrzne to bramki wyzwalane poziomem sygnału XLAT. Wszystkie dane przesyłane są w pierwszej kolejności najbardziej znaczącym bitem.

Zatrzask – zatrzask/zatrzask/zamek.
Rosnąca krawędź – zbocze natarcia impulsu
MSB na pierwszym miejscu – najbardziej znaczący (skrajnie lewy) bit do przodu.
do danych zegara – przesyłać dane sekwencyjnie (bit po bicie).

Słowo zatrzask często występuje w dokumentacji chipów i jest tłumaczony na różne sposoby, więc dla zrozumienia pozwolę sobie

mały program edukacyjnySterownik LED jest zasadniczo rejestrem przesuwnym. "Zmiana" (przesunięcie) w nazwie - bitowy ruch danych wewnątrz urządzenia: każdy nowy bit włożony do środka wypycha przed siebie cały łańcuch do przodu. Ponieważ nikt nie chce obserwować chaotycznego mrugania diod LED podczas zmiany, proces odbywa się w rejestrach buforowych oddzielonych tłumikiem od rejestrów roboczych (zatrzask) to rodzaj poczekalni, w której bity układane są w żądanej kolejności. Kiedy wszystko jest już gotowe, migawka otwiera się i bity zabierają się do pracy, zastępując poprzednią partię. Słowo zatrzask w dokumentacji mikroukładów prawie zawsze oznacza taki tłumik, bez względu na to, w jakich kombinacjach jest używany.

Zatem przesyłanie danych do DM634 odbywa się w następujący sposób: ustaw wejście DAI na wartość najbardziej znaczącego bitu dalekiej diody LED, pociągnij DCK w górę i w dół; ustaw wejście DAI na wartość następnego bitu, pociągnij DCK; i tak dalej, aż wszystkie bity zostaną przesłane (taktowany w), po czym wyciągamy LAT. Można to zrobić ręcznie (trochę huk), ale lepiej jest użyć specjalnie do tego dostosowanego interfejsu SPI, ponieważ jest on prezentowany na naszym STM32 w dwóch egzemplarzach.

Niebieska pigułka STM32F103

Wprowadzenie: Kontrolery STM32 są znacznie bardziej złożone niż Atmega328, niż mogłoby się wydawać przerażające. Co więcej, ze względu na oszczędność energii, prawie wszystkie urządzenia peryferyjne są na początku wyłączone, a częstotliwość taktowania wynosi 8 MHz ze źródła wewnętrznego. Na szczęście programiści STM napisali kod, który podkręca chip do „obliczonych” 72 MHz, a autorzy wszystkich znanych mi IDE uwzględnili go w procedurze inicjalizacji, więc nie musimy taktować (ale możesz, jeśli naprawdę chcesz). Ale będziesz musiał włączyć urządzenia peryferyjne.

Dokumentacja: Blue Pill wyposażony jest w popularny układ STM32F103C8T6, są do niego dwa przydatne dokumenty:

W arkuszu danych możemy być zainteresowani:

  • Pinouty – chipouty – w przypadku, gdy sami zdecydujemy się na wykonanie płytek;
  • Memory Map – mapa pamięci dla konkretnego chipa. Podręcznik referencyjny zawiera mapę całej linii i wspomina o rejestrach, których nasz nie ma.
  • Tabela definicji pinów – lista głównych i alternatywnych funkcji pinów; dla „niebieskiej pigułki” wygodniejsze zdjęcia można znaleźć w Internecie z listą pinów i ich funkcjami. Dlatego natychmiast wyszukujemy w Google pinout Blue Pill i trzymamy to zdjęcie pod ręką:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Uwaga: w obrazie z Internetu pojawił się błąd, co zostało odnotowane w komentarzach, dziękuję za to. Zdjęcie zostało podmienione, ale to lekcja - lepiej sprawdzać informacje, a nie z datasheetów.

Usuwamy arkusz danych, otwieramy Podręcznik referencyjny i odtąd używamy tylko tego.
Procedura: zajmujemy się standardowym wejściem/wyjściem, konfigurujemy SPI, włączamy niezbędne peryferia.

Wejście wyjście

W Atmega328 wejścia/wyjścia są zaimplementowane niezwykle prosto, dlatego mnogość opcji STM32 może być myląca. Teraz potrzebujemy tylko wniosków, ale nawet one mają cztery opcje:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
otwarty spust, push-pull, alternatywny push-pull, alternatywny otwarty spust

"Ciągnąć pchać" (pchać ciągnąć) jest zwykłym wyjściem Arduino, pin może przyjmować wartość WYSOKĄ lub NISKĄ. Ale w przypadku „otwartego odpływu” tak jest trudności, chociaż tak naprawdę tutaj wszystko jest proste:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Konfiguracja wyjścia / gdy port jest przypisany do wyjścia: / bufor wyjściowy włączony: / – tryb otwartego drenażu: „0” w rejestrze wyjściowym włącza N-MOS, „1” w rejestrze wyjściowym opuszcza port w tryb Hi-Z ( P-MOS nie jest aktywowany) / – tryb push-pull: „0” w rejestrze wyjściowym aktywuje N-MOS, „1” w rejestrze wyjściowym aktywuje P-MOS.

Cała różnica między otwartym odpływem (otwarty Spływ) z „push-pull” (pchać ciągnąć) polega na tym, że na pierwszym pinie nie może przyjąć stanu HIGH: przy zapisie jednego do rejestru wyjściowego przechodzi w tryb wysokiej rezystancji (wysoka impedancja, Cześć-Z). Przy zapisie zera pin zachowuje się tak samo w obu trybach, zarówno logicznie, jak i elektrycznie.

W normalnym trybie wyjściowym pin po prostu rozgłasza zawartość rejestru wyjściowego. W „alternatywie” sterowanie odbywa się za pomocą odpowiednich urządzeń peryferyjnych (patrz 9.1.4):

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Jeśli bit portu jest skonfigurowany jako pin z funkcją alternatywną, rejestr pinów jest wyłączony, a pin jest podłączony do pinu urządzenia peryferyjnego.

Alternatywna funkcjonalność każdego pinu jest opisana w Definicje pinów Arkusz danych znajduje się na pobranym obrazie. Na pytanie co zrobić jeśli pin ma kilka alternatywnych funkcji odpowiedź daje przypis w datasheet:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Jeśli wiele urządzeń peryferyjnych korzysta z tego samego pinu, aby uniknąć konfliktu między alternatywnymi funkcjami, należy używać tylko jednego urządzenia peryferyjnego na raz, przełączanego za pomocą bitu włączającego zegar peryferyjny (w odpowiednim rejestrze RCC).

Wreszcie, piny w trybie wyjściowym mają również prędkość zegara. To kolejna funkcja oszczędzania energii; w naszym przypadku po prostu ustawiamy ją na maksimum i zapominamy o tym.

A więc: używamy SPI, co oznacza, że ​​dwa piny (z danymi i sygnałem zegarowym) powinny pełnić „alternatywną funkcję push-pull”, a drugi (LAT) „zwykły push-pull”. Zanim jednak je przypiszemy, zajmijmy się SPI.

SPI

Kolejny mały program edukacyjny

SPI lub Serial Peripheral Interface (szeregowy interfejs peryferyjny) to prosty i bardzo skuteczny interfejs do łączenia MK z innymi MK i ogólnie ze światem zewnętrznym. Zasada jego działania została już opisana powyżej, gdzie mowa o chińskim sterowniku LED (w instrukcji obsługi patrz rozdział 25). SPI może pracować w trybie master („master”) i slave („slave”). SPI posiada cztery podstawowe kanały, z których nie wszystkie można wykorzystać:

  • MOSI, wyjście główne / wejście slave: ten pin przesyła dane w trybie master i odbiera dane w trybie slave;
  • MISO, wejście główne / wyjście podrzędne: przeciwnie, odbiera w urządzeniu głównym i transmituje w urządzeniu podrzędnym;
  • SCK, Serial Clock: ustawia częstotliwość transmisji danych w urządzeniu master lub odbiera sygnał zegara w urządzeniu slave. Zasadniczo uderzające rytmy;
  • SS, Slave Select: za pomocą tego kanału niewolnik wie, że czegoś się od niego chce. W STM32 nazywa się to NSS, gdzie N = ujemne, tj. sterownik staje się urządzeniem podrzędnym, jeśli w tym kanale jest masa. Dobrze łączy się z trybem wyjścia Open Drain, ale to już inna historia.

Jak wszystko inne, SPI w STM32 jest bogate w funkcjonalność, co sprawia, że ​​jest nieco trudne do zrozumienia. Może na przykład współpracować nie tylko z SPI, ale także z interfejsem I2S, a w dokumentacji ich opisy są pomieszane, nadmiar należy w odpowiednim czasie odciąć. Nasze zadanie jest niezwykle proste: wystarczy przesłać dane korzystając wyłącznie z MOSI i SCK. Przechodzimy do sekcji 25.3.4 (komunikacja półdupleksowa, komunikacja półdupleksowa), gdzie znajdujemy 1 zegar i 1 jednokierunkowy przewód danych (1 sygnał zegarowy i 1 jednokierunkowy strumień danych):

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
W tym trybie aplikacja korzysta z SPI w trybie tylko do transmisji lub tylko do odbioru. / Tryb tylko nadawania jest podobny do trybu dupleksu: dane są przesyłane na pinie nadawczym (MOSI w trybie master lub MISO w trybie slave), a pin odbiorczy (odpowiednio MISO lub MOSI) może być używany jako zwykły pin we/wy . W takim przypadku aplikacja musi jedynie zignorować bufor Rx (jeśli zostanie odczytany, nie będą tam przesyłane żadne dane).

Świetnie, pin MISO jest wolny, podłączmy do niego sygnał LAT. Przyjrzyjmy się Slave Select, którym na STM32 można sterować programowo, co jest niezwykle wygodne. Akapit o tej samej nazwie czytamy w sekcji 25.3.1 Opis ogólny SPI:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Sterowanie programowe NSS (SSM = 1) / Informacja o wyborze urządzenia Slave zawarta jest w bicie SSI rejestru SPI_CR1. Zewnętrzny pin NSS pozostaje wolny dla innych zastosowań.

Czas napisać do rejestrów. Zdecydowałem się na SPI2, poszukaj jego adresu bazowego w datasheet - w rozdziale 3.3 Mapa pamięci:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

Cóż, zacznijmy:

#define _SPI2_(mem_offset) (*(volatile uint32_t *)(0x40003800 + (mem_offset)))

Otwórz sekcję 25.3.3 o zrozumiałym tytule „Konfigurowanie SPI w trybie głównym”:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

1. Ustaw częstotliwość zegara szeregowego bitami BR[2:0] w rejestrze SPI_CR1.

Rejestry są zebrane w podręczniku referencyjnym, o tej samej nazwie. Zmiana adresu (Przesunięcie adresu) dla CR1 – 0x00, domyślnie wszystkie bity są wyczyszczone (Zresetuj wartość 0x0000):

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

Bity BR ustawiają dzielnik zegara kontrolera, określając w ten sposób częstotliwość, z jaką będzie działać SPI. Nasza częstotliwość STM32 będzie wynosić 72 MHz, sterownik LED zgodnie z jego arkuszem danych pracuje z częstotliwością do 25 MHz, więc musimy podzielić przez cztery (BR[2:0] = 001).

#define _SPI_CR1 0x00

#define BR_0        0x0008
#define BR_1        0x0010
#define BR_2        0x0020

_SPI2_ (_SPI_CR1) |= BR_0;// pclk/4

2. Ustaw bity CPOL i CPHA, aby zdefiniować związek pomiędzy przesyłaniem danych a taktowaniem zegara szeregowego (patrz diagram na stronie 240)

Ponieważ czytamy tutaj arkusz danych, a nie patrzymy na schematy, przyjrzyjmy się bliżej opisowi tekstowemu bitów CPOL i CPHA na stronie 704 (Ogólny opis SPI):

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Faza zegara i polaryzacja
Używając bitów CPOL i CPHA rejestru SPI_CR1, można programowo wybrać cztery zależności taktowania. Bit CPOL (polaryzacja zegara) kontroluje stan sygnału zegara, gdy nie są przesyłane żadne dane. Ten bit kontroluje tryby master i slave. Jeśli CPOL zostanie zresetowany, pin SCK jest w stanie spoczynku. Jeśli bit CPOL jest ustawiony, pin SCK jest w stanie wysokim w trybie spoczynku.
Gdy bit CPHA (faza zegara) jest ustawiony, wysoki strob pułapki bitowej jest drugim zboczem sygnału SCK (opada, jeśli CPOL jest czysty, rośnie, jeśli CPOL jest ustawiony). Dane są przechwytywane przy drugiej zmianie sygnału zegarowego. Jeśli bit CPHA jest czysty, wysoki strob pułapki bitowej jest zboczem narastającym sygnału SCK (zbocze opadające, jeśli ustawiono CPOL, zbocze narastające, jeśli CPOL jest wyczyszczone). Dane są rejestrowane przy pierwszej zmianie sygnału zegara.

Po przyswojeniu tej wiedzy dochodzimy do wniosku, że oba bity muszą pozostać zerami, ponieważ Chcemy, aby sygnał SCK pozostawał niski, gdy nie jest używany, a dane były przesyłane na narastającym zboczu impulsu (patrz rys. Rosnąca krawędź w arkuszu danych DM634).

Nawiasem mówiąc, tutaj po raz pierwszy zetknęliśmy się z cechą słownictwa w arkuszach danych ST: w nich zapisano frazę „zresetuj bit do zera” trochę zresetowaćI nie trochę oczyścić, jak na przykład Atmega.

3. Ustaw bit DFF, aby określić, czy blok danych jest w formacie 8-bitowym, czy 16-bitowym

Specjalnie wziąłem 16-bitowy DM634, aby nie zawracać sobie głowy przesyłaniem 12-bitowych danych PWM, takich jak DM633. Sensowne jest ustawienie DFF na jeden:

#define DFF         0x0800

_SPI2_ (_SPI_CR1) |= DFF; // 16-bit mode

4. Skonfiguruj bit LSBFIRST w rejestrze SPI_CR1, aby określić format bloku

LSBFIRST, jak sama nazwa wskazuje, konfiguruje transmisję od najmniej znaczącego bitu jako pierwszego. Ale DM634 chce odbierać dane zaczynając od najbardziej znaczącego bitu. Dlatego zostawiamy to zresetowane.

5. W trybie sprzętowym, jeśli wymagane jest wejście z pinu NSS, podaj wysoki sygnał na pin NSS podczas całej sekwencji przesyłania bajtów. W trybie oprogramowania NSS ustaw bity SSM i SSI w rejestrze SPI_CR1. Jeżeli pin NSS ma być używany jako wyjście, należy ustawić jedynie bit SSOE.

Zainstaluj SSM i SSI, aby zapomnieć o trybie sprzętowym NSS:

#define SSI         0x0100
#define SSM         0x0200

_SPI2_ (_SPI_CR1) |= SSM | SSI; //enable software control of SS, SS high

6. Należy ustawić bity MSTR i SPE (pozostają ustawione tylko wtedy, gdy sygnał NSS jest wysoki)

Właściwie za pomocą tych bitów wyznaczamy nasz SPI jako master i włączamy go:

#define MSTR        0x0004
#define SPE         0x0040

_SPI2_ (_SPI_CR1) |= MSTR; //SPI master
//когда все готово, включаем SPI
_SPI2_ (_SPI_CR1) |= SPE;

SPI skonfigurowane, napiszmy od razu funkcje wysyłające bajty do sterownika. Kontynuuj czytanie 25.3.3 „Konfiguracja SPI w trybie głównym”:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Zamówienie przeniesienia danych
Transmisja rozpoczyna się w momencie zapisania bajtu do bufora Tx.
Bajt danych jest ładowany do rejestru przesuwnego pod adresem równoległy trybie (z magistrali wewnętrznej) podczas transmisji pierwszego bitu, po czym jest on przesyłany do sekwencyjny Tryb pin MOSI, pierwszy lub ostatni bit do przodu w zależności od ustawienia bitu LSBFIRST w rejestrze CPI_CR1. Flaga TXE jest ustawiana po transmisji danych z bufora Tx do rejestru przesuwnego, a także generuje przerwanie, jeśli ustawiony jest bit TXEIE w rejestrze CPI_CR1.

Podkreśliłem kilka słów w tłumaczeniu, aby zwrócić uwagę na jedną cechę implementacji SPI w kontrolerach STM. Na Atmedze flaga TXE (Tx pusty, Tx jest pusty i gotowy do odbioru danych) jest ustawiany dopiero po wysłaniu całego bajtu zewnętrzny. I tutaj ta flaga jest ustawiana po wstawieniu bajtu do wewnętrznego rejestru przesuwnego. Ponieważ jest tam umieszczany ze wszystkimi bitami jednocześnie (równolegle), a następnie dane są przesyłane sekwencyjnie, wartość TXE jest ustawiana przed całkowitym wysłaniem bajtu. Jest to ważne, ponieważ w przypadku naszego sterownika LED po wysłaniu musimy wyciągnąć pin LAT wszystko dane, tj. Sama flaga TXE nam nie wystarczy.

Oznacza to, że potrzebujemy kolejnej flagi. Spójrzmy na 25.3.7 – „Flagi stanu”:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
<…>
Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Flaga ZAJĘTY
Flaga BSY jest ustawiana i kasowana sprzętowo (zapis do niej nie przynosi żadnego efektu). Flaga BSY wskazuje stan warstwy komunikacyjnej SPI.
Resetuje się:
po zakończeniu transferu (z wyjątkiem trybu głównego, jeśli transfer ma charakter ciągły)
gdy SPI jest wyłączone
gdy wystąpi błąd trybu głównego (MODF=1)
Jeśli transfer nie jest ciągły, flaga BSY jest kasowana pomiędzy każdym transferem danych

OK, to się przyda. Dowiedzmy się, gdzie znajduje się bufor Tx. Aby to zrobić, przeczytaj „Rejestr danych SPI”:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Bity 15:0 DR[15:0] Rejestr danych
Dane odebrane lub dane, które mają zostać przesłane.
Rejestr danych podzielony jest na dwa bufory - jeden do zapisu (bufor transmisji) i jeden do odczytu (bufor odbioru). Zapis do rejestru danych powoduje zapis do bufora Tx, a odczyt z rejestru danych zwróci wartość zawartą w buforze Rx.

No i rejestr stanu, w którym znajdują się flagi TXE i BSY:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

Piszemy:

#define _SPI_DR  0x0C
#define _SPI_SR  0x08

#define BSY         0x0080
#define TXE         0x0002

void dm_shift16(uint16_t value)
{
    _SPI2_(_SPI_DR) = value; //send 2 bytes
    while (!(_SPI2_(_SPI_SR) & TXE)); //wait until they're sent
}

Cóż, ponieważ musimy przesłać 16 razy dwa bajty, w zależności od liczby wyjść sterownika LED, coś takiego:

void sendLEDdata()
{
    LAT_low();
    uint8_t k = 16;
    do
    {   k--;
        dm_shift16(leds[k]);
    } while (k);

    while (_SPI2_(_SPI_SR) & BSY); // finish transmission

    LAT_pulse();
}

Ale nie wiemy jeszcze, jak wyciągnąć pin LAT, więc wrócimy do I/O.

Przypisywanie pinów

W STM32F1 rejestry odpowiedzialne za stan pinów są dość nietypowe. Wiadomo, że jest ich więcej niż Atmegi, ale różnią się też od pozostałych chipów STM. Sekcja 9.1 Ogólny opis GPIO:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Każdy z portów we/wy ogólnego przeznaczenia (GPIO) posiada dwa 32-bitowe rejestry konfiguracyjne (GPIOx_CRL i GPIOx_CRH), dwa 32-bitowe rejestry danych (GPIOx_IDR i GPIOx_ODR), 32-bitowy rejestr ustawiania/resetowania (GPIOx_BSRR), 16-bitowy rejestr resetowania (GPIOx_BRR) i 32-bitowy rejestr rejestr blokujący bity (GPIOx_LCKR).

Pierwsze dwa rejestry są nietypowe, a także dość niewygodne, ponieważ 16 pinów portu jest w nich rozproszonych w formacie „cztery bity na brata”. Te. piny od zera do siódmego znajdują się w CRL, a reszta w CRH. Jednocześnie pozostałe rejestry z powodzeniem zawierają bity wszystkich pinów portu – często pozostając w połowie „zarezerwowane”.

Dla uproszczenia zacznijmy od końca listy.

Nie potrzebujemy rejestru blokującego.

Rejestry set i reset są dość zabawne, ponieważ częściowo się duplikują: wszystko możesz zapisać tylko w BSRR, gdzie wyższe 16 bitów zresetuje pin do zera, a dolne zostaną ustawione na 1, lub możesz też użyj BRR, którego dolne 16 bitów resetuje tylko pin. Podoba mi się druga opcja. Rejestry te są ważne, ponieważ zapewniają atomowy dostęp do pinów:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Zestaw atomowy lub reset
Nie ma potrzeby wyłączania przerwań podczas programowania GPIOx_ODR na poziomie bitów: jeden lub więcej bitów można zmienić za pomocą pojedynczej atomowej operacji zapisu APB2. Osiąga się to poprzez wpisanie „1” do rejestru set/reset (GPIOx_BSRR lub, tylko w przypadku resetu, GPIOx_BRR) bitu, który należy zmienić. Pozostałe bity pozostaną niezmienione.

Rejestry danych mają dość zrozumiałe nazwy - IDR = Wkład Rejestr kierunkowy, rejestr wejściowy; ODR = Wydajność Rejestr kierunkowy, rejestr wyjściowy. Nie będziemy ich potrzebować w bieżącym projekcie.

I wreszcie rejestry kontrolne. Ponieważ interesują nas drugie piny SPI, a mianowicie PB13, PB14 i PB15, od razu patrzymy na CRH:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

I widzimy, że będziemy musieli napisać coś w bitach od 20 do 31.

Ustaliliśmy już powyżej, czego chcemy od pinów, więc tutaj obejdzie się bez zrzutu ekranu, powiem tylko, że MODE określa kierunek (wejście, jeśli oba bity są ustawione na 0) i prędkość pinów (potrzebujemy 50 MHz, tj. oba piny na „1”), a CNF ustawia tryb: zwykły „push-pull” – 00, „alternatywny” – 10. Domyślnie, jak widzimy powyżej, wszystkie piny mają trzeci bit od dołu (CNF0), ustawia je w tryb wejście pływające.

Ponieważ planuję zrobić z tym chipem coś innego, dla uproszczenia zdefiniowałem wszystkie możliwe wartości MODE i CNF zarówno dla dolnego, jak i górnego rejestru sterującego.

Jakoś tak

#define CNF0_0 0x00000004
#define CNF0_1 0x00000008
#define CNF1_0 0x00000040
#define CNF1_1 0x00000080
#define CNF2_0 0x00000400
#define CNF2_1 0x00000800
#define CNF3_0 0x00004000
#define CNF3_1 0x00008000
#define CNF4_0 0x00040000
#define CNF4_1 0x00080000
#define CNF5_0 0x00400000
#define CNF5_1 0x00800000
#define CNF6_0 0x04000000
#define CNF6_1 0x08000000
#define CNF7_0 0x40000000
#define CNF7_1 0x80000000
#define CNF8_0 0x00000004
#define CNF8_1 0x00000008
#define CNF9_0 0x00000040
#define CNF9_1 0x00000080
#define CNF10_0 0x00000400
#define CNF10_1 0x00000800
#define CNF11_0 0x00004000
#define CNF11_1 0x00008000
#define CNF12_0 0x00040000
#define CNF12_1 0x00080000
#define CNF13_0 0x00400000
#define CNF13_1 0x00800000
#define CNF14_0 0x04000000
#define CNF14_1 0x08000000
#define CNF15_0 0x40000000
#define CNF15_1 0x80000000

#define MODE0_0 0x00000001
#define MODE0_1 0x00000002
#define MODE1_0 0x00000010
#define MODE1_1 0x00000020
#define MODE2_0 0x00000100
#define MODE2_1 0x00000200
#define MODE3_0 0x00001000
#define MODE3_1 0x00002000
#define MODE4_0 0x00010000
#define MODE4_1 0x00020000
#define MODE5_0 0x00100000
#define MODE5_1 0x00200000
#define MODE6_0 0x01000000
#define MODE6_1 0x02000000
#define MODE7_0 0x10000000
#define MODE7_1 0x20000000
#define MODE8_0 0x00000001
#define MODE8_1 0x00000002
#define MODE9_0 0x00000010
#define MODE9_1 0x00000020
#define MODE10_0 0x00000100
#define MODE10_1 0x00000200
#define MODE11_0 0x00001000
#define MODE11_1 0x00002000
#define MODE12_0 0x00010000
#define MODE12_1 0x00020000
#define MODE13_0 0x00100000
#define MODE13_1 0x00200000
#define MODE14_0 0x01000000
#define MODE14_1 0x02000000
#define MODE15_0 0x10000000
#define MODE15_1 0x20000000

Nasze piny znajdują się na porcie B (adres bazowy – 0x40010C00), kod:

#define _PORTB_(mem_offset) (*(volatile uint32_t *)(0x40010C00 + (mem_offset)))

#define _BRR  0x14
#define _BSRR 0x10
#define _CRL  0x00
#define _CRH  0x04

//используем стандартный SPI2: MOSI на B15, CLK на B13
//LAT пусть будет на неиспользуемом MISO – B14

//очищаем дефолтный бит, он нам точно не нужен
_PORTB_ (_CRH) &= ~(CNF15_0 | CNF14_0 | CNF13_0 | CNF12_0);

//альтернативные функции для MOSI и SCK
_PORTB_ (_CRH) |= CNF15_1 | CNF13_1;

//50 МГц, MODE = 11
_PORTB_ (_CRH) |= MODE15_1 | MODE15_0 | MODE14_1 | MODE14_0 | MODE13_1 | MODE13_0;

I odpowiednio możesz napisać definicje LAT, które będą drgane przez rejestry BRR i BSRR:

/*** LAT pulse – high, then low */
#define LAT_pulse() _PORTB_(_BSRR) = (1<<14); _PORTB_(_BRR) = (1<<14)

#define LAT_low() _PORTB_(_BRR) = (1<<14)

(LAT_low tylko przez bezwładność, zawsze tak było, niech tak zostanie)

Teraz wszystko jest świetnie, ale nie działa. Ponieważ jest to STM32, oszczędzają energię elektryczną, co oznacza, że ​​należy włączyć taktowanie wymaganych urządzeń peryferyjnych.

Włącz taktowanie

Za odmierzanie czasu odpowiada zegarek, zwany także zegarem. I już mogliśmy zauważyć skrót RCC. Szukamy tego w dokumentacji: jest to Resetowanie i kontrola zegara.

Tak jak pisałem wyżej, na szczęście najtrudniejszą część tematu taktowania wykonali za nas ludzie z STM, za co im bardzo dziękujemy (jeszcze raz podam link do strona Di Halta, aby było jasne, jak bardzo jest to mylące). Potrzebujemy jedynie rejestrów odpowiedzialnych za umożliwienie taktowania peryferyjnego (Peripheral Clock Enable Registers). Najpierw znajdźmy adres bazowy RCC, znajduje się on na samym początku „Mapy pamięci”:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

#define _RCC_(mem_offset) (*(volatile uint32_t *)(0x40021000 + (mem_offset)))

A potem albo kliknij link, w którym próbujesz znaleźć coś w płytce, albo, znacznie lepiej, przejdź przez opisy rejestrów włączających z działów o włączyć rejestry. Gdzie znajdziemy RCC_APB1ENR i RCC_APB2ENR:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

Zawierają one zatem bity obejmujące taktowanie SPI2, IOPB (port I/O B) i funkcje alternatywne (AFIO).

#define _APB2ENR 0x18
#define _APB1ENR 0x1C

#define IOPBEN 0x0008
#define SPI2EN 0x4000
#define AFIOEN 0x0001

//включаем тактирование порта B и альт. функций
_RCC_(_APB2ENR) |= IOPBEN | AFIOEN;

//включаем  тактирование SPI2
_RCC_(_APB1ENR) |= SPI2EN;

Ostateczny kod można znaleźć tutaj.

Jeśli masz możliwość i chęć przetestowania to podłącz DM634 w ten sposób: DAI do PB15, DCK do PB13, LAT do PB14. Zasilamy sterownik z 5 woltów, nie zapomnij podłączyć masy.

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

STM8 PWM

PWM na STM8

Kiedy dopiero planowałem ten artykuł, postanowiłem jako przykład spróbować opanować jakąś funkcjonalność nieznanego chipa, korzystając jedynie z arkusza danych, aby nie skończyć z szewcem bez butów. STM8 idealnie sprawdził się w tej roli: po pierwsze miałem kilka chińskich płytek z STM8S103, a po drugie jest mało popularny, dlatego pokusa, aby poczytać i znaleźć rozwiązanie w Internecie opiera się na braku tych właśnie rozwiązań.

Chip też ma arkusz danych и instrukcja obsługi RM0016, w pierwszym znajdują się adresy pinów i rejestrów, w drugim wszystko inne. STM8 jest zaprogramowany w C w okropnym IDE Rozwój wizualny ST.

Taktowanie i wejścia/wyjścia

Domyślnie STM8 działa na częstotliwości 2 MHz, należy to natychmiast skorygować.

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Zegar HSI (High Speed ​​​​Internal).
Sygnał zegara HSI pochodzi z wewnętrznego oscylatora RC 16 MHz z programowalnym dzielnikiem (1 do 8). Jest on ustawiony w rejestrze dzielnika zegara (CLK_CKDIVR).
Uwaga: na początku jako główne źródło sygnału zegarowego wybierany jest oscylator HSI RC z dzielnikiem 8.

Znajdujemy adres rejestru w arkuszu danych, opis w refman i widzimy, że rejestr wymaga wyczyszczenia:

#define CLK_CKDIVR *(volatile uint8_t *)0x0050C6

CLK_CKDIVR &= ~(0x18);

Ponieważ zamierzamy uruchomić PWM i podłączyć diody LED, spójrzmy na pinout:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

Układ jest niewielki, wiele funkcji zawieszonych jest na tych samych pinach. To, co jest w nawiasach kwadratowych, to „alternatywna funkcjonalność”, jest ona przełączana za pomocą „bajtów opcji” (bajty opcji) – coś w rodzaju bezpieczników Atmega. Możesz programowo zmienić ich wartości, ale nie jest to konieczne, ponieważ Nowa funkcjonalność jest aktywowana dopiero po ponownym uruchomieniu. Łatwiej jest użyć programu ST Visual Programmer (pobranego z Visual Develop), który może zmienić te bajty. Układ pinów pokazuje, że piny CH1 i CH2 pierwszego timera są ukryte w nawiasach kwadratowych; konieczne jest ustawienie bitów AFR1 i AFR0 w STVP, a drugi przeniesie także wyjście CH1 drugiego timera z PD4 na PC5.

Zatem 6 pinów będzie sterować diodami LED: PC6, PC7 i PC3 dla pierwszego timera, PC5, PD3 i PA3 dla drugiego.

Konfiguracja samych pinów I/O w STM8 jest prostsza i bardziej logiczna niż w STM32:

  • znany z rejestru kierunku danych Atmega DDR (Rejestr kierunku danych): 1 = wyjście;
  • pierwszy rejestr kontrolny CR1, gdy jest wyprowadzany, ustawia tryb przeciwsobny (1) lub otwarty dren (0); ponieważ podłączam diody LED do chipa za pomocą katod, zostawiam tutaj zera;
  • drugi rejestr kontrolny CR2, gdy jest wyprowadzany, ustawia prędkość zegara: 1 = 10 MHz

#define PA_DDR     *(volatile uint8_t *)0x005002
#define PA_CR2     *(volatile uint8_t *)0x005004
#define PD_DDR     *(volatile uint8_t *)0x005011
#define PD_CR2     *(volatile uint8_t *)0x005013
#define PC_DDR     *(volatile uint8_t *)0x00500C
#define PC_CR2     *(volatile uint8_t *)0x00500E

PA_DDR = (1<<3); //output
PA_CR2 |= (1<<3); //fast
PD_DDR = (1<<3); //output
PD_CR2 |= (1<<3); //fast
PC_DDR = ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //output
PC_CR2 |= ((1<<3) | (1<<5) | (1<<6) | (1<<7)); //fast

Ustawienie PWM

Najpierw zdefiniujmy pojęcia:

  • Częstotliwość PWM – częstotliwość tykania timera;
  • Automatyczne przeładowanie, AR – wartość doładowania, do której będzie odliczany timer (okres impulsu);
  • Aktualizuj wydarzenie, UEV – zdarzenie, które nastąpi, gdy licznik czasu doliczy do AR;
  • Cykl pracy PWM – cykl pracy PWM, często nazywany „współczynnikiem wypełnienia”;
  • Przechwytuj/porównaj wartość – wartość do przechwycenia/porównania, do której odliczył timer coś zrobi (w przypadku PWM odwraca sygnał wyjściowy);
  • Wstępnie załaduj wartość – wstępnie załadowana wartość. Porównaj wartość nie można zmienić podczas odliczania czasu, w przeciwnym razie cykl PWM zostanie przerwany. Dlatego nowe przesyłane wartości są umieszczane w buforze i wyciągane, gdy licznik czasu osiągnie koniec odliczania i zostanie zresetowany;
  • Wyrównane do krawędzi и Tryby wyśrodkowane – ustawienie wzdłuż krawędzi i pośrodku, takie samo jak u Atmela Szybki PWM и PWM z poprawną fazą.
  • OCiREF, wyjściowy sygnał odniesienia porównania – referencyjny sygnał wyjściowy, a właściwie to, co pojawia się na odpowiednim pinie w trybie PWM.

Jak już wynika z pinoutu, możliwości PWM mają dwa timery – pierwszy i drugi. Oba są 16-bitowe, pierwszy ma wiele dodatkowych funkcji (w szczególności może liczyć zarówno w górę, jak i w dół). Obydwa muszą działać jednakowo, dlatego zdecydowałem się zacząć od wyraźnie uboższego drugiego, aby przypadkowo nie wykorzystać czegoś, czego nie ma. Pewnym problemem jest to, że opis funkcjonalności PWM wszystkich timerów w podręczniku referencyjnym znajduje się w rozdziale o pierwszym timerze (17.5.7 Tryb PWM), więc trzeba cały czas przeskakiwać w przód i w tył w całym dokumencie.

PWM na STM8 ma istotną przewagę nad PWM na Atmega:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Wyrównane do granic PWM
Konfiguracja konta od dołu do góry
Zliczanie od dołu do góry jest aktywne, jeśli bit DIR w rejestrze TIM_CR1 jest wyczyszczony
Przykład
W przykładzie wykorzystano pierwszy tryb PWM. Sygnał odniesienia PWM OCiREF jest utrzymywany na wysokim poziomie tak długo, jak TIM1_CNT < TIM1_CCRi. W przeciwnym razie wymaga niskiego poziomu. Jeżeli wartość porównania w rejestrze TIM1_CCRi jest większa niż wartość autoloadu (rejestr TIM1_ARR), sygnał OCiREF utrzymywany jest na poziomie 1. Jeśli wartość porównania wynosi 0, OCiREF jest utrzymywany na poziomie zera....

Timer STM8 w trakcie wydarzenie aktualizacji najpierw sprawdza porównaj wartośći dopiero wtedy wytwarza sygnał odniesienia. Timer Atmegi najpierw się psuje, a potem porównuje, w wyniku czego compare value == 0 wynikiem jest igła, z którą należy sobie jakoś poradzić (na przykład programowo odwracając logikę).

Więc co chcemy zrobić: 8-bitowe PWM (AR == 255), licząc od dołu do góry, wyrównanie wzdłuż krawędzi. Ponieważ żarówki są podłączone do chipa za pomocą katod, PWM powinien wysyłać 0 (dioda LED włączona) do momentu porównaj wartość i 1 po.

O niektórych już czytaliśmy Tryb PWM, więc znajdujemy wymagany rejestr drugiego timera, wyszukując w podręczniku referencyjnym tę frazę (18.6.8 - TIMx_CCMR1):

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
110: Pierwszy tryb PWM – licząc od dołu do góry, pierwszy kanał jest aktywny, gdy TIMx_CNT < TIMx_CCR1. W przeciwnym razie pierwszy kanał jest nieaktywny. [w dalszej części dokumentu błędnie skopiowano-wklej z timera 1] 111: Drugi tryb PWM – przy liczeniu od dołu do góry pierwszy kanał jest nieaktywny, natomiast TIMx_CNT < TIMx_CCR1. W przeciwnym razie aktywny jest pierwszy kanał.

Ponieważ diody LED są połączone z MK katodami, odpowiada nam drugi tryb (pierwszy też, ale tego jeszcze nie wiemy).

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Bit 3 OC1PE: Włącz wstępne ładowanie pinu 1
0: Rejestr wstępnego ładowania na TIMx_CCR1 jest wyłączony. W każdej chwili możesz napisać do TIMx_CCR1. Nowa wartość działa natychmiast.
1: Rejestr wstępnego ładowania na TIMx_CCR1 jest włączony. Operacje odczytu/zapisu uzyskują dostęp do rejestru wstępnego ładowania. Wstępnie załadowana wartość TIMx_CCR1 jest ładowana do rejestru cienia podczas każdego zdarzenia aktualizacji.
*Uwaga: Aby tryb PWM działał prawidłowo, muszą być włączone rejestry wstępnego ładowania. W trybie jednosygnałowym nie jest to konieczne (bit OPM jest ustawiony w rejestrze TIMx_CR1).

OK, włączmy wszystko, czego potrzebujemy dla trzech kanałów drugiego timera:

#define TIM2_CCMR1 *(volatile uint8_t *)0x005307
#define TIM2_CCMR2 *(volatile uint8_t *)0x005308
#define TIM2_CCMR3 *(volatile uint8_t *)0x005309

#define PWM_MODE2   0x70 //PWM mode 2, 0b01110000
#define OCxPE       0x08 //preload enable

TIM2_CCMR1 = (PWM_MODE2 | OCxPE);
TIM2_CCMR2 = (PWM_MODE2 | OCxPE);
TIM2_CCMR3 = (PWM_MODE2 | OCxPE);

AR składa się z dwóch ośmiobitowych rejestrów, wszystko jest proste:

#define TIM2_ARRH  *(volatile uint8_t *)0x00530F
#define TIM2_ARRL  *(volatile uint8_t *)0x005310

TIM2_ARRH = 0;
TIM2_ARRL = 255;

Drugi licznik czasu może liczyć tylko od dołu do góry, wyrównanie wzdłuż krawędzi, nic nie trzeba zmieniać. Ustawmy dzielnik częstotliwości na przykład na 256. Dla drugiego timera dzielnik jest ustawiony w rejestrze TIM2_PSCR i jest potęgą dwójki:

#define TIM2_PSCR  *(volatile uint8_t *)0x00530E

TIM2_PSCR = 8;

Pozostaje tylko włączyć wnioski i sam drugi timer. Pierwszy problem rozwiązują rejestry Przechwytuj/porównaj umożliwiać: są dwa, trzy kanały rozsiane po nich asymetrycznie. Tutaj również możemy dowiedzieć się, że istnieje możliwość zmiany polaryzacji sygnału, tj. w zasadzie można było zastosować PWM Mode 1. Piszemy:

#define TIM2_CCER1 *(volatile uint8_t *)0x00530A
#define TIM2_CCER2 *(volatile uint8_t *)0x00530B

#define CC1E  (1<<0) // CCER1
#define CC2E  (1<<4) // CCER1
#define CC3E  (1<<0) // CCER2

TIM2_CCER1 = (CC1E | CC2E);
TIM2_CCER2 = CC3E;

I na koniec uruchamiamy timer w rejestrze TIMx_CR1:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

#define TIM2_CR1   *(volatile uint8_t *)0x005300

TIM2_CR1 |= 1;

Napiszmy prosty analog AnalogWrite(), który przekaże aktualne wartości do timera w celu porównania. Rejestry mają przewidywalne nazwy Przechwytywanie/porównywanie rejestrów, są dwa z nich dla każdego kanału: 8 bitów niższego rzędu w TIM2_CCRxL i bity wyższego rzędu w TIM2_CCRxH. Ponieważ stworzyliśmy 8-bitowy PWM, wystarczy zapisać tylko najmniej znaczące bity:

#define TIM2_CCR1L *(volatile uint8_t *)0x005312
#define TIM2_CCR2L *(volatile uint8_t *)0x005314
#define TIM2_CCR3L *(volatile uint8_t *)0x005316

void setRGBled(uint8_t r, uint8_t g, uint8_t b)
{
    TIM2_CCR1L = r;
    TIM2_CCR2L = g;
    TIM2_CCR3L = b;
}

Uważny czytelnik zauważy, że mamy nieco uszkodzony PWM, który nie jest w stanie wytworzyć 100% wypełnienia (przy maksymalnej wartości 255 sygnał jest odwracany na jeden cykl timera). W przypadku diod LED nie ma to znaczenia, a uważny czytelnik już domyśla się, jak to naprawić.

PWM na drugim timerze działa, przejdźmy do pierwszego.

Pierwszy timer ma dokładnie te same bity w tych samych rejestrach (z tą różnicą, że bity, które pozostały „zarezerwowane” w drugim timerze, są aktywnie wykorzystywane w pierwszym timerze do najróżniejszych zaawansowanych rzeczy). Dlatego wystarczy znaleźć adresy tych samych rejestrów w arkuszu danych i skopiować kod. No to zmień wartość dzielnika częstotliwości, bo... pierwszy timer chce otrzymać nie potęgę dwójki, ale dokładną 16-bitową wartość w dwóch rejestrach Preskaler wysoki и niski. Robimy wszystko i... pierwszy timer nie działa. O co chodzi?

Problem można rozwiązać jedynie przeglądając całą sekcję dotyczącą rejestrów sterujących timera 1, gdzie szukamy tego, którego nie ma drugi timer. Tam będzie 17.7.30 Rejestr przerwania (TIM1_BKR), gdzie jest ten bit:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Włącz wyjście główne

#define TIM1_BKR   *(volatile uint8_t *)0x00526D

TIM1_BKR = (1<<7);

To już wszystko na pewno, kod w tym samym miejscu.

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

Multipleks STM8

Multipleksowanie na STM8

Trzeci miniprojekt polega na podłączeniu ośmiu diod RGB do drugiego timera w trybie PWM i sprawieniu, by pokazywały różne kolory. Opiera się ona na koncepcji multipleksowania diod LED, która polega na tym, że jeśli diody LED będziemy włączać i wyłączać bardzo, bardzo szybko, będzie nam się wydawać, że świecą one stale (utrzymywanie wizji, bezwładność percepcji wzrokowej). Kiedyś to zrobiłem coś takiego na Arduino.

Algorytm pracy wygląda następująco:

  • podłączyłem anodę pierwszej diody LED RGB;
  • zapalił go, wysyłając niezbędne sygnały do ​​katod;
  • poczekaj do końca cyklu PWM;
  • podłączyłem anodę drugiej diody LED RGB;
  • zapalił to...

Cóż, itp. Oczywiście do prawidłowego działania wymagane jest jednoczesne podłączenie anody i „zapalenie” diody LED. No, albo prawie. W każdym razie musimy napisać kod, który wyprowadzi wartości w trzech kanałach drugiego timera, zmieni je po osiągnięciu UEV i jednocześnie zmieni aktualnie aktywną diodę RGB.

Ponieważ przełączanie diod LED odbywa się automatycznie, musimy utworzyć „pamięć wideo”, z której procedura obsługi przerwań będzie odbierać dane. To jest prosta tablica:

uint8_t colors[8][3];

Aby zmienić kolor konkretnej diody LED, wystarczy wpisać do tej tablicy wymagane wartości. A zmienna będzie odpowiadać za liczbę aktywnej diody LED

uint8_t cnt;

Demux

Do poprawnego multipleksowania potrzebny jest, co dziwne, demultiplekser CD74HC238. Demultiplekser - chip implementujący operatora sprzętowo <<. Poprzez trzy piny wejściowe (bity 0, 1 i 2) podajemy mu trzybitową liczbę X, w odpowiedzi aktywuje numer wyjścia (1<<X). Pozostałe wejścia chipa służą do skalowania całego projektu. Potrzebujemy tego chipa nie tylko ze względu na zmniejszenie liczby zajętych pinów mikrokontrolera, ale także ze względów bezpieczeństwa - aby przypadkowo nie włączyć większej liczby diod LED niż to możliwe i nie spalić MK. Chip kosztuje grosza i zawsze należy go mieć w domowej apteczce.

Nasz CD74HC238 będzie odpowiedzialny za podanie napięcia na anodę wybranej diody LED. W pełnoprawnym multipleksie dostarczałby napięcie do kolumny poprzez P-MOSFET, ale w tym demo jest to możliwe bezpośrednio, ponieważ pobiera 20 mA wg absolutne maksimum ocen w arkuszu danych. Z Karta katalogowa CD74HC238 potrzebujemy pinoutów i tej ściągawki:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
H = wysoki poziom napięcia, L = niski poziom napięcia, X – nie przejmuj się

Łączymy E2 i E1 z masą, E3, A0, A1 i A3 z pinami PD5, PC3, PC4 i PC5 STM8. Ponieważ powyższa tabela zawiera zarówno niskie, jak i wysokie poziomy, konfigurujemy te kołki jako kołki push-pull.

PWM

PWM na drugim timerze konfiguruje się w taki sam sposób, jak w poprzedniej historii, z dwiema różnicami:

Najpierw musimy włączyć przerwanie Aktualizuj wydarzenie (UEV), który wywoła funkcję przełączającą aktywną diodę LED. Odbywa się to poprzez zmianę bitu Włącz przerwanie aktualizacji w rejestrze o wymownym nazwisku

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
Rejestr umożliwiający przerwanie

#define TIM2_IER   *(volatile uint8_t *)0x005303

//enable interrupt
TIM2_IER = 1;

Druga różnica związana jest ze zjawiskiem multipleksowania, np zanikający – pasożytnicze świecenie diod. W naszym przypadku może się to pojawić, ponieważ timer, który spowodował przerwanie w UEV, nadal tyka, a obsługa przerwania nie ma czasu na przełączenie diody LED, zanim timer zacznie zapisywać coś na pinach. Aby temu zaradzić, należy odwrócić logikę (0 = maksymalna jasność, 255 = nic się nie świeci) i unikać ekstremalnych wartości współczynnika wypełnienia. Te. upewnij się, że po UEV diody LED zgasną całkowicie na jeden cykl PWM.

Zmiana polaryzacji:

//set polarity 
    TIM2_CCER1 |= (CC1P | CC2P);
    TIM2_CCER2 |= CC3P;

Unikaj ustawiania r, gib na 255 i pamiętaj, aby je odwrócić podczas ich używania.

Przerywa

Istota przerwania polega na tym, że w pewnych okolicznościach chip przestaje wykonywać program główny i wywołuje jakąś funkcję zewnętrzną. Przerwania powstają na skutek wpływów zewnętrznych lub wewnętrznych, w tym timera.

Kiedy po raz pierwszy tworzyliśmy projekt w ST Visual Develop, oprócz main.c otrzymaliśmy okno z tajemniczym plikiem stm8_interrupt_vector.c, automatycznie dołączane do projektu. W tym pliku do każdego przerwania przypisana jest funkcja NonHandledInterrupt. Musimy powiązać naszą funkcję z żądanym przerwaniem.

Arkusz danych zawiera tabelę wektorów przerwań, w której znajdujemy te, których potrzebujemy:

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8
13 Aktualizacja/przepełnienie TIM2
14 Przechwytywanie/porównanie TIM2

Musimy zmienić diodę LED w UEV, więc potrzebujemy przerwania nr 13.

Odpowiednio, po pierwsze, w pliku stm8_interrupt_vector.c zmień domyślną nazwę funkcji odpowiedzialnej za przerwanie nr 13 (IRQ13) na własną:

{0x82, TIM2_Overflow}, /* irq13 */

Po drugie, będziemy musieli utworzyć plik main.h o następującej treści:

#ifndef __MAIN_H
#define __MAIN_H

@far @interrupt void TIM2_Overflow (void);
#endif

Na koniec zapisz tę funkcję w pliku main.c:

@far @interrupt void TIM2_Overflow (void)
{
    PD_ODR &= ~(1<<5); // вырубаем демультиплексор
    PC_ODR = (cnt<<3); // записываем в демультиплексор новое значение
    PD_ODR |= (1<<5); // включаем демультиплексор

    TIM2_SR1 = 0; // сбрасываем флаг Update Interrupt Pending

    cnt++; 
    cnt &= 7; // двигаем счетчик LED

    TIM2_CCR1L = ~colors[cnt][0]; // передаем в буфер инвертированные значения
    TIM2_CCR2L = ~colors[cnt][1]; // для следующего цикла ШИМ
    TIM2_CCR3L = ~colors[cnt][2]; // 

    return;
}

Pozostaje tylko włączyć przerwania. Odbywa się to za pomocą polecenia asemblera rim - będziesz musiał tego poszukać Podręcznik programowania:

//enable interrupts
_asm("rim");

Innym poleceniem asemblera jest sim – wyłącza przerwania. Należy je wyłączyć na czas zapisywania nowych wartości do „pamięci wideo”, aby przerwanie powstałe w niewłaściwym momencie nie zepsuło tablicy.

Cały kod - na GitHubie.

Przeczytaj arkusze danych 2: SPI na STM32; PWM, timery i przerwania na STM8

Jeśli chociaż ten artykuł komuś się przyda, to nie na próżno go pisałem. Chętnie przyjmę uwagi i uwagi, postaram się odpowiedzieć na wszystko.

Źródło: www.habr.com

Dodaj komentarz