Linux驱动开发学习记录02

记录学习Linux驱动开发中遇到的一系列问题

一.Linux常用指令

记录一些在学习时常用的指令
一些特别常用的就不记录啦

  1. %s/"cc"/"arm-buildroot-linux-gnueabihf-gcc"/g,在控制台打开complie文件,替换所有,注意格式,就可以用vs看linux内核源码了。

二.GPIO子系统

一般使用help查看

1.引脚编号

  1. 使用cat /sys/kernel/debug/gpio查看gpio模块
  2. 使用cd /sys/class/gpio/ls可以看到每一组模块的起始地址
  3. 原理图的gpio4对应gpioship3

2.常用函数

  1. gpio_requst
  2. gpio_deriction_input
  3. gpio_direction_output
  4. gpio_get_value
  5. gpio_set_value
  6. gpio_free

三.中断函数

建议看韦东山老师的文档,讲解很详细。

四.使用模板操作硬件

1.LED

①代码

drv.c
#include "asm-generic/errno-base.h"
#include "asm-generic/gpio.h"
#include "asm/uaccess.h"
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>//头文件不再赘述
struct gpio_desc{
	int gpio;
	int irq;
  char *name;
  int key;
} ;
static struct gpio_desc gpios[2] = {
    {131, 0, "led0", },
    //{132, 0, "led1", },
};
static int major = 0;//主设备号
static struct class *gpio_class;//这两个注意提前定义
/* 实现对应的open/read/write等函数,填入file_operations结构体*/
static ssize_t gpio_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	char tmp_buf[2];
	int err;
  int count = sizeof(gpios)/sizeof(gpios[0]);
	if (size != 2)
		return -EINVAL;

	err = copy_from_user(tmp_buf, buf, 1);
	if (tmp_buf[0] >= count)
		return -EINVAL;//是一个负值,表示无效参数

	tmp_buf[1] = gpio_get_value(gpios[tmp_buf[0]].gpio);
	err = copy_to_user(buf, tmp_buf, 2);
	return 2;
}
static ssize_t gpio_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
    unsigned char ker_buf[2];
    int err;
    if (size != 2)
        return -EINVAL;//因为在测试中要读取的是2个,如果不是两个就报错

    err = copy_from_user(ker_buf, buf, size);
    if (ker_buf[0] >= sizeof(gpios)/sizeof(gpios[0]))
        return -EINVAL;

    gpio_set_value(gpios[ker_buf[0]].gpio, ker_buf[1]);
    return 2;    
}
/* 第一步先定义file_operations结构体*/
static struct file_operations gpio_key_drv = {
	.owner	 = THIS_MODULE,
	.read    = gpio_drv_read,
	.write   = gpio_drv_write,
};//点灯只需要用到读写函数
/*第二步在入口函数注册结构体 */
static int __init gpio_drv_init(void)
{
    int err;
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	for (i = 0; i < count; i++)
	{		
		/* set pin as output */
		err = gpio_request(gpios[i].gpio, gpios[i].name);
		if (err < 0) {
			printk("can not request gpio %s %d\n", gpios[i].name, gpios[i].gpio);
			return -ENODEV;
		}
		gpio_direction_output(gpios[i].gpio, 1);//设置方向
	}
	/*注册file_operations字符设备*/
	major = register_chrdev(0, "100ask_led", &gpio_key_drv);/*major记得先全局定义,
	为0时表示内核自动分配主设备号"100ask_led"是设备驱动的名称,用于在系统中标识该驱动;&gpio_key_drv是指向struct file_operations结构体的指针,它定义了字符设备驱动的
	操作函数,例如打开、关闭、读取和写入等。*/
	gpio_class = class_create(THIS_MODULE, "100ask_led_class");/*创建一个类
	THIS_MODULE 是一个指向当前内核模块的指针,用于指示创建的类设备属于当前模块。"100ask_led_class" 是类设备的名称,用于在系统中标识该类设备,在该类设备下,可以创建具体的设备实例,并将其与相关的设备驱动关联起来。*/
	if (IS_ERR(gpio_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_led_class");
		return PTR_ERR(gpio_class);
	}/*IS_ERR(gpio_class) 是一个宏,用于检查 gpio_class 的返回值是否为错误指针。
	如果 gpio_class 是错误指针,表示创建类设备失败。
	printk 函数用于在内核日志中打印一条错误消息,显示出错的代码文件、函数和行号。
	unregister_chrdev 函数用于取消注册之前注册的字符设备驱动。
	PTR_ERR(gpio_class) 是一个宏,用于获取错误指针的错误码。
	return PTR_ERR(gpio_class) 将错误码返回给调用函数,以便进一步处理或报告错误。
	这段代码的作用是在创建类设备失败时,打印错误信息,并进行必要的清理操作,
	包括取消注册之前注册的字符设备驱动,并返回适当的错误码。这样可以在遇到错误时及时
	处理异常情况,以确保代码的正确执行。*/
	device_create(gpio_class, NULL, MKDEV(major, 0), NULL, "100ask_led"); /*创建
	一个具体的设备实例,并将其与之前创建的类设备关联起来。
	gpio_class 是之前使用 class_create创建的类设备的指针,表示新创建的设备实例将属于该类设备。
	第一个NULL表示新创建的设备实例没有父设备。
	MKDEV(major, 0) 用于创建设备实例的设备号。major 是之前注册的字符设备驱动的主设备号,
	而0表示次设备号,用于标识具体的设备实例。
	第二个NULL表示没有提供驱动特定的数据。
	"100ask_led" 是设备实例的名称,用于在系统中标识该设备实例。*/
	return err;
}
/* 第三步,有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数*/
static void __exit gpio_drv_exit(void)
{
    int i;
    int count = sizeof(gpios)/sizeof(gpios[0]);
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(gpio_class, MKDEV(major, 0));
	class_destroy(gpio_class);
	unregister_chrdev(major, "100ask_led");//按顺序销毁
	for (i = 0; i < count; i++)
	{
		gpio_free(gpios[i].gpio);		
	}/*用于释放GPIO 引脚资源。gpios[i].gpio 表示第 i 个 GPIO 引脚的标识符。通过
	调用gpio_free 函数,可以释放该引脚所占用的资源,包括引脚的注册、配置和占用等。*/
}
/* 7. 其他完善:提供设备信息,自动创建设备节点*/
module_init(gpio_drv_init);//宏,用于指定模块初始化函数
module_exit(gpio_drv_exit);//宏,用于指定模块退出函数
MODULE_LICENSE("GPL");
test.c
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <poll.h>
#include <signal.h>
static int fd;
//int led_on(int which);
//int led_off(int which);
//int led_status(int which);
/*
 * ./led_test <0|1|2|..>  on 
 * ./led_test <0|1|2|..>  off
 * ./led_test <0|1|2|..>
 */
int main(int argc, char **argv)
{
	int ret;
	char buf[2];
	int i;
	/* 1. 判断参数 */
	if (argc < 2) 
	{
		printf("Usage: %s <0|1|2|...> [on | off]\n", argv[0]);
		return -1;
	}
	/* 2. 打开文件 */
	fd = open("/dev/100ask_led", O_RDWR);//注册的名称
	if (fd == -1)
	{
		printf("can not open file /dev/100ask_led\n");
		return -1;
	}
	if (argc == 3)
	{
		/* write */
		buf[0] = strtol(argv[1], NULL, 0);//strtol 函数用于将字符串转换为长整型数值。
		if (strcmp(argv[2], "on") == 0)  //这里的0是亮,是结合电路图得出的,实际应用时应根据电路图决定
			buf[1] = 0;
		else
			buf[1] = 1;
		
		ret = write(fd, buf, 2);//write指向file_operations里的,再指向定义的函数
	}
	else
	{ 
		buf[0] = strtol(argv[1], NULL, 0);
		ret = read(fd, buf, 2);
		if (ret == 2)
		{
			printf("led %d status is %s\n", buf[0], buf[1] == 0 ? "on" : "off");
		}
	}
	close(fd);
	return 0;
}
makefile
KERN_DIR =  /home/book/100ask_imx6ull-sdk/Linux-4.9.88 # 板子所用内核源码的目录

all:
	make -C $(KERN_DIR) M=`pwd` modules //进入内核,pwd表示在当前目录
	$(CROSS_COMPILE)gcc -o led_test led_test.c
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order  led_test

# 要想把a.c, b.c编译成ab.ko, 可以这样指定:
# ab-y := a.o b.o
# obj-m += ab.o
obj-m += led_drv.o

②思考

  1. count不能作为全局变量,因为使用到gpios,可能还未定义所以将其声明为局部变量。
  2. int count = sizeof(gpios)/sizeof(gpios[0]);不直接指定count=2是因为假设将来需要修改gpios 数组的大小,如果手动指定count为 2,那么在修改数组大小后,还需要手动更新count的值,否则会导致count和数组大小不匹配,可能导致意想不到的错误。而使用 sizeof(gpios)/sizeof(gpios[0]) 的方式,无论你修改了 gpios 数组的大小,都可以正确计算出数组元素的个数。这样,你就不需要手动更新 count 的值,代码更具有可维护性。
  3. return -1;通常用于表示函数调用失败、资源不可用、不支持的操作等一般性错误。
    return -EINVAL;常用于在Linux内核编程中指示无效参数错误,例如驱动程序接口中的参数验证。
  4. strcmp 是一个字符串比较函数,用于比较两个字符串的内容是否相等,相等返回0。
  5. 在test中考虑了一个问题,在使用read函数时,考虑为什么不能传递1,因为在内核中定义的函数只用到了buf[0],进行实验后发现无法读出。
    思考再三后,原因在于内核中定义的是ker_buf[2],如果只传递1给 read 函数,它将只读取一个字节的数据,而不是完整的两个字节,这可能会导致数据读取不完整或错误。

③上机实验

1.打开开发板原理图,确定引脚标号
在这里插入图片描述2.打开串口,输入cat /sys/kernel/debug/gpio进入该目录,128+3=131,这就是drv.c中的131来历
3.之后就是常规操作了,先make再insmod led_drv.ko,再输入./led_test 0 on就可以看到灯亮了

  • 23
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值