楚雄彝族自治州网站建设_网站建设公司_轮播图_seo优化
2025/12/22 23:19:55 网站建设 项目流程

Linux系统74HC595驱动程序解析(基于设备树配置的多设备)

1. 驱动概述

本驱动程序是基于Linux内核的74HC595串行移位寄存器驱动,支持通过设备树进行配置,提供了字符设备接口和sysfs接口,方便用户空间程序控制74HC595芯片。

驱动特点

  • 基于Linux平台设备模型
  • 支持设备树配置
  • 提供字符设备和sysfs两种访问接口
  • 支持并发访问保护
  • 可配置的GPIO映射

2. 硬件介绍

74HC595是一款8位串行输入/并行输出移位寄存器,具有以下特性:

  • 8位串行输入
  • 8位并行输出
  • 三态输出缓冲
  • 级联能力强
  • 移位时钟频率可达25MHz

主要引脚功能

  • DS (SER):串行数据输入
  • SHCP (SRCLK):移位寄存器时钟
  • STCP (RCLK):存储寄存器时钟
  • OE:输出使能(低电平有效)
  • MR:主复位(低电平有效)
  • Q0-Q7:并行数据输出
  • Q7’:串行数据输出(用于级联)

3. 驱动架构

驱动采用Linux平台设备驱动模型,主要由以下部分组成:

  • 设备树解析模块
  • GPIO控制模块
  • 字符设备操作模块
  • Sysfs接口模块
  • 74HC595控制逻辑模块

驱动整体架构

+----------------+ +----------------+ +----------------+ +----------------+ | 用户空间程序 | --> | 字符设备接口 | --> | 74HC595控制逻辑 | --> | GPIO硬件 | +----------------+ +----------------+ +----------------+ +----------------+ ^ ^ | | v v +----------------+ +----------------+ | Sysfs接口 | --> | 74HC595控制逻辑 | +----------------+ +----------------+

4. 核心数据结构

4.1 GPIO枚举定义

enum{GPIO_NUM_OE=0,// 输出使能GPIO_NUM_DS,// 串行数据输入GPIO_NUM_SHCP,// 移位寄存器时钟GPIO_NUM_STCP,// 存储寄存器时钟GPIO_NUM_ENABLE,// 使能引脚GPIO_NUM_MAX};

4.2 设备控制结构体

structd74hc595_ctrl_dev{unsignedintval[1];// 设备寄存器值,用于存储要输出到74HC595的数据structmutexmtx;// 互斥锁,用于保护设备访问的并发安全性structcdevdev;// 字符设备结构体,用于注册字符设备structclass*sys_class;// 设备类指针intdev_major;// 主设备号intdev_minor;// 次设备号intgpio_n[GPIO_NUM_MAX];// GPIO编号数组charname[32];// 设备名称};

4.3 GPIO描述结构体

structhc595_gpio_desc{constchar*name;// GPIO名称intgpio;// GPIO编号intinit_value;// 初始值};

5. 主要功能实现

5.1 GPIO初始化与配置

驱动从设备树中获取GPIO配置信息:

// 定义GPIO描述数组,包含74HC595芯片所需的所有GPIO引脚staticstructhc595_gpio_descg_74hc595_gpios[]={{"ds",-1,0},// 数据输入引脚,初始值为0{"oe",-1,1},// 输出使能引脚,初始值为1(禁用输出){"stcp",-1,0},// 存储寄存器时钟引脚,初始值为0{"shcp",-1,0},// 移位寄存器时钟引脚,初始值为0{"enable",-1,0},// 使能引脚,初始值为0};staticintd74hc595_get_dt_gpio(structd74hc595_ctrl_dev*pdrvdata,structdevice*dev){inti,ret;for(i=0;i<ARRAY_SIZE(g_74hc595_gpios);i++){pdrvdata->gpio_n[i]=of_get_named_gpio(dev->of_node,g_74hc595_gpios[i].name,0);if(!gpio_is_valid(pdrvdata->gpio_n[i])){dev_err(dev,"Failed to get %s gpio\n",g_74hc595_gpios[i].name);return-ENODEV;}ret=gpio_request(pdrvdata->gpio_n[i],g_74hc595_gpios[i].name);if(ret){dev_err(dev,"Failed to request %s gpio\n",g_74hc595_gpios[i].name);returnret;}gpio_direction_output(pdrvdata->gpio_n[i],g_74hc595_gpios[i].init_value);}return0;}

5.2 74HC595输出控制

核心输出函数实现:

intd74hc595_ctrl_output(structd74hc595_ctrl_dev*pdrvdata,unsignedintudata,intbitnum){inti;/* 禁用输出 */gpio_direction_output(pdrvdata->gpio_n[GPIO_NUM_OE],1);/* 发送bitnum位数据 */for(i=0;i<bitnum;i++){/* 设置数据位 */gpio_direction_output(pdrvdata->gpio_n[GPIO_NUM_DS],(udata>>i)&0x01);/* 移位时钟操作 */gpio_direction_output(pdrvdata->gpio_n[GPIO_NUM_SHCP],0);d74hc595_delay_func(1);gpio_direction_output(pdrvdata->gpio_n[GPIO_NUM_SHCP],1);d74hc595_delay_func(1);/* 存储时钟操作 */gpio_direction_output(pdrvdata->gpio_n[GPIO_NUM_STCP],0);d74hc595_delay_func(1);gpio_direction_output(pdrvdata->gpio_n[GPIO_NUM_STCP],1);d74hc595_delay_func(1);}/* 数据发送完成,启用输出 */gpio_direction_output(pdrvdata->gpio_n[GPIO_NUM_OE],0);return0;}

5.3 延迟函数

staticvoidd74hc595_delay_func(intus){udelay(us);// 微秒级忙等待}

6. 字符设备操作

驱动实现了标准的字符设备操作接口:

6.1 打开与关闭

staticintd74hc595_ctrl_open(structinode*inode,structfile*filp){structd74hc595_ctrl_dev*dev;dev=container_of(inode->i_cdev,structd74hc595_ctrl_dev,dev);filp->private_data=dev;return0;}staticintd74hc595_ctrl_release(structinode*inode,structfile*filp){return0;}

6.2 读写操作

staticssize_td74hc595_ctrl_read(structfile*filp,char__user*buf,size_tcount,loff_t*f_pos){// 读取设备寄存器值// ...}staticssize_td74hc595_ctrl_write(structfile*filp,constchar__user*buf,size_tcount,loff_t*f_pos){// 写入设备寄存器值// ...}

7. Sysfs接口

驱动通过sysfs提供了更灵活的设备控制接口:

7.1 Sysfs属性定义

// 定义show函数宏d74hc595_ctrl_show(out)d74hc595_ctrl_store(out)d74hc595_ctrl_show(ds)d74hc595_ctrl_store(ds)d74hc595_ctrl_show(oe)d74hc595_ctrl_store(oe)d74hc595_ctrl_show(stcp)d74hc595_ctrl_store(stcp)d74hc595_ctrl_show(shcp)d74hc595_ctrl_store(shcp)d74hc595_ctrl_show(enable)d74hc595_ctrl_store(enable)// 定义设备属性数组staticstructdevice_attribute*attr_array[]={&dev_attr_out,&dev_attr_ds,&dev_attr_oe,&dev_attr_stcp,&dev_attr_shcp,&dev_attr_enable,};

7.2 属性访问函数

staticssize_t__d74hc595_ctrl_get_val(structd74hc595_ctrl_dev*drvdata,char*buf,constchar*name){intlen=256;if(mutex_lock_interruptible(&(drvdata->mtx))){return-ERESTARTSYS;}len=sprintf(buf,"%02x\n",drvdata->val[0]);mutex_unlock(&(drvdata->mtx));returnlen;}staticssize_t__d74hc595_ctrl_set_val(structd74hc595_ctrl_dev*drvdata,constchar*buf,size_tcount,constchar*name){unsignedintval=0;val=simple_strtol(buf,NULL,16);if(mutex_lock_interruptible(&(drvdata->mtx))){return-ERESTARTSYS;}drvdata->val[0]=val;if(!strcmp("out",name))d74hc595_output(drvdata,val);elsed74hc595_set_gpio(drvdata,name,val);mutex_unlock(&(drvdata->mtx));returncount;}

8. 设备树支持

驱动支持通过设备树配置GPIO映射:

8.1 设备树匹配表

staticconststructof_device_idd74hc595_of_match[]={{.compatible="d74hc595",},{}};MODULE_DEVICE_TABLE(of,d74hc595_of_match);

8.2 设备树示例

&pio { hc595@0 { compatible = "d74hc595"; label = "display"; ds-gpios = <&pio PC 12 GPIO_ACTIVE_HIGH>; oe-gpios = <&pio PC 13 GPIO_ACTIVE_HIGH>; stcp-gpios = <&pio PC 14 GPIO_ACTIVE_HIGH>; shcp-gpios = <&pio PC 15 GPIO_ACTIVE_HIGH>; enable-gpios = <&pio PC 16 GPIO_ACTIVE_HIGH>; }; hc595@1 { compatible = "d74hc595"; label = "leds"; ds-gpios = <&pio PD 0 GPIO_ACTIVE_HIGH>; oe-gpios = <&pio PD 1 GPIO_ACTIVE_HIGH>; stcp-gpios = <&pio PD 2 GPIO_ACTIVE_HIGH>; shcp-gpios = <&pio PD 3 GPIO_ACTIVE_HIGH>; enable-gpios = <&pio PD 4 GPIO_ACTIVE_HIGH>; }; };

说明

  1. 增加了两个hc595设备,分别使用不同的地址(@0和@1)
  2. 每个设备都添加了label属性,用于生成唯一的设备名称
  3. 第二个设备使用了不同的GPIO引脚(PD组)
  4. 设备名称将根据label自动生成:
    • 第一个设备:d74hc595-hc595-display
    • 第二个设备:d74hc595-hc595-leds

9. 驱动注册与初始化

9.1 驱动初始化

staticint__initd74hc595_init(void){returnplatform_driver_register(&d74hc595_driver);}staticvoid__exitd74hc595_exit(void){platform_driver_unregister(&d74hc595_driver);}module_init(d74hc595_init);module_exit(d74hc595_exit);

9.2 Platform驱动结构体

staticstructplatform_driverd74hc595_driver={.probe=d74hc595_probe,// 设备探测函数.remove=d74hc595_remove,// 设备移除函数.driver={.name="d74hc595",// 驱动名称.of_match_table=d74hc595_of_match,// 设备树匹配表},};

9.3 Probe函数

Probe函数是设备被发现时的入口点,主要完成以下工作:

  • 生成设备名称
  • 分配设备号
  • 分配设备结构体
  • 初始化字符设备
  • 创建设备类和设备文件
  • 创建设备属性文件
  • 解析设备树配置
  • 初始化硬件
完整Probe函数源码(来自当前目录d74hc595.c)
staticintd74hc595_probe(structplatform_device*pdev){interr=-1;dev_tdev=0;structdevice*temp=NULL;inti;structd74hc595_ctrl_dev*pdrvdata=NULL;//printk(KERN_ALERT"d74hc595_probe\n");/*分配设备结构体变量*/pdrvdata=devm_kzalloc(&pdev->dev,sizeof(structd74hc595_ctrl_dev),GFP_KERNEL);if(!pdrvdata){err=-ENOMEM;dev_err(&pdev->dev,"Failed to alloc pdrvdata.\n");gotounregister;}// 生成设备名称if(pdev->dev.of_node){constchar*node_name=pdev->dev.of_node->name;constchar*label=NULL;// 尝试读取label属性of_property_read_string(pdev->dev.of_node,"label",&label);if(label){snprintf(pdrvdata->name,sizeof(pdrvdata->name),"d74hc595-%s",label);}else{intreg=0;of_property_read_u32(pdev->dev.of_node,"reg",&reg);snprintf(pdrvdata->name,sizeof(pdrvdata->name),"d74hc595-%s-%d",node_name,reg);}}else{// 没有设备树,使用平台设备名snprintf(pdrvdata->name,sizeof(pdrvdata->name),"d74hc595-%s",pdev->dev.of_node->name);}/*动态分配主设备和从设备号*/err=alloc_chrdev_region(&dev,0,1,pdrvdata->name);if(err<0){dev_err(&pdev->dev,"Failed to alloc char dev region.\n");gotofail;}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);gotocleanup;}/*创建设备类别*/pdrvdata->sys_class=class_create(THIS_MODULE,pdrvdata->name);if(IS_ERR(pdrvdata->sys_class)){err=PTR_ERR(pdrvdata->sys_class);dev_err(&pdev->dev,"Failed to create class.\n");gotodestroy_cdev;}/*创建设备文件*/temp=device_create(pdrvdata->sys_class,NULL,dev,"%s",pdrvdata->name);if(IS_ERR(temp)){err=PTR_ERR(temp);dev_err(&pdev->dev,"Failed to create device.");gotodestroy_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.");gotodestroy_device;}}dev_set_drvdata(temp,pdrvdata);#ifdefDRV_PROC/*创建/proc文件*/d74hc595_ctrl_create_proc();#endif/*从设备树获取GPIO配置*/err=d74hc595_get_dt_gpio(pdrvdata,&pdev->dev);if(err<0){dev_err(&pdev->dev,"Failed to get GPIO from device tree\n");gotoremove_proc;}/*初始化硬件*/d74hc595_ctrl_init_hardware(pdrvdata);dev_info(&pdev->dev,"d74hc595 driver probe success.\n");return0;remove_proc:#ifdefDRV_PROCd74hc595_ctrl_remove_proc();#endifdestroy_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(pdrvdata->dev_major,pdrvdata->dev_minor),1);fail:returnerr;}
Probe函数解析
1. 设备结构体分配

函数首先使用devm_kzalloc分配设备结构体内存,这是一个带有自动释放功能的分配函数,当驱动卸载时,内核会自动释放这块内存:

pdrvdata=devm_kzalloc(&pdev->dev,sizeof(structd74hc595_ctrl_dev),GFP_KERNEL);
2. 设备名称生成逻辑

设备名称生成支持三种情况:

  • 如果设备树节点有label属性,使用d74hc595-label格式
  • 如果没有label属性但有reg属性,使用d74hc595-节点名-reg值格式
  • 注意:代码中存在一个bug,在else分支中仍然尝试访问pdev->dev.of_node->name,这会导致空指针解引用
3. 设备号分配

使用alloc_chrdev_region动态分配字符设备的主次设备号:

err=alloc_chrdev_region(&dev,0,1,pdrvdata->name);
4. 设备初始化

调用__d74hc595_ctrl_setup_dev函数初始化字符设备,设置文件操作方法等。

5. 设备类和设备文件创建
  • 使用class_create创建设备类
  • 使用device_create创建设备文件
  • 使用device_create_file为设备创建多个属性文件
6. 设备树GPIO配置获取

调用d74hc595_get_dt_gpio函数从设备树中解析GPIO配置信息。

7. 硬件初始化

调用d74hc595_ctrl_init_hardware函数初始化硬件,设置初始状态。

8. 错误处理

函数使用了goto跳转的方式实现错误处理,确保在任何步骤失败时都能正确释放已分配的资源。

remove_proc:#ifdefDRV_PROCd74hc595_ctrl_remove_proc();#endifdestroy_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(pdrvdata->dev_major,pdrvdata->dev_minor),1);fail:returnerr;

10. 使用方法示例

10.1 通过字符设备访问

#include<stdio.h>#include<fcntl.h>#include<unistd.h>intmain(){intfd;unsignedintdata=0xAA;// 要输出的数据// 打开设备文件,设备名称根据实际情况变化fd=open("/dev/d74hc595-leds",O_RDWR);if(fd<0){perror("open");return-1;}// 写入数据write(fd,&data,sizeof(data));// 关闭设备close(fd);return0;}

注意:设备名称会根据设备树配置动态生成,具体名称可以通过ls /dev/d74hc595-*命令查看。

10.2 通过Sysfs访问

# 输出数据0x55到74HC595echo"55">/sys/class/d74hc595-leds/d74hc595-leds/out# 读取当前输出值cat/sys/class/d74hc595-leds/d74hc595-leds/out# 直接控制OE引脚echo"1">/sys/class/d74hc595-leds/d74hc595-leds/oe# 禁用输出echo"0">/sys/class/d74hc595-leds/d74hc595-leds/oe# 启用输出

注意:Sysfs路径会根据设备名称动态生成,具体路径可以通过ls /sys/class/d74hc595-*命令查看。

11. 并发访问保护

驱动使用互斥锁保护并发访问:

/* 同步访问,获取互斥锁 */if(mutex_lock_interruptible(&(dev->mtx))){return-ERESTARTSYS;}/* 临界区操作 *//* 释放互斥锁 */mutex_unlock(&(dev->mtx));

12. 总结

12.1 驱动特点

  • 支持设备树配置,便于移植
  • 提供多种访问接口,灵活方便
  • 良好的并发访问控制
  • 模块化设计,易于维护和扩展

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询