Linux教程

Linux驱动开发二.点灯2——驱动完善及用户APP编写

本文主要是介绍Linux驱动开发二.点灯2——驱动完善及用户APP编写,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

在前面章节我们通过点亮LED完成了最基础的IO驱动,但是有两个问题:

  1. 只能通过挂载/卸载设备控制输出
  2. 没有对应的用户APP

下面我们就要针对这两点内容修改程序

驱动程序修改

驱动程序主要是修改write函数,思路就是先编写一个函数,用来实现LED的状态切换

#define LEDOFF  0
#define LEDON   1

static void led_switch(u8 sta)
{
    u32 val = 0;    
    if(sta == LEDON){
        val = readl(IMX6UL_GPIO1_DR);
        val &= ~(1<<3);
        writel(val,IMX6UL_GPIO1_DR);
    }
    else if(sta == LEDOFF){
        val = readl(IMX6UL_GPIO1_DR);
        val |= (1<<3);
        writel(val,IMX6UL_GPIO1_DR);
    }
}

函数的逻辑很简单没有什么可讲的,就是根据不动的参数(sta)来讲GPIO对应的为置零或置1。所以就不再写注释了。然后在write的时候调用这个函数就行了

/**
 * @brief 改变LED状态
 * 
 * @param file 
 * @param buf 
 * @param count 
 * @param ppos 
 * @return ssize_t 
 */
static ssize_t led_write(struct file *file, 
                        const char __user *buf, 
                        size_t count, 
                        loff_t *ppos)
{   
    int ret = 0;
    unsigned char databuf[1];                   //待写入的参数
    ret = copy_from_user(databuf,buf,count);    //获取从用户空间传递来的参数

    if (ret == 0){
        led_switch(databuf[0]);                 //根据参数改变LED状态
    }
    else{
        printk("kernelwrite err!\r\n");
        return -EFAULT;
    }
}   

整个函数也没什么可说的,直接调用函数就行。

应用程序构成

应用程序这块也没什么好说的

/**
 * @file app.c
 * @author your name (you@domain.com)
 * @brief 驱动应用程序
 * @version 0.1
 * @date 2022-03-27
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

#define LEDOFF  0
#define LEDON   1

/**
 * @brief 
 * 
 * @param argc                      //参数个数 
 * @param argv                      //参数
 * @return int 
 */
int main(int argc,char *argv[])
{
    char *filename;                 //文件名
    filename = argv[1];             //文件名为命令行后第二个参数(索引值为1)

    int ret = 0;                    //初始化操作返回值
    int f = 0;                      //初始化文件句柄
    int action = atoi(argv[2]);     //读写标志:0为关灯,1为开灯
    unsigned char databuf[1];

    databuf[0]= atoi(argv[2]);

    if(argc != 3){                  //输入参数个数不为3,提示输入格式错误
        printf("input format error!\r\n");
    }

    f = open(filename, O_RDWR);     //打开文件
    if(f < 0){
    printf("file open error\r\n");
    return -1;
    }

    ret = write(f,databuf,1);       //文件写入数据
    if(ret<0){                      //写入错误,输出信息并关闭文件
        printf("LED control failed\r\n");
        close(f);
        return -1;
    }
    close(f);                       //关闭文件
    return 0;
}

这里主要就是有个点要注意一下:在程序最后调用了close函数,对应的是驱动里的release函数,在上一章写驱动的时候灯的熄灭我们把代码放在了模块卸载的时候,还有一种方法是放在文件close的过程里。但是在配合应用程序使用的时候,由于我们在应用程序最后都使用了close命令,每次点亮LED闪一下就被close里的代码灭了。这一点一定要注意。

这个应用程序和前面讲框架时候的应用程序有点区别:前面那个我们用第3个参数0或1来分别表示读和写;而在这个应用程序中0和1都是写,只不过代表要写入不同的值。

最后把完整的驱动代码放在下面

/**
 * @file led.c
 * @author your name (you@domain.com)
 * @brief led点亮程序模块测试
 * @version 0.1
 * @date 2022-04-04
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define DEV_MAJOR   200             //设备号
#define DEV_NAME    "LED"   //设备名称

// static char writebuf[100];
// static char readbuf[100];

/**
 * @brief 寄存器物理地址
 * 
 */
#define CCM_CCGR1_BASE          (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE  (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE  (0X020E02F4)
#define GPIO1_GDIR_BASE         (0X0209C004)
#define GPIO1_DR_BASE           (0X0209C000)

/**
 * @brief   内存映射后虚拟内存地址 
 * 
 */
static void __iomem *IMX6UL_CCM_CCGR1;
static void __iomem *IMX6UL_SW_MUX_GPIO1_IO03;
static void __iomem *IMX6UL_SW_PAD_GPIO1_IO03;
static void __iomem *IMX6UL_GPIO1_DR;
static void __iomem *IMX6UL_GPIO1_GDIR;

#define LEDOFF  0
#define LEDON   1

