秦天qintian0303 发表于 2024-3-9 21:22

《原子Linux驱动开发》基础阅读2:Linux LED驱动开发

<article data-content="[{&quot;type&quot;:&quot;block&quot;,&quot;id&quot;:&quot;3060-1621846615933&quot;,&quot;name&quot;:&quot;paragraph&quot;,&quot;data&quot;:{&quot;style&quot;:{&quot;textIndent&quot;:28}},&quot;nodes&quot;:[{&quot;type&quot;:&quot;text&quot;,&quot;id&quot;:&quot;p5PQ-1621846617594&quot;,&quot;leaves&quot;:[{&quot;text&quot;:&quot;了解完字符设备驱动开发的一般流程与基本介绍,下面我们进行具体的一个字符设备驱动开发。&quot;,&quot;marks&quot;:[]}]}],&quot;state&quot;:{}},{&quot;type&quot;:&quot;block&quot;,&quot;id&quot;:&quot;npbu-1709988175956&quot;,&quot;name&quot;:&quot;paragraph&quot;,&quot;data&quot;:{&quot;style&quot;:{&quot;textIndent&quot;:28}},&quot;nodes&quot;:[{&quot;type&quot;:&quot;text&quot;,&quot;id&quot;:&quot;oCEe-1709988175941&quot;,&quot;leaves&quot;:[{&quot;text&quot;:&quot;和咱们进行裸机开发,或者是基于arm内核的stm32开发一样,咱们在进行开发都是先易后难,例如咱们进行LED的操作,在stm32中实际上就是对寄存器的初始化后进行具体的寄存器的操作,来实现LED的亮与灭,基于linux的LED驱动编写要符合linux的驱动框架。&quot;,&quot;marks&quot;:[]}]}],&quot;state&quot;:{}},{&quot;type&quot;:&quot;block&quot;,&quot;id&quot;:&quot;NsKj-1709989695358&quot;,&quot;name&quot;:&quot;paragraph&quot;,&quot;data&quot;:{&quot;style&quot;:{&quot;textIndent&quot;:28}},&quot;nodes&quot;:[{&quot;type&quot;:&quot;text&quot;,&quot;id&quot;:&quot;j3vN-1709989695356&quot;,&quot;leaves&quot;:[{&quot;text&quot;:&quot;ATK-CLMP135B中对应的LED控制口为PI3:&quot;,&quot;marks&quot;:[]}]}],&quot;state&quot;:{}},{&quot;type&quot;:&quot;block&quot;,&quot;id&quot;:&quot;Q7k2-1709989693830&quot;,&quot;name&quot;:&quot;image&quot;,&quot;data&quot;:{&quot;version&quot;:1,&quot;url&quot;:&quot;https://note.youdao.com/yws/res/0/WEBRESOURCEbaf3e4986f6d4a3b4ccb25091eb836b0&quot;,&quot;width&quot;:253,&quot;height&quot;:41},&quot;nodes&quot;:[],&quot;state&quot;:{&quot;loading&quot;:false,&quot;renderSource&quot;:&quot;https://note.youdao.com/yws/res/0/WEBRESOURCEbaf3e4986f6d4a3b4ccb25091eb836b0&quot;,&quot;initialSize&quot;:{&quot;width&quot;:253,&quot;height&quot;:41}}},{&quot;type&quot;:&quot;block&quot;,&quot;id&quot;:&quot;aLOA-1709988271310&quot;,&quot;name&quot;:&quot;paragraph&quot;,&quot;data&quot;:{&quot;style&quot;:{&quot;textIndent&quot;:28}},&quot;nodes&quot;:[{&quot;type&quot;:&quot;text&quot;,&quot;id&quot;:&quot;Oh9S-1709988271306&quot;,&quot;leaves&quot;:[{&quot;text&quot;:&quot;咱们在进行裸机开发时或者stm32的控制时,一般不会太重点注意到物理空间的位置,而这个时候我们在进行linux开发时就要注意一个问题了。地址的映射,我们要首先了解一下内存管理单元mmu完成的主要功能,一个就是虚拟空间和物理空间的映射那也就是驱动完成的具体功能,用户层不需要考虑设备的具体物理地址,但是设备的具体地址确是对外设的直接操作的,实现的用户层与物理层的映射,第二个功能就是内存保护,设置存储器的访问权限、设置虚拟存储空间的缓冲特性。&quot;,&quot;marks&quot;:[]}]}],&quot;state&quot;:{}},{&quot;type&quot;:&quot;block&quot;,&quot;id&quot;:&quot;qMeI-1709988394665&quot;,&quot;name&quot;:&quot;paragraph&quot;,&quot;data&quot;:{&quot;style&quot;:{&quot;textIndent&quot;:28}},&quot;nodes&quot;:[{&quot;type&quot;:&quot;text&quot;,&quot;id&quot;:&quot;wPRj-1709988394662&quot;,&quot;leaves&quot;:[{&quot;text&quot;:&quot;地址映射,由于其范围的局限性,一般会存在虚拟地址多对一物理地址的情况,虚拟地址比物理地址范围大的问题由处理器自行处理,内核启动的时候会初始化内存管理单元,设置好内存映射,以后的CPU访问的都是虚拟地址。这个时候我们可以通过ioremap函数获取指定物理地址空间对应的虚拟地址,应用层实际操作的也是通过对虚拟地址空间的操作,我们在卸载驱动的时候也需要使用iounmap函数释放掉前期所做的映射。&quot;,&quot;marks&quot;:[]}]}],&quot;state&quot;:{}},{&quot;type&quot;:&quot;block&quot;,&quot;id&quot;:&quot;Iowp-1709988510777&quot;,&quot;name&quot;:&quot;paragraph&quot;,&quot;data&quot;:{&quot;style&quot;:{&quot;textIndent&quot;:28}},&quot;nodes&quot;:[{&quot;type&quot;:&quot;text&quot;,&quot;id&quot;:&quot;ugov-1709988510774&quot;,&quot;leaves&quot;:[{&quot;text&quot;:&quot;LED的驱动实际上就是IO口的高低电平的控制,在这里IO的输入输出并不是我们学习单片机时候讲的gpio引脚。这里涉及两个概念,IO端口和IO内存。外部寄存器或内存映射到IO空间时称为端口,寄存器或内存映射到内存空间时称之为内存。通过ioremap函数将寄存器的物理地址映射到虚拟地址以后,就可以直接通过指针访问这些地址,Linux内核建议使用操作函数对映射后的内存进行读写操作。&quot;,&quot;marks&quot;:[]}]}],&quot;state&quot;:{}},{&quot;type&quot;:&quot;block&quot;,&quot;id&quot;:&quot;X4KE-1709989790863&quot;,&quot;name&quot;:&quot;paragraph&quot;,&quot;data&quot;:{&quot;style&quot;:{&quot;textIndent&quot;:0}},&quot;nodes&quot;:[{&quot;type&quot;:&quot;text&quot;,&quot;id&quot;:&quot;n9ba-1709989790862&quot;,&quot;leaves&quot;:[{&quot;text&quot;:&quot;接下来进行LED程序的编写:&quot;,&quot;marks&quot;:[]}]}],&quot;state&quot;:{}},{&quot;type&quot;:&quot;block&quot;,&quot;id&quot;:&quot;SK6n-1709989958324&quot;,&quot;name&quot;:&quot;paragraph&quot;,&quot;data&quot;:{&quot;style&quot;:{&quot;textIndent&quot;:0}},&quot;nodes&quot;:[{&quot;type&quot;:&quot;text&quot;,&quot;id&quot;:&quot;g97D-1709989958323&quot;,&quot;leaves&quot;:[{&quot;text&quot;:&quot;头文件:&quot;,&quot;marks&quot;:[]}]}],&quot;state&quot;:{}}]">
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;了解完字符设备驱动开发的一般流程与基本介绍,下面我们进行具体的一个字符设备驱动开发。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;和咱们进行裸机开发,或者是基于arm内核的stm32开发一样,咱们在进行开发都是先易后难,例如咱们进行LED的操作,在stm32中实际上就是对寄存器的初始化后进行具体的寄存器的操作,来实现LED的亮与灭,基于linux的LED驱动编写要符合linux的驱动框架。</p>

