LED字符设备驱动

一、驱动程序

led.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>

#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>



/**
 * file name:led
 * date: 2021-08-12  20:00
 * version:1.0
 * author:luatao
 * describe:Led device drive
 */



#define LED_MAJOR        200  /*主设备号*/
#define LED_NAME         "led"      /* 设备名*/

#define LEDOFF 1  /* 关灯 */
#define LEDON  0  /* 开灯 */

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


/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

/* LED状态转换 */
void led_switch(u8 cmd)
{
    u32 val  = 0;  // 寄存器临时变量值
    if(cmd == LEDON){  // 打开
        val = readl(GPIO1_DR);  // 读数据寄存器的值
        val &= ~(1 << 3);  // 把这一位清零 
        writel(val, GPIO1_DR);  // 再写入寄存器 
    }else if(cmd == LEDOFF){
        val = readl(GPIO1_DR);  // 读数据寄存器的值
        val |= (1 << 3);  // 把这一位置1
        writel(val, GPIO1_DR);  // 再写入寄存器 
    }
}


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

/* 从设备读取数据 */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    printk("led read !\r\n");
    return 0;
}

/* 往设备写数据 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int ret;
    unsigned char datebuf[1];  // 接收数据缓冲区
    unsigned char cmd;  // LED的命令 0 打开  1 关闭
     /* 被写入的内核空间的数据 ,需要用户空间向内核空间发送数据 */
    ret = copy_from_user(datebuf, buf, cnt);  // 接收发送过来的数据
    if(ret ==0){  // 成功返回0  失败返回有多少个B未完成copy
        // printk("kernel receivedata: %s !\r\n", datebuf);
    }else{
        printk("kernel receivedata failed!\r\n");
        return -1;
    }

    /* 处理接收的数据 */
    cmd = datebuf[0];  



    /* 控制LED执行命令 */
    if(cmd == LEDON){  // 打开LED
        led_switch(LEDON); 
    }else if(cmd ==LEDOFF){  // 关闭LED
        led_switch(LEDOFF); 
    }else{
        printk("cmd is invalid!\r\n");
    }
    return 0;
}

/* 释放设备 */
static int led_release(struct inode *inode, struct file *filp)
{
    //printk("led release!\r\n");
    return 0;
}


/* 设备操作函数结构体  */
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

/* 驱动入口函数 */
static int __init led_init(void)
{
    int ret = 0;
    u32 val = 0; // 寄存器的值

    /* 初始化LED */
    /* 1. 寄存器地址映射 */
    IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
    SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
    SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
    GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
    GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

    /* 2. 使能GPIO1时钟 */
     val = readl(IMX6U_CCM_CCGR1);
    val &= ~(3 << 26); /* 清除以前的设置 */
    val |= (3 << 26); /* 设置新值 */
    writel(val, IMX6U_CCM_CCGR1);

    /* 3、设置 GPIO1_IO03 的复用功能,将其复用为 GPIO1_IO03,最后设置 IO 属性。*/
    writel(5, SW_MUX_GPIO1_IO03);

    /* 寄存器 SW_PAD_GPIO1_IO03 设置 IO 属性 */
    writel(0x10B0, SW_PAD_GPIO1_IO03);

    /* 4、设置 GPIO1_IO03 为输出功能 */
    val = readl(GPIO1_GDIR);
    val &= ~(1 << 3); /* 清除以前的设置 */
    val |= (1 << 3); /* 设置为输出 */
    writel(val, GPIO1_GDIR);

    /* 5、默认关闭 LED */
    led_switch(LEDOFF);


    /* 6. 注册字符设备驱动 */
    ret = register_chrdev(LED_MAJOR,LED_NAME,&led_fops);
    if(ret < 0){
        printk("led drive register failed!\r\n");
        return ret;
    }
    printk("led drive register ok!\r\n");
    return 0;
}


/* 驱动出口函数 */
static void __exit led_exit(void)
{
    /* 1. 取消地址映射 */
    iounmap(IMX6U_CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03); 
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    /* 2. 注销字符设备驱动 */
    unregister_chrdev(LED_MAJOR,LED_NAME);
    printk("led drive unregsister ok !\r\n");
}

