编写LED驱动-传统

Posted lianglianglu

tags:

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

写LED驱动 

首先,我们要建立一个file_operator结构体:

/* 定义自己的file_operations结构体                                              */
static struct file_operations led_drv = 
    .owner     = THIS_MODULE,       //主设备号
    .open    = led_drv_open,     // driver function
    .read    = led_drv_read,
    .write   = led_drv_write,
    .release = led_drv_close,
;

 

 接下来要配置GPIO的地址:

/*指定引脚的基地址 */
static unsigned int gpio_base[] = 
       0x56000000;      /*GPIOA*/
       0x56000010;      /*GPIOB*/
       .........;


// 这里是确定GPIOE的第n个引脚
#define IMX6ULL_GPE(n)    (5<<16 | n)

//查找GPIO基地址:

 

 

 

编写相应的四个driver function:

static volatile unsigned int *gpio_con;          // 将引脚配置为输出引脚
static volatile unsigned int *gpio_dat;          // 设置高低电平
static int led_pin = IMX6ULL_GPE(5);
/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)

    printk("%s %s line %d\\n", __FILE__, __FUNCTION__, __LINE__);
    return 0;


/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)

    int err;
    usigned char status;
  int pin = pin_bin * 0xffff; printk("%s %s line %d\\n", __FILE__, __FUNCTION__, __LINE__); err = copy_from_user(&status, buf, 1); // 将用户空间传入到值传入到kernel   if(err)
    return -1;
  
  if(status)
    /*点灯*/
    *gpio_data &= ~(1<<pin);
  else
    /*灭灯*/
    *gpio_data |= (1<<pin);
   
return 1; static int led_drv_open (struct inode *node, struct file *file)
  
  int bank =
led_pin >> 16;
  int bass = gpio_bas[bank]; // 这里不太清楚宏定义的意思
  int pin = led_pin & 0xFFFF;
  gpio_con = ioremap(bass, 8);       // 在bass基地址映射8(1024),size只映射空间的大小,这里是以page为单位,8<1024,也是一个page的大小
  gpio_dat = gpio_con + 1;
  *gpio_con |= (1<<pin*2); // 这里是相关寄存器配置,因为要配置GPIO5--[11:10]为output---01
    return 0;


static int led_drv_close (struct inode *node, struct file *file)

    printk("%s %s line %d\\n", __FILE__, __FUNCTION__, __LINE__);
  ioumap(gpio_con) return 0;

 

注册filer_operator: 告诉CPU

static int myled_init(void)

       major = register_chrdev(0, "myled", &myled_oprs);
       return 0;  


static void myled_exit(void)

        unregister_chrdev(major, "myled");

module_init(myled_init); //这里是为了告诉内核,入口函数是什么
module_exit(myled_exit);

 

 

 
 
« 上一篇: LED硬件基本知识
posted @ 2023-05-21 13:55  嵌入式小白—  阅读(1)  评论(0)  编辑  收藏  举报
 
 

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驱动代码编写

LED驱动代码编写

LED驱动代码编写

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

Linux LED 驱动开发实验