wtorek, 12 stycznia 2016

[7] STM32 - ADC, DMA, wyświetlacz Nokia 5110

Ten post jest kontynuacją wpisu poprzedniego w którym przedstawiłem obsługę ADC oraz DMA. Tym razem natomiast zostanie zmieniony wyświetlacz z HD44780 2x16 na Nokia 5110. 

Samego sposobu działania wyświetlacza nie będę opisywał gdyż przedstawiłem już informacje na jego temat w poście dla STM32F103RB.

SPI w STM32F4


STM32F4 oferuje następujące możliwości podłączenia SPI:

  • SPI1 - Zestaw pinów 1 - MOSI - PA7; MISO - PA6; SCK - PA5; Taktowanie: APB2;
  • SPI1 - Zestaw pinów 2 - MOSI - PB5; MISO - PB4; SCK - PB3; Taktowanie: APB2;
  • SPI2 - Zestaw pinów 1 - MOSI - PC3; MISO - PC2; SCK - PB10; Taktowanie: APB1;
  • SPI2 - Zestaw pinów 2 - MOSI - PB15; MISO - PB14; SCK - PB13; Taktowanie: APB1;
  • SPI2 - Zestaw pinów 3 - MOSI - PI3; MISO - PI2; SCK - PI0; Taktowanie: APB1;
  • SPI3 - Zestaw pinów 1 - MOSI - PB5; MISO - PB4; SCK - PB3; Taktowanie: APB1;
  • SPI3 - Zestaw pinów 2 - MOSI - PC12; MISO - PC11; SCK - PC10; Taktowanie: APB1;
  • SPI4 - Zestaw pinów 1 - MOSI - PE6; MISO - PE5; SCK - PE2; Taktowanie: APB2;
  • SPI4 - Zestaw pinów 2 - MOSI - PE14; MISO - PE13; SCK - PE12; Taktowanie: APB2;
  • SPI5 - Zestaw pinów 1 - MOSI - PF9; MISO - PF8; SCK - PF7; Taktowanie: APB2;
  • SPI5 - Zestaw pinów 2 - MOSI - PF11; MISO - PH7; SCK - PH6; Taktowanie: APB2;
  • SPI6 - Zestaw pinów 1 - MOSI - PG14; MISO - PG12; SCK - PG13; Taktowanie: APB2;

Linia taktująca APB1 jest 4x wolniejsza od częstotliwości głównego taktowania całej płytki. Więc jeśli wykorzystywana jest maksymalna częstotliwość wynosząca 168 MHz to taktowanie APB1 wynosi 42 MHz. Minimalna wartość dzielnika częstotliwości dla SPI wynosi 2, więc będzie on taktowanay częstotliwością 21 MHz. Domyślnie wykorzystywany jest dzielnik o wartości 32, co daje wartość 1,3125 MHz. Maksymalnie można ustawić wartość dzielnika wynoszącą 256.

Linia APB2 jest dwukrotnie wolniejsza od linii taktującej.Wobec tego daje ona częstotliwość 84 MHz, co oznacza, że domyślne taktowanie SPI wynosi 42 MHz, natomiast minimalnie 2,625 MHz.

Podłączenie


Wyświetlacz:

  • RST - GPIOC15 - Reset wyświetlacza
  • CE - GPIOC13 - Wybranie urządzenia, Chip Enable
  • DC - GPIOC14 - Linia danych, komend
  • Din - GPIOC3 - MOSI, SPI2
  • Clk - GPIOB10 - Sygnał zegarowy, SPI2
  • VCC - 3,3V
  • BL - Podświetlenie, 3,3V, lub to samo tylko przez potencjometr do regulacji, lub poprzez PWM
  • GND - GND

Potencjometr został podłączony do pinu PC2.

Programowanie


Różnica w porównaniu z STM32F103RB jest inny sposób aktywowania interfejsu SPI oraz inicjalizacji pinów. Sam sposób programowania wyświetlacza pozostaje taki sam.

Poniżej wklejam pełny kod programu który zawiera opisy ważniejszych linii kodu.

#include "stm32f4xx.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_spi.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_adc.h"
#include "stm32f4xx_dma.h"
#include <stdio.h>
#include <stdint.h>
#include "stm32f4_delay.h"
 
