话说小时候总盼着放假,一年只有寒假暑假可放,而其中寒假因为带着春节,尤其热闹,更是盼望。小学班主任知道孩子贪玩,到了年底就更没心思好好上课了,提醒我们:“同学们,要是这几天不好好上课,那就是‘一年到头调皮捣蛋’了啊,注意啊!”所以后来我养成个习惯,就是每年末的最后一天也要学点、做点什么,这样可以跟人吹嘘“ouba一年到头刻苦style”。

 

——2012年12月31日 写完了I2C接口的飞思卡尔MPL115A2气压传感器cookie的驱动,第一版

 

这还要从2012年末说起。当时CooCox还是非著名组织,推出了个叫做Cookie的开发板,用的是著名的新塘的32位mcu,兼容著名的Arduino。兴趣所致,上网狂搜,发现他们竟然用雷军卖小米的办法卖板子,饥饿营销啊。

 

之后出于对开源的推崇和对CooCox的兴趣,参与了CooCox的开发开源驱动的活动,本来准备做PCD8544的驱动,把手册翻了n遍,觉得胸有成竹才去申请,没想到Walter竟然早了半天捷足先登,于是乎转而承揽了飞思卡尔MPL115A压力传感器。

 

飞思卡尔MPL115A压力传感器是一款测量气压的传感器,这个系列有2个型号,分别是串口的MPL115A1和I2C的MPL115A2,拿到手册之后发现2款只有协议和引脚定义略有区别,使用流程和算法是一样的。

 

先说使用流程,首先要保证!SHDN为高电平,然后读取几个参数,然后启动转换,再后面是读取气压、温度原始标量,最后是按照规定算法计算出实时气压值。这里后来发现有几个问题:一是使用I2C协议时,由于MPL115A2的地址固定为0x60,所以如果一条总线上要用多个MPL115A2,必须要有多个!SHDN才行;二是传感器参数并不是像我开始设想的那样,是出厂配置不会变,所以只好每次量气压前再读一次;三是得到的是格式不同的定点数,不好计算,这个后面会提到。

 

 

再说算法,算式很简单:

Pcomp= a0+(b1+c12×Tadc)×Padc+b2×Tadc

Pressure(kPa) = Pcomp × (115-50)/1023+ 50

 

但是做起来就费劲了。首先遇到的问题是:传感器上的6个数据是长度不等、小数位各异的。最开始用2个函数来做转换,实验代码如下:

 

 

#define MY_DEBUG printf

#include "stdio.h"

 

float getFloatByBIN(signed short intB,unsigned short iShift)

{

float f=0.0;

f+=intB;

f/=(1<<iShift);

return f;

}

float calcMACs(float a,float b,float x){

return a+b*x;

}

void main()

{

    float a0 ,b1 ,b2 ,c12 ;

    unsigned short padc,tadc;

    

    a0 = getFloatByBIN(0x3ECE,3);

    b1 = getFloatByBIN(0xB3F9,13);

    b2 = getFloatByBIN(0xC517,14);

    c12 = getFloatByBIN(0x33C8,2+9+13);

    padc=0x6680>>6;

    tadc=0x7EC0>>6;

    MY_DEBUG("%f,%f,%f,%f,%d,%d\n",a0 ,b1 ,b2 ,c12,padc,tadc);

    MY_DEBUG("%f\n",calcMACs(50, calcMACs( calcMACs( a0,calcMACs(b1,c12,tadc) ,padc) ,b2,tadc) ,(115-50)/1023.0));

}

 

其间发现手册有“印刷错误”(不知道这个典故的可以去找老罗语录),不过经认真比对之后还是不难搞定滴:p

虽然实现了功能,但是效率不怎么样,而且浮点数是会损失精确度的,需要修改!

正在苦B的写定点数转换算法时,发现飞思卡尔有一份应用笔记,里面有详细的算法,简直是定点数计算的极好教材,果断收下:

 

