驱动开发4 使用字符设备驱动的分步实现编写LED驱动(LED亮灯)

一、思维导图

二、通过字符设备驱动的分步实现编写LED驱动,另外实现特备文件和设备的绑定

应用程序 test.c

cs 复制代码
#include<stdlib.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include<unistd.h>
#include<string.h>
#include "head.h"

int main(int argc, char const *argv[])
{
    char buf[128]={0};
    int a;
    int fd=open("/dev/mycdev0",O_RDWR);
    if(fd<0)
    {
        printf("打开设备文件失败\n");
        exit(-1);
    }
    while(1)
    {
        //从终端读取
        printf("请输入要实现的功能 ");
        printf("0(关灯) 1(开灯)\n");
        printf("请输入>");
        scanf("%d",&a);
        switch(a)
        {
            case 1:
                ioctl(fd,LED_ON);
                break;
            case 0:
                ioctl(fd,LED_OFF);
                break;
        }
    }
    close(fd);
    return 0;
}

头文件 head.h

cs 复制代码
#ifndef __HEAD_H__
#define __HEAD_H__
typedef struct
{
    unsigned int MODER;
    unsigned int OTYPER;
    unsigned int OSPEEDR;
    unsigned int PUPDR;
    unsigned int IDR;
    unsigned int ODR;
}gpio_t;
#define PHY_LED1_ADDR 0X50006000
#define PHY_LED2_ADDR 0X50007000
#define PHY_LED3_ADDR 0X50006000
#define PHY_RCC_ADDR 0X50000A28
// 构建开灯关灯的功能码
#define LED_ON _IO('l', 1)
#define LED_OFF _IO('l', 0)
#endif

驱动程序 mycdev.c

cs 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include<linux/fs.h>
#include<linux/device.h>
#include<linux/cdev.h>
#include<linux/slab.h>
#include<linux/io.h>
#include<linux/uaccess.h>
#include "head.h"

struct cdev *cdev;
unsigned int major=0;
unsigned int minor=0;
dev_t devno;

char kbuf[128] = {0};
gpio_t *vir_led1;
gpio_t *vir_led2;
gpio_t *vir_led3;
unsigned int *vir_rcc;
struct class *cls;
struct device *dev;

int mycdev_open(struct inode *inode, struct file *file)
{
    //获取打开的文件的次设备号
    int min = MINOR(inode->i_rdev);
    file->private_data = (void *)min;
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    return 0;
}
long mycdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    //获取文件的次设备号
    int min = (int)file->private_data;
    switch (min)
    {
    case 0: // 开灯
        switch (cmd)
        {
        case LED_ON: // LED1
            vir_led1->ODR |= (0X1 << 10); //LED开
            break;
        case LED_OFF:
            vir_led1->ODR &= (~(0X1 << 10));
            break;
        }
        break;
    case 1: // 关灯
        switch (cmd)
        {
        case LED_ON: // LED1
            vir_led2->ODR |= (0X1 << 10);
            break;
        case LED_OFF:
            vir_led2->ODR &= (~(0X1 << 10));
            break;
        }
        break;
    case 2:
        switch (cmd)
        {
        case LED_ON: // LED1
            vir_led3->ODR |= (0X1 << 8);
            break;
        case LED_OFF:
            vir_led3->ODR &= (~(0X1 << 8));
            break;
        }
        break;
    }
    return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    return 0;
}
//定义操作方法结构体变量并赋值
struct file_operations fops={

