首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Linux驱动学习之第二个驱动程序(LED点亮关闭驱动程序-分层设计思想,使其能适应不同的板子-驱动程序模块为多个源文件怎么写Makefile)

  • 25-03-05 04:21
  • 3814
  • 7750
blog.csdn.net

目录

  • 看这篇博文前请先掌握下面这些博文中的知识
  • 需要的PDF资料
  • 完整源代码
    • board_fire_imx6ull-pro.c中的代码
    • leddrv.c中的代码
    • ledtest.c中的代码
  • 程序设计思想和文件结构
  • 实现分层思想的具体方法
    • 概述
    • 具体实现分析
      • 定义结构体`led_operations`用来集合各个单板硬件层面操作LED的函数
      • 定义具体的某个单板的操作LED的各个具体函数,并作为结构体`led_operations`的一个实例的成员函数
      • 定义函数`get_board_led_opr`返回结构体`led_operations`的一个实例的指针
      • 上层文件(leddrv.c)的模块初始化函数中调用函数`get_board_led_opr`获得单板的底层操作实例
      • 在驱动程序中去调用底层函数对硬件的操作
  • 本篇博文略去对驱动程序代码的分析,重点分析底层对硬件操作的代码
  • 驱动程序代码虽然略去分析,但是模块初始化函数中注册驱动程序的方法和之前的有所不同【函数register_chrdev()的介绍】
    • register_chrdev函数原型
    • 参数详解
    • 返回值
    • 示例代码
    • 卸载驱动时对应的函数
    • 小结
  • 在硬件层面操作的代码的分析(`board_fire_imx6ull-pro.c`中的代码)
    • 定义用于存储寄存器映射地址值的指针
      • 关键字`volatile`的含义
    • LED控制初始化操作函数`board_demo_led_init`分析
      • 源代码
      • `ioremap`函数分析
      • GPIO操作的第01步:使能对应GPIO口的时钟(CCGR寄存器)
      • GPIO操作的第02步:设置对应的GPIO口引脚为GPIO功能,而不是别的复用功能【IOMUXC相关的寄存器】
      • GPIO操作的第03步:设置对应的GPIO口为输出引脚(IO口中的Output)【GDIR寄存器】
      • GPIO操作的第04步:找到对应的GPIO口的数据寄存器并映射到虚拟地址空间【(GPIOx_DR寄存器】
    • LED控制函数`board_demo_led_ctl`分析
    • LED控制的关闭函数(释放内存映射)
  • Makfile文件的编写
  • 交叉编译
  • 上板测试
  • 附工程压缩文件和PDF文档

看这篇博文前请先掌握下面这些博文中的知识

http://iyenn.com/rec/1709451.html
http://iyenn.com/rec/1709535.html
http://iyenn.com/rec/1709540.html

需要的PDF资料

开发板的原理图文件:
https://pan.baidu.com/s/1CEwPbcNUIicA1HVGwG6e5A?pwd=m9wb
IMX6ULL-CPU芯片的操作手册:
https://pan.baidu.com/s/1hZWmgmvYAA9mvzAHsPiw4g?pwd=eiuh

完整源代码

board_fire_imx6ull-pro.c中的代码

#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "led_opr.h"

static volatile unsigned int *CCM_CCGR1                              ;
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 int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */       
{
    unsigned int val;

    //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
    if (which == 0)
    {
        if (!CCM_CCGR1)
        {
            CCM_CCGR1                               = ioremap(0x20C406C, 4);
            IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
            GPIO5_GDIR                              = ioremap(0x020AC000 + 0x4, 4);
            GPIO5_DR                                = ioremap(0x020AC000 + 0, 4);
        }
        
        /* GPIO5_IO03 */
        /* a. 使能GPIO5的时钟信号
         * set CCM to enable GPIO5
         * CCM_CCGR1[CG15] 0x20C406C
         * bit[31:30] = 0b11
         */
        *CCM_CCGR1 |= (3<<30);
        
        /* b. 设置GPIO5_IO03用于GPIO
         * set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
         *      to configure GPIO5_IO03 as GPIO
         * IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3  0x2290014
         * bit[3:0] = 0b0101 alt5
         */
        val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
        val &= ~(0xf);
        val |= (5);  //5的二进制表示为0101
        *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
        
        
        /* b. 设置GPIO5_IO03作为output引脚
         * set GPIO5_GDIR to configure GPIO5_IO03 as output
         * GPIO5_GDIR  0x020AC000 + 0x4
         * bit[3] = 0b1
         */
        *GPIO5_GDIR |= (1<<3);
    }
    
    return 0;
}

static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{
    //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
    if (which == 0)
    {
        if (status) /* on: output 0*/
        {
            /* d. 设置GPIO5_DR输出低电平
             * set GPIO5_DR to configure GPIO5_IO03 output 0
             * GPIO5_DR 0x020AC000 + 0
             * bit[3] = 0b0
             */
            *GPIO5_DR &= ~(1<<3);
        }
        else  /* off: output 1*/
        {
            /* e. 设置GPIO5_IO3输出高电平
             * set GPIO5_DR to configure GPIO5_IO03 output 1
             * GPIO5_DR 0x020AC000 + 0
             * bit[3] = 0b1
             */ 
            *GPIO5_DR |= (1<<3);
        }
    
    }
    return 0;
}

static int board_demo_led_close(int which) /* whice表示具体是要操作哪个LED*/
{
    if (which == 0)
    {
        if (CCM_CCGR1) {
            iounmap(CCM_CCGR1);
            CCM_CCGR1 = NULL;
        }
        if (IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3) {
            iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
            IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = NULL;
        }
        if (GPIO5_GDIR) {
            iounmap(GPIO5_GDIR);
            GPIO5_GDIR = NULL;
        }
        if (GPIO5_DR) {
            iounmap(GPIO5_DR);
            GPIO5_DR = NULL;
        }
    }
    
    return 0;
}


static struct led_operations board_demo_led_opr = {
    .num  = 1,
    .init = board_demo_led_init,
    .ctl  = board_demo_led_ctl,
    .close = board_demo_led_close,
};

struct led_operations *get_board_led_opr(void)
{
    return &board_demo_led_opr;
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138

leddrv.c中的代码

#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include "led_opr.h"


/* 1. 确定主设备号                                                                 */
static int major = 0;
static struct class *led_class;
struct led_operations *p_led_opr;


#define MIN(a, b) (a < b ? a : b)

/* 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;
	char status;
	struct inode *inode = file_inode(file);
	int minor = iminor(inode);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根据次设备号和status控制LED */
	p_led_opr->ctl(minor, status);
	
	return 1;
}

static int led_drv_open (struct inode *node, struct file *file)
{
	int minor = iminor(node);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根据次设备号初始化LED */
	p_led_opr->init(minor);
	
	return 0;
}

static int led_drv_close (struct inode *node, struct file *file)
{
	int minor = iminor(node);

	int err;
	/* 根据次设备号解除相关LED的寄存器映射关系 */
	err = p_led_opr->close(minor);
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	printk(KERN_INFO "LED driver: resources released.\n");
	return 0;
}

/* 2. 定义自己的file_operations结构体                                              */
static struct file_operations led_drv = {
	.owner	 = THIS_MODULE,
	.open    = led_drv_open,
	.read    = led_drv_read,
	.write   = led_drv_write,
	.release = led_drv_close,
};

/* 4. 把file_operations结构体告诉内核:注册驱动程序                                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{
	int err;
	int i;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "imx6ull_led", &led_drv);  /* /dev/led */


	led_class = class_create(THIS_MODULE, "imx6ull_led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led");
		return -1;
	}

	p_led_opr = get_board_led_opr();

	for (i = 0; i < p_led_opr->num; i++)
		device_create(led_class, NULL, MKDEV(major, i), NULL, "imx6ull_led%d", i); /* /dev/imx6ull_led0,1,... */

	
	return 0;
}

