OLED display SPI driver implementation of embedded linux (SH1106, ssd1306)

The free time on Sunday is too boring, and I don’t like playing games. What are your hobbies? I think typing code is also a hobby. It just so happens that there is a 0.96-inch OLED display at hand, which has been eating dust, why not play with it? So I did it, and finally used the spi user mode driver on my imax6ul linux development board to successfully light up. Here is a summary of the process and share it with those in need.

foreword

This article mainly introduces how to drive OLED display peripherals on the imax6ul-mini development board, and summarizes the process. Since the board defaults to the spi interface, let's play with the spi interface driver first, and then change to the i2c interface driver to play again.

My environment resource:

Linux kernel: linux-4.1.15

Development board used: punctual atom imax6ul-mini

OLED screen used: Zhongjingyuan Electronics 0.96-inch OLED display 12864 LCD screen module (supports spi and i2c interfaces)

OLED driver chip used: SH1106

Complete source code download address:

https://download.csdn.net/download/qq8864/88117562

Effect screenshot:

Implementation plan

There are many solutions to drive the OLED display of Zhongjingyuan Electronics. This module supports both spi and i2c interfaces, so you must use linux's spi or i2c driver.

Take the spi driver as an example, under embedded Linux, there are many ways to realize the SPI driver. Here are a few of the common ones:

1. Use GPIO to control the analog SPI: use the GPIO interface to control the timing and data transmission of the SPI bus, and you need to write your own driver to realize SPI communication.

2. Use the SPI framework driver: The Linux kernel provides the SPI framework driver, which can realize SPI communication by registering SPI devices and drivers. Need to write code for SPI device and driver.

3. Use the spidev driver: spidev is a general SPI device driver interface provided by the Linux kernel, which can simplify the use of SPI devices. It provides user space API, by opening /dev/spidev device file, using ioctl function for SPI communication. Using the spidev driver can facilitate SPI communication in user space without writing a kernel driver.

The implementation to be introduced here is to use the spidev driver. The reason is that to operate the OLED screen, you need to customize a series of operation interfaces such as oled_dispString(), oled_clear(), etc. Although using method 2 is the more popular one, it needs to be registered in the character device framework to provide the operation interface of the file system, which is a bit cumbersome to use and needs to be packaged again.

The spidev driver is a generic SPI device driver interface that allows user space to communicate with SPI devices through a simple API. Users can use the ioctl function for SPI communication by opening the /dev/spidev device file. The spidev driver provides some common operation functions, such as configuring SPI mode, setting clock frequency, transmitting data, etc. Using the spidev driver can facilitate SPI communication in user space without writing a kernel driver.

Compared with the traditional SPI driver, the advantage of the spidev driver is that it is easy to use, no need to write a kernel driver, and only needs to use the ioctl function in the user space for SPI communication. However, the spidev driver also has some limitations, such as being unable to support advanced functions such as interrupts and DMA transfers. If you need to use these advanced features, you may need to write a custom SPI driver.

Implementation process

First, you need to confirm that the spidev driver is enabled in the kernel configuration. See blog post: Summary of spidev usage of embedded linux general spi driver - Maverick cat a's blog - CSDN blog

Modify the device tree

Modify the imx6ull-14x14-evk.dts file, the device tree file is located in the kernel source linux/arch/arm/boot/dts/ directory.

&ecspi3 {
        fsl,spi-num-chipselects = <2>;/*cs管脚数配置*/
        cs-gpios = <0>,<&gpio1 20 GPIO_ACTIVE_LOW>;/*cs管脚配置*/
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_ecspi3>;
        status = "okay";/* status属性值为"okay" 表示该节点使能*/
 
	spidev: icm20608@0 {
	compatible = "alientek,icm20608";
        spi-max-frequency = <8000000>;
        reg = <0>;/*spi设备是没有设备地址的, 这里是指使用spi控制器的cs-gpios里的第几个片选io */
    };
 
	oled: oledsh1106@1 {
	compatible = "yang,oledsh1106";/*重要,会匹配spidev.c中指定的compatible*/
	spi-cpol;/*配置spi信号模式*/
	spi-cpha;
	spi-max-frequency = < 8000000 >;/* 指定spi设备的最大工作时钟 */
    reg = <1>;
    };
};

Note above: If there are multiple slave devices mounted under the spi interface, you need to set fsl, spi-num-chipselects = <2>; the default value is 1. Another thing to note is that the cs-gpios chip select signal needs to be configured with the corresponding number. The above is to configure two chip selection GPIO pins, the first one is default, and the second one is specified. If there is only one slave device, you can configure cs-gpio. Pay attention to the difference between cs-gpio and cs-gpios, there can be multiple identifiers with s.

Modify the spidev driver

