`使用QT程序控制Linux开发板上的继电器(一)`
- 测试平台介绍
- 测试系统接线
- 编写PlatformIO驱动程序
- 修改设备树
- 烧录设备树
- Platform字符设备驱动设备框架
- 完善驱动框架
- 编写测试程序
- 应用程序测试
测试平台介绍
系统为Ubuntu16.04LTS,QT版本为5.12,开发板是正点原子的IMX6ULL.ALPHA(NAND)开发板,使用的是正点原子出厂系统。最终要实现的效果为:通过点击QT程序上的按钮来控制连接在开发板IO上继电器的通断,进而控制连接在继电器上的LED的点亮与熄灭。
测试系统接线
系统接线如图所示,继电器控制端接到UART3_RX引脚:
编写PlatformIO驱动程序
修改设备树
首先需要修改设备树,添加对应的设备节点,重点是设置compatible以及引脚定义pinctrl-name以及pinctrl-0,因为platform总线需要通过compatible值来匹配设备和驱动,pinctrl-name和pinctrl-0对应实际用于控制的IO。
需要注意的是,UART3_RX在原来的设备树里,已经被使用了。需要将使用这个引脚的设备默认状态设置为disabled,或是直接将其它地方用到这个引脚的代码注释掉,否则后面加载模块的时候会加载不上,后面编写驱动程序的部分会提到。
修改后使用make dtbs
编译设备树文件,生成设备树文件imx6ull-14x14-nand-4.3-480x272-c.dtb、imx6ull-14x14-nand-4.3-800x480-c.dtb、imx6ull-14x14-nand-7-800x480-c.dtb、imx6ull-14x14-nand-7-1024x600-c.dtb、
imx6ull-14x14-nand-10.1-1280x800-c.dtb、imx6ull-14x14-nand-hdmi.dtb、imx6ull-14x14-nand-vga.dtb。
烧录设备树
通过U盘或是网络将生成的设备树文件拷贝到开发板上后,根据自己显示屏的尺寸和分辨率的情况,使用 nandwrite 指令将对应的设备树文件烧写到
NAND对应的地址中。完成后重启,在启动时,系统会根据屏幕类型自动选择设备树。
flash_erase /dev/mtd3 0 0
nandwrite -p /dev/mtd3 $Cur_Dir/imx6ull-14x14-nand-4.3-480x272-c.dtb
nandwrite -s 0x20000 -p /dev/mtd3 $Cur_Dir/imx6ull-14x14-nand-4.3-800x480-c.dtb
nandwrite -s 0x40000 -p /dev/mtd3 $Cur_Dir/imx6ull-14x14-nand-7-800x480-c.dtb
nandwrite -s 0x60000 -p /dev/mtd3 $Cur_Dir/imx6ull-14x14-nand-7-1024x600-c.dtb
nandwrite -s 0x80000 -p /dev/mtd3 $Cur_Dir/imx6ull-14x14-nand-10.1-1280x800-c.dtb
nandwrite -s 0xa0000 -p /dev/mtd3 $Cur_Dir/imx6ull-14x14-nand-hdmi.dtb
nandwrite -s 0xc0000 -p /dev/mtd3 $Cur_Dir/imx6ull-14x14-nand-vga.dtb
sync
reboot
如果使用的是EMMC版本的开发板,则使用以下命令(根据屏幕尺寸选择对应的设备树,以4.3寸800x480RGB屏为例)
cp imx6ull-14x14-emmc-4.3-800x480-c.dtb /run/media/mmcblk1p1
sync
reboot
这样,设备树就成功烧录到系统中了,烧录设备树后进入/proc/device-tree查看创建的节点是否存在,如果设备树存在就说明设备节点添加成功了。
Platform字符设备驱动设备框架
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
......
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
/*设备匹配后probe函数执行*/
static int relays_probe(struct platform_device *dev)
{
printk("relays module probe\r\n");
return 0;
}
/*移除platform设备的时候执行*/
static int relays_remove(struct platform_device *dev)
{
printk("relays module remove\r\n");
return 0;
}
/*匹配列表*/
static const struct of_device_id relays_of_match[] = {
{.compatible = "fzu,relays"},
{ }
};
/*platform驱动结构体*/
static struct platform_driver relays_driver = {
.driver = {
.name = "fzu,relays",
.of_match_table = relays_of_match,
},
.probe = relays_probe,
.remove = relays_remove,
};
static int __init relays_init(void)
{
printk("relays module init\r\n");
return platform_driver_register(&relays_driver);
}
static void __exit relays_exit(void)
{
platform_driver_unregister(&relays_driver);
printk("relays module exit\r\n");
}
module_init(relays_init);
module_exit(relays_exit);
MODULE_AUTHOR("xxxxxxx");
MODULE_LICENSE("GPL");
编写好设备框架后,编译,将驱动文件放到IMX6ULL开发版上,加载模块。如果引脚已经被其它设备占用,就会出现这样的报错:
这是因为这个引脚已经被作为UART3的RX使用了,如果要将这个引脚复用为普通IO,就需要在设备树禁用UART3,将status修改为disabled,或是直接修改pinctrl_uart3,将UART3_RX引脚注释掉。
重新编译,烧录设备树后,成功加载模块。
完善驱动框架
接下来就是往驱动框架里添加驱动注册、自动添加设备节点等内容了
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#define RELAYS_CNT 1
#define RELAYS_NAME "relays"
#define RELAYS_OFF 0
#define RELAYS_ON 1
/*设备结构体*/
struct relays_dev {
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
int minor;
struct device_node *nd;
int relaysgpio;
};
struct relays_dev relaysdev;
int relays_open(struct inode *inode,struct file *filp)
{
//printk("open\r\n");
filp->private_data = &relaysdev;
return 0;
}
int relays_write (struct file *filp, const char __user *buf, size_t cnt, loff_t *ppos)
{
//获取私有数据
struct relays_dev *dev = (struct relays_dev*)filp->private_data;
int retval;
unsigned char datebuf[1];
//printk("write\r\n");
//从应用程序获取数据
retval=copy_from_user(datebuf,buf,cnt);
if(retval < 0){
printk("kernel write failed\r\n");
return -EFAULT;
}
if(datebuf[0] == RELAYS_ON){
gpio_set_value(dev->relaysgpio,0);//打开LED灯
}else if(datebuf[0] == RELAYS_OFF){
gpio_set_value(dev->relaysgpio,1);//关闭LED灯
}
return 0;
}
int relays_release (struct inode *inode, struct file *filp)
{
//struct dtsled_dev *dev = (struct dtsled_dev*)filp->private_data;
return 0;
}
struct file_operations relays_fops = {
.owner = THIS_MODULE,
.open = relays_open,
.write = relays_write,
.release = relays_release,
};
/*设备匹配后probe函数执行*/
static int relays_probe(struct platform_device *dev)
{
int ret = 0;
printk("relays module probe\r\n");
/*注册设备号*/
relaysdev.major = 0;
if(relaysdev.major){//设备号已指定
relaysdev.devid = MKDEV(relaysdev.major,0);
ret = register_chrdev_region(relaysdev.devid,RELAYS_CNT,RELAYS_NAME);
}else{
ret = alloc_chrdev_region(&relaysdev.devid,0,RELAYS_CNT,RELAYS_NAME);
}
if(ret < 0){
printk("relays module fail devid\r\n");
goto fail_devid;
}else{
relaysdev.major = MAJOR(relaysdev.devid);
relaysdev.minor = MINOR(relaysdev.devid);
printk("major:%d minor:%d \r\n",relaysdev.major,relaysdev.minor);
}
/*自动添加设备节点*/
/*注册设备*/
relaysdev.cdev.owner = THIS_MODULE;
cdev_init(&relaysdev.cdev,&relays_fops);
cdev_add(&relaysdev.cdev,relaysdev.devid,RELAYS_CNT);
/*注册类*/
relaysdev.class = class_create(THIS_MODULE,RELAYS_NAME);
if(IS_ERR(relaysdev.device)){
ret = PTR_ERR(relaysdev.device);
goto fail_class;
}
/*创建设备*/
relaysdev.device = device_create(relaysdev.class,NULL,relaysdev.devid,NULL,RELAYS_NAME);
if(IS_ERR(relaysdev.device)){
ret = PTR_ERR(relaysdev.device);
goto fail_device;
}
/*初始化IO*/
relaysdev.nd = of_find_node_by_path("/relays");
if(relaysdev.nd == NULL){
ret = -EINVAL;
printk("relays module fail node\r\n");
goto fail_nd;
}
relaysdev.relaysgpio = of_get_named_gpio(relaysdev.nd,"gpios",0);
if(relaysdev.relaysgpio < 0){
ret = -EINVAL;
printk("relays module fail gpio\r\n");
goto fail_gpio;
}
gpio_request(relaysdev.relaysgpio,"gpios");
gpio_direction_output(relaysdev.relaysgpio,0);
return 0;
fail_gpio:
fail_nd:
device_destroy(relaysdev.class,relaysdev.devid);
fail_device:
class_destroy(relaysdev.class);
fail_class:
unregister_chrdev_region(relaysdev.devid,RELAYS_CNT);
cdev_del(&relaysdev.cdev);
fail_devid:
return ret;
}
/*移除platform设备的时候执行*/
static int relays_remove(struct platform_device *dev)
{
printk("relays module remove\r\n");
//注销设备时关闭
gpio_set_value(relaysdev.relaysgpio,0);
/*删除cdev*/
cdev_del(&relaysdev.cdev);
/*注销设备号*/
unregister_chrdev_region(relaysdev.devid,RELAYS_CNT);
/*注销设备*/
device_destroy(relaysdev.class,relaysdev.devid);
/*注销类*/
class_destroy(relaysdev.class);
//释放io
gpio_free(relaysdev.relaysgpio);
return 0;
}
/*匹配列表*/
static const struct of_device_id relays_of_match[] = {
{.compatible = "fzu,relays"},
{ }
};
/*platform驱动结构体*/
static struct platform_driver relays_driver = {
.driver = {
.name = "fzu,relays",
.of_match_table = relays_of_match,
},
.probe = relays_probe,
.remove = relays_remove,
};
static int __init relays_init(void)
{
printk("relays module init\r\n");
return platform_driver_register(&relays_driver);
}
static void __exit relays_exit(void)
{
platform_driver_unregister(&relays_driver);
printk("relays module exit\r\n");
}
module_init(relays_init);
module_exit(relays_exit);
MODULE_AUTHOR("xxxxxxx");
MODULE_LICENSE("GPL");
编写完驱动后,重新编译驱动,将驱动拷贝到开发板上,加载驱动后,进入/dev查看,可以看到设备节点存在
接下啦就要写一个测试程序来测试驱动功能
编写测试程序
在Linux系统中,控制IO的高低电平,需要在应用层对IO对应的设备文件写入数据,驱动从应用层中读取数据,做出对应的处理。例如用户向/dev/relays文件写入一个0,那么relays对应的驱动文件就可以读取到这个数,并做出处理。
驱动部分已经完成,这部分要完成的就是从应用层发送一个正确的数据给驱动。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/time.h>
/*
*argc:应用程序参数个数
*argv[]:参数内容字符串
*relaysAPP /dev/relays 0(1)
*/
int main(int argc,char *argv[])
{
int fd;
if(argc!=3){
printf("usage error\r\n");
return -1;
}
char *filename;
unsigned char datebuf[1];
filename = argv[1];
fd = open(filename,O_RDWR);
if(fd < 0){
printf("open file %s failed\r\n",filename);
return -1;
}
int ret;
datebuf[0]=atoi(argv[2]);//将字符转换为数字
ret=write(fd,datebuf,1);
if(ret < 0){
printf("led control failed\r\n");
close(fd);
return -1;
}
close(fd);
}
应用程序测试
编译完成后,使用交叉编译器编译应用程序
arm-linux-gnueabihf-gcc relaysAPP.c -o relaysAPP
编译完成后,将编译生成的可执行文件拷贝到开发板上运行
./relaysAPP /dev/relays 0
./relaysAPP /dev/relays 1