Linux 驱动开发 - 编写 MMA7660 三轴加速度传感器
- 2022 年 4 月 08 日
 本文字数:4370 字
阅读完需:约 14 分钟
1. MMA7660 芯片介绍
MMA7660FC 是 ± 1.5 克的三轴数字输出、超低功率、紧凑型电容式微电机的三轴加速度计,是非常低功耗,小型容性 MEMS 的传感器。具有低通滤波器,用于偏移和增益误差补偿, 以及用户可配置的转换成 6 位分辨率,用户可配置输出速率等功能。MMA7660 芯片可以通过中断引脚(INT)向外通知传感器数据变化、方向、姿态识别等信息。模拟工作电压范围是 2.4V 至 3.6V,数字工作电压范围是 1.71V 到 3.6V 。常用在手机、掌上电脑、车载导航,便携式电脑的防盗,自动自行车刹车灯、运动检测手环、数码机、自动叫醒闹钟里等等。
特别是计步的功能是现在最常见,不管是智能手环、还是手机都带有三轴加速度计,可以记录每天的步数,计算运动量等。现在很多的不倒翁,无人机、相机云台,很多常见的产品里都能看到三轴加速计的身影。
通过 MMA7660 可以做出很多项目: 比如: 老人防跌倒手环、自行车自动刹车灯,智能闹钟,烤火炉跌倒自动断电、运动手环等等。
这篇文章就介绍如何在 Linux 下编写 MMA7660 三轴加速度芯片的驱动,读取当前芯片的方向姿态,得到 X,Y,Z 三个轴的数据。MMA7660 是 IIC 接口的,当前驱动就采用标准的 IIC 子系统编写驱动,使用字符设备框架将得到的数据上传递给应用层。
 
 2. 硬件连线
当前使用的开发板是友善之臂 Tiny4412 开发板,使用三星 EXYNOS4412 芯片,板子本身自带了一颗 MMA7660 芯片,芯片的原理图如下:
 
 内核本身有 MMA7660 的驱动,下面是源码的路径:
 
 
 如果加载自己编写的驱动,还需要去掉原来内核自带的驱动,不然无法匹配。
Device Drivers  --->  <*> Hardware Monitoring support  --->          <*>   Freescale MMA7660 Accelerometer   (将*号去掉,编译内核、烧写内核即可)
 3. 源代码
3.1 mma7660 设备端代码: IIC 子系统
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/i2c.h>#include <linux/gpio.h>#include <mach/gpio.h>#include <plat/gpio-cfg.h>
static struct i2c_client *i2cClient = NULL;
static unsigned short  i2c_addr_list[]= {0x4c, I2C_CLIENT_END};/*地址队列*/
/*1. 获取控制器(总线)2. 探测设备是否存在3. 定义一个名字用于找到驱动端 */static int __init mma7660_dev_init(void){  /*mach-tiny4412.c*/  struct i2c_adapter *i2c_adap=NULL;  /*获取到的总线存放在这个结构体*/  struct i2c_board_info i2c_info;     /*设备描述结构体,里面存放着设备的名字还有地址*/
  /*1. 获取IIC控制器*/  i2c_adap = i2c_get_adapter(3);     /*要使用IIC_3号总线*/  if(!i2c_adap)  {    printk("获取IIC控制器信息失败!\n");    return -1;  }    memset(&i2c_info,0,sizeof(struct i2c_board_info));        /*清空结构体*/  strlcpy(i2c_info.type,"mma7660_drv",I2C_NAME_SIZE);    /*名称的赋值*/  i2c_info.irq=EXYNOS4_GPX3(1); /*中断IO口*/
  /*2. 创建IIC设备客户端*/  i2cClient = i2c_new_probed_device(i2c_adap,&i2c_info,i2c_addr_list,NULL);  if(!i2cClient)  {    printk("mma7660_探测地址出现错误!!\n");    return -1;  }
  i2c_put_adapter(i2c_adap);/*设置模块使用计数*/    printk("mma7660_dev_init!!\n");  return 0;}
