OLED ディスプレイ組み込み Linux (SH1106、SSD1306) の SPI ドライバー実装

日曜日の自由時間は退屈すぎて、ゲームをするのは好きではありません。趣味は何ですか? コードを打つのも趣味だと思います。たまたま手元に、埃をかぶっている0.96インチのOLEDディスプレイがあるので、遊んでみませんか?それで私はそれを実行し、最終的にimax6ul Linux開発ボードでspiユーザーモードドライバーを使用して正常に点灯しました。ここにプロセスの概要を示しますので、必要としている人たちと共有してください。

序文

この記事では主に、imax6ul-mini 開発ボードで OLED ディスプレイ周辺機器を駆動する方法を紹介し、そのプロセスをまとめます。ボードはデフォルトで spi インターフェイスになっているため、最初に spi インターフェイス ドライバーで遊んでから、i2c インターフェイス ドライバーに変更して再度プレイしてみましょう。

私の環境リソース:

Linux カーネル: linux-4.1.15

使用した開発ボード: punctual atom imax6ul-mini

使用される OLED スクリーン: Zhongjingyuan Electronics 0.96 インチ OLED ディスプレイ 12864 LCD スクリーン モジュール (spi および i2c インターフェイスをサポート)

使用OLEDドライバーチップ:SH1106

完全なソース コードのダウンロード アドレス:

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

効果のスクリーンショット:

実行計画

中京源電子の OLED ディスプレイを駆動するソリューションは数多くあります。このモジュールは spi インターフェイスと i2c インターフェイスの両方をサポートしているため、Linux の spi ドライバーまたは i2c ドライバーを使用する必要があります。

spi ドライバーを例に挙げると、組み込み Linux では SPI ドライバーを実現する方法がたくさんあります。一般的なもののいくつかを次に示します。

1. GPIO を使用してアナログ SPI を制御します。GPIO インターフェイスを使用して SPI バスのタイミングとデータ送信を制御します。SPI 通信を実現するには独自のドライバーを作成する必要があります。

2. SPI フレームワーク ドライバを使用する: Linux カーネルは SPI フレームワーク ドライバを提供しており、SPI デバイスとドライバを登録することで SPI 通信を実現できます。SPI デバイスとドライバーのコードを記述する必要があります。

3. spidev ドライバーを使用します。spidev は、Linux カーネルによって提供される一般的な SPI デバイス ドライバー インターフェイスであり、SPI デバイスの使用を簡素化できます。SPI 通信用の ioctl 関数を使用して、/dev/spidev デバイス ファイルを開くことで、ユーザー空間 API を提供します。spidev ドライバーを使用すると、カーネル ドライバーを作成しなくても、ユーザー空間での SPI 通信が容易になります。

ここで紹介する実装はspidevドライバーを使用するものです。その理由は、OLED 画面を操作するには、oled_dispString()、oled_clear() などの一連の操作インターフェイスをカスタマイズする必要があるためです。方法 2 の方が一般的ですが、ファイル システムの操作インターフェイスを提供するためにキャラクタ デバイス フレームワークに登録する必要があり、使用が少し面倒で、再度パッケージ化する必要があります。

spidev ドライバーは、ユーザー空間が単純な API を介して SPI デバイスと通信できるようにする汎用 SPI デバイス ドライバー インターフェイスです。ユーザーは、/dev/spidev デバイス ファイルを開くことで、SPI 通信に ioctl 関数を使用できます。spidev ドライバーは、SPI モードの構成、クロック周波数の設定、データの送信など、いくつかの一般的な操作機能を提供します。spidev ドライバーを使用すると、カーネル ドライバーを作成しなくても、ユーザー空間での SPI 通信が容易になります。

従来の SPI ドライバーと比較した場合、spidev ドライバーの利点は、使いやすく、カーネル ドライバーを作成する必要がなく、SPI 通信用のユーザー空間で ioctl 関数を使用するだけでよいことです。ただし、spidev ドライバーには、割り込みや DMA 転送などの高度な機能をサポートできないなど、いくつかの制限もあります。これらの高度な機能を使用する必要がある場合は、カスタム SPI ドライバーの作成が必要になる場合があります。

