RTV forum PL | NewsGroups PL

Generowanie sygnału VGA na STM32F411: jak skonfigurować VSYNC, HSYNC i DMA?

Generowanie sygnału VGA na STM32F411 z pomocą D MA

NOWY TEMAT

elektroda NewsGroups Forum Index - Elektronika Polska - Generowanie sygnału VGA na STM32F411: jak skonfigurować VSYNC, HSYNC i DMA?

Atlantis
Guest

Sat Mar 14, 2026 7:37 pm   



Pracuję teraz nad jednym projektem na STM32F411, który będzie
wykorzystywał stary monitor VGA do wyświetlania obrazu, a tak naprawdę
jedynie tekstu. Z tego powodu nie potrzebuję pełnego framebuffera - mam
zamiar trzymać w buforze jedynie informacje o znakach w poszczególnych
lokalizacjach (wiersz/kolumna) i na ich podstawie w locie generować
zawartość bufora z wartościami RGB do wyświetlenia w bieżącej linii.

Wartości napięć na liniach RGB będą generowane za pomocą drabinek
rezystorowych - po dwa rezystory na kanał. W teorii daje mi to możliwośc
użycia 64 kolorów, chociaż jak na razie zadowolę się zestawem kilku
podstawowych barw.

Logikę wypełniania tego bufora "scanline" na podstawie zawartości bufora
tekstowego i definicji czcionek jakoś sobie ogarnę. Teraz próbuję jednak
zorganizować generowanie sygnałów i wysyłanie danych do portu
sterującego rezystorami.

Interesuje mnie rozdzielczość 640x480 przy 60 Hz odświeżania. To daje
nam pixel clock w okolicy 25 MHz - zdecydowanie za dużo, żeby ogarnąć
software'owo, w przerwaniach. Z tego co widzę podobne projekty używają
do tego DMA.

Teraz tylko chciałem dopytać:
1. W jaki sposób generować VSYNC/HSYNC?
2. W jaki sposób inicjować wysyłanie kolorów pikseli na port za pomocą
DMA w taki sposób, żeby uwzględnić back porch (zaczynać w widocznej
części linii)?
3. W jaki sposób skonfigurować DMA, żeby transfer każdej kolejnej
zawartości z bufora scanline do portu był zsynchronizowany z pixel
clock, ale bez używania przerwań?

Mirek
Guest

Sun Mar 15, 2026 11:18 am   



W dniu 14.03.2026 o 18:37, Atlantis pisze:

Quote:
Teraz tylko chciałem dopytać:
1. W jaki sposób generować VSYNC/HSYNC?
2. W jaki sposób inicjować wysyłanie kolorów pikseli na port za pomocą
DMA w taki sposób, żeby uwzględnić back porch (zaczynać w widocznej
części linii)?
3. W jaki sposób skonfigurować DMA, żeby transfer każdej kolejnej
zawartości z bufora scanline do portu był zsynchronizowany z pixel
clock, ale bez używania przerwań?

https://www.artekit.eu/vga-output-using-a-36-pin-stm32/

--
Mirek

Waldek Hebisch
Guest

Sun Mar 15, 2026 6:15 pm   



Atlantis <marekw1986NOSPAM@wp.com> wrote:
Quote:
Pracuję teraz nad jednym projektem na STM32F411, który będzie
wykorzystywał stary monitor VGA do wyświetlania obrazu, a tak naprawdę
jedynie tekstu. Z tego powodu nie potrzebuję pełnego framebuffera - mam
zamiar trzymać w buforze jedynie informacje o znakach w poszczególnych
lokalizacjach (wiersz/kolumna) i na ich podstawie w locie generować
zawartość bufora z wartościami RGB do wyświetlenia w bieżącej linii.

Wartości napięć na liniach RGB będą generowane za pomocą drabinek
rezystorowych - po dwa rezystory na kanał. W teorii daje mi to możliwośc
użycia 64 kolorów, chociaż jak na razie zadowolę się zestawem kilku
podstawowych barw.

Logikę wypełniania tego bufora "scanline" na podstawie zawartości bufora
tekstowego i definicji czcionek jakoś sobie ogarnę. Teraz próbuję jednak
zorganizować generowanie sygnałów i wysyłanie danych do portu
sterującego rezystorami.