static void __exit mma7660_dev_exit(void)//平台设备端的出口函数{  printk(" mma7660_dev_exit ok!!\n");
  /*注销设备*/  i2c_unregister_device(i2cClient);
  /*释放*/  i2c_release_client(i2cClient);}
module_init(mma7660_dev_init);module_exit(mma7660_dev_exit);MODULE_LICENSE("GPL");
3.2 mma7660 驱动端代码: IIC 子系统
#include <linux/init.h>#include <linux/module.h>#include <linux/platform_device.h>#include <linux/i2c.h>#include <linux/irq.h>#include <linux/interrupt.h>#include <linux/gpio.h>#include <mach/gpio.h>#include <plat/gpio-cfg.h>#include <linux/workqueue.h>#include <linux/delay.h>
/* MMA7760 Registers */#define MMA7660_XOUT      0x00  // 6-bit output value X#define MMA7660_YOUT      0x01  // 6-bit output value Y#define MMA7660_ZOUT      0x02  // 6-bit output value Z#define MMA7660_TILT      0x03  // Tilt status#define MMA7660_SRST      0x04  // Sampling Rate Status#define MMA7660_SPCNT      0x05  // Sleep Count#define MMA7660_INTSU      0x06  // Interrupt Setup#define MMA7660_MODE      0x07  // Mode#define MMA7660_SR        0x08  // Auto-Wake/Sleep and Debounce Filter#define MMA7660_PDET      0x09  // Tap Detection#define MMA7660_PD        0x0a  // Tap Debounce Count
static const struct i2c_device_id mma7660_id[] ={  {"mma7660_drv",0}, /*设备端的名字,0表示不需要私有数据*/  {}};
static u32 mma7660_irq; /*触摸屏的中断编号*/static struct i2c_client *mma7660_client=NULL;static int  last_tilt = 0;
#define __need_retry(__v)  (__v & (1 << 6))#define __is_negative(__v)  (__v & (1 << 5))
static const char *mma7660_bafro[] = {  "未知", "前面", "背面"};
static const char *mma7660_pola[] = {  "未知",  "左面", "向右",  "保留", "保留",  "向下", "向上",  "保留",};
/*函数功能:读取一个字节的数据*/static int mma7660_read_tilt(struct i2c_client *client, int *tilt){  int val;  do {    val = i2c_smbus_read_byte_data(client, MMA7660_TILT);  } while (__need_retry(val));  *tilt = (val & 0xff);  return 0;}
/*函数功能: 读取XYZ坐标数据*/static int mma7660_read_xyz(struct i2c_client *client, int idx, int *xyz){  int val;  do {    val = i2c_smbus_read_byte_data(client, idx + MMA7660_XOUT);  } while (__need_retry(val));  *xyz = __is_negative(val) ? (val | ~0x3f) : (val & 0x3f);  return 0;}
/*工作队列处理函数*/static void mma7660_worker(struct work_struct *work){  int bafro, pola, shake, tap;  int val = 0;
  mma7660_read_tilt(mma7660_client,&val);
  /* TODO: report it ? */  bafro = val & 0x03;  if (bafro != (last_tilt & 0x03)) {    printk("%s\n", mma7660_bafro[bafro]);  }
  pola = (val >> 2) & 0x07;  if (pola != ((last_tilt >> 2) & 0x07)) {    printk("%s\n", mma7660_pola[pola]);  }
  shake = (val >> 5) & 0x01;  if (shake && shake != ((last_tilt >> 5) & 0x01)) {    printk("Shake\n");  }
  tap = (val >> 7) & 0x01;  if (tap && tap != ((last_tilt >> 7) & 0x01)) {    printk("Tap\n");  }
  /* Save current status */  last_tilt = val;    int axis[3];  int i;  for (i = 0; i < 3; i++)  {    mma7660_read_xyz(mma7660_client, i, &axis[i]);  }  printk("ABS_X=%d\n",axis[0]);  printk("ABS_Y=%d\n",axis[1]);  printk("ABS_Z=%d\n",axis[2]);}