The default spidev.c driver file does not match the device you added. Therefore, it is necessary to modify the spidev.c source code and add compatible matching. The spidev.c source code file is located in linux/drivers/spi/spidev.c

/* The main reason to have this class is to make mdev/udev create the
 * /dev/spidevB.C character device nodes exposing our userspace API.
 * It also simplifies memory management.
 */
 
static struct class *spidev_class;
 
//#ifdef CONFIG_OF
static const struct of_device_id spidev_dt_ids[] = {
	{ .compatible = "rohm,dh2228fv" },
  { .compatible = "yang,oledsh1106" },
	{},
};
MODULE_DEVICE_TABLE(of, spidev_dt_ids);
//#endif

Compile the kernel and device tree

#加载环境
source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
#编译内核
make zImage -j16
#编译指定的设备树
make imx6ull-14x14-nand-4.3-480x272-c.dtb

In order to facilitate debugging and update the kernel and device tree files, it is recommended to use the sd card to boot, so that the sd card can be pulled out and inserted into the circuit, which can easily update the kernel and device tree files. 

Device tree view

After the kernel and device tree are updated, boot the board. You can see if the spidev driver is in effect.

Check if there are newly added nodes in the device tree:

After updating the device tree to the board, you can see that the spi device node ( spidev2.1 ) is generated as follows:

After the above process, more than half of it has been successful. Or you can use tools to test the spi driver interface.

OELD driver implementation

On the basis of the above spidev bus driver being ready, the implementation of the OELD driver is simple.

First look at the wiring of the oled module board:

DO corresponds to the CLK of the SPI interface, and the D1 (spi) data line corresponds to the MOSI of the SPI interface.

Note: Since this screen shows that there is no reading, the MISO port of SPI is not used, and don’t connect the wires wrong. (For example, I connected MISO to DC from the beginning, which is wrong.). What is the DC port? This is the data and command selection pin of the module, which is divided into two-week type operations of sending instructions and sending data. When sending commands, the DC port needs to output a high level, and when sending data, the DC port needs to send a low level.

SPI (Serial Peripheral Interface) is a serial peripheral interface protocol for communication between microcontrollers or other devices. SPI uses four lines for communication: SCLK (clock line), MOSI (master output slave input line), MISO (master input slave device output line), and SS (chip select line).

MOSI (Master Out Slave In) is the line through which the master device sends data to the slave device, while MISO (Master In Slave Out) is the line through which the slave device sends data to the master device. The functions of these two lines are opposite. The master device sends data to the slave device through MOSI, and the slave device sends data to the master device through MISO.

The data transmission in the SPI protocol is bidirectional, and the master device and the slave device can send and receive data at the same time. Therefore, if only write operations are required, theoretically only the MOSI line can be used, while the MISO line is suspended and not connected. However, in practical applications, in order to maintain the integrity and stability of the SPI interface, the MISO line is usually connected even if it is not used during write operations.

The advantage of connecting the MISO line is the flexibility to achieve bidirectional communication in case you may need to read data from the slave device in the future. In addition, the data on the MISO line can also be used for error detection and verification, so as to improve the reliability of data transmission.

Driver implementation

spidev driver operation

Using the operation method driven by spidev, the general process operation is as follows:

1. Open the SPI device:

int spi_fd;
spi_fd = open("/dev/spidev0.0", O_RDWR);
if (spi_fd < 0) {
    perror("Failed to open SPI device");
    return -1;
}

2. Set the SPI mode, speed and number of bits:

int mode = SPI_MODE_0;
int speed = 1000000;
int bits_per_word = 8;
if (ioctl(spi_fd, SPI_IOC_WR_MODE, &mode) < 0) {
    perror("Failed to set SPI mode");
    return -1;
}
if (ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed) < 0) {
    perror("Failed to set SPI speed");
    return -1;
}
if (ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word) < 0) {
    perror("Failed to set SPI bits per word");
    return -1;
}

3. Create a spi_ioc_message structure and set related fields:

struct spi_ioc_transfer transfer;
memset(&transfer, 0, sizeof(struct spi_ioc_transfer));
transfer.tx_buf = (unsigned long)tx_buffer;  // 发送缓冲区
transfer.rx_buf = (unsigned long)rx_buffer;  // 接收缓冲区
transfer.len = length;  // 数据长度
transfer.speed_hz = speed;  // 传输速度
transfer.bits_per_word = bits_per_word;  // 每个字的位数
transfer.cs_change = 1;  // 控制片选信号的行为

Among them, transfer.cs_change is 1, which means that the chip select signal will be pulled down before each transfer, and the chip select signal will be pulled up after the transfer is completed.

4. Send commands and data:

if (ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer) < 0) {
    perror("Failed to send SPI message");
    return -1;
}

