6818 蜂鸣器驱动

需要先编译uboot和kernel

编译驱动前,需要先编译uboot和kernel

在我的环境中,已经有一个mk文件,先编译uboot,再编译kernel

  • ./mk -u 编译uboot
  • ./mk -k 编译内核

驱动相关的命令:

  • modinfo beepDriver.ko        查看驱动信息
  • rmmod gec6818_beep        卸载驱动
  • insmod beep.ko                   安装驱动

源文件

编译驱动的Makefile

需要指定kernel的源码位置,和交叉编译工具链的位置

# 定义了要编译的内核模块对象文件。obj-m 是一个 Makefile 变量,+= 表示追加内容。
# beepDriver.o 是要编译的目标对象文件,最终会被链接成名为 beepDriver.ko 的内核模块文件。obj-m 表示将目标编译为可加载的内核模块(.ko 文件)
obj-m += beepDriver.o

# 是用于编译内核模块所依赖的内核源码位置
KERNELDIR:=/home/ljh/MyStudy/6818KernelSys/kernel

# 指向了具体的交叉编译工具链的位置
CROSS_COMPILE:=/home/ljh/MyStudy/6818KernelSys/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8/bin/arm-eabi-

# 当前工作目录定义
PWD:=$(shell pwd)

default:
	$(MAKE) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(PWD) modules

clean:
	rm -rf *.o *.order .*.cmd *.ko *.mod.c *.symvers *.tmp_versions

蜂鸣器驱动程序

在Linux中,一切皆文件。

在 Linux 内核驱动开发中,struct file_operations 结构体是一个非常关键的结构体,它定义了内核与用户空间程序之间的接口,用于实现对设备文件的各种操作。

static const struct file_operations beep_6818_fops = 
{
    // THIS_MODULE 是一个宏,它会被展开为指向当前内核模块的指针。
    // 作用:内核利用这个指针来记录模块的使用情况,确保在有进程正在使用该模块时,模块不会被卸载。
    .owner = THIS_MODULE,
    
    // 当用户空间程序调用 open() 系统调用打开与该驱动关联的设备文件时,内核会调用这个 beepOpen 函数。
    .open = beepOpen,
    .write = beepWrite,
    .read = beepRead,
    
    // 使用 unlocked_ioctl 可以让驱动开发者更方便地实现 ioctl 功能,同时也提高了内核驱动的性能和稳定性。但需要注意的是,在 unlocked_ioctl 函数内部,不能进行会导致睡眠或阻塞的操作,因为它是在一个无锁的环境中执行的。
    .unlocked_ioctl = beepIoctl,
    
    // 当用户空间程序调用 close() 系统调用关闭与该驱动关联的设备文件时,内核会调用这个 beepRelease 函数。
    .release = beepRelease,
};

完整代码:

//头文件:/kernel/linux/*.h
//BEEP ---- GPIOC14
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "cfg_type.h"

#define BEEP_ON     1
#define BEEP_OFF    0

#define GPIO_BEEP       (PAD_GPIO_C + 14)

static dev_t beep_num; //设备号
static unsigned int beep_major = 0; //主设备号:0--动态分配,not 0 ---静态注册
static unsigned int beep_minor = 0;

//1.定义一个cdev
static struct cdev beep_dev;

static struct class *beepClass;
static struct device *beepDevice;

//3、定义一个文件操作集,并对其进行初始化
static int beepOpen(struct inode *inode, struct file *filp)
{
    printk("beep driver is openning\n");
    return 0;
}

static ssize_t beepWrite(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    char val;
    if (count != 1) 
    {
        return -EINVAL;
    }
    if (copy_from_user(&val, buf, 1)) 
    {
        return -EFAULT;
    }
    if (val) 
    {
        gpio_set_value(GPIO_BEEP, BEEP_ON);
    } 
    else 
    {
        gpio_set_value(GPIO_BEEP, BEEP_OFF);
    }
    return 1;
}

static ssize_t beepRead(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    int status = gpio_get_value(GPIO_BEEP);
    char val = status? 1 : 0;
    if (count != 1) 
    {
        return -EINVAL;
    }
    if (copy_to_user(buf, &val, 1)) 
    {
        return -EFAULT;
    }
    return 1;
}

static long beepIoctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    switch (cmd)
    {
        case BEEP_ON:
        case BEEP_OFF:
            gpio_set_value(GPIO_BEEP, cmd);
            break;

        default:
            return -ENOIOCTLCMD;
    }

    return 0;
}

static int beepRelease(struct inode *inode, struct file *filp)
{
    gpio_set_value(GPIO_BEEP, BEEP_OFF);
    return 0;
}

static const struct file_operations beep_6818_fops = 
{
   .owner = THIS_MODULE,
   .open = beepOpen,
   .write = beepWrite,
   .read = beepRead,
   .unlocked_ioctl = beepIoctl,
   .release = beepRelease,
};

