Linux系统 74HC595 驱动程序解析

Linux 74HC595 移位寄存器驱动程序分析

1. 驱动程序概述

本驱动程序是一个针对74HC595串行输入并行输出(SIPO)移位寄存器的Linux内核驱动,采用分层设计,支持设备树配置和多种用户空间访问接口。

  • 驱动类型: Linux字符设备驱动
  • 设计模式: 分层架构(设备框架层+硬件控制层)
  • 接口支持: 字符设备文件、sysfs属性文件
  • 配置方式: 设备树

2. 目录结构

d74hc595/
├── d74hc595.h         # 驱动程序头文件,定义设备结构体和常量
├── d74hc595.c         # 主驱动程序文件,实现设备框架和用户接口
├── d74hc595_ctrl.h    # 控制器头文件,定义硬件控制接口
└── d74hc595_ctrl.c    # 控制器实现文件,实现硬件操作逻辑

3. 核心文件分析

3.1 d74hc595.h

定义了驱动程序的核心数据结构和常量:

// 设备节点和文件名称定义
#define TEST_DEVICE_NODE_NAME  "d74hc595"
#define TEST_DEVICE_FILE_NAME  "hc595"
#define TEST_DEVICE_PROC_NAME  "d74hc595"
#define TEST_DEVICE_CLASS_NAME "drvtest"

// 设备结构体定义
struct d74hc595_ctrl_dev {
    unsigned int val[1];              // 输出数据寄存器
    struct mutex mtx;                 // 并发保护互斥锁
    struct cdev dev;                  // Linux字符设备结构体
    int dev_major;						//主设备号变量
    int dev_minor;						//从设备号变量
    struct d74hc595_controller controller; // 74HC595控制器
};

3.2 d74hc595_ctrl.h

定义了硬件控制层的接口:

// 控制函数类型定义
typedef void (*d74hc595_pin_func_t)(int value);
typedef void (*d74hc595_delay_func_t)(int ms);

// 控制器结构体
struct d74hc595_controller {
    d74hc595_pin_func_t mr_func;    // 主复位控制
    d74hc595_pin_func_t oe_func;    // 输出使能控制
    d74hc595_pin_func_t shcp_func;  // 移位时钟控制
    d74hc595_pin_func_t stcp_func;  // 存储时钟控制
    d74hc595_pin_func_t ds_func;    // 数据控制
    d74hc595_delay_func_t delay_func; // 延时函数
};

// 控制器API声明
int d74hc595_ctrl_init(struct d74hc595_controller *controller);
void d74hc595_ctrl_deinit(struct d74hc595_controller *controller);
int d74hc595_ctrl_output(struct d74hc595_controller *controller, unsigned int data, int bitnum);

3.3 d74hc595_ctrl.c

实现了硬件控制层的核心功能:

// 初始化74HC595控制器
int d74hc595_ctrl_init(struct d74hc595_controller *controller)
{
    if (!controller) return -EINVAL;
    
    // 初始化芯片到默认状态
    controller->mr_func(0);   // 主复位
    controller->mr_func(1);   // 取消主复位
    controller->oe_func(1);   // 禁用输出
    
    return 0;
}

// 向74HC595输出数据
int d74hc595_ctrl_output(struct d74hc595_controller *controller, unsigned int udata, int bitnum)
{
    int i;
    if (!controller) return -EINVAL;
    
    controller->mr_func(0);   // 主复位
    controller->mr_func(1);   // 取消主复位
    controller->oe_func(1);   // 禁用输出
    
    // 发送bitnum位数据
    for (i = 0; i < bitnum; i++) {
        controller->ds_func((udata >> i) & 0x01);  // 设置数据位
        
        // 移位时钟操作
        controller->shcp_func(0);
        controller->delay_func(1);
        controller->shcp_func(1);
        controller->delay_func(1);
        
        // 存储时钟操作
        controller->stcp_func(0);
        controller->delay_func(1);
        controller->stcp_func(1);
        controller->delay_func(1);
    }
    
    controller->oe_func(0);   // 启用输出
    return 0;
}

3.4 d74hc595.c

实现了设备框架层,包括:

  • 设备树解析
  • GPIO初始化
  • 字符设备注册
  • 用户空间接口
  • sysfs属性
