“This is the 23rd day of my participation in the First Challenge 2022. For details: First Challenge 2022”

1. Introduction

OLED display is widely used in smart wristbands and smartwatches. It has many advantages such as low power consumption and no glare. This article will introduce how to use OLED display in Linux system. To use OLED display, there are roughly two steps: (1) write a driver for OLED display; (2) write application layer program for testing.

The OLED display used is a 0.96 inch SPI interface display with a resolution of 128*64, which is relatively cheap and widely available on Taobao.

The test development board adopts friendly Arm Tiny4412, Samsung exyNOS-4412 chip, 4-core 1.5ghz, onboard 8G-EMMC, 2G-DDR.

2. Hardware cable connection effect

3. Driver code

The Linux kernel provides the standard SPI subsystem framework, which is similar to the IIC subsystem framework introduced earlier. The code is divided into device side and driver side. The purpose of the Linux kernel to provide subsystem is to unify the driver writing standard and improve the portability of driver code.

The code of this article does not use the SPI subsystem framework, and adopts the usual simulation SPI timing sequence of single chip microcomputer, which is easier to understand for the beginning.

3.1 OlEDS. C driver example code

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h> 
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/fb.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>

/* Define the register required for OLED */
static volatile unsigned int *GPB_CON=NULL;
static volatile unsigned int *GPB_DAT=NULL;

// Bottom interface of OLED screen
#define OLED_SCK(x) if(x){*GPB_DAT|=1<<0; }else{*GPB_DAT&=~(1<<0); }
#define OLED_MOSI(x) if(x){*GPB_DAT|=1<<3; }else{*GPB_DAT&=~(1<<3); }
#define OLED_RES(x)  if(x){*GPB_DAT|=1<<4; }else{*GPB_DAT&=~(1<<4); }
#define OLED_DC(x)   if(x){*GPB_DAT|=1<<5; }else{*GPB_DAT&=~(1<<5); }
#define OLED_CS(x)   if(x){*GPB_DAT|=1<<1; }else{*GPB_DAT&=~(1<<1); }

// Command and data differentiation
#define OLED_CMD 0
#define OLED_DAT 1

// Function declaration area
static void OLED_WriteOneByte(u8 data,u8 cmd);
static u8 OLED_GPIO_Init(void);
static void OLED_Init(void);
static void OLED_Clear(u8 data);
static void OLED_DrawPoint(u8 x,u8 y,u8 c);
static void OLED_RefreshGRAM(void);


/* Function function: OLED corresponding GPIO port initialization hardware connection: OLED module - Tiny4412 development board GND -- -- -- -- -- -- -- -- GND VCC -- -- -- -- -- -- -- -- VCC (5 v) do -- -- -- -- -- -- -- -- -- SCL -- -- -- -- -- -- -- -- -- -- -- -- -- -- GPB_0 D1 -- -- -- -- -- -- -- -- -- MOSI -- -- -- -- -- -- -- -- -- -- -- -- -- GPB_3 RES -- -- -- -- -- -- -- -- reset -- -- -- -- -- -- -- -- -- -- -- -- -- GPB_4 DC -- -- -- -- -- -- -- -- -- data/command -- -- -- -- -- -- -- -- GPB_5 CS---------CS tablet -----------GPB_1 */
static u8 OLED_GPIO_Init(void)
{
	/*1. Convert the physical address to a virtual address */
	GPB_CON=ioremap(0x11400040.4);
	GPB_DAT=ioremap(0x11400044.4);
	if(GPB_CON==NULL||GPB_DAT==NULL)
	{
		printk("Problem converting physical address to virtual address! \n");
		return - 1;
	}
	/*2. Configure the GPIO port mode */
	*GPB_CON&=0xFF000F00;
	*GPB_CON|=0x00111011;
	
	/*3. Pull the GPIO port */
	OLED_CS(1);
	OLED_DC(1);
	OLED_MOSI(1);
	OLED_RES(1);
	OLED_SCK(1);
}


