基于jetson nano,自定义内核模块控制LED亮灭,从应用层到内核层再到硬件层,实现流水灯效果

1、起因

学习内核驱动的过程中,希望进一步掌握字符设备框架;基于此,简单做一个可以从应用层调用,经过内核层,并在硬件层呈现流水灯(三个LED灯分别接在 Pin 12/13/19 )效果的小实验。

2、关键点分析

(1)通过阅读Tegra_X1芯片手册,确定所有需要写寄存器的地址(这里要注意板子上的引脚标号与芯片上的标号的对应关系,进而才能找到引脚所对应的控制寄存器)。这里需要写的寄存器有:引脚复用控制寄存器(配置目标引脚的功能为GPIO)和GPIO控制寄存器(包括CNF/OE/OUT等,主要用于配置该组GPIO8个引脚中的某个具体引脚);
(2)硬件层的地址如何在内核层中被使用?需要用到内核所提供的ioremap函数,该函数的输入为:硬件起始地址映射范围大小,返回值为 映射后得到的虚拟地址
(3)当内核层与硬件层的通道被打通后,下面考虑应用层如何到内核层。我们知道在linux中,一切设备皆为文件,将LED设备看做为文件,对应一些操作为:

  • 1)注册设备;
  • 2)将字符设备与文件操作绑定;
  • 3)将字符设备注册到内核中;
  • 4)在应用层使用mknod /dev/led c 500 0 在 Linux 系统中创建一个设备文件 /dev/led,并将其与字符设备类型(c)以及指定的设备号(主设备号 500,次设备号 0)关联;
3、流水灯代码(编译后生成led.ko)

(1) led.c文件

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/io.h>

#define LED_MA 500 //主设备号,区分不同设备类别
#define LED_MI 0   //次设备号

#define LED_MAGIC 'L'
#define LED0_ON _IOW(LED_MAGIC, 0, int) //使用幻术定义一个对应 LED0 的唯一操作数
#define LED0_OFF _IOW(LED_MAGIC, 1, int)
#define LED1_ON _IOW(LED_MAGIC, 2, int) 
#define LED1_OFF _IOW(LED_MAGIC, 3, int)
#define LED2_ON _IOW(LED_MAGIC, 4, int) 
#define LED2_OFF _IOW(LED_MAGIC, 5, int)

#define PINMUX_AUX_DAP4_SCLK_0 0x70003150 //管脚复用寄存器地址(12引脚)
#define PINMUX_AUX_SPI2_SCK_0 0x7000306c //定义第二个灯 管脚复用寄存器地址(13引脚)
#define PINMUX_AUX_SPI1_MOSI_0 0x70003050 //定义第三个灯 管脚复用寄存器地址(19引脚)

#define GPIO1 0x6000D000 //定义GPIO1寄存器的基地址

#define GPIO3 0x6000D200 //定义GPIO3寄存器的基地址
#define CNF 0x04 	//下面几个宏 定义了J组GPIO引脚的基本配置寄存器偏移地址(C组和J组相同)
#define OE 0x14
#define OUT 0x24	
#define MSK_CNF 0x84 
#define MSK_OE 0x94
#define MSK_OUT 0xA4

#define CNF_ 0x08 	//下面几个宏 定义了C组GPIO引脚的基本配置寄存器偏移地址
#define OE_ 0x18
#define OUT_ 0x28	
#define MSK_CNF_ 0x88 
#define MSK_OE_ 0x98
#define MSK_OUT_ 0xA8


struct cdev led_zhan; //声明字符设备

unsigned char *gpio_base0; //声明字符型指针,用来存放 ioremap()的返回值,gpio_base0 对应第一个灯的gpio控制器的基地址
unsigned char *gpio_pinmux0; //gpio_pinmux0 对应第一个灯的 管脚复用控制器的地址
unsigned char *gpio_base1;
unsigned char *gpio_pinmux1;
unsigned char *gpio_base2;
unsigned char *gpio_pinmux2;

int led_open(struct inode* innod, struct file * file) //打印模块插入提示信息
{
	printk("led is opened\n ");
	return 0;
}
int led_release(struct inode * innod, struct file * file) //打印模块卸载提示信息
{
	printk("led is closed\n");
	return 0;
}

