itop-3568开发板驱动学习笔记(5) 点灯实验(两种方法)

《【北京迅为】itop-3568开发板驱动开发指南.pdf》 学习笔记

ioremap 物理地址映射

通过开发板原理图得知 LED 控制引脚为 GPIO0_B7,IO 高电平时,三极管导通,LED 点亮,IO 低电平时,三极管截止,LED 熄灭,

在这里插入图片描述

1. IO 复用寄存器

复用寄存器用来配置引脚功能,在芯片手册搜索 GPIO0_B7,可以知晓它的引脚功能由复用寄存器 PMU_GRF_GPIO0B_IOMUX_H 决定,[14:12] 为 0h 时,该引脚为 GPIO 功能,

在这里插入图片描述

通过查看 PMU_GRF_GPIO0B_IOMUX_H 的寄存器描述,发现其默认值为 0x00000000,即 GPIO0_B7 默认为 GPIO 功能,所以无需修改该寄存器的值,

在这里插入图片描述

2. 数据方向寄存器

下面是 rk3568 所有 GPIO 寄存器,不过只有前四个是我们要用到的(数据寄存器和数据方向寄存器),

在这里插入图片描述
在这里插入图片描述

上表中并没有告知方向寄存器和数据寄存器的基地址,我们可以在芯片手册的 Address Mapping 菜单获取它们的基地址,

在这里插入图片描述

rk3568 有四组 GPIO(GPIOA、GPIOB、GPIOC、GPIOD),每组有各有 8 个(A0~A7…)GPIO,所以 GPIO0_B7 的数据方向由 GPIO_SWPORT_DDR_L 寄存器决定,该寄存器的低 16 位用于控制 IO 方向,高 16 位用于控制 IO 写使能。(低 16 位的写使能)

在这里插入图片描述

GPIO0_B7 在 GPIO0 和 GPIO1 两组 IO 中排第 16(从 1 算起) ,如果要将 GPIO0_B7 设置为输出模式,需要将 GPIO_SWPORT_DDR_L 的 15 位和 31 位设置为 1。

3. 数据寄存器

数据寄存器与数据方向寄存器类似,我们需要将 GPIO_SWPORT_DR_L 31 位设置为 1([15:0] 的写使能),同时通过该寄存器的第 15 位来控制 IO 电平(1 为高电平),

在这里插入图片描述

4 .实验程序

代码参考原文档

驱动核心代码:

通过 ioremap() ,可以将底层物理地址映射到内核虚拟地址,这样便能向操作单片机(寄存器)一样操作 arm-linux 开发板(寄存器)了,

// 将物理地址转化为虚拟地址(寄存器为 32 位,所以映射 4 字节)
	dev1.gpio_ddr = ioremap(GPIO_REG_BASE + GPIO_DDR_REG, 4); // IO 数据方向
	if(IS_ERR(dev1.gpio_ddr))
	{
    
    
		ret = PTR_ERR(dev1.gpio_ddr); // 返回错误码
		goto err_ddr_ioremap;
	}
	// 1000 0000 1000 0000 
	*(dev1.gpio_ddr) |= 0x80008000; // GPIO0_B7 设置输出模式

完整驱动代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define GPIO_REG_BASE 0xFDD60000// gpio 寄存器基地址
#define GPIO_DDR_REG  0x0008    // 数据方向寄存器偏移地址
#define GPIO_DR_REG   0x0000    // 数据寄存器偏移地址

// 定义一个私有数据结构体
struct my_device
{
    
    
	dev_t dev_num; // 设备号
	int major;     // 主设备号
	int minor;     // 次设备号
	struct cdev st_cdev;
	struct class *st_class;
	struct device *st_device;
	char kbuf[32];
	unsigned int *gpio_ddr; // io 数据方向
	unsigned int *gpio_dr;  // io 高低电平
	// 地址数据类型千万不能写小了!!!
};

// 定义一个全局私有数据结构体
struct my_device dev1;

// open()
static int  chrdev_open(struct inode *inode , struct file *file )
{
    
    
	file->private_data = &dev1; // 设置私有数据
	printk("chrdev_open.\n");
	return 0;
}

// close()
static int chrdev_release(struct inode *inode, struct file *file)
{
    
    
	printk("chrdev_release.\n");
	return 0;
}

