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

嵌入式 Linux 驱动开发:点灯大法

概要

一. 整体架构流程

1.1 硬件准备

1.2 软件准备

二 . 编写 LED 驱动

2.1 创建驱动文件

2.2 编辑驱动测试代码并编译驱动

三. 测试驱动

3.1 加载驱动

3.2 创建设备节点

四. 实验现象


概要

在嵌入式 Linux 系统中,设备驱动是连接硬件与操作系统的桥梁。点灯大法好,通过编写 Linux 驱动,可以方便地控制开发板上的 LED 灯。

一. 整体架构流程

1.1 硬件准备

  • IMX6ULL开发板
  • 板载外设LED。
  • LED 正极 -> GPIO 引脚(GPIO1_IO03)
  • LED 负极 -> GND

1.2 软件准备

  • Linux 内核源码。
  • 交叉编译工具链:用于编译内核模块。
  • 开发环境:ubuntu。

二 . 编写 LED 驱动

2.1 创建驱动文件

在 Linux 内核源码目录下,创建一个新的驱动文件 led.c。在驱动代码中,完成对LED引脚的初始化配置,将其复用为IO输出引脚。其次,定义用户空间应用程序要调用的led_open 函数led_read 函数,led_write 函数,led_release 函数以完成对设备文件的访问操作。

驱动代码如下所示:

#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 LEDOFF 	0		
#define LEDON 	1				
 
/* 寄存器物理地址 */
#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;

void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == LEDON) {
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	
		writel(val, GPIO1_DR);
	}else if(sta == LEDOFF) {
		val = readl(GPIO1_DR);
		val|= (1 << 3);	
		writel(val, GPIO1_DR);
	}	
}


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[1];
	unsigned char ledstat;

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

	ledstat = databuf[0];		/* 获取状态值 */

	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、寄存器地址映射 */
  	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属性
	 *bit 16:0 HYS关闭
	 *bit [15:14]: 00 默认下拉
     *bit [13]: 0 kepper功能
     *bit [12]: 1 pull/keeper使能
     *bit [11]: 0 关闭开路输出
     *bit [7:6]: 10 速度100Mhz
     *bit [5:3]: 110 R0/6驱动能力
     *bit [0]: 0 低转换率
	 */
	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);

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


static void __exit led_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);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jiajiajia");

2.2 编辑驱动测试代码并编译驱动

在驱动文件目录下完成对测试文件的编写,其中完成对驱动函数的调用,进而完成对设备的控制。在 Linux 内核源码目录下,使用以下命令编译驱动,并将编译后的.ko文件以及可执行的测试文件挂载到nfs文件夹下,启动IMX6ULL确认挂在成功。

三. 测试驱动

3.1 加载驱动

在开发板上,使用以下命令加载驱动:

modprobe led

3.2 创建设备节点

加载驱动后,使用以下命令创建设备节点:其中200表示驱动注册的主设备号。

mknod /dev/led c 200 0

确认设备节点创建成功。 

四. 实验现象

通过执行对应的测试文件,对目标设备传入0或1参数,完成对目标设备的控制

 

相关文章:

  • SpringBoot中使用MyBatis-Plus详细介绍
  • C++ 网络编程
  • 安卓逆向(签名校验)
  • SQL 注入漏洞原理以及修复方法
  • 开源语音克隆项目 OpenVoice V2 本地部署
  • 数据治理常用的开源项目有哪些?
  • CAS单点登录(第7版)2.规划
  • 数据结构与算法之排序算法-(计数,桶,基数排序)
  • 阿里云上线 DeepSeek,AI 领域再掀波澜
  • UE C++ UObject 功能的初步总结
  • 工作室如何实现一机一IP
  • moveable 一个可实现前端海报编辑器的 js 库
  • 进阶关卡 - 第4关 - InternVL 多模态模型部署微调实践
  • 第二月:学习 NumPy、Pandas 和 Matplotlib 是数据分析和科学计算的基础
  • CAS单点登录(第7版)7.授权
  • flv实时监控视频
  • Linux网络 | 多路转接selec
  • 基于web的留守儿童网站的设计与实现
  • 【C/C++】C++ Vector容器核心操作指南:增删改查全面解析
  • Kubernetes:容器编排的革命与未来
  • 躺着玩手机真有意思,我“瞎”之前最喜欢了
  • 2025五一档电影票房破7亿
  • 韩国总统选举民调:共同民主党前党首李在明支持率超46%
  • 贵州黔西市游船倾覆事故最后一名失联人员被找到,但已无生命体征
  • 多地政府机关食堂迎来大客流,重庆荣昌区委书记给厨师们鼓劲
  • 农村青年寻路纪|劳动者的书信⑤