/*********************************************************\

*  Calculate   the   compensated  pressure  PComp  from the  last  set

*  of  ADC  and   coefficient values.

\*********************************************************/

sint16   mpl115a1_CalculatePComp(void)

   uint16  Padc, Tadc;

   sint16  a0,  b1, b2, c12;

   sint16  PComp;

  

   //  extract   adc outputs

   Padc  =   (mpl115a1_regs[0x00] <<   8)  |  mpl115a1_regs[0x01];

   Tadc   =  (mpl115a1_regs[0x02] <<  8)  |  mpl115a1_regs[0x03];

  

   //  extract   coefficients

   a0     =   (mpl115a1_regs[0x04]  <<   8)  |  mpl115a1_regs[0x05];

   b1    =  (mpl115a1_regs[0x06]  <<  8)   |  mpl115a1_regs[0x07];

   b2    =  (mpl115a1_regs[0x08]  <<  8)   |  mpl115a1_regs[0x09];

   c12    =   (mpl115a1_regs[0x0A] <<  8)  |  mpl115a1_regs[0x0B];

   //  calculate   internally compenstated  PComp  value  using either  version

   //PComp  =  calculatePCompLong(Padc, Tadc, a0, b1, b2, c12);

   PComp   =  calculatePCompShort(Padc,  Tadc, a0, b1, b2,  c12);

   return  ( sint16)PComp;

}

/*********************************************************\

*  Calculate   the   pressure  in  1/16  kPa   from  a compensated

*  PComp  value.

\*********************************************************/

uint16  mpl115a1_CalculatePressure(sint16 PComp)

{

   sint32  Pressure;

  

   //  The  final   step  is  to   convert the  internal  PComp  value  into  units of  kPa.

   //     Pressure  =   PComp  ∙  ((115.0  ‐  50.0) /   1023.0)  +  50

   //

   //  The  us e  of   a  fl oating  point  divide  can  be  eliminated using  the   following  approximation:

   //     Pressure  =   ( (  PComp  ∙  1041 ) >> 14 )  +  50

   //

   //  Note  that  in  this  implementation  the  final  pressure  value is  reported   with  a  4   bit  fractional

   //  part. This  may  be eliminated by right  shifting  the  result  four   additional  bits.

  

   Pressure   =  ((((sint32)PComp) *   1041)  >>  14)  +  800;

  

   return  (uint16)Pressure;

}

  

/*********************************************************\

*  Calculate   the   compensated  pressure  PComp  value us ing   th e  detailed  description.

\*********************************************************/

sint16   calculatePCompLong(uint16   Padc,  uint16  Tadc,  sint16  a0,  sint16   b1, sint16  b2, sint16  c12)

