STM32F103C8T6 + SSD1306 OLED
: niedziela 15 sty 2017, 20:29
Post ten (pseudo-artykuł) będzie traktował o uruchomieniu magistrali I2C na mikrokontrolerze STM32F103 na malutkiej, taniutkiej, chińskiej płyteczce ewaluacyjnej w połączeniu z malutkim, chińskim ekranikiem OLED o wymiarach 128x32. Z racji tego, że ekran nie lubi komunikować się z uC, a jedynie przyjmuje dane ( no może oprócz odpowiedzi w formie ACK ), zajmiemy się tylko konfiguracja i wysyłaniem danych.
Do pisania używać będziemy środowiska Atollic 7.0.1 Lite. Kwestie ściągnięcia, instalacji i uruchomienia nie będą tu poruszane.
Obsługa wyświetlacza zostanie uruchomiona z wykorzystaniem bibliotek SPL. Dlaczego nie HAL? SPL wciąż cieszy się sporą popularnością, poza tym kod jest, wbrew pozorom, łatwiejszy do zrozumienia.
Poniżej linki do ebay.com do użytych w tym materiale narzędzi: (proszę zgłaszać nie działające linki)
http://www.ebay.ca/itm/STM32F103C8T6-ARM-STM32-Minimum-System-Development-Board-Module-For-Arduino-D-/112035273840?hash=item1a15d29c70:g:CioAAOSwMNxXaqSp
http://www.ebay.com/itm/IIC-I2C-0-91-128x32-white-OLED-LCD-Display-Module-3-3v-5v-For-Arduino-PIC-/152106706368?hash=item236a4425c0:g:BHQAAOSw6btXSWm3
Podstawowa konfiguracja i rozpoczęcie projektu.
Na poczatku wybieramy opcję FILE->NEW->C PROJECT
klikamy NEXT
Następnie nadajemy nazwę dla naszego projektu i wybieramy EMBEDDED C PROJECT z ramki po lewej i ATOLLIC ARM TOOLS z prawej strony
klikamy NEXT
Teraz wybieramy procek z jakim mamy do czynienia. Żeby było szybciej, górze mamy okno filtra i wpiszemy tam nazwę naszego kontrolera, czyli STM32F103c8, w tym czasie w oknie DEVICE powinien pojawić się właściwy i na niego klikamy. Automatycznie wypełnią się podstawowe dane łącznie z informacją o dostępnej ilości pamięci FLASH i RAM.
klikamy NEXT
Następna opcja to wybór biblioteki i opcje optymalizacji, niczego tu nie zmieniamy
klikamy NEXT
Wybieramy programator. Z racji, że używam J-LINK'a (w sumie jego klona) na obrazku własnie ten jest wybrany, ale są tam inne opcje i należy wybrać własciwą i odpowiednią dla nas i naszego sprzętu.
klikamy NEXT
I na koniec możemy wybrać konfiguracje z jakimi będziemy pracować, ja zawszę zostawiam jak jest, czyli opcje Debug oraz Release. Z reguły używam tylko opcji Debug, dlatego że tylko pracuję nad projektem, a nie wcielam go w życie w jakimś urządzeniu.
klikamy FINISH
Na naszym ekranie powinno pojawić się coś w ten desń
klikamy RESTORE, tam gdzie wskazuje strzałka i powinno pojawić się nam to:
Strzałkami zaznaczone jest:
1 - Outline, to możemy zminimalizować, raczej nam to nie będzie potrzebne
2 - Information Center, to polecam zamknąć, bo raczej przeglądarka ze stroną informacyjną Atollica nam się nie przyda
3 - Tu przełączamy na CONSOLE, jest to najważniejsze miejsce zawierające wszystko o wynikach naszej kompilacji, czyli błędy, ostrzeżenia
4 - Build Analyzer, .. to niestety nie będzie nam potrzebne, dlatego, że jest to opcja działająca w wersji PRO Atollica.
W edytorze pliku main.c ja z reguły pozbywam się całej wygenerowanej automatycznie zawrtości, dlatego że w większości zawiera dane debbugera i opcje dla zestawów, których nie posiadamy.
CTRL-A i DEL
plik main.c
I tu się wszystko zaczyna. Na początek zaczynamy od “inkludowania” najpotrzebniejszych plików systemowych.
Teraz kolej na definicje funkcji które będziemy konstruować
Teraz zajmiemy się utworzeniem tabeli zawierającej zestaw komend odpowiedzialnych za inicjalizację wyświetlacza. ( i tu należą się wielkie podziękowania koledze Antystatycznemu za znalezienie i optymalizację sekwencji inicjalizującej ten wyświetlacz ). Tablica jest static, by była widoczna tylko w tej jednostce kompilacji:
Zadeklarujmy jeszcze bufor, który odzwierciedla całą pamięć graficzną naszego wyświetlaczyka. SSD1306_Width, SSD_Height to makra zdefiniowane w pliku SSD1306.h i definiują szerokość i wysokość ekranu.
Ekran składa się z czterech stron ułożonych poziomo. Każda strona składa się ze 128-miu bajtów. Każdy bajt strony to osiem pikseli (bitów) na ekranie ułożonych w pionie.
Jak widzimy na obrazku wpisanie do pierwszego bajtu bufora liczby 1 zaświeci nam punkt w lewym górnym rogu
a jeżeli wpiszemy 0xFF (255, czyli wypełnimy bajt jedynkami) zaświeci się nam pionowa linia po lewej stronie ekranu. Linia ta symbolizuje też wysokość pierwszej strony pamięci wyświetlacza.
----------------------------------------------------------------------------------------------
Zaczynamy z właściwym programem.
Na początek trzeba zainicjalizować piny odpowiedzialne za I2C. Dla F103 są to PB6 i PB7 - SCL,SDA. Zaczynamy więc od przypisana Definicji Typu Inicjalizacyjnego dla portów wejścia-wyjścia ogólnego przeznaczenia:
Aby móc korzystać z portów GPIO [ i nie tylko ] na mikrokontrolerach z rdzeniem Cortex-M3 [ i nie tylko ] koniecznie trzeba (należy?) zacząć konfigurację portu od uruchomienia sygnału zegarowego dla danego peryferium. W tym przypadku będzie to:
Kolejną rzeczą jaką musimy zrobić to “odblokować” alternatywne funkcje pinów. Jest to nam niezbędne do uruchomienia wbudowanego, sprzętowego I2C:
Następnym krokiem jest uruchomienie magistrali I2C:
I teraz, kiedy mamy już włączone podstawowe elementy uC, możemy skonfigurować właściwe piny. Najpierw struktura inicjalizacji GPIO (ta, którą powołaliśmy do życia chwilę wcześniej):
Ustalmy, z którymi pinami chcemy pracować:
Następnie podajemy informację o tym, że w/w piny będą pracować ze swoją alternatywną funkcją:
(*OD oznacza Open Drain, I2C wymaga aby jego piny pracowały w ten sposób)...to chyba kazdy wie, co? A jeśli nie wie, to RTFM
Kolejnym krokiem konfiguracji pinów jest określenie maksymalnej prędkości, z jaką mogą pracować:
(umawiamy się na 50 MHz, czyli maksimum dla tego procesora. Nie zasilamy układu bateryjnie, więc nie musimy się zbytnio przejmować oszczędzaniem energii).
I na koniec wstępu do początku wcielimy w życie strukturę, którą właśnie skonfigurowaliśmy:
Kolejny etap to konfiguracja I2C.
Jak przy konfiguracji pinów powołujemy do życia strukturę inicjalizacyjną magistralę.
Tak samo jak w przypadku GPIO będziemy ustawiać teraz parametry pracy I2C.
Ustalimy tutaj coś, co możemy nazwać wypełnieniem zegara. Mamy do wyboru dwie opcje 2 i 16/9. Chodzi o stosunek długości stanu niskiego do stanu wysokiego na wyjściu zegarowym szyny. Nas interesuje opcja 2, czyli stosunek stanów zegara wynosi 1:1..
Przypisanie adresu układu MASTER (W naszym przypadku nieistotne):
Włączenie żądania (oczekiwania) na odpowiedź od urządzenia
Teraz decydujemy o długości adresów układów , z którymi będziemy się komunikować. Możliwe opcje to adres 7 bitowy lub 10 bitowy. Większość układów SLAVE posiada 7 bitowy adres. Aby uniknąć pomyłki, zawsze należy zajrzeć do dokumentacji wykorzystywanego układu SLAVE.
W tym miejscu decydujemy w jakim trybie będzie pracowała nasza magistrala ( do wyboru jest jeszcze SMBus_Host i SMBus_Device, ale to nas nie interesuje)
… oraz, co bardzo ważne, ustalamy prędkość z jaką nasze urządzenia będą się komunikować (tu też bardzo ważne jest dokładne przeczytanie Datasheeta naszego wyświetlacza, czy czegokolwiek innego, bo właśnie tam producent podaje wartości z jakimi układ może pracowac. W naszym przypadku jest również ważna errata do DS, gdzie dowiadujemy się, iż nasz oledzik pracuje poprawnie tylko z prędkością 400KHz)
Inicjalizujemy strukturę
i włączamy “peryferium” (cudne słowo)
KONIEC CZĘŚCI PIERWSZEJ, NIE OSTATNIEJ.
Do pisania używać będziemy środowiska Atollic 7.0.1 Lite. Kwestie ściągnięcia, instalacji i uruchomienia nie będą tu poruszane.
Obsługa wyświetlacza zostanie uruchomiona z wykorzystaniem bibliotek SPL. Dlaczego nie HAL? SPL wciąż cieszy się sporą popularnością, poza tym kod jest, wbrew pozorom, łatwiejszy do zrozumienia.
Poniżej linki do ebay.com do użytych w tym materiale narzędzi: (proszę zgłaszać nie działające linki)
http://www.ebay.ca/itm/STM32F103C8T6-ARM-STM32-Minimum-System-Development-Board-Module-For-Arduino-D-/112035273840?hash=item1a15d29c70:g:CioAAOSwMNxXaqSp
http://www.ebay.com/itm/IIC-I2C-0-91-128x32-white-OLED-LCD-Display-Module-3-3v-5v-For-Arduino-PIC-/152106706368?hash=item236a4425c0:g:BHQAAOSw6btXSWm3
Podstawowa konfiguracja i rozpoczęcie projektu.
Na poczatku wybieramy opcję FILE->NEW->C PROJECT
klikamy NEXT
Następnie nadajemy nazwę dla naszego projektu i wybieramy EMBEDDED C PROJECT z ramki po lewej i ATOLLIC ARM TOOLS z prawej strony
klikamy NEXT
Teraz wybieramy procek z jakim mamy do czynienia. Żeby było szybciej, górze mamy okno filtra i wpiszemy tam nazwę naszego kontrolera, czyli STM32F103c8, w tym czasie w oknie DEVICE powinien pojawić się właściwy i na niego klikamy. Automatycznie wypełnią się podstawowe dane łącznie z informacją o dostępnej ilości pamięci FLASH i RAM.
klikamy NEXT
Następna opcja to wybór biblioteki i opcje optymalizacji, niczego tu nie zmieniamy
klikamy NEXT
Wybieramy programator. Z racji, że używam J-LINK'a (w sumie jego klona) na obrazku własnie ten jest wybrany, ale są tam inne opcje i należy wybrać własciwą i odpowiednią dla nas i naszego sprzętu.
klikamy NEXT
I na koniec możemy wybrać konfiguracje z jakimi będziemy pracować, ja zawszę zostawiam jak jest, czyli opcje Debug oraz Release. Z reguły używam tylko opcji Debug, dlatego że tylko pracuję nad projektem, a nie wcielam go w życie w jakimś urządzeniu.
klikamy FINISH
Na naszym ekranie powinno pojawić się coś w ten desń
klikamy RESTORE, tam gdzie wskazuje strzałka i powinno pojawić się nam to:
Strzałkami zaznaczone jest:
1 - Outline, to możemy zminimalizować, raczej nam to nie będzie potrzebne
2 - Information Center, to polecam zamknąć, bo raczej przeglądarka ze stroną informacyjną Atollica nam się nie przyda
3 - Tu przełączamy na CONSOLE, jest to najważniejsze miejsce zawierające wszystko o wynikach naszej kompilacji, czyli błędy, ostrzeżenia
4 - Build Analyzer, .. to niestety nie będzie nam potrzebne, dlatego, że jest to opcja działająca w wersji PRO Atollica.
W edytorze pliku main.c ja z reguły pozbywam się całej wygenerowanej automatycznie zawrtości, dlatego że w większości zawiera dane debbugera i opcje dla zestawów, których nie posiadamy.
CTRL-A i DEL
plik main.c
I tu się wszystko zaczyna. Na początek zaczynamy od “inkludowania” najpotrzebniejszych plików systemowych.
Kod: Zaznacz cały
#include <string.h> //zawiera memset
#include <stm32f10x.h> // to wszystkie podstawowe (i nie tylko) "cechy" naszego mikrokontrolera
#include <ssd1306.h> // a to pliczek zawierający komendy dzięki którym możemy sterować ekranemTeraz kolej na definicje funkcji które będziemy konstruować
Kod: Zaznacz cały
void oled_init(void); // funkcja inicjalizująca nasz ekran
void oled_cls(void); // funkcja czyszcząca
void update_screen(void); // funkcja wysyłająca zawartość bufora do sterownika ekranuTeraz zajmiemy się utworzeniem tabeli zawierającej zestaw komend odpowiedzialnych za inicjalizację wyświetlacza. ( i tu należą się wielkie podziękowania koledze Antystatycznemu za znalezienie i optymalizację sekwencji inicjalizującej ten wyświetlacz ). Tablica jest static, by była widoczna tylko w tej jednostce kompilacji:
Kod: Zaznacz cały
static uint8_t Init_Table[]=
{
SSD1306_DISPLAYOFF,
SSD1306_SETLOWCOLUMN,
SSD1306_SETHIGHCOLUMN,
SSD1306_SETPAGESTARTADDRESS,
SSD1306_SETSTARTLINE,
SSD1306_SEGREMAP | 0x01,
SSD1306_SETCOMPINS,
0x02, /* Set com pins data. */
SSD1306_SETDISPLAYOFFSET,
0x00, /* Set display offset data. No offset. */
SSD1306_COMSCANDEC,
SSD1306_NORMALDISPLAY,
SSD1306_DISPLAYALLON_RESUME,
SSD1306_SETCONTRAST,
0x01, /* Set contrast data. */
SSD1306_MEMORYMODE,
0x00, /* Memory addressing mode data. Horizontal addressing. */
SSD1306_SETMULTIPLEX,
0x1F, /* Set MUX ratio data. 1/32 duty cycle. */
SSD1306_SETPRECHARGE,
0xF1, /* Set pre-charge period data. */
SSD1306_SETVCOMDESELECT,
0x40, /* Set V com deselect data. */
SSD1306_CHARGEPUMP,
0x14, /* Charge pump setting data. */
SSD1306_DISPLAYON,
}Zadeklarujmy jeszcze bufor, który odzwierciedla całą pamięć graficzną naszego wyświetlaczyka. SSD1306_Width, SSD_Height to makra zdefiniowane w pliku SSD1306.h i definiują szerokość i wysokość ekranu.
Kod: Zaznacz cały
static uint8_t buffer [ SSD1306_Width * SSD_Height / 8 ];
Ekran składa się z czterech stron ułożonych poziomo. Każda strona składa się ze 128-miu bajtów. Każdy bajt strony to osiem pikseli (bitów) na ekranie ułożonych w pionie.
Jak widzimy na obrazku wpisanie do pierwszego bajtu bufora liczby 1 zaświeci nam punkt w lewym górnym rogu
a jeżeli wpiszemy 0xFF (255, czyli wypełnimy bajt jedynkami) zaświeci się nam pionowa linia po lewej stronie ekranu. Linia ta symbolizuje też wysokość pierwszej strony pamięci wyświetlacza.
----------------------------------------------------------------------------------------------
Zaczynamy z właściwym programem.
Na początek trzeba zainicjalizować piny odpowiedzialne za I2C. Dla F103 są to PB6 i PB7 - SCL,SDA. Zaczynamy więc od przypisana Definicji Typu Inicjalizacyjnego dla portów wejścia-wyjścia ogólnego przeznaczenia:
Kod: Zaznacz cały
GPIO_InitTypeDef gpio;Aby móc korzystać z portów GPIO [ i nie tylko ] na mikrokontrolerach z rdzeniem Cortex-M3 [ i nie tylko ] koniecznie trzeba (należy?) zacząć konfigurację portu od uruchomienia sygnału zegarowego dla danego peryferium. W tym przypadku będzie to:
Kod: Zaznacz cały
RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB);Kolejną rzeczą jaką musimy zrobić to “odblokować” alternatywne funkcje pinów. Jest to nam niezbędne do uruchomienia wbudowanego, sprzętowego I2C:
Kod: Zaznacz cały
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);Następnym krokiem jest uruchomienie magistrali I2C:
Kod: Zaznacz cały
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);I teraz, kiedy mamy już włączone podstawowe elementy uC, możemy skonfigurować właściwe piny. Najpierw struktura inicjalizacji GPIO (ta, którą powołaliśmy do życia chwilę wcześniej):
Kod: Zaznacz cały
GPIO_StructInit(&gpio);Ustalmy, z którymi pinami chcemy pracować:
Kod: Zaznacz cały
gpio.GPIO_pin = GPIO_Pin_6 | GPIO_Pin_7; // SCL, SDA odpowiednioNastępnie podajemy informację o tym, że w/w piny będą pracować ze swoją alternatywną funkcją:
Kod: Zaznacz cały
gpio.GPIO_Mode = GPIO_Mode_AF_OD; // alternatywna funkcja, Open Drain(*OD oznacza Open Drain, I2C wymaga aby jego piny pracowały w ten sposób)...to chyba kazdy wie, co? A jeśli nie wie, to RTFM
Kolejnym krokiem konfiguracji pinów jest określenie maksymalnej prędkości, z jaką mogą pracować:
Kod: Zaznacz cały
gpio.GPIO_Speed = GPIO_Speed_50MHz;(umawiamy się na 50 MHz, czyli maksimum dla tego procesora. Nie zasilamy układu bateryjnie, więc nie musimy się zbytnio przejmować oszczędzaniem energii).
I na koniec wstępu do początku wcielimy w życie strukturę, którą właśnie skonfigurowaliśmy:
Kod: Zaznacz cały
GPIO_Init(GPIOB, &gpio);
Kolejny etap to konfiguracja I2C.
Jak przy konfiguracji pinów powołujemy do życia strukturę inicjalizacyjną magistralę.
Kod: Zaznacz cały
I2C_InitTypeDef i2c;
I2C_StructInit(&i2c);Tak samo jak w przypadku GPIO będziemy ustawiać teraz parametry pracy I2C.
Ustalimy tutaj coś, co możemy nazwać wypełnieniem zegara. Mamy do wyboru dwie opcje 2 i 16/9. Chodzi o stosunek długości stanu niskiego do stanu wysokiego na wyjściu zegarowym szyny. Nas interesuje opcja 2, czyli stosunek stanów zegara wynosi 1:1..
Kod: Zaznacz cały
i2c.I2C_DutyCycle = I2C_DutyCycle_2;Przypisanie adresu układu MASTER (W naszym przypadku nieistotne):
Kod: Zaznacz cały
i2c.I2C_OwnAddress1 = 0x00;Włączenie żądania (oczekiwania) na odpowiedź od urządzenia
Kod: Zaznacz cały
i2c.I2C_Ack = I2C_Ack_Enable;Teraz decydujemy o długości adresów układów , z którymi będziemy się komunikować. Możliwe opcje to adres 7 bitowy lub 10 bitowy. Większość układów SLAVE posiada 7 bitowy adres. Aby uniknąć pomyłki, zawsze należy zajrzeć do dokumentacji wykorzystywanego układu SLAVE.
Kod: Zaznacz cały
i2c.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;W tym miejscu decydujemy w jakim trybie będzie pracowała nasza magistrala ( do wyboru jest jeszcze SMBus_Host i SMBus_Device, ale to nas nie interesuje)
Kod: Zaznacz cały
i2c.I2C_Mode = I2C_Mode_I2C;… oraz, co bardzo ważne, ustalamy prędkość z jaką nasze urządzenia będą się komunikować (tu też bardzo ważne jest dokładne przeczytanie Datasheeta naszego wyświetlacza, czy czegokolwiek innego, bo właśnie tam producent podaje wartości z jakimi układ może pracowac. W naszym przypadku jest również ważna errata do DS, gdzie dowiadujemy się, iż nasz oledzik pracuje poprawnie tylko z prędkością 400KHz)
Kod: Zaznacz cały
i2c.I2C_ClockSpeed = 400000;Inicjalizujemy strukturę
Kod: Zaznacz cały
I2C_Init(I2C1, &i2c);i włączamy “peryferium” (cudne słowo)
Kod: Zaznacz cały
I2C_Cmd(I2C1, ENABLE);KONIEC CZĘŚCI PIERWSZEJ, NIE OSTATNIEJ.