最近做项目用到了ADS1118,用来做热电偶温度采集,主控为STM32F103。记录一下在编写驱动时遇到的问题和解决方法。
在编写驱动前,要认真阅读数据手册,如果芯片厂商给出了相关解决方案,最好根据其方案进行设计。
《ADS1118数据手册》,《简单的热电偶测量解决方案》,主要依据这两个手册完成硬件电路和驱动程序的编写。
ADS1118使用的SPI总线,因此驱动还是比较好写的。
最开始写的时候,因为没有认真看数据手册,采集到的数据一直不正确,最后仔细看了一下时序图,发现是由于在 CS 引脚拉低后没有延时就开始采集数据造成的。详见下图:
在 CS 引脚拉低后,要最少延时100ns,实测STM32则要延时100us(没有继续测试,也许可以更短)。
STM32的SPI引脚配置时,可以使用8位帧数据格式,也可以使用16位帧数据格式,ADS1118数据为MSB优先,空闲时SCLK保持低电平,下降沿时对数据采样。
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;//SCLK空闲状态低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;//第二个跳变沿采样
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;//CRC值计算的多项式
SPI_Init(SPI1, &SPI_InitStructure);
由于低功耗的要求,ADS1118被配置为单次模式。以下说明均按照单次模式。
数据手册上对数据传输的说明如下:
在单次转换模式和连续转换模式下, ADS1118 均以相同方式写入或读取数据, 无需发出任何命令。 ADS1118 的工作模式通过配置寄存器中的 MODE 位进行选择。
将 MODE 位置为 0 可使器件在连续转换模式下工作。 在连续转换模式下, 器件连续启动新的转换, 即使 CS 处于高电平也是如此。
将 MODE 位置为 1 可启动单次转换模式。在单次转换模式下, 只有向 SS 位写入 1 时才会启动新的转换。
始终对转换数据进行缓冲并在新转换数据替换前始终保留当前数据。 因此, 可随时读取数据, 无需担心数据损坏。当 DOUT/DRDY 置为低电平时, 指示新转换数据已就绪, 可通过移出 DOUT/DRDY 中的数据进行读取。
ADS1118 还可以在同一数据传输周期内直接回读配置寄存器设置。 完整的数据传输周期由 32 位( 使用配置寄存器数据回读) 或 16 位( 仅在 CS 线路受控且永久置为低电平时使用) 组成。
在编写驱动的时候,参考了TI给出的MSP430的驱动程序,采用32位完整传输周期。
32 位数据传输周期中的数据由四个字节组成:两字节用于转换结果,另外两字节用于配置寄存器回读。
我个人认为每个周期中的DATA是上一次的转换结果,因此要获取当前时刻的转换结果需要写入四次数据,头两次写入有效的寄存器配置,启动转换,后两次则随意写入无效数据读取转换结果。
在读取转换结果之前需要判断是否完成了数据转换。这里就有两种方式:
(1)根据DOUT/DRDY引脚是否变为低电平。
(2)根据设定的数据传输速率做合理延时,例如860SPS完成一次转换需要最少1.2ms,因此在头两次寄存器配置写入之后,再延时2ms就可以执行数据读取。
这两种方式的代码如下(注释为方式1):
ADC1_CS_LOW();
delay_us(200);
//第一次写入配置,返回值为上一次采集的数据
SPI_ReadWrite16Bit(SPI1,data);
//第二次写入配置,返回值为寄存器回读值
tmp = SPI_ReadWrite16Bit(SPI1,data);
delay_ms(2);
//ADS1118_SPI_MISO_PIN引脚低电平指示新数据可用
//GPIO_ResetBits(ADS1118_SPI_GPIO_PORT, ADS1118_SPI_MISO_PIN);
//GPIO_InitStructure.GPIO_Pin = ADS1118_SPI_MISO_PIN;
//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
//GPIO_Init(ADS1118_SPI_GPIO_PORT, &GPIO_InitStructure);
//while(GPIO_ReadInputDataBit(ADS1118_SPI_GPIO_PORT, ADS1118_SPI_MISO_PIN) != 0) ;
//GPIO_InitStructure.GPIO_Pin = ADS1118_SPI_MISO_PIN;
//GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
//GPIO_Init(ADS1118_SPI_GPIO_PORT, &GPIO_InitStructure);
//GPIO_SetBits(ADS1118_SPI_GPIO_PORT, ADS1118_SPI_MISO_PIN);
//读取转换数据
value = SPI_ReadWrite16Bit(SPI1,0x00FF);
tmp = SPI_ReadWrite16Bit(SPI1,0x00FF);
delay_us(200);
ADC1_CS_HIGH();
ADS1118内置14位的高精度温度传感器,可直接读取热电偶冷端温度,便于进行冷端温度补偿。对于温度的数据格式,数据手册有如下说明:
温度数据以 14 位结果呈现, 与 16 位转换结果左对齐。 数据从最高有效字节 (MSB) 开始输出。 当读取这两个数据字节, 前 14 位用来指定温度测量结果。 一个 14 位 LSB 等于 0.03125°C。 负数以二进制补码形式表示.
要将数字代码转换为温度,首先需要检查 MSB 是0还是1。如果 MSB 为0,将十进制代码乘以 0.03125°C即可获得结果。如果 MSB = 1, 则将结果减“1”后对各位取补码。之后将结果乘以 –0.03125°C。
刚开始由于没有仔细看采用的是左对齐格式,对数据的处理就出了问题。最后实在TI的论坛上看到的才又仔细看了一下数据手册。
也就是说,在采集回温度数据之后:
(1)MSB为0,把数据右移两位,乘以 0.03125°C。
(2)MSB为1,数据减1后取反,再右移两位,乘以 -0.03125°C。
至此,对于ADS1118驱动编写的注意事项就说完了,总的来说都不是什么大问题,但是需要详细阅读数据手册,所以在编写驱动前一定要熟读数据手册,特别是对时序和数据格式的说明,一定要弄明白。
这里的数据处理就比较简单了,按照《简单的热电偶测量解决方案》 给出的软件方案来写就可以了。
首先,读取热端的电压值,然后在读取冷端的温度,这里测得的片载温度转换为相应的所使用的热电偶类型的电压。我们使用的是K型热电偶,因此就按照厂家提供的K型热电偶分度表进行转换。才用了32点插值,通过手册给出的公式进行温度电压之间的转换。
/*温度(度)*/
const u16 TempList[2][33] = /*电压(uV)*/
{//K型热电偶分度表
{0,5, 10, 15, 20, 25, 30, 35, 40, 55, 70, 85, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 220, 240, 260, 280, 300, 400, 500, 600, 700, 800},
{0,198,397,597,789,1000,1203,1407,1611,2229,2850,3473,4095,4508,4919,5327,5733,6137,6539,6939,7338,7737,8137,8938,9745,10560,11381,12207,16395,20640,24902,29128,33277}
};
热电偶温度计算的代码如下:
//获取热端电压uV
temp->HotVoltage = ADS1118_WriteReadData(0,START_ADC1_DP_CH0) * 7.8125;
//热电偶最大量程1375度,54875uV
if(temp->HotVoltage >= TCMax) return 0xFFFF;
//ADC温度传感器采集值转换为度
tmp = (ADS1118_WriteReadData(0,START_ADC1_TS)) >> 2;
//ADC温度1LSB为0.03125度
temp->ColdTemp = tmp * 0.03125;
//获取冷端电压(芯片温度转为分度表中的电压)
temp->TInList = FindList(TinList,temp->ColdTemp);
temp->ColdVoltage = (TempList[1][temp->VInList-1]+(TempList[1][temp->VInList]-TempList[1][temp->VInList-1])*((temp->ColdTemp-TempList[0][temp->VInList-1])/(TempList[0][temp->VInList]-TempList[0][temp->VInList-1])));
//热电偶冷端温度补偿
voltage = temp->HotVoltage + temp->ColdVoltage;
//转换为热端实际温度
temp->VInList = FindList(VinList,voltage);
temp->Temperature =(TempList[0][temp->VInList-1]+(TempList[0][temp->VInList]-TempList[0][temp->VInList-1])*((voltage-TempList[1][temp->VInList-1])/(TempList[1][temp->VInList]-TempList[1][temp->VInList-1])));
温度测量范围为0~800度,所以就不考虑零下温度的情况。
经过实际测试,精度大概为 ± 2°C,跟TI的方案还差好多,目前仍在优化中。