STM32F466RE Hello World su LCD 2022.02.28 at 12:59
Jau rašiau apie pačią pradžią, STM32F466RE Hello World su LED!, o dabar pabandžiau pasiaiškinti, kaip su šituo moduliu ir procesoriumi pasijungti prie LCD ir gauti tą patį „Hello world”. Solidarizuodamasis su dabartiniais įvykiais ir tas Hello World bus truputį kitoks. Pradedam nuo grafinės konfigūracijos.
Mums reikalingi kontaktai:
- HEARTBEAT
- TFT_Backlight
- TFT_SCK
- TFT_MISO
- TFT_MOSI
- TFT_D-C
- SD_CS
- TFT_CS
- TFT_Reset
Kiti kontaktai ten „atsiranda” gamykliškai, pasirinkus modulį. Kol kas viskas paprasta, tą HEARBEAT naudojau miksėti modulio LEDuku, kad indikuoti reikiamas operacijas, rašymus ir pan. Indikavimo vieta kode vis kitur buvo, pagal tai, ką noriu indikuoti. Visi kiti kontaktai aiškūs pagal pavadinimus. SPI konfigūracija:
Full-Duplex nereikalingas, nes iš displėjaus nieko neskaitau ir Baud Rate Prescaler gali būti mažesnis, bet čia maksimalių parametrų veikimo testas. Iš konfigūravimo tiek, toliau pereiname prie programos. Žinau, kad displėjaus valdiklis yra ST7735. Valdiklio aprašymas yra čia (viena iš versijų). Yra prikurta ir gatavų bibliotekų, to valdiklio valdymui, bet – su biblioteka displėjus neveikė, vaizdelis buvo toks:
Tos linijos ne dėl fotografavimo/filmavimo kadravimo ypatumų, jos iš tikrųjų yra. Kadangi paprastai keičiant bibliotekos standartinius nustatymus vaizdelio pataisyti nepavyko, matyt kažkas priburta su inicializavimu, sugalvojau pats pasirašyti displėjaus inicializavimo kodą. Tam tereikia valdiklio aprašymo, kurį jau įdėjau. Inicializavimo logika:
Sutinku, girto narkomano briedas. Melejonas būsenų, pasirinkimų ir konfigūracijų. O tas „Sleep Out” reiškia „Power ON”. Bet kol daėjo… Kad neišsilaužti smegenų, metam lauk tą diagramą ir einam į Command skyrių. Pradžiai, kad būtų suprantamiau, apsirašom komandas, priskiriant standartinius sutrimpinumus.
#define NOP 0x00 // DC = 0
#define SWRESET 0x01 // DC = 0
#define RDDID 0x04 // DC = 0 + 1
#define RDDST 0x09 // DC = 0 + 1
#define RDDPM 0x0A // DC = 0 + 1
#define RDDMADCTL 0x0B // DC = 0 + 1
#define RDDCOLMOD 0x0C // DC = 0 + 1
#define RDDIM 0x0D // DC = 0 + 1
#define RDDSM 0x0E // DC = 0 + 1
#define SLPIN 0x10 // DC = 0
#define SLPOUT 0x11 // DC = 0
#define PTLON 0x12 // DC = 0
#define NORON 0x13 // DC = 0
#define INVOFF 0x20 // DC = 0
#define INVON 0x21 // DC = 0
#define GAMSET 0x26 // DC = 0 + 1
#define DISPOFF 0x28 // DC = 0
#define DISPON 0x29 // DC = 0
#define CASET 0x2A // DC = 0 + 1
#define RASET 0x2B // DC = 0 + 1
#define RAMWR 0x2C // DC = 0 + 1
#define RAMRD 0x2E // DC = 0 + 1
#define PTLAR 0x30 // DC = 0 + 1
#define TEOFF 0x34 // DC = 0
#define TEON 0x35 // DC = 0 + 1
#define MADCTL 0x36 // DC = 0 + 1
#define IDMOFF 0x38 // DC = 0
#define IDMON 0x39 // DC = 0
#define COLMOD 0x3A // DC = 0 + 1
#define FRMCTR1 0xB1 // DC = 0 + 1
#define FRMCTR2 0xB2 // DC = 0 + 1
#define FRMCTR3 0xB3 // DC = 0 + 1
#define INVCTR 0xB4 // DC = 0 + 1
#define DISSET5 0xB6 // DC = 0 + 1
#define PWCTR1 0xC0 // DC = 0 + 1
#define PWCTR2 0xC1 // DC = 0 + 1
#define PWCTR3 0xC2 // DC = 0 + 1
#define PWCTR4 0xC3 // DC = 0 + 1
#define PWCTR5 0xC4 // DC = 0 + 1
#define VMCTR1 0xC5 // DC = 0 + 1
#define VMOFCTR 0xC7 // DC = 0 + 1
#define WRID2 0xD1 // DC = 0 + 1
#define WRID3 0xD2 // DC = 0 + 1
#define PWCTR6 0xFC // DC = 0 + 1
#define NVCTR1 0xD9 // DC = 0 + 1
#define NVCTR2 0xDE // DC = 0 + 1
#define NVCTR3 0xDF // DC = 0 + 1
#define RDID1 0xDA // DC = 0 + 1
#define RDID2 0xDB // DC = 0 + 1
#define RDID3 0xDC // DC = 0 + 1
#define GAMCTRP1 0xE0 // DC = 0 + 1
#define GAMCTRN1 0xE1 // DC = 0 + 1
#define EXTCTRL 0xF0 // DC = 0 + 1
#define VCOM4L 0xFF // DC = 0 + 1
Čia komandos iš aprašymo, o komentarus pasirašiau kad žinočiau ar tai yra tik komanda ar ji dar turi kokių parametrų, it tuo pačiu kaip junginėti TFT_D-C kontaktą. Jeigu tik komanda – DC = 0, jeigu komanda turi dar ir papildomus parametrus DC = 1, tai ten kur yra 0 + 1 reiškia komanda turės papildomų duomenų. O toliau skaitom aprašymą ir paeiliui renkamės pagal savo supratimą ir logiką, ko gali reikėti. Geras dalykas tame, kad kažko nesukonfogūravus, po HW RESET ir/arba SW RESET displėjaus valdiklis pasiima defaultinius nustatymus, tai gal kažką ir praleidau, bet tam reikės papildomų eksperimentų. Visą ta inicializavimą sukišau į „void TFT_start_init(void)”. Pasianalizuojam.
HAL_GPIO_WritePin(GPIOB, TFT_Reset_Pin, RESET); // RS - active low, thus resetting display with low
HAL_Delay(20); // Min reset time 10 ms, doubled to be sure
HAL_GPIO_WritePin(GPIOB, TFT_Reset_Pin, SET);// RS - active low, thus defaulting to high
HAL_Delay(120); // After HW reset time 120 ms
SPI_sendCommand(SWRESET); // 0x01
HAL_Delay(120); // After SWRESET time 120 ms
SPI_sendCommand(SLPOUT); // 0x11
HAL_Delay(120); // After SLPOUT time 120 ms
Beje, komentarus kažkaip beveik visada rašau angliškai… Taip patogiau. Taigi, patampom už RESETo, tada palaukiam 120 ms, kad displėjaus valdiklis persikraut spėtų, tada darom programinį perkrovimą SWRESET komanda (nesu tikras, kad būtina, turbūt užtenka HWRESET). Dar palaukiam, kad spėtų persikrauti. Tada įjungiam vidinius valdiklio modulius (įtampų generatorius, dažnio generatorius ir kt.) komanda SLPOUT. Dar palaukiam, kad spėtų įsijungti. Laikai paimti iš aprašymo. Viskas, dabar jau ekraniuko valdiklis įsijungė ir laukia konfigūracijos. Einam per komandas, skaitom aprašymus ir pagal savo supratimą dedam į komandų sąrašą:
SPI_sendCommand(FRMCTR1); // 0xB1
uint8_t FRMCTR1data[] = { 0x03, 0x01, 0x01 };
SPI_sendData(FRMCTR1data, sizeof(FRMCTR1data));
SPI_sendCommand(FRMCTR2); // 0xB2
uint8_t FRMCTR2data[] = { 0x03, 0x01, 0x01 };
SPI_sendData(FRMCTR2data, sizeof(FRMCTR2data));
SPI_sendCommand(FRMCTR3); // 0xB3
uint8_t FRMCTR3data[] = { 0x03, 0x01, 0x01, 0x03, 0x01, 0x01 };
SPI_sendData(FRMCTR3data, sizeof(FRMCTR3data));
SPI_sendCommand(INVCTR); // 0xB4
uint8_t INVCTRdata[] = { 0x07 };
SPI_sendData(INVCTRdata, sizeof(INVCTRdata));
SPI_sendCommand(PWCTR1); // 0xC0
uint8_t PWCTR1data[] = { 0x1D, 0x70, }; // 1D = 3,35 V 0x11 = 1 uA
SPI_sendData(PWCTR1data, sizeof(PWCTR1data));
SPI_sendCommand(PWCTR2); // 0xC1
uint8_t PWCTR2data[] = { 0x05 }; //
SPI_sendData(PWCTR2data, sizeof(PWCTR2data));
SPI_sendCommand(PWCTR3); // 0xC2
uint8_t PWCTR3data[] = { 0x03, 0x00 }; //
SPI_sendData(PWCTR3data, sizeof(PWCTR3data));
SPI_sendCommand(PWCTR4); // 0xC3
uint8_t PWCTR4data[] = { 0x03, 0x00 }; //
SPI_sendData(PWCTR4data, sizeof(PWCTR4data));
SPI_sendCommand(PWCTR5); // 0xC4
uint8_t PWCTR5data[] = { 0x03, 0x00 }; //
SPI_sendData(PWCTR5data, sizeof(PWCTR5data));
SPI_sendCommand(VMCTR1); // 0xC4
uint8_t VMCTR1data[] = { 0x20, 0x64 }; //
SPI_sendData(VMCTR1data, sizeof(VMCTR1data));
SPI_sendCommand(INVOFF); // Color inversion
SPI_sendCommand(MADCTL); // 0x36
uint8_t MADCTLdata[] = { 0x08 }; //
SPI_sendData(MADCTLdata, sizeof(MADCTLdata));
SPI_sendCommand(COLMOD); // 0x3A
uint8_t COLMODdata[] = { 0x05 }; //
SPI_sendData(COLMODdata, sizeof(COLMODdata));
SPI_sendCommand(CASET); // 0x2A
uint8_t CASETdata[] = { 0x00, 0x00, 0x00, 0x00 }; //
SPI_sendData(CASETdata, sizeof(CASETdata));
SPI_sendCommand(RASET); // 0x2B
uint8_t RASETdata[] = { 0x00, 0x00, 0x00, 0x00 }; //
SPI_sendData(RASETdata, sizeof(RASETdata));
Kai kurios iš jų yra būtinos, kai kurios čia yra tik tam, kad naudosiu jas bandyme, o ne vien defaultines reikšmes. Detaliau bus aprašymuose, aš tik papasakosiu apie pagrindinius dalykus. Atsimenate tą paveiksliuką su brūkšniais, taip, šitas dalykas susitvarkė sudėjus teisingus parametrus į FRMCTR1-2-3. Šitie registrai atsakingi už kadravimą, sinchronizavimą ir susijusį vaizdo generavimą, todėl netinkami parametrai juose sugadina vaizdelį. PWCTR1-2-3-4-5 – kažkas apie įtampas ir sroves, iš aprašymo taip ir nesupratau, kokie konkrečiai turi būti ir kaip juos parinkti, turbūt tas dalykas nustatomas pagal matricos tipą, kurio aš nežinau, taigi paėmiau tokias vidutines arba palubinsko vertes. INVOFF komanda išjungia spalvų inversiją, įdėjau inicializavime, nes po to cikle dar įjungiau inversiją. Bet ir inicializavime šita komanda nebūtina, po HW/SW RESET ji jau būna išjungta pagal nutylėjimą. CASET RASET – kažkokie kosminiai parametrai, į kuriuos displėjus kol kas niekaip nereaguoja :D. Turbūt reaguos, kai reiks paišyti rimtesnę grafiką, paveiksliuką ar video, dėl to kol kas dumenyse tik nuliai. Ir dar liko COLMOD, 0x05 nustatymas valdiklio interfeisui, 16 bitų taškui (pikseliui). Su 18 b/pix – atsiranda artefaktai. Matyt irgi priklauso tik nuo matricos, tiksliau nuo jos pajungimo šleifo pločio. Ir paskutinės dvi komandos:
SPI_sendCommand(NORON); // 0x13
HAL_Delay(10);
SPI_sendCommand(DISPON); // 0x29
ST7735_FillScreen(ST7735_COLOR565(0, 0, 0));
NORON – Normal ON, irgi nebūtina, nes po RESET iš karto toks ir pasidaro, o vat DISPON tiesiog įjungia vaizdą displėjuje (Display ON). O teisingai sukonfigūravus displėjų, toliau jau galima naudoti bibliotekos komandas, nors turbūt ir jas pasirašysiu savaip… Bet jau turime teisingą vaizdelį:
Tik vietoje įprastinio „Hello World” kuo geriausi palinkėjimai Ukrainai.
[…] But lets put away toys and use STM32F466RE on Nucleo-F446RE development board. We already have LCD display outputting some information, so lets use it to display temperature, but we will not use DMA this time. First, lets prepare the […]