// write()
static ssize_t chrdev_write(struct file *file , const char __user *buf, size_t size, loff_t *off)
{
    
    
	struct my_device *tmp_dev = (struct my_device*)file->private_data;
	unsigned int tmp;

	int ret = copy_from_user(tmp_dev->kbuf, buf, size); // 从应用空间读取数据
	if(ret != 0)
	{
    
    
		printk("copy_from_user error.\r\n");
		return -1;
	}

	if(tmp_dev->kbuf[0] == 1) // 如果读到 1,点亮 LED
	{
    
    
		*(tmp_dev->gpio_dr) |= 0x80008000; // GPIO0_B7 高电平
	}
	else if(tmp_dev->kbuf[0] == 0) // 读到 0,熄灭 LED
	{
    
    
		tmp = *(dev1.gpio_dr) | 0x80000000; // 数据寄存器使能(中间值)
		*(dev1.gpio_dr) = tmp & ~(0x00008000); // GPIO0_B7 低电平
		// 实测:数据寄存器写使能必须和 IO 电平值同时写入寄存器,所以这里用到了中间变量
	}
	return 0;
}

// read()
static  ssize_t chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    
    
	printk("chrdev_read.\n");
	return 0;
}

static struct file_operations chrdev_fops = {
    
    
	.owner = THIS_MODULE, //将 owner 成员指向本模块,可以避免在模块的操作正在被使用时卸载该模块
	.open = chrdev_open,  //将 open 成员指向 chrdev_open()函数
	.read = chrdev_read,  //将 read 成员指向 chrdev_read()函数
	.write = chrdev_write,//将 write 字段指向 chrdev_write()函数
	.release = chrdev_release,//将 release 字段指向 chrdev_release()函数
};
// 驱动入口函数
static int __init chrdev_init(void)
{
    
    
	int ret;

	// 自动获取设备号(只申请一个,次设备号从 0 开始)
	ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "chrdev_test");
	if(ret < 0)
	{
    
    
		goto err_alloc;
	}
	printk("alloc chrdev region successfully.\n");
	dev1.major = MAJOR(dev1.dev_num); // 获取主设备号
	dev1.minor = MINOR(dev1.dev_num); // 获取次设备号
	printk("major is %d.\nminor is %d\n", dev1.major, dev1.minor);
	
	dev1.st_cdev.owner = THIS_MODULE; // 将 owner 成员指向本模块,可以避免模块 st_cdev 被使用时卸载模块
	cdev_init(&dev1.st_cdev, &chrdev_fops); // 初始化字符设备	
	ret = cdev_add(&dev1.st_cdev, dev1.dev_num, 1); // 将字符设备添加到系统
	if(ret < 0)
	{
    
    
		goto err_cdev_add;
	}
	printk("cdev add successfully.\n");
	
	dev1.st_class = class_create(THIS_MODULE, "chrdev_class"); // 创建设备类
	if(IS_ERR(dev1.st_class))
	{
    
    
		ret = PTR_ERR(dev1.st_class); // 返回错误码
		goto err_class_create;
	}
	
	dev1.st_device = device_create(dev1.st_class, NULL, dev1.dev_num, NULL, "chrdev_device"); // 创建设备
	if(IS_ERR(dev1.st_device))
	{
    
    
		ret = PTR_ERR(dev1.st_device); // 返回错误码
		goto err_device_create;
	}
	
	// 将物理地址转化为虚拟地址(寄存器为 32 位,所以映射 4 字节)
	dev1.gpio_ddr = ioremap(GPIO_REG_BASE + GPIO_DDR_REG, 4); // IO 数据方向
	if(IS_ERR(dev1.gpio_ddr))
	{
    
    
		ret = PTR_ERR(dev1.gpio_ddr); // 返回错误码
		goto err_ddr_ioremap;
	}
	// 1000 0000 1000 0000 
	*(dev1.gpio_ddr) |= 0x80008000; // GPIO0_B7 设置输出模式

	dev1.gpio_dr = ioremap(GPIO_REG_BASE + GPIO_DR_REG, 4);   // IO 电平
	if(IS_ERR(dev1.gpio_dr))
	{
    
    
		ret = PTR_ERR(dev1.gpio_dr); // 返回错误码
		goto err_dr_ioremap;
	}
	
	return 0;

err_dr_ioremap:
	iounmap(dev1.gpio_dr); // 取消映射
	
err_ddr_ioremap:
	iounmap(dev1.gpio_ddr); // 取消映射

err_device_create:
	class_destroy(dev1.st_class); // 删除类

err_class_create:
	cdev_del(&dev1.st_cdev); // 删除 cdev

err_cdev_add:
	unregister_chrdev_region(dev1.dev_num, 1); // 注销设备号

err_alloc:
	return ret; // 返回错误号

}

// 驱动出口函数
static void __exit chrdev_exit(void)
{
    
    
	iounmap(dev1.gpio_dr); // 取消映射
	iounmap(dev1.gpio_ddr); // 取消映射
	device_destroy(dev1.st_class, dev1.dev_num); // 删除设备
	class_destroy(dev1.st_class); //删除设备类
	cdev_del(&dev1.st_cdev); // 删除字符设备
	unregister_chrdev_region(dev1.dev_num, 1); // 注销设备号
	printk("chrdev_exit.\n");
}

