【Linux笔记】LED驱动程序
前言
上一篇我們分享了字符設(shè)備驅(qū)動框架:【Linux筆記】驅(qū)動基礎(chǔ)篇,當(dāng)時(shí)分享的是hello驅(qū)動程序。
學(xué)STM32我們從點(diǎn)燈開始,學(xué)Linux驅(qū)動我們自然也要點(diǎn)個(gè)燈來玩玩,盡量在從這些基礎(chǔ)例程中榨取知識,細(xì)摳、細(xì)摳,為之后更復(fù)雜的知識打好基礎(chǔ)。
與硬件無關(guān)的LED驅(qū)動
回顧hello驅(qū)動程序,我們的根據(jù)實(shí)際需求對其進(jìn)行寫字符串與讀字符串操作。這里我們當(dāng)然也要根據(jù)實(shí)際來思考我們的LED驅(qū)動程序。
在STM32點(diǎn)燈的時(shí)候,一般輸出低電平點(diǎn)燈,輸出高電平滅燈。在嵌入Linux操作系統(tǒng)的情況下,我們自然也要想到有個(gè)寫1/0的思想。
類比我們上一篇的hello程序:
我們的LED程序自然要寫入的數(shù)據(jù)為0/1來點(diǎn)亮、熄滅LED。
這里我們做的實(shí)驗(yàn)室與硬件無關(guān)的LED實(shí)驗(yàn):我們的驅(qū)動程序在收到應(yīng)用程序發(fā)送過來的0時(shí)打印led on、收到1時(shí)打印led off。
模仿上一篇的hello程序,我們修改得到的與硬件無關(guān)的LED程序(核心部分)如下:
LED應(yīng)用程序:
LED驅(qū)動程序:
加載led驅(qū)動模塊及運(yùn)行應(yīng)用程序:
與硬件有關(guān)的LED驅(qū)動
上面那一節(jié)分享的是與硬件無關(guān)的LED驅(qū)動實(shí)驗(yàn),主要是為了理清LED驅(qū)動的大體思路。這里我們再加入與硬件有關(guān)的相關(guān)操作以構(gòu)造與硬件有關(guān)的LED驅(qū)動程序。
我們在進(jìn)行STM32的裸機(jī)編程的時(shí)候,對一些外設(shè)進(jìn)行配置其實(shí)就是操作一些地址的過程,這些外設(shè)地址在芯片手冊中可以看到:
這是地址映射圖,這里圖中只是列出的外設(shè)的邊界地址,每個(gè)外設(shè)又有很多寄存器,這些寄存器的地址都是對外設(shè)基地址進(jìn)行偏移得到的。
同樣的,對于NXP的IMX6ULL芯片來說,也是有類似這樣的地址的:
此時(shí)我們要編寫Linux系統(tǒng)下的led驅(qū)動,涉及到硬件操作的地方操作的并不是這些地址(物理地址),而是操作系統(tǒng)給我們提供的地址(虛擬地址)。
操作系統(tǒng)根據(jù)物理地址來給我們生成一個(gè)虛擬地址,我們的led驅(qū)動操控這個(gè)地址就是間接的操控物理地址。
至于這兩個(gè)地址是怎么聯(lián)系起來的,里面?zhèn)€原理我們暫且不展開。我們從函數(shù)層面來看,內(nèi)核給我們提供了ioremap 函數(shù),這個(gè)函數(shù)可以把物理地址映射為虛擬地址。
這個(gè)函數(shù)在內(nèi)核文件arch/arm/include/asm/io.h ?中:
左右滑動查看全部代碼>>>
void __iomem *ioremap(resource_size_t res_cookie, size_t size);res_cookie:要映射給的物理起始地址 。
size:要映射的內(nèi)存空間大小。
返回值:指向映射后的虛擬空間首地址。
與ioremap函數(shù)相對應(yīng)的函數(shù)為:
void iounmap (volatile void __iomem *addr)addr:要取消映射的虛擬地址空間首地址。
地址映射完成之后,我們可以直接通過指針來訪問虛擬地址,如:
*GPIO5_DR &= ~(1 << 3); /* GPIO5_IO03輸出低電平 */ *GPIO5_DR |= (1 << 3); /* GPIO5_IO03輸出高電平 */這里簡單介紹一下i.MX 6ULL的GPIO。對于i.MX 6ULL來說,以數(shù)字來給IO端口(組別)命令,GPIO5為第五組,所以GPIO5_IO03為第五組端口的第3個(gè)引腳。
而STM32中是以大寫字母來表示端口(組別),如PA3表示A端口的第3個(gè)引腳。
i.MX 6ULL有 5 組 GPIO(GPIO1~ GPIO5),每組引腳最多有 32 個(gè):
GPIO1 有 32 個(gè)引腳:GPIO1_IO0~GPIO1_IO31; GPIO2 有 22 個(gè)引腳:GPIO2_IO0~GPIO2_IO21; GPIO3 有 29 個(gè)引腳:GPIO3_IO0~GPIO3_IO28; GPIO4 有 29 個(gè)引腳:GPIO4_IO0~GPIO4_IO28; GPIO5 有 12 個(gè)引腳:GPIO5_IO0~GPIO5_IO11;地址映射完成之后,我們不僅可以通過指針來訪問虛擬地址,而且還可以使用內(nèi)核給我們提供的一些讀寫函數(shù):
/* 寫操作函數(shù) */ void writeb(u8 value, volatile void __iomem *addr); void writew(u16 value, volatile void __iomem *addr); void writel(u32 value, volatile void __iomem *addr); /* 讀操作函數(shù) */ u8 readb(const volatile void __iomem *addr); u16 readw(const volatile void __iomem *addr); u32 readl(const volatile void __iomem *addr);writeb、 writew 和 writel 這三個(gè)函數(shù)分別對應(yīng) 8bit、 16bit 和 32bit 寫操作,參數(shù) value 是要寫入的數(shù)值, addr 是要寫入的地址。
readb、 readw 和 readl 這三個(gè)函數(shù)分別對應(yīng) 8bit、 16bit 和 32bit 讀操作,參數(shù) addr 就是要讀取寫內(nèi)存地址,返回值就是讀取到的數(shù)據(jù)。
此時(shí)我們可以把上一節(jié)的led_init函數(shù)led_drv_write函數(shù)進(jìn)行修改:
與STM32一樣,對于i.MX 6ULL的GPIO外設(shè)來說,也有很多寄存器:
上面我們只是點(diǎn)一個(gè)燈,如果是要點(diǎn)多個(gè)燈呢?那就得操控多個(gè)GPIO。如果進(jìn)行地址映射的寫法還像上面那樣,代碼就會顯得很臃腫。
回想一下我們STM32,GPIO外設(shè)通過結(jié)構(gòu)體來管理它的寄存器:
這里的__IO是個(gè)宏,代表的是C語言的關(guān)鍵字volatile ,為了防止編譯器對我們的一些硬件操作進(jìn)行優(yōu)化,從而得不到想要的結(jié)果。比如:
/* 假設(shè)REG為寄存器的地址 */ uint32 *REG; *REG = 0;/* 點(diǎn)燈 */ *REG = 1;/* 滅燈 */此時(shí)若是REG不加volatile進(jìn)行修飾,則點(diǎn)燈操作將被優(yōu)化掉,只執(zhí)行滅燈操作。關(guān)于volatile關(guān)鍵字更多的解釋可以查看往期筆記:《來看一看volatile關(guān)鍵字》
在這里,我們也可以模仿STM32那樣子,用一個(gè)結(jié)構(gòu)體來對i.MX 6ULL的GPIO的寄存器進(jìn)行管理,如:
struct GPIO_RegDef {volatile unsigned int DR;volatile unsigned int GDIR;volatile unsigned int PSR;volatile unsigned int ICR1;volatile unsigned int ICR2;volatile unsigned int IMR;volatile unsigned int ISR;volatile unsigned int EDGE_SEL; };結(jié)構(gòu)體里的成員排序是要按照特定順序來的:
因?yàn)檫@些寄存器都是相對于GPIO外設(shè)的基地址作偏移得到的,比如:
不能打亂順序,否則就不能正確訪問到對應(yīng)的寄存器了。用結(jié)構(gòu)體進(jìn)行管理之后,我們就可以用類似下面的方式進(jìn)行映射:
struct GPIO_RegDef *GPIO5 = ioremap(0x20AC000, sizeof(struct GPIO_RegDef));然后就可以向STM32那樣來操控GPIO寄存器,如:
GPIO5->DR &= ~(1 << 3); /* GPIO5_IO03輸出低電平 */ GPIO5->DR |= (1 << 3); /* GPIO5_IO03輸出高電平 */LED驅(qū)動(升級版)
上一節(jié)我們分享的LED驅(qū)動是一個(gè)常規(guī)的LED驅(qū)動,只能適用于我們當(dāng)前的開發(fā)版,所以是一個(gè)專用的LED驅(qū)動程序。
若是換了另一塊板,led所連接的gpio引腳可能不一樣了,我們就修改我們的驅(qū)動程序led_drv.c里與寄存器相關(guān)的操作。
有沒有更好的辦法不用再修改我們的led_drv.c驅(qū)動程序了?
若是led_drv.c不用再修改了,那么這個(gè)led_drv.c驅(qū)動就是一個(gè)通用的驅(qū)動程序了。具體可查看韋東山老師的《嵌入式Linux應(yīng)用開發(fā)完全手冊第2版》第五篇第3~7節(jié)進(jìn)行學(xué)習(xí)。
下面來簡單地梳理一下:
由于篇幅問題,具體的部分就不貼出來了。
之前的筆記中:《C語言、嵌入式重點(diǎn)知識:回調(diào)函數(shù)》中我們也有提到通用與專用的含義,可以了解了解加深對這兩個(gè)詞的認(rèn)識。
這里我們學(xué)到了很重要的思想軟件分層的思想及技巧,但也只是點(diǎn)了一下,未來的路還很長,需要持續(xù)學(xué)習(xí),繼續(xù)提高。
最后
以上就是本次的分享,如有錯(cuò)誤,歡迎指出!謝謝
原創(chuàng)不易,如果覺得文章不錯(cuò),轉(zhuǎn)發(fā)、在看,也是我們繼續(xù)更新得動力。
參考/學(xué)習(xí)資料:
百問網(wǎng)《嵌入式Linux應(yīng)用開發(fā)完全手冊第2版》
正點(diǎn)原子《I.MX6U嵌入式Linux驅(qū)動開發(fā)指南V1.2》
野火《i.MX Linux開發(fā)實(shí)戰(zhàn)指南》
? 回復(fù)「?籃球的大肚子」進(jìn)入技術(shù)群聊
回復(fù)「1024」獲取1000G學(xué)習(xí)資料
總結(jié)
以上是生活随笔為你收集整理的【Linux笔记】LED驱动程序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: PHP常用函数性能对比
- 下一篇: Linux内存管理(最透彻的一篇)