V příspěvku nehledejte nic světoborného. Jen jsem se nudil a chtěl jsem vyzkoušet zda displej s MAX7219 poběží (v rozporu s datasheetem) i s 3.3V napájením. Abych to ověřil musel jsem ho krmit nějakými daty. Komunikuje po SPI a jen tak z hecu mě napadlo, že by stálo za to vyzkoušet jak si s tím poradí USART na STM32. Jak brzy uvidíte roli SPI masteru zvládl dobře. Na tomto místě se možná zkušenější čtenáři zastaví a položí si otázku proč se trápit s modifikací USARTu když máme na čipu plnohodnotné SPI. Jeden slušný argument by se našel. Na čipech řady F4 umí SPI pracovat jen v 8mi nebo 16ti bitovém režimu. Neumí tedy poslat například 9bitů (SPI na rodinách F0 a F3 to umí). Některé grafické LCD nebo OLED displeje mají právě 9bitový SPI protkol a jak jsitě víte USART/UART umí posílat i 9bit zprávy a právě na čipech F4 může pracovat jako slušná náhrada za nedokonalou SPI periferii. A teď pojďme na věc.
Klasickou inicializaci UARTu (asynchronní komunikace) máte jistě dobře zažitou, takže se k ní není potřeba vyjadřovat. Mrkneme se tedy co umožňuje synchronní provoz a jak jej nastavit. Protože jsem konzerva (a HAL je pro mě nástroj na mučení programátorů) budu vše provádět pomocí SPL. Pin sloužící v USARTu jako clock má v datasheetech označení CK a například na mém testovacím STM32F030F4 je na pinu PA4. Ten přirozeně spolu s TX pinem (PA2) musíme nastavit jako "alternate function". Krom nich si také musíme nastavit jako výstup nějaký (nebo nějaké) CS piny. Aby to bylo pěkně v řadě zvolil jsem k tomuto účelu PA3. Ke konfiguraci clocku (tím myslím signál který poleze ven z USARTu pinem CK) slouží struktura USART_ClockInitTypeDef a má čtyři položky:
#include "stm32f0xx.h" // registry/příkazy MAX7219 #define MAX7219_NOP 0x0 #define MAX7219_DECODEMODE 0x9 #define MAX7219_INTENSITY 0xA #define MAX7219_SCANLIMIT 0xB #define MAX7219_SHUTDOWN 0xC #define MAX7219_TESTDISP 0xF #define MAX7219_DIGIT0 0x1 #define MAX7219_DIGIT1 0x2 #define MAX7219_DIGIT2 0x3 #define MAX7219_DIGIT3 0x4 #define MAX7219_DIGIT4 0x5 #define MAX7219_DIGIT5 0x6 #define MAX7219_DIGIT6 0x7 #define MAX7219_DIGIT7 0x8 #define MAX7219_BLANK 127 // dolní 4bity při zapnuté znakové sadě vyberou znak "blank", nejvyšší bit aktivuje desetinnou tečku #define MAX7219_ON 1 // hodnota v registru MAX7219_SHUTDOWN #define MAX7219_OFF 0 // hodnota v registru MAX7219_SHUTDOWN #define MAX7219_DOT 1<<7 // desetinná tečka se rozsvěcí nejvyšším bitem // makra pro ovládání Chip Select #define CS_L GPIOA->BRR = GPIO_Pin_3; #define CS_H GPIOA->BSRR = GPIO_Pin_3; void init_usartspi(void); void init_clock_48(void); void usartspi_send16(uint16_t data); void max7219_set(uint8_t command, uint8_t data); uint8_t i; int main(void){ init_clock_48(); // taktujeme čip na 48MHz init_usartspi(); // vyčistit obsah displeje (MAX7219) for(i=1;i<8;i++){max7219_set(i, MAX7219_BLANK);} max7219_set(MAX7219_DECODEMODE,0xff); // dekódovat všechny cifry (využít znakovou sadu) max7219_set(MAX7219_SCANLIMIT, 4); // zobrazovat jen dolní 4 cifry max7219_set(MAX7219_TESTDISP, 0); // test-display vypnout max7219_set(MAX7219_INTENSITY, 7); // poloviční intenzita max7219_set(MAX7219_SHUTDOWN, MAX7219_ON); // zapnout displej max7219_set(MAX7219_DIGIT3, 2); // na 3. pozici zobraz cifru "2" max7219_set(MAX7219_DIGIT2, 5 | MAX7219_DOT); // na druhé pozici zobraz cifru "5" a desetinnou tečku max7219_set(MAX7219_DIGIT1, 4); // na první pozici zobraz cifru "4" max7219_set(MAX7219_DIGIT0, 0); // na nulté pozici zobraz cifru "0" while(1){ } } // složí data do 16bit čísla a pošle po SPI (tato funkce by šla nahradit makrem) void max7219_set(uint8_t command, uint8_t data){ usartspi_send16(((uint16_t)command)<<8 | (uint16_t)data); } void usartspi_send16(uint16_t data){ CS_L; // aktivovat slave // zde by neměla nastat situace že v bufferu nebude místo, proto si dovolím to nekontrolovat USART_SendData(USART1, data>>8); // naložit první bajt zprávy do bufferu while (!USART_GetFlagStatus(USART1,USART_FLAG_TXE)){} // počkat až bude v odesílacím bufferu místo USART_SendData(USART1, data & 0xff); // naložit druhý bajt zprávy do bufferu while (!USART_GetFlagStatus(USART1,USART_FLAG_TC)){} // počkat na dokončení přenosu CS_H; // deaktivovat slave } void init_usartspi(void){ GPIO_InitTypeDef gp; USART_InitTypeDef usart_is; USART_ClockInitTypeDef usart_clk; // inicializace pinů (PA2 - CK/SCK, PA4 - TX/MOSI, PA3 - CS) RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 , ENABLE); gp.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_4; gp.GPIO_Mode = GPIO_Mode_AF; gp.GPIO_OType = GPIO_OType_PP; gp.GPIO_PuPd = GPIO_PuPd_NOPULL; gp.GPIO_Speed = GPIO_Speed_Level_1; GPIO_Init(GPIOA, &gp); // přidělit kontrolu nad CK/SCK a TX/MOSI USARTu GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource4, GPIO_AF_1); // CS (PA3) ovládáme manuálně gp.GPIO_Pin = GPIO_Pin_3; gp.GPIO_Mode = GPIO_Mode_OUT; GPIO_Init(GPIOA, &gp); CS_H; // hned deaktivovat slave // clock v SPI MODE 0 usart_clk.USART_CPOL = USART_CPOL_Low; // clock neutrálně v log.0 usart_clk.USART_CPHA = USART_CPHA_1Edge; // čtení dat na první hranu (tedy vzestupnou) usart_clk.USART_Clock = USART_Clock_Enable; // vypouštět clock usart_clk.USART_LastBit = USART_LastBit_Enable; // clock i pro poslední bit USART_ClockInit(USART1, &usart_clk); // inicializovat clock usart_is.USART_BaudRate = 1000000; // datová rychlost 1Mb/s (ze 48MHz ji lze realizovat) usart_is.USART_WordLength = USART_WordLength_8b; // zpráva 8 bitů usart_is.USART_StopBits = USART_StopBits_1; // to je celkem jedno, stopbit dělá jen zbytečnou prodlevu usart_is.USART_Parity = USART_Parity_No; // paritu nechceme usart_is.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // řízení toku nepoužíváme usart_is.USART_Mode = USART_Mode_Tx; // budeme jen vysílat USART_Init(USART1, &usart_is); // inicializovat UART USART_MSBFirstCmd(USART1,ENABLE); // pro SPI je běžné posílat jako první MSB (u UARTu je to běžně naopak) USART_Cmd(USART1, ENABLE); // spustit USART } void init_clock_48(void){ // čip je zatím taktován z HSI RCC_PLLConfig(RCC_PLLSource_HSI,RCC_PLLMul_12); // nastavit PLL na násobení 12x (8MHz / 2 * 12 = 48MHz) RCC_PLLCmd(ENABLE); // spustit PLL while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) != SET); // počkat na rozběh PLL RCC_HCLKConfig(RCC_SYSCLK_Div1); // SYSCLK z PLL nijak nedělit RCC_PCLKConfig(RCC_HCLK_Div1); // HCLK ze SYSCLK nijak nedělit (jedeme naplno) RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); // přepnout SYSCLK na PLL (jedeme na 48MHz) SystemCoreClockUpdate(); // updatovat globální proměnnou SystemCoreClock (nutné pro korektní výpočet baudrate) }
Jak vidíte z fotografie driver si vystačil i s 3.3V napájením (červený displej má naštěstí nízká prahová napětí). Pro ty z vás, kteří MAX7219 neznají si dovolím pár slov. Jde o driver LED displejů se společnou anodou. Může řídit až 8x8 segmentů, tedy jak klasické 7-segmentové displeje tak displeje typu "dot-matrix". U nás je driver poměrně drahý (~200kč), ale v číně jsou k dostání moduly 8x7segment nebo 8x8dot matrix s tímto driverem v ceně jednoho dolaru (a zatím jsem nenarazil na žádný problémový). Drivery je možné spojovat za sebe (tzv. "daisy-chain"), a to tak, že datový výstup jednoho driveru připojíte na datový vstup druhého (stejně jako lze spojovat posuvné registry) a rozšiřovat tak displej v podstatě neomezeně. Komunikujete s ním pomocí SPI v 16bitových rámcích (8bitů příkaz + 8bitů data). Driver má v sobě zabudovanou znakovou sadu (0,1,2,3,4,5,6,7,8,9,-,E,H,L,P), kterou můžete individuálně zapínat a vypínat vybraným cifrám. Využití znakové sady vyžaduje odpovídající zapojení segmentů k displeji. Nic vám ale nebrání kontrolovat jednotlivé LEDky a znakovou sadu nepoužívat (logická volba u dot-matrix displejů). Proud do jednotlivých segmentů se nastavuje jedním externím rezistorem a programově je možné snižovat intenzitu jasu v 16ti krocích.
Co se našeho programu týče tak displej ovládáme funkcí max7219_set(). Ta složí příkaz i data do 16bitového čísla a odešle. Na začátku inicializace naplním všech 8 pozic displeje hodnotou 127 (0b111 1111). Dolní čtyři bity nesou informaci o znaku (0b1111 odpovídá prázdnému znaku - "blank"). Nejvyšším bitem lze zapnout desetinnou tečku. Tři zbývající bity nemají význam a mohou mít libovolnou hodnotu. V registru Decode mode aktivuji znakovou sadu pro všech 8 cifer (každý bit dat aktivuje/deaktivuje znakovou sadu pro odpovídající cifru). Protože chci v této modelové situaci používat jen 4 cifry (tedy půlku displeje) nastavím obsah registru Scan limit na 4. Driver pak multiplexuje jen přes dolní 4 cifry displeje. Tato volba ovlivňuje jas. Čím méně cifer driver budí, tím větší je pro každou cifru střída a tím větší je i jas displeje. Není tedy vhodné používat tuto funkci na "zhasínání" například nul před číslem a podobně. Driver je po startu "vypnutý" (v úsporném režimu), je proto potřeba ho aktivovat zápisem hodnoty 1 do registru Shutdown. Pro jistotu je dobré zapsat nulu i do registru Test display (log.1 by rozsvítila celý displej). V rámci ukázky jsem si dovolil vypsat na displej jedno číslo. Při praktickém použití si budete muset implementovat nějakou zobrazovací funkci. Například s pomocí itoa() nebo sprintf by to neměl být problém. Jediná "komplikace" je fakt, že v ASCII cifry 0 až 9 začínají na hodnotě 48, kdežto ve znakové sadě našeho displeje začínají na 0. Prosté odečtení čísla 48 od všech znaků v řetězci by mělo problém zvládnout (samozřejmě za předpokladu že v jsou v řetezci jen cifry). A to je ode mě vše :)
Home
| V1.01 13.3.2019 /
| By Michal Dudka (m.dudka@seznam.cz) /