使用QT程序控制Linux开发板上的继电器(一)

测试平台介绍

系统为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

接下来编写QT程序
使用QT程序控制Linux开发板上的继电器(二)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值