/* Function function: OLED screen initialization */
static void OLED_Init(void)
{
	/*1. Initialize the GPIO port */
	OLED_GPIO_Init(a);/*2. Perform initial configuration of the OLED screen */
	OLED_RES(1);
	udelay(2000);
	OLED_RES(0);
	udelay(2000);
	OLED_RES(1);
	udelay(2000);
	
	OLED_WriteOneByte(0xAE,OLED_CMD); //0xAE indicates off display,0xAF indicates on display

	OLED_WriteOneByte(0x00,OLED_CMD);
	OLED_WriteOneByte(0x10,OLED_CMD);

	OLED_WriteOneByte(0x40,OLED_CMD);

	OLED_WriteOneByte(0xB0,OLED_CMD);

	OLED_WriteOneByte(0x81,OLED_CMD);
	OLED_WriteOneByte(0xCF,OLED_CMD);

	OLED_WriteOneByte(0xA1,OLED_CMD);

	OLED_WriteOneByte(0xA6,OLED_CMD);

	OLED_WriteOneByte(0xA8,OLED_CMD);
	OLED_WriteOneByte(0x3F,OLED_CMD);

	OLED_WriteOneByte(0xC8,OLED_CMD);

	OLED_WriteOneByte(0xD3,OLED_CMD);
	OLED_WriteOneByte(0x00,OLED_CMD);

	OLED_WriteOneByte(0xD5,OLED_CMD);
	OLED_WriteOneByte(0x80,OLED_CMD);

	OLED_WriteOneByte(0xD9,OLED_CMD);
	OLED_WriteOneByte(0xF1,OLED_CMD);

	OLED_WriteOneByte(0xDA,OLED_CMD);
	OLED_WriteOneByte(0x12,OLED_CMD);

	OLED_WriteOneByte(0xDB,OLED_CMD);
	OLED_WriteOneByte(0x30,OLED_CMD);

	OLED_WriteOneByte(0x8D,OLED_CMD);
	OLED_WriteOneByte(0x14,OLED_CMD);

	OLED_WriteOneByte(0xAF,OLED_CMD); // Normal mode
}

CMD =0 for command, CMD =1 for data */
static void OLED_WriteOneByte(u8 data,u8 cmd)
{
	u8 i;
	/*1. Distinguish between commands and screen data */
	if(cmd){OLED_DC(1); }else {OLED_DC(0); }udelay(2);
	
	/*2. Send the actual data */
	OLED_CS(0); / / select the OLED
	
	for(i=0; i<8; i++) {udelay(2);
		OLED_SCK(0); // Tell the slave that the host is about to send data
		if(data&0x80) {OLED_MOSI(1); }// Send data
		else {OLED_MOSI(0); }udelay(2);
		OLED_SCK(1); // Tell the slave that the host data has been sent
		data<<=1;   // Continue sending the next bit of data
	}
	
	OLED_CS(1);  // Uncheck OLED
	OLED_SCK(1); // Pull up the clock line to restore the idle level
}


/* Function function: clear screen (all lights on, all lights off) */
static void OLED_Clear(u8 data)
{
	u8 i,j;
	for(i=0; i<8; i++) {OLED_WriteOneByte(0xB0+i,OLED_CMD); // Set the page address
		OLED_WriteOneByte(0x10,OLED_CMD);   // Set the column height start address (half a byte)
		OLED_WriteOneByte(0x00,OLED_CMD);   // Set the lower start address of the column (half byte)
		for(j=0; j<128; j++) {OLED_WriteOneByte(data,OLED_DAT); / / write data}}}/* Define the video memory array: 8 rows, 128 columns each, corresponding to the OLED screen */
static u8 OLED_GRAM[8] [128]; 

X: 0~128 y: 0~64 C: 1 indicates on, 0 indicates off */
static void OLED_DrawPoint(u8 x,u8 y,u8 c)
{
	u8 page;
	page=y/8; // Get the page number of the current point 0/8=0 1/8=0
	y=y%8;    // Get the midpoint of a column. (0 ~ 7)
  / / 8 = 0 0% 1% 8 = 1... 8 = 7 8 = 0 8% 7% 9% 8 = 1...
	if(c) OLED_GRAM[page][x]|=1<<y;  
	else  OLED_GRAM[page][x]&=~(1<<y);
}

