Linux帧缓冲注册OLED驱动

时间:2022-11-21 18:04:21

Linux帧缓冲注册OLED驱动

1.帧缓冲Framebuff

     在 linux 系统中 LCD 这类设备称为帧缓冲设备,英文 frameBuffer 设备。

       frameBuffer 是出现在 2.2.xx 内核当中的一种驱动程序接口。

       帧缓冲( framebuffer)是 Linux 系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。用户不必关心物理显示缓冲区的具体位置及存放方式,这些都由帧缓冲设备驱动本身来完成。

       用户可以将 Framebuffer 看成是显示内存的一个映像, 将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。

       帧缓冲驱动是字符类设备的一种,主设备号为29,生成的设备节点为/dev/fb*

       使用帧缓冲完成屏幕驱动注册,应用层只需调用open函数打开失败,再通过ioctl函数获取屏幕的参数信息,再调用mmap函数将屏幕显存地址映射到进程空间,接下来对地址的写入即是对屏幕的刷。

2.帧缓冲Framebuff应用编程

  •  帧缓冲应用层编程步骤
  1. 打开LCD设备open(“/dev/fb0”,2);
  2. 获取固定参数和可变参数ioctl;
  3. 将屏幕缓冲区映射到进程空间mmap;
  4. 实现屏幕最核心函数画点函数;

2.1 帧缓冲Framebuff设备节点

  通过帧缓冲完成屏幕驱动注册,会在/dev下生成设备节点,主设备号为29,注册的一个设备驱动为/dev/fb0,第二个为/dev/fb1,依此类推,最大可以注册32个设备。

2.2 固定参数

      FBIOGET_FSCREENINFO,固定参数结构体为struct fb_fix_screeninfo。在固定参数可获得的屏幕信息有:smem_len屏幕缓冲区大小、line_length一行的字节数。

#define FBIOGET_FSCREENINFO 0x4602 /*获取屏幕固定参数*/
/*固定参数结构体*/
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
unsigned long smem_start; /* Start of frame buffer mem 屏幕物理地址 */
/* (physical address) */
__u32 smem_len; /* Length of frame buffer mem 屏幕缓冲区大小*/
__u32 type; /* see FB_TYPE_* */
__u32 type_aux; /* Interleave for interleaved Planes */
__u32 visual; /* see FB_VISUAL_* */
__u16 xpanstep; /* zero if no hardware panning */
__u16 ypanstep; /* zero if no hardware panning */
__u16 ywrapstep; /* zero if no hardware ywrap */
__u32 line_length; /* length of a line in bytes 一行的字节数 */
unsigned long mmio_start; /* Start of Memory Mapped I/O */
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */
__u32 accel; /* Indicate to driver which */
/* specific chip/card we have */
__u16 capabilities; /* see FB_CAP_* */
__u16 reserved[2]; /* Reserved for future compatibility */
};

2.3 可变参数

      FBIOGET_VSCREENINFO,固定参数结构体为struct fb_fix_screeninfo。在固定参数可获得的屏幕信息有:屏幕宽度xres、屏幕高度yres、颜色位数bits_per_pixel。

#define FBIOGET_VSCREENINFO 0x4600 /*获取屏幕可变参数*/
/*可变参数结构体*/
struct fb_var_screeninfo {
__u32 xres; /* visible resolution屏幕宽度 */
__u32 yres; /*屏幕高度*/
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible */
__u32 yoffset; /* resolution */

__u32 bits_per_pixel; /* guess what 颜色位数 */
__u32 grayscale; /* 0 = color, 1 = grayscale, */
/* >1 = FOURCC */
struct fb_bitfield red; /* bitfield in fb mem if true color, */
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency */

__u32 nonstd; /* != 0 Non standard pixel format */

__u32 activate; /* see FB_ACTIVATE_* */

__u32 height; /* height of picture in mm */
__u32 width; /* width of picture in mm */

__u32 accel_flags; /* (OBSOLETE) see fb_info.flags */

/* Timing: All values in pixclocks, except pixclock (of course) */
__u32 pixclock; /* pixel clock in ps (pico seconds) */
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* angle we rotate counter clockwise */
__u32 colorspace; /* colorspace for FOURCC-based modes */
__u32 reserved[4]; /* Reserved for future compatibility */
};

