下麵的系列文章記錄瞭如何使用一塊linux開發扳和一塊OLED屏幕實現視頻的播放: 項目介紹 為OLED屏幕開發I2C驅動 使用cuda編程加速視頻處理 這是此系列文章的第2篇, 主要總結和記錄一個I2C從設備的驅動, 在linux內核中如何實現, 如何給用戶態的程式暴露合適的介面, 讓用戶態有機會 ...
下麵的系列文章記錄瞭如何使用一塊linux開發扳和一塊OLED屏幕實現視頻的播放:
這是此系列文章的第2篇, 主要總結和記錄一個I2C從設備的驅動, 在linux內核中如何實現, 如何給用戶態的程式暴露合適的介面, 讓用戶態有機會操作真實的硬體設備. 可以通過下麵的視頻快速瞭解最終達到的效果和實現的總體思路.
跳轉到6:48, 直接觀看演示
1). I2C驅動架構
I2C匯流排是一種主從, 同步, 半雙工的低速通信匯流排, 硬體標準可以參考這裡. 這篇文章只討論I2C匯流排上從設備的驅動在linux平臺下如何實現, 下圖是linux中I2C匯流排相關的軟體模塊, 其中i2c core提供給驅動開發人員重要的數據結構和介面函數:
- i2c_adapter: 表示匯流排上的主設備, 或者說匯流排控制器
- i2c_algorithm: 當主設備想要通信時, 它負責具體硬體時序的實現, 比如, 在匯流排上產生開始/結束條件, 發送/接收數據
- i2c_client: 表示匯流排上的從設備
- i2c_driver: 表示從設備對應的驅動, 需要實現其中的介面函數之後, 把驅動註冊到i2c core之中
- i2c_add_driver: 註冊i2c_driver到i2c core, 一般在模塊初始化函數中調用
- i2c_del_driver: 刪除i2c_driver, 一般在模塊退出函數中調用
- i2c_master_send/recv: 主設備發送/接收數據, 實際上為了驅動從設備, 需要讓主設備向從設備發送合適的命令, 或者讀取從設備的狀態, 具體發送或者接收什麼, 參考從設備的datasheet即可
2). 實現ssd1306屏幕的I2C驅動
- 註冊i2c_driver
使用module_i2c_driver巨集, 並傳遞我們實現的i2c_driver, 該巨集能夠為我們生成模塊的init和exit函數, 在函數中自動註冊和刪除傳遞進來的i2c_driver. 如果需要在init和exit中做一些其他工作, 則需要自己實現, 不能使用這個巨集.
module_i2c_driver(ssd130x_driver);
- 實現i2c_driver中的介面
static struct i2c_driver ssd130x_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "ssd130x_driver",
},
.probe = ssd130x_probe,
.remove = ssd130x_remove,
.id_table = ssd130x_id_table,
};
這裡只實現了i2c_driver中的probe和remove. 當驅動和設備匹配成功時, probe函數被調用, 在probe函數中, 完成了字元設備的相關的操作, 包括:
- 分配設備號
- 初始化字元設備結構體
- 添加字元設備到內核
- 創建設備文件
- 實現字元設備介面, 暴露給用戶態程式
static struct file_operations ssd130x_fops = {
.owner = THIS_MODULE,
.open = ssd130x_open,
.release = ssd130x_close,
.write = ssd130x_write,
};
用戶態程式可以對設備文件進行打開, 關閉, 寫入3種操作. 當打開設備文件時, ssd130x_open被調用, 完成OLED屏幕的初始化; 關閉設備文件時, ssd130x_close被調用, 屏幕被關閉; 當向設備文件寫入數據時, ssd130x_write被調用, 一幀數據被髮送到ssd1306的RAM上, 屏幕顯示的內容被更新. 以上3種操作, 底層都是通過i2c_master_send向從設備發送特定的命令或者數據實現的.
2.1). 閱讀數據手冊
ssd1306的數據手冊參考這裡, 手冊內容較多, 不宜通讀, 主要關註以下幾點:
- 基本硬體參數: 屏幕解析度, 支持的通信介面, 支持哪些顯示相關的功能(比如滾動, 反轉等) ...
- 基本工作原理: 通過向RAM中寫入數據, 控制屏幕像素點的亮滅
- 基本使用方法: 支持哪些命令? 分別能控制它的什麼功能?
- Application Note: 典型硬體電路, 示例代碼
2.2). 設備的初始化
在數據手冊的Application Note中包含使用ssd1306時的初始化流程, 如下圖所示. 在此基礎上, 可以做一些調整, 比如我在驅動中關閉了屏幕滾動.
2.3). 調整I2C的頻率
我在beaglebone black板子上刷入的debian系統, 其設備樹中的i2c時鐘頻率是100kbits/s, 內核中的i2c_algorithm會根據這個頻率計算在i2c匯流排上發送數據時使用的延時. 實際測試之後發現按照這個頻率播放視頻存在一些卡頓, 因此需要對i2c時鐘頻率做修改, 有兩種方式:
- 在uboot啟動時, 進入uboot的shell, 使用fdt相關的命令修改始終頻率
- 備份原來的設備樹文件, 使用dtc編譯器從dtb得到dts, 在dts中修改始終頻率, 再編譯得到新的dtb, 替換原來的設備樹文件
我這裡採用的是dtc的方式, 這樣就不需要每次系統啟動都手動修改了, 修改之後的時鐘頻率為400kbits/s, 播放視頻流暢很多.
3). 測試驅動功能
驅動代碼編寫完成之後, 需要實際測試一下功能, 下麵代碼首先打開OLED屏幕的設備文件, 寫入一幀數據, 每個位元組都填充為0x88, 這樣屏幕上會顯示出預期的條紋, sleep兩秒之後, 關閉設備文件, 屏幕熄滅.
#define FRAME_SIZE (128 * 8)
int main(int argc, char **argv)
{
int device_fd = open("/dev/ssd130x0", O_WRONLY);
if (device_fd < 0) {
return -1;
}
char *frame = malloc(FRAME_SIZE);
memset(frame, 0x88, FRAME_SIZE);
write(device_fd, frame, FRAME_SIZE);
sleep(2);
free(frame);
close(device_fd);
return 0;
}
4). 文末推廣
歡迎關註我的B站賬號, 或者加入QQ群838923389, 一起研究電腦底層技術, 一起搞事情:P
其實還有很多實現的細節沒有在博客中寫出來, 只有自己在做的時候遇到了才能夠體會的到, 需要完整代碼的老鐵直接在qq群中問一下.