【Linux下LED基础设备驱动】

寄存器映射及内存访问

内存基础概念

用户空间和内核空间

IO空间:

CPU通过IO操作设备上的寄存器实现对设备的控制,一般厂商按照IO空间性质将IO划分为IO端口和IO内存

IO端口:

采用独立编址方式。外部设备中的寄存器(IO端口)地址与内存地址是相互独立、互不影响的,它们分别位于不同的地址空间内

IO内存:

采用统一编址方式。外部设备中的寄存器和内存中的存储单元被同等看待,每个IO端口占用一个内存存储单元的地址,将内存地址的一部分划出来用作IO地址空间。外设寄存器占用了内存的地址空间,使内存的存储容量减小

申请/释放IO内存

地址映射

我们访问一个寄存器时,需要将物理地址转换成虚拟地址才能够访问,这个过程就是映射,映射分为动态映射和静态映射

**静态映射:**静态映射在内核启动过程中就建立好,整个内核的生命周期都存在

**动态映射:**动态映射在模块需要的时候才进行,使用完后要解除映射,只有映射时才占用地址资源

IO内存映射/释放映射

内存分配

申请/释放内存

gfp_mask

申请/释放内存(可申请任意大小)

申请/释放内存(申请更大内存)

IO内存读写

读IO内存

写IO内存

LED基础设备驱动

寄存器介绍

模式寄存器配置

输出类型寄存器

置位复位寄存器

LED灯驱动实现流程

1、搭建基础模块框架

2、模块加载函数

注册字符设备实现GPIOZ组基地址映射修改GPIOZ_MODER,配置PZ5、PZ6、PZ7模式为输出模式修改GPIOZ_OTYPER,配置PZ5、PZ6、PZ7为推挽输出修改GPIOZ_BSRR,配置PZ5、PZ6、PZ7输出低电平,关闭LED实现加载函数出错处理,出错则释放之前申请的资源,返回错误码

2、模块卸载函数

注销字符设备、取消地址映射

3、定义命令

创建头文件,将命令定义放在头文件中

4、实现struct file_operations结构体的unlocked_ioctl成员

获取用户的命令和参数将用户参数和命令组合成寄存器位偏移将0x1左移组合后的偏移量,设置复位置位寄存器,实现LED灯控制

led.h

cs 复制代码
#ifndef LED_HHHHH
#define LED_HHHHH

#define LED_TYPE 'H'

#define LED1_SET	_IOW(LED_TYPE, 0, int)
#define LED2_SET	_IOW(LED_TYPE, 1, int)
#define LED3_SET	_IOW(LED_TYPE, 2, int)

#define LED_ON		0x0
#define LED_OFF		0x1

#endif

led.c

cs 复制代码
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#include <asm/io.h>

#include "led.h"

MODULE_LICENSE("GPL");

static int led_major = 300;
static int led_minor = 0;

static int device_number = 1;

static struct cdev cdev;

#define GPIOZ_BASE_ADDR 0x54004000
#define GPIOZ_MODER		0x00
#define GPIOZ_OTYPER	0x04
#define GPIOZ_BSRR		0x18

void __iomem *gpioz_base;

static int led_open(struct inode *inodep, struct file *filp)
{
	printk("led open\n");
	return 0;
}

static int led_close(struct inode *inodep, struct file *filp)
{
	printk("led close\n");
	return 0;
}

static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int state = 0;  
	int offset = 0;
	int tmp = 0;
	printk("led ioctl\n");

	/* 获取用户传递过来的参数存储在state变量中 */
	if(copy_from_user(&state, (void *)arg, sizeof(int)))
		return -EFAULT;

    /* 如果用户设置的LED灯状态为LED_OFF则操作BSRR寄存器的高16位 */
	if(state == LED_OFF) {
		offset += 16;
	}

	/* 判断命令执行对应操作 */
	switch(cmd) {
		case LED1_SET:
			/* LED1/PZ5位偏移 */
			offset += 5;
			break;
		case LED2_SET:
			/* LED2/PZ6位偏移 */
			offset += 6;
			break;
		case LED3_SET:
			/* LED3/PZ7位偏移 */
			offset += 7;
			break;
		default:
			return -EINVAL;
	}

	/* 设置指定GPIO输出状态 */
	tmp = 0x1 << offset; 				
	writel(tmp, gpioz_base + GPIOZ_BSRR);

	return 0;
}