/* Refresh data to OLED display */
static void OLED_RefreshGRAM(void)
{
	u8 i,j;
	for(i=0; i<8; i++) {OLED_WriteOneByte(0xB0+i,OLED_CMD); // Set the page address
		OLED_WriteOneByte(0x10,OLED_CMD);   // Set the column height start address (half a byte)
		OLED_WriteOneByte(0x00,OLED_CMD);   // Set the lower start address of the column (half byte)
		for(j=0; j<128; j++) {OLED_WriteOneByte(OLED_GRAM[i][j],OLED_DAT); / / write data}}}static u8 *mmap_buffer=NULL; /* pointer - the first address to store the requested space */

static int lcd_open(struct fb_info *info, int user)
{
	mmap_buffer=kmalloc(4096,GFP_ATOMIC);
	if(mmap_buffer==NULL)
	{
		printk("Space request failed! \n");
		return 0;
	}
	printk("Lcd_open call successful \n");
	return 0;
}


static int lcd_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
	vma->vm_flags |= VM_IO;// Indicates the mapping of the DEVICE I/O space
	vma->vm_flags |= VM_RESERVED;// Indicates that the memory area cannot be swapped out. The relationship between the virtual page and the physical page in the device driver should be long-term and should be retained, and cannot be swapped out by other virtual pages
	if(remap_pfn_range(vma,// The virtual memory area to which the device address will be mapped
						vma->vm_start,// The start address of the virtual space
						virt_to_phys(mmap_buffer)>>PAGE_SHIFT,// The page frame number corresponding to the physical memory is moved 12 bits to the right
						vma->vm_end - vma->vm_start,// Map area size, usually an integer multiple of the page size
						vma->vm_page_prot))// Protect attributes,
	{
		return -EAGAIN;
	}
	
	printk("(DRV) mapping length :%d\n",vma->vm_end - vma->vm_start);
	printk("Physical address :0x%X\n".virt_to_phys(mmap_buffer));
	/* DDR capacity of development board: 1G 0x40000000 to 0x80000000 0x10000000=256M */
	return 0;
}

#define _OLED_RefreshGRAM 0x12345 /* Refresh the application layer data to the OLED screen */
#define _OLED_ClearGRAM 0x45678 /* Refresh the application layer data to the OLED screen */
static int lcd_ioctl(struct fb_info *info, unsigned int cmd,unsigned long arg)
{
	switch(cmd)
	{
		case _OLED_RefreshGRAM:
			 memcpy(OLED_GRAM,mmap_buffer,1024); // Copy data
			 OLED_RefreshGRAM(a);/ * refresh * /
			 break;
		case _OLED_ClearGRAM: / * screen clearing * /
			 OLED_Clear(0);
			 break;
	}
	return 0;
}

static int lcd_release(struct fb_info *info, int user)
{
	if(mmap_buffer! =NULL)
	{
		kfree(mmap_buffer);
	}
	printk("Lcd_release call successful \n");
	return 0;
}


/* File manipulation interface for frame buffering devices */
static struct fb_ops fbops=
{
	.fb_open=lcd_open,
	.fb_release=lcd_release,
	.fb_mmap=lcd_mmap,
	.fb_ioctl=lcd_ioctl
};


/* Frame buffer device structure */
static struct fb_info lcd_info=
{
	.var= /* Deformable parameter */
		{
			.xres=128,
			.yres=64,
			.bits_per_pixel=1
		},
	.fix=
		{
			.smem_len=4096,
			.line_length=128
		},
	.fbops=&fbops
};

static int __init tiny4412_oled_init(void)
{
	/*1. Initialize the OLED screen */
	OLED_Init(a);OLED_Clear(0);// Clear the screen to black
	
	/*2. Frame buffer driver registration */
	if(register_framebuffer(&lcd_info)! =0)
	{
		printk("Warning: LCD driver installation failed! \n");
		return - 1;
	}
	else
	{
		printk("Warning: LCD driver installed successfully! \n");	
	}
	
    return 0;
}


