Category / STM32

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.

O čia yra visi projekto failai.

STM32 PWM prescaler calculator for PWM frequency 2022.02.01 at 15:13

Just to have a quick way to calculate the prescaler value for required PWM frequency… Made this while testing the Nucleo-F446RE devboard with STM32F446RE processor, should be valid for all STM32-s.

Prescaler calculator
Clock frequency (MHz):
Wished PWM steps:
Wished PWM frequency (Hz):
Calculated prescaler value:
Rounded prescaler value:

STM32F466RE Hello World su LED! 2022.01.28 at 15:22

Toks pirmas programavimo greitukas, tik tam, kad pabandyti su kuo tas STMas valgomas, tiksliau kaip jį valgyti. Viskas daug maž intuityviai pasidaro, vos ne vedlio (wizard) principu, todėl per daug į smulkmenas nesileisiu. O be to pilna tiek video (YouTube), tiek aprašymų (šiaip Internete), kaip pradėti ir parašyti kokią nors paprastą programėlę. Nors ir trumpai, bet pažingsniui.

  1. Instaliuojam STM32CubeIDE, teksto rašymo metu versija 1.8.0.
  2. Prijungiam kūrybinį modulį, paleidžiam IDE.
  3. Susirandam modulį IDE aplinkoje (beje, ten galima iš karto parsitempti PDF aprašymus), nieko labai nekonfigūruojam, nes viską sumes pagal nutylėjimą pagal modulį. Važiuojam per vedlį kol pamatom procesoriaus paveiksliuką.
  4. Kad nebūčiau toks visai nulinukas, pridėjau papildomą LEDą ant PA6 prievado.

Pavadinau jį LED1 [Yellow LED], nes geltonas, ir šalia „gamyklinis“ LED, toks kaip Arduine ant 13 prievado kaba, taigi jį pavadinau LED2 [Green Led] nes žalias. Abo prievadai nustatyti kaip GPIO_Output (spragtelk ant pino). Ir kol kas užteks, spaudžiam F11 arba Run > Debug arba plaktuką meniu juostoje, ir iš to kas sureguliuota pagamins main.c failiuką (bus tarpinis vedliukas, bet ten lyg nieko nekeičiau).

  1. Turim jau sugeneruot visą naudžiąją kodo dalį su konfigūravimu. Toliau Main programoje tik pasidarom kintamąjį, kuris laikys prievado būklės reikšmę (1 arba 0):
    /* USER CODE BEGIN 2 */
    static unsigned short pin_state = 0; // Inicializuojamas kintamasis
    /* USER CODE END 2 */
    Pastaba – būtina savo kodą rašyti tarp USER CODE BEGIN ir atitinkamai END. Šitas atitinka Arduino void setup() { }
  2. Važiuojam į while (1) ciklą, kuris suks mūsų kodą be sustojimo. Atitinka Arduino void loop() { } ir sakom ko mes norim iš procesoriaus.
    /* USER CODE BEGIN WHILE */
    while (1)
    {
    HAL_GPIO_WritePin(GPIOA, LED1_Pin, pin_state); // Geltonas LED
    HAL_GPIO_WritePin(GPIOA, LED2_Pin, !pin_state); // Zalias LED
    pin_state = !pin_state;
    HAL_Delay(1000);
    /* USER CODE END WHILE */
    Vėl viskas rašoma tarp tų BEGIN ir END. HAL – Hardware Abstraction Layer. Panašiai kaip bibliotekos Arduine, viena komanda padaro visą darbą. Prie modulio aprašymo įdėjau HAL aprašymą, ten ~2000 puslapių :D.
  3. Spaudžiam žalia mygtuką su trikampiuku ir laukiam kol sukompiliuos ir užprogramuos procesorių. Viskas :).