Interesuje mnie rozdzielczość 640x480 przy 60 Hz odświeżania. To daje
nam pixel clock w okolicy 25 MHz - zdecydowanie za dużo, żeby ogarnąć
software'owo, w przerwaniach. Z tego co widzę podobne projekty używają
do tego DMA.

Teraz tylko chciałem dopytać:
1. W jaki sposób generować VSYNC/HSYNC?
2. W jaki sposób inicjować wysyłanie kolorów pikseli na port za pomocą
DMA w taki sposób, żeby uwzględnić back porch (zaczynać w widocznej
części linii)?
3. W jaki sposób skonfigurować DMA, żeby transfer każdej kolejnej
zawartości z bufora scanline do portu był zsynchronizowany z pixel
clock, ale bez używania przerwań?

Wersja monochromatyczna może używać SPI (jak w linku podanym przez
Mirka), to daje ciąg bitów z jednakowymi odstępami między
poszczególnymi bitami. No i jest tylko jeden transfer DMA na
8 pikseli.

W STM32 jest możliwość sterowania urządzeń przez liczniki. Jak
chcesz mieć kolor to jeden licznik może generować żądania DMA,
a drugi generować impulsy synchornizacji poziomej. Przy tym
ten licznik od synchronizacji poziomej może też generować
sygnał startu dla licznika pikseli.

Nie wiem czy Ci się uda: dokumentacja STM straszy że przełączanie
dostępu do pamięci między procesorem a DMA zabiera ileś cykli
co w domyśle oznacza że maksymalny użyteczny zegar dla DMA jest
istotnie wolniejszy od zegara procesora. Maksymalny legalny
zegar dla 411 to 100MHz, czyli transmitując bajty masz
transfer co 4 takty. Możesz kombinować z FIFO w DMA, z dokumentacji
nie jest jasne czy da się zrobić co potrzeba, ale teortycznie
możesz zmniejszyć liczbę przesłań tranmitując 4 bajtowe słowa
z pamięci a bajty do portu.

--
Waldek Hebisch

Atlantis
Guest

Mon Mar 16, 2026 12:41 am   



On 3/15/26 17:15, Waldek Hebisch wrote:

Quote:
Nie wiem czy Ci się uda: dokumentacja STM straszy że przełączanie
dostępu do pamięci między procesorem a DMA zabiera ileś cykli
co w domyśle oznacza że maksymalny użyteczny zegar dla DMA jest
istotnie wolniejszy od zegara procesora. Maksymalny legalny
zegar dla 411 to 100MHz, czyli transmitując bajty masz
transfer co 4 takty.
Prawdę mówiąc trochę się tego obawiałem. Przyjąłem założenie z użyciem

GPIO, DMA i rezystorów, bo takie rozwiązanie podpatrzyłem w jakimś
projekcie napisanym w Arduino pod ESP32. No cóż, ale ESP32 jednak może
być taktowany szybciej (i być może nie posiada takich ograniczeń po
stronie DMA).
W teorii mógłbym spróbować zmniejszyć dwukrotnie pixel clock do 12,5
MHz, zostawiając te same parametry HSYNC i VSYNC i rysując jedną linię
dwa razy. W ten sposób rzeczywista rozdzielczość spadłaby do 320x240. To
jednak za mało, żeby uzyskać dobrze wyglądający tryb 80 kolumn.
No cóż... Chyba będę musiał poeksperymentować z wersją monochromatyczną
na SPI - kolory byłyby fajnym dodatkiem, ale priorytet stanowi dla mnie
jednak wyraźny tekst w osiemdziesięciu kolumnach...

W ogóle eksperyment miał być tak naprawdę przymiarką do poważniejszego
projektu z VGA na STM32. Widzę, że poważniejszych projektach tego
rodzaju wykorzystuje się LTDC i pewnie właśnie tej metody spróbuję.

Atlantis
Guest

Mon Mar 16, 2026 9:39 am   



On 3/15/26 23:41, Atlantis wrote:

Quote:
No cóż... Chyba będę musiał poeksperymentować z wersją monochromatyczną
na SPI - kolory byłyby fajnym dodatkiem, ale priorytet stanowi dla mnie
jednak wyraźny tekst w osiemdziesięciu kolumnach...