#define LCD_DC      GPIO_Pin_14
#define LCD_CE      GPIO_Pin_13
#define LCD_RST     GPIO_Pin_15
#define LCD_DIN     GPIO_Pin_3
#define LCD_CLK     GPIO_Pin_10
#define LCD_CLK_LINE  GPIOB
 
 
//Deklaracja tablicy znaków
const uint8_t font_ASCII[][5] = {
   {0x00, 0x00, 0x00, 0x00, 0x00} // 20
  ,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 !
  ,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 "
  ,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 #
  ,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $
  ,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 %
  ,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 &
  ,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 '
  ,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 (
  ,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 )
  ,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a *
  ,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b +
  ,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c ,
  ,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d -
  ,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e .
  ,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f /
  ,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0
  ,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1
  ,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2
  ,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3
  ,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4
  ,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5
  ,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6
  ,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7
  ,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8
  ,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9
  ,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a :
  ,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ;
  ,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c <
  ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d =
  ,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e >
  ,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ?
  ,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @
  ,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A
  ,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B
  ,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C
  ,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D
  ,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E
  ,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F
  ,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G
  ,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H
  ,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I
  ,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J
  ,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K
  ,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L
  ,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M
  ,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N
  ,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O
  ,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P
  ,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q
  ,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R
  ,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S
  ,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T
  ,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U
  ,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V
  ,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W
  ,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X
  ,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y
  ,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z
  ,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [
  ,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c
  ,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ]
  ,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^
  ,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _
  ,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 `
  ,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a
  ,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b
  ,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c
  ,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d
  ,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e
  ,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f
  ,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g
  ,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h
  ,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i
  ,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j
  ,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k
  ,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l
  ,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m
  ,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n
  ,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o
  ,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p
  ,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q
  ,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r
  ,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s
  ,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t
  ,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u
  ,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v
  ,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w
  ,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x
  ,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y
  ,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z
  ,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b {
  ,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c |
  ,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d }
  ,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ~
  ,{0x78, 0x46, 0x41, 0x46, 0x78} // 7f DEL
};
 
 
uint32_t ADCValue[1] = {0};
 
uint8_t lcd_buffer[84 * 48 / 8];
 
//Wyslanie komendy na wyswietlacz
static uint8_t SPISend(uint8_t byte);
 
//Funkcja pozwalajaca na przesylanie danych do ukladu
static void NOKIACMD(uint8_t cmd);
 
//Inicjalicja wyswietlacza
void NokiaInit(void);
 
//Czyszczenie wyswietlacza
void LCDClear(void);
 
void LCDDraw(int row, int col, const char* text);
 
//Inicjalizacja portów GPIO
void GPIOInit_Nokia(void);
 
//Inicjalizacja SPI
void SPIInit_Nokia(void);
 
void LCDCopy(void);
 
void ADCInit(void);
void DMAInit(void);
 
int main(void)
{
 //Deklaracja zmiennych
 char res[20];
 int pomocnicza = 0;
 int i = 0;
 
 ADCInit();
 DMAInit();
 DELAY_Init();
 GPIOInit_Nokia();
 SPIInit_Nokia();
 NokiaInit();
 
 LCDClear();
 
 while(1)
 {
  LCDDraw(1, 1, "Nokia");
   LCDDraw(2, 1, "Odczyt z ADC:");
 
  //Pobranie 10 wartosci z DMA
  for(i=0;i<10;i++)
  {
   pomocnicza = pomocnicza + ADCValue[0];
  }
 
  //Wyciagniecie sredniej arytmetycznej
  pomocnicza = (pomocnicza / 10);
 
  //Wyswietlenie wyniku
  sprintf(res, "A:%d", pomocnicza);
  LCDDraw(3, 1, res);
 
  sprintf(res, "V:%.2f", ((3.3/4096.0) * (pomocnicza)));
  LCDDraw(4, 1, res);
 
  //Wyzerowanie zmiennej
  pomocnicza = 0;
 
  Delayms(500);
  LCDClear();
  }
}
 
void ADCInit()
{
  //Definicja struktury
  ADC_InitTypeDef ADCInit;
  GPIO_InitTypeDef GPIOInit;
 
  //Konfiguracja zegarów
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
  //Zegar dla portu ADC
  RCC_AHB1PeriphClockCmd(RCC_AHB1ENR_GPIOCEN, ENABLE);
 
  //Konfiguracja pinu analogowego
  //Kanal 12 podlaczony do PC2
  GPIOInit.GPIO_Pin = GPIO_Pin_2;
  GPIOInit.GPIO_Mode = GPIO_Mode_AN;
  GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIOInit.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_Init(GPIOC, &GPIOInit);
 
  //Konfiguracja ADC
  ADC_DeInit();
  //Przetworzone dane wyrównane do prawej
  ADCInit.ADC_DataAlign = ADC_DataAlign_Right;
  //Dane wejsciowe przetwarzane w 12 bitowy numer 
  //z duza dokladnoscia, max 4096
  ADCInit.ADC_Resolution = ADC_Resolution_12b;
  //Konwersacja jest ciagla przetwarzanie 
  //wiecej niz jeden raz
  ADCInit.ADC_ContinuousConvMode = ENABLE;
  ADCInit.ADC_ExternalTrigConv = ADC_ExternalTrigConvEdge_None;
  //Bez wyzwalania zewnetrznego
  ADCInit.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
  //Liczba konwersji
  ADCInit.ADC_NbrOfConversion = 16;
  //Pomiar jednego kanalu, skanowanie kanalow zostalo wylaczone
  ADCInit.ADC_ScanConvMode = DISABLE;
  //Inicjalizacja ADC
  ADC_Init(ADC1, &ADCInit);
 
 
  //Wybranie kanalu z którego bedzie odczytywane
  //ADC kanal 12, GPIOC2, Czestotliwosc probkowania = 1Mhz
  ADC_RegularChannelConfig( ADC1, ADC_Channel_12, 1, ADC_SampleTime_144Cycles);
 
  ADC_DMARequestAfterLastTransferCmd(ADC1, ENABLE);
 
  //Wlaczenie DMA dla ADC1
  ADC_DMACmd(ADC1, ENABLE);
 
  //Wlaczenie konwersji ADC
  ADC_Cmd(ADC1,ENABLE);
 
  //Rozpoczecie konwersji
  ADC_SoftwareStartConv(ADC1);
}
 
//Konfiguracja DMA
void DMAInit(void)
{
 DMA_InitTypeDef DMAInit;
 
 DMA_StructInit(&DMAInit);
 
 //Wlaczenie zegara taktujacego
 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
 
 //Wlaczenie ustawien standardowych
 DMA_DeInit(DMA2_Stream4);
 DMAInit.DMA_Channel = DMA_Channel_0;
 //deklaracja zrodla danych, rejestr ADC1 DR
 DMAInit.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
 //Adres poczatku bloku do przeslania,
 DMAInit.DMA_Memory0BaseAddr = (uint32_t)&ADCValue[0];
 //Kierunek transferu danych z urzadzenia do pamieci
 DMAInit.DMA_DIR = DMA_DIR_PeripheralToMemory;
 //Rozmiar bufora
 DMAInit.DMA_BufferSize = 1;
 //Wylaczenie automatycznego zwiekszania adresu ADC
 DMAInit.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
 //Wylaczenie obslugi transferu z pamieci do pamieci
 DMAInit.DMA_MemoryInc = DMA_MemoryInc_Enable;
 //Rozmiar przesylanych danych ADC, 16b
 DMAInit.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
 //Rozmiar przesylanych danych po stronie pamieci
 DMAInit.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
 //Tryb pracy ciagly
 DMAInit.DMA_Mode = DMA_Mode_Circular;
 //Ustawienie priorytetu na wysoki
 DMAInit.DMA_Priority = DMA_Priority_High;
 
 //Wylaczenie trybu fifom, automatycznie wlaczony tryb bezposredni
 DMAInit.DMA_FIFOMode = DMA_FIFOMode_Disable;
 DMAInit.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
 
 //Wyzwalanie pojedyncze
 DMAInit.DMA_MemoryBurst = DMA_MemoryBurst_Single;
 DMAInit.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
 
 //Inicjalizacja DMA
 DMA_Init(DMA2_Stream4, &DMAInit);
 //Wlaczenie DMA
 DMA_Cmd(DMA2_Stream4, ENABLE);
}
 
//Wyslanie komendy na wyswietlacz
static uint8_t SPISend(uint8_t byte)
{
  //Petla while czeka az bufor nadawczy bedzie wolny
  while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);
  GPIO_ResetBits(GPIOC, LCD_CE);
  SPI_I2S_SendData(SPI2, byte);
 
  // poczekaj na dane w buforze odbiorczym
  while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);
  GPIO_SetBits(GPIOC, LCD_CE);
  return SPI_I2S_ReceiveData(SPI2);
}
 
//Funkcja pozwalajaca na przesylanie danych do ukladu
static void NOKIACMD(uint8_t cmd)
{
  //Ustawienie stanu niskiego na CE i DC
  GPIO_ResetBits(GPIOC, LCD_CE|LCD_DC);
  //Przeslanie komendy
  SPISend(cmd);
  //Ustawienie stanu wysokiego na ChipSelect
  GPIO_SetBits(GPIOC, LCD_CE);
}
 
//Inicjalicja wyswietlacza
void NokiaInit(void)
{
  //Sygnal niski na RST, reset wyswietlacza
  GPIO_ResetBits(GPIOC, LCD_RST);
 
  Delayms(2);
 
  //Stan wysoki na pin reset
  GPIO_SetBits(GPIOC, LCD_RST);
 
  //Wlaczenie zasilania wyswietlacza
  //Function SET 00100001
  NOKIACMD(0x21);
 
  //Napiecie zasilania matrycy VOP 11001000
  NOKIACMD(0xc8);
 
  //Wybór krzywej kompensacji temperatury
  //00001100
  NOKIACMD(0x06);
 
  //Wspólczynnik multipleksowania 00010011
  NOKIACMD(0x13);
 
  //Wlaczenie zasilania matrycy 00100000
  NOKIACMD(0x20);
 
  //Standardowy tryb pracy 00001100
  //Do wyboru sa dwa tryby pracy
  NOKIACMD(0x0c);
 
  //Zresetowanie licznika wierszy
  NOKIACMD(0x40);
 
  //Zresetowanie liczniki kolumn
  NOKIACMD(0x80);
}
 
//Czyszczenie wyswietlacza
void LCDClear(void)
{
  //void * memset ( void * buffer, int c, size_t num );
  //Wypelnia kolejne bajty w pamieci ustalona wartoscia
  //W tym przypadku jest to zero, czyszczony jest wyswietlacz
  //Ustawienie stanu niskiego na CE i DC
  memset(lcd_buffer, 0, (84 * 48 / 8));
}
 
void LCDDraw(int row, int col, const char* text)
{
  //Deklaracja zmiennych
  int i;
  uint8_t* pbuf = &lcd_buffer[row * 84 + col];
 
  //Dopóki bedzie jakis tekst do wyswietlania oraz bedzie mozliwosc
  //wpisania danych wtedy funkcja bedzie wykonywana
  while ((*text) && (pbuf < &lcd_buffer[(84 * 48 / 8) - 6]))
  {
  //Wskaznik na wartosc, zostaje ona zwiekszona o 1
  int ch = *text++;
  //Deklaracja zmiennej w kodzie ASCII 
  //z wskaznikiem na jej adres.
  //Liczenie od 0 odejmnowanie znaku spacji
  const uint8_t* font = &font_ASCII[ch - ' '][0];
  //Przejscie w tablicy po poszczególnych
  //wartosciach podanego znaku
  for (i = 0; i < 5; i++)
   *pbuf++ = *font++;
  *pbuf++ = 0;
  }
 
  i=0;
 
  //Ustawienie lini DC na wysoki
  GPIO_SetBits(GPIOC, LCD_DC);
  //Wybranie urzadzenia, niski na CE
  GPIO_ResetBits(GPIOC, LCD_CE);
 
  for (i = 0; i < (84 * 48 / 8); i++)
  SPISend(lcd_buffer[i]);
 
  GPIO_SetBits(GPIOC, LCD_CE);
}
 
//Inicjalizacja portów GPIO
void GPIOInit_Nokia(void)
{
  GPIO_InitTypeDef GPIOInit;
 
  //Wlaczenie zegarów
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
 
  //Definicja funkcji alternatywnych dla pinów MOSI oraz CLK
  GPIO_PinAFConfig(GPIOC, GPIO_PinSource3, GPIO_AF_SPI2);
  GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_SPI2);
 
  //Inicjalizacja pinów MOSI
  GPIO_StructInit(&GPIOInit);
  GPIOInit.GPIO_Pin = LCD_DIN;
  GPIOInit.GPIO_Mode = GPIO_Mode_AF;
  GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIOInit.GPIO_OType = GPIO_OType_PP;
  GPIOInit.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_Init(GPIOC, &GPIOInit);
 
  //Inicjalizacja SCK
  GPIO_StructInit(&GPIOInit);
  GPIOInit.GPIO_Pin = LCD_CLK;
  GPIOInit.GPIO_Mode = GPIO_Mode_AF;
  GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIOInit.GPIO_OType = GPIO_OType_PP;
  GPIOInit.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_Init(LCD_CLK_LINE, &GPIOInit);
 
 
  //Inicjalizacja MISO, niewykorzystywane
  GPIOInit.GPIO_Pin = GPIO_Pin_2;
  GPIOInit.GPIO_Mode = GPIO_Mode_AF;
  GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIOInit.GPIO_OType = GPIO_OType_PP;
  GPIOInit.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_Init(GPIOB, &GPIOInit);
 
  //Wlaczenie pozostalych linii
  GPIOInit.GPIO_Pin = LCD_DC|LCD_CE|LCD_RST;
  GPIOInit.GPIO_Mode = GPIO_Mode_OUT;
  GPIOInit.GPIO_PuPd = GPIO_PuPd_NOPULL;
  GPIOInit.GPIO_OType = GPIO_OType_PP;
  GPIOInit.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOC, &GPIOInit);
 
  //Ustawienie stanów wysokich na liniach CE oraz RST.
  GPIO_SetBits(GPIOC, LCD_CE|LCD_RST);
}
 
//Inicjalizacja SPI
void SPIInit_Nokia(void)
{
  SPI_InitTypeDef SPIInit;
 
  //Wlaczenie zegara dla SPI
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
 
  SPI_StructInit(&SPIInit);
  //Transmisja z wykorzystaniem dwóch linii
  SPIInit.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
  //Tryb pracy mikrokontrolera
  SPIInit.SPI_Mode = SPI_Mode_Master;
  //Stan sygnalu taktujacego przy braku transmisji
  SPIInit.SPI_CPOL = SPI_CPOL_Low;
  //Aktywne zbocze sygnalu taktujacego
  SPIInit.SPI_CPHA = SPI_CPHA_1Edge;
  //Wylaczenie sprzetowej obslugi linii CS
  SPIInit.SPI_NSS = SPI_NSS_Soft;
  //Rozmiar danych
  SPIInit.SPI_DataSize = SPI_DataSize_8b;
  //Co idzie pierwsze w transmisji
  SPIInit.SPI_FirstBit = SPI_FirstBit_MSB;
  //Szybkosc transmisji
  SPIInit.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;
  //Wylaczenie SPI
  SPI2->CR1 &= ~SPI_CR1_SPE;
  //Inicjalizacja SPI
  SPI_Init(SPI2, &SPIInit);
  //Wlaczenie SPI
  SPI_Cmd(SPI2, ENABLE);
}

Na wyświetlaczu pokaże się wartość odczytana z portu ADC w normalnej postaci oraz przetworzonej na napięcie. Całość spięta na płytce stykowej wygląda tak jak na rysunku poniżej.


Rys. 1. Prezentacja danych na wyświetlaczu

Aby korzystać z funkcji sprintf w kompilatorze System Workbench AC6 należy wprowadzić odpowiednią linijkę w następującym miejscu:
PPM na projekt->Properties->C/C++ Build->Settings->Tool Settings->MCU GCC Linker->Miscellaneous. Tam w wąskim pasku opisanym jako Linker flags należy wpisać "-specs=nosys.specs -specs=nano.specs -u _printf_float", oczywiście bez cudzysłowów. Po takiej operacji powinno wszystko działać bardzo dobrze.