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

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

杂话

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


内核子系统三层概念

user	逻辑代码—————————————————————————————————————————————设备驱动层(今天的驱动在这一层)       通过各个子系统  完成对硬件的操作 并向上层提供接口                              驱动工程师______________________________________________________________________________
kernel核心层(内核维护者)      屏蔽底层细节          向上层提供接口  各类子系统api               内核工程师———————————————————————————————————————厂商驱动层(瑞芯微)   各个厂商针对不同硬件的操作  包括内存映射     						厂商—————————————————————————————————————————————
HARDWAREmotor  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@fe6c0000image_led                        serial@fe6d0000interrupt-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"
返回值:成功 返回结构体首地址失败 返回NULLstatic inline struct device_node *of_find_node_by_name(struct device_node *from,const char *name)功能:通过节点名获取节点
参数:from 填NULL 从根节点搜索name 节点名
返回值:成功 返回结构体首地址失败 返回NULLstatic inline struct device_node *of_find_compatible_node(struct device_node *fromconst char *type,const char *compat)
功能:通过compatible获取节点
参数:from 填NULL 从根节点搜索type 填NULcompat 厂商名 设备名
返回值:成功 返回结构体首地址失败 返回NULL获取属性相关的API
static inline struct property *of_find_property(const struct device_node *np,const char *name,int *lenp)
功能:获取键值对
参数:np device_node结构体首地址name 键的名字lenp 值的长度 单位字节
返回值:成功 返回结构体首地址失败 返回NULLstatic 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 cleanrm 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.rhkb.cn/news/496491.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

文档解析丨高效准确的PDF解析工具,赋能企业非结构化数据治理

在数据为王的时代浪潮中&#xff0c;企业数据治理已成为组织优化运营、提高竞争力的关键。随着数字化进程的加速&#xff0c;企业所积累的数据量呈爆炸式增长&#xff0c;数据类型也愈发多样化&#xff0c;这些数据构成了现代企业数据资产的重要组成部分。 然而&#xff0c;传…

Maven项目中不修改 pom.xml 状况下直接运行OpenRewrite的配方

在Java 的Maven项目中&#xff0c;可以在pom.xml 中配置插件用来运行OpenRewrite的Recipe&#xff0c;但是有一些场景是希望不修改pom.xml 文件就可以运行Recipe&#xff0c;比如&#xff1a; 因为不需要经常运行 OpenRewrite&#xff0c;所以不想在pom.xml 加入不常使用的插件…

windows使用zip包安装MySQL

windows通过zip包安装MySQL windows通过zip包安装MySQL下载MySQL的zip安装包创建安装目录和数据目录解压zip安装包创建配置目录 etc 和 配置文件 my.ini安装MySQL进入解压后的bin目录执行命令初始化执行命令安装 验证安装查看服务已安装 启动MySQL查看服务运行情况修改密码创建…

书签管理工具的使用技巧

分类与筛选技巧 多层级分类&#xff1a;创建多层级的文件夹结构&#xff0c;如先按大的主题分类&#xff0c;再在每个主题下细分小类。例如&#xff0c;先创建 “工作”“学习”“生活” 等大文件夹&#xff0c;在 “工作” 文件夹下再细分 “项目文档”“办公软件”“行业资讯…

Spring API 接口加密/解密

API 接口加密/解密 为了安全性需要对接口的数据进行加密处理&#xff0c;不能明文暴露数据。为此应该对接口进行加密/解密处理&#xff0c;对于接口的行为&#xff0c;分别有&#xff1a; 入参&#xff0c;对传过来的加密参数解密。接口处理客户端提交的参数时候&#xff0c;…

CKA认证 | Day7 K8s存储

第七章 Kubernetes存储 1、数据卷与数据持久卷 为什么需要数据卷&#xff1f; 容器中的文件在磁盘上是临时存放的&#xff0c;这给容器中运行比较重要的应用程序带来一些问题。 问题1&#xff1a;当容器升级或者崩溃时&#xff0c;kubelet会重建容器&#xff0c;容器内文件会…

C/C++ 数据结构与算法【树和森林】 树和森林 详细解析【日常学习,考研必备】带图+详细代码

一、树的存储结构 1&#xff09;双亲表示法实现&#xff1a; 定义结构数组存放树的结点&#xff0c;每个结点含两个域: 数据域&#xff1a;存放结点本身信息。双亲域&#xff1a;指示本结点的双亲结点在数组中的位置。 特点&#xff1a;找双亲简单&#xff0c;找孩子难 C语…

flask后端开发(11):User模型创建+注册页面模板渲染

目录 一、数据库创建和配置信息1.新建数据库2.数据库配置信息3.User表4.ORM迁移 二、注册页面模板渲染1.导入静态文件2.蓝图注册路由 一、数据库创建和配置信息 1.新建数据库 终端中 CREATE DATABASE zhiliaooa DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;2…

通过 Ansys Electronics Desktop 中的高级仿真优化 IC 设计