Among them, SPI_IOC_MESSAGE(1) means sending a spi_ioc_transfer structure.

OELD driver interface package

#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <linux/spi/spidev.h>
#include "oledfont.h"
#include "bmp.h"
#include "oled.h"


#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))


static void pabort(const char *s)
{
	perror(s);
	abort();
}

static const char *device = "/dev/spidev2.1";

static int32_t  spi_fd;
static uint32_t spi_mode;
static uint8_t  spi_bits = 8;
static uint32_t spi_speed = 800000;
static uint16_t spi_delay;
static int verbose;

static void hex_dump(const void *src, size_t length, size_t line_size, char *prefix)
{
	int i = 0;
	const unsigned char *address = src;
	const unsigned char *line = address;
	unsigned char c;

	printf("%s | ", prefix);
	while (length-- > 0) {
		printf("%02X ", *address++);
		if (!(++i % line_size) || (length == 0 && i % line_size)) {
			if (length == 0) {
				while (i++ % line_size)
					printf("__ ");
			}
			printf(" | ");  /* right close */
			while (line < address) {
				c = *line++;
				printf("%c", (c < 33 || c == 255) ? 0x2E : c);
			}
			printf("\n");
			if (length > 0)
				printf("%s | ", prefix);
		}
	}
}

/*
 *  Unescape - process hexadecimal escape character
 *      converts shell input "\x23" -> 0x23
 */
static int unescape(char *_dst, char *_src, size_t len)
{
	int ret = 0;
	char *src = _src;
	char *dst = _dst;
	unsigned int ch;

	while (*src) {
		if (*src == '\\' && *(src+1) == 'x') {
			sscanf(src + 2, "%2x", &ch);
			src += 4;
			*dst++ = (unsigned char)ch;
		} else {
			*dst++ = *src++;
		}
		ret++;
	}
	return ret;
}

static int transfer(int fd, uint8_t const *tx, uint8_t const *rx, size_t len)
{
	int ret;

	struct spi_ioc_transfer tr = {
		.tx_buf = (unsigned long)tx,
		.rx_buf = (unsigned long)rx,
		.len = len,
		.delay_usecs = spi_delay,
		.speed_hz = spi_speed,
		.bits_per_word = spi_bits,
        .cs_change = 0,
	};

	if (spi_mode & SPI_TX_QUAD)
		tr.tx_nbits = 4;
	else if (spi_mode & SPI_TX_DUAL)
		tr.tx_nbits = 2;
	if (spi_mode & SPI_RX_QUAD)
		tr.rx_nbits = 4;
	else if (spi_mode & SPI_RX_DUAL)
		tr.rx_nbits = 2;
	if (!(spi_mode & SPI_LOOP)) {
		if (spi_mode & (SPI_TX_QUAD | SPI_TX_DUAL))
			tr.rx_buf = 0;
		else if (spi_mode & (SPI_RX_QUAD | SPI_RX_DUAL))
			tr.tx_buf = 0;
	}

	ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
	if (ret < 1)
		pabort("can't send spi message");

	if (verbose){
		hex_dump(tx, len, 32, "TX");
        hex_dump(rx, len, 32, "RX");
    }
    
    return ret;
}

//向SSD1106写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 cmd)
{			  
    u8 tx[2];
    u8 rx[2];
    tx[0] = dat;
    if(cmd)
    {
        system("echo 1 > /sys/class/gpio/gpio1/value");
        transfer(spi_fd,tx,rx,1);
    }
    else
    {
       system("echo 0 > /sys/class/gpio/gpio1/value");
       transfer(spi_fd,tx,rx,1);
    }  
} 