{

   //  TEMPORARY  DATA VARIABLES:

   sint32  lt1,   lt2,  lt3;

   sint32  c12x2,   a1, a1x1,  y1, a2x2, PComp;

   //  Pressure   calculation   (long)

   //============================

   //  This   version of  the  pressure  calculation  function  has the  long   description  showing  ex aclty

   //  how  the  bit widths  of   the   coefficients  align through the  calculation.

   //

   //  Variables  used  to  do large  calculation  as  3  temp  variables   in  the  process below

   //    signed   long  (sint32) lt1,   lt2,  lt3;

   //

   //  Variables  used  for Pressure   and   Temperature Raw.

   //    unsigned   short (uint16)  Padc, Ta dc.

   //

   //  In  order  to  optimize  the  fixed point arithmetic,  each  value  is  annotated with  a  descriptor  x(N,F).

   //    x  is   's'   for  signed  or  'u'  for  unsigned

   //    N  is   the   number of  significant  digits in  the  value

   //    F  is   the  number of   fractional bits,  right  of   the   decimal point

   //

   //  Each  of   the   input  values  and   coefficients   are  identified below,  based  upon the  coefficient bit

   //  width table in   the   data  sheet:

   //     Padc    : u(10.0)

   //     Tadc    : u(10,0)

   //     a0      :  s(16,3)

   //     b1     :  s(16,13)

   //     b2     :  s(16,14)

   //     c12     :  s(16,24)  //  s(14,13) +  9   zero  pad   =  s(16,15+9) =>  s(16,24) left   justified

   //     PComp  :  s(16,4)   //  compensated   pressure  value  contains   a 8  bit  integer  part   and   a 4  bit  fractional part

   //

   //  The  compensation  formula is:

   //     PComp  =   a0   +  (b1  +   c12  ∙  Tadc) ∙  Padc  +  b2 ∙  Tadc

  

   //  The  calculation  can  be  broken  down into  individual  steps 

   Padc>>=6;  //Note  that   the   Padc   is  the  raw  value  from  Pegasus  >>6  since its  10  bit  unsigned

   Tadc>>=6;   //Note  that   the  Tadc  is   the  raw  value from Pegasus >>6   since its   10 bit unsigned

   //******* STEP  1  :  c12x2 =   c1 2   *  Ta dc

   lt1  =  c12;            //  s(16,24) //   c12   is  s(14,13)+9 zero  pad  =  s(16,15)+9 =>   s(16,24) left  justified

   lt2  =  (sint16)Tadc;  //  u(10,0)

   lt3  =  lt1 *  lt2;      //  s(26,24) =  c12   *  Tadc

   c12x2  =  lt3 >>  11;   //  s(15,13) ‐  EQ  3  =  c12x2

   //******* STEP  2  :  a1   =  b1 +  c12x2

   lt1  =  (sint16)b1;     //  s(16,13)

   lt2  =  c12x2;            //  s(15,13)

   lt3  =  lt1 +  lt2;      //  s(16,13) =  b1 +   c12x2

   a1    =  lt3;            //  s(16,13) ‐  EQ 4  =   a1

   //******* STEP  3  :  a1x1  =  a1 *   Padc

   lt1  =  a1;            //  s(16,13)

   lt2  =  (sint16)Padc;  //   u( 10,0)

   lt3  =  lt1 *  lt2;      //  s(26,13) =  a1 *   Padc

   a1x1  =  lt3;          //  s(26,13) ‐  EQ  5  =  a1x1

   //******* STEP  4     y1  =   a0   +  a1x1

   lt1  =  ((sint32)a0)  <<  10; //  s(26,13) shifted   to  match a1x1   F value to   add.   So  s(16,3)<<10   =  s(26,13)

   lt2  =  a1x1;         //  s(26,13)

   lt3  =  lt1 +  lt2;      //  s(26,13) =  a0 +   a1x1

   y1    =  lt3;            //  s(26,13) ‐  EQ 6  =   y1

   //******* STEP  5  :  a2x2  =  b2 *   Tadc

   lt1  =  (sint32)b2;     //  s(16,14)

   lt2  =  (sint32)Tadc;  //  u(10,0)

   lt3  =  lt1 *  lt2;      //  s(26,14) =  b2 *   Tadc

   a2x2  =  lt3  >>  1;      //  s(25,13) ‐  EQ 7  =   a2x2

   //*******  STEP  6   : PComp  =   y1  +  a2x2

   lt1  =  y1;            //  s(26,13)

   lt2  =  a2x2;         //  s(25,13)

   lt3  =  lt1 +  lt2;      //  s(26,13) =  y1  +  a2x2

   PComp   =  lt3 >>   9;     //  s(17,4)  ‐  EQ 8  =   PComp  

  

   return  (sint16)PComp; //   By calibration  this  is  less  th an  16 bits

}

/*********************************************************\

*  Calculate   the   compensated  pressure  PComp  value using  the   brief  version

\*********************************************************/

sint16   calculatePCompShort(uint16  Padc, uint16  Tadc, sint16   a0, sint16  b1,  sint16   b2, sint16  c12)