//驱动的安装函数
static int __init beepInit(void)
{
    int ret;
    //2、申请设备号
    if (beep_major == 0) //动态分配
    {
        ret = alloc_chrdev_region(&beep_num, beep_minor, 1, "2025LJHBeep");
    }
    else
    {
        //静态注册
        beep_num = MKDEV(beep_major, beep_minor);
        ret = register_chrdev_region(beep_num, 1, "2025LJHBeep");
    }
    if (ret < 0)
    {
        printk("can not get a device number\n");
        return ret;
    }

    //4、字符设备初始化
    cdev_init(&beep_dev, &beep_6818_fops);

    //5、将cdev加入到内核
    ret =  cdev_add(&beep_dev, beep_num, 1);
    if (ret < 0)
    {
        printk("cdev add faibeep\n");
        goto cdev_add_err;
    }

    //6、创建class
    beepClass = class_create(THIS_MODULE, "2025LJHBeep_class");
    if (beepClass == NULL)
    {
        printk("class create error\n");
        ret = -EBUSY;
        goto class_create_err;
    }

    //7、创建device
    beepDevice = device_create(beepClass, NULL, beep_num, NULL, "2025LJHBeep");
    if (beepDevice == NULL)
    {
        printk("device create error\n");
        ret = -EBUSY;
        goto device_create_err;
    }

    //8、申请GPIO,并配置位输出
    ret = gpio_request(GPIO_BEEP, "gpioc14_beep");
    if (ret < 0)
    {
        printk("can not request gpio beep\n");
        goto gpio_request_error;
    }
    gpio_direction_output(GPIO_BEEP, 0);

    printk("6818 beep device driver init\n");

    return 0;

gpio_request_error:
    device_destroy(beepClass, beep_num);
device_create_err:
    class_destroy(beepClass);
class_create_err:
    cdev_del(&beep_dev);
cdev_add_err:
    unregister_chrdev_region(beep_num, 1);

    return ret;
}

//驱动的卸载函数
static void __exit beepExit(void)
{
    unregister_chrdev_region(beep_num, 1); //释放设备号
    cdev_del(&beep_dev); //删除cdev
    device_destroy(beepClass, beep_num); //销毁device
    class_destroy(beepClass); //删除class

    gpio_free(GPIO_BEEP);

    printk("6818 beep device driver exit\n");
}

//module的入口和出口
module_init(beepInit); //安装驱动:#insmod beep_drv.ko --->module_init()--->6818_beep_init( )
module_exit(beepExit);   //卸载驱动:#rmmod beep_drv.ko --->module_exit()--->6818_beep_exit()

//module的描述, #modinfo *.ko
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("6818: 2025LJHBeep Device Driver");
MODULE_LICENSE("GPL");
MODULE_VERSION("V1.0");

直接make来编译,得到.ko文件

拷贝到ARM板上,使用insmod加载驱动

在/dev/目录可以找到,在这里的名字是在驱动程序中指定的,和ko文件名无关。

在我们上面这个,驱动名是 2025LJHBeep,路径是 /dev/2025LJHBeep

测试驱动的应用程序程序

因为驱动的路径是/dev/2025LJHBeep,Linux中一切皆文件,打开这个文件描述符进行操作,就是操作驱动程序。

#include 
#include 
#include 
#include 

#include 
#include 
#include 

#define DEVICE_FILE_PATH "/dev/2025LJHBeep"  // 这里的路径要与驱动中创建设备节点的名称一致

const char BEEP_ON = 1;
const char BEEP_OFF = 0;

int main() 
{
    int fd;
    char write_buf[1];
    char read_buf[1];

    // 打开设备文件
    fd = open(DEVICE_FILE_PATH, O_RDWR);
    if (fd < 0) 
    {
        perror("Failed to open device file");
        return 1;
    }

    // 尝试打开蜂鸣器
    write_buf[0] = BEEP_ON;
    if (write(fd, write_buf, 1) < 0) 
    {
        perror("Failed to write to device");
        close(fd);
        return 1;
    }
    sleep(1);

    // 读取蜂鸣器状态
    if (read(fd, read_buf, 1) < 0) 
    {
        perror("Failed to read from device");
        close(fd);
        return 1;
    }
    if (read_buf[0] == BEEP_ON) 
    {
        printf("Beep status: ON\n");
    } 
    else 
    {
        printf("Beep status: OFF\n");
    }

    sleep(1);
    printf("--------------------------\n");

    // 尝试关闭蜂鸣器
    write_buf[0] = BEEP_OFF;
    if (write(fd, write_buf, 1) < 0) 
    {
        perror("Failed to write to device");
        close(fd);
        return 1;
    }
    printf("Beep turned off.\n");

    // 再次读取蜂鸣器状态
    if (read(fd, read_buf, 1) < 0) 
    {
        perror("Failed to read from device");
        close(fd);
        return 1;
    }
    if (read_buf[0] == BEEP_ON) 
    {
        printf("Beep status: ON\n");
    } 
    else 
    {
        printf("Beep status: OFF\n");
    }

    sleep(2);

    printf("-------------use ioctl------------\n");
    // 使用ioctl
    ioctl(fd, BEEP_ON);

    sleep(2);

    ioctl(fd, BEEP_OFF);



    // 关闭设备文件
    close(fd);

    return 0;
}

你可能感兴趣的:(arm,linux,驱动开发,arm开发)