//初始化SSD1306					    
void OLED_Init(void)
{ 	
    //OLED_RST_Set();
	usleep(100000);
	//OLED_RST_Clr();
	usleep(100000);
	//OLED_RST_Set(); 
					  
	OLED_WR_Byte(0xAE,OLED_CMD);//--turn off oled panel
	OLED_WR_Byte(0x02,OLED_CMD);//---set low column address
	OLED_WR_Byte(0x10,OLED_CMD);//---set high column address
	OLED_WR_Byte(0x40,OLED_CMD);//--set start line address  Set Mapping RAM Display Start Line (0x00~0x3F)
	OLED_WR_Byte(0x81,OLED_CMD);//--set contrast control register
	OLED_WR_Byte(0xCF,OLED_CMD); // Set SEG Output Current Brightness
	OLED_WR_Byte(0xA1,OLED_CMD);//--Set SEG/Column Mapping     0xa0左右反置 0xa1正常
	OLED_WR_Byte(0xC8,OLED_CMD);//Set COM/Row Scan Direction   0xc0上下反置 0xc8正常
	OLED_WR_Byte(0xA6,OLED_CMD);//--set normal display
	OLED_WR_Byte(0xA8,OLED_CMD);//--set multiplex ratio(1 to 64)
	OLED_WR_Byte(0x3f,OLED_CMD);//--1/64 duty
	OLED_WR_Byte(0xD3,OLED_CMD);//-set display offset	Shift Mapping RAM Counter (0x00~0x3F)
	OLED_WR_Byte(0x00,OLED_CMD);//-not offset
	OLED_WR_Byte(0xd5,OLED_CMD);//--set display clock divide ratio/oscillator frequency
	OLED_WR_Byte(0x80,OLED_CMD);//--set divide ratio, Set Clock as 100 Frames/Sec
	OLED_WR_Byte(0xD9,OLED_CMD);//--set pre-charge period
	OLED_WR_Byte(0xF1,OLED_CMD);//Set Pre-Charge as 15 Clocks & Discharge as 1 Clock
	OLED_WR_Byte(0xDA,OLED_CMD);//--set com pins hardware configuration
	OLED_WR_Byte(0x12,OLED_CMD);
	OLED_WR_Byte(0xDB,OLED_CMD);//--set vcomh
	OLED_WR_Byte(0x40,OLED_CMD);//Set VCOM Deselect Level
	OLED_WR_Byte(0x20,OLED_CMD);//-Set Page Addressing Mode (0x00/0x01/0x02)
	OLED_WR_Byte(0x02,OLED_CMD);//
	OLED_WR_Byte(0x8D,OLED_CMD);//--set Charge Pump enable/disable
	OLED_WR_Byte(0x14,OLED_CMD);//--set(0x10) disable
	OLED_WR_Byte(0xA4,OLED_CMD);// Disable Entire Display On (0xa4/0xa5)
	OLED_WR_Byte(0xA6,OLED_CMD);// Disable Inverse Display On (0xa6/a7) 
	OLED_WR_Byte(0xAF,OLED_CMD);//--turn on oled panel
	
	OLED_WR_Byte(0xAF,OLED_CMD); /*display ON*/ 
	OLED_Clear();
	OLED_Set_Pos(0,0); 	
}

void OLED_Set_Pos(unsigned char x, unsigned char y) 
{ 
	OLED_WR_Byte(0xb0+y,OLED_CMD);
	OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
	OLED_WR_Byte((x&0x0f)|0x01,OLED_CMD); 
}   	  
//开启OLED显示    
void OLED_Display_On(void)
{
	OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令
	OLED_WR_Byte(0X14,OLED_CMD);  //DCDC ON
	OLED_WR_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}
//关闭OLED显示     
void OLED_Display_Off(void)
{
	OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令
	OLED_WR_Byte(0X10,OLED_CMD);  //DCDC OFF
	OLED_WR_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}		   			 
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!	  
void OLED_Clear(void)  
{  
	u8 i,n;		    
	for(i=0;i<8;i++)  
	{  
		OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)
		OLED_WR_Byte (0x02,OLED_CMD);      //设置显示位置—列低地址
		OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   
		for(n=0;n<128;n++)OLED_WR_Byte(0,OLED_DATA); 
	} //更新显示
}


//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示				 
//size:选择字体 16/12 
void OLED_ShowChar(u8 x,u8 y,u8 chr)
{      	
	unsigned char c=0,i=0;	
		c=chr-' ';//得到偏移后的值			
		if(x>Max_Column-1){x=0;y=y+2;}
		if(SIZE ==16)
			{
			OLED_Set_Pos(x,y);	
			for(i=0;i<8;i++)
			OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
			OLED_Set_Pos(x,y+1);
			for(i=0;i<8;i++)
			OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
			}
			else {	
				OLED_Set_Pos(x,y+1);
				for(i=0;i<6;i++)
				OLED_WR_Byte(F6x8[c][i],OLED_DATA);
				
			}
}
//m^n函数
u32 oled_pow(u8 m,u8 n)
{
	u32 result=1;	 
	while(n--)result*=m;    
	return result;
}				  
//显示2个数字
//x,y :起点坐标	 
//len :数字的位数
//size:字体大小
//mode:模式	0,填充模式;1,叠加模式
//num:数值(0~4294967295);	 		  
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size)
{         	
	u8 t,temp;
	u8 enshow=0;						   
	for(t=0;t<len;t++)
	{
		temp=(num/oled_pow(10,len-t-1))%10;
		if(enshow==0&&t<(len-1))
		{
			if(temp==0)
			{
				OLED_ShowChar(x+(size/2)*t,y,' ');
				continue;
			}else enshow=1; 
		 	 
		}
	 	OLED_ShowChar(x+(size/2)*t,y,temp+'0'); 
	}
} 
//显示一个字符号串
void OLED_ShowString(u8 x,u8 y,u8 *chr)
{
	unsigned char j=0;
	while (chr[j]!='\0')
	{		OLED_ShowChar(x,y,chr[j]);
			x+=8;
		if(x>120){x=0;y+=2;}
			j++;
	}
}
//显示汉字
void OLED_ShowCHinese(u8 x,u8 y,u8 no)
{      			    
	u8 t,adder=0;
	OLED_Set_Pos(x,y);	
    for(t=0;t<16;t++)
		{
				OLED_WR_Byte(Hzk[2*no][t],OLED_DATA);
				adder+=1;
     }	
		OLED_Set_Pos(x,y+1);	
    for(t=0;t<16;t++)
			{	
				OLED_WR_Byte(Hzk[2*no+1][t],OLED_DATA);
				adder+=1;
      }					
}
/***********功能描述:显示显示BMP图片128×64起始点坐标(x,y),x的范围0~127,y为页的范围0~7*****************/
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char BMP[])
{ 	
 unsigned int j=0;
 unsigned char x,y;
  
  if(y1%8==0) y=y1/8;      
  else y=y1/8+1;
	for(y=y0;y<y1;y++)
	{
		OLED_Set_Pos(x0,y);
    for(x=x0;x<x1;x++)
	    {      
	    	OLED_WR_Byte(BMP[j++],OLED_DATA);	    	
	    }
	}
} 