// /**
//  * @brief 打开设备文件
//  * 
//  * @return int 
//  */
static int led_open(struct inode *inode, struct file *filp)
{
    printk("dev open!\r\n");
    return 0;
}

/**
 * @brief 关闭设备文件
 * 
 * @return int 
 */
static int led_release(struct inode *inode, struct file *filp)
{
    printk("dev release!\r\n");
    return 0;
}

/**
 * @brief 读设备文件数据
 * 
 * @param filp 
 * @param buf 
 * @param count 
 * @param ppos 
 * @return ssize_t 
 */
static ssize_t led_read(struct file *filp, 
                               __user char *buf,
                               size_t count, 
                               loff_t *ppos)
{
    int ret = 0;
    printk("dev read data!\r\n");

    if (ret == 0){
        return 0;
    }
    else{
        printk("kernel read data error!");
        return -1;
    }
}

static void led_switch(u8 sta)
{
    u32 val = 0;    
    if(sta == LEDON){
        val = readl(IMX6UL_GPIO1_DR);
        val &= ~(1<<3);
        writel(val,IMX6UL_GPIO1_DR);
    }
    else if(sta == LEDOFF){
        val = readl(IMX6UL_GPIO1_DR);
        val |= (1<<3);
        writel(val,IMX6UL_GPIO1_DR);
    }
}


/**
 * @brief 改变LED状态
 * 
 * @param file 
 * @param buf 
 * @param count 
 * @param ppos 
 * @return ssize_t 
 */
static ssize_t led_write(struct file *file, 
                        const char __user *buf, 
                        size_t count, 
                        loff_t *ppos)
{   
    int ret = 0;
    unsigned char databuf[1];                   //待写入的参数
    ret = copy_from_user(databuf,buf,count);    //获取从用户空间传递来的参数

    if (ret == 0){
        led_switch(databuf[0]);                 //根据参数改变LED状态
    }
    else{
        printk("kernelwrite err!\r\n");
        return -EFAULT;
    }
}   

/**
 * @brief 字符设备操作操作集
 * 
 */
static struct file_operations led_fops= {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .read = led_read,
    .write = led_write,
};

/**
 * @brief 初始化
 * 
 * @return int 
 */
static int __init led_init(void)
{   
    int ret = 0;
    unsigned int val = 0;
    /*led初始化*/
    //获取地址映射
    IMX6UL_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
    IMX6UL_SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    IMX6UL_SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    IMX6UL_GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    IMX6UL_GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
    printk("ioremap finished!\r\n");

    //时钟初始化
    
    val = readl(IMX6UL_CCM_CCGR1);  //读取CCM_CCGR1的值
    val &= ~(3<<26);                //清除bit26、27
    val |= (3<<26);                //bit26、27置1
    writel(val, IMX6UL_CCM_CCGR1);
    printk("CCM init finished!\r\n");

    /*GPIO初始化*/
    writel(0x5, IMX6UL_SW_MUX_GPIO1_IO03);
    writel(0x10B0, IMX6UL_SW_PAD_GPIO1_IO03);
    printk("GPIO SW init finished!\r\n");

    val = readl(IMX6UL_GPIO1_GDIR);
    val |= 1<<3;                        //bit3=1,设置为输出
    writel(val, IMX6UL_GPIO1_GDIR);
    printk("GPIO GDIR init finished!\r\n");

    val = readl(IMX6UL_GPIO1_DR);
    val &= ~(1<<3);
    writel(val,IMX6UL_GPIO1_DR);

    printk("device init!\r\n");
  
    //字符设备注册
    ret = register_chrdev(DEV_MAJOR, DEV_NAME, &led_fops);
    if(ret < 0 ){
        printk("device init failed\r\n");
        return -EIO;
    }
    return 0;
}

/**
 * @brief 卸载
 * 
 */
static void __exit led_exit(void)
{   
    unsigned int val = 0;
    //关闭led
    val = readl(IMX6UL_GPIO1_DR);
    val |= (1<<3);
    writel(val ,IMX6UL_GPIO1_DR);


    //取消地址映射
    iounmap(IMX6UL_CCM_CCGR1);
    iounmap(IMX6UL_SW_MUX_GPIO1_IO03);
    iounmap(IMX6UL_SW_PAD_GPIO1_IO03);
    iounmap(IMX6UL_GPIO1_DR);
    iounmap(IMX6UL_GPIO1_GDIR);

    //字符设备注销
    unregister_chrdev(DEV_MAJOR,DEV_NAME);
    printk("device exit\r\n");

}

module_init(led_init);      //模块加载
module_exit(led_exit);      //模块卸载

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZeqiZ");
led.c

写在后面

通过这个点亮LED的过程我们主要了解一下数据的写入、文件的操作、寄存器地址映射等方法,主要是要我们有个大概的思路,后面的操作中我们应该不太常用这种方式来写驱动。但是整个流程一定要清楚!

这篇关于Linux驱动开发二.点灯2——驱动完善及用户APP编写的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!