static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)//文件定制操作,分别表示各个灯的开关
{
	switch(cmd){
		case LED0_ON:
			writel(readl(gpio_base0+OUT) | 1 << 7, gpio_base0+OUT);  //指定板子上12引脚输出高电平
			printk("LED0 state is ON! \n");
			break;
		case LED0_OFF:
			writel(readl(gpio_base0+OUT) & ~(1 << 7), gpio_base0+OUT);  //指定输出低电平
			printk("LED0 state is OFF! \n");
			break;
		case LED1_ON:
			writel(readl(gpio_base1+OUT) | 1 << 6, gpio_base1+OUT);  //指定板子上13引脚输出高电平
			printk("LED1 state is ON! \n");
			break;
		case LED1_OFF:
			writel(readl(gpio_base1+OUT) & ~(1 << 6), gpio_base1+OUT);  //指定输出低电平
			printk("LED1 state is OFF! \n");
			break;
		case LED2_ON:
			writel(readl(gpio_base2+OUT_) | 1 << 0, gpio_base2+OUT_);  //指定板子上19引脚输出高电平
			printk("LED2 state is ON! \n");
			break;
		case LED2_OFF:
			writel(readl(gpio_base2+OUT_) & ~(1 << 0), gpio_base2+OUT_);  //指定输出低电平
			printk("LED2 state is OFF! \n");
			break;
		default:
			printk("no found this cmd!!!\n");
	}
	return 0;
}

//声明文件操作,并进行关联
struct file_operations led_fops = {  
	.owner = THIS_MODULE,
	.open = led_open,
	.release = led_release,
	.unlocked_ioctl = led_ioctl,	
};


static int led_init(void) //模块插入后,首先进入的函数
{
	int ret, ret_1;
	int devnum = MKDEV(LED_MA, LED_MI); //合并主次设备号,生成新的32位设备ID,避免资源浪费
	
	/*下面调用内核函数来注册设备号,
	parameter_1 : 起始设备编号;
	parameter_2 : 请求的连续设备编号数
	parameter_2 : 这个设备或驱动的名字*/
	ret = register_chrdev_region(devnum, 1, "zhan_char_dev");
	if(ret < 0) //添加异常判断
	{
		printk("register_chrdev_region failed! \n");
		return ret;
	}
	
	//初始化字符设备(init 和 add 函数)
	cdev_init(&led_zhan,&led_fops); //将字符设备与文件操作绑定
	ret_1 = cdev_add(&led_zhan, devnum, 1);	//将字符设备注册到内核中
	if(ret_1 < 0) //添加异常判断
	{
		printk("cdev_add failed! \n");
		goto err1;
	}

	//动态映射物理地址到内核虚拟地址,返回值
	gpio_pinmux0 = ioremap(PINMUX_AUX_DAP4_SCLK_0, 8);//指定管脚复用寄存器地址,‘8’为映射范围大小,单位字节
	if(gpio_pinmux0 == NULL){
		printk("ioremap gpio_pinmux0 error!\n");
		goto err2;
	}
	writel((readl(gpio_pinmux0)& ~(1<<4)) | 1, gpio_pinmux0);

	gpio_base0 = ioremap(GPIO3,0xFF);
	if(gpio_base0 == NULL){
		printk("ioremap gpio_base0 error! \n");
		goto err3;
	}
	writel(readl(gpio_base0+CNF) | 1 << 7, gpio_base0+CNF);   //配置引脚GPIO3_PJ.07 为 GPIO模式
    writel(readl(gpio_base0+OE) | 1 << 7, gpio_base0+OE);	  //使能引脚(7号)
    writel(readl(gpio_base0+OUT) | ~(1 << 7), gpio_base0+OUT); //输出低电平,保持初始状态为:灯不亮
    writel(readl(gpio_base0+MSK_CNF) | 1 << 7, gpio_base0+MSK_CNF); //取消对GPIO模下引脚的屏蔽
    writel(readl(gpio_base0+MSK_OE) | 1 << 7, gpio_base0+MSK_OE);  //取消引脚 使能屏蔽
	//

	gpio_pinmux1 = ioremap(PINMUX_AUX_SPI2_SCK_0, 8);//指定管脚复用寄存器地址,‘8’为映射范围大小,单位字节
	if(gpio_pinmux1 == NULL){
		printk("ioremap gpio_pinmux1 error!\n");
		goto err4;
	}
	writel((readl(gpio_pinmux1)& ~(1<<4)) | 1, gpio_pinmux1);

	gpio_base1 = ioremap(GPIO1,0xFF);
	if(gpio_base1 == NULL){
		printk("ioremap gpio_base1 error! \n");
		goto err5;
	}
	writel(readl(gpio_base1+CNF) | 1 << 6, gpio_base1+CNF);   //配置引脚GPIO1_PB.06 为 GPIO模式
    writel(readl(gpio_base1+OE) | 1 << 6, gpio_base1+OE);	  //使能引脚(6号)
    writel(readl(gpio_base1+OUT) | ~(1 << 6), gpio_base1+OUT); //输出低电平,保持初始状态为:灯不亮
    writel(readl(gpio_base1+MSK_CNF) | 1 << 6, gpio_base1+MSK_CNF); //取消对GPIO模下引脚的屏蔽
    writel(readl(gpio_base1+MSK_OE) | 1 << 6, gpio_base1+MSK_OE);  //取消引脚 使能屏蔽

	
	gpio_pinmux2 = ioremap(PINMUX_AUX_SPI1_MOSI_0, 8);//指定管脚复用寄存器地址,‘8’为映射范围大小,单位字节
	if(gpio_pinmux2 == NULL){
		printk("ioremap gpio_pinmux2 error!\n");
		goto err6;
	}
	writel((readl(gpio_pinmux2)& ~(1<<4)) | 1, gpio_pinmux2);

	gpio_base2 = ioremap(GPIO1,0xFF);
	if(gpio_base2 == NULL){
		printk("ioremap gpio_base2 error! \n");
		goto err7;
	}
	writel(readl(gpio_base2+CNF_) | 1 << 0, gpio_base2+CNF_);   //配置引脚GPIO3_PY.02 为 GPIO模式
    writel(readl(gpio_base2+OE_) | 1 << 0, gpio_base2+OE_);	  //使能引脚(2号)
    writel(readl(gpio_base2+OUT_) | ~(1 << 0), gpio_base2+OUT_); //输出低电平,保持初始状态为:灯不亮
    writel(readl(gpio_base2+MSK_CNF_) | 1 << 0, gpio_base2+MSK_CNF_); //取消对GPIO模下引脚的屏蔽
    writel(readl(gpio_base2+MSK_OE_) | 1 << 0, gpio_base2+MSK_OE_);  //取消引脚 使能屏蔽

	printk("led init_1_24 is done\n");
	return 0;
err7: //顺序申请,逆序释放
	iounmap(gpio_pinmux2); 	
err6: 
	iounmap(gpio_base1); 
err5: 
	iounmap(gpio_pinmux1); 
err4: 
	iounmap(gpio_base0); 
err3: 
	iounmap(gpio_pinmux0); 
err2:
	cdev_del(&led_zhan);
err1:
	unregister_chrdev_region(devnum, 1);
	return -1;
}