module_init(chrdev_init);  //注册入口函数
module_exit(chrdev_exit);  //注册出口函数
MODULE_LICENSE("GPL v2");  //同意GPL协议
MODULE_AUTHOR("xiaohui");  //作者信息

app 代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define DEV_FILE "/dev/chrdev_device"

int main(int argc, char** argv)
{
    
    
	int fd, tmp;
	int ret = 0;
	
	// 打开设备文件
	fd = open(DEV_FILE, O_RDWR);
	if(fd < 0)
	{
    
    
		printf("%s open failed.\n", DEV_FILE);
		return 0;
	}
	printf("%s open successfully.\n", DEV_FILE);
	

	// 调用 write(),写 1 点亮 LED,写 0 熄灭 LED.
	while(1)
	{
    
    
		tmp = 1;
		write(fd, &tmp, 1);
		sleep(1);
		tmp = 0;
		write(fd, &tmp, 1);
		sleep(1);
	}

	// 关闭设备文件
	close(fd);
	return 0;
}

Makefile

#目标文件,与驱动源文件同名,编译成模块
obj-m := chrdev_test.o

#架构平台选择
export ARCH=arm64

#编译器选择
export CROSS_COMPILE=aarch64-linux-gnu-

#内核目录
KDIR := /home/topeet/Linux/rk356x_linux/kernel/
#KDIR := /lib/modules/$(shell uname -r)/build

#编译模块
all:
	make -C $(KDIR) M=$(shell pwd) modules
	$(CROSS_COMPILE)gcc app.c -o app

#清除编译文件
clean:
	make -C $(KDIR) M=$(shell pwd) clean
	rm app

测试结果

串口终端安装驱动,然后执行测试程序,

在这里插入图片描述

开发板效果:

请添加图片描述

调用 API

物理地址映射的方式可以让我们对 linux 开发板点灯有一个较全面的认识,但操作比较复杂,且移植性不好,下面我们试着使用调用 API 的方式控制 LED(GPIO),

只是简单地调用 Linux GPIO API,不作深入了解

先提一下 GPIO 编号,到网上找资料发现,RK3568 的 GPIO 编号很好计算,以 GPIO0_B7 为例,其 bank 为 0,group 为 1(A 为 0,以此类推),X 为 7,那么它的 pin 编号为 bank * 32 + group + X = 15,直白点讲,GPIO 的编号就是它在所有 IO 中所属序号,

// GPIO0_B7 (bank = 0, group = 1(B), X = 7, pin = 0 * 32 + 1 * 8 + 7 = 15)
#define LED_GPIO 15 // LED GPIO 编号

关键代码1,请求 gpio 和设置 gpio 模式:

	ret = gpio_request(LED_GPIO, "LED9"); // 请求 IO 资源
	if(ret < 0)
	{
    
    
		goto err_gpio_request;
	}
	printk("gpio request successfully.\n");

	ret = gpio_direction_output(LED_GPIO, 0); // 设置为输出模式
	if(ret < 0)
	{
    
    
		goto err_gpio_dir_out;
	}
	printk("set gpio output mode successfully.\n");
	

关键代码2,设置 gpio 电平

	if(tmp_dev->kbuf[0] == 1) // 如果读到 1,点亮 LED
	{
    
    
		gpio_set_value(LED_GPIO, 1); // LED_GPIO 拉高
	}
	else if(tmp_dev->kbuf[0] == 0) // 读到 0,熄灭 LED
	{
    
    
		gpio_set_value(LED_GPIO, 0); // LED_GPIO 拉低
	}

完整驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/gpio.h>

// GPIO0_B7 (bank = 0, group = 1(B), X = 7, pin = 0 * 32 + 1 * 8 + 7 = 15)
#define LED_GPIO 15 // LED GPIO 编号

// 定义一个私有数据结构体
struct my_device
{
    
    
	dev_t dev_num; // 设备号
	int major;     // 主设备号
	int minor;     // 次设备号
	struct cdev st_cdev;
	struct class *st_class;
	struct device *st_device;
	char kbuf[32];
};

// 定义一个全局私有数据结构体
struct my_device dev1;

// open()
static int  chrdev_open(struct inode *inode , struct file *file )
{
    
    
	file->private_data = &dev1; // 设置私有数据
	printk("chrdev_open.\n");
	return 0;
}

// close()
static int chrdev_release(struct inode *inode, struct file *file)
{
    
    
	printk("chrdev_release.\n");
	return 0;
}

