0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux驱动开发-编写OLED显示屏驱动

DS小龙哥-嵌入式技术 2022-09-17 15:19 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

【摘要】 OLED显示屏在是智能手环,智能手表上用的非常的多,功耗低,不刺眼,优点特别多。本篇文章就介绍,在Linux系统里如何使用OLED显示屏,要使用OLED显示屏,大致分为两步: (1) 针对OLED显示屏编写一个驱动 (2) 编写应用层程序进行测试。

1. 前言

OLED显示屏在是智能手环,智能手表上用的非常的多,功耗低,不刺眼,优点特别多。本篇文章就介绍,在Linux系统里如何使用OLED显示屏,要使用OLED显示屏,大致分为两步: (1) 针对OLED显示屏编写一个驱动 (2) 编写应用层程序进行测试。

采用的OLED显示屏是0.96寸SPI接口显示屏,分辨率是128*64,比较便宜,淘宝上非常多。

测试开发板采用友善之臂Tiny4412,三星的EXYNOS-4412芯片,4核1.5GHZ,板载8G-EMMC,2G-DDR

2. 硬件接线效果


image-20220104161740431image-20220104161800291image-20220104161852986image-20220104161906616

3. 驱动代码

Linux内核提供了标准SPI子系统框架,和前面介绍的IIC子系统框架使用类似,代码分为设备端和驱动端,Linux内核提供子系统的目的就是为了统一驱动编写标准,提高驱动代码的移植性。

本篇文章代码没有采用SPI子系统框架,采用单片机惯用的模拟SPI时序,对入门而言,代码更容易理解。

3.1 oled.c 驱动示例代码

#include 
#include 
#include  
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*
定义OLED需要使用的寄存器
*/
static volatile unsigned int *GPB_CON=NULL;
static volatile unsigned int *GPB_DAT=NULL;

//OLED屏幕底层接口
#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);}

//命令与数据区分
#define OLED_CMD 0
#define OLED_DAT 1

//函数声明区域
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);
/*
函数功能: OLED对应的GPIO口初始化
硬件连接:
OLED模块---Tiny4412开发板
GND--------GND
VCC--------VCC(5V)
D0---------SCL--------------GPB_0
D1---------MOSI-------------GPB_3
RES--------复位-------------GPB_4
DC---------数据/命令--------GPB_5
CS---------CS片选-----------GPB_1
*/
static u8 OLED_GPIO_Init(void)
{
	/*1. 将物理地址转换为虚拟地址*/
	GPB_CON=ioremap(0x11400040,4);
	GPB_DAT=ioremap(0x11400044,4);
	if(GPB_CON==NULL||GPB_DAT==NULL)
	{
		printk("物理地址转换为虚拟地址出现问题!\n");
		return -1;
	}
	/*2. 配置GPIO口模式*/
	*GPB_CON&=0xFF000F00;
	*GPB_CON|=0x00111011;
	
	/*3. 上拉GPIO口*/
	OLED_CS(1);
	OLED_DC(1);
	OLED_MOSI(1);
	OLED_RES(1);
	OLED_SCK(1);
}
/*
函数功能: OLED屏幕初始化
*/
static void OLED_Init(void)
{
	/*1. 初始化配置GPIO口*/
	OLED_GPIO_Init();

	/*2. 执行OLED屏幕的初始化配置*/
	OLED_RES(1);
	udelay(2000);
	OLED_RES(0);
	udelay(2000);
	OLED_RES(1);
	udelay(2000);
	
	OLED_WriteOneByte(0xAE,OLED_CMD); //0xAE表示关显示,0xAF表示开显示

	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); //正常模式
}