実装プロセス

まず、spidev ドライバーがカーネル構成で有効になっていることを確認する必要があります。ブログ投稿を参照してください:組み込み Linux 一般 spi ドライバーの spidev 使用法の概要 - Maverick cat a のブログ - CSDN ブログ

デバイスツリーを変更する

imx6ull-14x14-evk.dts ファイルを変更します。デバイス ツリー ファイルは、カーネル ソースの linux/arch/arm/boot/dts/ ディレクトリにあります。

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

上記の注意: spi インターフェイスの下に複数のスレーブ デバイスがマウントされている場合は、fsl, spi-num-chipselects = <2> を設定する必要があります。デフォルト値は 1 です。もう 1 つ注意すべき点は、cs-gpios チップ選択信号を対応する番号で構成する必要があることです。上記は 2 つのチップ選択 GPIO ピンを設定するもので、最初のピンはデフォルトで、2 番目のピンは指定されています。スレーブ デバイスが 1 台のみの場合は、cs-gpio を設定できます。cs-gpio と cs-gpios の違いに注意してください。s を含む識別子は複数存在する可能性があります。

spdevドライバーを変更する

デフォルトの spidev.c ドライバー ファイルは、追加したデバイスと一致しません。したがって、spidev.c ソース コードを変更し、互換性のあるマッチングを追加する必要があります。spidev.c ソース コード ファイルは、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

カーネルとデバイスツリーをコンパイルする

#加载环境
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

デバッグを容易にし、カーネルお​​よびデバイス ツリー ファイルを更新するには、SD カードを使用して起動することをお勧めします。これにより、SD カードを回路に抜き差しして、カーネルお​​よびデバイス ツリー ファイルを簡単に更新できます。 。 

デバイスツリービュー

カーネルとデバイス ツリーが更新されたら、ボードを起動します。spdev ドライバーが有効かどうかを確認できます。

デバイス ツリーに新しく追加されたノードがあるかどうかを確認します。

ボードのデバイス ツリーを更新すると、spi デバイス ノード ( spidev2.1 ) が次のように生成されていることがわかります。

上記のプロセスを経て、半分以上が成功しました。または、ツールを使用して spi ドライバー インターフェイスをテストすることもできます。

OELドライバーの実装

上記の spidev バス ドライバーが準備できていることに基づいて、OELD ドライバーの実装は簡単です。

まず、OLEDモジュールボードの配線を見てください。

DO は SPI インターフェイスの CLK に対応し、D1 (spi) データ ラインは SPI インターフェイスの MOSI に対応します。

注: この画面では読み取りが行われていないことを示しているため、SPI の MISO ポートは使用されません。配線を間違って接続しないでください。(例えば、最初からMISOをDCに接続したのは間違いです。)DCポートとは何ですか? モジュールのデータとコマンドの選択端子で、命令送信とデータ送信の2週間型動作に分かれています。コマンドを送信する場合、DC ポートはハイ レベルを出力する必要があり、データを送信する場合、DC ポートはロー レベルを出力する必要があります。

SPI (シリアル ペリフェラル インターフェイス) は、マイクロコントローラーまたは他のデバイス間の通信用のシリアル ペリフェラル インターフェイス プロトコルです。SPI は、SCLK (クロック ライン)、MOSI (マスター出力スレーブ入力ライン)、MISO (マスター入力スレーブ デバイス出力ライン)、および SS (チップ セレクト ライン) の 4 つのラインを通信に使用します。

MOSI (Master Out Slave In) はマスター デバイスがスレーブ デバイスにデータを送信するラインであり、MISO (Master In Slave Out) はスレーブ デバイスがマスター デバイスにデータを送信するラインです。この 2 つのラインの機能は逆で、マスター デバイスは MOSI を介してスレーブ デバイスにデータを送信し、スレーブ デバイスは MISO を介してマスター デバイスにデータを送信します。

