logo_elektromys.eu

/ STM32 USART III |

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:

Funkce USART_ClockInit() pak podle této struktury konfiguruje periferii. Protože USART běžně odesílá zprávy ve formátu LSB first (tedy nejprve nejnižší bit zprávy) a SPI se provozuje většinou opačně, musíme si pořadí bitů ve zprávě otočit pomocí funkce USART_MSBFirstCmd(). Přenosová rychlost (baudrate) se nastavuje stejně jako u asynchronního režimu ve struktuře USART_InitTypeDef položkou USART_BaudRate. Já zvolil svižnější 1Mb/s. Samotné vysílání (16bit dat), realizuje funkce usartspi_send16() a je vcelku triviální. Nejprve nastaví CS pin do log.0, čímž aktivuje slave obvod. Pak zapíše do vysílacího bufferu první bajt dat, počká až se začne vysílat a uvolní se další místo v bufferu (vlajka USART_FLAG_TXE). Naloží do něj druhý bajt dat a sledováním vlajky USART_FLAG_TC (Transfer Complete) počká na konec přenosu. Pak vrátí CS do log.1 čímž deaktivuje slave (pro MAX7219 to taky znamená pokyn ke zpracování dat). Když se mrknete na oscilogram z vysílání, uvidíte, že jsou mezi jednotlivými bajty zprávy prodlevy. Ty vznikají z toho důvodu, že USART vysílá na TX linku i start a stop bity (log.0 na před začátkem bajtu a log.1 za koncem bajtu). Naštěstí k nim USART negeneruje clock, takže tyto bity se našeho SPI vysílání neúčastní a jen vytváří prodlevy.

#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)
}
Na oscilogramu můžete vidět komunikaci ze začátku našeho programu. Všimněte si v průběhu clocku prodlev pro start a stop bity.
Bastl na testy. Vlevo STLINK, uprostřed malý STM32F030F4 v TSSOP pouzdře, vpravo displej s MAX7219

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 :)

| Odkazy /

Home
| V1.01 13.3.2019 /
| By Michal Dudka (m.dudka@seznam.cz) /