当前位置: 首页 > news >正文

《i.MX6ULL LED 驱动实战:内核模块开发与 GPIO 控制》

LED驱动准备


在上一篇裸机开发实验中,我们是直接向寄存器地址写入数据的。

但在 Linux 驱动开发 中就不能这样做了。因为在 Linux 内核中,不允许直接操作物理地址
这是由于系统启用了 MMU(内存管理单元),内核采用了 虚拟内存机制

换句话说,我们在裸机中使用的那些物理地址(如 0x020E0068),在 Linux 环境中已经不再是可直接访问的。
我们必须先获得该寄存器物理地址对应的虚拟地址,才能对其进行读写。

为此,Linux 提供了两个非常关键的函数:

  • ioremap() —— 将物理地址映射为内核可访问的虚拟地址
  • iounmap() —— 在不再使用时解除映射

这对函数是驱动开发中访问硬件寄存器的“入口”和“出口”。

接下来是对两个函数的讲解


ioremap() 函数

原型
void __iomem *ioremap(resource_size_t phys_addr, unsigned long size);
功能

将设备的物理地址区间映射到内核虚拟地址空间,并返回虚拟地址指针。

参数说明
参数含义
phys_addr起始物理地址,比如寄存器地址 0x0209C000
size要映射的字节大小,通常是 4、8 或 0x1000
返回值
  • 成功:返回一个类型为 void __iomem * 的虚拟地址指针
  • 失败:返回 NULL
示例
#define GPIO1_DR_BASE (0x0209C000)static void __iomem *GPIO1_DR;GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
if (!GPIO1_DR) {pr_err("Failed to map GPIO1_DR\n");return -ENOMEM;
}

此时 GPIO1_DR 就是映射后的虚拟地址,可以通过 readl()writel() 安全访问寄存器。



iounmap() 函数

原型
void iounmap(void __iomem *addr);
功能

释放之前通过 ioremap() 映射的虚拟地址空间。

使用场景
  • 驱动卸载(module_exit)时;
  • 或者资源释放阶段。
示例
iounmap(GPIO1_DR);

表示释放对应的虚拟地址映射。


注意事项

  1. 不能直接对映射指针解引用:

    *GPIO1_DR = 1; // 错误
    

    应使用内核提供的接口:

    writel(1, GPIO1_DR);
    val = readl(GPIO1_DR);
    
  2. 映射范围应与寄存器手册对应。
    如果寄存器区域是 0x1000 字节,应完整映射,否则可能越界。

  3. 同一物理区域重复调用 ioremap 会得到不同虚拟地址,但都指向同一物理区域,不推荐这样做。

  4. 用户态程序不能使用 ioremap,它是内核态函数。用户态可通过 /dev/memmmap() 实现类似效果。



类比说明

环境行为比喻
裸机直接写寄存器地址拿钥匙直接开门
Linux 内核ioremap 映射后再访问先申请门禁卡再刷卡开门


小结

函数作用常用位置搭配函数
ioremap()将物理地址映射到虚拟地址驱动初始化、probe函数readl()writel()
iounmap()释放映射驱动卸载、exit函数

LED内核态驱动代码

这里就继续运用到我们《从系统调用到驱动回调:read() 如何映射到 chrdev_read()》这一篇博客所介绍的字符设备驱动开发内容

这段代码的重点就是:

  • 地址映射
  • 释放映射