    .open = mycdev_open,
    .unlocked_ioctl = mycdev_ioctl,
    .release = mycdev_close,
};
int all_led_init(void)
{
    // 寄存器地址的映射
    vir_led1 = ioremap(PHY_LED1_ADDR, sizeof(gpio_t));
    if (vir_led1 == NULL)
    {
        printk("ioremap filed:%d\n", __LINE__);
        return -ENOMEM;
    }
    vir_led2 = ioremap(PHY_LED2_ADDR, sizeof(gpio_t));
    if (vir_led2 == NULL)
    {
        printk("ioremap filed:%d\n", __LINE__);
        return -ENOMEM;
    }
    vir_led3 = vir_led1;
    vir_rcc = ioremap(PHY_RCC_ADDR, 4);
    if (vir_rcc == NULL)
    {
        printk("ioremap filed:%d\n", __LINE__);
        return -ENOMEM;
    }
    printk("物理地址映射成功\n");
    // 寄存器的初始化
    // rcc
    (*vir_rcc) |= (3 << 4);
    // led1
    vir_led1->MODER &= (~(3 << 20));
    vir_led1->MODER |= (1 << 20);
    vir_led1->ODR &= (~(1 << 10));
    // led2
    vir_led2->MODER &= (~(3 << 20));
    vir_led2->MODER |= (1 << 20);
    vir_led2->ODR &= (~(1 << 10));
    // led3
    vir_led3->MODER &= (~(3 << 16));
    vir_led1->MODER |= (1 << 16);
    vir_led1->ODR &= (~(1 << 8));
    printk("寄存器初始化成功\n");

    return 0;
}
static int __init mycdev_init(void)
{
    //1.申请一个对象空间cdev_alloc
    int ret;
    cdev = cdev_alloc();
    if (NULL == cdev)
    {
        printk("申请字符设备驱动对象失败\n");
        ret = -EFAULT;
        goto out1;
    }
    printk("字符设备驱动对象申请成功\n");

    //2.初始化对象cdev_ini
    cdev_init(cdev,&fops);

    //3.申请设备号register_chrdev_region()/alloc_chrdev_region()
    if(0 == major)
    {
        ret = alloc_chrdev_region(&devno,minor,3,"mychrdev");
        if(ret)
        {
            printk("动态申请设备号失败\n");
            goto out2;
        }
        major=MAJOR(devno); //根据设备号获取主设备号
        minor=MINOR(devno); //根据设备号获取次设备号
    }
    else //静态制定设备号
    {
        ret=register_chrdev_region(MKDEV(major,minor),3,"mychrdev");
        if(ret)
        {
            printk("静态指定设备号失败\n");
            goto out2;
        }
    }
    printk("设备号申请成功\n");

    //4.注册驱动对象 cdev_add
    ret = cdev_add(cdev,MKDEV(major,minor),3);
    if(ret)
    {
        printk("注册字符设备驱动对象失败\n");
        goto out3;
    }
    printk("注册字符设备驱动对象成功\n");

    //5.向上提交目录 class_create
    cls = class_create(THIS_MODULE,"mychrdev");
    if(IS_ERR(cls))
    {
        printk("向上提交目录失败\n");
        goto out4;
    }
    printk("向上提交目录成功\n");

    //6.向上提交设备节点信息 device_create
    int i;
    for(i=0;i<3;i++)
    {
        dev = device_create(cls,NULL,MKDEV(major,i),NULL,"mycdev%d",i);
        if(IS_ERR(dev))
        {
            printk("向上提交设备节点失败\n");
            goto out5;
        }
    }
    printk("向上提交设备节点信息成功\n");

    // 寄存器映射以及初始化
    all_led_init();

    return 0;
out5:
    //奖提交成功的节点信息释放
    for(--i;i>=0;i--)
    {
        device_destroy(cls,MKDEV(major,i));
    }
    //销毁目录
    class_destroy(cls);
out4:
    cdev_del(cdev);
out3:
    unregister_chrdev_region(MKDEV(major,minor),3);
out2:
    kfree(cdev);
out1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    // 取消地址映射
    iounmap(vir_led1);
    iounmap(vir_led2);
    iounmap(vir_rcc);
    //1.销毁设备节点信息
    int i;
    for(i=0;i<3;i++)
    {
        device_destroy(cls,MKDEV(major,i));
    }
    //2.销毁目录
    class_destroy(cls);
    //3.注销字符设备驱动对象
    cdev_del(cdev);
    //4.释放设备号
    unregister_chrdev_region(MKDEV(major,minor),3);
    //5.释放申请到的字符设备驱动对象空间
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

Makefile

bash 复制代码
modname ?= demo
arch ?= arm
ifeq ($(arch),arm)  #通过命令行传过来的架构决定怎么编译
#KERBELDIR保存开发板内核源码路径
KERNELDIR := /home/ubuntu/FSMP1A/linux-stm32mp-5.10.61-stm32mp-r2-r0/linux-5.10.61
else
#保存UBUNTU内核源码路径
KERNELDIR := /lib/modules/$(shell uname -r)/build
endif

#PWD保存当前内核模块的路径
PWD := $(shell pwd)
all:
#make modules是模块化编译命令
#make -C $(KERNLEDIR) 执行make之前先切换到KERNELDIR对应的路径
#M=$(PWD)表示进行模块化编译的路径是PWD保存的路径
	make -C $(KERNELDIR) M=$(PWD) modules
clean:
#编译清除
	make -C $(KERNELDIR) M=$(PWD) clean
#将obj-m保存的文件单独链接为内核模块
obj-m :=  $(modname).o

效果实现

相关推荐
dlz08366 小时前
POE驱动开发流程
驱动开发
嵌入式-老费8 小时前
Linux camera驱动开发(DVP接口的camera sensor)
驱动开发
VernonJsn1 天前
visual studio 2022的windows驱动开发
ide·驱动开发·visual studio
嵌入式郑工1 天前
RK3566 LubanCat 开发板 USB Gadget 配置完整复盘
linux·驱动开发·ubuntu
雾削木3 天前
树莓派 ESPHome 固件编译与烧录全攻略(解决超时与串口识别问题)
驱动开发
春日见3 天前
win11 分屏设置
java·开发语言·驱动开发·docker·单例模式·计算机外设
DarkAthena4 天前
【GaussDB】手动编译不同python版本的psycopg2驱动以适配airflow
驱动开发·python·gaussdb
松涛和鸣4 天前
DAY66 SPI Driver for ADXL345 Accelerometer
linux·网络·arm开发·数据库·驱动开发
嵌入式郑工5 天前
# RK3576 平台 RTC 时钟调试全过程
linux·驱动开发·ubuntu
GS8FG5 天前
针对Linux,RK3568平台下,I2C驱动的一点小小的领悟
linux·驱动开发