<p>ATK-CLMP135B中对应的LED控制口为PI3:</p>

<p style="text-align: center;"> &nbsp;</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;咱们在进行裸机开发时或者stm32的控制时,一般不会太重点注意到物理空间的位置,而这个时候我们在进行linux开发时就要注意一个问题了。地址的映射,我们要首先了解一下内存管理单元mmu完成的主要功能,一个就是虚拟空间和物理空间的映射那也就是驱动完成的具体功能,用户层不需要考虑设备的具体物理地址,但是设备的具体地址确是对外设的直接操作的,实现的用户层与物理层的映射,第二个功能就是内存保护,设置存储器的访问权限、设置虚拟存储空间的缓冲特性。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;地址映射,由于其范围的局限性,一般会存在虚拟地址多对一物理地址的情况,虚拟地址比物理地址范围大的问题由处理器自行处理,内核启动的时候会初始化内存管理单元,设置好内存映射,以后的CPU访问的都是虚拟地址。这个时候我们可以通过ioremap函数获取指定物理地址空间对应的虚拟地址,应用层实际操作的也是通过对虚拟地址空间的操作,我们在卸载驱动的时候也需要使用iounmap函数释放掉前期所做的映射。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;LED的驱动实际上就是IO口的高低电平的控制,在这里IO的输入输出并不是我们学习单片机时候讲的gpio引脚。这里涉及两个概念,IO端口和IO内存。外部寄存器或内存映射到IO空间时称为端口,寄存器或内存映射到内存空间时称之为内存。通过ioremap函数将寄存器的物理地址映射到虚拟地址以后,就可以直接通过指针访问这些地址,Linux内核建议使用操作函数对映射后的内存进行读写操作。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;接下来进行LED程序的编写:</p>

