泰山派GPIO子系统驱动---亮灯

news2025/1/2 22:29:40

本人linux驱动小白,文章基于B站up主 李Sir______ 视频内容记录,做笔记用。如有错误欢迎指正。本文将以开发板第40引脚GPIO3_B4作为LED灯珠的控制引脚,高电平灯亮,低电平灯灭。

杂话

linux内核中,芯片厂商已经把所有控制器的设备树编写好了。硬件层与子系统的API也都适配好无需使用者关心。我们编写驱动只需要将自己所需要的硬件资源与厂商定义好的设备树匹配,在驱动程序中调用相应子系统API函数即可写出一份自己的GPIO驱动程序。相比传统无官方库支持的单片机需操作寄存器来操作底层,程序可移植性大大提高。


内核子系统三层概念

user	逻辑代码

—————————————————————————————————————————————

			设备驱动层(今天的驱动在这一层)       通过各个子系统  完成对硬件的操作 并向上层提供接口                              驱动工程师
			______________________________________________________________________________
kernel
			核心层(内核维护者)      屏蔽底层细节          向上层提供接口  各类子系统api               内核工程师

			———————————————————————————————————————

			厂商驱动层(瑞芯微)   各个厂商针对不同硬件的操作  包括内存映射     						厂商

—————————————————————————————————————————————
HARDWARE

		motor  led  lcd ...

厂商的设备树文件

厂商已经把所有的控制器设备树信息都提供了,下面是与gpio相关的一些文件所在位置。

path:kenel\arch\arm64\boot\dts\rockchip\rk3568.dtsi
...
gpio3: gpio@fe760000 {//节点名
	compatible = "rockchip,gpio-bank"; //厂商名  设备名
	reg = <0x0 0xfe760000 0x0 0x100>; //地址  0x0 0xfe760000控制器地址   0x0 0x100地址范围
	interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;//中断
	clocks = <&cru PCLK_GPIO3>, <&cru DBCLK_GPIO3>;//时钟

	gpio-controller;//标识
	#gpio-cells = <2>;//描述子节点个数
	gpio-ranges = <&pinctrl 0 96 32>; //引脚范围 &pinctrl 引用pinctrl节点 0    96 GPIO起始序号  32引脚范围
	interrupt-controller;
	#interrupt-cells = <2>;//描述子节点个数
};
...
//一些宏厂商也已经做出定义
path:kernel\include\dt-binding\pinctrl\rockchip.h
path:kernel\include\dt-binding\pinctrl\pinctrl-amd.h
path:kernel\include\dt-binding\gpio\gpio.h

gpio设备树书写规则

[names]-gpios=<控制器地址 引脚编号 高/低电平>;

设备树中添加我们的设备

仿照设备树中的节点,添加一个我们自己的节点,位置在根节点处