int spidev_init()
{
    int ret = 0;
    spi_mode = SPI_MODE_0;
    verbose = 0;

	spi_fd = open(device, O_RDWR);
	if (spi_fd < 0)
		pabort("can't open device");

	/*
	 * spi mode
	 */
	ret = ioctl(spi_fd, SPI_IOC_WR_MODE32, &spi_mode);
	if (ret == -1)
		pabort("can't set spi mode");

	ret = ioctl(spi_fd, SPI_IOC_RD_MODE32, &spi_mode);
	if (ret == -1)
		pabort("can't get spi mode");

	/*
	 * bits per word
	 */
	ret = ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits);
	if (ret == -1)
		pabort("can't set bits per word");

	ret = ioctl(spi_fd, SPI_IOC_RD_BITS_PER_WORD, &spi_bits);
	if (ret == -1)
		pabort("can't get bits per word");

	/*
	 * max speed hz
	 */
	ret = ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);
	if (ret == -1)
		pabort("can't set max speed hz");

	ret = ioctl(spi_fd, SPI_IOC_RD_MAX_SPEED_HZ, &spi_speed);
	if (ret == -1)
		pabort("can't get max speed hz");

	printf("spi mode: 0x%x\n", spi_mode);
	printf("bits per word: %d\n", spi_bits);
	printf("max speed: %d Hz (%d KHz)\n", spi_speed, spi_speed/1000);

	return ret;
}

 test use

int main(int argc, char *argv[])
{
    //导出DC口,这里使用的是GPIO1管脚,作为DC口使用(命令数据选择管脚)
    system("echo 1 > /sys/class/gpio/export");
    system("echo out >/sys/class/gpio/gpio1/direction");
    
    spidev_init();
    OLED_Init();
    
    OLED_ShowString(0,0,"hello world");

    return 0;
}

 Note that a lazy approach is used here, directly exporting a GPIO1 pin for use, and first exporting the IO port through the system call. In the sending interface of the OLED, the method of calling the system command is also passed (it is quite inefficient and resource-intensive. If it is used officially, it must not be done this way. This is only for testing).

gpiochipxx: GPIO controllers included in the current SoC, I.MX6UL/I.MX6ULL contains a total of 5 GPIO controllers, namely GPIO1, GPIO2, GPIO3, GPIO4, and GPIO5, which correspond to gpiochip0, gpiochip32, gpiochip64, and gpiochip96 here , gpiochip128 these 5 folders, each gpiochipxx folder is used to manage a group of GPIO.

 For example, to export GPIO1_1, you can do this:

echo 1 > /sys/class/gpio/export

For a given GPIO pin, how to calculate its corresponding number in sysfs? In fact, it is very simple. For example, given a GPIO pin as GPIO4_IO16, what is its corresponding number? First of all, we need to make sure that GPIO4 corresponds to gpiochip96, and the minimum number of GPIO pins in this group is 96 (corresponding to GPIO4_IO0), so the number corresponding to GPIO4_IO16 is naturally 96 + 16 = 112; similarly, the number corresponding to GPIO3_IO20 is 64 + 20 = 84 .