<p>头文件:</p>

<pre>
<code class="language-cs">#include &lt;linux/types.h&gt;
#include &lt;linux/kernel.h&gt;
#include &lt;linux/delay.h&gt;
#include &lt;linux/init.h&gt;
#include &lt;linux/module.h&gt;
#include &lt;linux/errno.h&gt;
#include &lt;linux/gpio.h&gt;
#include &lt;asm/mach/map.h&gt;
#include &lt;asm/uaccess.h&gt;
#include &lt;asm/io.h&gt;</code></pre>

<p>宏定义</p>

<pre>
<code class="language-cs">#define LED_MAJOR                200                /* 主设备号 */
#define LED_NAME                "led"         /* 设备名字 */

#define LEDOFF         0                                /* 关灯 */
#define LEDON         1                                /* 开灯 */

/* 寄存器物理地址 */
#define PERIPH_BASE                                (0x40000000)
#define MPU_AHB4_PERIPH_BASE                        (PERIPH_BASE + 0x10000000)
#define RCC_BASE                                (MPU_AHB4_PERIPH_BASE + 0x0000)       
#define RCC_MP_AHB4ENSETR                                (RCC_BASE + 0XA28)
#define GPIOI_BASE                                                (MPU_AHB4_PERIPH_BASE + 0xA000)       
#define GPIOI_MODER                                (GPIOI_BASE + 0x0000)       
#define GPIOI_OTYPER                                (GPIOI_BASE + 0x0004)       
#define GPIOI_OSPEEDR                                (GPIOI_BASE + 0x0008)       
#define GPIOI_PUPDR                                (GPIOI_BASE + 0x000C)       
#define GPIOI_BSRR                                (GPIOI_BASE + 0x0018)