path:kenel\arch\arm64\boot\dts\tspi-rk3566-user-v10-linux.dts
..............
/ {
	model = "lckfb tspi V10 Board";
	compatible = "lckfb,tspi-v10", "rockchip,rk3566";
	//自己新建的led灯的设备树 
	image_led{
		compatible = "image,led";
		status = "okay";
		led_gpio=<&gpio3 RK_PB4 GPIO_ACTIVE_LOW>;
	};   
	rk_headset: rk-headset {
		compatible = "rockchip_headset";
		headset_gpio = <&gpio0 RK_PC5 GPIO_ACTIVE_HIGH>;
		pinctrl-names = .......
		.....

编译内核,将boot.img文件烧录到开发板

./build.sh kernel

查看添加的设备树节点是否存在

root@RK356X:/tmp# ls  /proc/device-tree
	...........
 iep@fdef0000                     serial@fe6c0000
 image_led                        serial@fe6d0000
 interrupt-controller@fd400000    sfc@fe300000
 ..........

GPIO相关的API

static inline int of_get_named_gpio(struct device_node *np, const char *propname, int index)
功能:获取gpio编号
参数:
		np 结构体指针
		propname 属性名
		index 编号
返回值:
		成功 返回gpio编号
		失败 返回错误码


static inline int gpio_request(unsigned gpio, const char *label)
功能:申请gpio
参数:
		gpio   GPIO编号
		label  标签 NULL
返回值:
		成功 返回0
		失败 返回错误码
static inline int gpio_direction_input(unsigned gpio)
功能:设置gpio为输入
参数:
		gpio   GPIO编号
返回值:
		成功 返回0
		失败 返回错误码


static inline int gpio_direction_output(unsigned gpio, int value)
功能:设置gpio为输出
参数:
		gpio   GPIO编号
		value  输出电平       1高电平     0是低电平
返回值:
		成功 返回0
		失败 返回错误码

static inline void gpio_set_value(unsigned int gpio, int value)
功能:设置gpio输出电平
参数:
		gpio   GPIO编号
		value  输出电平       1高电平     0是低电平
返回值:
		成功 返回0
		失败 返回错误码


static inline int gpio_get_value(unsigned int gpio)
功能:获取gpio电平状态
参数:
		gpio   GPIO编号
返回值:
		1  是高电平
		0  是低电平


static inline void gpio_free(unsigned gpio)
功能:释放gpio
参数:
		gpio   GPIO编号
返回值:
		无

解析设备树相关API

device_node 结构体讲解
struct device_node {
	const char *name;  //节点名
	#const char *type;  //节点类型
	#phandle phandle; //唯一标识
	const char *full_name;  //节点全名
	#struct fwnode_handle fwnode; //用于支持不同设备

	struct	property *properties;  //设备数属性键值对的链表
	#struct	property *deadprops;	/* removed properties */ 链表头
	struct	device_node *parent; //父节点
	struct	device_node *child;  //子节点
	struct	device_node *sibling; //兄弟节点
#if defined(CONFIG_OF_KOBJ)
	#struct	kobject kobj;//内核对象
#endif
	#unsigned long _flags;//状态标志
	#void	*data;//节点相关的私有数据
#if defined(CONFIG_SPARC)
	#const char *path_component_name;//表示设备节点路径的一部分
	#unsigned int unique_id;//唯一ID
	#struct of_irq_controller *irq_trans;//中断控制器结构体指针
#endif
};


property结构体
struct property {
	char	*name;//键的名字
	int	length;  //值长度
	void	*value; //值  注意  强转
	struct property *next; //下一个键值对的property结构体指针
};

static inline struct device_node *of_find_node_by_path(const char *path)
功能:通过路径获取节点
参数:
		path  路径  "/image_led"
返回值:
		成功 返回结构体首地址
		失败 返回NULL


static inline struct device_node *of_find_node_by_name(struct device_node *from,const char *name)

功能:通过节点名获取节点
参数:
		from 填NULL 从根节点搜索
		name 节点名
返回值:
		成功 返回结构体首地址
		失败 返回NULL

static inline struct device_node *of_find_compatible_node(struct device_node *fromconst char *type,const char *compat)
功能:通过compatible获取节点
参数:
		from 填NULL 从根节点搜索
		type 填NUL
		compat 厂商名 设备名
返回值:
		成功 返回结构体首地址
		失败 返回NULL

获取属性相关的API
static inline struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
功能:获取键值对
参数:
		np device_node结构体首地址
		name 键的名字
		lenp 值的长度 单位字节
返回值:
		成功 返回结构体首地址
		失败 返回NULL
		
static inline int of_property_read_u8_array(const struct device_node *np,const char *propname, u8 *out_values, size_t sz)
功能:获取单字节数据数组
参数:
		np device_node结构体首地址
		propname 键的名字
		out_values 获取到的数据
		sz 值的个数
返回值:
		成功 返回0
		失败 返回错误码


static inline int of_property_read_u32_index(const struct device_node *np,const char *propname, u32 index, u32 *out_value)
功能:获取32位数值
参数:
		np device_node结构体首地址
		propname 键的名字
		index 索引号 下标
		out_values 获取到的数据
返回值:
		成功 返回0
		失败 返回错误码

static inline int of_property_read_string(const struct device_node *np, const char *propname, const char **out_string)
功能:获取字符串
参数:
		np device_node结构体首地址
		propname 键的名字
		index 索引号 下标
		out_string 获取到的字符串
返回值:
		成功 返回0
		失败 返回错误码

编写的驱动文件

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>

//字符设备名 sys/class 描述名称
#define CDEV_NAME         "image_led"
//设备树中 自定义led节点路径
#define LED_NODE_PATH     "/image_led"
//自定义设备树节点中 led引脚名称
#define LED_NODE_PROPERTY "led_gpio"

int major = -1;
int gpionum = -1;
struct class *cls = NULL;
struct device *dev = NULL;
struct device_node *led_node = NULL;  //设备树节点结构体
char kbuf[128] = {0};

int my_led_open(struct inode *inode, struct file *file)
{
	printk("device:image_led is open\r\n");
	return 0;
}

ssize_t my_led_read (struct file *file, char __user *ubuf, size_t size, loff_t *offset)
{
	char level = 0;
	if(size > 1)
	{
		size = 1;
	}
	level = gpio_get_value(gpionum);
	if(copy_to_user(ubuf, &level, size))
	{
		printk("copy data from user failed!\n");
		return -EINVAL;
	}
	printk("device:image_led cur level is %d\r\n",level);
	return size;
}
ssize_t my_led_write (struct file *file, const char __user *ubuf, size_t size, loff_t *offset)
{
	size = size > sizeof(kbuf)  ? sizeof(kbuf) : size;
	if(copy_from_user(kbuf, ubuf, size))
	{
		printk("copy data from user failed!\n");
		return -EINVAL;
	}
	if(kbuf[0] == '1')
	{
		gpio_set_value(gpionum,1); //设置高电平
		printk("device:image_led set level is %d\r\n",kbuf[0]);
	}
	else
	{
		gpio_set_value(gpionum,0); //设置低电平
		printk("device:image_led set level is %d\r\n",kbuf[0]);
	}
	return size;
}
int my_led_close (struct inode *inode, struct file *file)
{
	printk("device:image_led is close\r\n");
	return 0;
}

struct file_operations fops = {
	.open    = my_led_open,
	.read    = my_led_read,
	.write   = my_led_write,
	.release = my_led_close
};

int led_gpio_init(void)
{
	//读取设备树上节点路径获取节点数据
	led_node = of_find_node_by_path(LED_NODE_PATH);
	if(led_node == NULL)
	{
		printk("find node %s failed!\n",LED_NODE_PATH );
		return -ENODEV;
	}

	//通过节点数据中的名称获取gpio编号
	gpionum = of_get_named_gpio(led_node, LED_NODE_PROPERTY, 0);
	if(gpionum < 0)
	{
		printk("get property %s failed!\n",LED_NODE_PROPERTY );
		return -EINVAL;
	}

	//申请GPIO 防止占用冲突
	if(gpio_request(gpionum,NULL))
	{
		printk("request gpio failed!\n");
		return -EINVAL;
	}
	//设置GPIO为输出
	if(gpio_direction_output(gpionum, 0))
	{
		printk("set gpio direction failed!\n");
		return -EINVAL;
	}
	return 0;
}
void led_gpio_deinit(void)
{
	gpio_set_value(gpionum,0);
	gpio_free(gpionum);
}
static int __init image_led_init(void)
{
	//注册字符设备
	major = register_chrdev(0,CDEV_NAME,&fops);
	if(major < 0)
	{
		printk("register chardev failed! \n");
		return -1;
	}
	
	//自动创建设备节点
	//提交目录信息
	cls = class_create(THIS_MODULE,CDEV_NAME);
	if(IS_ERR(cls))
	{
		printk("auto create node failed! \n");
		goto del_cdev; //
	}
	
	//提交设备信息
	dev = device_create(cls,NULL,MKDEV(major,0),NULL,"image_led0");
	
	if(IS_ERR(dev))
	{
		printk("device create failed! \n");
		goto destroy_class;
	}
	if(led_gpio_init()<0)
	{
		goto free_gpio;
	}
    return 0;

free_gpio:
	led_gpio_deinit();
destroy_class:
	class_destroy(cls);
del_cdev:
	unregister_chrdev(major,CDEV_NAME);
return -EIO;
}


static void __exit image_led_exit(void)
{
	led_gpio_deinit();
	device_destroy(cls, MKDEV(major,0));
	class_destroy(cls);
	unregister_chrdev(major,CDEV_NAME);
}

module_init(image_led_init);
module_exit(image_led_exit);
MODULE_LICENSE("GPL");

APP测试文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#define MY_CHRDEV_NAME "/dev/image_led0"

int fd = 0;
int main(void)
{
    char light_cmd = '1';
    char off_cmd = '0';
    char count = 0;
    char read_buf[1] = {0};
    fd = open(MY_CHRDEV_NAME,O_RDWR);
    if(fd < 0)
    {
        printf("open %s is failed fd:%d\r\n",MY_CHRDEV_NAME,fd);
        return -1;
    }
    for(count=0;count<10;count++)
    {
        write(fd,&light_cmd,1);
        printf("cmd:%c\r\n",light_cmd);
        usleep(500*1000);
        read(fd,read_buf,1);
        printf("read gpio level is %d\r\n",read_buf[0]);
        
        write(fd,&off_cmd,1);
        printf("cmd:%c\r\n",off_cmd);
        usleep(500*1000);
        read(fd,read_buf,1);
        printf("read gpio level is %d\r\n",read_buf[0]);
    }
    close(fd);
    return 0;
}

Makefile文件

PWD ?= $(shell pwd)
KERNELDIR:=/home/image/work/tspi/sdk/kernel
CROSS_COMPILE ?= /home/image/work/tspi/sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
obj-m += demo.o
CC := $(CROSS_COMPILE)gcc
module:
	make -C $(KERNELDIR) M=$(PWD) ARCH=arm64 modules
	$(CC) test_app.c -o test_app
clean:
	make -C $(KERNELDIR) M=$(PWD) ARCH=arm64 clean
	rm test_app

APP执行效果

root@RK356X:/tmp# ./test_app
cmd:1
read gpio level is 1
cmd:0
read gpio level is 0
cmd:1
read gpio level is 1
cmd:0
read gpio level is 0
cmd:1
read gpio level is 1
cmd:0
read gpio level is 0
cmd:1
..............

开发板打印调试信息

root@RK356X:/tmp# dmesg
[ 6757.176165] device:image_led is open
[ 6757.176265] device:image_led set level is 49
[ 6757.676878] device:image_led cur level is 1
[ 6757.677111] device:image_led set level is 48
[ 6758.177583] device:image_led cur level is 0
[ 6758.177804] device:image_led set level is 49
[ 6758.678220] device:image_led cur level is 1
[ 6758.678423] device:image_led set level is 48
[ 6759.178801] device:image_led cur level is 0
[ 6759.178988] device:image_led set level is 49
[ 6759.679358] device:image_led cur level is 1
[ 6759.679582] device:image_led set level is 48
[ 6760.179943] device:image_led cur level is 0
[ 6760.180143] device:image_led set level is 49
[ 6760.680495] device:image_led cur level is 1
[ 6760.680716] device:image_led set level is 48
[ 6761.181149] device:image_led cur level is 0
[ 6761.181349] device:image_led set level is 49
[ 6761.681747] device:image_led cur level
..................
//48 49为ASCII码的0 和 1这里打印没处理好

开发板运行效果

开发板运行效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2267963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

探索仓颉编程语言:功能、实战与展望

目录 引言 一.使用体验 二.功能剖析 1.丰富的数据类型与控制结构 2.强大的编程范式支持 3.标准库与模块系统 4.并发编程能力 三.实战案例 1.项目背景与目标 2.具体实现步骤 (1).导入必要的模块 (2).发送 HTTP 请求获取网页内容 (3).解析 HTML 页面提取文章信息 (…

JavaFX FXML模式下的布局

常见布局方式概述 在 JavaFX FXML 模式下&#xff0c;有多种布局方式可供选择。这些布局方式可以帮助您有效地组织和排列 UI 组件&#xff0c;以创建出美观且功能良好的用户界面。常用布局容器及布局方式 BorderPane 布局 特点&#xff1a;BorderPane 将空间划分为五个区域&…

OpenFeign介绍以及使用

介绍 OpenFeign 是一个声明式的 Web 服务客户端&#xff0c;用于简化在 Java 应用中调用 HTTP API 的过程&#xff0c;在 Spring Cloud 体系里被广泛应用&#xff0c;它有以下关键特性&#xff1a; 声明式调用&#xff1a;基于注解&#xff0c;开发人员只需定义接口并添加注解…

李永乐线性代数:A可逆,AX=B相关推论和例题解题思路

例题1&#xff1a; 思路讲解&#xff1a; 这个 (A-2E)可逆,所以有P(A-2E) E&#xff0c; 也就是(A-2E)的逆矩阵是P&#xff1b; 那么PA (A-2E)的逆 * A B P(A-2E,A)(E,B) 所以就可以直接求出B&#xff0c;也就是(A-2E)的逆 * A 例题2&#xff1a; 思路讲解&#xff1a;…

【Compose multiplatform教程18】多平台资源的设置和配置

要正确配置项目以使用多平台资源&#xff0c;请执行以下操作&#xff1a; 添加库依赖项。 为每种资源创建必要的目录。 为限定资源创建其他目录&#xff08;例如&#xff0c;深色 UI 主题或本地化字符串的不同图像&#xff09;。 依赖项和目录设置 要访问多平台项目中的资源…

Doris的SQL原理解析

今天来介绍下Doris的SQL原理解析&#xff0c;主要从语法、解析、分析、执行等几个方面来介绍&#xff0c;可以帮助大家对Doris底层有个清晰的理解~ 一、Doris简介 Apache Doris是一个基于MPP架构的高性能、实时的分析型数据库&#xff0c;能够较好的满足报表分析、即席查询、…

Excel for Finance 07 `FV PV` 函数

Excel 的 FV 函数用于计算一笔投资在未来的价值&#xff0c;基于固定的利率和定期付款。这是一个金融函数&#xff0c;常用来分析储蓄计划、贷款、或投资的增长。 语法&#xff1a; FV(rate, nper, pmt, [pv], [type])参数说明&#xff1a; rate&#xff08;必需&#xff09;&…

【运维】部署Gitea

部署Gitea Gitea文档 系统&#xff1a;Ubuntu 20.04.6 LTS 步骤&#xff1a; 准备数据库 使用内置 SQLite&#xff0c;无需额外准备。 下载安装 下载最新版本的 Gitea 并安装&#xff1a; wget -O gitea https://dl.gitea.com/gitea/version/gitea-version-linux-amd64 chm…

Redis KEYS查询大批量数据替代方案(推荐SCAN 命令)

文章目录 前言KEYS命令问题背景替代方案1.使用 SCAN 命令2. 使用有序集合&#xff08;Sorted Set&#xff09;3. 使用哈希&#xff08;Hash&#xff09;4. 使用 Redis 模块&#xff08;如 RediSearch&#xff09; 总结 前言 在使用 Redis 时&#xff0c;KEYS 命令虽然简单直接…

Apache Doris 创始人:何为“现代化”的数据仓库?

在 12 月 14 日的 Doris Summit Asia 2024 上&#xff0c;Apache Doris 创始人 & PMC 成员马如悦在开场演讲中&#xff0c;围绕“现代化数据仓库”这一主题&#xff0c;指出 3.0 版本是 Apache Doris 研发路程中的重要里程碑&#xff0c;他将这一进展总结为“实时之路”、“…

3. 指针、数组

目录 一、指针和数组 &#x1f350; 数组名指向首地址 &#x1f34a; 例子 二、数组作为函数参数 &#x1f34b; 数组名作为函数参数&#xff0c;为什么必须传递数组大小&#xff1f; 三、指针和字符数组 &#x1f34c;怎么样存储一个string&#xff1f; &#x1f349…

upload-labs关卡记录14

让上传图片马&#xff0c;并且三种后缀都要上传成功才算成功&#xff1a; 先试试gif的吧&#xff1a; 可以上传&#xff0c;同理&#xff1a;查看源码 只检查了两个字节&#xff0c;我们直接修改一句话木马&#xff0c;先改后缀php为png&#xff0c;然后winhex修改头部就完了 …

前端(htmlcss)

前端页面 Web页面 PC端程序页面 移动端APP页面 ... HTML页面 HTML超文本标记页面 超文本&#xff1a;文本&#xff0c;声音&#xff0c;图片&#xff0c;视频&#xff0c;表格&#xff0c;链接 标记&#xff1a;由许多标签组成 HTML页面运行到浏览器上面 vscode便捷插件使用 vs…

HTML——16.相对路径

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title></head><body><a href"../../fj1/fj2/c.html" target"_blank">链接到c</a><!--相对路径&#xff1a;-->…

coturn docker 项目 搭建【一切正常】

业务需求&#xff1a;需要coturn这个服务 定制语音视频连线 请参考"小红的逃脱外星人追踪计划" coturn项目 本地测试连接服务 turnutils_stunclient -p 3478 127.0.0.1turnutils_stunclient -p 3478 -L 127.0.0.1 127.0.0.1telnet localhost 3478turnutils_uclient …

【回溯】LeetCode经典题目总结:组合、排列、子集、分割、N皇后、单词搜索

回溯 组合问题组合总和全排列子集分割回文串N皇后电话号码的字母组合单词搜索括号生成 组合问题 给定两个整数 n 和 k&#xff0c;返回 1 … n 中所有可能的 k 个数的组合。 示例: 输入: n 4, k 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ] 树形结构&#xff1…

Linux-frp_0.61.1内网穿透的配置和使用

下载frp frp官网 https://gofrp.org/zh-cn/docs/setup/ frp安装包下载地址 https://github.com/fatedier/frp/releases?page1 下载之后在服务器上 解压 tar -zxvf frp_0.61.1_linux_amd64.tar.gztar&#xff1a;一个用于压缩和解压缩的工具。-z&#xff1a;表示使用 gzi…

WEB攻防-通用漏洞-文件上传-js验证-MIME验证-user.ini-语言特征

目录 定义 1.前端验证 2.MIME验证 3.htaccess文件和.user. ini 4.对内容进行了过滤&#xff0c;做了内容检测 5.[ ]符号过滤 6.内容检测php [] {} ; 7.()也被过滤了 8.反引号也被过滤 9.文件头检测 定义 文件上传漏洞是指攻击者上传了一个可执行文件&#xff08;如木马…

Excel基础知识

一&#xff1a;数组 一行或者一列数据称为一维数组&#xff0c;多行多列称为二维数组&#xff0c;数组支持算术运算&#xff08;如加减乘除等&#xff09;。 行&#xff1a;{1,2,3,4} 数组中的每个值用逗号分隔列&#xff1a;{1;2;3;4} 数组中的每个值用分号分隔行列&#xf…

快速下载pytorch_geometric

注意&#xff1a;千万不要一上去就使用pip去安装&#xff01;&#xff01;&#xff01; 1.找到GitHub手动下载所需依赖: https://github.com/pyg-team/pytorch_geometric 进入网址后点击此处&#xff1a; 2.点击here进去后寻找自己的torch版本&#xff08;我的是torch2.1.2的…