{

   sint32  c12x2,   a1, a1x1,  y1, a2x2, PComp;

   //  Pressure   calculation   (short)

   //=============================

   //  This   version of  the  pressure  calculation  function  has the  sa me  func tion  as  the  long

   //  version  and   gets   exaclty   the  same result,  but is   implemented   more succinctly.

   Padc  >>=  6;  //Note  that  the  Padc  is   the   raw   value from Pegasus >>6   since its  10 bit unsigned

   Tadc   >>= 6;   //Note   that  the   Tadc  is  the   raw   value  from Pegasus  >>6  si nce it s  10  bit unsigned

   c12x2  =  (((sint32)c12)   *  Tadc)  >>  11;   //  c12x2 =  c12   *  Tadc

   a1      =  (sint32)b1  +  c12x2;              //  a1     =   b1   +  c12x2

   a1x1    =   a1  *  Padc;                      //  a1x1    =  a1   *  Padc

   y1      =  (((sint32)a0) <<  10) +   a1x1;    //   y1      =  a0   +  a1x1

   a2x2    =   (((sint32)b2) *   Tadc) >>   1;      //   a2x2    =   b2   *  Tadc

   PComp   =  (y1 +   a2x2)  >>  9;               //  PComp  =   y1    +   a2x2

  

   return  (sint16)PComp;

}

总结起来基本上是以下几点:

1】正负问题交给语言去解决

2】注意小数点对齐

3】及时清理压缩数据的容器,当然,这需要准确掌握计算结果的长度和实际值域,比如[m,n]+[j,k]的长度是[max(m,j),max(n,k)],[m,n]×[j,k]的长度是[m+j,n+k]。

4】把 65/1023 改成成 1041>>14 简直是位于牛A与牛C之间的极品,深受触动,大受启发。

 

搞定算法后开始拿板子调试接口,先做MPL115A2,所以启用I2C——把板上跳线A4,A5跳到23,然后就开始写协议代码了。由于当时coIDE和coocox的库还在升级中,偶尔会出现示例不能用之类的情况,不过在coocox的liam帮助下,还是搞明白了I2C的用法,后来就是调试了。没想到启动转换的命令0x12发送后就飞了,后来发现,在命令后还要跟一个0x00——看手册不仔细害死人啊。

 

启动转换没问题了,可是取气压温度原始数据时又出问题了——虽然调试的时候有时能正常,但一旦开跑,每次取出来的都是0,这个问题直到31日晚上才解决:当时手册已经看到第n次方遍了,快崩溃了,正要关机歇业,下年再说,突然灵机一动,把等待adc的延时加大了,然后就洗具了。这才踏踏实实过了年。

 

2012年就这么过去了,2013年赶紧把驱动传给liam看,结果发现一堆问题,主要是不符合coocox的文档规范。coocox的文档除了在程序文本中要加入doxygen语法外,还对预定义的格式等有严格要求。虽然学习过doxygen,不过有些东西还是做得不到位啊。后来liam提出的问题全部解决,顺手着还发现、处理了几个结构上不够紧凑的毛病。

 

MPL115A2做完之后,该做MPL115A1了,这个是SPI协议,用的也是cox的库来做的,相比做MPL115A1容易了一些,但调底层时还是遇到了一个问题:必须要用xSPISSSet对spi进行使能。当时参考了Walter的方法,不成功,后来参数改用xSPI_SS_SOFTWARE才调通,又发现每次发送前都要重新做一次xSPISSSet才可以,不知是否MPL115A1会自动拉高?总之,MPL115A1也搞定了。

 

都搞定后,liam告诉我,还要上传一套用于介绍组件的主页,这样使用者就可以从主页上大概了解这个组件有什么功能、如何使用。手册翻了那么多遍,这个很好写了。写好后,问了问如何写sample文件、如何上传等等,最后就毫无悬念的上传了,这是上传之后的部分截图: