NanoPi NEO Air使用八:编写个简单的驱动和应用程序

本文详细介绍了如何在NanoPiNEOAir开发板上进行驱动程序和应用程序的开发与测试。内容涵盖从固件烧录到GPIO、WiFi、蓝牙等硬件功能的使用,再到Linux内核驱动的编写和加载,最后通过Xfce和xrdp实现远程访问,并利用摄像头、SPI驱动等进行高级应用。通过示例代码展示了字符设备驱动的编写、编译及测试过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NanoPi NEO Air使用一:介绍
NanoPi NEO Air使用二:固件烧录
NanoPi NEO Air使用三:OverlayFS、CPU温度和频率、wifi、蓝牙、npi-config
NanoPi NEO Air使用四:操作GPIO
NanoPi NEO Air使用五:安装Xfce和xrdp,实现远程访问
NanoPi NEO Air使用六:使用摄像头
NanoPi NEO Air使用七:获取并编译U-boot和Linux的源码
NanoPi NEO Air使用八:编写个简单的驱动和应用程序
NanoPi NEO Air使用九:使用Linux内核自带的LED驱动
NanoPi NEO Air使用十:自己编写驱动来控制LED
NanoPi NEO Air使用十一:编写SPI驱动点亮TFT屏幕,ST7789V

在Ubuntu用户文件夹下新建个测试目录,比如我的linux源码目录为:/home/ql/linux/H3/linux
本驱动和应用程序源码放置的目录为/home/ql/linux/H3/MyDriver/01_chrdevbase
然后在此目录下添加3个文件:

驱动源文件

chrdevbase.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: chrdevbase.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: chrdevbase驱动文件。
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/1/30 左忠凯创建
***************************************************************/

#define CHRDEVBASE_MAJOR	200				/* 主设备号 */
#define CHRDEVBASE_NAME		"chrdevbase" 	/* 设备名     */

static char readbuf[100];		/* 读缓冲区 */
static char writebuf[100];		/* 写缓冲区 */
static char kerneldata[] = {"kernel data!"};

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase open!\r\n");
	return 0;
}

/*
 * @description		: 从设备读取数据 
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue = 0;
	
	/* 向用户空间发送数据 */
	memcpy(readbuf, kerneldata, sizeof(kerneldata));
	retvalue = copy_to_user(buf, readbuf, cnt);
	if(retvalue == 0){
		printk("kernel senddata ok!\r\n");
	}else{
		printk("kernel senddata failed!\r\n");
	}
	
	//printk("chrdevbase read!\r\n");
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	int retvalue = 0;
	/* 接收用户空间传递给内核的数据并且打印出来 */
	retvalue = copy_from_user(writebuf, buf, cnt);
	if(retvalue == 0){
		printk("kernel recevdata:%s\r\n", writebuf);
	}else{
		printk("kernel recevdata failed!\r\n");
	}
	
	//printk("chrdevbase write!\r\n");
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase release!\r\n");
	return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE,	
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

/*
 * @description	: 驱动入口函数 
 * @param 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
	int retvalue = 0;

	/* 注册字符设备驱动 */
	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	if(retvalue < 0){
		printk("chrdevbase driver register failed\r\n");
	}
	printk("chrdevbase init!\r\n");
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit chrdevbase_exit(void)
{
	/* 注销字符设备驱动 */
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("chrdevbase exit!\r\n");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

应用源文件

chrdevbaseApp.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: chrdevbaseApp.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: chrdevbase驱测试APP。
其他	   	: 使用方法:./chrdevbase /dev/chrdevbase <1>|<2>
  			 argv[2] 1:读文件
  			 argv[2] 2:写文件		
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/1/30 左忠凯创建
***************************************************************/

static char usrdata[] = {"usr data!"};

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	char readbuf[100], writebuf[100];

	if(argc != 3){
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开驱动文件 */
	fd  = open(filename, O_RDWR);
	if(fd < 0){
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

	if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */
		retvalue = read(fd, readbuf, 50);
		if(retvalue < 0){
			printf("read file %s failed!\r\n", filename);
		}else{
			/*  读取成功,打印出读取成功的数据 */
			printf("read data:%s\r\n",readbuf);
		}
	}

	if(atoi(argv[2]) == 2){
 	/* 向设备驱动写数据 */
		memcpy(writebuf, usrdata, sizeof(usrdata));
		retvalue = write(fd, writebuf, 50);
		if(retvalue < 0){
			printf("write file %s failed!\r\n", filename);
		}
	}

	/* 关闭设备 */
	retvalue = close(fd);
	if(retvalue < 0){
		printf("Can't close file %s\r\n", filename);
		return -1;
	}

	return 0;
}

Makefile

KERNELDIR := /home/ql/linux/H3/linux
CURRENT_PATH := $(shell pwd)
obj-m := chrdevbase.o

build: kernel_modules

kernel_modules:
	make ARCH=arm CROSS_COMPILE=arm-linux- -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux- -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译

上面3个文件添加好后:
在这里插入图片描述

编译驱动

进入驱动文件源码所在目录编译驱动:

cd /home/ql/linux/H3/MyDriver/01_chrdevbase
make

在这里插入图片描述
此时已编译出ko文件。

编译应用程序

再编译应用程序,执行

arm-linux-gcc chrdevbaseApp.c -o chrdevbaseApp

编译完成以后会生成一个叫做 chrdevbaseApp 的可执行程序,输入file chrdevbaseApp命令查看chrdevbaseAPP 这个程序的文件信息:
在这里插入图片描述

在开发板上测试运行

把编译好的chrdevbase.ko和chrdevbaseApp 通过SCP传到开发板上。

scp chrdevbase.ko root@192.168.0.103:/lib/modules/4.14.111/
scp chrdevbaseApp  root@192.168.0.103:/lib/modules/4.14.111/

在这里插入图片描述
在这里插入图片描述
使用如下命令更改控制台消息等级:

echo 5 >/proc/sys/kernel/printk
cat /proc/sys/kernel/printk

在这里插入图片描述

驱动模块加载

输入如下命令加载 chrdevbase.ko 驱动文件:

insmod chrdevbase.ko

在这里插入图片描述
可以看到有“chrdevbase init!”输出,这正是驱动入口函数的输出,说明驱动模块加载成功。

输入lsmod命令即可查看当前系统中存在的模块:
在这里插入图片描述
输入cat /proc/devices命令查看当前系统中有没有 chrdevbase 这个设备:
在这里插入图片描述
可以看出,当前系统存在 chrdevbase 这个设备,主设备号为 200,跟我们设置的主设备号一致。

应用程序测试

驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。输入如下命令创建/dev/chrdevbase 这个设备节点文件:
mknod /dev/chrdevbase c 200 0
其中“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这是个字符设备,“ 200”是设备的主设备号,“ 0”是设备的次设备号。创建完成以后就会存在/dev/chrdevbase 这个文件,可以使用ls /dev/chrdevbase -l命令查看。

Linux 下一切皆文件,因此可以像windows下一样对文件进行读写、打开、关闭操作。对文件的操作在应用程序中实现了,这里直接运行它即可。首先进行读操作,输入如下命令:
./chrdevbaseApp /dev/chrdevbase 1

在这里插入图片描述
接下来测试对 chrdevbase 设备的写操作,输入如下命令:
./chrdevbaseApp /dev/chrdevbase 2

在这里插入图片描述

卸载驱动模块

rmmod chrdevbase.ko
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qlexcel

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值