static void __exit tiny4412_oled_exit(void)
{
	/*1. Frame buffer driver logout */
	if(unregister_framebuffer(&lcd_info)! =0)
	{
		printk("Warning: LCD driver uninstallation failed! \n");
		return - 1;
	}
	else
	{
		printk("Warning: LCD driver uninstalled successfully! \n");
	}
	
	/*2. Delete the virtual address mapping */
	iounmap(GPB_CON);
	iounmap(GPB_DAT);
}

module_init(tiny4412_oled_init);  /* Specifies the driver's entry function */
module_exit(tiny4412_oled_exit);  /* Specifies the driver's exit function */
MODULE_LICENSE("GPL");      	  /* Specify driver license */
Copy the code

3.2 App.c application layer code

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <string.h>

unsigned char *lcd_mem=NULL; /* Memory address of LCD */
struct fb_fix_screeninfo finfo; /* Fixed the parameter */
struct fb_var_screeninfo vinfo; /* Deformable parameter */
	
unsigned char font[]=
{
/*-- text: */
/ * -- song typeface 42; The corresponding lattice is: width x height =56x56 width /8* height */
0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x01.0xC0.0x00.0x00.0x00.0x07.0x00.0x01.0xFF.0xFF.0xFF.0xFF.0xFF.0xC0.0x01.0xFF.0xFF.0xFF.0xFF.0xFF.0xE0.0x01.0xF0.0x00.0x00.0x00.0x07.0xC0.0x01.0xF0.0x00.0x00.0x00.0x07.0x80.0x01.0xF0.0x00.0x00.0x00.0x07.0x80.0x01.0xF0.0x00.0x00.0x01.0x87.0x80.0x01.0xF0.0x00.0x00.0x03.0xC7.0x80.0x01.0xF7.0xFF.0xFF.0xFF.0xE7.0x80.0x01.0xF3.0xFF.0xFF.0xFF.0xF7.0x80.0x01.0xF1.0xC0.0x7C.0x00.0x07.0x80.0x01.0xF0.0x00.0x7C.0x00.0x07.0x80.0x01.0xF0.0x00.0x7C.0x00.0x07.0x80.0x01.0xF0.0x00.0x7C.0x00.0x07.0x80.0x01.0xF0.0x00.0x7C.0x00.0x07.0x80.0x01.0xF0.0x00.0x7C.0x00.0x07.0x80.0x01.0xF0.0x00.0x7C.0x00.0x07.0x80.0x01.0xF0.0x00.0x7C.0x00.0x07.0x80.0x01.0xF0.0x00.0x7C.0x06.0x07.0x80.0x01.0xF0.0x00.0x7C.0x0F.0x07.0x80.0x01.0xF1.0xFF.0xFF.0xFF.0x87.0x80.0x01.0xF1.0xFF.0xFF.0xFF.0xC7.0x80.0x01.0xF0.0xF0.0x7C.0x00.0x07.0x80.0x01.0xF0.0x00.0x7C.0x00.0x07.0x80.0x01.0xF0.0x00.0x7F.0xC0.0x07.0x80.0x01.0xF0.0x00.0x7D.0xF0.0x07.0x80.0x01.0xF0.0x00.0x7C.0xFC.0x07.0x80.0x01.0xF0.0x00.0x7C.0x7E.0x07.0x80.0x01.0xF0.0x00.0x7C.0x3F.0x07.0x80.0x01.0xF0.0x00.0x7C.0x3F.0x07.0x80.0x01.0xF0.0x00.0x7C.0x1F.0x07.0x80.0x01.0xF0.0x00.0x7C.0x0F.0x07.0x80.0x01.0xF0.0x00.0x7C.0x0E.0x07.0x80.0x01.0xF0.0x00.0x7C.0x07.0x87.0x80.0x01.0xF0.0x00.0x7C.0x03.0xC7.0x80.0x01.0xF0.0x00.0x7C.0x07.0xE7.0x80.0x01.0xFF.0xFF.0xFF.0xFF.0xF7.0x80.0x01.0xFF.0xFF.0xFF.0xFF.0xFF.0x80.0x01.0xF7.0x80.0x00.0x00.0x07.0x80.0x01.0xF0.0x00.0x00.0x00.0x07.0x80.0x01.0xF0.0x00.0x00.0x00.0x07.0x80.0x01.0xF0.0x00.0x00.0x00.0x07.0x80.0x01.0xF0.0x00.0x00.0x00.0x07.0x80.0x01.0xFF.0xFF.0xFF.0xFF.0xFF.0x80.0x01.0xFF.0xFF.0xFF.0xFF.0xFF.0x80.0x01.0xF0.0x00.0x00.0x00.0x07.0x80.0x01.0xF0.0x00.0x00.0x00.0x07.0x80.0x01.0xF0.0x00.0x00.0x00.0x07.0x80.0x01.0xF0.0x00.0x00.0x00.0x07.0x00.0x01.0xC0.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00.0x00};/* Define the video memory array: 8 rows, 128 columns each, corresponding to the OLED screen */
static unsigned char OLED_GRAM[8] [128]; 

