Linux驱动学习记录-13.demo

基于前面所学知识,写了一个完整的小程序,功能如下:
App可控制led的亮灭,采用定时器模拟pwm输出,key1增大占空比,led亮度增高,key2减少占空比。
gpio中断控制key,按键按一次,输出此时占空比,每次±10%,范围在0%-100%
两个定时器,定时器1负责输出PWM,定时器2用于按键延迟确认。
窗口输入,可直接设置占空比
可设置输出,显示目前亮度占空比

一、设备树

1.iomuxc设置

定义的gpio在imx6ul-pinfunc.h找。

pinctrl_key1:key1zch{
			fsl,pins=<
				MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01    0xF080
			>;
		};
		pinctrl_key2:key2zch{
			fsl,pins=<
				MX6UL_PAD_NAND_CE1_B__GPIO4_IO14          0xF080
				>;
		};

		pinctrl_led: ledzch{
			fsl,pins = <
				MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03    0x000010B0
				>;
		};

2.节点设置

写完记得查重,屏蔽重复端口

gpio_key1{
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "zch-key1";
	pinctrl-names = "default";
	pinctrl-0 = "&pinctrl_key1";
	key1-gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;
	interrupt-parent = <&gpio5>;
	interrupts = <1 IRQ_TYPE_EDGE_FALLING>;
	status = "okay";
};

gpio_key2{
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "zch-key2";
	pinctrl-names = "default";
	pinctrl-0 = "&pinctrl_key2";
	key2-gpio = <&gpio4 14 GPIO_ACTIVE_LOW>;
	interrupt-parent = <&gpio4>;
	interrupts = <14 IRQ_TYPE_EDGE_FALLING>;
	status = "okay";
};

gpio_led{
	#address-cells = <1>;
	#size-cells = <1>;
	compatible = "zch-led";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_led>;
	led-gpio = <&gpio5 3 GPIO_ACTIVE_LOW>;
	status = "okey";
};

二、驱动程序

按照编写程序思路,而非程序结构思路

1.两个结构体

CMD命令参照下面连接解释_IO等宏的使用,和ioctl的命令cmd构造详解

#define SET_CMD    (_IO(0XEF, 0x01))
#define OPEN_CMD   (_IO(0XEF, 0x02))
#define CLOSE_CMD  (_IO(0XEF, 0x03))
struct zchirq_desc { //外部中断结构体
    int gpio; //gpio编号
    int irqnum;  //中断号
    int flag; //按键标志
    char name[10]; //按键名字
    irqreturn_t (*handler) (int, void *);  //服务函数
};

struct irq_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;  //节点
    int led_gpio;          //led的gpio
    atomic_t keyvalue;   //原子变量,敏感,在gpio中断中更改
    spinlock_t spinlock;  //自旋锁
    struct timer_list timer[2];  //两个定时器
    int period;  //10自减至1
    struct zchirq_desc keydesc[2];  //两个按键
};
struct irq_dev irqled;

2.入口函数

static int __init irq_initt(void)
{
    int ret = 0;
    spin_lock_init(&irqled.spinlock);    //初始化自旋锁
    /*设备号*/
    if (irqled.major)
    {
        irqled.devid = MKDEV(irqled.major, 0);
        register_chrdev_region(irqled.devid, IRQ_CNT, IRQ_NAME);
    }
    else
    {
        alloc_chrdev_region(&irqled.devid, 0, IRQ_CNT, IRQ_NAME);
        irqled.major = MAJOR(irqled.devid);
        irqled.minor = MINOR(irqled.devid);
    }
    irqled.cdev.owner = THIS_MODULE;
    cdev_init(&irqled.cdev, &irq_fops);
    cdev_add(&irqled.cdev, irqled.devid, IRQ_CNT);
    irqled.class = class_create(THIS_MODULE, IRQ_NAME);
    irqled.device = device_create(irqled.class, NULL, irqled.devid, NULL, IRQ_NAME);
  //初始化,占空比5
    atomic_set(&irqled.keyvalue, 5);
    ret = gpio_init();
    if(ret < 0){
        printk("gpioinit error");
        return -1;
    }
    //两个定时器初始化,还未设置周期,不会激活
    init_timer(&irqled.timer[0]);
    init_timer(&irqled.timer[1]);
    irqled.timer[0].function = timer1_func;
    irqled.timer[1].function = timer2_func;
    irqled.timer[0].data = (unsigned long) &irqled;
    irqled.timer[1].data = (unsigned long) &irqled;
    printk("gpioinit success\r\n");
    return 0;
}

3.gpio初始化

 int ret = 0;
    unsigned char i = 0;
    //先设置ledgpio
    irqled.nd = of_find_node_by_path("/gpio_led"); //找gpio节点
    if(irqled.nd == NULL){
        printk("ledgpio node not find");
        return -1;
    }
    irqled.led_gpio = of_get_named_gpio(irqled.nd, "led-gpio", 0); //找gpio编号
    if(irqled.led_gpio < 0){
        printk("ledgpio not get");
        return -1;
    }
    gpio_request(irqled.led_gpio, "led"); //申请gpio 使用之前要申请
    ret = gpio_direction_output(irqled.led_gpio, 1);  //设置ledgpio输出模式,初始值1
    if(ret < 0)
        printk("not set ledgpio");
    else
        printk("gpioled set success");
    //再设置keygpio1
    irqled.nd = of_find_node_by_path("/gpio_key1");  //找key1节点
    if(irqled.nd == NULL){
        printk("keygpio1 node not find");
        return -1;
    }
    irqled.keydesc[0].gpio = of_get_named_gpio(irqled.nd, "key1-gpio", 0); //编号
    if(irqled.keydesc[0].gpio < 0){
        printk("key1 not get");
        return -1;
    }
    sprintf(irqled.keydesc[0].name, "key1");//设置名字label
    irqled.keydesc[0].handler = key1_handler; //中断函数句柄
    //再设置keygpio2
    irqled.nd = of_find_node_by_path("/gpio_key2");  //找key2节点
    if(irqled.nd == NULL){
        printk("keygpio2 node not find");
        return -1;
    }
    irqled.keydesc[1].gpio = of_get_named_gpio(irqled.nd, "key1-gpio", 0); //编号
    if(irqled.keydesc[1].gpio < 0){
        printk("key2 not get");
        return -1;
    }
    sprintf(irqled.keydesc[1].name, "key2");//设置名字label
    irqled.keydesc[1].handler = key2_handler; //中断函数句柄
    //key设置中断,两个一起
    for(i=0; i < 2; ++i){
        irqled.keydesc[i].flag = 0;     //标志位清零
        gpio_request(irqled.keydesc[i].gpio, irqled.keydesc[i].name);  //申请
        gpio_direction_input(irqled.keydesc[i].gpio);  //设置方向
        irqled.keydesc[i].irqnum = gpio_to_irq(irqled.keydesc[i].gpio);  //获取中断号
        //申请中断
        ret = request_irq(irqled.keydesc[i].irqnum,  //中断号
                          irqled.keydesc[i].handler, //中断函数句柄
                          IRQF_TRIGGER_FALLING,      //中断触发标志
                          irqled.keydesc[i].name,    //中断名字
                          &irqled);                  //设备结构体地址
        if(ret < 0){
            printk("irq %d request faild ", irqled.keydesc[i].irqnum);
            return -1;
        }
    }
    return 0;
 }

4.两个外部中断函数

//减少占空比,keyvalue
static irqreturn_t key1_handler (int irqnum, void *dev_id)
{
    struct irq_dev *dev = (struct irq_dev *)dev_id;
    irqled.keydesc[0].flag = 1; //key1按下
    printk("key1 push\r\n");
    mod_timer(&dev->timer[1], jiffies + msecs_to_jiffies(10)); //10ms,启动定时器2
    return IRQ_RETVAL(IRQ_HANDLED);
}
//增大占空比,
static irqreturn_t key2_handler (int irqnum, void *dev_id)
{
    struct irq_dev *dev = (struct irq_dev *)dev_id;
    irqled.keydesc[1].flag = 1; //key2按下
    printk("key2 push\r\n");
    mod_timer(&dev->timer[1], jiffies + msecs_to_jiffies(10)); //10ms,启动定时器2
    return IRQ_RETVAL(IRQ_HANDLED);
}

4.两个定时器服务函数