/*函数功能: mma7660初始化*/static int mma7660_initialize(struct i2c_client *client){  int val;
  /* Using test mode to probe chip */  i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);  mdelay(10);  i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x04);  mdelay(10);  i2c_smbus_write_byte_data(client, MMA7660_XOUT, 0x3f);  i2c_smbus_write_byte_data(client, MMA7660_YOUT, 0x01);  i2c_smbus_write_byte_data(client, MMA7660_ZOUT, 0x15);  val = i2c_smbus_read_byte_data(client, MMA7660_ZOUT);  if (val != 0x15) {    dev_err(&client->dev, "no device\n");    return -ENODEV;  }
  /* Goto standby mode for configuration */  i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);  mdelay(10);
  /* Sample rate: 64Hz / 16Hz; Filt: 3 samples  */  i2c_smbus_write_byte_data(client, MMA7660_SR, ((2<<5) | (1<<3) | 1));
  /* Sleep count */  i2c_smbus_write_byte_data(client, MMA7660_SPCNT, 0xA0);
  /* Tap detect and debounce ~4ms */  i2c_smbus_write_byte_data(client, MMA7660_PDET, 4);  i2c_smbus_write_byte_data(client, MMA7660_PD, 15);
  /* Enable interrupt except exiting Auto-Sleep */  i2c_smbus_write_byte_data(client, MMA7660_INTSU, 0xe7);
  /* IPP, Auto-wake, auto-sleep and standby */  i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x59);  mdelay(10);
  /* Save current tilt status */  mma7660_read_tilt(client, &last_tilt);
  mma7660_client = client;  return 0;}
/*静态方式初始化工作队列*/DECLARE_WORK(mma7660_work,mma7660_worker);static irqreturn_t mma7660_interrupt(int irq, void *dev_id){  /*调度共享工作队列*/  schedule_work(&mma7660_work);  return IRQ_HANDLED;}
/*匹配成功时调用*/static int mma7660_probe(struct i2c_client *client, const struct i2c_device_id *device_id){  printk("mma7660_probe!!!\n");  printk("驱动端IIC匹配的地址=0x%x\n",client->addr);    mma7660_client=client;    /*1. 注册中断*/  mma7660_irq=gpio_to_irq(client->irq);/*获取中断编号*/    if(request_irq(mma7660_irq,mma7660_interrupt,IRQF_TRIGGER_FALLING,"mma7660_irq",NULL)!=0)    {    printk("mma7660_中断注册失败!\n");  }    /*2. 初始化mma7660*/  if(mma7660_initialize(client) < 0)  {    printk(" 初始化mma7660失败!\n");  }    return 0;}
static int mma7660_remove(struct i2c_client *client){  free_irq(mma7660_irq,NULL);  printk("mma7660_remove!!!\n");  return 0;}
struct i2c_driver i2c_drv ={  .driver =  {    .name = "mma7660",    .owner = THIS_MODULE,  },    .probe = mma7660_probe,   //探测函数  .remove = mma7660_remove, //资源卸载  .id_table = mma7660_id,   //里面有一个名字的参数用来匹配设备端名字};
static int __init mma7660_drv_init(void){  /*向iic总线注册一个驱动*/  i2c_add_driver(&i2c_drv);  return 0;}
static void __exit mma7660_drv_exit(void){  /*从iic总线注销一个驱动*/  i2c_del_driver(&i2c_drv);}
module_init(mma7660_drv_init);module_exit(mma7660_drv_exit);MODULE_LICENSE("GPL");
版权声明: 本文为 InfoQ 作者【DS小龙哥】的原创文章。
原文链接:【http://xie.infoq.cn/article/6d806f0f9f6cf558d9e6aa371】。文章转载请联系作者。
DS小龙哥
之所以觉得累,是因为说的比做的多。 2022.01.06 加入
熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域











    
评论