找回密码
 立即注册

linux 驱动开发入门(一) led驱动开发

2024-11-21 20:51| 发布者: admin| 查看: 253| 评论: 0|来自: CSDN

摘要: 0.前言点灯在裸机上似乎非常容易,只需要在代码中按照手册配置一系列寄存器,编译后烧录即可。但是在上了系统的平台上,对于硬件的操作就需要遵循一定的规范。1.准备点灯无疑需要对芯片的GPIO进行操作。需要对芯片进 ...
0.前言
点灯在裸机上似乎非常容易,只需要在代码中按照手册配置一系列寄存器,编译后烧录即可。

但是在上了系统的平台上,对于硬件的操作就需要遵循一定的规范。

1.准备
点灯无疑需要对芯片的GPIO进行操作。需要对芯片进行操作,我们必须清楚使用的芯片型号以获取其官方的参考手册。

在运行平台上,使用

cat /proc/cpuinfo
查看cpu信息。 

如下所示,我使用的是 i.MX6 UltraLite,这样就可以去官网获得对应的手册了。

processor       : 0
model name      : ARMv7 Processor rev 5 (v7l)
BogoMIPS        : 3.00
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc07
CPU revision    : 5
 
Hardware        : Freescale i.MX6 UltraLite (Device Tree)
Revision        : 0000
Serial          : 0000000000000000
开发环境中也需要配置对应的工具链,完成诸如配置交叉编译工具、设置linux源码路径等工作,本文暂时不加赘述。

2.功能分析
对于不同的板级结构,操作led的逻辑也不同。我使用的led是共阳极的,所以点亮led需要将对应的GPIO置为低电平,连接led的GPIO为GPIO5_3.

查阅手册,我们需要做以下几个步骤才能使得GPIO置低:

1 使能对应GPIO的时钟

2 设置IO复用为GPIO功能

3 设置GPIO为输出模式

4 修改GPIO的数据寄存器,将对应位设置为0

3.驱动代码框架
该驱动为字符设备驱动,对于这类驱动,有较为统一的格式,入口函数、出口函数、设备号指定、文件操作函数、创建类与设备。

在linux中,一切皆为文件,对于设备的操作实际上就是一种文件操作。

我们需要定义一个文件操作结构体,其成员就是我们在对驱动文件进行对应操作时需要做的事情,即一个个函数。例如在应用程序使用open()打开驱动文件时,驱动程序会执行led_open()函数中的操作。

static struct file_operations led_fops ={
    .owner = THIS_MODULE,
    .write = led_write,
    .open = led_open,
};
那么我们就需要对函数进行具体的编写,使得驱动可以完成我们需要的事情:

这类函数的返回值、参数等内容可以参考其他字符驱动程序来进行编写。

static ssize_t led_write(struct file *filp,const char __user *buf,size_t count, loff_t *ppos){
    /*get data from app*/
    char val;
    copy_from_user(&val,buf,1);
    /*set gpio register output 1 or 0*/
    if(val ==  1){
        /*set gpio to make led on*/
        *GPIO5_DR &= 0x7;  //0111
    }
    else{
        /*set gpio to make led off*/
        *GPIO5_DR |= 0x8;   //1000
    }
    return 1;
}
static int led_open(struct inode *inode,struct file *filp){
    /*enable gpio
     configure pin as gpio mode
     configure gpio as output */
    //*CCM_CCGR1 default on
    *IOMUX &= ~0xf;//set as gpio mode
    *IOMUX |= 0x5;
    *GPIO5_GDIR |= (1<<3);//set the pin as output mode
    return 0;
}
其中,我对各类寄存器指针进行了定义,它们指向哪里需要参考你的芯片的手册。不过我们不能直接让他们指向物理地址,需要使用ioremap进行映射,让它们指向一个虚拟地址,我们在入口函数中进行。同时在入口函数中进行的还有注册设备、创建设备等操作:

static int __init led_init(void)
{
    printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);
    /*register driver*/
    major = register_chrdev(0,"chk_led",&led_fops);//
    /*map the virtual address*/
    /*ioremap*/
 
    IOMUX = ioremap(0x02290000 + 0x14,4);
    CCM_CCGR1 = ioremap(0x020c4000 + 0x6c,4);
    GPIO5_GDIR = ioremap(0x020ac004,4);
    GPIO5_DR = ioremap(0x020ac000,4);
 
 
 
    /*class create*/
    /*system will automatically create /dev/myled */
    led_class = class_create(THIS_MODULE,"my_led");
    device_create(led_class, NULL, MKDEV(major,0),NULL,"myled");
 
 
 
    return 0;
}
这样,驱动程序进入时就会对你需要的寄存器进行映射,并且注册设备号,在/dev/下创建驱动文件。

有入口肯定有出口函数,用于在卸载驱动时取消注册设备号,并取消io设备的虚拟映射,最后销毁设备和类。

