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-gpios | GPIO | 数据输入引脚 |
| oe-gpios | GPIO | 输出使能引脚 |
| stcp-gpios | GPIO | 存储寄存器时钟引脚 |
| shcp-gpios | GPIO | 移位寄存器时钟引脚 |
| enable-gpios | GPIO | 芯片使能引脚 |
| status | 字符串 | 设备状态,"okay"表示启用 |
8. 总结
本驱动程序具有以下特点:
- 分层设计: 将设备框架与硬件控制分离,提高代码可维护性和可移植性
- 设备树支持: 通过设备树动态配置GPIO引脚,增强驱动灵活性
- 多种接口: 提供字符设备文件和sysfs属性文件两种用户空间接口
- 并发安全: 使用互斥锁保护设备访问,确保并发安全
- 可扩展性: 支持级联多个74HC595芯片,扩展更多输出引脚
该驱动程序可广泛应用于需要扩展GPIO输出的嵌入式Linux系统,如LED矩阵控制、数码管显示、继电器控制等场景。

2万+

被折叠的 条评论
为什么被折叠?