/*
函数功能:  写一个字节
函数参数:
		cmd=0表示命令,cmd=1表示数据
*/
static void OLED_WriteOneByte(u8 data,u8 cmd)
{
	u8 i;
	/*1. 区分发送数据是命令还是屏幕数据*/
	if(cmd){OLED_DC(1);}
	else {OLED_DC(0);}
	udelay(2);
	
	/*2. 发送实际的数据*/
	OLED_CS(0); //选中OLED
	
	for(i=0;i<8;i++)
	{
		udelay(2);
		OLED_SCK(0); //告诉从机,主机将要发送数据
		if(data&0x80){OLED_MOSI(1);} //发送数据
		else {OLED_MOSI(0);}
		udelay(2);
		OLED_SCK(1); //告诉从机,主机数据发送完毕
		data<<=1;   //继续发送下一位数据
	}
	
	OLED_CS(1);  //取消选中OLED
	OLED_SCK(1); //上拉时钟线,恢复空闲电平
}
/*
函数功能: 清屏 (开全部灯、关全部灯)
*/
static void OLED_Clear(u8 data)
{
	u8 i,j;
	for(i=0;i<8;i++)
	{
		OLED_WriteOneByte(0xB0+i,OLED_CMD); //设置页地址
		OLED_WriteOneByte(0x10,OLED_CMD);   //设置列高起始地址(半字节)
		OLED_WriteOneByte(0x00,OLED_CMD);   //设置列低起始地址(半字节)
		for(j=0;j<128;j++)
		{
			OLED_WriteOneByte(data,OLED_DAT); //写数据
		}
	}
}

/*
定义显存数组:  8行,每行128列,与OLED屏幕对应
*/
static u8 OLED_GRAM[8][128]; 

/*
函数功能: 画点函数
		x: 横向坐标0~128
		y: 纵坐标0~64	
		c: 1表示亮、0表示灭
*/
static void OLED_DrawPoint(u8 x,u8 y,u8 c)
{
	u8 page;
	page=y/8; //得到当前点的页数0/8=0 1/8=0 
	y=y%8;    //得到一列中点的位置。(0~7)
  //0%8=0 1%8=1 .....7%8=7  8%8=0  9%8=1 ......
	if(c) OLED_GRAM[page][x]|=1vm_flags |= VM_RESERVED;//标志该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出
	if(remap_pfn_range(vma,//虚拟内存区域,即设备地址将要映射到这里
						vma->vm_start,//虚拟空间的起始地址
						virt_to_phys(mmap_buffer)>>PAGE_SHIFT,//与物理内存对应的页帧号,物理地址右移 12 位
						vma->vm_end - vma->vm_start,//映射区域大小,一般是页大小的整数倍
						vma->vm_page_prot))//保护属性,
	{
		return -EAGAIN;
	}
	
	printk("(drv)映射的长度:%d\n",vma->vm_end - vma->vm_start);
	printk("物理地址:0x%X\n",virt_to_phys(mmap_buffer));
	/*
		开发板的DDR容量: 1G
		0x40000000 ~ 0x80000000 
		0x10000000=256M
	*/
	return 0;
}

#define _OLED_RefreshGRAM 0x12345 /*将应用层的数据刷新到OLED屏幕上*/
#define _OLED_ClearGRAM 0x45678 /*将应用层的数据刷新到OLED屏幕上*/
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); //拷贝数据
			 OLED_RefreshGRAM(); /*刷新*/
			 break;
		case _OLED_ClearGRAM: /*清屏*/
			 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调用成功\n");
	return 0;
}
/*帧缓冲设备专用的文件操作接口*/
static struct fb_ops fbops=
{
	.fb_open=lcd_open,
	.fb_release=lcd_release,
	.fb_mmap=lcd_mmap,
	.fb_ioctl=lcd_ioctl
};
/*帧缓冲的设备结构体*/
static struct fb_info lcd_info=
{
	.var= /*可变形参*/
		{
			.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. 初始化OLED屏幕*/
	OLED_Init();
	OLED_Clear(0);//清屏为黑色
	
	/*2. 帧缓冲驱动注册*/
	if(register_framebuffer(&lcd_info)!=0)
	{
		printk("提示: lcd驱动安装失败!\n");
		return -1;
	}
	else
	{
		printk("提示: lcd驱动安装成功!\n");	
	}
	
    return 0;
}
static void __exit tiny4412_oled_exit(void)
{
	/*1. 帧缓冲驱动注销*/
	if(unregister_framebuffer(&lcd_info)!=0)
	{
		printk("提示: lcd驱动卸载失败!\n");
		return -1;
	}
	else
	{
		printk("提示: lcd驱动卸载成功!\n");
	}
	
	/*2. 解除虚拟地址映射关系*/
	iounmap(GPB_CON);
	iounmap(GPB_DAT);
}

module_init(tiny4412_oled_init);  /*指定驱动的入口函数*/
module_exit(tiny4412_oled_exit);  /*指定驱动的出口函数*/
MODULE_LICENSE("GPL");      	  /*指定驱动许可证*/
;>

3.2 app.c 应用层代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 

unsigned char *lcd_mem=NULL; /*LCD的内存地址*/
struct fb_fix_screeninfo finfo; /*固定形参*/
struct fb_var_screeninfo vinfo; /*可变形参*/
	
unsigned char font[]=
{
/*--  文字:  国  --*/
/*--  宋体42;  此字体下对应的点阵为:宽x高=56x56   宽/8*高*/
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,
};

/*
定义显存数组:  8行,每行128列,与OLED屏幕对应
*/
static unsigned char OLED_GRAM[8][128]; 

/*
函数功能: 画点函数
		x: 横向坐标0~128
		y: 纵坐标0~64	
		c: 1表示亮、0表示灭
*/
static void OLED_DrawPoint(unsigned char x,unsigned char y,unsigned char c)
{
	unsigned char page;
	page=y/8; //得到当前点的页数0/8=0 1/8=0 
	y=y%8;    //得到一列中点的位置。(0~7)
  //0%8=0 1%8=1 .....7%8=7  8%8=0  9%8=1 ......
	if(c) OLED_GRAM[page][x]|=1<;>
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • OLED
    +关注

    关注

    121

    文章

    6332

    浏览量

    232535
  • 显示屏
    +关注

    关注

    30

    文章

    4672

    浏览量

    78691
  • Linux
    +关注

    关注

    88

    文章

    11628

    浏览量

    217981
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    【免费送书】成为硬核Linux开发者:《Linux 设备驱动开发(第 2 版)》

    Linux系统的设备驱动开发,一直给人门槛较高的印象,主要因内核机制抽象、需深度理解硬件原理、开发调试难度大所致。2021年,一本讲解驱动
    的头像 发表于 11-18 08:06 439次阅读
    【免费送书】成为硬核<b class='flag-5'>Linux</b><b class='flag-5'>开发</b>者:《<b class='flag-5'>Linux</b> 设备<b class='flag-5'>驱动</b><b class='flag-5'>开发</b>(第 2 版)》

    蜂鸟E203驱动OLED显示

    利用GPIO模拟IIC驱动4pin的OLED显示字符,开发平台为芯来官方IDE。 不想写过程,上传整个工程文件,主要代码如下: 下载:led
    发表于 10-31 06:08

    力芯微矩阵型恒压LED驱动芯片为车载显示屏带来“智”变

    在汽车智能化飞速发展的今天,车载显示屏作为人车交互的重要窗口,其显示效果和性能直接影响到用户的驾驶体验和行车安全。力芯微矩阵型恒压LED驱动芯片,凭借其卓越的性能和创新的技术,为车载显示屏
    的头像 发表于 10-27 16:02 234次阅读
    力芯微矩阵型恒压LED<b class='flag-5'>驱动</b>芯片为车载<b class='flag-5'>显示屏</b>带来“智”变

    解析LED显示屏背后的驱动方案

    当前,各式各样的LED显示屏正以创新的显示效果与丰富造型,拓展着人们感知视觉世界的维度。而这些视觉盛宴的背后,都离不开“幕后操控者”——LED驱动技术。
    的头像 发表于 10-27 15:04 3678次阅读
    解析LED<b class='flag-5'>显示屏</b>背后的<b class='flag-5'>驱动</b>方案

    【RA4M2-SENSOR】+OLED显示驱动

    RA4M2-SENSOR开发板是一款近于最小系统的开发板,通过添加相应的外设,可丰富其功能。 这里就为它配置一个I2C接口的OLED驱动
    发表于 09-02 18:28

    【RA-Eco-RA6M4开发板评测】点亮OLED显示屏

    【点亮OLED显示屏】 瑞萨 RA6M2 支持硬件 I2C,开发板上有 OLED 显示屏接口,如下:
    发表于 08-31 10:25

    【Milk-V Duo S 开发板免费体验】DuoS 超声波测距 OLED 显示

      上篇搭建开发环境并点亮了 OLED 显示屏,详见: https://bbs.elecfans.com/jishu_2498771_1_1.html   本篇使用 DuoS 驱动超声
    发表于 08-22 03:55

    【RA-Eco-RA6M4开发板评测】+OLED显示驱动

    SDIN------P209 在使用I2C进行硬件驱动前,先以模拟的方式来驱动显示屏。 在程序设计前,需使用RASC对所用引脚加以配置,以是其作为GPIO口来使用。 然后,在回到KEIL中进
    发表于 07-23 17:33

    液晶显示屏背光驱动设计的核心要点

    在液晶显示屏的世界里,无论是信息清晰的单色还是色彩绚丽的彩色,背光都是其视觉呈现的灵魂。然而,背光驱动绝非简单的“通电即亮”。忽视设计细节,轻则导致亮度不均、用户体验打折,重则缩短
    的头像 发表于 07-10 11:46 4010次阅读
    液晶<b class='flag-5'>显示屏</b>背光<b class='flag-5'>驱动</b>设计的核心要点

    冠显光电0.6&quot;HDMI 单目驱动板方案,加速微显示方案落地

    该方案主要包括0.6”硅基显示屏,HDMI单目显示屏驱动板。驱动板以 Micro HDMI 接口为视频数据传输接口,可用于 TDO 硅基产品的 demo 展示、产品特性评估以及基于该
    的头像 发表于 06-09 15:50 529次阅读
    冠显光电0.6&quot;HDMI 单目<b class='flag-5'>驱动</b>板方案,加速微<b class='flag-5'>显示</b>方案落地

    基于Linux的液晶显示屏驱动技术的研究与应用

    液 晶 在我们 日 常 的 生产和 生活 中 应用 广泛 。 本文所描述 的液 晶 驱动 与 应用 是基于 电解铝厂 中 实 际 的 生产 需 要设计而成 , 并且可将其移植到其它 相关 系 统 。 
    发表于 05-06 16:22 0次下载

    户外显示屏驱动板的具体防护措施

    驱动板在户外显示屏中的防水设计是确保显示屏长期稳定运行的关键之一。
    的头像 发表于 04-22 15:58 663次阅读

    迅为RK3568开发驱动指南Linux中通用SPI设备驱动

    迅为RK3568开发驱动指南Linux中通用SPI设备驱动
    的头像 发表于 01-23 11:02 3433次阅读
    迅为RK3568<b class='flag-5'>开发</b>板<b class='flag-5'>驱动</b>指南<b class='flag-5'>Linux</b>中通用SPI设备<b class='flag-5'>驱动</b>

    天马携手康宁推出最新柔性OLED车载显示屏

    在CES 2025上,天马携手康宁联合推出双方共创的最新车载显示成果——双13英寸多曲率一体黑OLED显示屏和13英寸OLED滑移显示屏,通
    的头像 发表于 01-13 09:41 1569次阅读

    【敏矽微ME32G070开发板免费体验】开箱+点灯+点亮OLED

    ME32G070 支持硬件 I2C,这里偷个懒,不想使用繁琐的跳线,直接在开发板上随意插入 OLED 显示屏的引脚,如下: 直接插上 OLED
    发表于 12-19 00:25