Koneksi
Kami akan menghubungkan tampilan ke mikrokontroler melalui antarmuka SPI1 sebagai berikut:
- VDD-> + 3.3V
- GND-> Ground
- SCK -> PA5
- SDA -> PA7 (MOSI)
- RES-> PA1
- CS-> PA2
- DS-> PA3


Transmisi data terjadi pada tepi naik dari sinyal sinkronisasi pada 1 byte per frame. Garis SCK dan SDA digunakan untuk mentransfer data melalui antarmuka SPI, RES - mem-boot ulang pengontrol tampilan pada level logika rendah, CS bertanggung jawab untuk memilih perangkat pada bus SPI pada level logika rendah, DS menentukan jenis data (perintah - 1 / data - 0) yang dikirimkan layar. Karena tidak ada yang bisa dibaca dari tampilan, kami tidak akan menggunakan keluaran MISO.
Organisasi Memori Pengontrol Tampilan
Sebelum menampilkan apa pun di layar, Anda perlu memahami bagaimana memori diatur dalam pengontrol ssd1306.


Semua memori grafis (GDDRAM) adalah luas 128 * 64 = 8192 bit = 1KB. Area ini dibagi menjadi 8 halaman, yang disajikan sebagai kumpulan 128 segmen 8-bit. Memori ditangani oleh nomor halaman dan nomor segmen, masing-masing.
Dengan metode pengalamatan ini, ada fitur yang sangat tidak menyenangkan - ketidakmungkinan menulis 1 bit informasi ke dalam memori, karena perekaman terjadi di segmen (masing-masing 8 bit). Dan karena untuk tampilan yang benar dari satu piksel di layar, Anda perlu mengetahui status piksel yang tersisa di segmen tersebut, disarankan untuk membuat buffer 1 KB di memori mikrokontroler dan secara siklis memuatnya ke dalam memori tampilan (di sinilah DMA berguna), masing-masing, membuat pembaruan lengkapnya. Saat menggunakan metode ini, dimungkinkan untuk menghitung ulang posisi setiap bit dalam memori ke koordinat klasik x, y. Kemudian untuk menampilkan sebuah titik dengan koordinat x dan y, kita akan menggunakan metode berikut:
displayBuff[x+(y/8)*SSD1306_WIDTH]|=(1<<(y%8));
Dan untuk menghapus intinya
displayBuff[x+(y/8)*SSD1306_WIDTH]&=~(1<<(y%8));
Penyiapan SPI
Seperti disebutkan di atas, kami akan menghubungkan layar ke SPI1 dari mikrokontroler STM32F103C8.

