• Linux帧缓冲注册OLED驱动


    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 固定参数

      通过ioctl函数,命令参数为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 可变参数

      通过ioctl函数,命令参数为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
    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
    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
    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 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、 构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
    在这里插入图片描述
      本次选用OLED屏幕为0.96寸,驱动IC为SSD1306,驱动协议为SPI。分辨率为128*64;单色屏幕。采用页面寻址方式。

    • 引脚说明
    1. GND 电源地
    2. VCC 电源正( 3~5.5V)
    3. D0 OLED 的 D0 脚,在 SPI 和 IIC 通信中为时钟管脚
    4. D1 OLED 的 D1 脚,在 SPI 和 IIC 通信中为数据管脚
    5. RES OLED 的 RES#脚,用来复位(低电平复位)
    6. DC OLED 的 D/C#E 脚, 数据和命令控制管脚
    7. 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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include 
    #include 
    #include 
    #include 
    /***************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 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #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 \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);
    			}
    	}		
    }
    
    

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    IE浏览器歇菜之后,建模助手干了这件事
    MySQL 的几种碎片整理方案总结(解决delete大量数据后空间不释放的问题)
    Linux 操作系统内核之进程
    江南爱窗帘十大品牌 | 主卧用什么窗帘最好 主卧窗帘颜色搭配有技巧
    讲解用Python处理Excel表格
    husky + lint-staged + commitizen 配置提交代码规范
    数据结构(树)基础,虚拟dom,树的类型,二叉树的类型
    四 . 分支和循环——Java基础篇
    Hyper-V 简介
    哈希表题目:宝石与石头
  • 原文地址:https://blog.csdn.net/weixin_44453694/article/details/126939865