應用場景:使用的是3399pro,控制GPIO1_B5(RS485)的高低電平。來控制uart0的收發。
http://wiki.t-firefly.com/AIO-3399C/driver_gpio.html 有關於3399詳細GPIO使用說明
http://www.wowotech.net/device_model/429.html GPIO調試相關
1.調試485確認硬件沒問題
查看哪些引腳被占用: cat /sys/kernel/debug/gpio
查看當前開發板哪些引腳被占用。可以看到uart0的引腳被藍牙占用。(因此到時候要在設備樹中將藍牙disabled了,沒用到)
cd /sys/class/gpio echo 45 > export 將gpio 45暴露給用戶層 這樣在gpio目錄下就有一個gpio45的文件,就可以直接對gpio45進行操作。 echo out > gpio45/direction echo 0 > gpio45/value echo 1 > gpio45/value
這樣直接echo 111 > /dev/ttyS0。可以在PC上的串口助手看到有輸出。
但是我用cat /dev/ttyS0發現數據一直不能接收進來。
后面必須要編寫串口程序配置串口才能接收。
2.修改設備樹
2.1 禁用藍牙,因為藍牙占用了uart0
將status = "okay";修改成disabled。
以下設備樹配置有問題:
3.編寫GPIO驅動
#include <linux/kernel.h> #include <linux/gpio.h> #include <linux/init.h> #include <linux/module.h> #include <linux/delay.h> #include <linux/of.h> #include <linux/of_gpio.h> #include <linux/of_platform.h> #include <linux/version.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/sched.h> #include <linux/pm.h> #include <linux/sysctl.h> #include <linux/proc_fs.h> #include <asm/uaccess.h> #include <asm/io.h> #define GPIO_LOW 0 #define GPIO_HIGH 1 int gpio; int major; static struct class *cls; static int rs485_ctrl_open(struct inode *inode, struct file *file) { printk(KERN_EMERG "%s-%d: enter\n",__FUNCTION__,__LINE__); return 0; } static ssize_t rs485_ctrl_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; int ret; printk(KERN_EMERG "%s-%d: enter\n",__FUNCTION__,__LINE__); ret = copy_from_user(&val, buf, count); //copy_to_user(); if (val == 1) { gpio_set_value(gpio,GPIO_HIGH); } else { gpio_set_value(gpio,GPIO_LOW); } return 0; } static long rs485_ctrl_ioctl( struct file *files, unsigned int cmd, unsigned long arg){ //printk("cmd is %d,arg is %d\n",cmd,arg); if(cmd > 1){ printk(KERN_EMERG "rs485 control gpio cmd is 0 or 1\n"); } if(arg > 1){ printk(KERN_EMERG "rs485 control gpio arg is only 1\n"); } gpio_set_value(gpio,cmd); return 0; } static struct file_operations rs485_ctrl_fops = { .owner = THIS_MODULE, .open = rs485_ctrl_open, .write = rs485_ctrl_write, .unlocked_ioctl = rs485_ctrl_ioctl, }; static int rs485_ctrl_probe(struct platform_device *pdev) { int ret ; enum of_gpio_flags flag; //設備節點結構體 struct device_node *rs485_ctrl_node = pdev->dev.of_node; printk(KERN_EMERG "rs485 control gpio %s-%d: enter\n",__FUNCTION__,__LINE__); //of_get_named_gpio_flags 從設備樹中讀取 rs485_ctrl_gpio 的 GPIO 配置編號和標志 gpio = of_get_named_gpio_flags(rs485_ctrl_node,"rs485_ctrl_gpio", 0,&flag); //gpio_is_valid 判斷該 GPIO 編號是否有效。 if (!gpio_is_valid(gpio)){ printk(KERN_INFO "hello: invalid gpio : %d\n",gpio); return -1; } //gpio_request 則申請占用該 GPIO。如果初始化過程出錯,需要調用 gpio_free 來釋放之前申請過且成功的 GPIO 。 ret = gpio_request(gpio, "rs485_ctrl-gpio"); if (ret) { gpio_free(gpio); return -EIO; } //調用 gpio_direction_output 就可以設置輸出高還是低電平 gpio_direction_output(gpio, GPIO_HIGH); major = register_chrdev(0, "rs485_ctrl_dev", &rs485_ctrl_fops); cls = class_create(THIS_MODULE, "rs485_ctrl_dev"); device_create(cls, NULL, MKDEV(major, 0), NULL, "rs485_ctrl_drv"); gpio_set_value(gpio, GPIO_HIGH); printk(KERN_INFO "rs485 control gpio %s-%d: exit\n", __FUNCTION__,__LINE__); return 0; } static int rs485_ctrl_remove(struct platform_device *pdev) { printk(KERN_INFO "rs485 control gpio %s\n", __FUNCTION__); gpio_free(gpio); device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "rs485_ctrl_dev"); return 0; } static const struct of_device_id of_rs485_ctrl_match[] = { { .compatible = "rs485_ctrl" }, { /* Sentinel */ } }; static struct platform_driver rs485_ctrl_driver = { .probe = rs485_ctrl_probe, .remove = rs485_ctrl_remove, .driver = { .name = "rs485_ctrl_drv", .owner = THIS_MODULE, .of_match_table = of_rs485_ctrl_match, }, }; static int __init rs485_ctrl_init(void) { printk(KERN_INFO "rs485 control gpio init %s\n", __FUNCTION__); return platform_driver_register(&rs485_ctrl_driver); } static void __exit rs485_ctrl_exit(void) { platform_driver_unregister(&rs485_ctrl_driver); printk(KERN_INFO "rs485 control gpio exit!\n"); } module_init(rs485_ctrl_init); module_exit(rs485_ctrl_exit); MODULE_LICENSE("GPL");
4.將GPIO驅動編譯進內核
要給linux內核添加模塊(驅動)有如下兩種方式:
(1)動態方式:采用insmod命令來給運行中的linux加載模塊。
(2)靜態方式:修改linux的配置菜單,添加模塊相關文件到源碼對應目錄,然后把模塊直接編譯進內核。
內核的配置系統一般由以下幾部分組成:
(1)Makefile:分布在Linux內核源代碼中的Makefile,定義Linux內核的編譯規則。
(2)配置文件(Kconfig):給用戶提供配置選項,修改該文件來改變配置菜單選項。
(3)配置工具(make menuconfig):包括配置命令解釋器(對配置腳本中使用的配置命令進行解釋),配置用戶界面(提供字符界面和圖形界面)。這些配置工具都是使用腳本語言編寫的,如Tcl/TK、Perl等。
配置工具(make menuconfig)根據kconfig配置腳本產生配置菜單,然后根據配置菜單的配置情況生成頂層目錄下的.config(只有一個.config),在.config里定義了配置選擇的配置宏定義,如下所示:
可以看到這里配置了很多CONFIG_XXX相關的宏定義。
打開/drivers/char/Makefile:
所以.config里面的配置項也就是應用到了Makefile里面。
流程就是:Kconfig(修改該文件來改變配置菜單選項) ----> Make menuconfig(配置菜單) ----> .config(配置生成) ----> Makefile(根據.config里面的配置項確定要編譯哪些驅動)
不創建新的驅動文件夾:
(1)把我們的驅動源文件(rs485_driver.c)放到對應目錄下,具體放到哪里需要根據驅動的類型和特點。這里假設我們放到./driver/char下。
(2)然后我們修改./driver/char下的Kconfig文件
注意這里的RS485_DRIVER這個名字可以隨便寫,他並不需要跟驅動源文件保持一致,但最好保持一致,等下我們在修改Makefile時會用到這個名字,他將會變成CONFIG_RS485_DRIVER,那個名字必須與這個名字對應。如上所示,tristate定義了這個配置選項的可選項有幾個。(具體查看Kconfig語法規則)
(3)修改./driver/char下的Makefile文件,如下所示:
Makefile的CONFIG_RS485_DRIVER需要和Kconfig中的RS485_DRIVER對應起來。因為Kconfig中的RS485_DRIVER在經過make menuconfig配置過,會生成.config。在.config中就會變成CONFIG_RS485_DRIVER。
這樣配置就完成了。只需要再頂層目錄make menuconfig。
然后選擇配置。再make一下就可以了。
創建新的驅動文件夾:
(1)在源碼的對應目錄下建立自己的目錄(rs485),這里假設為/drivers/char/rs485。
(2) 把驅動源碼放到新建的rs485目錄下,並在此目錄下新建Kconfig和Makefile文件。然后給新建的Kconfig和Makefile添加內容。
(3)在drivers/char/Kconfig中加入:source “drivers/char/rs485/Kconfig”
在drivers/char/Makefile中加入:obj-$(CONFIG_RS485_DRIVER) += rs485/ (這邊直接指定我們創建的目錄,Makefile就會去文件rs485下的Makefile找並編譯)
然后make menuconfig選擇編譯:
在.config就可以看到相關配置信息