Linux LED驱动开发实验(直接操作寄存器 -- 实际开发很少这样做)
目錄
- Linux 下LED 燈驅(qū)動(dòng)原理
- 地址映射(ioremap映射、iounmap釋放)
- I/O 內(nèi)存訪問(wèn)函數(shù)
- 硬件原理圖分析
- 實(shí)驗(yàn)程序編寫(xiě)
- LED 燈驅(qū)動(dòng)程序編寫(xiě)
- APP測(cè)試程序編寫(xiě)
- 運(yùn)行測(cè)試
- 編譯驅(qū)動(dòng)程序和測(cè)試APP
- 拷貝led.ko 和ledApp到指定目錄
- 加載led.ko 驅(qū)動(dòng)模塊到內(nèi)核
- 創(chuàng)建應(yīng)用層“/dev/led”設(shè)備節(jié)點(diǎn)
- 運(yùn)行測(cè)試
- 其他:網(wǎng)絡(luò)問(wèn)題解決方法
上一章我們?cè)敿?xì)的講解了字符設(shè)備驅(qū)動(dòng)開(kāi)發(fā)步驟,并且用一個(gè)虛擬的chrdevbase 設(shè)備為例完成了第一個(gè)字符設(shè)備驅(qū)動(dòng)開(kāi)發(fā)。本章我們就開(kāi)始編寫(xiě)第一個(gè)真正的Linux 字符設(shè)備驅(qū)動(dòng)。在I.MX6U-ALPHA 開(kāi)發(fā)板上有一個(gè)LED 燈,我們?cè)诼銠C(jī)篇中已經(jīng)編寫(xiě)過(guò)此LED 燈的裸機(jī)驅(qū)動(dòng)。
Linux 下LED 燈驅(qū)動(dòng)原理
Linux 下的任何外設(shè)驅(qū)動(dòng),最終都是要配置相應(yīng)的硬件寄存器。所以本章的LED 燈驅(qū)動(dòng)最終也是對(duì)I.MX6ULL 的IO 口進(jìn)行配置,與裸機(jī)實(shí)驗(yàn)不同的是,在Linux 下編寫(xiě)驅(qū)動(dòng)要符合Linux的驅(qū)動(dòng)框架。I.MX6U-ALPHA 開(kāi)發(fā)板上的LED 連接到I.MX6ULL 的GPIO1_IO03 這個(gè)引腳上。
地址映射(ioremap映射、iounmap釋放)
在編寫(xiě)驅(qū)動(dòng)之前,我們需要先簡(jiǎn)單了解一下MMU,MMU 全稱(chēng)叫做Memory Manage Unit(內(nèi)存管理單元)。在老版本的Linux 中要求處理器必須有MMU,但是現(xiàn)在新版Linux 內(nèi)核已經(jīng)支持無(wú)MMU 的處理器了(STM32可以跑linux了)。MMU 主要完成的功能如下:
①、完成虛擬空間到物理空間的映射。
②、內(nèi)存保護(hù),設(shè)置存儲(chǔ)器的訪問(wèn)權(quán)限,設(shè)置虛擬存儲(chǔ)空間的緩沖特性。
重點(diǎn)關(guān)注第①點(diǎn),也就是虛擬空間到物理空間的映射。首先了解兩個(gè)地址概念:虛擬地址(VA,Virtual Address)、物理地址(PA,Physcical Address)。對(duì)于32 位的處理器來(lái)說(shuō),虛擬地址范圍是2^32=4GB,我們的開(kāi)發(fā)板上有512MB 的DDR3,這512MB 的內(nèi)存就是物理內(nèi)存,經(jīng)過(guò)MMU 可以將其映射到整個(gè)4GB 的虛擬空間,如圖41.1.1 所示:
物理內(nèi)存只有512MB,虛擬內(nèi)存有4GB,那么肯定存在多個(gè)虛擬地址映射到同一個(gè)物理地址上去,虛擬地址范圍比物理地址范圍大的問(wèn)題處理器自會(huì)處理,這里我們不去深究,MMU是很復(fù)雜的一個(gè)東西。
這里就涉及到了物理內(nèi)存和虛擬內(nèi)存之間的轉(zhuǎn)換,需要用到兩個(gè)函數(shù):ioremap 和iounmap。
1、ioremap 函數(shù)
ioremap 函數(shù)用于獲取指定物理地址空間映射的虛擬地址空間,定義在
arch/arm/include/asm/io.h 文件中,定義如下:
ioremap 是個(gè)宏,有兩個(gè)參數(shù):cookie 和size,真正起作用的是函數(shù)__arm_ioremap,此函數(shù)有三個(gè)參數(shù)和一個(gè)返回值,這些參數(shù)和返回值的含義如下:
phys_addr:要映射給的物理起始地址。
size:要映射的內(nèi)存空間大小。
mtype:ioremap 的類(lèi)型,可以選擇MT_DEVICE、MT_DEVICE_NONSHARED、
MT_DEVICE_CACHED 和MT_DEVICE_WC,ioremap 函數(shù)選擇MT_DEVICE。
返回值:__iomem 類(lèi)型的指針,指向映射后的虛擬空間首地址。
假如我們要獲取I.MX6ULL 的IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器對(duì)應(yīng)的虛擬地址,使用如下代碼即可:
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068) static void __iomem* SW_MUX_GPIO1_IO03; SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);宏SW_MUX_GPIO1_IO03_BASE 是寄存器物理地址,SW_MUX_GPIO1_IO03 是映射后的虛擬地址。對(duì)于I.MX6ULL 來(lái)說(shuō)一個(gè)寄存器是4 字節(jié)(32 位)的,因此映射的內(nèi)存長(zhǎng)度為4。映射完成以后直接對(duì)SW_MUX_GPIO1_IO03 進(jìn)行讀寫(xiě)操作即可。
2、iounmap 函數(shù)
卸載驅(qū)動(dòng)的時(shí)候需要使用iounmap 函數(shù)釋放掉ioremap 函數(shù)所做的映射,iounmap 函數(shù)原型如下:
void iounmap (volatile void __iomem *addr)iounmap 只有一個(gè)參數(shù)addr,此參數(shù)就是要取消映射的虛擬地址空間首地址。假如我們現(xiàn)在要取消掉IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器的地址映射,使用如下代碼即可:
iounmap(SW_MUX_GPIO1_IO03);I/O 內(nèi)存訪問(wèn)函數(shù)
當(dāng)外部寄存器或內(nèi)存映射到虛擬內(nèi)存空間時(shí),稱(chēng)為I/O 內(nèi)存。但是對(duì)于ARM 來(lái)說(shuō)沒(méi)有I/O 空間這個(gè)概念,因此ARM 體系下只有I/O 內(nèi)存(可以直接理解為內(nèi)存)。使用ioremap 函數(shù)將寄存器的物理地址映射到虛擬地址以后,我們就可以直接通過(guò)指針訪問(wèn)這些地址,但是Linux 內(nèi)核不建議這么做,而是推薦使用一組操作函數(shù)來(lái)對(duì)映射后的內(nèi)存進(jìn)行讀寫(xiě)操作。
1、讀操作函數(shù)
讀操作函數(shù)有如下幾個(gè):
readb、readw 和readl 這三個(gè)函數(shù)分別對(duì)應(yīng)8bit、16bit 和32bit 讀操作,參數(shù)addr 就是要讀取寫(xiě)內(nèi)存地址,返回值就是讀取到的數(shù)據(jù)。
2、寫(xiě)操作函數(shù)
寫(xiě)操作函數(shù)有如下幾個(gè):
1 void writeb(u8 value, volatile void __iomem *addr) 2 void writew(u16 value, volatile void __iomem *addr) 3 void writel(u32 value, volatile void __iomem *addr)writeb、writew 和writel 這三個(gè)函數(shù)分別對(duì)應(yīng)8bit、16bit 和32bit 寫(xiě)操作,參數(shù)value 是要寫(xiě)入的數(shù)值,addr 是要寫(xiě)入的地址。
硬件原理圖分析
本章實(shí)驗(yàn)硬件原理圖參考8.3 小節(jié)即可。
實(shí)驗(yàn)程序編寫(xiě)
本實(shí)驗(yàn)對(duì)應(yīng)的例程路徑為:開(kāi)發(fā)板光盤(pán)-> 2、Linux 驅(qū)動(dòng)例程-> 2_led。
本章實(shí)驗(yàn)編寫(xiě)Linux 下的LED 燈驅(qū)動(dòng),可以通過(guò)應(yīng)用程序?qū).MX6U-ALPHA 開(kāi)發(fā)板上的LED 燈進(jìn)行開(kāi)關(guān)操作。
LED 燈驅(qū)動(dòng)程序編寫(xiě)
新建名為“2_led”文件夾,然后在2_led 文件夾里面創(chuàng)建VSCode 工程,工作區(qū)命名為“l(fā)ed”。
工程創(chuàng)建好以后新建led.c 文件,此文件就是led 的驅(qū)動(dòng)文件,在led.c 里面輸入如下內(nèi)容:
第22~26 行,定義了一些宏,包括主設(shè)備號(hào)、設(shè)備名字、LED 開(kāi)/關(guān)宏。
第29~33 行,本實(shí)驗(yàn)要用到的寄存器宏定義。
第36~40 行,經(jīng)過(guò)內(nèi)存映射以后的寄存器地址指針。
第47~59 行,led_switch 函數(shù),用于控制開(kāi)發(fā)板上的LED 燈亮滅,當(dāng)參數(shù)sta 為L(zhǎng)EDON(1)的時(shí)候打開(kāi)LED 燈,sta 為L(zhǎng)EDOFF(0)的時(shí)候關(guān)閉LED 燈。
第68~71 行,led_open 函數(shù),為空函數(shù),可以自行在此函數(shù)中添加相關(guān)內(nèi)容,一般在此函數(shù)中將設(shè)備結(jié)構(gòu)體作為參數(shù)filp 的私有數(shù)據(jù)(filp->private_data)。
第81~84 行,led_read 函數(shù),為空函數(shù),如果想在應(yīng)用程序中讀取LED 的狀態(tài),那么就可以在此函數(shù)中添加相應(yīng)的代碼,比如讀取GPIO1_DR 寄存器的值,然后返回給應(yīng)用程序。
第94~114 行,led_write 函數(shù),實(shí)現(xiàn)對(duì)LED 燈的開(kāi)關(guān)操作,當(dāng)應(yīng)用程序調(diào)用write 函數(shù)向led 設(shè)備寫(xiě)數(shù)據(jù)的時(shí)候此函數(shù)就會(huì)執(zhí)行。首先通過(guò)函數(shù)copy_from_user 獲取應(yīng)用程序發(fā)送過(guò)來(lái)的操作信息(打開(kāi)還是關(guān)閉LED),最后根據(jù)應(yīng)用程序的操作信息來(lái)打開(kāi)或關(guān)閉LED 燈。
第121~124 行,led_release 函數(shù),為空函數(shù),可以自行在此函數(shù)中添加相關(guān)內(nèi)容,一般關(guān)閉設(shè)備的時(shí)候會(huì)釋放掉led_open 函數(shù)中添加的私有數(shù)據(jù)。
第127~133 行,設(shè)備文件操作結(jié)構(gòu)體led_fops 的定義和初始化。
第140~185 行,驅(qū)動(dòng)入口函數(shù)led_init,此函數(shù)實(shí)現(xiàn)了LED 的初始化工作,147~151 行通過(guò)ioremap 函數(shù)獲取物理寄存器地址映射后的虛擬地址,得到寄存器對(duì)應(yīng)的虛擬地址以后就可以完成相關(guān)初始化工作了。比如使能GPIO1 時(shí)鐘、設(shè)置GPIO1_IO03 復(fù)用功能、配置GPIO1_IO03的屬性等等。最后,最重要的一步!使用register_chrdev 函數(shù)注冊(cè)led 這個(gè)字符設(shè)備。
第192~202 行,驅(qū)動(dòng)出口函數(shù)led_exit,首先使用函數(shù)iounmap 取消內(nèi)存映射,最后使用函數(shù)unregister_chrdev 注銷(xiāo)led 這個(gè)字符設(shè)備。
第205~206 行,使用module_init 和module_exit 這兩個(gè)函數(shù)指定led 設(shè)備驅(qū)動(dòng)加載和卸載函數(shù)。
第207~208 行,添加LICENSE 和作者信息。
APP測(cè)試程序編寫(xiě)
編寫(xiě)測(cè)試APP,led 驅(qū)動(dòng)加載成功以后手動(dòng)創(chuàng)建/dev/led 節(jié)點(diǎn),應(yīng)用APP 通過(guò)操作/dev/led文件來(lái)完成對(duì)LED 設(shè)備的控制。向/dev/led 文件寫(xiě)0 表示關(guān)閉LED 燈,寫(xiě)1 表示打開(kāi)LED 燈。
新建ledApp.c 文件,在里面輸入如下內(nèi)容:
ledApp.c 的內(nèi)容還是很簡(jiǎn)單的,就是對(duì)led 的驅(qū)動(dòng)文件進(jìn)行最基本的打開(kāi)、關(guān)閉、寫(xiě)操作等。
運(yùn)行測(cè)試
編譯驅(qū)動(dòng)程序和測(cè)試APP
1、編譯驅(qū)動(dòng)程序
編寫(xiě)Makefile 文件,本章實(shí)驗(yàn)的Makefile 文件和第四十章實(shí)驗(yàn)基本一樣,只是將obj-m 變量的值改為led.o,Makefile 內(nèi)容如下所示:
第4 行,設(shè)置obj-m 變量的值為led.o。
輸入如下命令編譯出驅(qū)動(dòng)模塊文件:
編譯成功以后就會(huì)生成一個(gè)名為“l(fā)ed.ko”的驅(qū)動(dòng)模塊文件。
2、編譯測(cè)試APP輸入如下命令編譯測(cè)試ledApp.c 這個(gè)測(cè)試程序:
arm-linux-gnueabihf-gcc ledApp.c -o ledApp編譯成功以后就會(huì)生成ledApp 這個(gè)應(yīng)用程序。
注意!如果大家使用的正點(diǎn)原子出廠系統(tǒng)來(lái)做本實(shí)驗(yàn),那么會(huì)發(fā)現(xiàn)LED 燈會(huì)一直閃爍。這是因?yàn)檎c(diǎn)原子出廠系統(tǒng)默認(rèn)將LED 燈作為了心跳燈,因此系統(tǒng)啟動(dòng)以后LED 燈就會(huì)自動(dòng)閃爍,這樣會(huì)影響大家做實(shí)驗(yàn)。如果是完全按照本教程自行移植的內(nèi)核和根文件系統(tǒng),那么就不會(huì)遇到此問(wèn)題。如果直接使用出廠系統(tǒng)來(lái)做實(shí)驗(yàn),我們需要關(guān)閉LED 燈的心跳功能,關(guān)閉方法參考《【正點(diǎn)原子】I.MX6U 用戶(hù)快速體驗(yàn)》第3.1 小節(jié),或者輸入如下命令即可:
echo none > /sys/class/leds/sys-led/trigger // 改變LED 的觸發(fā)模式拷貝led.ko 和ledApp到指定目錄
將上一小節(jié)編譯出來(lái)的led.ko 和ledApp 這兩個(gè)文件拷貝到rootfs/lib/modules/4.1.15 目錄中【視頻里使用nfs文件夾網(wǎng)絡(luò)掛載】
加載led.ko 驅(qū)動(dòng)模塊到內(nèi)核
重啟開(kāi)發(fā)板,進(jìn)入到目錄lib/modules/4.1.15 中,輸入如下命令加載led.ko 驅(qū)動(dòng)模塊:
depmod //第一次加載驅(qū)動(dòng)的時(shí)候需要運(yùn)行此命令 modprobe led.ko //加載驅(qū)動(dòng)創(chuàng)建應(yīng)用層“/dev/led”設(shè)備節(jié)點(diǎn)
驅(qū)動(dòng)加載成功以后創(chuàng)建“/dev/led”設(shè)備節(jié)點(diǎn),命令如下:
mknod /dev/led c 200 0運(yùn)行測(cè)試
驅(qū)動(dòng)節(jié)點(diǎn)創(chuàng)建成功以后就可以使用ledApp 軟件來(lái)測(cè)試驅(qū)動(dòng)是否工作正常,輸入如下命令打開(kāi)LED 燈:
./ledApp /dev/led 1 //打開(kāi)LED 燈輸入上述命令以后觀察I.MX6U-ALPHA 開(kāi)發(fā)板上的紅色LED 燈是否點(diǎn)亮,如果點(diǎn)亮的話說(shuō)明驅(qū)動(dòng)工作正常。在輸入如下命令關(guān)閉LED 燈:
./ledApp /dev/led 0 //關(guān)閉LED 燈輸入上述命令以后觀察I.MX6U-ALPHA 開(kāi)發(fā)板上的紅色LED 燈是否熄滅,如果熄滅的話說(shuō)明我們編寫(xiě)的LED 驅(qū)動(dòng)工作完全正常!至此,我們成功編寫(xiě)了第一個(gè)真正的Linux 驅(qū)動(dòng)設(shè)備程序。
如果要卸載驅(qū)動(dòng)的話輸入如下命令即可:
rmmod led.ko其他:網(wǎng)絡(luò)問(wèn)題解決方法
總結(jié)
以上是生活随笔為你收集整理的Linux LED驱动开发实验(直接操作寄存器 -- 实际开发很少这样做)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Linux 命令快捷键
- 下一篇: 嵌入式linux学习笔记(2)