//向SSD1106写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 cmd)
{			  
    u8 tx[2];
    u8 rx[2];
    tx[0] = dat;
    if(cmd)
    {
        system("echo 1 > /sys/class/gpio/gpio1/value");
        transfer(spi_fd,tx,rx,1);
    }
    else
    {
       system("echo 0 > /sys/class/gpio/gpio1/value");
       transfer(spi_fd,tx,rx,1);
    }  
}

Using echothe command to export and control the GPIO port through the file system is a simple and easy-to-use method, but it may not be efficient enough in scenarios with high performance requirements. Consider using GPIO libraries, device drivers, or userspace libraries and tools to improve efficiency, such as the way you can use the libgpiod library. 

Introduction to libgpiod library

libgpiod is a C library and tool for interacting with linux GPIO. Since linux 4.8, the official GPIO sysfs interface is not recommended. The libgpiod library encapsulates ioctl calls and simple API interfaces. Libgpiod is a character device interface, and GPIO access control is implemented by manipulating character device files (such as /dev/gpiodchip0).
Compared with the sysfs method, libgpiod can ensure that all allocated resources are completely released after closing the file descriptor, and has functions that do not exist in the sysfs method interface (such as time polling, setting/reading multiple gpio values ​​at a time) . In addition, libgpiod also includes a set of command-line tools that allow users to use scripts to personalize gpio.

详细介绍:libgpiod/libgpiod.git - C library and tools for interacting with the linux GPIO character device

Simple use of libgpiod library:

#include <gpiod.h>
#include <stdio.h>
 int main() {
    struct gpiod_chip *chip;
    struct gpiod_line *line;
    int value;
     // 打开 GPIO 控制器
    chip = gpiod_chip_open("/dev/gpiochip0");
    if (!chip) {
        perror("Failed to open GPIO chip");
        return -1;
    }
     // 获取 GPIO 口
    line = gpiod_chip_get_line(chip, 17);
    if (!line) {
        perror("Failed to get GPIO line");
        gpiod_chip_close(chip);
        return -1;
    }
     // 设置 GPIO 口为输出模式
    int ret = gpiod_line_request_output(line, "example", 0);
    if (ret < 0) {
        perror("Failed to set GPIO line as output");
        gpiod_line_release(line);
        gpiod_chip_close(chip);
        return -1;
    }
     // 控制 GPIO 输出高低电平
    ret = gpiod_line_set_value(line, 1);
    if (ret < 0) {
        perror("Failed to set GPIO line value");
        gpiod_line_release(line);
        gpiod_chip_close(chip);
        return -1;
    }
     // 读取 GPIO 输入值
    value = gpiod_line_get_value(line);
    printf("GPIO value: %d\n", value);
     // 释放资源
    gpiod_line_release(line);
    gpiod_chip_close(chip);
     return 0;
}

In the above example, first gpiod_chip_openopen the specified GPIO controller (for example /dev/gpiochip0), and then use to gpiod_chip_get_lineobtain the specified GPIO port (for example, port 17). Next, we use to gpiod_line_request_outputset the GPIO port to output mode, and use gpiod_line_set_valueto control the output high and low levels. Finally, we read gpiod_line_get_valuethe GPIO input value using and release the resource using gpiod_line_releaseand gpiod_chip_close. It should be noted that the use of libgpiod needs to install the corresponding library files and header files, and link the libgpiod library when compiling.

Note that libgpiod requires a Linux kernel version of at least 4.8 because it relies on the kernel's GPIO character device interface (gpiochip). In kernels prior to 4.8, this interface may not exist or be incomplete, so the libgpiod library cannot be used. You can use the following methods:

int main(int argc, char *argv[])
{
    //导出DC口,这里使用的是GPIO1管脚,作为DC口使用(命令数据选择管脚)
    //system("echo 1 > /sys/class/gpio/export");
    //system("echo out >/sys/class/gpio/gpio1/direction");
    dc_p = fopen("/sys/class/gpio/export","w");
    fprintf(dc_p,"%d",1);
    fclose(dc_p);
    dc_p = fopen("/sys/class/gpio/gpio1/direction","w");
    fprintf(dc_p,"out");
    fclose(dc_p);
    dc_p = fopen("/sys/class/gpio/gpio1/value","w");
    fprintf(dc_p,"1");
    fflush(dc_p);
    
    spidev_init();
    OLED_Init();
    
    OLED_ShowString(0,0,"hello world");

    return 0;
}
//向SSD1106写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 cmd)
{			  
    u8 tx[2];
    u8 rx[2];
    tx[0] = dat;
    if(cmd)
    {
        //system("echo 1 > /sys/class/gpio/gpio1/value");
        fprintf(dc_p,"1");
        fflush(dc_p);
        transfer(spi_fd,tx,rx,1);
    }
    else
    {
       //system("echo 0 > /sys/class/gpio/gpio1/value");
       fprintf(dc_p,"0");
       fflush(dc_p);
       transfer(spi_fd,tx,rx,1);
    }  
} 

