zynq linux内核驱动编写,【原创】Linux下驱动Zynq GPIO (Switch、button、led)
版權聲明:
本文由電子技術應用博主“cuter”發布。歡迎轉載,但不得擅自更改博文內容,也不得用于任何盈利目的。轉載時不得刪除作者簡介和作者單位簡介。如有盜用而不說明出處引起的版權糾紛,由盜用者自負。
博客官方地址:
1硬件設計:
1.1) Vivado Block Design
1.1.1)設計自主AXI-Lite IP核
關鍵邏輯如下:
下圖所示的是寄存器讀操作,主要目的是為了讀取led、switch和button的狀態。這里并沒有做任何處理,對于button而言,可以利用硬件進行預處理之后,將結果傳遞給軟件,這樣可以減少軟件工作量。
下圖所示的邏輯用于將寄存器內的值送至led引腳。
1.1.2)添加IP核至block design
完成后的block design如下圖所示。
1.1.3)編寫約束文件
led、switch和button所用的引腳都需要約束。使用Run Automation的時候,Vivado會幫助完成約束,我們自主IP暫時未實現Run Automation功能,所以要手動約束。約束代碼如下:
#NET LD0 ? ? ? ? ? LOC = T22 ?| IOSTANDARD=LVCMOS33; ?# "LD0"
set_property PACKAGE_PIN T22 [get_ports {zed_led[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[0]}]
#NET LD1 ? ? ? ? ? LOC = T21 ?| IOSTANDARD=LVCMOS33; ?# "LD1"
set_property PACKAGE_PIN T21 [get_ports {zed_led[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[1]}]
#NET LD2 ? ? ? ? ? LOC = U22 ?| IOSTANDARD=LVCMOS33; ?# "LD2"
set_property PACKAGE_PIN U22 [get_ports {zed_led[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[2]}]
#NET LD3 ? ? ? ? ? LOC = U21 ?| IOSTANDARD=LVCMOS33; ?# "LD3"
set_property PACKAGE_PIN U21 [get_ports {zed_led[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[3]}]
#NET LD4 ? ? ? ? ? LOC = V22 ?| IOSTANDARD=LVCMOS33; ?# "LD4"
set_property PACKAGE_PIN V22 [get_ports {zed_led[4]}]
set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[4]}]
#NET LD5 ? ? ? ? ? LOC = W22 ?| IOSTANDARD=LVCMOS33; ?# "LD5"
set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[5]}]
set_property PACKAGE_PIN W22 [get_ports {zed_led[5]}]
#NET LD6 ? ? ? ? ? LOC = U19 ?| IOSTANDARD=LVCMOS33; ?# "LD6"
set_property PACKAGE_PIN U19 [get_ports {zed_led[6]}]
set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[6]}]
#NET LD7 ? ? ? ? ? LOC = U14 ?| IOSTANDARD=LVCMOS33; ?# "LD7"
set_property IOSTANDARD LVCMOS33 [get_ports {zed_led[7]}]
set_property PACKAGE_PIN U14 [get_ports {zed_led[7]}]
#NET SW0 ? ? ? ? ? LOC = F22 ?| IOSTANDARD=LVCMOS18; ?# "SW0"
set_property PACKAGE_PIN F22 [get_ports {zed_sw[0]}]
set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[0]}]
#NET SW1 ? ? ? ? ? LOC = G22 ?| IOSTANDARD=LVCMOS18; ?# "SW1"
set_property PACKAGE_PIN G22 [get_ports {zed_sw[1]}]
set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[1]}]
#NET SW2 ? ? ? ? ? LOC = H22 ?| IOSTANDARD=LVCMOS18; ?# "SW2"
set_property PACKAGE_PIN H22 [get_ports {zed_sw[2]}]
set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[2]}]
#NET SW3 ? ? ? ? ? LOC = F21 ?| IOSTANDARD=LVCMOS18; ?# "SW3"
set_property PACKAGE_PIN F21 [get_ports {zed_sw[3]}]
set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[3]}]
#NET SW4 ? ? ? ? ? LOC = H19 ?| IOSTANDARD=LVCMOS18; ?# "SW4"
set_property PACKAGE_PIN H19 [get_ports {zed_sw[4]}]
set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[4]}]
#NET SW5 ? ? ? ? ? LOC = H18 ?| IOSTANDARD=LVCMOS18; ?# "SW5"
set_property PACKAGE_PIN H18 [get_ports {zed_sw[5]}]
set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[5]}]
#NET SW6 ? ? ? ? ? LOC = H17 ?| IOSTANDARD=LVCMOS18; ?# "SW6"
set_property PACKAGE_PIN H17 [get_ports {zed_sw[6]}]
set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[6]}]
#NET SW7 ? ? ? ? ? LOC = M15 ?| IOSTANDARD=LVCMOS18; ?# "SW7"
set_property PACKAGE_PIN M15 [get_ports {zed_sw[7]}]
set_property IOSTANDARD LVCMOS18 [get_ports {zed_sw[7]}]
#NET BTNC ? ? ? ? ?LOC = P16 ?| IOSTANDARD=LVCMOS18; ?# "BTNC"
set_property IOSTANDARD LVCMOS18 [get_ports {zed_btn[0]}]
set_property PACKAGE_PIN P16 [get_ports {zed_btn[0]}]
# BTNU
set_property IOSTANDARD LVCMOS18 [get_ports {zed_btn[1]}]
set_property PACKAGE_PIN T18 [get_ports {zed_btn[1]}]
# BTND
set_property IOSTANDARD LVCMOS18 [get_ports {zed_btn[2]}]
set_property PACKAGE_PIN R16 [get_ports {zed_btn[2]}]
# BTNL
set_property IOSTANDARD LVCMOS18 [get_ports {zed_btn[3]}]
set_property PACKAGE_PIN N15 [get_ports {zed_btn[3]}]
# BTNR
set_property IOSTANDARD LVCMOS18 [get_ports {zed_btn[4]}]
set_property PACKAGE_PIN R18 [get_ports {zed_btn[4]}]
1.1.4)生成bitstream
1.2)制作BOOT.bin
1.3)修改dts文件
小改動,為簡單起見,保持和驅動程序中的設備名稱一致。IP的物理地址一定要改!
2驅動設計:
2.1) digilent驅動學習
首先,姑且不管每個函數的具體作用,我們將驅動程序的框架剝離出來進行分析,這樣程序結構更加清晰。
2.1.1) platform_driver成員函數
//設備的驅動:platform_driver這個結構體中包含probe()、remove()、shutdown()、suspend()、resume()函數,通常也需要由驅動實現。
struct platform_driver?{
int?(*probe)(struct platform_device?*);
int?(*remove)(struct platform_device?*);
void?(*shutdown)(struct platform_device?*);
int?(*suspend)(struct platform_device?*,?pm_message_t state);
int?(*suspend_late)(struct platform_device?*,?pm_message_t state);
int?(*resume_early)(struct platform_device?*);
int?(*resume)(struct platform_device?*);
struct pm_ext_ops?*pm;
struct device_driver driver;
};
驅動程序對platform_driver進行初始化的代碼:
/* platform driver structure for mygpio driver */
static struct platform_driver mygpio_driver =
{
.driver =
{
.name = DRIVER_NAME,
.owner = THIS_MODULE,
.of_match_table = mygpio_of_match
},
.probe = mygpio_probe,
.remove = __devexit_p(mygpio_remove),
.shutdown = __devexit_p(mygpio_shutdown)
};
這些函數的命名本身具有一定的自明性,此段代碼進一步闡明了probe()、remove()和shutdown()函數的作用,具體每個函數的作用可以參考函數體前面的注釋,寫得很詳細。
2.1.2)文件操作函數
關鍵代碼:
static const struct file_operations proc_mygpio_operations = {
.open = proc_mygpio_open,
.read = seq_read,
.write = proc_mygpio_write,
.llseek = seq_lseek,
.release = single_release
};
文件操作深究起來,也能獨立成文了,對于字符設備而言,要提供的主要入口有:open()、release()、read()、write()、ioctl()、llseek()、poll()等,這里簡單說一下用到的幾個函數。
loff_t (*llseek) (struct file *, loff_t, int);
llseek方法用作改變文件中的當前讀/寫位置,并且新位置作為(正的)返回值。
ssize_t (*read) (struct file * filp, char __user * buffer, size_t??? size , loff_t *? p);
這個函數用來從設備中獲取數據。
ssize_t (*write) (struct file *? filp, const char __user *?? buffer, size_t? count, loff_t * ppos);
發送數據給設備。
int (*open) (struct inode * inode , struct file *? filp ) ;
對設備文件進行open()系統調用時,將調用驅動程序的open()函數。該函數主要作用是確定硬件處在就緒狀態、驗證次設備號的合法性、控制使用設備的進程數、根據執行情況返回狀態量等。
int (*release) (struct inode *, struct file *);
release()函數在文件結構被釋放時引用這個操作,當最后一個打開設備的用戶進程執行close()系統調用的時候,內核將調用驅動程序release()函數。release()函數的主要任務是清理未結束的輸入輸出操作,釋放資源,用戶自定義排他標志的復位等。
2.2)驅動修改
名字什么的就不多說了,必然要改的,最大的改動在于proc_xxxx_show(),添加了寄存器讀取操作,具體代碼如下:
static int proc_mygpio_show(struct seq_file *p, void *v)
{
u32 mygpio_value;
mygpio_value = ioread32(base_addr);// read out data
seq_printf(p, "led = 0x%x ", mygpio_value);
mygpio_value = ioread32(base_addr+0x01);// read out data
seq_printf(p, "switch = 0x%x ", mygpio_value);
mygpio_value = ioread32(base_addr+0x02);// read out data
seq_printf(p, "button = 0x%x ", mygpio_value);
mygpio_value = ioread32(base_addr+0x03);// read out data
seq_printf(p, "reg3 = 0x%x ", mygpio_value);
return 0;
}
Ps~關于這塊,我在想,既然修改的地方這么具有規律性,那么是不是能夠設計出一種方法自動創建驅動程序模板,從而可以把精力集中在讀寫函數的實現上來?
3測試結果:
開關這塊稍微有點問題:bit3狀態讀取結果始終為1,改變sw位置無效,具體原因待查。Button和led正常(測試button時,長按5個按鍵中間一個BTNC)。
附1:錯誤筆記
正所謂無知者無畏,在沒有完全掌握一些知識就妄下定論,是不負責任的,以后要多注意。
上一篇博文提到:“這里發現一點小問題,初始化proc_myled_opertaions.read時使用了seq_read,但在驅動程序里定義的讀函數卻是proc_myled_show,在實際使用時,讀led狀態也是失敗的。所以這里的初始化應該是有問題的,下次要改掉測試一下。”最近又深入學習了驅動程序里的各個函數,發現使用seq_read對proc_myled_operations.read進行初始化是正確的,是一種“套路”。
調用cat指令時,系統首先會調用proc_myled_open()函數,在open()函數內會調用proc_myled_show將讀取到的數據存入seq_file。讀失敗的真正原因是Vivado中使用的axi-gpio IP核導致,該IP核的引腳用作輸入時,需要操作方向寄存器,將引腳設為輸入才可以讀取到引腳狀態。但我嘗試操作方向寄存器總是失敗,換成自己的IP,操作多個寄存器又沒有問題,不知道咋回事……
總結
以上是生活随笔為你收集整理的zynq linux内核驱动编写,【原创】Linux下驱动Zynq GPIO (Switch、button、led)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WEB安全基础-WEB服务器相关知识
- 下一篇: Java高级语法笔记-抽象类