下面是代码展示:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define Led_Major 200
#define Led_Name "led"#define LED_ON 1
#define LED_OFF 0/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C) 
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;/*led开关转换*/
void led_switch(u8 sta)
{u32 val=0;if(sta==LED_ON){val=readl(GPIO1_DR);val &=~(1<<3);//输出低电平,点亮LEDwritel(val,GPIO1_DR);}else if(sta==LED_OFF){val = readl(GPIO1_DR);val |= (1 << 3);//输出高电平,熄灭LEDwritel(val, GPIO1_DR);} 
}/*设备开启*/
static int leddev_open(struct inode *inode, struct file *filp)
{printk("leddev open!\n");return 0;
}
/*设备读取*/
static ssize_t leddev_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{printk("leddev read!\n");return 0;  
}
/*设备写入*/
static ssize_t leddev_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int ret;unsigned char write_buf[1];unsigned char led_stat;ret = copy_from_user(write_buf, buf, cnt);if (ret == 0)printk("write successful!\n");else{printk("write error!\n");return -EFAULT;}led_stat=write_buf[0];if(led_stat==LED_ON)led_switch(LED_ON);else if(led_stat==LED_OFF)led_switch(LED_OFF);return 0;
}static int leddev_release(struct inode *inode, struct file *filp)
{return 0;
}static struct file_operations leddev_fops = {.owner = THIS_MODULE,.open = leddev_open,.read = leddev_read,.write = leddev_write,.release = leddev_release,
};// 模块加载
static int __init chrdev_init(void)
{int ret;u32 val;//初始化LED/* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);/* 2、使能 GPIO1 时钟 */val=readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26); // 清除以前的设置val |= (3 << 26); //设置新值writel(val,IMX6U_CCM_CCGR1);/* 3、设置 GPIO1_IO03 的复用功能,将其复用为GPIO1_IO03,最后设置 IO 属性。*/writel(5, SW_MUX_GPIO1_IO03);//寄存器 SW_PAD_GPIO1_IO03 设置 IO 属性writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、设置 GPIO1_IO03 为输出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3); // 清除以前的设置 val |= (1 << 3); // 设置为输出 writel(val,GPIO1_GDIR);/* 5、默认关闭 LED */val=readl(GPIO1_DR);val|=(1<<3);writel(val,GPIO1_DR);//注册设备ret = register_chrdev(Led_Major,Led_Name, &leddev_fops);if (ret < 0){printk("register error!\n");return -EIO;}elseprintk("chrdev init successful!\n");return 0;
}// 模块卸载
static void __exit chrdev_exit(void)
{/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);/* 注销字符设备驱动 */ unregister_chrdev(Led_Major,Led_Name);printk("chrdev exit.\n");
}module_init(leddev_init);
module_exit(leddev_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("Yunes");
MODULE_DESCRIPTION("Simple Led device driver demo");

LED用户态测试代码

这个就比较简单了,代码如下:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>#define LED_ON 1
#define LED_OFF 0int main(int argc, char const *argv[])
{unsigned char led_buf[1];int fd, tmp;if (argc != 3) {printf("Usage: %s <device> <0=OFF | 1=ON>\n", argv[0]);return -1;}char * filename = (char *)argv[1];fd = open(filename, O_RDWR);if (fd < 0) {perror("open");return -1;}led_buf[0]=atoi(argv[2]);tmp = write(fd, led_buf, sizeof(led_buf));if (tmp < 0)perror("write error");close(fd);return 0;
}

总结

这就是本篇博客的全部内容。通过本文,我们不仅了解了物理地址与虚拟地址之间的关系,也掌握了如何在 Linux 驱动中通过寄存器映射操作硬件,最终实现了在开发板上对 LED 的控制。

http://www.dtcms.com/a/512384.html

相关文章:

  • Effective Java学习笔记:用静态工厂方法代替构造器(第一条)
  • TDengine 数学函数 POW 用户手册
  • AI大模型“战国策”:主流LLM平台简单介绍
  • Prometheus监控部署——pushgateway自动推送
  • 网站布局优化问问建设网站的人
  • 做网站宜宾深圳龙华网站建设公司
  • vue3 setup的平级函数(宏函数)
  • 企业门户网站开发代码游戏公司网站模板下载
  • 基于MATLAB的证件照片背景变换实例
  • 网站模板资源
  • 网站开发与托管协议诚信通旺铺网站建设
  • 做a图片网站公众号涨粉
  • 安卓实例——统一动画
  • 数字化转型:概念性名词浅谈(第七十三讲)
  • 【推荐系统】快手OneSearch 提升3.2%订单量
  • jsp做的网页是网站吗三亚网红
  • 网页浏览器图标电商网站如何优化
  • 几种常用关系型数据库详细介绍
  • R-CNN详解
  • 网站维护要求哈尔滨网站建设效果
  • 多线程:线程类的方法做什么
  • 网站快速备案安全开发公司抽奖送房
  • Java一、二维数组
  • 企业网站模板观叫湖南岚鸿团队discuz好还是wordpress
  • 定制网站开发系统wordpress 繁简转换插件
  • 饲料网站建设 中企动力怎么做钓鱼网站生成器
  • vue 中 file-saver 功能介绍,使用场景,使用示例
  • 战略选择与系统性杠杆效应
  • @tanstack/react-query中isLoading,isFetchingisRefetching的区别
  • 深入解析C语言中的位域(Bit Fields):原理、规则与实践