// write()
static ssize_t chrdev_write(struct file *file , const char __user *buf, size_t size, loff_t *off)
{
    
    
	struct my_device *tmp_dev = (struct my_device*)file->private_data;

	int ret = copy_from_user(tmp_dev->kbuf, buf, size); // 从应用空间读取数据
	if(ret != 0)
	{
    
    
		printk("copy_from_user error.\r\n");
		return -1;
	}

	if(tmp_dev->kbuf[0] == 1) // 如果读到 1,点亮 LED
	{
    
    
		gpio_set_value(LED_GPIO, 1); // LED_GPIO 拉高
	}
	else if(tmp_dev->kbuf[0] == 0) // 读到 0,熄灭 LED
	{
    
    
		gpio_set_value(LED_GPIO, 0); // LED_GPIO 拉低
	}
	return 0;
}

// read()
static  ssize_t chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    
    
	printk("chrdev_read.\n");
	return 0;
}

static struct file_operations chrdev_fops = {
    
    
	.owner = THIS_MODULE, //将 owner 成员指向本模块,可以避免在模块的操作正在被使用时卸载该模块
	.open = chrdev_open,  //将 open 成员指向 chrdev_open()函数
	.read = chrdev_read,  //将 read 成员指向 chrdev_read()函数
	.write = chrdev_write,//将 write 字段指向 chrdev_write()函数
	.release = chrdev_release,//将 release 字段指向 chrdev_release()函数
};
// 驱动入口函数
static int __init chrdev_init(void)
{
    
    
	int ret;

	// 自动获取设备号(只申请一个,次设备号从 0 开始)
	ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "chrdev_test");
	if(ret < 0)
	{
    
    
		goto err_alloc;
	}
	printk("alloc chrdev region successfully.\n");
	dev1.major = MAJOR(dev1.dev_num); // 获取主设备号
	dev1.minor = MINOR(dev1.dev_num); // 获取次设备号
	printk("major is %d.\nminor is %d\n", dev1.major, dev1.minor);
	
	dev1.st_cdev.owner = THIS_MODULE; // 将 owner 成员指向本模块,可以避免模块 st_cdev 被使用时卸载模块
	cdev_init(&dev1.st_cdev, &chrdev_fops); // 初始化字符设备	
	ret = cdev_add(&dev1.st_cdev, dev1.dev_num, 1); // 将字符设备添加到系统
	if(ret < 0)
	{
    
    
		goto err_cdev_add;
	}
	printk("cdev add successfully.\n");
	
	dev1.st_class = class_create(THIS_MODULE, "chrdev_class"); // 创建设备类
	if(IS_ERR(dev1.st_class))
	{
    
    
		ret = PTR_ERR(dev1.st_class); // 返回错误码
		goto err_class_create;
	}
	
	dev1.st_device = device_create(dev1.st_class, NULL, dev1.dev_num, NULL, "chrdev_device"); // 创建设备
	if(IS_ERR(dev1.st_device))
	{
    
    
		ret = PTR_ERR(dev1.st_device); // 返回错误码
		goto err_device_create;
	}

	ret = gpio_is_valid(LED_GPIO); // 检查 gpio 是否可用
	if(ret < 0)
	{
    
    
		goto err_gpio_valid;
	}
	printk("%d: gpio is valid.\n", LED_GPIO);
	
	ret = gpio_request(LED_GPIO, "LED9"); // 请求 IO 资源
	if(ret < 0)
	{
    
    
		goto err_gpio_request;
	}
	printk("gpio request successfully.\n");

	ret = gpio_direction_output(LED_GPIO, 0); // 设置为输出模式
	if(ret < 0)
	{
    
    
		goto err_gpio_dir_out;
	}
	printk("set gpio output mode successfully.\n");
	
	return 0;
err_gpio_dir_out:
	gpio_free(LED_GPIO); // 释放 GPIO
err_gpio_request:
err_gpio_valid:
	// 无操作

err_device_create:
	class_destroy(dev1.st_class); // 删除类

err_class_create:
	cdev_del(&dev1.st_cdev); // 删除 cdev

err_cdev_add:
	unregister_chrdev_region(dev1.dev_num, 1); // 注销设备号

err_alloc:
	return ret; // 返回错误号

}

// 驱动出口函数
static void __exit chrdev_exit(void)
{
    
    
	gpio_free(LED_GPIO); // 释放 GPIO
	device_destroy(dev1.st_class, dev1.dev_num); // 删除设备
	class_destroy(dev1.st_class); //删除设备类
	cdev_del(&dev1.st_cdev); // 删除字符设备
	unregister_chrdev_region(dev1.dev_num, 1); // 注销设备号
	
	printk("chrdev_exit.\n");
}

module_init(chrdev_init);  //注册入口函数
module_exit(chrdev_exit);  //注册出口函数
MODULE_LICENSE("GPL v2");  //同意GPL协议
MODULE_AUTHOR("xiaohui");  //作者信息

应用代码、Makefile 及实物效果与上一个实验相同,这里不重复展示,下面是串口终端上的运行结果,

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43772810/article/details/129394668