Untuk kemudahan penulisan kode, kami akan mendeklarasikan beberapa konstanta dan membuat fungsi untuk menginisialisasi SPI.
#define SSD1306_WIDTH 128
#define SSD1306_HEIGHT 64
#define BUFFER_SIZE 1024
// , /
#define CS_SET GPIOA->BSRR|=GPIO_BSRR_BS2
#define CS_RES GPIOA->BSRR|=GPIO_BSRR_BR2
#define RESET_SET GPIOA->BSRR|=GPIO_BSRR_BS1
#define RESET_RES GPIOA->BSRR|=GPIO_BSRR_BR1
#define DATA GPIOA->BSRR|=GPIO_BSRR_BS3
#define COMMAND GPIOA->BSRR|=GPIO_BSRR_BR3
void spi1Init()
{
return;
}
Nyalakan pencatatan jam kerja dan konfigurasikan keluaran GPIO, seperti yang ditunjukkan pada tabel di atas.
RCC->APB2ENR|=RCC_APB2ENR_SPI1EN | RCC_APB2ENR_IOPAEN;// SPI1 GPIOA
RCC->AHBENR|=RCC_AHBENR_DMA1EN;// DMA
GPIOA->CRL|= GPIO_CRL_MODE5 | GPIO_CRL_MODE7;//PA4,PA5,PA7 50MHz
GPIOA->CRL&= ~(GPIO_CRL_CNF5 | GPIO_CRL_CNF7);
GPIOA->CRL|= GPIO_CRL_CNF5_1 | GPIO_CRL_CNF7_1;//PA5,PA7 - push-pull, PA4 - push-pull
Selanjutnya, mari konfigurasikan SPI ke mode master dan frekuensi 18 MHz.
SPI1->CR1|=SPI_CR1_MSTR;//
SPI1->CR1|= (0x00 & SPI_CR1_BR);// 2
SPI1->CR1|=SPI_CR1_SSM;// NSS
SPI1->CR1|=SPI_CR1_SSI;//NSS - high
SPI1->CR2|=SPI_CR2_TXDMAEN;// DMA
SPI1->CR1|=SPI_CR1_SPE;// SPI1
Mari kita siapkan DMA.
DMA1_Channel3->CCR|=DMA_CCR1_PSIZE_0;// 1
DMA1_Channel3->CCR|=DMA_CCR1_DIR;// DMA
DMA1_Channel3->CCR|=DMA_CCR1_MINC;//
DMA1_Channel3->CCR|=DMA_CCR1_PL;// DMA
Selanjutnya kita akan menulis fungsi untuk mengirimkan data melalui SPI (selama ini tanpa DMA). Proses pertukaran data adalah sebagai berikut:
- Menunggu SPI dirilis
- CS = 0
- Mengirim data
- CS = 1
void spiTransmit(uint8_t data)
{
CS_RES;
SPI1->DR = data;
while((SPI1->SR & SPI_SR_BSY))
{};
CS_SET;
}
Kami juga akan menulis fungsi untuk mengirimkan perintah secara langsung ke layar (Kami mengganti jalur DC hanya saat mengirimkan perintah, dan kemudian mengembalikannya ke status "data", karena kami tidak akan sering mengirimkan perintah dan tidak akan kehilangan performa).
void ssd1306SendCommand(uint8_t command)
{
COMMAND;
spiTransmit(command);
DATA;
}
Selanjutnya, kita akan membahas fungsi untuk bekerja secara langsung dengan DMA, untuk ini kita akan mendeklarasikan buffer di memori mikrokontroler dan membuat fungsi untuk memulai dan menghentikan pengiriman siklik buffer ini ke memori layar.
static uint8_t displayBuff[BUFFER_SIZE];//
void ssd1306RunDisplayUPD()
{
DATA;
DMA1_Channel3->CCR&=~(DMA_CCR1_EN);// DMA
DMA1_Channel3->CPAR=(uint32_t)(&SPI1->DR);// DMA SPI1
DMA1_Channel3->CMAR=(uint32_t)&displayBuff;//
DMA1_Channel3->CNDTR=sizeof(displayBuff);//
DMA1->IFCR&=~(DMA_IFCR_CGIF3);
CS_RES;//
DMA1_Channel3->CCR|=DMA_CCR1_CIRC;// DMA
DMA1_Channel3->CCR|=DMA_CCR1_EN;// DMA
}
void ssd1306StopDispayUPD()
{
CS_SET;//
DMA1_Channel3->CCR&=~(DMA_CCR1_EN);// DMA
DMA1_Channel3->CCR&=~DMA_CCR1_CIRC;//
}
Inisialisasi layar dan keluaran data
Sekarang mari kita buat fungsi untuk menginisialisasi layar itu sendiri.
void ssd1306Init()
{
}
Pertama, kita akan mengkonfigurasi jalur CS, RESET dan DC, dan juga mengatur ulang pengontrol tampilan.
uint16_t i;
GPIOA->CRL|= GPIO_CRL_MODE2 |GPIO_CRL_MODE1 | GPIO_CRL_MODE3;
GPIOA->CRL&= ~(GPIO_CRL_CNF1 | GPIO_CRL_CNF2 | GPIO_CRL_CNF3);//PA1,PA2,PA3
//
RESET_RES;
for(i=0;i<BUFFER_SIZE;i++)
{
displayBuff[i]=0;
}
RESET_SET;
CS_SET;//
Selanjutnya, kami akan mengirimkan urutan perintah untuk inisialisasi (Anda dapat mempelajari lebih lanjut tentang mereka di dokumentasi untuk pengontrol ssd1306).
ssd1306SendCommand(0xAE); //display off
ssd1306SendCommand(0xD5); //Set Memory Addressing Mode
ssd1306SendCommand(0x80); //00,Horizontal Addressing Mode;01,Vertical
ssd1306SendCommand(0xA8); //Set Page Start Address for Page Addressing
ssd1306SendCommand(0x3F); //Set COM Output Scan Direction
ssd1306SendCommand(0xD3); //set low column address
ssd1306SendCommand(0x00); //set high column address
ssd1306SendCommand(0x40); //set start line address
ssd1306SendCommand(0x8D); //set contrast control register
ssd1306SendCommand(0x14);
ssd1306SendCommand(0x20); //set segment re-map 0 to 127
ssd1306SendCommand(0x00); //set normal display
ssd1306SendCommand(0xA1); //set multiplex ratio(1 to 64)
ssd1306SendCommand(0xC8); //
ssd1306SendCommand(0xDA); //0xa4,Output follows RAM
ssd1306SendCommand(0x12); //set display offset
ssd1306SendCommand(0x81); //not offset
ssd1306SendCommand(0x8F); //set display clock divide ratio/oscillator frequency
ssd1306SendCommand(0xD9); //set divide ratio
ssd1306SendCommand(0xF1); //set pre-charge period
ssd1306SendCommand(0xDB);
ssd1306SendCommand(0x40); //set com pins hardware configuration
ssd1306SendCommand(0xA4);
ssd1306SendCommand(0xA6); //set vcomh
ssd1306SendCommand(0xAF); //0x20,0.77xVcc
Mari buat fungsi untuk mengisi seluruh layar dengan warna yang dipilih dan menampilkan satu piksel.
typedef enum COLOR
{
BLACK,
WHITE
}COLOR;
void ssd1306DrawPixel(uint16_t x, uint16_t y,COLOR color){
if(x<SSD1306_WIDTH && y <SSD1306_HEIGHT && x>=0 && y>=0)
{
if(color==WHITE)
{
displayBuff[x+(y/8)*SSD1306_WIDTH]|=(1<<(y%8));
}
else if(color==BLACK)
{
displayBuff[x+(y/8)*SSD1306_WIDTH]&=~(1<<(y%8));
}
}
}
void ssd1306FillDisplay(COLOR color)
{
uint16_t i;
for(i=0;i<SSD1306_HEIGHT*SSD1306_WIDTH;i++)
{
if(color==WHITE)
displayBuff[i]=0xFF;
else if(color==BLACK)
displayBuff[i]=0;
}
}
Selanjutnya, di badan program utama, kami menginisialisasi SPI dan tampilan.
RccClockInit();
spi1Init();
ssd1306Init();
Fungsi RccClockInit () dimaksudkan untuk menyetel jam mikrokontroler.
Kode RccClockInit
int RccClockInit()
{
//Enable HSE
//Setting PLL
//Enable PLL
//Setting count wait cycles of FLASH
//Setting AHB1,AHB2 prescaler
//Switch to PLL
uint16_t timeDelay;
RCC->CR|=RCC_CR_HSEON;//Enable HSE
for(timeDelay=0;;timeDelay++)
{
if(RCC->CR&RCC_CR_HSERDY) break;
if(timeDelay>0x1000)
{
RCC->CR&=~RCC_CR_HSEON;
return 1;
}
}
RCC->CFGR|=RCC_CFGR_PLLMULL9;//PLL x9
RCC->CFGR|=RCC_CFGR_PLLSRC_HSE;//PLL sourse:HSE
RCC->CR|=RCC_CR_PLLON;//Enable PLL
for(timeDelay=0;;timeDelay++)
{
if(RCC->CR&RCC_CR_PLLRDY) break;
if(timeDelay>0x1000)
{
RCC->CR&=~RCC_CR_HSEON;
RCC->CR&=~RCC_CR_PLLON;
return 2;
}
}
FLASH->ACR|=FLASH_ACR_LATENCY_2;
RCC->CFGR|=RCC_CFGR_PPRE1_DIV2;//APB1 prescaler=2
RCC->CFGR|=RCC_CFGR_SW_PLL;//Switch to PLL
while((RCC->CFGR&RCC_CFGR_SWS)!=(0x02<<2)){}
RCC->CR&=~RCC_CR_HSION;//Disable HSI
return 0;
}
Isi seluruh tampilan dengan warna putih dan lihat hasilnya.
ssd1306RunDisplayUPD();
ssd1306FillDisplay(WHITE);

