Linux帧缓冲注册OLED驱动(下)
1.帧缓冲驱动编程
帧缓冲驱动是属于字符类设备的一种,主设备号为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;单色屏幕。采用页面寻址方式。
引脚说明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 #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]|=1var.yres; char *p=info->screen_base; //printk(“w=%d,h=%dn”,w,h); switch(cmd) { case OLED_REFLASH://更新数据到屏幕 for(i=0;imax_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” ,=”” },=”” };=”” int=”” __init=”” wbyq_oled_init(void)=”” spi_register_driver(&sdrv);=”” 驱动注册=”” *驱动释放*=”” 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”);=”” ;j++)=””>;>;> 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(“屏幕缓存大小:%dn”,fb_fix.smem_len); printf(“一行的字节数:%dn”,fb_fix.line_length); /*3.获取屏幕可变参数*/ memset(&fb_var,0, sizeof(fb_var)); ioctl(fd,FBIOGET_VSCREENINFO,&fb_var); printf(“屏幕尺寸:%d*%dn”,fb_var.xres,fb_var.yres); printf(“颜色位数:%dn”,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缓存过大 https://www.yhzz.com.cn/a/7276.html