SPI プロトコルでのデータ伝送は双方向であり、マスター デバイスとスレーブ デバイスは同時にデータを送受信できます。したがって、書き込み操作のみが必要な場合、理論的には MOSI ラインのみを使用でき、MISO ラインは一時停止され、接続されません。ただし、実際のアプリケーションでは、SPI インターフェイスの完全性と安定性を維持するために、書き込み操作中に使用されない場合でも、MISO ラインは通常接続されます。

MISO ラインを接続する利点は、将来スレーブ デバイスからデータを読み取る必要がある場合に備えて、双方向通信を実現できる柔軟性です。さらに、MISO ライン上のデータはエラー検出と検証にも使用できるため、データ伝送の信頼性が向上します。

ドライバーの実装

spidevドライバーの操作

spidev による操作方法を使用すると、一般的なプロセス操作は次のようになります。

1. SPI デバイスを開きます。

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

2. SPI モード、速度、ビット数を設定します。

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. spi_ioc_message 構造体を作成し、関連フィールドを設定します。

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;  // 控制片选信号的行为

このうち、transfer.cs_change は 1 で、各転送前にチップ セレクト信号がプルダウンされ、転送完了後にチップ セレクト信号がプルアップされることを意味します。

4. コマンドとデータを送信します。

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

このうち、SPI_IOC_MESSAGE(1)はspi_ioc_transfer構造体の送信を意味します。

OELドライバーインターフェースパッケージ

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

 テスト使用

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

 ここでは遅延アプローチが使用されており、使用するために GPIO1 ピンを直接エクスポートし、最初にシステム コールを通じて IO ポートをエクスポートすることに注意してください。OLEDの送信インターフェースでは、システムコマンドを呼び出す方法も渡されます(これは非常に非効率でリソースを大量に消費します。正式に使用する場合は、この方法で実行してはなりません。これはテストのみです)。

gpiochipxx: 現在の SoC に含まれる GPIO コントローラー。I.MX6UL/I.MX6ULL には、合計 5 つの GPIO コントローラー (GPIO1、GPIO2、GPIO3、GPIO4、および GPIO5) が含まれており、それぞれ gpiochip0、gpiochip32、gpiochip64、および gpiochip96 に対応します。 、 gpiochip128 の 5 つのフォルダーで、各 gpiochipxx フォルダーは GPIO のグループを管理するために使用されます。

 たとえば、GPIO1_1 をエクスポートするには、次のようにします。

echo 1 > /sys/class/gpio/export

特定の GPIO ピンについて、sysfs で対応する番号を計算するにはどうすればよいですか? 実際、これは非常に単純で、たとえば、GPIO ピンが GPIO4_IO16 である場合、それに対応する番号は何でしょうか。まず、GPIO4 が gpiochip96 に対応していることを確認する必要があり、このグループの GPIO ピンの最小数は 96 (GPIO4_IO0 に対応) であるため、GPIO4_IO16 に対応する数は当然 96 + 16 = 112 になります。 GPIO3_IO20 に対応する数値は 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);
    }  
}

コマンドを使用してechoファイル システム経由で GPIO ポートをエクスポートおよび制御することは、シンプルで使いやすい方法ですが、高いパフォーマンス要件があるシナリオでは十分に効率的ではない可能性があります。libgpiod ライブラリの使用方法など、効率を向上させるために GPIO ライブラリ、デバイス ドライバー、またはユーザー空間ライブラリとツールの使用を検討してください。 

libgpiod ライブラリの紹介

libgpiod は、Linux GPIO と対話するための C ライブラリおよびツールです。Linux 4.8 以降、公式 GPIO sysfs インターフェイスは推奨されません。libgpiod ライブラリは、ioctl 呼び出しと単純な API インターフェイスをカプセル化します。Libgpiod はキャラクタ デバイス インターフェイスであり、GPIO アクセス制御はキャラクタ デバイス ファイル (/dev/gpiodchip0 など) を操作することによって実装されます。
sysfs メソッドと比較して、libgpiod は、ファイル記述子を閉じた後に割り当てられたすべてのリソースが完全に解放されることを保証でき、sysfs メソッドのインターフェイスには存在しない機能 (タイムポーリング、一度に複数の GPIO 値の設定/読み取りなど) を備えています。時間)。さらに、libgpiod には、ユーザーがスクリプトを使用して gpio をカスタマイズできるようにする一連のコマンドライン ツールも含まれています。