X: 0~128 y: 0~64 C: 1 indicates on, 0 indicates off */
static void OLED_DrawPoint(unsigned char x,unsigned char y,unsigned char c)
{
	unsigned char page;
	page=y/8; // Get the page number of the current point 0/8=0 1/8=0
	y=y%8;    // Get the midpoint of a column. (0 ~ 7)
  / / 8 = 0 0% 1% 8 = 1... 8 = 7 8 = 0 8% 7% 9% 8 = 1...
	if(c) OLED_GRAM[page][x]|=1<<y;  
	else  OLED_GRAM[page][x]&=~(1<<y);	
	memcpy(lcd_mem,OLED_GRAM,1024);
}


/* Make sure the font width and height are the same, and must be multiples of 8. * /
void ShowFont(int x,int y,int size,char *font)
{
	int i,j,x0=x;
	unsigned char data;
	for(i=0; i<size/8*size; i++) { data=font[i];for(j=0; j<8; j++) {// select * from *
			if(data&0x80)
			{
				// Font color
				OLED_DrawPoint(x0,y,1);
			}
			else
			{
				// Background color
				OLED_DrawPoint(x0,y,0);
			}
			x0++;
			data<<=1;
		}
		if(x0-x==size)
		{
			x0=x;
			y++;/ / a newline}}}#define OLED_RefreshGRAM 0x12345 /* Refresh the application layer data to the OLED screen */
#define OLED_ClearGRAM 0x45678   /* Refresh the application layer data to the OLED screen */

int main(int argc,char **argv)
{
	1. Open the device file */
	int fd=open("/dev/fb5",O_RDWR);
	if(fd<0)
	{
		printf("/dev/fb5 device file failed to open! \n");
		return 0;
	}
	
	/*2. Read the LCD parameters */
	ioctl(fd,FBIOGET_FSCREENINFO,&finfo);// Set parameters
	printf("Length of mapping :%d\n",finfo.smem_len);
	
	ioctl(fd,FBIOGET_VSCREENINFO,&vinfo);// Variable argument,32 bits
	printf("%d*%d,%d\n",vinfo.xres,vinfo.yres,vinfo.bits_per_pixel);

	/*3. Map the LCD address to the process space */
	lcd_mem=mmap(NULL,finfo.smem_len,PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
	if(lcd_mem==NULL)
	{
		printf("Lcd_mem mapping failed! \n");
		return - 1;
	}
	
	/ / OLED screen
	ioctl(fd,OLED_ClearGRAM);
	
	/*4. Display Chinese */
	ShowFont(0.0.56,font);
	ShowFont(56.0.56,font);
	ioctl(fd,OLED_RefreshGRAM);
	
	/*5. Close file */
	munmap(lcd_mem,finfo.smem_len);
	close(fd);
	return 0;
}
Copy the code