You can also use the open system call function. Which one is more efficient? Several factors need to be considered, including data volume, buffer size, number of system calls, etc. The above recommends using fopen and fwrite.

fopenand openare two functions used to open files in Linux, there are some differences between them.

1. fopenIt is a function in the C standard library, which is used to open the file in the form of a stream. It returns a FILE*pointer of type, which can use standard library functions (such as fread, fwrite, fprintfetc.) to read and write files. fopenThe function provides some convenient functions, such as automatic buffering, formatting input and output, etc. However, since it is a standard library function, there may be a performance penalty when dealing with large amounts of data.

2. openIt is a system call function used to open a file in the form of a file descriptor. readIt returns a file descriptor of integer type, and the system call function (such as , write, etc.) can be used ioctlto read and write files. Functions are the raw interface provided by the operating system to directly interact with the kernel, so they are usually more efficient openthan when dealing with large amounts of data . fopenIn general, if you just do simple file read and write operations and want to use standard library functions for processing, you can choose to use them fopen. However, if you need more efficient file operations and need to use system call functions for low-level control, you can choose to use them open

fwriteis a function in the C standard library that writes data to a file. It will first write the data to the buffer, and then write the buffer's data to the file. In contrast, writeis a system call that writes data directly to the file without going through a buffer. For small data write operations, fwritethe efficiency may be higher, because it can write multiple small data to the buffer at one time, and then write to the file at one time, reducing the number of system calls. For write operations with large amounts of data, writethe efficiency may be higher. Because fwritethe data needs to be written to the buffer first, and then written to the file, this process may involve multiple buffer flush operations. And writewrite data directly to the file, reducing the overhead of buffer flushing. In addition, the effect of buffer size also needs to be considered. fwriteThere may be better performance if the buffer size is appropriate for the amount of data . However, if the amount of data exceeds the buffer size, fwritethe buffer needs to be flushed multiple times, which may lead to performance degradation. fwriteIn general, it may be more efficient for write operations with small data volumes . For write operations with large amounts of data, writeit may be more efficient. However, the specific efficiency needs to be tested and evaluated according to specific scenarios and needs.

The way to use the system call is as follows:

int main(int argc, char *argv[])
{
    //导出DC口,这里使用的是GPIO1管脚,作为DC口使用(命令数据选择管脚)
    //system("echo 1 > /sys/class/gpio/export");
    //system("echo out >/sys/class/gpio/gpio1/direction");
    system("echo 1 > /sys/class/gpio/unexport");
    ssize_t bytes_written;
    const char* value = "1";

    // 打开 /sys/class/gpio/export 文件
    fd_dc = open("/sys/class/gpio/export", O_WRONLY,0644);
    if (fd_dc == -1) {
        // 处理打开文件失败的情况
        printf("open error\n");
        return -1;
    }
    bytes_written = write(fd_dc, "1", 1);
    close(fd_dc);
    if (bytes_written == -1) {
        // 处理写入文件失败的情况
        printf("write failed: %s\n", strerror(errno));
        return -1;
    }

    // 打开 /sys/class/gpio/gpio1/direction 文件
    fd_dc = open("/sys/class/gpio/gpio1/direction", O_WRONLY,0644);
    if (fd_dc == -1) {
        // 处理打开文件失败的情况
        printf("open error1\n");
        return -1;
    }
    bytes_written = write(fd_dc, "out", 3);
    close(fd_dc);
    if (bytes_written == -1) {
        // 处理写入文件失败的情况
        printf("write failed1: %s\n", strerror(errno));
        return -1;
    }

    // 打开 /sys/class/gpio/gpio1/value 文件
    fd_dc = open("/sys/class/gpio/gpio1/value", O_WRONLY,0644);
    if (fd_dc == -1) {
        // 处理打开文件失败的情况
        printf("open error2\n");
        return -1;
    }
    bytes_written = write(fd_dc, value, strlen(value));
    fsync(fd_dc);
    
    spidev_init();
    OLED_Init();
    
    OLED_ShowString(0,0,"hello world");

    return 0;
}

When calling openthe function, you can specify the access permission of the file through the second parameter. The permission parameter is an octal number indicating the read, write and execute permissions of the file. The following are some commonly used permission parameter values:

- O_RDONLY: read-only mode, means to open the file for reading.

- O_WRONLY: Write-only mode, means to open the file for writing.

- O_RDWR: read-write mode, means to open the file for reading and writing.

- O_CREAT: Create the file if it does not exist.

- O_EXCL: O_CREATUsed with , returns an error if the file already exists.

- O_TRUNC: If the file exists and is opened in write mode, truncate the file to zero length.

