2026/3/28 19:19:09
网站建设
项目流程
哈尔滨建站,淘宝客网站的模板,适合网站开发工程师的公司,旅游网站这么做Linux系统74HC595驱动程序解析(基于设备树配置的多设备)
1. 驱动概述
本驱动程序是基于Linux内核的74HC595串行移位寄存器驱动#xff0c;支持通过设备树进行配置#xff0c;提供了字符设备接口和sysfs接口#xff0c;方便用户空间程序控制74HC595芯片。
驱动特点#xff1…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_OE0,// 输出使能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(i0;iARRAY_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;}retgpio_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(i0;ibitnum;i){/* 设置数据位 */gpio_direction_output(pdrvdata-gpio_n[GPIO_NUM_DS],(udatai)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;devcontainer_of(inode-i_cdev,structd74hc595_ctrl_dev,dev);filp-private_datadev;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){intlen256;if(mutex_lock_interruptible((drvdata-mtx))){return-ERESTARTSYS;}lensprintf(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){unsignedintval0;valsimple_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[]{{.compatibled74hc595,},{}};MODULE_DEVICE_TABLE(of,d74hc595_of_match);8.2 设备树示例pio { hc5950 { 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; }; hc5951 { 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; }; };说明增加了两个hc595设备分别使用不同的地址0和1每个设备都添加了label属性用于生成唯一的设备名称第二个设备使用了不同的GPIO引脚PD组设备名称将根据label自动生成第一个设备d74hc595-hc595-display第二个设备d74hc595-hc595-leds9. 驱动注册与初始化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{.probed74hc595_probe,// 设备探测函数.removed74hc595_remove,// 设备移除函数.driver{.named74hc595,// 驱动名称.of_match_tabled74hc595_of_match,// 设备树匹配表},};9.3 Probe函数Probe函数是设备被发现时的入口点主要完成以下工作生成设备名称分配设备号分配设备结构体初始化字符设备创建设备类和设备文件创建设备属性文件解析设备树配置初始化硬件完整Probe函数源码来自当前目录d74hc595.cstaticintd74hc595_probe(structplatform_device*pdev){interr-1;dev_tdev0;structdevice*tempNULL;inti;structd74hc595_ctrl_dev*pdrvdataNULL;//printk(KERN_ALERTd74hc595_probe\n);/*分配设备结构体变量*/pdrvdatadevm_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_namepdev-dev.of_node-name;constchar*labelNULL;// 尝试读取label属性of_property_read_string(pdev-dev.of_node,label,label);if(label){snprintf(pdrvdata-name,sizeof(pdrvdata-name),d74hc595-%s,label);}else{intreg0;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);}/*动态分配主设备和从设备号*/erralloc_chrdev_region(dev,0,1,pdrvdata-name);if(err0){dev_err(pdev-dev,Failed to alloc char dev region.\n);gotofail;}pdrvdata-dev_majorMAJOR(dev);pdrvdata-dev_minorMINOR(dev);/*初始化设备*/err__d74hc595_ctrl_setup_dev(pdrvdata);if(err){dev_err(pdev-dev,Failed to setup dev: %d.\n,err);gotocleanup;}/*创建设备类别*/pdrvdata-sys_classclass_create(THIS_MODULE,pdrvdata-name);if(IS_ERR(pdrvdata-sys_class)){errPTR_ERR(pdrvdata-sys_class);dev_err(pdev-dev,Failed to create class.\n);gotodestroy_cdev;}/*创建设备文件*/tempdevice_create(pdrvdata-sys_class,NULL,dev,%s,pdrvdata-name);if(IS_ERR(temp)){errPTR_ERR(temp);dev_err(pdev-dev,Failed to create device.);gotodestroy_class;}/*创建属性文件*/for(i0;iARRAY_SIZE(attr_array);i){errdevice_create_file(temp,attr_array[i]);if(err0){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配置*/errd74hc595_get_dt_gpio(pdrvdata,pdev-dev);if(err0){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分配设备结构体内存这是一个带有自动释放功能的分配函数当驱动卸载时内核会自动释放这块内存pdrvdatadevm_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动态分配字符设备的主次设备号erralloc_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 通过字符设备访问#includestdio.h#includefcntl.h#includeunistd.hintmain(){intfd;unsignedintdata0xAA;// 要输出的数据// 打开设备文件设备名称根据实际情况变化fdopen(/dev/d74hc595-leds,O_RDWR);if(fd0){perror(open);return-1;}// 写入数据write(fd,data,sizeof(data));// 关闭设备close(fd);return0;}注意设备名称会根据设备树配置动态生成具体名称可以通过ls /dev/d74hc595-*命令查看。10.2 通过Sysfs访问# 输出数据0x55到74HC595echo55/sys/class/d74hc595-leds/d74hc595-leds/out# 读取当前输出值cat/sys/class/d74hc595-leds/d74hc595-leds/out# 直接控制OE引脚echo1/sys/class/d74hc595-leds/d74hc595-leds/oe# 禁用输出echo0/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 驱动特点支持设备树配置便于移植提供多种访问接口灵活方便良好的并发访问控制模块化设计易于维护和扩展