Mari menggambar di layar dalam kotak dengan peningkatan 10 piksel.
for(i=0;i<SSD1306_WIDTH;i++)
{
for(j=0;j<SSD1306_HEIGHT;j++)
{
if(j%10==0 || i%10==0)
ssd1306DrawPixel(i,j,WHITE);
}
}

Fungsi ini bekerja dengan benar, buffer terus menerus ditulis ke memori pengontrol tampilan, yang memungkinkan penggunaan sistem koordinat Cartesian saat menampilkan grafik primitif.
Menampilkan kecepatan refresh
Karena buffer dikirim secara siklis ke memori tampilan, itu akan cukup untuk mengetahui waktu yang dibutuhkan DMA untuk mentransfer data ke perkiraan kasar dari kecepatan refresh tampilan. Untuk debugging real-time, kita akan menggunakan perpustakaan EventRecorder Keil.
Untuk mengetahui saat akhir transfer data, kami mengkonfigurasi interupsi DMA untuk mengakhiri transfer.
DMA1_Channel3->CCR|=DMA_CCR1_TCIE;//
DMA1->IFCR&=~DMA_IFCR_CTCIF3;//
NVIC_EnableIRQ(DMA1_Channel3_IRQn);//
Kami akan melacak interval waktu menggunakan fungsi EventStart dan EventStop.

Kami mendapatkan 0,00400881-0,00377114 = 0,00012767 dtk, yang sesuai dengan kecepatan refresh 4,2 KHz. Faktanya, frekuensinya tidak terlalu tinggi, yang disebabkan oleh ketidakakuratan metode pengukuran, tetapi jelas lebih dari standar 60 Hz.
Tautan
- Proyek di Keil
- STM32F103 referensi manual
- lembar data ssd1306