static struct file_operations led_fops = {
	.open = led_open,
	.release = led_close,
	.unlocked_ioctl = led_ioctl
};

int led_drv_init(void)
{
	int ret;
	unsigned int tmp;
	/* 构建设备号 */
	dev_t devno = MKDEV(led_major, led_minor);

	printk("led driver loading\n");
    /* 申请设备号 */
	ret = register_chrdev_region(devno, device_number, "led");
	if (ret < 0) {
		printk("failed: register_chrdev_region\n");
		return ret;
	}
    /* cdev初始化 */
	cdev_init(&cdev, &led_fops);

	/* 设备注册 */
	ret = cdev_add(&cdev, devno, device_number);
	if (ret < 0) {
		printk("faile: cdev_add\n");
		goto err1;
	}

	/* 寄存器映射 */
	gpioz_base = ioremap(GPIOZ_BASE_ADDR, 0xff);
	if (!gpioz_base) {
		printk("failed: ioremap\n");
		goto err2;
	}

	/* 设置GPIO口工作模式 */
	tmp = readl(gpioz_base + GPIOZ_MODER);
	tmp = (tmp & ~(0x3 << 10)) | 0x1 << 10; //设置PZ5为输出
	tmp = (tmp & ~(0x3 << 12)) | 0x1 << 12; //设置PZ6为输出
	tmp = (tmp & ~(0x3 << 14)) | 0x1 << 14; //设置PZ7为输出
	writel(tmp, gpioz_base + GPIOZ_MODER);

    /* 设置GPIO输出类型 */
	tmp = readl(gpioz_base + GPIOZ_OTYPER);
	tmp &= ~(0x7 << 5); 						//设置PZ5 PZ6 PZ7为推挽输出
	writel(tmp, gpioz_base + GPIOZ_OTYPER);

	/* 设置GPIO默认输出状态 */
	tmp = 0x7 << (5 + 16); 				//设置PZ5 PZ6 PZ7输出低电平,关闭LED灯
	writel(tmp, gpioz_base + GPIOZ_BSRR);

	return 0;

	/* 出错处理依次释放掉前文申请的资源 */
err2:
	cdev_del(&cdev);
err1:
	unregister_chrdev_region(devno, device_number);
	return ret;
}

void led_drv_exit(void)
{
	/* 构建设备号 */
	dev_t devno = MKDEV(led_major, led_minor);

	printk("led driver unloading\n");
	/* 去掉寄存器地址映射 */
	iounmap(gpioz_base);
	/* 设备注销 */
	cdev_del(&cdev);
	/* 释放设备号 */
	unregister_chrdev_region(devno, device_number);
}

module_init(led_drv_init);
module_exit(led_drv_exit);

MODULE_AUTHOR("led <caozm_yf@hqyj.com>");

LED灯应用层实现流程

1、应用程序

打开字符设备

设置向模块传递的参数,用于开关灯

通过ioctl循环向模块发送命令和参数,延时1s

test.c

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>

#include "led.h"

int main(int argc, const char *argv[])
{
	int fd, state;

	fd = open("/dev/led", O_RDWR);
	if (fd < 0) {
		perror("open");
		return -1;
	}

	while(1) {
		state = LED_ON;
		ioctl(fd, LED1_SET, &state);
		ioctl(fd, LED2_SET, &state);
		ioctl(fd, LED3_SET, &state);
		sleep(1);
		state = LED_OFF;
		ioctl(fd, LED1_SET, &state);
		ioctl(fd, LED2_SET, &state);
		ioctl(fd, LED3_SET, &state);
		sleep(1);
	}

	close(fd);
	
	return 0;
}
相关推荐
HLC++12 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室13 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy15 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕16 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫17 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生18 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1119 小时前
linux基础IO
linux·运维·服务器
BelongPanda21 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯21 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983421 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器