IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 0.96寸OLED屏移植到搭载mx6ull的linux系统 -> 正文阅读

[系统运维]0.96寸OLED屏移植到搭载mx6ull的linux系统

之前买了个开发板,但是板载没有I2C和SPI的资源,但是公司要用到SPI,想练练手,并且熟悉一下linux下的SPI总线设备的开发,于是买了一款0.96寸的OLED显示屏,驱动芯片型号为SSD1306 来玩

我买的这个oled屏幕是七针的,如下图:
oled屏幕
支持SPI和I2C协议,不过要用I2C的话需要改电阻,我这里使用SPI协议。

官方例程有51的,有stm32的,有arduino的,基本是MCU的,需要自己移植到linux下,开搞!

设备树修改

用到SPI3这个spi控制器,于是在设备树中创建:
pinctrl_ecspi3节点

		pinctrl_ecspi3: ecspi3grp {
			fsl,pins = <
				MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO        0x100b1  /* MISO*/
				MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI        0x100b1  /* MOSI*/
				MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK      0x100b1  /* CLK*/
				MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20       0x100b0  /* CS*/
			>;
		};

因为这个oled屏幕有reset脚和dc命令脚,所以再用两个GPIO:

		pinctrl_oled96_reset: oled96resetgrp {
			fsl,pins = <
				/* used for oled96 reset */
				MX6UL_PAD_UART3_TX_DATA__GPIO1_IO24           0x10B0
			>;
		};
			
		pinctrl_oled96_DC: oled96DCgrp {
			fsl,pins = <
				/* used for oled96 DC */
				MX6UL_PAD_UART3_RX_DATA__GPIO1_IO25           0x10B0
			>;
		};

创建oled96spi节点:

&ecspi3 {
	fsl,spi-num-chipselects = <1>;
	cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>;
	reset-gpio = <&gpio1 24 GPIO_ACTIVE_LOW>;
	dc-gpio = <&gpio1 25 GPIO_ACTIVE_LOW>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_ecspi3
				 &pinctrl_oled96_reset
				 &pinctrl_oled96_DC>;
	status = "okay";

	spidev: oled96spi@0 {
	compatible = "alientek,oled96spi";
        spi-max-frequency = <8000000>;
        reg = <0>;
    };
};

我把两个新的gpio用这种方式加进来了,有啥更好的方式欢迎大佬评论区留言!

注意:上面修改设备树的时候要看下有没有IO被占用了,把占用的IO注释掉,我这里注释掉了uart2节点。

编写驱动

这里用SPI总线方式编写设备驱动,主机驱动已经好了。

#include <linux/types.h>
.../*省略头文件*/
/***************************************************************
文件名	: oled96spi.c
作者	  	: 1997AURORA
版本	   	: V1.0
描述	   	: SSD1306 SPI驱动程序
其他	   	: 无
日志	   	: 初版V1.0 2021/7/31 1997AURORA创建
***************************************************************/
#define oled96_CNT	1
#define oled96_NAME	"oled96spi"

#define OLED_CMD  0
#define OLED_DATA 1

static u8 OLED_GRAM[144][8];
unsigned char BMP1[] =
{
/*这里存放64*16个Bytes的数据,即128*64像素
字数太多,删除。可以使用PCtoLCD2002取模*/
};

unsigned char BMP2[] =
{
	/*同理,删除*/
	/*"C:\Users\zjh46\Desktop\2.BMP",0*/
};

struct oled96_dev {
	dev_t devid;				/* 设备号 	 */
	struct cdev cdev;			/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;		/* 设备 	 */
	struct device_node	*nd; 	/* 设备节点 */
	int major;					/* 主设备号 */
	void *private_data;			/* 私有数据 		*/
	int cs_gpio;				/* 片选所使用的GPIO编号		*/
	int reset_gpio;				/* 片选所使用的GPIO编号		*/
	int dc_gpio;				/* 片选所使用的GPIO编号		*/
	//u8 OLED_GRAM[144][8];     /* 显存    */
};

static struct oled96_dev oled96dev;

static s32 oled96_write_regs(struct oled96_dev *dev, u8 *buf, u8 len)
{
	int ret;

	unsigned char txdata[len];
	struct spi_message m;
	struct spi_transfer *t;
	struct spi_device *spi = (struct spi_device *)dev->private_data;

	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);
	gpio_set_value(dev->cs_gpio, 0);	/* 片选拉低 */ 	

	t->tx_buf = buf;		
	t->len = len;				
	spi_message_init(&m);		
	spi_message_add_tail(t, &m);
	ret = spi_sync(spi, &m);

	kfree(t);				
	gpio_set_value(dev->cs_gpio, 1);/* 片选拉高,释放oled96 */ 
	return ret;
}

 static void oled96_write_onereg(struct oled96_dev *dev, u8 value, u8 cmd)
 {
	 u8 buf = value;
	 if(cmd)
		 gpio_set_value(dev->dc_gpio, 1);
	 else 
		 gpio_set_value(dev->dc_gpio, 0);
	 oled96_write_regs(dev, &buf, 1);
	 gpio_set_value(dev->dc_gpio, 1);
 }

 //更新显存到OLED	 
 static void OLED_Refresh(void)
 {
	 u8 i,n;
	 for(i=0;i<8;i++)
	 {
		oled96_write_onereg(&oled96dev, 0xb0+i,OLED_CMD); //设置行起始地址
		oled96_write_onereg(&oled96dev, 0x00,OLED_CMD);   //设置低列起始地址
		oled96_write_onereg(&oled96dev, 0x10,OLED_CMD);   //设置高列起始地址
		for(n=0;n<128;n++)
		  oled96_write_onereg(&oled96dev,OLED_GRAM[n][i],OLED_DATA);
   }
 }
 //清屏函数
 static void OLED_Clear(void)
 {
	 u8 i,n;
	 for(i=0;i<8;i++)
	 {
		for(n=0;n<128;n++)
			 {
			  OLED_GRAM[n][i]=0;//清除所有数据
			 }
   }
	 OLED_Refresh();//更新显示
 }
 //画点 
 //x:0~127
 //y:0~63