//pwm输出定时器
//定义period,从10循环自减1。小于value输出1,大于value输出0,
static void timer1_func (unsigned long dev_num)
{
    struct irq_dev *dev = (struct irq_dev *)dev_num;
    int value = atomic_read(&dev->keyvalue);
    if(dev->period > value)
        gpio_set_value(dev->led_gpio, 0);
    else
        gpio_set_value(dev->led_gpio, 1);
    spin_lock(&dev->spinlock);     //加锁
    dev->period--;        //保护
    if(dev->period < 1){
        dev->period = 10;  //范围1-10
    }
    spin_unlock(&dev->spinlock);   //解锁
    //重启定时器
    mod_timer(&dev->timer[0], jiffies + msecs_to_jiffies(100));
}
//按键延时定时器10ms,keyvalue的值:0-10
static void timer2_func (unsigned long dev_num)
{
    struct irq_dev *dev = (struct irq_dev *)dev_num;
    //key1
    if(dev->keydesc[0].flag == 1)  //判断哪个按键
        if(gpio_get_value(dev->keydesc[0].gpio) == 0)
            if(atomic_inc_return(&dev->keyvalue) > 10)//value自加1,并返回判断
                atomic_set(&dev->keyvalue, 10);         //最大为10
    dev->keydesc[0].flag = 0; //标志清零
    //key2
    if(dev->keydesc[1].flag == 1)
        if(gpio_get_value(dev->keydesc[1].gpio) == 0)
            if(atomic_dec_return(&dev->keyvalue) < 0) //value自减1,并返回判断
                atomic_set(&dev->keyvalue, 0);         //最小为0
    dev->keydesc[1].flag = 0;
}

5.开、读、写函数

static int irq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &irqled;
    //初始化period,从10开始减,激活定时器
    mod_timer(&irqled.timer[0], jiffies + msecs_to_jiffies(100));
    irqled.period = 10;
    return 0;
}
static ssize_t irq_read (struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret, value;
    struct irq_dev *dev = filp->private_data;
    //获取原子变量,并输出到用户空间
    value = atomic_read(&dev->keyvalue);
    ret = copy_to_user(buf, &value, sizeof(value));
    return ret;
}
static ssize_t irq_write (struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int ret;
    unsigned char databuf[1];
    struct irq_dev *dev = filp->private_data;
    //取值
    ret = copy_from_user(databuf, buf, cnt);
    dev->timer[0].expires = jiffies + msecs_to_jiffies(100);
    if(ret < 0){
        printk("Error");
    }
    return 0;
}

6.ioctl函数

static long irq_unlocked_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct irq_dev *dev = filp->private_data;
    switch(cmd){
        case CLOSE_CMD: //关
            del_timer_sync(&dev->timer[0]);  //关定时器,并保存状态
            gpio_set_value(dev->led_gpio, 0); //关灯
            break;
        case OPEN_CMD: //开 10ms
            mod_timer(&dev->timer[0], jiffies + msecs_to_jiffies(100));
            break;
        case SET_CMD:
            if(arg > 10) //最大10
                atomic_set(&dev->keyvalue, 10);
            else if(arg < 0)  //最小0
                atomic_set(&dev->keyvalue, 0);
            else //中间数 直接赋值
                atomic_set(&dev->keyvalue, arg);
            break;
        default:
            break;
    }
    printk("ioctl success\r\n");
    return 0;
}

7.出口函数

static void __exit irq_exitt(void)
{
    del_timer_sync(&irqled.timer[0]);
    del_timer_sync(&irqled.timer[1]);
    free_irq(irqled.keydesc[0].irqnum, &irqled);
    free_irq(irqled.keydesc[1].irqnum, &irqled);
    cdev_del(&irqled.cdev);
    unregister_chrdev_region(irqled.devid, IRQ_CNT);
    device_destroy(irqled.class, irqled.devid);
    class_destroy(irqled.class);
}

三、测试

1.测试app

int main (int argc, char *argv[])
{
    int fd, ret, value;
    char *filename;
    unsigned char databuf[1];
    unsigned int cmd, arg;

    if(argc != 3){
        printf("error\r\n");
        return (-1);
    }
    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("error file");
        return -1;
    }

    databuf[0] = atoi(argv[2]);
    ret = write(fd, databuf, sizeof(databuf));
    if(ret < 0){
        printf("error write");
        close(fd);
        return -1;
    }

    while(1) {
        printf("Input CMD:\r\n");
        printf("1 is set; 2 is open, 3 is close, 4 is printf\r\n");
        ret = scanf("%d", &cmd);

        if(cmd == 3)  //关闭
            cmd = CLOSE_CMD;
        else if(cmd == 2)  //打开
            cmd = OPEN_CMD;
        else if(cmd == 1){//直接设置占空比
            cmd = SET_CMD;
            printf("input pwm 0-10\r\n");
            ret = scanf("%d",&arg);
        }
        else if(cmd == 4){ //打印占空比
            read(fd, &value, sizeof(value));
            printf("pwm is %d \r\n", value);
            }
        ioctl(fd, cmd, arg); //控制
    }

    close(fd);
    return 0;
}

2.编译测试

和前面一样

3.调试cat /var/log/message可以打印日志

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值