半导体行业继续通过日益复杂的集成电路 (IC) 设计突破技术界限。随着工艺节点缩小和电路密度达到前所未有的水平&#xff0c;电磁效应对设备性能和可靠性变得越来越重要。现代 IC 设计面临着来自复杂的布局相关耦合机制、信号完整性问题和功率分布问题的挑战&#xff0c;这些问…

Android OpenGl(二) Shader

一、Shader 1、什么是Shader&#xff0c;为什么要使用Shder &#xff08;1&#xff09;shader运行在gpu上的小程序 &#xff08;2&#xff09;以前使用固定管线&#xff0c;但缺点是灵活度不够&#xff0c;无法满足复杂需求&#xff0c;为了解决固定管线的缺点&#xff0c;出…

Vue(四)

1.Vuex 1.1 Vuex是什么 Vuex 是一个插件&#xff0c;可以帮我们管理 Vue 通用的数据。例如&#xff1a;购物车数据、个人信息数据。 1.2 vuex的使用 1.安装 vuex 安装 vuex 与 vue-router 类似&#xff0c;vuex 是一个独立存在的插件&#xff0c;如果脚手架初始化没有选 v…

【已解决】pyinstaller打包ico图片报错:OSError: [WinError 225] 无法成功完成操作,因为文件包含病毒或潜在的垃圾软件。

起因&#xff1a; pyinstaller加上 --icon 参数打包时报错。 命令如下&#xff1a; 解决&#xff1a; 关闭 Windows 的病毒防护即可&#xff0c;步骤如下。 点屏幕右下角通知栏&#xff0c;进入“病毒和威胁防护”&#xff1a; 打开&#xff1a; 关闭实时保护&#xff08…

多旋翼无人机理论 | 四旋翼动力学数学模型与Matlab仿真

多旋翼无人机理论 | 四旋翼动力学数学模型与Matlab仿真 力的来源数学模型数学模型总结Matlab 仿真 力的来源 无人机的动力系统&#xff1a;电调-电机-螺旋桨 。 给人最直观的感受就是 电机带动螺旋桨转&#xff0c;产生升力。 螺旋桨旋转产生升力的原因&#xff0c;在很多年…

Vue中动态样式绑定+CSS变量实现切换明暗主题功能——从入门到进阶

1.直接借助Vue的动态绑定样式绑定 Vue动态样式绑定 在Vue中&#xff0c;动态样式绑定是一种强大的功能&#xff0c;它允许开发者根据数据的变化动态地更新元素的样式。以下是对Vue动态样式绑定的详细知识梳理与详解&#xff1a; 一、基础知识 Vue的动态样式绑定主要通过v-b…

智能家居实训室中,STC单片机驱动的“互联网+”智能家居系统设计

一、引言 随着经济的快速发展&#xff0c;人们对家居环境的智能化、网络化需求日益增强&#xff0c;智能家居的研究也因此受到了国内外相关机构的广泛关注。STC单片机凭借其卓越的性能和广泛的应用领域&#xff0c;成为了智能家居系统设计的优选方案。作为一种先进的微控制器&…

计算机网络——期末复习(3)4-6章考试重点

第四章 根据IPv4第1个十进制数值判断&#xff0c;127以下为A类&#xff0c;128~191为B类&#xff0c;192~223为C类不能分配给主机或路由器接口的&#xff1a;A类网络号0和127&#xff0c;主机号全为0或全为1私有地址&#xff08;Private IP Address&#xff09;是指一类专门保…

内置ALC的前置放大器D2538A/D3308

一、概述 D2538A/D3308是芯谷科技推出的带有ALC&#xff08;自动电平控制&#xff09;的前置音频放大器芯片&#xff0c;最初产品为单声道/立体声收录机及盒式录音机而开发&#xff0c;作为录音/回放的磁头放大器使用&#xff1b;由于产品的高增益、低噪声及ALC外部可调的特性&…

【玩转MacBook】Git安装

Git 官网也提到了MacBook 可以使用 Homebrew 安装 Git&#xff0c;所以在此使用 Homebrew 安装。 1、安装 Homebrew 执行安装脚本 在 Terminal 中执行如下命令&#xff1a; /bin/bash -c "$(curl -fsSL https://gitee.com/ineo6/homebrew-install/raw/master/install.…

Speckly:基于Speckle文档的RAG智能问答机器人

前言 Speckly 是一个基于 检索增强生成 (RAG) 技术的智能问答机器人&#xff0c;它能像一位经验丰富的工程师&#xff0c;理解你的问题&#xff0c;并从 Speckle 文档中精准地找到答案。更厉害的是&#xff0c;它甚至可以帮你生成代码片段&#xff01;&#x1f680; 本文将详…

Excel无法插入新单元格怎么办?有解决方法吗?

在使用Excel时&#xff0c;有时会遇到无法插入新单元格的困扰。这可能是由于多种原因导致的&#xff0c;比如单元格被保护、冻结窗格、合并单元格等。本文将详细介绍3种可能的解决方案&#xff0c;帮助你顺利插入新单元格。 一、消冻结窗格 冻结窗格功能有助于在滚动工作表时保…