嵌入式Linux——IIC匯流排驅動(3):IIC驅動OLED外設
簡介:
本文主要介紹在jz2440開發板上驅動OLED外設,使其顯示我們在應用層輸入的語句。而同時我將該文分成了兩部分,第一部分講解i2c匯流排的實現,而第二部分講解在i2c匯流排實現後,我們使用字元裝置驅動來實現對OLED的控制。
Linux核心:linux-2.6.22.6
所用開發板:JZ2440 V3
所用OLED 螢幕:中景園電子0.96 寸OLED 顯示屏12864液晶屏模組
所用OLED 驅動晶片:SSD1306
宣告:
本文主要講解在JZ2440上如何驅動OLED,因此我們更注重的是講解如何實現這個功能,而關於i2c匯流排的相關原理,如果你不明白可以去看我的上一篇文章:
第一部分:2c匯流排的實現
i2c匯流排的實現其實就是申請,設定和註冊i2c_driver結構體以及i2c_client結構體的過程。而說詳細點就是使用i2c_driver結構體的attach_adapter函式來確定我們的裝置驅動與外接的i2c裝置是否匹配,如果匹配則呼叫相應的處理函式。在處理函式中我們實現對這個從機裝置的註冊,為他註冊一個i2c_client結構體。然後就是我們第二部分要講的使用字元驅動實現對OLED的控制。下面我們進入正題,直接進入程式,順著程式慢慢了解驅動OLED的過程。
我們先看入口函式:s3c_i2c_init
static int s3c_i2c_init(void)
{
i2c_add_driver(&oled_driver); /* 將i2c_driver結構體註冊到核心 */
return 0;
}
從上面我們看出他主要就是將i2c_driver結構體註冊進核心。而i2c_driver結構體是怎麼設定的我們就要看繼續分析了:
static struct i2c_driver oled_driver = { .driver = { .name = "oled" , }, .attach_adapter = oled_attach, .detach_client = oled_detach, };
而在上面的程式中,我們可以看到驅動的名稱,以及兩個回撥函式:attach_adapter和detach_client。而attach_adapter的作用是呼叫i2c_probe函式來測試從機地址與從機裝置是否匹配。而detach_client的作用是解除安裝這個驅動後如果之前發現能夠支援的裝置則呼叫他來清理。由此可見這兩個回撥函式是很重要的。我們先按著attach_adapter函式這條線分析下去。
static int oled_attach(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, oled_detect);
}
從上面看,他確實只調用了i2c_probe函式,那麼我們就要了解這個函式的功能了。從上面看我們知道他有三個引數,分別為:介面卡,7位從機地址以及從機匹配成功後的處理函式(而該函式回撥我們所寫的函式)。而從這些引數我們不難猜出這個函式做了什麼。他是將我們的從機地址與連線在介面卡上的從機裝置進行匹配,如果他們匹配成功,我們將呼叫第三個引數:處理函式。
不過我們的第二個引數:從機地址也是有格式要求的:
struct i2c_client_address_data {
unsigned short *normal_i2c; /* 正常模式 */
unsigned short *probe;
unsigned short *ignore; /* 忽略 */
unsigned short **forces; /* 強制模式 */
};
我們主要介紹上面的兩種模式:正常模式和強制模式。
正常模式:要發出S訊號和地址訊號並得到ACK訊號才能確定是否存在這個裝置
強制模式:強制認為存在這個裝置
我下面舉個例子說一下這兩種模式的不同,比如我們的裝置的7位從機地址為0x3c,而在正常模式下,我們要發出S訊號和地址訊號並得到ACK訊號才能確定是否存在這個裝置,如果存在呼叫處理函式。而在強制模式是不過你從機地址與從機裝置是否匹配我們都強制認為存在這個裝置,並且匹配,而直接呼叫處理函式。而在本文中我們使用正常模式,程式為:
static unsigned short ignore[] = {I2C_CLIENT_END};
static unsigned short normal_addr[] = {0x3c,I2C_CLIENT_END}; /* 地址值為7位,如果將0x50改為0x60,
*由於不存在裝置地址為0x60的裝置
*所以oled_detect不會被呼叫
*/
static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr, /* 要發出S訊號和地址訊號並得到ACK訊號才能確定是否存在這個裝置 */
.probe = ignore,
.ignore = ignore,
//.forces = forces, /* 強制認為存在這個裝置 */
};
下面我們順著程式繼續講,當我們的從機地址和從機裝置匹配後,會呼叫我們的處理函式:oled_detect,而在我們的處理函式中做了什麼那?
static int oled_detect (struct i2c_adapter *adapter, int address, int kind)
{
/* 構造一個i2c_client結構體:以後收發資料時會用到它 */
new_client = kzalloc(sizeof(struct i2c_client),GFP_KERNEL);
new_client->addr = address; /* 從機地址 */
new_client->adapter = adapter; /* 介面卡 */
new_client->driver = &oled_driver; /* i2c_driver結構體 */
strcpy(new_client->name,"oled");
i2c_attach_client(new_client);
/* 註冊字元裝置驅動 */
auto_major = register_chrdev(0,"oled",&oled_fops);
cls = class_create(THIS_MODULE,"oled");
class_device_create(cls,NULL,MKDEV(auto_major,0),NULL,"oled");
return 0;
}
我們主要就是做了兩件事:
1. 構造一個i2c_client結構體:以後收發資料時會用到它
2. 註冊字元裝置驅動
我們知道我們從機地址和外設從機匹配後我們就會用一個i2c_client結構體來記錄這個從機裝置並將它註冊到核心,而上面程式中我們做的第一件事就是記錄這個從機裝置,這其中包括他的從機地址,所屬介面卡以及所呼叫的i2c_driver結構體。
第二部分:使用字元裝置驅動實現OLED的控制
而從設定字元驅動我們就進入了第二部分,而這部分也是我們的重點了,因為上面講解的這部分程式碼是我借鑑他人的,而你也可以在其他的i2c驅動中看到相似的程式碼。而下面這部分程式碼才是真正我自己所寫。
而要了解字元裝置驅動我們就要看他的file_operations結構體,通過他我們就知道字元裝置做了什麼。
static struct file_operations oled_fops = {
.owner = THIS_MODULE,
.write = oled_write,
.open = oled_open,
};
從上面我們可以看出他主要有兩個函式:oled_open和oled_write,而oled_open的工作就是開啟這個裝置檔案時對OLED進行初始化,而oled_write的工作是從客戶端獲得資料,並將其顯示到OLED上。
我們先看open函式:
int oled_open (struct inode * inode, struct file *file)
{
int data_len;
int i;
unsigned char j,n;
/* 初始化OLED */
unsigned char data[] ={0xAE,0x00,0x10,0x40,0xB0,0x81,0xFF,0xA1,0xA6,0xA8,0x3F,0xC8,0xD3,0x00,0xD5,0x80,0xD8,0x05,0xD9,0xF1,0xDA,0x12,0xDB,0x30,0x8D,0x14,0xAF};
data_len = sizeof(data)/sizeof(data[0]);
for(i=0;i<data_len;i++){
oled_write_1bit_cmd(data[i]);
}
/* 清屏 */
for(j=0;j<8;j++)
{
oled_write_1bit_cmd(0xb0+j); //page0-page7
oled_write_1bit_cmd(0x00); //low column start address
oled_write_1bit_cmd(0x10); //high column start address
for(n=0;n<128;n++){
oled_write_1bit_dat(0);
}
} //更新顯示
return 0;
}
從上面可以看出主要做了兩件事:
1.初始化OLED
2. 清屏OLED
而初始化OLED就是使用oled_write_1bit_cmd函式將晶片SSD1306中所對應的命令依次寫入SSD1306晶片中。而關於SSD1306晶片中具體的命令就要各位讀者自己查了。而清屏就是使用oled_write_1bit_dat函式將資料“0”寫入SSD1306晶片資料儲存器的每一頁。而具體oled_write_1bit_cmd函式和oled_write_1bit_dat函式是如何實現的,我們看下面:
/********************************************
* oled_write_1bit_cmd
* 寫一位元組命令
********************************************/
int oled_write_1bit_cmd(unsigned char cmd)
{
struct i2c_msg msg[1];
unsigned char cmds[2];
int ret;
cmds[0] = 0x00;
cmds[1] = cmd;
/* 資料傳輸三要素:源,目的,長度 */
msg[0].addr = new_client->addr; /* 目的 */
msg[0].buf = cmds; /* 源 */
msg[0].len = 2; /* 長度為兩byte = 資料 + 地址 */
msg[0].flags= 0; /* 0表示寫 */
ret = i2c_transfer(new_client->adapter,msg,1);
if(ret == 1)
return 2;
else
return -EIO;
}
/********************************************
* oled_write_1bit_dat
* 寫一位元組資料
********************************************/
int oled_write_1bit_dat(unsigned char data)
{
struct i2c_msg msg[1];
unsigned char datas[2];
int ret;
datas[0] = 0x40;
datas[1] = data;
/* 資料傳輸三要素:源,目的,長度 */
msg[0].addr = new_client->addr; /* 目的 */
msg[0].buf = datas; /* 源 */
msg[0].len = 2; /* 長度為兩byte = 資料 + 地址 */
msg[0].flags= 0; /* 0表示寫 */
ret = i2c_transfer(new_client->adapter,msg,1);
if(ret == 1)
return 2;
else
return -EIO;
}
為什麼將這兩個函式放到一起看那?因為他們太相似了,他們唯一的區別就是命令的第一個字元為0x00,而資料的第一個字元為0x40.而這個差別是由於SSD1306晶片確定的,看下圖:
從第二個紅框可以看出,0x00表示命令而0x40表示資料。
而在上面兩個函式中都用到了一個結構體:i2c_msg,他的作用是作為資訊的載體,通過呼叫i2c_transfer函式,實現我們向i2c從機讀寫資料。
struct i2c_msg {
__u16 addr; /* 從機地址 */
__u16 flags; /* 傳輸標記位 */
__u16 len; /* 資料長度 */
__u8 *buf; /* 指向資料的指標 */
};
我們知道資料傳輸的三要素:源,目的和長度。而當標誌為0時表示寫,那麼addr為目的(向從機寫)而buf為源(從主機出)。而當標誌為1時表示讀,那麼addr為源(從從機讀)而buf為目的(讀往主機)。
而上面講的i2c_transfer函式的作用就是將資料傳到從機,對從機進行相應的操作,而他的呼叫關係為:
i2c_transfer(adapter, msg, num) < 0)
adap->algo->master_xfer(adap,msgs,num);
.master_xfer= s3c24xx_i2c_xfer,
s3c24xx_i2c_doxfer(i2c, msgs, num);
i2c->msg = msgs;
i2c->msg_num = num;
i2c->msg_ptr = 0;
i2c->msg_idx = 0;
i2c->state = STATE_START;
s3c24xx_i2c_enable_irq(i2c);
s3c24xx_i2c_message_start(i2c, msgs);
iiccon = readl(i2c->regs + S3C2410_IICCON);
writel(stat, i2c->regs + S3C2410_IICSTAT);
writeb(addr, i2c->regs + S3C2410_IICDS);
從中我們可以看出,他最後會落實到對主機的暫存器進行操作,進而控制從機。
有了上面寫一個位元組的命令或者寫一個位元組的資料函式。我們就可以做很多關於OLED設定的事情了,其中包括:
/********************************************
* OLED_WR_Byte
* OLED寫位元組,當cmd為0時表示寫命令
* 當cmd為1時表示寫資料
********************************************/
void OLED_WR_Byte(unsigned dat,unsigned cmd)
{
if(cmd)
{
oled_write_1bit_dat(dat);
}
else
{
oled_write_1bit_cmd(dat);
}
}
/********************************************
* OLED_Set_Pos
* 座標設定
* 引數:
* x:表示橫向的起始地址,0~127
* y:表示縱向的起始地址,0~63
********************************************/
void OLED_Set_Pos(unsigned char x, unsigned char y)
{
OLED_WR_Byte(0xb0+y,OLED_CMD);
OLED_WR_Byte(((x&0xf0)>>4)|0x10,OLED_CMD);
OLED_WR_Byte((x&0x0f),OLED_CMD);
}
/********************************************
* OLED_ShowChar
* 在指定位置顯示一個字元,包括部分字元
* 引數:
* x:表示橫向的起始地址,0~127
* y:表示縱向的起始地址,0~63
* chr:表示使用者輸入字元
********************************************/
void OLED_ShowChar(unsigned char x,unsigned char y,unsigned char chr)
{
unsigned char c=0,i=0;
c=chr-' ';//得到偏移後的值
if(x>128-1){x=0;y=y+2;}
OLED_Set_Pos(x,y);
for(i=0;i<8;i++)
OLED_WR_Byte(F8X16[c*16+i],OLED_DATA);
OLED_Set_Pos(x,y+1);
for(i=0;i<8;i++)
OLED_WR_Byte(F8X16[c*16+i+8],OLED_DATA);
}
/********************************************
* OLED_ShowString
* 顯示一個字元號串
* 引數:
* x:表示橫向的起始地址
* y:表示縱向的起始地址
* chr:表示使用者輸入字串
*注:函式中說明,當一行資料寫滿是會自動跳到下一行顯示
********************************************/
void OLED_ShowString(unsigned char x,unsigned char y,unsigned char *chr)
{
unsigned char j=0;
while (chr[j]!='\0')
{ OLED_ShowChar(x,y,chr[j]);
x+=8;
if(x>120){x=0;y+=2;}
j++;
}
}
上面就是對OLED一些功能的設定,其中包括OLED寫位元組,座標設定,在指定位置顯示一個字元以及顯示一個字串。而我們主要做的就是顯示一個字串,所以我先將要用到的陣列寫下來:
const unsigned char F8X16[]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 0
0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 1
0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 2
·····················································································
······················································································
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v 86
0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w 87
0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x 88
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y 89
0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z 90
0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{ 91
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//| 92
0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//} 93
0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~ 94
};
由於這個陣列比較長,我這裡做了相應的省略,但大致我們知道,我們鍵盤上所有的字母在這個陣列中都可以找到。
有了上面的介紹,我們現在講解如何在write函式中實現將使用者程式所輸的字串顯示到OLED。
static ssize_t oled_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
unsigned char val[64];
copy_from_user(val,buf,64);
OLED_ShowString(0,0,val,16);
return 0;
}
其實你看後會覺得很簡單,就是通過copy_from_user函式從使用者程式獲得字串,然後通過函式OLED_ShowString將該字串顯示到OLED。而關於OLED_ShowString函式我在上面已經介紹了。
現在我們的驅動程式就寫完了,同時我還寫了一個測試程式用它來測試這個驅動程式。我們看他主要做了什麼:
int main(int argc,char **argv)
{
int fd;
unsigned char buf[64] = {0};
int i;
fd = open("/dev/oled",O_RDWR); /* 開啟字元裝置/dev/oled */
if(fd < 0){
printf(" can't open /dev/oled \n\r");
return -1;
}
write(fd,argv[1],64); /* 將接收的第二個字串傳入核心的字元裝置 */
return 0;
}
而測試的效果圖為:
而相應的OLED顯示為:
相關推薦
嵌入式Linux——IIC匯流排驅動(3):IIC驅動OLED外設
簡介: 本文主要介紹在jz2440開發板上驅動OLED外設,使其顯示我們在應用層輸入的語句。而同時我將該文分成了兩部分,第一部分講解i2c匯流排的實現,而第二部分講解在i2c匯流排實現後,我們使用字元裝置驅動來實現對OLED的控制。 Linux
嵌入式Linux——IIC驅動(2):i2c驅動框架分析
簡介: 本文主要介紹i2c匯流排框架,即對i2c的各個層次(i2c匯流排,i2c核心,i2c裝置)進行分析。同時我也會結合程式對框架進行說明。所以本文將分為兩部分,第一部分對i2c的框架進行介紹,而第二部分就是結合程式碼分析。 核心:linux-2.6.2
Linux日常管理技巧(3):Linux網絡相關和防火墻
127.0.0.1 網絡 修改網卡 cal 網卡ip lis oot back col 一、Linux網絡相關 1. ifconfig 查看網卡IP ifconfig命令被用於配置和顯示Linux內核中網絡接口的網絡參數。用ifconfig命令配置的網卡信息,在網卡重啟後機
《Linux學習並不難》Linux網絡命令(3):ping命令測試與目標計算機之間的連通性
Linux ping 測試 27.3 《Linux學習並不難》Linux網絡命令(3):ping命令測試與目標計算機之間的連通性使用ping命令可以用來測試與目標計算機之間的連通性。執行ping命令會使用ICMP傳輸協議發出要求回應的信息,如果遠程主機的網絡功能沒有問題,就會回應該信息,因而得知
多研究些架構,少談些框架( 3 ):事件驅動架構
object 邏輯 查詢 同時 最新 order ring 手機 enc 接上篇,我們采用了領域驅動的開發方式,使用了充血模型,享受了他的好處,但是也不得不面對他帶來的弊端。這個弊端在分布式的微服務架構下面又被放大。 事務一致性 事務一致性的問題在Monolithic下面不
塊裝置驅動(3)——nand flash驅動
/* 參考 * drivers\mtd\nand\s3c2410.c * drivers\mtd\nand\at91_nand.c */ #include <linux/module.h> #include <linux/types.h> #
linux裝置驅動(3)I2C驅動
i2c驅動程式的核心是建立i2c_driver結構體 /* This is the driver that will be inserted */ static struct i2c_driver at24cxx_driver = { .driver = { .name
linux設備驅動(3)I2C驅動
eric without this flags res 創建 sig pri 數據傳輸 i2c驅動程序的核心是創建i2c_driver結構體 /* This is the driver that will be inserted */ static struct i2c_
linux裝置驅動(3)字元驅動 -led
本文基於mini2440 /* * kernel : linux-2.6.22.6 * gcc : arm-linux-gcc -3.4.5 */ #include <linux/module.h> #include <linux/kerne
linux裝置驅動(3)字元驅動 -按鍵(查詢法)
本文描述查詢法。 所謂查詢法,就是在應用程式裡面執行 while (1) { read(fd, key_vals, sizeof(key_vals)); ... } 載入驅動並在後臺執行應用程式時, 通過top可以看到CPU利用率,該應用程序佔用9
嵌入式Linux——網絡卡驅動(1):網絡卡驅動框架介紹
宣告:文字是看完韋東山老師的視訊和看了一些文章後,所寫的總結。我會盡力將自己所瞭解的知識寫出來,但由於自己感覺並沒有學的很好,所以文中可能有錯的地方敬請指出,謝謝。 在介紹本文之前,我想先對前面的知識做一下總結,我們知道Linux系統的裝置分為字元裝置(ch
嵌入式Linux——nand flash 驅動(三):原始碼分析
再次宣告:本文是看過一些文章後寫的,如果與你的文章有相同的地方,敬請告知,如果對你有幫助,是我的榮幸。 接下來的這篇文章我們將要分析一下nand flash在S3C2440中的驅動函式。下面我們以一張圖來引入: 從上圖可以看出,MTD裝置層與原始裝置層
痞子衡嵌入式:超級下載演算法(RT-UFL)開發筆記(3) - 統一FlexSPI驅動訪問
---- 大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家介紹的是**超級下載演算法開發筆記(3)之統一FlexSPI驅動訪問**。 文接上篇 [《超級下載演算法(RT-UFL)開發筆記(2) - 識別當前i.MXRT型號》](https://www.cnblogs.com/henjay
Linux存儲管理(3)
可擴展性 linux 下一代 開發 動態 之前詳細講述了如何進行磁盤管理,對所創建的磁盤創建文件系統,對其進行邏輯上的編址,主要講了ext系列的文件系統,今天在這裏總結一下,關於當前Linux文件系統中發展較為迅速的btrfs文件系統,btrfs開發目的就是取代ext系列文件系統,成為
Linux保護開機密碼(3)
linux 密碼經過二的過程我們知道,其他人在沒有能力進入系統的時候可以很有效的阻止,但是,萬一有人能夠進入系統裏面,並且查看了/etc/grub.cof呢,這該怎麽辦呢?這就涉及到了一個MD5加密,這種加密方式原則上是不可破解的,但是通過暴力破解,一些簡單的密碼很容易就被破解開來,所以,密碼設置的越復雜越有
linux系統編程(3)
linux系統編程一 線程間同步 同步:相互之間配合完成一件事情 互斥:保證訪問共享資源的完整性(有你沒我) POSIX 線程中同步:使用信號量實現 信號量 : 表示一類資源,它的值表示資源的個數 對資源訪問: p操作(申請資源) [將資源的值 - 1] .... V操作(釋放資源) [將資源的值
linux信號解釋(3)--信號處理機制
信號處理 如果需要進程捕獲某個信號,並作出相應的處理,就需要註冊信號處理函數(其實就是內核裏需要識別信號函數,類似C語言裏的include某函數庫)。 處理信號就類似軟中斷,內核為每個進程準備了一段信號向量表,記錄信號的處理機制。當某個信號發生後,內核就會調用註冊的函數去處理。 信號何時來,
《Linux學習並不難》使用RPM軟件包(3):安裝RPM軟件包
Linux 軟件包 rpm 14.3 《Linux學習並不難》使用RPM軟件包(3):安裝RPM軟件包RPM軟件包的安裝流程如圖9.1所示,如果軟件包滿足依賴條件則允許安裝,如果不滿足依賴關系則需要先安裝其它軟件包。使用rpm命令可以在Linux系統中安裝、刪除、刷新、升級、查詢RPM軟件包。命
《Linux學習並不難》用戶管理(3):/etc/shadow文件詳細介紹
Linux 用戶 shadow 9.3 《Linux學習並不難》用戶管理(3):/etc/shadow文件詳細介紹/etc/shadow文件是/etc/passwd的影子文件,這兩個文件應該是對應互補的。/etc/shadow文件的內容包括用戶被加密的密碼以及其它/etc/passwd文件不能包
《Linux學習並不難》文件系統管理(3):在Linux系統中創建文件系統
Linux xfs 文件系統 18.3 《Linux學習並不難》文件系統管理(3):在Linux系統中創建文件系統使用mkfs命令可以在分區上創建各種文件系統。mkfs命令本身並不執行建立文件系統的工作,而是去調用相關的程序來執行。這裏的文件系統是要指定的,比如xfs、ext4、ext3、vfa