static void led_exit(void)
{
	int devnum = MKDEV(LED_MA, LED_MI);
	
	cdev_del(&led_zhan); //和字符设备添加相配对
	unregister_chrdev_region(devnum, 1);//卸载设备时,需要取消注册,释放资源(与注册行为配对)
	
	printk("led exit_1 is done\n");
}

module_init(led_init);//模块安装入口声明
module_exit(led_exit); //模块卸载入口声明
MODULE_LICENSE("GPL"); //模块免费开源声明
MODULE_AUTHOR("Chanlgin"); //模块作者声明(可选)


(2)Makefile

ifeq ($(KERNELRELEASE),)
KERNELDIR ?= ~/kernel-4.9
PWD := $(shell pwd)

all:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions Module* modules* a.out 

else
    obj-m := led.o
endif

(3)app.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>

#define LED_MAGIC 'L'  //幻数

#define LED0_ON _IOW(LED_MAGIC, 0, int) //使用幻术定义和LED对应唯一操作数
#define LED0_OFF _IOW(LED_MAGIC, 1, int)
#define LED1_ON _IOW(LED_MAGIC, 2, int) //使用幻术定义和LED对应唯一操作数
#define LED1_OFF _IOW(LED_MAGIC, 3, int)
#define LED2_ON _IOW(LED_MAGIC, 4, int) //使用幻术定义和LED对应唯一操作数
#define LED2_OFF _IOW(LED_MAGIC, 5, int)

int main(int argc, char **argv)
{
	int fd;
	fd = open("/dev/led", O_RDWR);  //打开设备文件
	if (fd < 0) {
		perror("open");
		exit(1);
	}
	while(1)
	{
		ioctl(fd, LED0_ON);  //发送控制命令 LED0_ON
		usleep(500000);
		ioctl(fd, LED0_OFF); //发送控制命令 LED0_OFF
		usleep(500000);
        ioctl(fd, LED1_ON);  //发送控制命令 LED1_ON
		usleep(500000);
		ioctl(fd, LED1_OFF); //发送控制命令 LED1_OFF
		usleep(500000);
        ioctl(fd, LED2_ON);  //发送控制命令 LED2_ON
		usleep(500000);
		ioctl(fd, LED2_OFF); //发送控制命令 LED2_OFF
		usleep(500000);
	}
	return 0;
}
4、测试
$ make
$ aarch64-linux-gnu-gcc app.c  //编译应用程序,生成a.out
$ cp led.ko a.out /nfs/rootfs
# insmod led.ko
# mknod /dev/led c 500 0  //创建设备文件
#./a.out
5、效果

终端打印信息:
在这里插入图片描述
LED闪烁效果:

VID20250121222225

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值