static void OLED_DrawPoint(u8 x,u8 y)
 {
	 u8 i,m,n;
	 i=y/8;
	 m=y%8;
	 n=1<<m;
	 OLED_GRAM[x][i]|=n;
 }

 //x,y:圆心坐标
 //r:圆的半径
static void OLED_DrawCircle(u8 x,u8 y,u8 r)
 {
	 int a, b,num;
	 a = 0;
	 b = r;
	 while(2 * b * b >= r * r)		
	 {
		 OLED_DrawPoint(x + a, y - b);
		 OLED_DrawPoint(x - a, y - b);
		 OLED_DrawPoint(x - a, y + b);
		 OLED_DrawPoint(x + a, y + b);
  
		 OLED_DrawPoint(x + b, y + a);
		 OLED_DrawPoint(x + b, y - a);
		 OLED_DrawPoint(x - b, y - a);
		 OLED_DrawPoint(x - b, y + a);
		 
		 a++;
		 num = (a * a + b * b) - r*r;//计算画的点离圆心的距离
		 if(num > 0)
		 {
			 b--;
			 a--;
		 }
	 }
 }
//配置写入数据的起始位置
void OLED_WR_BP(u8 x,u8 y)
{
	oled96_write_onereg(&oled96dev, 0xb0+y,OLED_CMD);//设置行起始地址
	oled96_write_onereg(&oled96dev, ((x&0xf0)>>4)|0x10,OLED_CMD);//设置低列起始地址
	oled96_write_onereg(&oled96dev, (x&0x0f)|0x01,OLED_CMD);//设置高列起始地址
}

//x0,y0:起点坐标
//x1,y1:终点坐标
//BMP[]:要写入的图片数组
void OLED_ShowPicture(u8 x0,u8 y0,u8 x1,u8 y1,u8 BMP[])
{
	u32 j=0;
	u8 x=0,y=0;
	if(y%8==0)y=0;
	else y+=1;
	for(y=y0;y<y1;y++)
	 {
		 OLED_WR_BP(x0,y);
		 for(x=x0;x<x1;x++)
		 {
			 oled96_write_onereg(&oled96dev, BMP[j],OLED_DATA);
			 j++;
    	 }
	 }
}

static int oled96_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &oled96dev; 
	return 0;
}

static ssize_t oled96_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
	return 0;
}

static ssize_t oled96_write(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
	unsigned char databuf[1];
	unsigned char oledstat;
	long err = 0;
	struct oled96_dev *dev = (struct oled96_dev *)filp->private_data;

	err = copy_from_user(databuf, buf, sizeof(cnt));
	if(err < 0) {
		printk("kernel write failed!\r\n");
		return -EFAULT;
	}
	oledstat = databuf[0];		/* 获取终端值 */
	if(oledstat == 0) {	
		OLED_Clear();
		OLED_DrawCircle(72,20,20);
		OLED_Refresh();
		printk("draw a 20 circle\r\n");
	} else if(oledstat == 1) {
		OLED_Clear();
		OLED_ShowPicture(0,0,128,8,BMP2);
		//OLED_Refresh();
		printk("draw a picture\r\n");
	}
	
	return 0;
}

static int oled96_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static const struct file_operations oled96_ops = {
	.owner = THIS_MODULE,
	.open = oled96_open,
	.read = oled96_read,
	.write = oled96_write,
	.release = oled96_release,
};

