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);
}
}
}
;j++)>;j++)>
-
OLED
+關注
關注
119文章
6219瀏覽量
224787 -
Linux
+關注
關注
87文章
11342瀏覽量
210335 -
函數
+關注
關注
3文章
4345瀏覽量
62952
發布評論請先 登錄
相關推薦
評論