详细介绍:libgpiod/libgpiod.git - Linux GPIO キャラクターデバイスと対話するための C ライブラリおよびツール

libgpiod ライブラリの簡単な使用:

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

上の例では、まずgpiod_chip_open指定された GPIO コントローラー (例: /dev/gpiochip0) を開き、次に を使用してgpiod_chip_get_line指定された GPIO ポート (例: ポート 17) を取得します。次に、 を使用してgpiod_line_request_outputGPIO ポートを出力モードに設定し、 を使用してgpiod_line_set_value出力の高レベルと低レベルを制御します。最後に、 を使用して GPIO 入力値を読み取り、と をgpiod_line_get_value使用してリソースを解放しますlibgpiod を使用するには、対応するライブラリ ファイルとヘッダー ファイルをインストールし、コンパイル時に libgpiod ライブラリをリンクする必要があることに注意してください。gpiod_line_releasegpiod_chip_close

libgpiod はカーネルの GPIO キャラクター デバイス インターフェイス (gpiochi) に依存するため、Linux カーネル バージョン 4.8 以上が必要であることに注意してください。4.8 より前のカーネルでは、このインターフェイスが存在しないか不完全であるため、libgpiod ライブラリを使用できません。次の方法を使用できます。

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

オープンシステムコール機能も利用できます。どちらがより効率的ですか? データ量、バッファ サイズ、システム コールの数など、いくつかの要素を考慮する必要があります。上記では、fopen と fwrite の使用を推奨しています。

fopenと はopenLinux でファイルを開くために使用される 2 つの関数ですが、これらの間にはいくつかの違いがあります。

1. fopenC 標準ライブラリの関数で、ファイルをストリーム形式で開くために使用されます。これは、標準ライブラリ関数 ( 、など)を使用してファイルの読み取りと書き込みを行うFILE*ことができる type のポインターを返します。この関数は、自動バッファリング、入出力のフォーマットなど、いくつかの便利な機能を提供します。ただし、これは標準ライブラリ関数であるため、大量のデータを扱う場合にはパフォーマンスが低下する可能性があります。freadfwritefprintffopen

2.openファイルディスクリプタ形式でファイルをオープンするためのシステムコール関数です。readこれは、システム コール関数 ( 、など) を使用してファイルwriteioctl読み取りおよび書き込みに使用できる整数型のファイル記述子を返します。open関数は、カーネルと直接対話するためにオペレーティング システムによって提供される生のインターフェイスであるため、通常、大量のデータを処理する場合よりも効率的ですfopen一般に、単純なファイルの読み取りおよび書き込み操作のみを実行し、処理に標準ライブラリ関数を使用したい場合は、それらの使用を選択できますfopenただし、より効率的なファイル操作が必要で、低レベルの制御にシステム コール関数を使用する必要がある場合は、それらの使用を選択できますopen。 

fwriteデータをファイルに書き込む C 標準ライブラリの関数です。まずデータをバッファに書き込み、次にバッファのデータをファイルに書き込みます。対照的に、writeはバッファを経由せずにデータをファイルに直接書き込むシステム コールです。小さなデータの書き込み操作の場合、fwrite複数の小さなデータを一度にバッファに書き込み、その後一度にファイルに書き込むことができるため、システム コールの数が減り、効率が高くなる可能性があります。大量のデータを含む書き込み操作の場合、write効率が高くなる可能性があります。fwriteデータは最初にバッファに書き込まれ、次にファイルに書き込まれる必要があるため、このプロセスには複数のバッファ フラッシュ操作が含まれる場合がありますまた、writeデータをファイルに直接書き込むことで、バッファ フラッシュのオーバーヘッドを削減します。さらに、バッファ サイズの影響も考慮する必要があります。バッファ サイズがデータ量に対して適切であれば、fwriteパフォーマンスが向上する可能性があります。ただし、データ量がバッファ サイズを超えると、fwriteバッファを複数回フラッシュする必要があり、パフォーマンスの低下につながる可能性があります。一般に、データ量が少ない書き込み操作の方がfwrite効率的である可能性があります。大量のデータを含む書き込み操作の場合は、writeより効率的になる可能性があります。ただし、特定の効率は、特定のシナリオとニーズに従ってテストおよび評価する必要があります。