/* 映射后的寄存器虚拟地址指针 */
static void __iomem *MPU_AHB4_PERIPH_RCC_PI;
static void __iomem *GPIOI_MODER_PI;
static void __iomem *GPIOI_OTYPER_PI;
static void __iomem *GPIOI_OSPEEDR_PI;
static void __iomem *GPIOI_PUPDR_PI;
static void __iomem *GPIOI_BSRR_PI;</code></pre>

<p>函数</p>

<pre>
<code class="language-cs">void led_switch(u8 sta)
{
        u32 val = 0;
        if(sta == LEDON) {
                val = readl(GPIOI_BSRR_PI);
                val |= (1 &lt;&lt; 19);       
                writel(val, GPIOI_BSRR_PI);
        }else if(sta == LEDOFF) {
                val = readl(GPIOI_BSRR_PI);
                val|= (1 &lt;&lt; 3);       
                writel(val, GPIOI_BSRR_PI);
        }       
}

void led_unmap(void)
{
        /* 取消映射 */
        iounmap(MPU_AHB4_PERIPH_RCC_PI);
        iounmap(GPIOI_MODER_PI);
        iounmap(GPIOI_OTYPER_PI);
        iounmap(GPIOI_OSPEEDR_PI);
        iounmap(GPIOI_PUPDR_PI);
        iounmap(GPIOI_BSRR_PI);
}

static int led_open(struct inode *inode, struct file *filp)
{
        return 0;
}

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
        return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
        int retvalue;
        unsigned char databuf;
        unsigned char ledstat;

        retvalue = copy_from_user(databuf, buf, cnt);
        if(retvalue &lt; 0) {
                printk("kernel write failed!\r\n");
                return -EFAULT;
        }

        ledstat = databuf;                /* 获取状态值 */

        if(ledstat == LEDON) {       
                led_switch(LEDON);                /* 打开LED灯 */
        } else if(ledstat == LEDOFF) {
                led_switch(LEDOFF);                /* 关闭LED灯 */
        }
        return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
        return 0;
}

static struct file_operations led_fops = {
        .owner = THIS_MODULE,
        .open = led_open,
        .read = led_read,
        .write = led_write,
        .release =         led_release,
};

static int __init led_init(void)
{
        int retvalue = 0;
        u32 val = 0;

        /* 初始化LED */
        /* 1、寄存器地址映射 */
        MPU_AHB4_PERIPH_RCC_PI = ioremap(RCC_MP_AHB4ENSETR, 4);
        GPIOI_MODER_PI = ioremap(GPIOI_MODER, 4);
        GPIOI_OTYPER_PI = ioremap(GPIOI_OTYPER, 4);
        GPIOI_OSPEEDR_PI = ioremap(GPIOI_OSPEEDR, 4);
        GPIOI_PUPDR_PI = ioremap(GPIOI_PUPDR, 4);
        GPIOI_BSRR_PI = ioremap(GPIOI_BSRR, 4);

        /* 2、使能PI时钟 */
        val = readl(MPU_AHB4_PERIPH_RCC_PI);
        val &amp;= ~(0X1 &lt;&lt; 8);        /* 清除以前的设置 */
        val |= (0X1 &lt;&lt; 8);        /* 设置新值 */
        writel(val, MPU_AHB4_PERIPH_RCC_PI);

        /* 3、设置PI3通用的输出模式。*/
        val = readl(GPIOI_MODER_PI);
        val &amp;= ~(0X3 &lt;&lt; 3);        /* bit0:1清零 */
        val |= (0X1 &lt;&lt; 3);        /* bit0:1设置01 */
        writel(val, GPIOI_MODER_PI);

        /* 3、设置PI3为推挽模式。*/
        val = readl(GPIOI_OTYPER_PI);
        val &amp;= ~(0X1 &lt;&lt; 3);        /* bit0清零,设置为上拉*/
        writel(val, GPIOI_OTYPER_PI);

        /* 4、设置PI3为高速。*/
        val = readl(GPIOI_OSPEEDR_PI);
        val &amp;= ~(0X3 &lt;&lt; 3); /* bit0:1 清零 */
        val |= (0x2 &lt;&lt; 3); /* bit0:1 设置为10*/
        writel(val, GPIOI_OSPEEDR_PI);

        /* 5、设置PI3为上拉。*/
        val = readl(GPIOI_PUPDR_PI);
        val &amp;= ~(0X3 &lt;&lt; 3); /* bit0:1 清零*/
        val |= (0x1 &lt;&lt; 3); /*bit0:1 设置为01*/
        writel(val,GPIOI_PUPDR_PI);

        /* 6、默认关闭LED */
        val = readl(GPIOI_BSRR_PI);
        val |= (0x1 &lt;&lt; 3);       
        writel(val, GPIOI_BSRR_PI);

        /* 6、注册字符设备驱动 */
        retvalue = register_chrdev(LED_MAJOR, LED_NAME, &amp;led_fops);
        if(retvalue &lt; 0) {
                printk("register chrdev failed!\r\n");
                goto fail_map;
        }
        return 0;
       
fail_map:
        led_unmap();
        return -EIO;
}