W sumie jeszcze raz przyjrzałem się i wygląda na to, że chyba mam
szczęście. Jedna z linii sterujących zielonym kolorem może być
wykorzystana jako wyjście MOSI SPI SPI1 oraz SPI3. Czyli jeśli usunę
pozostałe rezystory, a ten na tej linii zamienię na 270 omów, będę mógł
generować 0.7V impulsy zapalające zielone piksele, bez konieczności
cięcia ścieżek i przerabiania płytki. Tekst będzie zielony, ale to nada
mu charakterystycznego retro klimatu. ;)

W teorii mógłbym minimalnie przerobić projekt i podłączyć sygnał MOSI
pod trzy rezystory, na każdym kanale RGB. Tyle tylko, że wtedy pobór
prądu z pinu wyniesie prawie 30 mA - a to już oznacza jego przeciążenie.
Trzeba by jednak dać jakiś bufor.

Dlatego właśnie zastanawia mnie rozwiązanie użyte projekcie spod linku
przesłanego przez Mirka. Tam linie GREEN jest połączona bezpośrednio z
wyjściem mikrokontrolera - nie dosyć, że monitor dostaje napięcie 3,3V
(to jeszcze nie problem, bo większość nowoczesnych monitorów pewnie
sobie z tym poradzi, interpretując to jako maksymalną jasność) to
jeszcze jedynym ograniczeniem prądu jest wejściowa impedancja linii
koloru (75 omów)...

Atlantis
Guest

Tue Mar 17, 2026 12:57 am   



Udało mi się osiągnąć częściowy sukces. Układ generuje już poprawne
przebiegi HSYNC i VSYNC. Wyglądają ok na oscyloskopie, a mój monitor
(stary CRT) wykrywa sygnał y wybudza się z trybu uśpienia.
Natomiast za nic nie mogę wygenerować sygnału pikseli na kanale GREEN za
pomocą SPI1. Skonfigurowałem SPI do pracy na 25 MHz i podpiąłem MOSI do
właściwego pinu. Na razie próbuję po prostu w pętli głównej wysyłać
cyklicznie 0xAA, ale na linii MISO cisza - znajduje się ona w stanie
niskim. CLK również milczy.
Co więcej, jeśli skonfiguruję tę linię do pracy w trybie wyjścia GPIO
(PB5) to ni jestem w stanie zmieniać jej stanu w pętli głównej, ale już
domyślna wartość ustawiona w CubeMX się zmienia (więc raczej można
odrzucić hipotezę, że pin/port się upalił).
Co więcej, spróbowałem wysłać na ten pin sygnał PWM z TIM3_CH2 i on jest
widoczny. Ktoś spotkał się z takim problemem?

Waldek Hebisch
Guest

Tue Mar 17, 2026 8:54 pm   



Atlantis <marekw1986NOSPAM@wp.com> wrote:
Quote:
Udało mi się osiągnąć częściowy sukces. Układ generuje już poprawne
przebiegi HSYNC i VSYNC. Wyglądają ok na oscyloskopie, a mój monitor
(stary CRT) wykrywa sygnał y wybudza się z trybu uśpienia.
Natomiast za nic nie mogę wygenerować sygnału pikseli na kanale GREEN za
pomocą SPI1. Skonfigurowałem SPI do pracy na 25 MHz i podpiąłem MOSI do
właściwego pinu. Na razie próbuję po prostu w pętli głównej wysyłać
cyklicznie 0xAA, ale na linii MISO cisza - znajduje się ona w stanie
niskim. CLK również milczy.
Co więcej, jeśli skonfiguruję tę linię do pracy w trybie wyjścia GPIO
(PB5) to ni jestem w stanie zmieniać jej stanu w pętli głównej, ale już
domyślna wartość ustawiona w CubeMX się zmienia (więc raczej można
odrzucić hipotezę, że pin/port się upalił).
Co więcej, spróbowałem wysłać na ten pin sygnał PWM z TIM3_CH2 i on jest
widoczny. Ktoś spotkał się z takim problemem?

Nie używam CubeMX więc nie wiem co może zepsuć. "domyślna wartość
ustawiona w CubeMX" to wygląda jak zmienna w GUI która dopiero
pośrednio powinna wpływać na pin, ale samodzielnie nic nie znaczy.
Ja bezpośrednio programuję porty, "typowy" problem to nie uaktywnienie
zegara do jakiegoś potrzebnego urządzenia. Jak coś nie działa to przez
debuger czytam rejestry urządzenia i sprawdzam czy mają oczekiwną
wartość. STM często blokuje zmiany rejestrów czy specyficznych
bitów jeśli urządzenie jest aktywne więc trzeba pilnować kolejności
konfiguracji urządzeń.