/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
static void __exit led_exit(void)
{
	int i;
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	for (i = 0; i < p_led_opr->num; i++)
		device_destroy(led_class, MKDEV(major, i)); /* /dev/100ask_led0,1,... */

	class_destroy(led_class);

	unregister_chrdev(major, "imx6ull_led");
}


/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134

ledtest.c中的代码


#include 
#include 
#include 
#include 
#include 
#include 

/*
 * ./ledtest /dev/100ask_led0 on
 * ./ledtest /dev/100ask_led0 off
 */
int main(int argc, char **argv)
{
	int fd;
	char status;
	
	/* 1. 判断参数 */
	if (argc != 3) 
	{
		printf("Usage: %s  \n", argv[0]);
		return -1;
	}

	/* 2. 打开文件 */
	fd = open(argv[1], O_RDWR);
	if (fd == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	/* 3. 写文件 */
	if (0 == strcmp(argv[2], "on"))
	{
		status = 1;
		write(fd, &status, 1);
	}
	else
	{
		status = 0;
		write(fd, &status, 1);
	}
	
	close(fd);
	
	return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

程序设计思想和文件结构

希望写出的程序能给分层,即把真正对各单板的LED操作的实现最为最底层,然后有个中间层,是驱动程充中各单板实现LED操作都需要的代码,这样将来有个新板子只需要修改下最底层的代码就能用了。比如下面的工程文件结构:
在这里插入图片描述
以board为前缀的文件都是每个单板具体的对LED操作的底层代码,然后中间层代码放在文件leddrv.c中。
IMX6ULL开发板的底层代码在文件board_fire_imx6ull-pro.c中。
测试程序代码放在ledtest.c中。

实现分层思想的具体方法

概述

每个单板都有各自的LED控制的在硬件层面上的初始化函数、控制函数、释放(关闭)函数,我们只需要把这些函数集合到我们自定义的名为led_operations结构体中,然后在驱动程序的文件操作结构体(file_operations 结构体)中的open、read、write、release函数中去调用led_operations结构体实例中对应的函数即可,用led_operations结构体的一个实例就相当于是一个单板。

具体实现分析

定义结构体led_operations用来集合各个单板硬件层面操作LED的函数

首先在led_opr.h中定义一个名为led_operations结构体,用于存储各个单板具体的LED的硬件层面的操作函数,代码如下:

struct led_operations {
	int num; /* 表示有几个LED,实际上也是LED的 */
	int (*init) (int which); /* 初始化LED, which-哪个LED */
	int (*ctl) (int which, char status); /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
	int (*close) (int which); /* 主要是对映射的寄存器作解除映射操作 */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

定义具体的某个单板的操作LED的各个具体函数,并作为结构体led_operations的一个实例的成员函数

然后在文件board_fire_imx6ull-pro.c中定义具体的IMX6ULL开发板操作LED的硬件层面的函数,然后把这些函数作为led_operations结构体的一个实例的成员。相关代码如下:

static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */       
{
    unsigned int val;

    //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
    if (which == 0)
    {
        if (!CCM_CCGR1)
        {
            CCM_CCGR1                               = ioremap(0x20C406C, 4);
            IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
            GPIO5_GDIR                              = ioremap(0x020AC000 + 0x4, 4);
            GPIO5_DR                                = ioremap(0x020AC000 + 0, 4);
        }
        
        /* GPIO5_IO03 */
        /* a. 使能GPIO5
         * set CCM to enable GPIO5
         * CCM_CCGR1[CG15] 0x20C406C
         * bit[31:30] = 0b11
         */
        *CCM_CCGR1 |= (3<<30);
        
        /* b. 设置GPIO5_IO03用于GPIO
         * set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
         *      to configure GPIO5_IO03 as GPIO
         * IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3  0x2290014
         * bit[3:0] = 0b0101 alt5
         */
        val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
        val &= ~(0xf);
        val |= (5);
        *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
        
        
        /* b. 设置GPIO5_IO03作为output引脚
         * set GPIO5_GDIR to configure GPIO5_IO03 as output
         * GPIO5_GDIR  0x020AC000 + 0x4
         * bit[3] = 0b1
         */
        *GPIO5_GDIR |= (1<<3);
    }
    
    return 0;
}

static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{
    //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
    if (which == 0)
    {
        if (status) /* on: output 0*/
        {
            /* d. 设置GPIO5_DR输出低电平
             * set GPIO5_DR to configure GPIO5_IO03 output 0
             * GPIO5_DR 0x020AC000 + 0
             * bit[3] = 0b0
             */
            *GPIO5_DR &= ~(1<<3);
        }
        else  /* off: output 1*/
        {
            /* e. 设置GPIO5_IO3输出高电平
             * set GPIO5_DR to configure GPIO5_IO03 output 1
             * GPIO5_DR 0x020AC000 + 0
             * bit[3] = 0b1
             */ 
            *GPIO5_DR |= (1<<3);
        }
    
    }
    return 0;
}

static int board_demo_led_close(int which) /* whice表示具体是要操作哪个LED*/
{
    if (which == 0)
    {
        if (CCM_CCGR1) {
            iounmap(CCM_CCGR1);
            CCM_CCGR1 = NULL;
        }
        if (IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3) {
            iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
            IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = NULL;
        }
        if (GPIO5_GDIR) {
            iounmap(GPIO5_GDIR);
            GPIO5_GDIR = NULL;
        }
        if (GPIO5_DR) {
            iounmap(GPIO5_DR);
            GPIO5_DR = NULL;
        }
    }
    
    return 0;
}


static struct led_operations board_demo_led_opr = {
    .num  = 1,
    .init = board_demo_led_init,
    .ctl  = board_demo_led_ctl,
    .close = board_demo_led_close,
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106

定义函数get_board_led_opr返回结构体led_operations的一个实例的指针

最后,提供一个实例化结构体board_demo_led_opr的函数供上层代码调用,从而获得一个单板LED操作实例:

struct led_operations *get_board_led_opr(void)
{
    return &board_demo_led_opr;
}
  • 1
  • 2
  • 3
  • 4

注意:这个函数需要在头文件中声明,因为它是在文件leddrv.c中被调用的。
在这里插入图片描述

上层文件(leddrv.c)的模块初始化函数中调用函数get_board_led_opr获得单板的底层操作实例

在上层文件(leddrv.c)的模块初始化函数中调用函数get_board_led_opr获得单板的底层操作的led_operations结构体的实例的指针,比如下面这句代码:

p_led_opr = get_board_led_opr();
  • 1

在这里插入图片描述

在驱动程序中去调用底层函数对硬件的操作

在驱动程序的文件操作结构体(struct file_operations led_drv)的成员函数中去调用底层的对硬件的操作,说句题外话对底层硬件的操作其实就是对寄存器的操作,比如下面的代码:

static int led_drv_open (struct inode *node, struct file *file)
{
	int minor = iminor(node);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 根据次设备号初始化LED */
	p_led_opr->init(minor);
	
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在这里插入图片描述

static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	struct inode *inode = file_inode(file);
	int minor = iminor(inode);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根据次设备号和status控制LED */
	p_led_opr->ctl(minor, status);
	
	return 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

在这里插入图片描述

static int led_drv_close (struct inode *node, struct file *file)
{
	int minor = iminor(node);

	int err;
	/* 根据次设备号解除相关LED的寄存器映射关系 */
	err = p_led_opr->close(minor);
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	printk(KERN_INFO "LED driver: resources released.\n");
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述

本篇博文略去对驱动程序代码的分析,重点分析底层对硬件操作的代码

本篇博文略去对驱动程序代码的分析,因为在前面的三篇博文中我已经把Linux驱动程序的框架和结构搞得比较清楚了,具体是下面三篇博文。
http://iyenn.com/rec/1709451.html
http://iyenn.com/rec/1709535.html
http://iyenn.com/rec/1709540.html
所以,本文重点去关注硬件层面的实现,也就是文件board_fire_imx6ull-pro.c中的代码。

驱动程序代码虽然略去分析,但是模块初始化函数中注册驱动程序的方法和之前的有所不同【函数register_chrdev()的介绍】

led_init函数的代码如下:

static int __init led_init(void)
{
	int err;
	int i;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "imx6ull_led", &led_drv);  /* /dev/led */


	led_class = class_create(THIS_MODULE, "imx6ull_led_class");
	err = PTR_ERR(led_class);
	if (IS_ERR(led_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "led");
		return -1;
	}

	p_led_opr = get_board_led_opr();

	for (i = 0; i < p_led_opr->num; i++)
		device_create(led_class, NULL, MKDEV(major, i), NULL, "imx6ull_led%d", i); /* /dev/imx6ull_led0,1,... */

	
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

从中我们可以看出,在整个设备的注册过程中,并没有用到博文 http://iyenn.com/rec/1709451.html中提到的函数alloc_chrdev_region、cdev_init、cdev_add,也没有像博文http://iyenn.com/rec/1709540.html那样去使用函数alloc_chrdev_region、cdev_init、cdev_add,而是使用了快速注册函数register_chrdev完成这几个函数的功能,下面是其详细介绍:

register_chrdev 是 Linux 驱动程序开发中用于注册字符设备的函数。它的主要作用是向内核注册一个字符设备,从而允许用户空间程序通过设备文件与驱动程序交互。

register_chrdev函数原型

int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
  • 1

参数详解

  1. major
    指定设备的主设备号。如果为 0,内核会自动分配一个主设备号,并返回分配的值。主设备号用于区分设备的类别。

  2. name
    主设备号对应的驱动程序的名称,用于标识主设备号(调试时使用,内核实际上没有用到)。可以通过 /proc/devices 文件查看注册的驱动程序和对应的主设备号,相当于rc = alloc_chrdev_region(&devid, 0, 1, "hello");中的第4个参数 "hello"。

  3. fops
    指向 struct file_operations 的指针,用于定义该字符设备支持的操作集。例如,open、read、write 等函数。

返回值

  • 如果成功,返回分配的主设备号(major 参数)。
  • 如果失败,返回一个负数错误码。

示例代码

int major;
struct file_operations led_drv = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .write = led_write,
};

major = register_chrdev(0, "imx6ull_led", &led_drv);
if (major < 0) {
    printk(KERN_ERR "Failed to register device: %d\n", major);
    return major;
}
printk(KERN_INFO "Registered device with major number %d\n", major);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

解释

  1. register_chrdev(0, "imx6ull_led", &led_drv)

    • 0: 让内核自动分配一个主设备号。
    • "imx6ull_led": 驱动程序名称(主设备号用来找到驱动程序),便于识别。
    • &led_drv: 定义字符设备操作的函数指针集合。
  2. struct file_operations led_drv
    包含设备的操作方法,例如 open、release 和 write。这些方法在应用程序访问设备时被调用。

  3. 返回的主设备号
    如果注册成功,major 变量会保存分配的主设备号,可以用于创建设备节点。例如:

    mknod /dev/imx6ull_led c <major> 0
    
    • 1
  4. 错误检查
    如果注册失败,打印错误信息并返回错误码。


卸载驱动时对应的函数

在卸载驱动时,需要调用 unregister_chrdev 注销设备:

unregister_chrdev(major, "imx6ull_led");
  • 1

小结

register_chrdev 是字符设备驱动程序中快速注册设备的方法。尽管较为简单,但在复杂场景中可能不够灵活,因此通常在学习或简单驱动开发中使用,推荐更复杂的场景使用 cdev 和 alloc_chrdev_region。

在硬件层面操作的代码的分析(board_fire_imx6ull-pro.c中的代码)

定义用于存储寄存器映射地址值的指针

static volatile unsigned int *CCM_CCGR1                              ;
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
static volatile unsigned int *GPIO5_GDIR                             ;
static volatile unsigned int *GPIO5_DR                               ;
  • 1
  • 2
  • 3
  • 4

关键字volatile的含义

这些定义里面首先要理解关键字volatile的含义,详细解释请参见我的另一篇博文:
http://iyenn.com/rec/1709541.html

LED控制初始化操作函数board_demo_led_init分析

源代码

static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */       
{
    unsigned int val;

    //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
    if (which == 0)
    {
        if (!CCM_CCGR1)
        {
            CCM_CCGR1                               = ioremap(0x20C406C, 4);
            IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
            GPIO5_GDIR                              = ioremap(0x020AC000 + 0x4, 4);
            GPIO5_DR                                = ioremap(0x020AC000 + 0, 4);
        }
        
        /* GPIO5_IO03 */
        /* a. 使能GPIO5
         * set CCM to enable GPIO5
         * CCM_CCGR1[CG15] 0x20C406C
         * bit[31:30] = 0b11
         */
        *CCM_CCGR1 |= (3<<30);
        
        /* b. 设置GPIO5_IO03用于GPIO
         * set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
         *      to configure GPIO5_IO03 as GPIO
         * IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3  0x2290014
         * bit[3:0] = 0b0101 alt5
         */
        val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
        val &= ~(0xf);
        val |= (5);
        *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
        
        
        /* b. 设置GPIO5_IO03作为output引脚
         * set GPIO5_GDIR to configure GPIO5_IO03 as output
         * GPIO5_GDIR  0x020AC000 + 0x4
         * bit[3] = 0b1
         */
        *GPIO5_GDIR |= (1<<3);
    }
    
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

ioremap函数分析

相关代码如下:

CCM_CCGR1                               = ioremap(0x20C406C, 4);
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
GPIO5_GDIR                              = ioremap(0x020AC000 + 0x4, 4);
GPIO5_DR                                = ioremap(0x020AC000 + 0, 4);
  • 1
  • 2
  • 3
  • 4

这些代码显然就是把寄存器的物理地址映射到Linux的虚拟地址中,在前面的博文
IMX6ULL开发板基础实验:Framebuffer驱动程序的简单应用实例代码详细分析中,用到了函数mmap,也是把地址映射到虚拟地址,不同的是mmap映射的是设备的内存,而这里咱们需要映射的是CPU的物理地址。
对于函数mmap的讲解详见我的另一篇博文
嵌入式Linux开发中的内存映射函数mmap详解?什么叫内存映射?什么叫对设备的显式和隐式读写操作?什么叫程序的虚拟内存?

对这里遇到的函数 ioremap()的详细分析,见博文 http://iyenn.com/rec/1709542.html

GPIO操作的第01步:使能对应GPIO口的时钟(CCGR寄存器)

GPIO操作的第01步例是使能对应GPIO口的时钟。

相关的控制模块是CCM(Clock Controller Module-时钟控制模块) 。

首先我们要知道我们要操作哪个GPIO口。

从下面这幅开发板的原理图截图我们可以知道:
在这里插入图片描述
我们需要操作的对象是第5组GPIO口的第3个GPIO口,即GPIO5_IO03,别人的经验告诉我们GPIO的时钟信号由CCM(Clock Controller Module)寄存器控制,所以去IMX6ULL的的操作手册中去查找相关的CCM(Clock Controller Module)寄存器,不妨直接在手册中搜索“gpio5”,发现有下面的内容:
在这里插入图片描述
点击一看,发现正是CCM模块中的内容:
在这里插入图片描述
上面红框中的内容显示GPIO5的时钟控制为CCGR1[CG15],即CCGR1寄存器的CG15位,然后我们就去查CCGR1寄存器,先搜索PDF左边的书签:
在这里插入图片描述
通过PDF的书签搜索到了,点击查看:
在这里插入图片描述
从这幅截图中我们可以看出,CG15位是指CCGR1寄存器的第30位和31位,其初始值为11,但在Description描述中却为Reserved,Reserved的意思是保留未用的意思,那这样看来,这个操作手册有点前后矛盾了,可能是文档的疏漏吧,别人的经验告诉我们,其实这两位就是控制GPIO5的时钟的,具体意义如下:

  • 00: 时钟关闭。
  • 01: 时钟仅在硬件需要时使能。
  • 10: 时钟保留,通常不用。
  • 11: 时钟始终使能。

这里它的Reset值为11,说明它默认就是使能的,所以其实这里不需要设置,不过为了程序的健壮性和兼容性,有可能这个系列别的CPU需要设置嘛,所以不忍这里也设置一下其值。

从上面这幅截图我们还可以看出,CCGR1的物理地址为20C_406Ch,即20C406C,这样我们就获得了其物理地址,所以便可以写出下面的代码了:

CCM_CCGR1 = ioremap(0x20C406C, 4);

/* GPIO5_IO03 */
/* a. 使能GPIO5的时钟信号
 * set CCM to enable GPIO5
 * CCM_CCGR1[CG15] 0x20C406C
 * bit[31:30] = 0b11
 */
*CCM_CCGR1 |= (3<<30);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

注意:*CCM_CCGR1 |= (3<<30);只设置了31位和32位为1,寄存器别的位的值没有变 ,这个或运算表达式应该是比较好懂的。

GPIO操作的第02步:设置对应的GPIO口引脚为GPIO功能,而不是别的复用功能【IOMUXC相关的寄存器】

在这里,我们需要设置GPIO5_IO03,即第5组GPIO口的第3个引脚为输出功能。

别人的经验告诉我们,这涉及到 IOMUXC(I/O Multiplex Controller),所以去书签中搜索IOMUXC
在这里插入图片描述
可见是第32章的内容,所以就浏览这一章的内容,然后浏览起来才一个麻烦,不妨借助搜索引擎或chatgpt,找到最有可能的寄存器,然后去操作手册中去确认。

问chatgpt:
我想去控制IMX6ULL的GPIO5的第3个引脚为GPIO功能,该设置IOMUXC中的哪个寄存器?

回答:
在 IMX6ULL 中,控制 GPIO5 的第 3 个引脚(GPIO5_IO03) 为 GPIO 功能时,需要设置 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 寄存器。


寄存器名称与地址

  • 寄存器名称:IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
  • 寄存器地址:0x2290014

寄存器功能说明
该寄存器用于控制引脚 SNVS_TAMPER3(复用为 GPIO5_IO03)的复用功能。寄存器的低 4 位(bit[3:0])决定该引脚的功能。


复用模式设置
为了将引脚配置为 GPIO 功能,需要将低 4 位设置为 0101(ALT5 模式):

  • 低 4 位值说明:
    • 0000(ALT0):复用为主功能(例如某外设)。
    • 0101(ALT5):复用为 GPIO 功能。

chatgpt说是寄存器IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3的低 4 位(bit 3:0)决定该引脚的功能,并且说这个寄存器的物理地址为0x2290014。

我们可以搜索TAMPER3,也可以按操作手册的格式搜索地址229_0014h,去找到操作手册中对应的内容(从下面的截图来看,还是搜索地址是最精确的):
在这里插入图片描述
对应的内容为:
在这里插入图片描述
注意,这个寄存器的第3位SION的意义如下:
在 IMX6ULL 的 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 寄存器中,第3 位(SION)用于控制引脚的输入路径是否始终有效。
以下是详细说明:


SION 位的作用

  1. 名称:

    • SION: Software Input On
  2. 位置:

    • 寄存器的第 3 位(从 0 开始计数)。
  3. 功能描述:

    • 0:正常模式(默认值)。
      • 引脚的输入路径根据复用功能(MUX_MODE)的设置和引脚状态进行控制。
    • 1:强制输入模式。
      • 无论复用功能(MUX_MODE)的设置如何,始终保持引脚的输入路径打开。
  4. 影响:

    • 在复用为 GPIO 功能时,如果引脚仅用于输出,可以关闭输入路径(即 SION = 0),以节省功耗。
    • 如果引脚需要作为输入或具有输入功能(如读取状态、实现中断等),需要打开输入路径(即 SION = 1)。

SION 位的使用场景

  • GPIO 作为输入引脚:
    如果引脚配置为 GPIO 输入,或者需要读取引脚的状态,必须将 SION 设置为 1,确保输入路径打开。

  • GPIO 作为输出引脚:
    如果引脚仅用作 GPIO 输出,可以将 SION 设置为 0,避免不必要的输入路径开销。

  • 复用其他功能时:

    • 某些复用功能(如 UART、I2C 等)可能需要强制打开输入路径,即设置 SION = 1。
    • 如果复用功能本身不需要输入,通常可以关闭 SION(设置为 0)。

从上面的截图中可以看出,chatgpt是对的,所以可以写出下面的代码:

IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);

/* b. 设置GPIO5_IO03用于GPIO
 * set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
 *      to configure GPIO5_IO03 as GPIO
 * IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3  0x2290014
 * bit[3:0] = 0b0101 alt5
 */
val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
val &= ~(0xf);
val |= (5); //5的二进制表示为0101
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

其中val &= ~(0xf);的作用是清零低 4 位,防止之前的值对当前配置产生干扰。
其清零低4位的详细过程如下

  1. 位操作分析:

    • 0xf 的二进制表示是 0000 1111,表示低 4 位全为 1。
    • ~(0xf) 进行按位取反后,结果是 1111 0000,即低 4 位全为 0,其他位全为 1。
  2. 与运算(&):

    • val &= ~(0xf); 等价于 val = val & ~(0xf);
    • 这会将 val 的低 4 位强制清零,同时保留其他位的原始值。
  3. 效果:
    假设 val 的原始值为 0b1011_1111,执行 val &= ~(0xf); 后:

    原始值: 1011 1111
    ~(0xf): 1111 0000
    按位与: 1011 0000
    
    • 1
    • 2
    • 3

    结果是 0b1011_0000,即低 4 位清零,其他位保持不变。

GPIO操作的第03步:设置对应的GPIO口为输出引脚(IO口中的Output)【GDIR寄存器】

还是按第02步中的方法,直接先问chatgpt,然后再去手册中查阅。

问:已经设置好了IMX6ULL的GPIO5的第3个引脚为GPIO功能,现在想把它设为output,该设置哪个寄存器,怎么设置?

在 IMX6ULL 中,将 GPIO5 的第 3 个引脚(GPIO5_IO03) 配置为 输出,需要设置 GPIO5_GDIR(GPIO Direction Register) 寄存器。


寄存器名称与地址

  • 寄存器名称:GPIO5_GDIR
  • 寄存器地址:0x020AC004(GPIO5 基地址 0x020AC000 + 偏移 0x4)

寄存器功能说明

  1. GDIR(GPIO Direction Register):

    • 每个 GPIO 引脚都有一个对应的方向位。
    • 位值:
      • 0:输入(Input)。
      • 1:输出(Output)。
  2. GPIO5_IO03 对应的位:

    • GPIO5 的第 3 个引脚对应 GDIR 寄存器的 第 3 位(bit[3])。

所以我们去操作手册中搜索20A_C004h,结果没有搜索到结果…

那就去搜索GPIO5_GDIR,有一个结果:
在这里插入图片描述
在这里插入图片描述
从上需截图可以看出,其地址为20AC004,所以以后我们第一次搜索地址的时候如果找不到结果,不妨去掉最后的h再试
最后面的蓝色的字表明了详细介绍是在多少页,我们可以看到是在1359页。
在这里插入图片描述
这个介绍是个通用的介绍,从中我们可以看出你要操作某个组的GPIO的第几个GPIO口,就设置哪个位,比如我要操作GPIO5的第3位,我就去设置GPIO5_GDIR的第3位就行了,当值为0时是输入口,当值为1时为输出口。
据此,可以写出代码如下:

GPIO5_GDIR = ioremap(0x020AC000 + 0x4, 4);

/* b. 设置GPIO5_IO03作为output引脚
 * set GPIO5_GDIR to configure GPIO5_IO03 as output
 * GPIO5_GDIR  0x020AC000 + 0x4
 * bit[3] = 0b1
 */
*GPIO5_GDIR |= (1<<3);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

GPIO操作的第04步:找到对应的GPIO口的数据寄存器并映射到虚拟地址空间【(GPIOx_DR寄存器】

先要找到目标寄存器,还是先问chatgpt,然后去应证。

问:现在我把GPIO5_IO03已经设为输出引脚了,我到哪个寄存器中去设置它的输出值?

在 IMX6ULL 中,GPIO5_IO03 配置为输出引脚后,设置其输出值需要操作 GPIO5 的数据寄存器 GPIO5_DR(GPIO Data Register)。


GPIO5_DR 寄存器

  1. 寄存器名称:GPIO5_DR
  2. 寄存器地址:0x020AC000(GPIO5 基地址 0x020AC000)。
  3. 功能描述:
    • 用于设置 GPIO 引脚的输出值。
    • 每个 GPIO 引脚对应寄存器中的一位:
      • 0:引脚输出低电平。
      • 1:引脚输出高电平。
    • 对于 GPIO5_IO03,对应 GPIO5_DR 的第 3 位(bit[3])。

设置 GPIO 输出值的方法

  1. 将引脚输出低电平:

    • 清除 GPIO5_DR 的第 3 位(bit[3])。
      *GPIO5_DR &= ~(1 << 3);
      
      • 1
  2. 将引脚输出高电平:

    • 设置 GPIO5_DR 的第 3 位(bit[3])。
      *GPIO5_DR |= (1 << 3);
      
      • 1

完整代码示例
以下是将 GPIO5_IO03 输出高电平或低电平的代码:

#define GPIO5_BASE  (0x020AC000)   // GPIO5 基地址
#define GPIO5_DR    (GPIO5_BASE + 0x0) // GPIO5_DR 寄存器地址

void set_gpio5_io03_output_value(int value)
{
    volatile unsigned int *dr;
    unsigned int val;

    // 映射 GPIO5_DR 寄存器
    dr = ioremap(GPIO5_DR, 4);
    if (!dr) {
        printk("Failed to ioremap GPIO5_DR register\n");
        return;
    }

    // 读取当前 GPIO5_DR 的值
    val = readl(dr);

    if (value) {
        // 设置 GPIO5_IO03 输出高电平 (bit[3] = 1)
        val |= (1 << 3);
    } else {
        // 设置 GPIO5_IO03 输出低电平 (bit[3] = 0)
        val &= ~(1 << 3);
    }

    // 写回 GPIO5_DR 寄存器
    writel(val, dr);

    // 解除映射
    iounmap(dr);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

注意事项

  1. 引脚功能:

    • 确保 GPIO5_IO03 已经通过 IOMUXC 配置为 GPIO 功能。
    • 确保 GPIO5_IO03 的方向已设置为输出(通过 GPIO5_GDIR 配置)。
  2. 输出控制的实际效果:

    • 输出低电平:物理引脚拉低,输出电压为 0V。
    • 输出高电平:物理引脚拉高,输出电压为供电电压(通常为 3.3V 或 1.8V,具体取决于硬件设计)。

接下来,我们去手册中应证一下,看下chatgpt说的对不对,搜索020A_C000…
只找到下面这个结果:
在这里插入图片描述
看不出什么,只知道这个地址与GPIO5有关…
再继续搜索书签:GPIO5_DR 也没有结果
全文搜索 GPIO5_DR,有了:
在这里插入图片描述
可见,其物理地址为 20A_C000 详细介绍在1358页的28.5.1节:
在这里插入图片描述
可见你要使GPIO5_IO03输出0和1,只需要写GPIO5_DR的第3位就行了。
所以可以写出下面的代码:

 GPIO5_DR = ioremap(0x020AC000 + 0, 4);

/* b. 设置GPIO5_IO03作为output引脚
 * set GPIO5_GDIR to configure GPIO5_IO03 as output
 * GPIO5_GDIR  0x020AC000 + 0x4
 * bit[3] = 0b1
 */
*GPIO5_GDIR |= (1<<3);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

LED控制函数board_demo_led_ctl分析

源代码如下:

static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{
    //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
    if (which == 0)
    {
        if (status) /* on: output 0*/
        {
            /* d. 设置GPIO5_DR输出低电平
             * set GPIO5_DR to configure GPIO5_IO03 output 0
             * GPIO5_DR 0x020AC000 + 0
             * bit[3] = 0b0
             */
            *GPIO5_DR &= ~(1<<3);
        }
        else  /* off: output 1*/
        {
            /* e. 设置GPIO5_IO3输出高电平
             * set GPIO5_DR to configure GPIO5_IO03 output 1
             * GPIO5_DR 0x020AC000 + 0
             * bit[3] = 0b1
             */ 
            *GPIO5_DR |= (1<<3);
        }
    
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

这个代码就没啥好说了,只要清楚了LED控制的初始化函数board_demo_led_init,这里理解起来就很简单了。

LED控制的关闭函数(释放内存映射)

static int board_demo_led_close(int which) /* whice表示具体是要操作哪个LED*/
{
    if (which == 0)
    {
        if (CCM_CCGR1) {
            iounmap(CCM_CCGR1);
            CCM_CCGR1 = NULL;
        }
        if (IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3) {
            iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
            IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = NULL;
        }
        if (GPIO5_GDIR) {
            iounmap(GPIO5_GDIR);
            GPIO5_GDIR = NULL;
        }
        if (GPIO5_DR) {
            iounmap(GPIO5_DR);
            GPIO5_DR = NULL;
        }
    }
    
    return 0;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

这段代码也没啥好说的,只是在释放前先判断下对应的指针是否为NULL,如果不是NULL,再去执行释放操作。这段代码是我自己写的,不是教程中提供的。

Makfile文件的编写

这个Makfile文件和之前的驱动程序的Makefile文件相比(http://iyenn.com/rec/1709540.html),不同的是这里的驱动程序为两个C文件,具体是board_fire_imx6ull-pro.c和leddrv.c,所以难度相当于增加了,不过大部分Makefile语句还是和(http://iyenn.com/rec/1709540.html)中的代码相同,所以要了解这里的这个Makefile文件,可以先看下(http://iyenn.com/rec/1709540.html)中对Makefile文件的解读。具体来说,你需要打开链接 http://iyenn.com/rec/1709543.html 查看。

具体的Makefile文件的内容如下:

# 使用不同的Linux内核时, 一定要修改KERN_DIR,KERN_DIR代表已经配置、编译好的Linux源码的根目录

KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

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

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

# 参考内核源码drivers/char/ipmi/Makefile
# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o

# leddrv.c board_demo.c 编译成 led_driver.ko
led_driver-y := leddrv.o board_fire_imx6ull-pro.o
obj-m	+= led_driver.o

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

其实关键的就是最后两行语句:

# leddrv.c board_demo.c 编译成 led_driver.ko
led_driver-y := leddrv.o board_fire_imx6ull-pro.o
obj-m	+= led_driver.o
  • 1
  • 2
  • 3

这其实没啥好说的,照这个格式去填写就行了,这里由Linux源码中的Makefile定义的多文件模块构建规则。再比如,如果我要用 leddrv.o 和board_fire_imx6ull-pro.o生成模块文件suwenhao.ko,那我就像下面这样写:

suwenhao-y := leddrv.o board_fire_imx6ull-pro.o
obj-m	+= suwenhao.o
  • 1
  • 2

交叉编译

代码复制到Ubuntu中…
在这里插入图片描述

make 
  • 1

在这里插入图片描述
在这里插入图片描述
复制上面两个红框中的文件到NFS文件系统中以备用…

在这里插入图片描述

上板测试

cd /mnt/led_driver
  • 1

加载驱动:

insmod led_driver.ko
  • 1

在这里插入图片描述
运行测试程序:

首先打开LED

./led_driver_test /dev/imx6ull_led0 on
  • 1

在这里插入图片描述
上面打印输出的语句:

[  294.737362] LED driver: resources released.
  • 1

是下面这句代码产生的:
在这里插入图片描述
当主函数执行到close(fd);时例会调用函数led_drv_close。

发现板子上编号为2的LED打开了:

在这里插入图片描述
然后关闭LED:

./led_driver_test /dev/imx6ull_led0 off
  • 1

在这里插入图片描述
发现灯关了~

卸载驱动程序模块:

卸载前先看下系统中当前存在的驱动程序模块:

cat /proc/devices
  • 1

在这里插入图片描述
然后再执行卸载驱动模块的命令:

rmmod led_driver
  • 1

再查看系统中当前存在的驱动程序模块:

cat /proc/devices
  • 1

在这里插入图片描述
发现正常卸载了…

测试结束…一切OK~

附工程压缩文件和PDF文档

工程压缩文件:
https://pan.baidu.com/s/1LzdbQ1LDcAxbv2T7akvnxQ?pwd=ixqv

开发板的原理图文件:
https://pan.baidu.com/s/1CEwPbcNUIicA1HVGwG6e5A?pwd=m9wb
IMX6ULL-CPU芯片的操作手册:
https://pan.baidu.com/s/1hZWmgmvYAA9mvzAHsPiw4g?pwd=eiuh

昊虹嵌入式技术交流群
QQ群名片
注:本文转载自blog.csdn.net的昊虹AI笔记的文章"https://blog.csdn.net/wenhao_ir/article/details/144973219"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top