设备树支持
// 设备树匹配表
static const struct of_device_id d74hc595_of_match[] = {
    { .compatible = "d74hc595", },
    { }
};
MODULE_DEVICE_TABLE(of, d74hc595_of_match);
GPIO操作宏
#define STCP(x)  {gpio_direction_output(hc595_gpios[2].gpio, x);}
#define SHCP(x)  {gpio_direction_output(hc595_gpios[3].gpio, x);}
#define DS(x)    {gpio_direction_output(hc595_gpios[0].gpio, x);}
#define OE(x)    {gpio_direction_output(hc595_gpios[1].gpio, x);}
字符设备操作
static struct file_operations d74hc595_ctrl_fops = {
    .owner = THIS_MODULE,
    .open = d74hc595_ctrl_open,
    .release = d74hc595_ctrl_release,
    .read = d74hc595_ctrl_read,
    .write = d74hc595_ctrl_write,
};

4. 驱动程序架构

4.1 分层设计

本驱动采用分层架构设计,将设备框架层与硬件控制层分离:

+-------------------+
|   用户空间程序    |
+-------------------+
        |
+-------------------+
|   字符设备接口    |
|   sysfs属性接口   |
+-------------------+
|   设备框架层      |<-- d74hc595.c
+-------------------+
|   硬件控制层      |<-- d74hc595_ctrl.c/h
+-------------------+
|   74HC595芯片     |
+-------------------+

4.2 核心数据结构关系

struct platform_device
        |
        v
struct d74hc595_ctrl_dev
        |
        +---> struct cdev (字符设备)
        |
        +---> struct mutex (互斥锁)
        |
        +---> struct d74hc595_controller
                    |
                    +---> mr_func (主复位控制)
                    +---> oe_func (输出使能控制)
                    +---> shcp_func (移位时钟控制)
                    +---> stcp_func (存储时钟控制)
                    +---> ds_func (数据控制)
                    +---> delay_func (延时函数)

5. 工作原理

驱动程序的工作原理主要包括以下几个方面:

5.1 设备探测与初始化

驱动通过 platform_driver_register 注册平台驱动,当设备树中匹配的设备被发现时,d74hc595_probe 函数被调用。完整的设备探测与初始化函数如下:

static int d74hc595_probe(struct platform_device *pdev)
{
    int err = -1;
    dev_t dev = 0;
    struct device* temp = NULL;
    int i;
    struct d74hc595_ctrl_dev *pdrvdata = NULL;

    // 动态分配主设备和从设备号
    err = alloc_chrdev_region(&dev, 0, 1, TEST_DEVICE_NODE_NAME);
    if(err < 0) {
        dev_err(&pdev->dev, "Failed to alloc char dev region.\n");
        goto fail;
    }     

    // 分配设备结构体变量
    pdrvdata = devm_kzalloc(&pdev->dev, sizeof(struct d74hc595_ctrl_dev), GFP_KERNEL);
    if(!pdrvdata) {
        err = -ENOMEM;
        dev_err(&pdev->dev, "Failed to alloc pdrvdata.\n");
        goto unregister;
    }
    
    pdrvdata->dev_major= MAJOR(dev);
    pdrvdata->dev_minor = MINOR(dev);
    
    // 初始化设备
    err = __d74hc595_ctrl_setup_dev(pdrvdata);
    if(err) {
        dev_err(&pdev->dev, "Failed to setup dev: %d.\n", err);
        goto cleanup;
    }
        
    // 创建设备类别
    pdrvdata->sys_class = class_create(THIS_MODULE, TEST_DEVICE_CLASS_NAME);
    if (IS_ERR(pdrvdata->sys_class)) {
        err = PTR_ERR(pdrvdata->sys_class);
        dev_err(&pdev->dev, "Failed to create class.\n");
        goto destroy_cdev;
    }

    // 创建设备文件
    temp = device_create(pdrvdata->sys_class, NULL, dev, "%s", TEST_DEVICE_FILE_NAME);
    if(IS_ERR(temp)) {
        err = PTR_ERR(temp);
        dev_err(&pdev->dev, "Failed to create device.");
        goto destroy_class;
    }
        
    // 创建属性文件
    for(i=0; i<ARRAY_SIZE(attr_array); i++)
    {
        err = device_create_file(temp, attr_array[i]);
        if(err < 0) {
            dev_err(&pdev->dev, "Failed to create attribute val.");                
            goto destroy_device;
        }
    }

    dev_set_drvdata(temp, pdrvdata);        
    
    // 从设备树获取GPIO配置
    err = d74hc595_get_dt_gpio(&pdev->dev);
    if (err < 0) {
        dev_err(&pdev->dev, "Failed to get GPIO from device tree\n");
        goto remove_proc;
    }

    // 初始化硬件
    d74hc595_ctrl_InitHardware(pdrvdata);

    dev_info(&pdev->dev, "d74hc595 driver probe success.\n");
    
    return 0;

remove_proc:
#ifdef DRV_PROC
    d74hc595_ctrl_remove_proc();
#endif

destroy_device:
    device_destroy(pdrvdata->sys_class, dev);

destroy_class:
    class_destroy(pdrvdata->sys_class);

destroy_cdev:
    cdev_del(&(pdrvdata->dev));

cleanup:

unregister:
    unregister_chrdev_region(MKDEV(d74hc595_ctrl_major, d74hc595_ctrl_minor), 1);

fail:
    return err;
}