static void __exit led_exit(void){
    iounmap(IOMUX);
    iounmap(CCM_CCGR1);
    iounmap(GPIO5_GDIR);
    iounmap(GPIO5_DR);
    unregister_chrdev(major,"chk_led");
    /*destory the device created before:/dev/myled */
    /*destory the class*/
    device_destroy(led_class,MKDEV(major,0));
    class_destroy(led_class);
 
   
}
完整的驱动代码如下:

/*
    A Simple LED Driver
    char device driver
    hardware structure: VDD_3V3 --->|LED2---- GPIO5_3
    Chip:IMX6UL
    Author: chk
 
Enable GPIO5 clock    <CCM_CCGR1> 0x020c4000 + 0x6c    0x020c406c 
Configure the pad as GPIO mode    <IOMUX>  0x02290000 + 0x14 
Configure the pad as output mode     <GPIO5_GDIR>  0x020ac004
Write the value to registers     <GPIO5_DR>    0x020ac000
     a driver:
0.major
1.file_operations
2.register_chrdev
3.entrance
4.exit
*/
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <asm/io.h>
#include <linux/uaccess.h>
#include <linux/device.h>
static int major;
static struct class *led_class; 
static volatile unsigned int *IOMUX;
static volatile unsigned int *CCM_CCGR1;
static volatile unsigned int *GPIO5_GDIR;
static volatile unsigned int *GPIO5_DR;
//you can not use the physical address directly, need to re map to a virtual address
 
static ssize_t led_write(struct file *filp,const char __user *buf,size_t count, loff_t *ppos){
    /*get data from app*/
    char val;
    copy_from_user(&val,buf,1);
    /*set gpio register output 1 or 0*/
    if(val ==  1){
        /*set gpio to make led on*/
        *GPIO5_DR &= 0x7;  //0111
    }
    else{
        /*set gpio to make led off*/
        *GPIO5_DR |= 0x8;   //1000
    }
    return 1;
}
static int led_open(struct inode *inode,struct file *filp){
    /*enable gpio
     configure pin as gpio mode
     configure gpio as output */
    //*CCM_CCGR1 default on
    *IOMUX &= ~0xf;//set as gpio mode
    *IOMUX |= 0x5;
    *GPIO5_GDIR |= (1<<3);//set the pin as output mode
    return 0;
}
 
static struct file_operations led_fops ={
    .owner = THIS_MODULE,
    .write = led_write,
    .open = led_open,
};
 
 
/*init function*/
 
static int __init led_init(void)
{
    printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);
    /*register driver*/
    major = register_chrdev(0,"chk_led",&led_fops);//
    /*map the virtual address*/
    /*ioremap*/
 
    IOMUX = ioremap(0x02290000 + 0x14,4);
    CCM_CCGR1 = ioremap(0x020c4000 + 0x6c,4);
    GPIO5_GDIR = ioremap(0x020ac004,4);
    GPIO5_DR = ioremap(0x020ac000,4);
 
 
 
    /*class create*/
    /*system will automatically create /dev/myled */
    led_class = class_create(THIS_MODULE,"my_led");
    device_create(led_class, NULL, MKDEV(major,0),NULL,"myled");
 
 
 
    return 0;
}
 
/*exit function*/
static void __exit led_exit(void){
    iounmap(IOMUX);
    iounmap(CCM_CCGR1);
    iounmap(GPIO5_GDIR);
    iounmap(GPIO5_DR);
    unregister_chrdev(major,"chk_led");
    /*destory the device created before:/dev/myled */
    /*destory the class*/
    device_destroy(led_class,MKDEV(major,0));
    class_destroy(led_class);
 
   
}
 
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
 
 
 
 
 
为了测试驱动程序,我们需要编写一个简单的应用程序来调用它:

//a test programme for my_led driver
//usage: ./ledtest /dev/myled on
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
 
 
int main(int argc,char **argv){
    int fd;
    char status = 0;
    
    if(argc != 3){
        printf("arg error\n");
        printf("usage eg: ./ledtest /dev/myled on");
        return -1;
    }
    fd = open(argv[1],O_RDWR);
    if(fd < 0){
        printf("can not open device %s\n",argv[1]);
        return -1;
    }
    
    if(strcmp(argv[2],"on") == 0){
        status = 1;
        
    }
    else{
        status = 0;
    }
    
    write(fd,&status,1);
 
 
 
 
    return 0;
}
应用程序中,调用open来打开驱动文件,用write来向内核空间传入值来控制寄存器。

编译的makefile如下,确保你已经配置好了环境变量:

KERN_DIR = your_path/Linux-4.9.88
 
all:
make -C $(KERN_DIR) M=$(shell pwd) modules
$(CROSS_COMPILE)gcc -o ledtest ledtest.c
 
 
clean:
make -C $(KERN_DIR) M=$(shell pwd) modules clean
rm -rf modules.order
rm -f ledtest
 
obj-m += led.o


路过

雷人

握手

鲜花

鸡蛋

相关分类

QQ|Archiver|手机版|小黑屋|软件开发编程门户 ( 陇ICP备2024013992号-1|甘公网安备62090002000130号 )

GMT+8, 2025-1-18 10:02 , Processed in 0.041373 second(s), 16 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

返回顶部