void oled96_reginit(struct oled96_dev *dev)
{
	u8 value = 0;
	
	gpio_set_value(dev->reset_gpio, 1);
	mdelay(100);
	gpio_set_value(dev->reset_gpio, 0);//复位
	mdelay(200);
	gpio_set_value(dev->reset_gpio, 1);

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

static int oled96_probe(struct spi_device *spi)
{
	int ret = 0;

	if (oled96dev.major) {
		oled96dev.devid = MKDEV(oled96dev.major, 0);
		register_chrdev_region(oled96dev.devid, oled96_CNT, oled96_NAME);
	} else {
		alloc_chrdev_region(&oled96dev.devid, 0, oled96_CNT, oled96_NAME);
		oled96dev.major = MAJOR(oled96dev.devid);
	}

	cdev_init(&oled96dev.cdev, &oled96_ops);
	cdev_add(&oled96dev.cdev, oled96dev.devid, oled96_CNT);

	oled96dev.class = class_create(THIS_MODULE, oled96_NAME);
	if (IS_ERR(oled96dev.class)) {
		return PTR_ERR(oled96dev.class);
	}

	oled96dev.device = device_create(oled96dev.class, NULL, oled96dev.devid, NULL, oled96_NAME);
	if (IS_ERR(oled96dev.device)) {
		return PTR_ERR(oled96dev.device);
	}

	oled96dev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
	if(oled96dev.nd == NULL) {
		printk("ecspi3 node not find!\r\n");
		return -EINVAL;
	} 

	oled96dev.cs_gpio = of_get_named_gpio(oled96dev.nd, "cs-gpio", 0);
	if(oled96dev.cs_gpio < 0) {
		printk("can't get cs-gpio");
		return -EINVAL;
	}
	oled96dev.reset_gpio = of_get_named_gpio(oled96dev.nd, "reset-gpio", 0);
	if(oled96dev.reset_gpio < 0) {
		printk("can't get reset-gpio");
		return -EINVAL;
	}
	oled96dev.dc_gpio = of_get_named_gpio(oled96dev.nd, "dc-gpio", 0);
	if(oled96dev.dc_gpio < 0) {
		printk("can't get reset-gpio");
		return -EINVAL;
	}

	ret = gpio_direction_output(oled96dev.cs_gpio, 1);
	if(ret < 0) {
		printk("can't set gpio!\r\n");
	}

	ret = gpio_direction_output(oled96dev.reset_gpio, 1);
	if(ret < 0) {
		printk("can't set res gpio!\r\n");
	}

	ret = gpio_direction_output(oled96dev.dc_gpio, 1);
	if(ret < 0) {
		printk("can't set dc gpio!\r\n");
	}

	spi->mode = SPI_MODE_0;
	spi_setup(spi);
	oled96dev.private_data = spi; 

	/* 初始化oled96内部寄存器 */
	oled96_reginit(&oled96dev);	
	OLED_DrawCircle(72,30,30);
	OLED_Refresh();
	printk("draw a circle\r\n");
	return 0;
}

static int oled96_remove(struct spi_device *spi)
{
	cdev_del(&oled96dev.cdev);
	unregister_chrdev_region(oled96dev.devid, oled96_CNT);

	device_destroy(oled96dev.class, oled96dev.devid);
	class_destroy(oled96dev.class);
	return 0;
}

static const struct spi_device_id oled96_id[] = {
	{"alientek,oled96spi", 0},  
	{}
};

static const struct of_device_id oled96_of_match[] = {
	{ .compatible = "alientek,oled96spi" },
	{ /* Sentinel */ }
};

static struct spi_driver oled96_driver = {
	.probe = oled96_probe,
	.remove = oled96_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "oled96spi",
		   	.of_match_table = oled96_of_match, 
		   },
	.id_table = oled96_id,
};

static int __init oled96_init(void)
{
	return spi_register_driver(&oled96_driver);
}

static void __exit oled96_exit(void)
{
	spi_unregister_driver(&oled96_driver);
}

module_init(oled96_init);
module_exit(oled96_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("1997AURORA");

编写APP程序

#include <linux/types.h>
.../*省略头文件*/
/***************************************************************
文件名	: oled96spiApp.c
作者	  	: 1997AURORA
版本	   	: V1.0
描述	   	: SSD1306 SPI应用程序
其他	   	: 无
日志	   	: 初版V1.0 2021/7/31 1997AURORA创建
***************************************************************/
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	unsigned char databuf[1];
	
	if(argc != 3){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR);	/* 打开oled96spi驱动 */
	if(fd < 0){
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	databuf[0] = atoi(argv[2]);	/* 要执行的操作*/
	retvalue = write(fd, databuf, sizeof(databuf));
	if(retvalue < 0){
		printf("oled Control Failed!\r\n");
		close(fd);
		return -1;
	}

	retvalue = close(fd); /* 关闭文件 */
	if(retvalue < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	return 0;
}

这个程序可以操作这个设备,使其显示不同的东西,这里程序写的简单,只有两种,一种是画圆,一种是显示图片 。

我这里测试的log是这样的:

/ # 
/ # cd /lib/modules/4.1.15/
/lib/modules/4.1.15 # depmod 
/lib/modules/4.1.15 # modprobe oled96spi.ko 
draw a circle
/lib/modules/4.1.15 # ./oled96spiApp /dev/oled96spi 1
random: nonblocking pool is initialized
draw a picture
/lib/modules/4.1.15 # ./oled96spiApp /dev/oled96spi 0
draw a 20 circle
/lib/modules/4.1.15 # ./oled96spiApp /dev/oled96spi 1
draw a picture
/lib/modules/4.1.15 # 

显示图片的时候如下图:
oled显示图片

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-08-03 11:36:38  更:2021-08-03 11:37:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/10 0:20:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码