5.2 数据传输流程

用户空间通过 write 系统调用向字符设备写入数据,驱动的 d74hc595_ctrl_write 函数处理数据写入请求:

static ssize_t d74hc595_ctrl_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) {
	struct d74hc595_ctrl_dev* dev = filp->private_data;
	ssize_t err = 0;        

	/*同步访问,获取互斥锁*/
	if(mutex_lock_interruptible(&(dev->mtx))) {
		return -ERESTARTSYS;        
	}      

	/*将用户提供的缓冲区的值写到设备寄存器去*/
	if(copy_from_user(&(dev->val), buf, count)) {
		err = -EFAULT;
		goto out;
	}
	
	// 输出数据到74HC595
	d74hc595_output(drvdata, dev->val[0]);  

	err = sizeof(dev->val);

out:
	mutex_unlock(&(dev->mtx));  // 释放互斥锁
	return err;
}

数据输出函数 d74hc595_output 将数据发送到74HC595芯片:

static void d74hc595_output(struct d74hc595_controller *controller, int data)
{
    d74hc595_ctrl_output(controller, data, 32);  // 调用控制器输出函数,发送32位数据
}

而实际执行数据传输的核心函数是 d74hc595_ctrl_output,定义在d74hc595_ctrl.c中:

5.3 GPIO控制机制

驱动从设备树获取GPIO配置信息并初始化:

// GPIO描述结构
struct hc595_gpio_desc {
    const char *name;
    int gpio;
    int init_value;
};

// 74HC595所需GPIO定义
static struct hc595_gpio_desc hc595_gpios[] = {
    {"ds", -1, 0},     // 数据输入
    {"oe", -1, 1},     // 输出使能
    {"stcp", -1, 0},   // 存储寄存器时钟
    {"shcp", -1, 0},   // 移位寄存器时钟
    {"enable", -1, 0}, // 使能引脚
};

static int d74hc595_get_dt_gpio(struct device *dev)
{
    int i, ret;
    
    // 遍历GPIO描述数组,为每个GPIO引脚配置设备树中的实际GPIO
    for (i = 0; i < ARRAY_SIZE(hc595_gpios); i++) {
        // 从设备树中获取命名GPIO
        hc595_gpios[i].gpio = of_get_named_gpio(dev->of_node, hc595_gpios[i].name, 0);
        // 检查GPIO是否有效
        if (!gpio_is_valid(hc595_gpios[i].gpio)) {
            dev_err(dev, "Failed to get %s gpio\n", hc595_gpios[i].name);
            return -ENODEV;  // 返回设备不存在错误
        }
        
        // 请求GPIO使用权
        ret = gpio_request(hc595_gpios[i].gpio, hc595_gpios[i].name);
        if (ret) {
            dev_err(dev, "Failed to request %s gpio\n", hc595_gpios[i].name);
            return ret;  // 返回请求失败错误
        }
        
        // 设置GPIO初始方向和值
        gpio_direction_output(hc595_gpios[i].gpio, hc595_gpios[i].init_value);
    }
    
    return 0;  // 成功返回
}

5.4 硬件控制器初始化

硬件控制器初始化函数配置74HC595控制器的各项功能:

static int d74hc595_ctrl_InitHardware(struct d74hc595_ctrl_dev* dev)
{
    int ret = 0;
    
    // 初始化74HC595控制器结构体,将控制函数指针赋值
    dev->controller.mr_func = d74hc595_mr_func;      // 主复位控制函数
    dev->controller.oe_func = d74hc595_oe_func;      // 输出使能控制函数
    dev->controller.shcp_func = d74hc595_shcp_func;  // 移位时钟控制函数
    dev->controller.stcp_func = d74hc595_stcp_func;  // 存储时钟控制函数
    dev->controller.ds_func = d74hc595_ds_func;      // 数据输入控制函数
    dev->controller.delay_func = d74hc595_delay_func; // 延迟函数
    
    // 初始化控制器
    d74hc595_ctrl_init(&dev->controller);
    
    return ret;
}

5.5 设备移除

当设备被移除时,d74hc595_remove 函数负责清理资源:

static int d74hc595_remove(struct platform_device *pdev)
{
    struct d74hc595_ctrl_dev *pdrvdata = (struct d74hc595_ctrl_dev*)dev_get_drvdata(&pdev->dev);
    dev_t devno = MKDEV(d74hc595_ctrl_major, d74hc595_ctrl_minor);

    dev_info(&pdev->dev, "Remove d74hc595 device.\n");     

    ctrl_free_gpio();  // 释放GPIO资源
    
#ifdef DRV_PROC
    // 删除/proc文件
    d74hc595_ctrl_remove_proc();        
#endif
    // 销毁设备类别和设备
    if(pdrvdata->sys_class) {
        device_destroy(pdrvdata->sys_class, MKDEV(d74hc595_ctrl_major, d74hc595_ctrl_minor));
        class_destroy(pdrvdata->sys_class);
    }
 
    // 删除字符设备和释放设备内存
    cdev_del(&(pdrvdata->dev));

    // 释放设备号
    unregister_chrdev_region(devno, 1);
    
    return 0;
}

5.6 并发控制

驱动使用互斥锁(mutex)确保并发访问安全,所有对设备寄存器的访问都通过互斥锁进行保护,如在 d74hc595_ctrl_read 函数中:

static ssize_t d74hc595_ctrl_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) {
    ssize_t err = 0;
    struct d74hc595_ctrl_dev* dev = filp->private_data; 

    // 同步访问,获取互斥锁
    if(mutex_lock_interruptible(&(dev->mtx))) {
        return -ERESTARTSYS;        
    }
        
    // 检查用户缓冲区大小是否足够
    if(count < sizeof(dev->val)) {
        goto out;
    }
        
    // 将寄存器val的值拷贝到用户提供的缓冲区
    if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) {
        err = -EFAULT;
        goto out;
    }

    err = sizeof(dev->val);

out:
    mutex_unlock(&(dev->mtx));  // 释放互斥锁
    return err;
}

5.7 驱动初始化与退出

驱动通过模块初始化和退出函数完成注册和注销:

static int __init d74hc595_init(void)
{
    return platform_driver_register(&d74hc595_driver);  // 注册平台驱动
}

static void __exit d74hc595_exit(void)
{
    platform_driver_unregister(&d74hc595_driver);  // 注销平台驱动
}

module_init(d74hc595_init);
module_exit(d74hc595_exit);

6. 用户空间接口

6.1 字符设备文件

/dev/hc595
  • 写入: 向设备写入32位数据,格式为二进制
  • 读取: 读取当前输出的数据
// 示例代码
int fd = open("/dev/hc595", O_RDWR);
if (fd < 0) {
    perror("open");
    return -1;
}

unsigned int data = 0x12345678;
writ(fd, &data, sizeof(data));

close(fd);

6.2 sysfs属性文件

位于 /sys/class/drvtest/hc595/ 目录下:

属性文件功能操作
out输出数据读写
ds数据引脚控制读写
oe输出使能控制读写
stcp存储时钟控制读写
shcp移位时钟控制读写
enable使能引脚控制读写
# 示例:通过sysfs设置输出数据
echo "1234" > /sys/class/drvtest/hc595/out

# 示例:读取当前输出数据
cat /sys/class/drvtest/hc595/out

7. 设备树配置

7.1 设备树节点示例

&pio {
    d74hc595@0 {
        compatible = "d74hc595";
        ds-gpios = <&pio PE 0 GPIO_ACTIVE_HIGH>;
        oe-gpios = <&pio PE 1 GPIO_ACTIVE_HIGH>;
        stcp-gpios = <&pio PE 2 GPIO_ACTIVE_HIGH>;
        shcp-gpios = <&pio PE 3 GPIO_ACTIVE_HIGH>;
        enable-gpios = <&pio PE 4 GPIO_ACTIVE_HIGH>;
        status = "okay";
    };
};

7.2 属性说明

属性类型描述
compatible字符串设备兼容性标识,必须为"d74hc595"
ds-gpiosGPIO数据输入引脚
oe-gpiosGPIO输出使能引脚
stcp-gpiosGPIO存储寄存器时钟引脚
shcp-gpiosGPIO移位寄存器时钟引脚
enable-gpiosGPIO芯片使能引脚
status字符串设备状态,"okay"表示启用

8. 总结

本驱动程序具有以下特点:

  1. 分层设计: 将设备框架与硬件控制分离,提高代码可维护性和可移植性
  2. 设备树支持: 通过设备树动态配置GPIO引脚,增强驱动灵活性
  3. 多种接口: 提供字符设备文件和sysfs属性文件两种用户空间接口
  4. 并发安全: 使用互斥锁保护设备访问,确保并发安全
  5. 可扩展性: 支持级联多个74HC595芯片,扩展更多输出引脚

该驱动程序可广泛应用于需要扩展GPIO输出的嵌入式Linux系统,如LED矩阵控制、数码管显示、继电器控制等场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值