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