システムコールの使用方法は以下のとおりです。

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

関数を呼び出すときにopen、2 番目のパラメーターを通じてファイルのアクセス許可を指定できます。許可パラメータは、ファイルの読み取り、書き込み、および実行の許可を示す 8 進数です。一般的に使用される権限パラメータ値の一部を次に示します。

- O_RDONLY: 読み取り専用モード。ファイルを読み取り用に開くことを意味します。

- O_WRONLY: 書き込み専用モード。書き込み用にファイルを開くことを意味します。

- O_RDWR: 読み取り/書き込みモード。読み取りと書き込みのためにファイルを開くことを意味します。

- O_CREAT: ファイルが存在しない場合は作成します。

- O_EXCL:O_CREATと一緒に使用すると、ファイルがすでに存在する場合にエラーが返されます。

- O_TRUNC: ファイルが存在し、書き込みモードで開かれている場合、ファイルの長さが 0 に切り詰められます。

- O_APPEND: ファイルを追加モードで開きます。つまり、書き込み時にファイルの末尾にデータを追加します。許可パラメータは、上記フラグビットとのビット演算を行うことにより、ファイルのアクセス許可を指定することができる。たとえば、ファイルを読み取り/書き込みモードで開き、ファイルが存在しない場合は作成するには、次の権限パラメータを使用できます。

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

上の例では、0644はファイルのアクセス許可を表す 8 進数です。このうち、 は08 進数を表し、6ユーザー(ファイル所有者)が読み取りおよび書き込み権限を持っていることを示し、4グループユーザーが読み取り専用権限を持っていることを示し、4その他のユーザーが読み取り専用権限を持っていることを示します。許可パラメータはファイルの作成時にのみ機能し、既存のファイルの場合、許可パラメータはファイルの許可を変更しないことに注意してください。ファイルの実際の権限は、ファイル システムの権限制御によって決まります。

最後に、OLED をテストするために、簡単にコンパイルできるように単純な Makefile を作成します。

# 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 

テストデモ:

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

}

   

 プロジェクトの完全なソース コードのダウンロード アドレス:

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

その他のリソース

組み込み Linux - IIC バス ドライバー (3): IIC が OLED 周辺機器を駆動する_iic が周辺機器をウェイクアップする方法_moxue10 のブログ - CSDN ブログ

Linux での GPIO アプリケーション プログラミング system_linux 制御 gpio プログラム

Linux システムは、syfs_Embedded に基づいて gpio_gpio linux sys を制御します。 Linux 開発ブログ - CSDN ブログ

オープン ソース コードをクロス コンパイルします (例として libgpiod を使用します)。

[imx6ull アプリケーション開発] GPIO プログラミングの LED ランプ デバイス制御 ---sysfs モードと libgpiod モード_gpiod_line_request_output_WH^2 のブログ - CSDN ブログ

Feiling Embedded Technology 投稿 - i.MX9352 の GPIO の使用方法? _dts_device_operation

[imx6ull アプリケーション開発] GPIO プログラミングの LED ランプ デバイス制御 ---sysfs モードと libgpiod モード_gpiod_line_request_output_WH^2 のブログ - CSDN ブログ

C 言語メソッド (libgpiod) - Sipeed Wiki

LEDを点灯するためのLibgpiodライブラリの使用 - プログラマーが求めた

おすすめ

転載: blog.csdn.net/qq8864/article/details/131978153