Menghubungkan layar OLED ssd1306 ke STM32 (SPI + DMA)

Artikel ini akan menjelaskan proses menghubungkan layar oled dengan pengontrol ssd1306 dengan resolusi 128x64 ke mikrokontroler stm32f103C8T6 melalui antarmuka SPI. Saya juga ingin mencapai kecepatan refresh tampilan yang maksimal, jadi disarankan untuk menggunakan DMA, dan memprogram mikrokontroler menggunakan pustaka CMSIS.



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


gambargambar



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.



gambar

gambar



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.



gambar



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:



  1. Menunggu SPI dirilis
  2. CS = 0
  3. Mengirim data
  4. 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);


gambar



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);
	}
}


gambar



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.



gambar



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



All Articles