2.4 将屏幕缓冲区映射到进空间

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
函数功能: 将文件映射到进程空间
形参: addr --映射的虚拟地址,一般填NULL,有系统自行分配
   length --要映射的空间大小
   prot --PROT_READ可读;PROT_WRITE可写
   flags --MAP_SHARED可读写,读写内容同步到文件;MAP_PRIVATE修改的内容不会同步到文件
   fd --文件描述符
   offset --一般填0,表示映射整个文件
返回值: 成功返回映射的地址
   失败返回-1


int munmap(void *addr, size_t length);
形参: addr --mamp函数返回值
   length --映射空间大小

2.5 帧缓冲获取固定参数和可变参数示例

int main()
{
/*1.打开设备*/
int fd=open("/dev/fb0", 2);
if(fd<0)
{
printf("打开设备失败\n");
}
/*2.获取固定参数*/
memset(&fb_fix,0, sizeof(fb_fix));
ioctl(fd,FBIOGET_FSCREENINFO,&fb_fix);
printf("屏幕缓存大小:%d\n",fb_fix.smem_len);
printf("一行的字节数:%d\n",fb_fix.line_length);
/*3.获取屏幕可变参数*/
memset(&fb_var,0, sizeof(fb_var));
ioctl(fd,FBIOGET_VSCREENINFO,&fb_var);
printf("屏幕尺寸:%d*%d\n",fb_var.xres,fb_var.yres);
printf("颜色位数:%d\n",fb_var.bits_per_pixel);
/*4.将屏幕缓冲区映射到进程空间*/
lcd_p=mmap(NULL,fb_fix.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
close(fd);
if(lcd_p==(void *)-1)
{
printf("内存映射失败\n");
return 0;
}
memset(lcd_p,0xff,fb_fix.smem_len);//将屏幕清空为白色
//取消映射
munmap(lcd_p,fb_fix.smem_len);
return 0;
}
/*画点函数实现*/
static inline void LCD_DrawPoint(int x,int y,int c)
{
//获取要绘制的点的地址
unsigned int *p= (unsigned int *)(lcd_p+y*fb_fix.line_length+x*fb_var.bits_per_pixel/8);
*p=c;//写入颜色值
}

3.帧缓冲驱动编程

  帧缓冲驱动是属于字符类设备的一种,主设备号为29,生成的设备节点为/dev/fb*。实现帧缓冲驱动注册,只需要调用驱动注册函数register_framebuffer,驱动注册注销函数unregister_framebuffer

  •  注册和注销驱动函数
#include <linux/fb.h>
int unregister_framebuffer(struct fb_info *fb_info);
int register_framebuffer(struct fb_info *fb_info);
  •  struct fb_info结构体

    struct fb_info结构体中需要关心的参数有:

       1. 屏幕固定参数结构体struct fb_fix_screeninfo fix、屏幕可变参数结构体struct fb_var_screeninfo var 应用层提供屏幕信息。

       2.帧缓冲文件操作集合struct fb_ops *fbops,需要为应用层接口函数提供入口。

      3.屏幕的内核申请的虚拟地址char __iomem *screen_base,应用层mmap函数映射地址就是和该地址的连接桥梁。

struct fb_info {
atomic_t count;
int node;
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs */
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
struct fb_var_screeninfo var; /* 可变参数 */
struct fb_fix_screeninfo fix; /* 固定参数 */
struct fb_monspecs monspecs; /* Current Monitor specs */
struct work_struct queue; /* Framebuffer event queue */
struct fb_pixmap pixmap; /* Image hardware mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper */
struct fb_cmap cmap; /* Current cmap */
struct list_head modelist; /* mode list */
struct fb_videomode *mode; /* current mode */

#ifdef CONFIG_FB_BACKLIGHT
/* assigned backlight device */
/* set before framebuffer registration,
remove after unregister */
struct backlight_device *bl_dev;

/* Backlight level curve */
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
struct fb_deferred_io *fbdefio;
#endif

struct fb_ops *fbops;/*帧缓冲文件操作集合*/
struct device *device; /* This is the parent */
struct device *dev; /* This is this fb device */
int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* Tile Blitting */
#endif
char __iomem *screen_base; /* Virtual address虚拟地址 */
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */
void *pseudo_palette; /* Fake palette of 16 colors */
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /* Hardware state i.e suspend */
void *fbcon_par; /* fbcon use-only private area */
/* From here on everything is device dependent */
void *par;
/* we need the PCI or similar aperture base/size not
smem_start/size as smem_start may just be an object
allocated inside the aperture so may not actually overlap */
struct apertures_struct {
unsigned int count;
struct aperture {
resource_size_t base;
resource_size_t size;
} ranges[0];
} *apertures;
  •  内核层申请物理地址dma_alloc_writecombine

 因为应用层是通过mmap内存映射方式将屏幕缓冲区映射到进程空间,因此驱动层需要调用dma_alloc_writecombine函数来实现分配屏幕的的物理缓冲区。

#include <linux/dma-mapping.h>
void *dma_alloc_writecombine(struct device *dev, size_t size,dma_addr_t *handle, gfp_t gfp)
函数功能: 内核层动态分配物理内存空间。
形参: dev --没有可直接填NULL
   size --要申请的空间大小
   dma_handle --申请的物理地址
   flag —GFP_KERNEL申请不到就阻塞
返回值: 成功返回申请成功的物理地址对应的虚拟地址
  •  内核层释放申请的物理空间dma_free_writecombine

     dma_free_writecombine函数来完成物理空间释放。

void dma_free_writecombine(struct device *dev, size_t size,void *cpu_addr, dma_addr_t handle)
形参:dev --没有可直接填NULL
   size --要申请的空间大小
   cpu_addr —dma_alloc_writecombine函数返回值
   handle --物理地址

3.1 OLED简介

       OLED,即有机发光二极管( Organic Light Emitting Diode)。 OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、 构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。

Linux帧缓冲注册OLED驱动

  本次选用OLED屏幕为0.96寸,驱动IC为SSD1306,驱动协议为SPI分辨率为128*64单色屏幕。采用页面寻址方式。

  • ​ 引脚说明

     GND 电源地

     VCC 电源正( 3~5.5V)

     D0 OLED 的 D0 脚,在 SPI 和 IIC 通信中为时钟管脚

     D1 OLED 的 D1 脚,在 SPI 和 IIC 通信中为数据管脚

     RES OLED 的 RES#脚,用来复位(低电平复位)

     DC OLED 的 D/C#E 脚, 数据和命令控制管脚

     CS OLED 的 CS#脚,也就是片选管脚

3.2 帧缓冲注册示例

硬件平台: tiny4412
开发平台: ubuntu18.04
交叉编译器: arm-linux-gcc
内核: linux3.5
OLED驱动IC: SSD1306
OLED驱动方式: SPI(采用SPI子系统实现)

  注册SPI子系统实现OLED屏幕驱动,OLED屏幕画点函数实现;通过帧缓冲驱动注册OLED驱动,在/dev下生成设备节点,实现应用层帧缓冲接口。

#include <linux/kernel.h>
#include <linux/module.h>
#include <asm/io.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>

#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/mutex.h>
/***************OLED gpio初始化************
**D0 --时钟线SPI0_SCLK --GPB_0
**D1 --主机输出线SPI0_MOSI --GPB_3
**RES --复位脚 GPB_4
**DC --数据命令选择脚 GPB_5
**CS --片选 SPI0_CS --GPB_1
**
******************************************/
#define OLED_DAT 1//发送数据
#define OLED_CMD 0//发送命令
struct spi_device *oled_spi;
static unsigned int *GPB_CON=NULL;
static unsigned int *GPB_DAT=NULL;
#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);} //时钟脚 //数据命令选择脚

void OLED_Clear(u8 data);
void OLED_ClearGram(void);
void OLED_RefreshGram(void);

void OLED_GPIO_Init(void)
{

GPB_CON=ioremap(0x11400040, 8);//将物理地址映射为虚拟地址
GPB_DAT=GPB_CON+1;
*GPB_CON&=0xff00ffff;
*GPB_CON|=0x00110000;//配置为输出模式
//上拉
OLED_RES(1);
}
/*******************发送一个字节函数***************
**形参:u8 dat -- 要发送数据
** u8 cmd --0发送数据,1发送命令
**
****************************************************/
void OLED_SendByte(u8 dat,u8 cmd)
{
if(cmd)
{
OLED_DC(1);//发送数据
}
else
{
OLED_DC(0);//发送命令
}
spi_write(oled_spi,&dat,1);//发送一个字节
}
/****************OLED初始化***************/
void OLED_Init(void)
{
OLED_GPIO_Init();//OLED GPIO初始化
//软件复位
OLED_RES(1);
mdelay(200);
OLED_RES(0);
mdelay(200);
OLED_RES(1);
mdelay(200);
//OLED初始化序列
OLED_SendByte(0xAE,OLED_CMD); /*进入睡眠模式*/
OLED_SendByte(0x00,OLED_CMD); /*set lower column address*/
OLED_SendByte(0x10,OLED_CMD); /*set higher column address*/
OLED_SendByte(0x40,OLED_CMD); /*set display start line*/
OLED_SendByte(0xB0,OLED_CMD); /*set page address*/
OLED_SendByte(0x81,OLED_CMD); /*设置对比度*/
OLED_SendByte(0xCF,OLED_CMD); /*128*/
OLED_SendByte(0xA1,OLED_CMD); /*set segment remap*/
OLED_SendByte(0xA6,OLED_CMD); /*normal / reverse*/
OLED_SendByte(0xA8,OLED_CMD); /*multiplex ratio*/
OLED_SendByte(0x3F,OLED_CMD); /*duty = 1/64*/
OLED_SendByte(0xC8,OLED_CMD); /*Com scan direction*/
OLED_SendByte(0xD3,OLED_CMD); /*set display offset*/
OLED_SendByte(0x00,OLED_CMD);
OLED_SendByte(0xD5,OLED_CMD); /*set osc division*/
OLED_SendByte(0x80,OLED_CMD);
OLED_SendByte(0xD9,OLED_CMD); /*set pre-charge period*/
OLED_SendByte(0Xf1,OLED_CMD);
OLED_SendByte(0xDA,OLED_CMD); /*set COM pins*/
OLED_SendByte(0x12,OLED_CMD);
OLED_SendByte(0xdb,OLED_CMD); /*set vcomh*/
OLED_SendByte(0x30,OLED_CMD);
OLED_SendByte(0x8d,OLED_CMD); /*set charge pump enable*/
OLED_SendByte(0x14,OLED_CMD);
OLED_SendByte(0xAF,OLED_CMD); /*恢复正常模式*/
OLED_ClearGram();//清空缓冲区
OLED_RefreshGram();//更新显示
}
/****************清屏函数***********
**形参:u8 data -- 0全灭
** -- 0xff全亮
*************************************/
void OLED_Clear(u8 data)
{
u8 i,j;
for(i=0;i<8;i++)
{
OLED_SendByte(0xb0+i,OLED_CMD);//设置页地址
OLED_SendByte(0x10,OLED_CMD);//设置列高地址
OLED_SendByte(0x0,OLED_CMD);//设置列低地址
for(j=0;j<128;j++)OLED_SendByte(data,OLED_DAT);//写满一列
}
}
/******************OLED设置光标*************
**形参:u8 x -- x坐标(0~127)
** u8 y -- y坐标(0~7)
**
********************************************/
void OLED_SetCursor(u8 x,u8 y)
{
OLED_SendByte(0xb0+y,OLED_CMD);//设置页地址
OLED_SendByte(0x10|((x>>4)&0xf),OLED_CMD);//设置列的高位地址
OLED_SendByte(0x00|(x&0xf),OLED_CMD);
}

static u8 OLED_GRAM[8][128];//定义屏幕缓冲区大小
/****************封装画点函数**************
**形参:u8 x -- x坐标0~127
** u8 y -- y坐标:0~63
** u8 c -- 1,亮 ,0灭
**假设:x,y (5,6),9
*******************************************/
void OLED_DrawPoint(u8 x,u8 y,u8 c)
{
u8 page=0;
page=y/8;//y坐标对应在哪一页
//y=12,y/8=1,y%8=12%8=1....4
y=y%8;//对应页上的哪一行6%8=0---6
if(c)OLED_GRAM[page][x]|=1<<y;//点亮对应的点阵
else OLED_GRAM[page][x]&=~(1<<y);//关闭对应的点阵
}
/**************更新数据到屏幕*****************/
void OLED_RefreshGram(void)
{
u8 i,j;
for(i=0;i<8;i++)
{
OLED_SendByte(0xb0+i,OLED_CMD);//设置页地址
OLED_SendByte(0x10,OLED_CMD);//设置列高地址
OLED_SendByte(0x0,OLED_CMD);//设置列低地址
for(j=0;j<128;j++)OLED_SendByte(OLED_GRAM[i][j],OLED_DAT);
}
}
/*******************清屏函数*****************/
void OLED_ClearGram(void)
{
memset(OLED_GRAM,0x0,sizeof(OLED_GRAM));//清除缓冲区
}

#define OLED_REFLASH 0X80
static int oled_ioctl(struct fb_info *info, unsigned int cmd,unsigned long arg)
{
int i,j;
int w,h;
w=info->var.xres;//屏幕宽
h=info->var.yres;
char *p=info->screen_base;
//printk("w=%d,h=%d\n",w,h);
switch(cmd)
{
case OLED_REFLASH://更新数据到屏幕
for(i=0;i<h;i++)
{
for(j=0;j<w;j++)
{
if(*p)
{
OLED_DrawPoint(j,i,1);
}
else
{
OLED_DrawPoint(j,i,0);
}
p++;
}
}
OLED_RefreshGram();//更新显示
break;
}

return 0;
}
/*帧缓冲文件操作集合*/
static struct fb_ops oled_fbops=
{
.fb_ioctl=oled_ioctl

};

static struct fb_info fb_info=
{
.var=
{
.xres=128,
.yres=64,
.bits_per_pixel=8,//8位表示一个像素点
},
.fix=
{
.id="ssd1306",
//.smem_start= //物理地址
.smem_len=128*64,//屏幕缓冲区大小
.line_length=128,//一行的字节数

},
.fbops=&oled_fbops,//文件操作集合
//screen_base 虚拟地址
};
static int oled_probe(struct spi_device *spi)
{
printk("资源匹配成功\n");
printk("name=%s,频率=%d,模式=%d,数据位数:%d\n",spi->modalias,spi->max_speed_hz,spi->mode,spi->bits_per_word);
spi->max_speed_hz=20*1000*1000;//工作频率为20Mhz
spi->bits_per_word=8;//数据8位
spi_setup(spi);//设置SPI参数
oled_spi=spi;

OLED_Init();

/*dma申请物理空间*/
fb_info.screen_base=dma_alloc_writecombine(NULL,fb_info.fix.smem_len,(dma_addr_t *)&fb_info.fix.smem_start,GFP_KERNEL);
/*注册帧缓冲驱动*/
register_framebuffer(&fb_info);

return 0;
}
static int oled_remove(struct spi_device *spi)
{
printk("资源释放成功\n");
/*注销帧缓冲设备*/
unregister_framebuffer(&fb_info);
/*释放空间*/
dma_free_writecombine(NULL,fb_info.fix.smem_len,fb_info.screen_base,fb_info.fix.smem_start);
iounmap(GPB_CON);//取消映射
return 0;
}

static struct spi_driver sdrv=
{
.probe =oled_probe,
.remove =oled_remove,
.driver=
{
.name="spidev",
},
};
static int __init wbyq_oled_init(void)
{
spi_register_driver(&sdrv);//驱动注册
return 0;
}
/*驱动释放*/
static void __exit wbyq_oled_cleanup(void)
{
spi_unregister_driver(&sdrv);//驱动注销
printk("驱动出口,驱动注销成功\n");
}
module_init(wbyq_oled_init);//驱动入口函数
module_exit(wbyq_oled_cleanup);//驱动出口函数

MODULE_LICENSE("GPL");//驱动注册协议
MODULE_AUTHOR("it_ashui");
MODULE_DESCRIPTION("Exynos4 oled Driver");

3.3 帧缓冲应用层

  通过LCD应用编程实现OLED应用程序编写,调用矢量字库实现字符串显示,移植第三方数码管显示示例实现动态数码管式时间显示。

#include <stdio.h>
#include <linux/fb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include "./freetype/freetype.h"
#include "SuperNumber/SuperNumber.h"
#define OLED_REFLASH 0X80
typedef unsigned char u8;
typedef unsigned short u16;
void sDynamicClockInitial(void);
void sDynamicClockProcess(void);
int imag_w,imag_h;
static unsigned char *lcd_p=NULL;//屏幕缓存地址
static struct fb_fix_screeninfo fb_fix;//固定参数结构体
static struct fb_var_screeninfo fb_var;//可变参数结构体
/*LCD画点函数*/
void LCD_DrawPoint(int x,int y,int c)
{
if(fb_var.bits_per_pixel==8)
{
//获取要绘制的点的地址
unsigned char *p= (unsigned char *)(lcd_p+y*fb_fix.line_length+x*fb_var.bits_per_pixel/8);
*p=c;//写入颜色值
}
else
{
//获取要绘制的点的地址
unsigned int *p= (unsigned char *)(lcd_p+y*fb_fix.line_length+x*fb_var.bits_per_pixel/8);
*p=c;//写入颜色值

}
}
int fd;
int main(int argc,char *argv[])
{
if(argc!=2)
{
printf("格式:./a.out </dev/fbx>\n");
return 0;
}
/*1.打开设备*/
fd=open(argv[1], 2);
if(fd<0)
{
printf("打开设备失败\n");
}
/*2.获取固定参数*/
memset(&fb_fix,0, sizeof(fb_fix));
ioctl(fd,FBIOGET_FSCREENINFO,&fb_fix);
printf("屏幕缓存大小:%d\n",fb_fix.smem_len);
printf("一行的字节数:%d\n",fb_fix.line_length);
/*3.获取屏幕可变参数*/
memset(&fb_var,0, sizeof(fb_var));
ioctl(fd,FBIOGET_VSCREENINFO,&fb_var);
printf("屏幕尺寸:%d*%d\n",fb_var.xres,fb_var.yres);
printf("颜色位数:%d\n",fb_var.bits_per_pixel);
imag_w=fb_var.xres;
imag_h=fb_var.yres;
/*4.将屏幕缓冲区映射到进程空间*/
lcd_p=mmap(NULL,fb_fix.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(lcd_p==(void *)-1)
{
printf("内存映射失败\n");
return 0;
}
memset(lcd_p,0x00,fb_fix.smem_len);//将屏幕清空为白色
if(InitConfig_FreeType("msyhbd.ttc"))//初始化freetype
{
printf("字库打开失败\n");
return 0;
}
sDynamicClockInitial();
sDynamicClockProcess();

FreeType_Config();//释放freetype
AA:
//取消映射
munmap(lcd_p,fb_fix.smem_len);
return 0;
}
//一个电子钟包括8个部分
sSuperNum stSuperNum1;
sSuperNum stSuperNum2;
sSuperNum stSuperNum3;
sSuperNum stSuperNum4;
sSuperNum stSuperNum5;
sSuperNum stSuperNum6;
sSuperNum stSuperNum7;
sSuperNum stSuperNum8;

//特效状态转移查询库
uint8_t SegAction[MAX_SEG_STATUE][MAX_SEG_STATUE][SEG_NUM];
/*************************************************************************
** Function Name: sDynamicClockInitial
** Purpose: 初始化时钟的各个数码段部分
** Params:
** @
** Return:
** Notice: None.
** Author: 公众号:最后一个bug
*************************************************************************/
void sDynamicClockInitial(void)
{
#define NUM_OFFSET (19)

uint16_t x_Location = 5;
uint16_t y_Location = 20;

stSuperNum1.pDrawPoint = LCD_DrawPoint;
InitialSuperNum(&stSuperNum1,x_Location,y_Location,10,10,2);
InitialSegShowAction(&stSuperNum1,(uint8_t*)SegAction);

x_Location += NUM_OFFSET;

stSuperNum2.pDrawPoint = LCD_DrawPoint;
InitialSuperNum(&stSuperNum2,x_Location,y_Location,10,10,2);
InitialSegShowAction(&stSuperNum2,(uint8_t*)SegAction);

x_Location += NUM_OFFSET;
stSuperNum3.pDrawPoint = LCD_DrawPoint;
InitialSuperNum(&stSuperNum3,x_Location,y_Location,2,10,2);
InitialSegShowAction(&stSuperNum3,(uint8_t*)SegAction);

x_Location += NUM_OFFSET/2 + 2;
stSuperNum4.pDrawPoint = LCD_DrawPoint;
InitialSuperNum(&stSuperNum4,x_Location,y_Location,10,10,2);
InitialSegShowAction(&stSuperNum4,(uint8_t*)SegAction);

x_Location += NUM_OFFSET;
stSuperNum5.pDrawPoint = LCD_DrawPoint;
InitialSuperNum(&stSuperNum5,x_Location,y_Location,10,10,2);
InitialSegShowAction(&stSuperNum6,(uint8_t*)SegAction);

x_Location += NUM_OFFSET;
stSuperNum6.pDrawPoint = LCD_DrawPoint;
InitialSuperNum(&stSuperNum6,x_Location,y_Location,2,10,2);
InitialSegShowAction(&stSuperNum6,(uint8_t*)SegAction);

x_Location += NUM_OFFSET/2+2;
stSuperNum7.pDrawPoint = LCD_DrawPoint;
InitialSuperNum(&stSuperNum7,x_Location,y_Location+10,5,5,2);
InitialSegShowAction(&stSuperNum7,(uint8_t*)SegAction);

x_Location += NUM_OFFSET/2+4;
stSuperNum8.pDrawPoint = LCD_DrawPoint;
InitialSuperNum(&stSuperNum8,x_Location,y_Location+10,5,5,2);
InitialSegShowAction(&stSuperNum8,(uint8_t*)SegAction);

}


/*************************************************************************
** Function Name: sDynamicClockProcess
** Purpose: 动态时钟处理
** Params:
** @
** Return:
** Notice: None.
** Author: 公众号:最后一个bug
*************************************************************************/
void sDynamicClockProcess(void)
{
static timerCnt = 0;
static uint16_t DPoint = 11;
static uint16_t CurrHour = 23; //当前小时
static uint16_t CurrMin = 59; //当前分钟
static uint16_t CurrSec = 50; //当前s
static uint16_t CurrSecOld = 0xFFFF;//保存的s
static uint16_t SecondPoint = 0;
time_t timep,timep2;//保存当前系统秒单位时间
struct tm result;//保存时间结构体
while(1)
{
timep=time(NULL);
if(timep!=timep2)
{
timep2=timep;
localtime_r(&timep,&result);//将秒单位时间转换为时间结构体
CurrHour=result.tm_hour;
CurrMin=result.tm_min;
CurrSec=result.tm_sec;
}
//下面是更新显示处理
if(CurrSecOld != CurrSec)
{
if(CurrSecOld == 0xFFFF) //表示开机第1s不处理
{
CurrSecOld = 0xFFFE;
}
else
{
CurrSecOld = CurrSec;//更新
DPoint = ((DPoint == 11)?(DPoint = 10):(DPoint = 11)); //点闪烁
}
}

if(CurrSecOld < 60)
{
SuperNumActionPlay(&stSuperNum1,(uint8_t*)SegAction,CurrHour/10);
SuperNumActionPlay(&stSuperNum2,(uint8_t*)SegAction,CurrHour%10);
SuperNumActionPlay(&stSuperNum3,(uint8_t*)SegAction,DPoint);
SuperNumActionPlay(&stSuperNum4,(uint8_t*)SegAction,CurrMin/10);
SuperNumActionPlay(&stSuperNum5,(uint8_t*)SegAction,CurrMin%10);
SuperNumActionPlay(&stSuperNum6,(uint8_t*)SegAction,DPoint);
SuperNumActionPlay(&stSuperNum7,(uint8_t*)SegAction,CurrSecOld/10);
SuperNumActionPlay(&stSuperNum8,(uint8_t*)SegAction,CurrSecOld%10);
ioctl(fd,OLED_REFLASH);
}
}
}

Linux帧缓冲注册OLED驱动

Linux帧缓冲注册OLED驱动

Linux帧缓冲注册OLED驱动