static void __exit led_exit(void)
{
        /* 取消映射 */
        led_unmap();

        /* 注销字符设备驱动 */
        unregister_chrdev(LED_MAJOR, LED_NAME);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");</code></pre>

<p>&nbsp; &nbsp; &nbsp; &nbsp; 是不是和裸机开发驱动很相似,主要的功能就是LED的初始化,LED的操作,设备的注册与注销,设备的打开和释放等等,主要的区别就是linux驱动开发要在其开发框架下进行。</p>
</article>

psf240309 发表于 2024-3-9 21:42

<p>感谢分享,谢谢!</p>

damiaa 发表于 2024-3-10 10:36

<p>楼主这书感觉怎么样?详细介绍一下吧。</p>

<p>比如针对哪些板子,linux版本号,例子有哪些。</p>

<p>&nbsp;</p>

秦天qintian0303 发表于 2024-3-10 10:42

damiaa 发表于 2024-3-10 10:36
楼主这书感觉怎么样?详细介绍一下吧。

比如针对哪些板子,linux版本号,例子有哪些。

&nbsp;

<p>正点原子的例程资料还是相当全的,这个书里面用的I.MX6U的板子,不过我这边只有STM32MP135的</p>

秦天qintian0303 发表于 2024-3-10 10:43

damiaa 发表于 2024-3-10 10:36
楼主这书感觉怎么样?详细介绍一下吧。

比如针对哪些板子,linux版本号,例子有哪些。

&nbsp;

<p>书里面的例子非常多,书也是相当的厚,估计规定时间内也就对基础知识了解一遍就不错了,后续实际应用的时候再一个一个试验</p>

damiaa 发表于 2024-3-10 10:46

秦天qintian0303 发表于 2024-3-10 10:43
书里面的例子非常多,书也是相当的厚,估计规定时间内也就对基础知识了解一遍就不错了,后续实际应用的时 ...

<p>要是st家就好点。这些每家的还是有一些不一样。</p>

秦天qintian0303 发表于 2024-3-10 11:00

damiaa 发表于 2024-3-10 10:46
要是st家就好点。这些每家的还是有一些不一样。

<p>确实每家都不同,我本来计划一边实践一边学的,结构发现太难了,想要两个月彻底掌握也没可能,所以还是先了解基础,慢慢推进了</p>

chejm 发表于 2024-3-18 08:55

<p>感谢楼主推荐的这本书籍信息,希望楼主能提供一下这本书的全面简介,</p>
页: [1]
查看完整版本: 《原子Linux驱动开发》基础阅读2:Linux LED驱动开发