- O_APPEND: Open the file in append mode, that is, append data to the end of the file when writing. The permission parameter can perform bit operation with the above-mentioned flag bits to specify the access permission of the file. For example, to open a file in read-write mode and create it if it does not exist, the following permission parameters can be used:

int fd = open("filename.txt", O_RDWR | O_CREAT, 0644);

In the above example, 0644is an octal number representing the permissions of the file. Among them, 0represents an octal number, 6indicates that the user (file owner) has read and write permissions, 4indicates that group users have read-only permissions, and 4indicates that other users have read-only permissions. It should be noted that the permission parameter only works when creating a file, and for an existing file, the permission parameter will not change the permission of the file. The actual permissions of the file are determined by the permission control of the file system.

Finally, in order to test OLED, write a simple makefile for easy compilation.

# test spidev-oled
#
# Copyright (C) 2023 yangyongzhen <[email protected]>
#

CC	?= arm-linux-gnueabihf-gcc
AR	?= arm-linux-gnueabihf-ar
STRIP	?= strip

CFLAGS		?= -O2
# When debugging, use the following instead
#CFLAGS		:= -O -g
CFLAGS		+= -Wall
SOCFLAGS	:= -fpic -D_REENTRANT $(CFLAGS)

#KERNELVERSION	:= $(shell uname -r)

.PHONY: all strip clean 

all:
	$(CC) spidev_oled.c -o spidev_oled
  
clean:
	rm -rf *.o 

Test demo:

int main(int argc, char *argv[])
{
    //导出DC口,这里使用的是GPIO1管脚,作为DC口使用(命令数据选择管脚)
    //system("echo 1 > /sys/class/gpio/export");
    //system("echo out >/sys/class/gpio/gpio1/direction");
    u8 t;
    dc_p = fopen("/sys/class/gpio/export","w");
    fprintf(dc_p,"%d",1);
    fclose(dc_p);
    dc_p = fopen("/sys/class/gpio/gpio1/direction","w");
    fprintf(dc_p,"out");
    fclose(dc_p);
    dc_p = fopen("/sys/class/gpio/gpio1/value","w");
    fprintf(dc_p,"1");
    fflush(dc_p);
    
    spidev_init();
    OLED_Init();
    
    while(1) 
	{		
		OLED_Clear();
		OLED_ShowCHinese(0,0,0);//中
		OLED_ShowCHinese(18,0,1);//景
		OLED_ShowCHinese(36,0,2);//园
		OLED_ShowCHinese(54,0,3);//电
		OLED_ShowCHinese(72,0,4);//子
		OLED_ShowCHinese(90,0,5);//科
		OLED_ShowCHinese(108,0,6);//技
		OLED_ShowString(0,3,"1.3' OLED TEST");
		//OLED_ShowString(8,2,"ZHONGJINGYUAN");  
	 //	OLED_ShowString(20,4,"2014/05/01");  
		OLED_ShowString(0,6,"ASCII:");  
		OLED_ShowString(63,6,"CODE:");  
		OLED_ShowChar(48,6,t);//显示ASCII字符	   
		t++;
		if(t>'~')t=' ';
		OLED_ShowNum(103,6,t,3,16);//显示ASCII字符的码值 	
			
		
		delay_ms(8000);
		OLED_Clear();
		delay_ms(8000);
		OLED_DrawBMP(0,0,128,8,BMP1);  //图片显示(图片显示慎用,生成的字表较大,会占用较多空间,FLASH空间8K以下慎用)
		delay_ms(8000);
		OLED_DrawBMP(0,0,128,8,BMP2);
		delay_ms(8000);
	}	  

}

   

 Project complete source code download address:

https://download.csdn.net/download/qq8864/88117562 

other resources

Embedded Linux - IIC bus driver (3): IIC drives OLED peripherals_how iic wakes up peripherals_moxue10's blog-CSDN blog

GPIO application programming in Linux system_linux control gpio program

Linux system controls gpio_gpio linux sys based on syfs_Embedded Linux Development Blog-CSDN Blog

Cross compile open source code (take libgpiod as an example)

[imx6ull application development] LED lamp device control of GPIO programming---sysfs mode and libgpiod mode_gpiod_line_request_output_WH^2's Blog-CSDN Blog

Feiling Embedded Technology Post - How to use the GPIO of i.MX9352? _dts_device_operation

[imx6ull application development] LED lamp device control of GPIO programming---sysfs mode and libgpiod mode_gpiod_line_request_output_WH^2's Blog-CSDN Blog

C language method (libgpiod) - Sipeed Wiki

The use of Libgpiod library to light up the LED - Programmer Sought

Guess you like

Origin blog.csdn.net/qq8864/article/details/131978153