Ja używałem SPI z niezbyt szybkim zegarem, najszybszy to było
wysyłanie danych do wyświetlacza SPI. W zasadzie wszystko działało
zgodnie z dokumentacją (uwzględniając erraty). Z innymi urządzeniami
(i chyba z klonem a nie oryginalnym STM) miałem problem z stylu
że pomiędzy dwoma zapisami do rejestu potrzebne było odczekanie
przez 2 czy 3 takty, "optymalnie szybki" kod nie działał. W jednym
chińskim klonie jeden kanał licznika zliczał OK, pin dało się
wysterować przez GPIO, ale skierowanie wyjścia licznika do pinu nie
działało.

A propo, do ustawienia SPI1 na PB3 i PB5 z podzielnikiem 64 oraz
PB4 jako GPIO ma taki kod (który u mnie działa):

/* Activate clocks */
rcc->ahb1enr |= RCC_AHB1ENR_IOPAEN|RCC_AHB1ENR_IOPBEN;
rcc->apb2enr |= RCC_APB2ENR_SPI1EN;

/* Activate GIPO-s */
/* GPIOB3 and GPIOB5 output push-pull alternate function 50 MHz
GPIOB4 output push-pull 2 MHz */
gpiob->moder = (gpiob->moder & ~((3<<(2*5))|(3<<(2*4))|(3<<(2*3))))
| (2<<(2*5)) | (1<<(2*4)) | (2<<(2*3));
gpiob->otyper = (gpiob->otyper & ~((1<<5) | (1<<4)| (1<<3)));
gpiob->ospeedr = (gpiob->ospeedr & ~((3<<(2*5))|(3<<(2*4))|(3<<(2*3))))
| (2<<(2*5)) | (2<<(2*3));
/* B3 and B5 and AF5 */
gpiob->afrl = (gpiob->afrl & ~((0xf<<(5*4)) | (0xf<<(3*4))))
| (5<<(4*5)) | (5<<(4*3));
/* Configure SPI1 */
uint32_t spi_conf = SPI_CR1_LSBFIRST | SPI_CR1_DFF_8BIT
| SPI_CR1_BAUDRATE_FPCLK_DIV_64
| SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI ;
spi1->cr1 = spi_conf;
spi1->cr2 = 0;
spi1->cr1 = spi_conf|SPI_CR1_SPE;

to używa kombinację moich prywatnych nagłówków mapujących rejestry i
nagłówków libopencm3 które definiują maski bitowe (jak widać część
stałych wyżej jest po prostu zaszyta). Mam nadzieję że konfiguracja
jest w miarę oczywista. Ta konfiguracja daje następujący stan
kluczowych rejestrów:

0x40023830 czyli AHB1ENR 0x00000087
(istone jest ustawienie bitu numer 1 uaktywniające GPIOB)
0x40023844 czyli APB2ENR 0x00005000
(istotne jest ustawienie bitu numer 12 uaktywniające SPI1)
0x40020400 czyli GPIOB_MODER 0x00000980
0x40020404 czyli GPIOB_OTYPER 0
0x40020408 czyli GPIOB_OSPEEDR 0x00000880
0x40020420 czyli GPIOB_AFRL 0x00505000
0x40013000 czyli SPI1_CR1 0x000003ec
0x40013004 czyli SPI1_CR2 0

Oczywiście poza błędną konfiguracją można mieć całą masę normalnych
błędów. Z tą konfiguracją możesz "ręcznie" trasmitować: jak przerwiesz
program to z debugera możesz wpisać wartość do rejestru danych SPI
(pod adresem 0x4001300c), te dane powinny lecieć na PB5. Po takiej
ręcznej transmisji odczyt z rejestru stanu SPI (pod adresem 0x40013008)
powinien dać 3 tzn. TXE i RXNE ustawione. Normalnie jak program
czyta odebrane dane (ja to robię żeby wiedzieć kiedy jest koniec
transmisji na drucie) RXNE jest 0, niezerowa wartość pokazuje że
SPI zadziałało.

--
Waldek Hebisch

Atlantis
Guest

Wed Mar 18, 2026 9:27 am   



On 3/17/26 19:54, Waldek Hebisch wrote:

Quote:
Nie używam CubeMX więc nie wiem co może zepsuć. "domyślna wartość
ustawiona w CubeMX" to wygląda jak zmienna w GUI która dopiero
pośrednio powinna wpływać na pin, ale samodzielnie nic nie znaczy.

Ok, okazało się, że problem miał banalną przyczynę. Przygotowując się do
testów zakomentowałem większość kodu niezwiązanego z działaniem portu
VGA. Niestety umknęło mi wywołanie jednej funkcji, która co prawda się
wykonywała, ale bez inicjacji (która została zakomentowana) wpadała w
nieskończoną pętlę. Operacje związane z uruchomieniem generowania
HSYNC/VSYNC odbywały się wcześniej, ale transfer przez SPI czy operacje
na porcie już nie miały okazji się wykonać.

W chwili obecnej "coś" już wyświetla mi się na ekranie, ale niestety nie
jest to ten obraz, którego oczekuję.

Bufor scanline (80 bajtów uint8_t) wypełniam wartościami 0xF0. Zawartość
bufora jest wysyłana z szybkością pixel clock (25 MHz) za pomocą DMA
przez SPI, na kanał GREEN. Wysyłanie jest inicjowane dopiero wtedy, gdy
OC na timerze HSYNC odmierzy koniec backporch. Do tego jeszcze sprawdzam
wartość timera liczącego linie (w celu generowania VSYNC) i aktywuję
transmisje SPI tylko wtedy, jeśli mieścimy się w widocznym zakresie.

Ne ekranie spodziewam się zobaczyć zestaw nieruchomych, pionowych linii.
Widzę natomiast bardziej chaotyczny obraz, na którym w poszczególnych
liniach faktycznie widać ten przerywany wzór, jednak linie są
przesunięte względem siebie, tworząc coś przypominającego nieregularną
kratę, która w dodatku lekko szumi/faluje.

Chyba mimo wszystko spróbuję dodać generator znaków i zobaczę w jaki
sposób zniekształcona będzie oczekiwana zawartość bufora tekstowego.

Atlantis
Guest

Sat Mar 21, 2026 9:05 am   



Ok, na chwilę obecną udało mi się naprawić problemy związane z
synchronizacją pionową i generowaniem obrazu tylko w widocznym okienku.
Zniknął problem obrazu trzęsącego się w pionie. Przyczyna składała się z
kilku czynników: po pierwsze musiałem zoptymalizować ustawienia
priorytetów przerwań, po drugie w złym miejscu decydowałem czy linia
znajduje się w widocznym oknie, przez co opóźnienia pojawiały się
krytycznym punkcie. Wprowadzenie flagi ustawianej w mniej krytycznym
obliczeniowo momencie rozwiązało ten problem.

Teraz już udało mi się wygenerować (względnie) stabilny obraz testowy,
złożony z pionowych linii. Pozostał do rozwiązania ostatni problem, ale
przynajmniej znam jego przyczynę.

W tej chwili (testowo) odpalam transmisje DMA/SPI za pomocą
HAL_SPI_Transmit_DMA(). Te funkcja jest dość ciężka i zawiera sporo
logiki. Efekt jest taki, że jeśli wołam ją na końcu HSYNC back porch, to
właściwa transmisja startuje dopiero w chwilę później - na tyle późno,
że linia zaczyna się dopiero w okolic 1/4 szerokości ekranu.

Właściwy schemat powinien wyglądać raczej tak:
- Po wysłaniu linii (HSYNC front porch) ustawiamy wszystkie parametry
następnej transmisji DMA, ale jej nie startujemy.
- Właściwą transmisję odpalamy w HSYNC back porch.

stary grzyb
Guest

Sat Mar 21, 2026 8:01 pm   



Quote:
... HAL_SPI_Transmit_DMA(). Te funkcja jest dość ciężka i zawiera
sporo logiki.


I dlatego ja nie używam gotowych f-cji HALowskich, lecz je maksymalnie
odchudzam, dopasowując dokładnie do moich potrzeb. Jest to czasochłonne,
ale opłacalne, bo dzięki temu w pełni panuję nad procesami.

elektroda NewsGroups Forum Index - Elektronika Polska - Generowanie sygnału VGA na STM32F411: jak skonfigurować VSYNC, HSYNC i DMA?

NOWY TEMAT

Regulamin - Zasady uzytkowania Polityka prywatnosci Kontakt RTV map News map