话说小时候总盼着放假,一年只有寒假暑假可放,而其中寒假因为带着春节,尤其热闹,更是盼望。小学班主任知道孩子贪玩,到了年底就更没心思好好上课了,提醒我们:“同学们,要是这几天不好好上课,那就是‘一年到头调皮捣蛋’了啊,注意啊!”所以后来我养成个习惯,就是每年末的最后一天也要学点、做点什么,这样可以跟人吹嘘“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,2);
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文件、如何上传等等,最后就毫无悬念的上传了,这是上传之后的部分截图: