news 2026/10/15 3:35:05

Linux 驱动实战:ADXL345 驱动

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 驱动实战:ADXL345 驱动

Linux 驱动实战:从 SPI 到 ioctl、mmap,完整 ADXL345 驱动

最近整理嵌入式 Linux 驱动时,把SPI 驱动、内核模块、字符设备、ioctl、mmap串成了一个完整例子:用 SPI 读取 ADXL345 三轴加速度数据,并通过字符设备提供read、ioctl和mmap接口。

一、ADXL345 与 SPI 接口

ADXL345 是一款三轴加速度传感器,支持 SPI 和 I2C 接口,量程可配置为 ±2g / ±4g / ±8g / ±16g。

SPI 通信时,常用两种方式:

  • spi_write_then_read:适合简单的寄存器读写;
  • spi_message+spi_transfer:适合多字节、复杂传输。

读取三轴数据时,通常从DATAX0寄存器0x32开始连续读取 6 字节:

tx[0] = 0x32 | 0x80 | 0x40; /* 读操作 + 多字节 */

其中:

  • 0x80:读标志;
  • 0x40:多字节连续读;
  • 0x32:起始寄存器地址。

二、内核模块与驱动注册

Linux 驱动通常以内核模块形式存在。最基础写法是:

module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL");

如果驱动是平台设备驱动,可以用:

module_platform_driver(my_driver);

如果是 SPI 设备驱动,更常见的是:

module_spi_driver(my_spi_driver);

module_platform_driver和module_spi_driver都是封装宏,内部仍然基于module_init/module_exit,只是自动完成了platform_driver_register或spi_driver注册。

三、字符设备接口

字符设备驱动通过file_operations向用户空间暴露接口。

常见映射关系:

用户空间内核回调
open().open
read().read
ioctl().unlocked_ioctl
mmap().mmap
close().release

read适合返回数据流,比如三轴加速度原始数据;ioctl适合发送控制命令,比如设置量程、复位、读取器件 ID。

四、ioctl 控制命令

ioctl 命令码通常使用<linux/ioctl.h>中的宏定义:

#define ADXL345_IOC_MAGIC 'A' #define ADXL345_IOC_RESET _IO(ADXL345_IOC_MAGIC, 0) #define ADXL345_IOC_SET_RANGE _IOW(ADXL345_IOC_MAGIC, 1, unsigned char) #define ADXL345_IOC_GET_ID _IOR(ADXL345_IOC_MAGIC, 2, unsigned char)

方向从用户空间视角看:

  • _IO:无参数;
  • _IOW:用户向内核写;
  • _IOR:内核向用户读;
  • _IOWR:双向。

驱动实现示例:

static long adxl345_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct adxl345_dev *dev = filp->private_data; switch (cmd) { case ADXL345_IOC_GET_ID: if (copy_to_user((unsigned char __user *)arg, &dev->id, sizeof(dev->id))) return -EFAULT; break; case ADXL345_IOC_SET_RANGE: /* 从用户空间获取量程并写入寄存器 */ break; case ADXL345_IOC_RESET: /* 复位或重新初始化传感器 */ break; default: return -ENOTTY; } return 0; }

五、mmap 内存映射

mmap可以把内核物理内存映射到用户空间,避免每次读写都走copy_to_user/copy_from_user。

驱动中实现.mmap时,通常步骤是:

  1. 分配物理连续内存;
  2. 计算物理页帧号 PFN;
  3. 设置 VMA 属性;
  4. 调用remap_pfn_range建立映射。

示例:

static int adxl345_mmap(struct file *filp, struct vm_area_struct *vma) { struct adxl345_dev *dev = filp->private_data; unsigned long size = vma->vm_end - vma->vm_start; unsigned long pfn; if (size > dev->buf_size) return -EINVAL; pfn = virt_to_phys(dev->sample_buf) >> PAGE_SHIFT; vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); }

如果是 DMA 缓冲区,PFN 计算要改为使用 DMA 物理地址,不能简单依赖virt_to_phys。

六、完整驱动骨架

