嵌入式Linux中的LED驅動控制(續)

fxzq發表於2024-06-11

前面的例項實現了在野火STM32MP157開發板上對三個LED燈的控制,這裡來討論一下該驅動程式的具體實現方式。由於例項使用的是STM32MP157這款晶片,所以先來看一下與該晶片埠操作相關的暫存器。

先看埠模式暫存器MODER,該型別的暫存器在STM32MP157中有11個,即x的值從A到K。它們分別針對11組不同的I/O埠,其結構完全相同(下同)。下面是該暫存器的全部位結構。它的偏移地址為:0x00。

第31~0位分別配置第15~第0引腳的具體模式,每兩位配置一個引腳。當值為00時,引腳為輸入模式,值為01時,引腳為通用輸出模式,值為10時,引腳為多功能模式,值為11時,引腳為模擬模式。預設為模擬模式。

接著看埠輸出型別暫存器OTYPER,下面是該暫存器的全部位結構。它的偏移地址為:0x04。

第15~0位分別配置第15~第0引腳的輸出型別,每一位配置一個引腳,第31~16位未使用。當值為0時,引腳為推輓模式,值為1時,引腳為開漏模式。預設為推輓模式。

接下來是埠輸出速度暫存器OSPEEDR,下面是該暫存器的全部位結構。它的偏移地址為:0x08。

第31~0位分別配置第15~第0引腳的具體模式,每兩位配置一個引腳。當值為00時,引腳為低速模式,值為01時,引腳為中速模式,值為10時,引腳為高速模式,值為11時,引腳為超高速模式。預設為低速模式。

然後看埠上/下拉暫存器PUPDR,下面是該暫存器的全部位結構。它的偏移地址為:0x0C。

第31~0位分別配置第15~第0引腳的具體模式,每兩位配置一個引腳。當值為00時,引腳為無上下拉(浮空)模式,值為01時,引腳為上拉模式,值為10時,引腳為下拉模式,值為11時保留。預設為浮空模式。

接著看埠輸入資料暫存器IDR,下面是該暫存器的全部位結構。它的偏移地址為:0x10。

第15~0位分別提供第15~第0引腳的輸入資料,每一位對應一個引腳,第31~16位未使用。當值為0時,引腳輸入為低電平,值為1時,引腳輸入為高電平。該暫存器為只讀。

然後是埠輸出資料暫存器ODR,下面是該暫存器的全部位結構。它的偏移地址為:0x14。

第15~0位分別提供第15~第0引腳的輸出資料,每一位對應一個引腳,第31~16位未使用。當值為0時,引腳輸出低電平,值為1時,引腳輸出高電平。預設為低電平。

最後看埠位配置暫存器BSRR,下面是該暫存器的全部位結構。它的偏移地址為:0x18。

第31~16位分別提供第15~第0引腳的清零,每一位對應一個引腳,寫1時引腳清零(輸出低電平),寫0無效。第15~0位分別提供第15~第0引腳的置位,每一位對應一個引腳,寫1時引腳置位(輸出高電平),寫0無效。預設為無效。

在操作埠的輸出電平時,可以有ODR和BSRR兩個暫存器選擇,雖然兩者都能實現埠電平的輸出,但還是有所區別的。當要求操作某一引腳的電平而不影響其他引腳的電平時,若使用ODR暫存器則要實現“讀——改——寫”的過程,即先把ODR資料讀出,然後透過邏輯與或進行修改,再寫入到ODR暫存器中。若使用BSRR暫存器則可直接寫入,非常方便。

除了上述對埠控制的暫存器之外,還有一個時鐘使能暫存器也需要討論(只有一個),它暫存器名稱為RCC_MP_AHB4ENSETR,下面是該暫存器的全部位結構。它的偏移地址為:0xA28。

第0~10位分別控制第A~第K組埠的時鐘,值為1時使能該組埠時鐘,值為0時禁止時鐘。預設為禁止。

以上就是本例中使用到的暫存器,他們都位於AHB4匯流排之中,這些暫存器的地址都是以AHB4的基址為偏移的,具體地址如下表所示。

在Linux系統中,並不能直接使用實體地址對暫存器進行操作,必須先把暫存器的實體地址對映到作業系統中來形成虛擬地址,然後才能進行相應地操作。實現對映功能的函式名為ioremap,其原型為:void __iomem *ioremap(phys_addr_t paddr, unsigned long size)。第一個引數為實體地址,第二個引數為長度,返回值是一個指向__iomem型別的指標。__iomem是一個宏,它表示返回的地址是一個IO儲存空間的有效地址。相應的解除對映函式iounmap,其原型為:void iounmap(void *addr)。只有一個引數,為前面對映時的返回指標,該函式沒有返回值。

下面以對映GPIO_MODER_PA暫存器為例進行說明,先對GPIO_MODER_PA暫存器的實體地址進行一個宏定義,定義採取基址+偏移量的方式,如下。

#define AHB4_PERIPH_BASE (0x50000000)
#define GPIOA_BASE (AHB4_PERIPH_BASE + 0x2000)
#define GPIOA_MODER (GPIOA_BASE + 0x0000)

然後定義一個用於接收對映的返回值的指標變數,如下。

volatile void __iomem *GPIO_MODER_PA

變數加上字首volatile關鍵字,表示它不接受編譯器最佳化(一般定義暫存器都要如此)。

然後就可以進行地址對映了,如下。

GPIO_MODER_PA = ioremap(GPIOA_MODER, 4);

對映成功後,就可以使用GPIO_MODER_PA來進行賦值了,如下。

tmp = ioread32(GPIO_MODER_PA);
tmp &= ~(0x3 << 26);
tmp |= (0x1 << 26);
iowrite32(tmp, GPIO_MODER_PA);

上面的賦值採用了“讀——改——寫”的方式,所以只改變暫存器的第26、27兩位,其餘位不變。經過賦值後,GPIOA的第13引腳就被配置成了通用輸出模式。

在Linux中,對埠的讀寫有專用的函式,一般不推薦直接對指標進行操作。ioread32函式用於讀取一個32位的值,iowrite32函式用於寫入一個32位的值。(早期可能會使用readl和writel函式,現在不推薦使用)

本例把暫存器的地址對映放在了入口函式(led_init)中,同時也把埠配置放在了入口中。把解除對映函式放在出口函式(led_exit)中進行。對晶片暫存器的讀、寫等操作放在了檔案操作介面裡面(file_operations結構體成員函式中)。在使用裝置時,應用程式會開啟裝置節點,並透過裝置節點的inode結構體、file結構體最終找到file_operations結構體,然後從file_operations結構體中得到操作裝置的具體方法。 這部分的具體內容可參見“嵌入式Linux中字元型驅動程式的基本框架”一文。在函式alloc_chrdev_region(&devno, 0, 3, "led")執行後,字串led會出現在/proc/devices檔案中。函式class_create(THIS_MODULE, "led_dev")執行後,字串led_dev會出現在/sys/class目錄下。函式device_create(led_chrdev_class, Null, devid, Null, "led%d", i)執行後,字串ledi會出現在/dev目錄下。可在開發板上自行檢視。

相關文章