/* 加载驱动入口和出口函数 */
module_init(led_init);
module_exit(led_exit);

/* LICENSE 和 AUTHOR 信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("luatao");

二、应用程序

ledApp.c 实现LED打开和关闭

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

/**
 * file name:ledApp
 * date: 2021-08-12  20:33
 * version:1.0
 * author:luatao
 * describe:字符设备驱动LED测试APP
 * 执行命令:./ledApp 1 关闭灯 或者是 ./ledApp 0 打开灯
 */


#define LEDOFF  1  /* 关闭LED */
#define LEDON   0  /* 打开LED*/

/* 主程序 */
int main(int argc, char *argv[])
{
    char *filename;  // 可执行文件名
    int fd,ret;  //  fd: 文件句柄 ret:函数操作返回值
    unsigned char databuf[1]; // 缓冲区


    /* 先判断输入的参数 */
    if(argc !=  3){  // 本身文件名带1个 执行文件1个  读出或者写入一个 
       printf("parameter error!\r\n");
       return -1;
    }

    /* 分析参数 ,提取有用的信息 */
    filename = argv[1];  // 可执行文件名 
    databuf[0] = atoi(argv[2]); // 执行命令


    
    /* 打开LED文件 */
    fd = open(filename, O_RDWR);  // 可读可写 
    if(fd < 0){
        printf("can't open file:%s\r\n",filename);
        return -1;
    }

    /* 向文件中写入数据 */
    ret = write(fd, databuf, sizeof(databuf));
    if(ret < 0){
            printf("write file %s failed !\r\n",filename);
            goto close_file;   // 关闭文件 
    }else{ // 写入成功
     //   printf("led operation ok!\r\n");
    }



close_file:
    /* 关闭文件 */
    ret = close(fd);
    if(ret < 0){
        printf("can't close file %s \r\n", filename);
        return -1;
    }

    return 0;
}
 

led_twinkle.c 实现LED的闪烁

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

/**
 * file name:led_twinkle
 * date: 2021-08-12  21:25
 * version:1.0
 * author:luatao
 * describe:字符设备驱动LED测试APP
 * 执行命令:./led_twinkle n LED闪烁延时间隔  单位s
 */


#define LEDOFF  1  /* 关闭LED */
#define LEDON   0  /* 打开LED*/

/* 主程序 */
int main(int argc, char *argv[])
{
    char *filename;  // 可执行文件名
    int fd,ret,n_interval ;  //  fd: 文件句柄 ret:函数操作返回值 n_interval Led的闪烁时间间隔
    unsigned char databuf[1] = {0}; // 缓冲区


    /* 先判断输入的参数 */
    if(argc !=  3){  // 本身文件名带1个 执行文件1个  读出或者写入一个 
       printf("parameter error!\r\n");
       return -1;
    }

    /* 分析参数 ,提取有用的信息 */
    filename = argv[1];  // 可执行文件名 
    n_interval = atoi(argv[2]); // 闪烁的延时间隔 


    
    /* 打开LED文件 */
    fd = open(filename, O_RDWR);  // 可读可写 
    if(fd < 0){
        printf("can't open file:%s\r\n",filename);
        return -1;
    }

    while(1){

        ret = write(fd, databuf, sizeof(databuf));
        if(ret < 0){
                printf("write file %s failed !\r\n",filename);
                goto close_file;   // 关闭文件 
        }else{ // 写入成功
        //   printf("led operation ok!\r\n");
        }
        
        /* 延时加翻转 */
        if(databuf[0]  == 1)
            databuf[0]  = 0;
         else
            databuf[0] = 1;
        sleep(n_interval); //延时n_interval秒

    }
 


close_file:
    /* 关闭文件 */
    ret = close(fd);
    if(ret < 0){
        printf("can't close file %s \r\n", filename);
        return -1;
    }

    return 0;
}
 

三、测试

加载驱动:
在这里插入图片描述
生成设备节点文件:

mknod /dev/led c 200 0

在这里插入图片描述
打开灯:

./ledApp /dev/led 0

在这里插入图片描述
关闭灯:
在这里插入图片描述
闪烁 间隔1s:

./led_twinkle /dev/led 1

在这里插入图片描述
按ctrl + c 退出

卸载:

rmmod led

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值