LED驱动代码编写

Posted Heavy sea

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LED驱动代码编写相关的知识,希望对你有一定的参考价值。

为了编写LED驱动程序,需要查看开发板的原理图,查看需要配置的引脚。这里LED驱动代码的编写基于100ask-imx6ull mini开发板。

一、查看原理图


由LED硬件图可以得知配置GPIO5_3 输出低电平,LED点亮,GPIO5_3输出高电平,LED熄灭。
具体的寄存器操作应该是怎样的?
1.使能寄存器,但有些芯片的GPIO是没有使能开关的,即它总是使能的。
2.选择引脚功能,一个引脚可以用于GPIO、串口、USB或其他功能,有对应的寄存器来选择引脚的功能。
3.对于已经设置为GPIO功能的引脚,有方向寄存器用来设置它的方向:输出、输入。
4.对于已经设置为GPIO功能的引脚,有数据寄存器用来写、读引脚电平状态。

下面通过查阅手册获取我们想要的寄存器有关信息:
我们想要获取的是寄存器的地址,需要配置哪个寄存器,配置寄存器相应位上的哪个值这些信息。
通过查阅《Chapter 18: Clock Controller Module (CCM)》中的CCM clock tree,获知使能GPIO5需要配置CCGR1寄存器中的CG15

二、查看芯片手册

1.使能寄存器


① 00:该GPIO模块全程被关闭
② 01:该GPIO模块在CPU run mode情况下是使能的;在WAIT或STOP模式下,关闭
③ 10:保留
④ 11:该GPIO模块全程使能


需要配置30和31位为1使能寄存器(默认使能,可以不配置)

2.配置功能选择寄存器

根据GPIO章节提供的框图可知需要去找IOMUXC配置相应寄存器

参考《Chapter 32: IOMUX Controller (IOMUXC)》去配置引脚功能
IOMUXC中有2个寄存器用来设置它,MUX_MODE是配置我们所需要的引脚功能,pad settings是设置上拉电阻这类,暂不用管它。

需要配置IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3寄存器,配置相应位为101.

3.配置方向寄存器

GPIOx_GDIR:设置引脚方向,每位对应一个引脚,1-output,0-input

根据需要要将GPIO5_GDIR第三位配置1,即让GPIO5_3 输出

4.数据寄存器

GPIOx_DR:设置输出引脚的电平,每位对应一个引脚,1-高电平,0-低电平

根据需求配置GPIO5_DR第三位为1或0

5.读寄存器

GPIOx_PSR:读取引脚的电平,每位对应一个引脚,1-高电平,0-低电平

以上的基地址参考《Chapter 2: memory map》

三、编写代码

1.驱动代码的编写

需要注意的是对于寄存器的物理地址需要先映射成虚拟地址,才能使用

编写驱动程序的套路:
① 确定主设备号,也可以让内核分配
② 定义自己的file_operations结构体
③ 实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体
④ 把file_operations结构体告诉内核:register_chrdev
⑤ 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
⑥ 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
⑦ 其他完善:提供设备信息,自动创建设备节点:class_create, device_create

驱动怎么操作硬件?通过ioremap映射寄存器的物理地址得到虚拟地址,读写虚拟地址。
驱动怎么和APP传输数据?通过copy_to_user、copy_from_user这2个函数。

因为编写的是字符设备驱动,可在source insight 的linux源码环境下光标定位到register_chrdev按住ctrl + / 来搜索相应源码来参考。

/*
*  led 驱动代码
*/

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <asm/io.h>

static int led_major;
static struct class *led_class;

// volatile 直接从变量地址中读取数据,不让编译器去优化
// 比如让GPIO5_DR先配置成0再配置成1,反映在硬件上是LED先亮后灭
// 但软件优化的结果可能是直接配置成1

static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
static volatile unsigned int *GPIO5_GDIR;
static volatile unsigned int *GPIO5_DR;

static ssize_t led_write(struct file *file, const char __user *buf,size_t count, loff_t * ppos)

    /* set gpio to led on */
    int ret;
    char val;

    ret = copy_from_user(&val,buf,1);
    if(val)
    
        *GPIO5_DR  &= ~(1<<3);
    else
    
        *GPIO5_DR  |= (1<<3);
    

    return 1;


static int led_open(struct inode *inode, struct file *file)

    /* 
     *  enable GPIO5  默认已经使能
     *  configure GPIO5_3 as gpio
     *  configure GPIO5_3 as OUTPUT
     */
     // 需要清零 因为这里需要配置成101,既有0,也有1
    *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
    *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5;

    *GPIO5_GDIR |= (1<<3);

    return 0;



static const struct file_operations led_fops =

	.owner		= THIS_MODULE,
	.write		= led_write,
	.open		= led_open,
;


static int __init led_init(void)
   
    printk("%s %s line:%d",__FILE__,__FUNCTION__,__LINE__);

    led_major = register_chrdev(0, "100ask_led", &led_fops);

    /* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 101 2290000h+ 14h = 2290014h
     * GPIO5_GDIR   020AC000 + 4h 3
     * GPIO5_DR     020AC000
     * 
     * ioremap 
     * 映射寄存器物理地址 得到虚拟地址
     * 
     */
    IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14,4);
    GPIO5_GDIR = ioremap(0x020AC000 + 0x04,4);
    GPIO5_DR = ioremap(0x020AC000,4);
    
    led_class = class_create(THIS_MODULE, "myled");
    device_create(led_class, NULL, MKDEV(led_major, 0),NULL, "myled");
    
    return 0;


static void __exit led_exit(void)
   
    iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
    iounmap(GPIO5_GDIR);
    iounmap(GPIO5_DR);
    
    device_destroy(led_class,MKDEV(led_major, 0));
    class_destroy(led_class);
    
    unregister_chrdev(led_major, "100ask_led");



module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");

2.测试代码的编写

打开设备节点 /dev/myled,写入0或1

/*
*  led 驱动测试代码       上层应用
*/

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

/*
*  ./led_drv_test /dev/myled on
*  ./led_drv_test /dev/myled off
*/

int main(int argc,char **argv)
   
    char status = 0;
    int fd;
    if(argc != 3)
    
        printf("Usage:%s /dev/myled on/off\\n",argv[0]);
        printf("eg:%s /dev/myled on\\n",argv[0]);
        printf("eg:%s /dev/myled off\\n",argv[0]);

        return -1;
    

    fd = open(argv[1],O_RDWR);
    if(fd<0)
    
        printf("can not open\\n");

        return -1;
    
    
    if(strcmp(argv[2],"on")==0)
    
        status = 1;
    
    else if(strcmp(argv[2],"off")==0)
    
        status = 0;
    

    write(fd,&status,1);
    
    return 0;


四、编译测试

Makefile(直接在工作目录下执行make,但注意相应的环境要配好)

KERN_DIR = /home/heavysea/100ask/100ask_imx6ull_mini-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o ledtest led_drv_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f ledtest

obj-m	+= led_drv.o


注意:工作目录文件名不要有多余的空格,否则执行会报错

调整文件名。执行成功

将.ko文件和测试程序拷贝到板子上运行,加载驱动进行测试

echo "7 4 1 7" > /proc/sys/kernel/printk    // 打开板子的内核打印信息

其他有关内容参考博文,不再赘述。

以上是关于LED驱动代码编写的主要内容,如果未能解决你的问题,请参考以下文章

LED驱动代码编写

LED驱动代码编写

迅为iTOP-RK3568开发板编写LED驱动

编写LED驱动

字符设备驱动程序--LED驱动

不写一行代码:实现安卓基于GPIO的LED设备驱动