#include <linux/module.h> #include <linux/init.h> #include <linux/spi/spi.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/slab.h> #include <linux/mutex.h> #include <linux/delay.h> #include <linux/ioctl.h> #include <linux/platform_device.h> #include <linux/mm.h> #define ADXL345_REG_DEVID 0x00 #define ADXL345_REG_POWER_CTL 0x2D #define ADXL345_REG_DATAX0 0x32 #define ADXL345_IOC_MAGIC 'A' #define ADXL345_IOC_RESET _IO(ADXL345_IOC_MAGIC, 0) #define ADXL345_IOC_SET_RANGE _IOW(ADXL345_IOC_MAGIC, 1, unsigned char) #define ADXL345_IOC_GET_ID _IOR(ADXL345_IOC_MAGIC, 2, unsigned char) #define ADXL345_SAMPLE_NUM 64 struct adxl345_dev { struct spi_device *spi; struct cdev cdev; dev_t devno; struct mutex lock; unsigned char id; unsigned char range; void *sample_buf; unsigned int buf_size; }; static struct adxl345_dev *g_adxl; static int adxl345_spi_read_regs(struct spi_device *spi, unsigned char reg, unsigned char *buf, unsigned int len) { unsigned char tx[7] = {0}; unsigned char rx[7] = {0}; struct spi_message msg; struct spi_transfer xfer; if (len > 6) return -EINVAL; tx[0] = reg | 0x80 | 0x40; memset(&xfer, 0, sizeof(xfer)); xfer.tx_buf = tx; xfer.rx_buf = rx; xfer.len = len + 1; xfer.delay_usecs = 20; spi_message_init(&msg); spi_message_add_tail(&xfer, &msg); return spi_sync(spi, &msg) ? : (memcpy(buf, rx + 1, len), 0); } static int adxl345_spi_write_reg(struct spi_device *spi, unsigned char reg, unsigned char val) { unsigned char tx[2] = { reg, val }; return spi_write(spi, tx, sizeof(tx)); } static int adxl345_read_xyz(struct adxl345_dev *dev, short *x, short *y, short *z) { unsigned char buf[6]; int ret; ret = adxl345_spi_read_regs(dev->spi, ADXL345_REG_DATAX0, buf, 6); if (ret) return ret; *x = (short)((buf[1] << 8) | buf[0]); *y = (short)((buf[3] << 8) | buf[2]); *z = (short)((buf[5] << 8) | buf[4]); return 0; } static int adxl345_open(struct inode *inode, struct file *filp) { filp->private_data = g_adxl; return 0; } static ssize_t adxl345_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct adxl345_dev *dev = filp->private_data; short xyz[3]; int ret; if (count < sizeof(xyz)) return -EINVAL; mutex_lock(&dev->lock); ret = adxl345_read_xyz(dev, &xyz[0], &xyz[1], &xyz[2]); mutex_unlock(&dev->lock); if (ret) return ret; if (copy_to_user(buf, xyz, sizeof(xyz))) return -EFAULT; return sizeof(xyz); } static long adxl345_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct adxl345_dev *dev = filp->private_data; switch (cmd) { case ADXL345_IOC_RESET: mutex_lock(&dev->lock); adxl345_spi_write_reg(dev->spi, ADXL345_REG_POWER_CTL, 0x00); msleep(10); adxl345_spi_write_reg(dev->spi, ADXL345_REG_POWER_CTL, 0x08); mutex_unlock(&dev->lock); break; case ADXL345_IOC_SET_RANGE: { unsigned char range; if (copy_from_user(&range, (unsigned char __user *)arg, sizeof(range))) return -EFAULT; mutex_lock(&dev->lock); dev->range = range; adxl345_spi_write_reg(dev->spi, 0x31, range); mutex_unlock(&dev->lock); break; } case ADXL345_IOC_GET_ID: mutex_lock(&dev->lock); if (copy_to_user((unsigned char __user *)arg, &dev->id, sizeof(dev->id))) return -EFAULT; mutex_unlock(&dev->lock); break; default: return -ENOTTY; } return 0; } static int adxl345_mmap(struct file *filp, struct vm_area_struct *vma) { struct adxl345_dev *dev = filp->private_data; unsigned long size = vma->vm_end - vma->vm_start; unsigned long pfn; if (size > dev->buf_size) return -EINVAL; pfn = virt_to_phys(dev->sample_buf) >> PAGE_SHIFT; vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP; vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); return remap_pfn_range(vma, vma->vm_start, pfn, size, vma->vm_page_prot); } static const struct file_operations adxl345_fops = { .owner = THIS_MODULE, .open = adxl345_open, .read = adxl345_read, .unlocked_ioctl = adxl345_ioctl, .mmap = adxl345_mmap, }; static int adxl345_probe(struct platform_device *pdev) { struct spi_device *spi; struct adxl345_dev *dev; int ret; spi = spi_alloc_dummy(); if (!spi) return -ENOMEM; dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) { spi_unregister_device(spi); return -ENOMEM; } dev->spi = spi; mutex_init(&dev->lock); dev->buf_size = ADXL345_SAMPLE_NUM * sizeof(short) * 3; dev->sample_buf = kzalloc(dev->buf_size, GFP_KERNEL | GFP_DMA); if (!dev->sample_buf) { ret = -ENOMEM; goto err_free_dev; } ret = alloc_chrdev_region(&dev->devno, 0, 1, "adxl345"); if (ret) goto err_free_buf; cdev_init(&dev->cdev, &adxl345_fops); dev->cdev.owner = THIS_MODULE; ret = cdev_add(&dev->cdev, dev->devno, 1); if (ret) goto err_unregister_chrdev; adxl345_spi_write_reg(spi, ADXL345_REG_POWER_CTL, 0x08); adxl345_spi_read_regs(spi, ADXL345_REG_DEVID, &dev->id, 1); pr_info("adxl345 id = 0x%02x\n", dev->id); g_adxl = dev; platform_set_drvdata(pdev, dev); return 0; err_unregister_chrdev: unregister_chrdev_region(dev->devno, 1); err_free_buf: kfree(dev->sample_buf); err_free_dev: kfree(dev); spi_unregister_device(spi); return ret; } static int adxl345_remove(struct platform_device *pdev) { struct adxl345_dev *dev = platform_get_drvdata(pdev); if (!dev) return 0; cdev_del(&dev->cdev); unregister_chrdev_region(dev->devno, 1); kfree(dev->sample_buf); kfree(dev); g_adxl = NULL; return 0; } static const struct of_device_id adxl345_of_match[] = { { .compatible = "adi,adxl345-spi" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, adxl345_of_match); static struct platform_driver adxl345_driver = { .probe = adxl345_probe, .remove = adxl345_remove, .driver = { .name = "adxl345-spi", .of_match_table = adxl345_of_match, }, }; module_platform_driver(adxl345_driver); MODULE_LICENSE("GPL");

七、用户空间测试

#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include <sys/mman.h> #define ADXL345_IOC_MAGIC 'A' #define ADXL345_IOC_RESET _IO(ADXL345_IOC_MAGIC, 0) #define ADXL345_IOC_SET_RANGE _IOW(ADXL345_IOC_MAGIC, 1, unsigned char) #define ADXL345_IOC_GET_ID _IOR(ADXL345_IOC_MAGIC, 2, unsigned char) #define SAMPLE_NUM 64 int main(void) { int fd = open("/dev/adxl345", O_RDWR); if (fd < 0) { perror("open"); return -1; } unsigned char id; ioctl(fd, ADXL345_IOC_GET_ID, &id); printf("ADXL345 ID: 0x%02x\n", id); unsigned char range = 0x0B; ioctl(fd, ADXL345_IOC_SET_RANGE, &range); short xyz[3]; if (read(fd, xyz, sizeof(xyz)) == sizeof(xyz)) printf("x=%d y=%d z=%d\n", xyz[0], xyz[1], xyz[2]); size_t size = SAMPLE_NUM * sizeof(short) * 3; short *buf = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); if (buf != MAP_FAILED) { printf("mmap addr = %p\n", buf); munmap(buf, size); } close(fd); return 0; }

八、注意事项

  • 上板时spi_device通常来自设备树和 SPI 控制器匹配,示例里的spi_alloc_dummy只是骨架用法。
  • mmap如果后续接 DMA 缓冲区,PFN 需要用 DMA 物理地址计算。
  • read接口目前只演示单次读取,实际采样可以放到工作队列、hrtimer 或内核线程里周期性填充缓冲区。
  • 设备节点可以通过class_create/device_create自动生成,也可以手动mknod。
  • 寄存器地址、量程配置、SPI 模式需要以实际 ADXL345 规格书和硬件连接为准。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 3:34:29

单张纹理驱动的动态发光Shader实现

1. 项目概述&#xff1a;一张贴图如何让武器“呼吸”起来&#xff1f;你有没有在玩3D游戏时&#xff0c;被某把剑刃边缘泛起的幽蓝光晕吸引过&#xff1f;或者看到角色手持法杖&#xff0c;杖身随着施法节奏明暗起伏、仿佛有生命般脉动&#xff1f;这种视觉表现&#xff0c;往往…

作者头像 李华
网站建设 2026/10/15 3:33:39

仓颉编程语言 I/O 节点流完全指南:标准流与文件流的实战解析

文档知识库教程OpenHarmony 【免费下载链接】CangjieCorpus 本仓库为仓颉编程语言语料库&#xff0c;聚焦 仓颉编程语言 核心知识体系&#xff0c;整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源&#xff0c;经结构化处理与专业标注&#xff0c;构建为支持 R…

作者头像 李华
网站建设 2026/10/15 3:32:06

Spring Security源码解析:WebSecurityConfigurerAdapter配置原理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/15 3:31:15

一场用英语讨论中国流量生意的会议

2026 年深圳 SEO 大会公布了一组数字&#xff1a;参会约 500 人&#xff0c;其中中国大陆之外的面孔占 49%。 在中国本土举办、讨论全球搜索生态的会议上&#xff0c;海内外从业者接近对半——这个比例放在五年前难以想象。会议组织者把这种状态概括为“Western Meet Eastern”…

作者头像 李华