文章目录[隐藏]
前言
今年的芯片慌已经迫使我换了三四个厂家的MCU,每次使用新的MCU总是会碰到各种奇怪的坑或者难点,故写个简单的笔记记录一下。
温度采样的ADC配置
温度采样的ADC配置大体上和普通IO口的配置是一样的,但是需要注意以下几个点:
- 温度采样的采样时间需要大于40us,如果没有特殊要求,请尽量的大于40us。在使用过程中,发现过采样时间为60us时,温度采样有概率失败的问题。后我进一步提高了采样时间到88us,问题不再复现;
- 检测ADC配置中TSEN寄存器是否开启;
- 在使能ADC之前,关闭VREFOE,开启TEMPSENSE,然后再使能ADC,最后再读取温度。
- 下图为我使用的ADC配置。
配置代码如下:
void ADC_Initialize( void )
{
/* Reset ADC */
ADC_REGS->ADC_CTRLA = (uint8_t)ADC_CTRLA_SWRST_Msk;
while((ADC_REGS->ADC_SYNCBUSY & ADC_SYNCBUSY_SWRST_Msk) == ADC_SYNCBUSY_SWRST_Msk)
{
/* Wait for Synchronization */
}
/* Write linearity calibration in BIASREFBUF and bias calibration in BIASCOMP */
uint32_t calib_low_word = (uint32_t)(*(uint64_t*)OTP5_ADDR);
ADC_REGS->ADC_CALIB = (uint16_t)((ADC_CALIB_BIASREFBUF((calib_low_word & ADC_LINEARITY_Msk) >> ADC_LINEARITY_POS)) |
(ADC_CALIB_BIASCOMP((calib_low_word & ADC_BIASCAL_Msk) >> ADC_BIASCAL_POS)));
/* Prescaler */
ADC_REGS->ADC_CTRLB = (uint8_t)ADC_CTRLB_PRESCALER_DIV2;
/* Sampling length */
ADC_REGS->ADC_SAMPCTRL = (uint8_t)ADC_SAMPCTRL_SAMPLEN(31UL);
/* Reference */
ADC_REGS->ADC_REFCTRL = (uint8_t)ADC_REFCTRL_REFSEL_INTVCC2;
/* Input pin */
ADC_REGS->ADC_INPUTCTRL = (uint16_t) ADC_POSINPUT_TEMP;
/* Resolution & Operation Mode */
ADC_REGS->ADC_CTRLC = (uint16_t)(ADC_CTRLC_RESSEL_12BIT | ADC_CTRLC_WINMODE(0UL) );
/* Clear all interrupt flags */
ADC_REGS->ADC_INTFLAG = (uint8_t)ADC_INTFLAG_Msk;
while(0U != ADC_REGS->ADC_SYNCBUSY)
{
/* Wait for Synchronization */
}
}
温度采集的公式换算
一般的MCU采用的内部温度换算公式比较简单,只需采集到AD值,经过简单的计算即可得到温度值,但是这款SAM L21为了确保温度的精准性,在出厂时为每个芯片写入了不同的基准电压。这也导致了温度换算的公式变得比一般的MCU更为复杂,这也是这个转换的麻烦点所在。我问过原厂的技术支持,官方好像并没有标准的温度例程,所以这得需要自己来完成。
以下是使用的代码,我简单讲解以下。厂家在出厂前,会往“TEMP_LOG_ADDR“寄存器写入几个初始值分别为室温温度值,高温温度值,这两个温度值对应的AD值,以及温度对应的参考电压的温漂值。知道这几个值后,我们就可以通过以下的这个公式变换得到当前的温度值。
VADC :温度AD采样出来的电压
temp :当前的温度
VADCR:室温对应的电压
tempR:室温
VADCH:高温对应的电压
tempH:高温
其中6个参数,后4个都是出厂时写好的固定值,VADC也是采集出来的变量了。通过以上这些值,可以很容易的获得当前温度。但是,这都是在不考虑参考电压的温漂的情况下的。如果你的实际应用不需要太精准的温度,那么通过以上的这个公式变换,就可以得到粗略的温度值。也足够满足日常使用了。但是如果你想要获得更精准的温度,那么就要通过以下的公式,进行进一步的换算。
公式1,是一个标准的AD值转换成电压的转换公式,将这个公式带入到最上方的温度计算公式,就可以得到公式2。
合并同类项,得到公式3
通过公式3的计算,可以得到一个粗略的温度值,这时,还没有考虑温度对参考电压的影响。
PS:公式4中INT1VVR应为INT1VR
通过温度的线性关系,可以得到公式四,再通过公式4以及粗略温度tempc,可以计算出精确的基准电压,也就是公式5。
最后,将精确的基准电压INT1Vm,替换掉公式3中的INT1Vc。即可得到公式6,从而也计算出了精准的温度。
下列是实际应用的代码。
#ifndef TEMPERATURE_H
#define TEMPERATURE_H
#include <stdint.h>
#define TEMP_LOG_ADDR _UL_(0x00806030) /**< TEMP_LOG base address (type: fuses)*/
#define NVM_TEMPERATURE_LOG_ROW_BASE (*(uint32_t*)TEMP_LOG_ADDR)
#define NVM_TEMPERATURE_LOG_ROW_HIG_BASE (*(uint32_t*)(TEMP_LOG_ADDR + 4))
// 室温温度值
#define ROOM_TEMP_VAL_INT (NVM_TEMPERATURE_LOG_ROW_BASE & 0xFF)
#define ROOM_TEMP_VAL_DEC ((NVM_TEMPERATURE_LOG_ROW_BASE>>8) & 0x0F)
// 高温温度值
#define HOT_TEMP_VAL_INT ((NVM_TEMPERATURE_LOG_ROW_BASE>>12) & 0xFF)
#define HOT_TEMP_VAL_DEC ((NVM_TEMPERATURE_LOG_ROW_BASE>>20) & 0x0F)
// 相应温度下,参考电压的浮动值
#define ROOM_INT1V_VAL ((NVM_TEMPERATURE_LOG_ROW_BASE>>24) & 0xFF)
#define HOT_INT1V_VAL ((NVM_TEMPERATURE_LOG_ROW_HIG_BASE) & 0xFF)
// 相应温度的AD值
#define ROOM_ADC_VAL ((NVM_TEMPERATURE_LOG_ROW_HIG_BASE>>8) & 0xFFF)
#define HOT_ADC_VAL ((NVM_TEMPERATURE_LOG_ROW_HIG_BASE>>20) & 0xFFF)
室温温度值
#define ROOM_TEMP_VAL (ROOM_TEMP_VAL_INT + ROOM_TEMP_VAL_DEC*0.1)
// 高温温度值
#define HOT_TEMP_VAL (HOT_TEMP_VAL_INT + HOT_TEMP_VAL_DEC*0.1)
extern void Device_Temp_Init(void);
extern int8_t Get_Device_Temp(void);
#endif /* GRID_H */
#include "definitions.h" // SYS function prototypes
#include "temperature.h"
#include "sapmle_pow.h"
static float f_int1_h; // 基准电压
static float f_int1_r;
static float f_temp_h; // 高温
static float f_temp_r; // 室温
static uint16_t u16_adc_h; // 校准过的ADC值
static uint16_t u16_adc_r; // 校准过的ADC值
void Device_Temp_Init(void) {
// 基准电压换算
f_int1_h = ((int8_t) HOT_TEMP_VAL_INT) * 0.001 + 1;
f_int1_r = ((int8_t) ROOM_INT1V_VAL) * 0.001 + 1;
// 温度换算
f_temp_h = HOT_TEMP_VAL_INT + HOT_TEMP_VAL_DEC * 0.1;
f_temp_r = ROOM_TEMP_VAL_INT + ROOM_TEMP_VAL_DEC * 0.1;
// 校准ADC值
u16_adc_h = HOT_ADC_VAL * f_int1_h + 0.5;
u16_adc_r = ROOM_ADC_VAL * f_int1_r + 0.5;
}
int8_t Get_Device_Temp(void) {
float f_temp_c; // 粗略温度值
float f_temp_f; // 实际温度值
float f_int1_m; // 基准电压
uint16_t u16_adc_m;
u16_adc_m = Get_ADC_Val(ADC_TEMP_CH) * 3.3; // 基准使用 3.3 V 所以放大3.3倍
f_temp_c = f_temp_r + ((u16_adc_m - u16_adc_r)*(f_temp_h - f_temp_r) / (u16_adc_h - u16_adc_r)); // 温度粗值计算
f_int1_m = f_int1_r + ((f_int1_h - f_int1_r)*(f_temp_c - f_temp_r) / (f_temp_h - f_temp_r)); // 基准电压计算
u16_adc_m = u16_adc_m * f_int1_m + 0.5;
f_temp_f = f_temp_r + ((u16_adc_m - u16_adc_r)*(f_temp_h - f_temp_r) / (u16_adc_h - u16_adc_r));
return (int8_t) f_temp_f;
}
尾
这次是我第一次在CSDN上发表文章。之前一直没有记录的习惯,这也算是我迈出舒适圈的一步吧。水平有限,如有错漏之处,敬请指正。谢谢。
版权声明:本文为CSDN博主「MALPLBYA」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42341717/article/details/122070550
前言
今年的芯片慌已经迫使我换了三四个厂家的MCU,每次使用新的MCU总是会碰到各种奇怪的坑或者难点,故写个简单的笔记记录一下。
温度采样的ADC配置
温度采样的ADC配置大体上和普通IO口的配置是一样的,但是需要注意以下几个点:
- 温度采样的采样时间需要大于40us,如果没有特殊要求,请尽量的大于40us。在使用过程中,发现过采样时间为60us时,温度采样有概率失败的问题。后我进一步提高了采样时间到88us,问题不再复现;
- 检测ADC配置中TSEN寄存器是否开启;
- 在使能ADC之前,关闭VREFOE,开启TEMPSENSE,然后再使能ADC,最后再读取温度。
- 下图为我使用的ADC配置。
配置代码如下:
void ADC_Initialize( void )
{
/* Reset ADC */
ADC_REGS->ADC_CTRLA = (uint8_t)ADC_CTRLA_SWRST_Msk;
while((ADC_REGS->ADC_SYNCBUSY & ADC_SYNCBUSY_SWRST_Msk) == ADC_SYNCBUSY_SWRST_Msk)
{
/* Wait for Synchronization */
}
/* Write linearity calibration in BIASREFBUF and bias calibration in BIASCOMP */
uint32_t calib_low_word = (uint32_t)(*(uint64_t*)OTP5_ADDR);
ADC_REGS->ADC_CALIB = (uint16_t)((ADC_CALIB_BIASREFBUF((calib_low_word & ADC_LINEARITY_Msk) >> ADC_LINEARITY_POS)) |
(ADC_CALIB_BIASCOMP((calib_low_word & ADC_BIASCAL_Msk) >> ADC_BIASCAL_POS)));
/* Prescaler */
ADC_REGS->ADC_CTRLB = (uint8_t)ADC_CTRLB_PRESCALER_DIV2;
/* Sampling length */
ADC_REGS->ADC_SAMPCTRL = (uint8_t)ADC_SAMPCTRL_SAMPLEN(31UL);
/* Reference */
ADC_REGS->ADC_REFCTRL = (uint8_t)ADC_REFCTRL_REFSEL_INTVCC2;
/* Input pin */
ADC_REGS->ADC_INPUTCTRL = (uint16_t) ADC_POSINPUT_TEMP;
/* Resolution & Operation Mode */
ADC_REGS->ADC_CTRLC = (uint16_t)(ADC_CTRLC_RESSEL_12BIT | ADC_CTRLC_WINMODE(0UL) );
/* Clear all interrupt flags */
ADC_REGS->ADC_INTFLAG = (uint8_t)ADC_INTFLAG_Msk;
while(0U != ADC_REGS->ADC_SYNCBUSY)
{
/* Wait for Synchronization */
}
}
温度采集的公式换算
一般的MCU采用的内部温度换算公式比较简单,只需采集到AD值,经过简单的计算即可得到温度值,但是这款SAM L21为了确保温度的精准性,在出厂时为每个芯片写入了不同的基准电压。这也导致了温度换算的公式变得比一般的MCU更为复杂,这也是这个转换的麻烦点所在。我问过原厂的技术支持,官方好像并没有标准的温度例程,所以这得需要自己来完成。
以下是使用的代码,我简单讲解以下。厂家在出厂前,会往“TEMP_LOG_ADDR“寄存器写入几个初始值分别为室温温度值,高温温度值,这两个温度值对应的AD值,以及温度对应的参考电压的温漂值。知道这几个值后,我们就可以通过以下的这个公式变换得到当前的温度值。
VADC :温度AD采样出来的电压
temp :当前的温度
VADCR:室温对应的电压
tempR:室温
VADCH:高温对应的电压
tempH:高温
其中6个参数,后4个都是出厂时写好的固定值,VADC也是采集出来的变量了。通过以上这些值,可以很容易的获得当前温度。但是,这都是在不考虑参考电压的温漂的情况下的。如果你的实际应用不需要太精准的温度,那么通过以上的这个公式变换,就可以得到粗略的温度值。也足够满足日常使用了。但是如果你想要获得更精准的温度,那么就要通过以下的公式,进行进一步的换算。
公式1,是一个标准的AD值转换成电压的转换公式,将这个公式带入到最上方的温度计算公式,就可以得到公式2。
合并同类项,得到公式3
通过公式3的计算,可以得到一个粗略的温度值,这时,还没有考虑温度对参考电压的影响。
PS:公式4中INT1VVR应为INT1VR
通过温度的线性关系,可以得到公式四,再通过公式4以及粗略温度tempc,可以计算出精确的基准电压,也就是公式5。
最后,将精确的基准电压INT1Vm,替换掉公式3中的INT1Vc。即可得到公式6,从而也计算出了精准的温度。
下列是实际应用的代码。
#ifndef TEMPERATURE_H
#define TEMPERATURE_H
#include <stdint.h>
#define TEMP_LOG_ADDR _UL_(0x00806030) /**< TEMP_LOG base address (type: fuses)*/
#define NVM_TEMPERATURE_LOG_ROW_BASE (*(uint32_t*)TEMP_LOG_ADDR)
#define NVM_TEMPERATURE_LOG_ROW_HIG_BASE (*(uint32_t*)(TEMP_LOG_ADDR + 4))
// 室温温度值
#define ROOM_TEMP_VAL_INT (NVM_TEMPERATURE_LOG_ROW_BASE & 0xFF)
#define ROOM_TEMP_VAL_DEC ((NVM_TEMPERATURE_LOG_ROW_BASE>>8) & 0x0F)
// 高温温度值
#define HOT_TEMP_VAL_INT ((NVM_TEMPERATURE_LOG_ROW_BASE>>12) & 0xFF)
#define HOT_TEMP_VAL_DEC ((NVM_TEMPERATURE_LOG_ROW_BASE>>20) & 0x0F)
// 相应温度下,参考电压的浮动值
#define ROOM_INT1V_VAL ((NVM_TEMPERATURE_LOG_ROW_BASE>>24) & 0xFF)
#define HOT_INT1V_VAL ((NVM_TEMPERATURE_LOG_ROW_HIG_BASE) & 0xFF)
// 相应温度的AD值
#define ROOM_ADC_VAL ((NVM_TEMPERATURE_LOG_ROW_HIG_BASE>>8) & 0xFFF)
#define HOT_ADC_VAL ((NVM_TEMPERATURE_LOG_ROW_HIG_BASE>>20) & 0xFFF)
室温温度值
#define ROOM_TEMP_VAL (ROOM_TEMP_VAL_INT + ROOM_TEMP_VAL_DEC*0.1)
// 高温温度值
#define HOT_TEMP_VAL (HOT_TEMP_VAL_INT + HOT_TEMP_VAL_DEC*0.1)
extern void Device_Temp_Init(void);
extern int8_t Get_Device_Temp(void);
#endif /* GRID_H */
#include "definitions.h" // SYS function prototypes
#include "temperature.h"
#include "sapmle_pow.h"
static float f_int1_h; // 基准电压
static float f_int1_r;
static float f_temp_h; // 高温
static float f_temp_r; // 室温
static uint16_t u16_adc_h; // 校准过的ADC值
static uint16_t u16_adc_r; // 校准过的ADC值
void Device_Temp_Init(void) {
// 基准电压换算
f_int1_h = ((int8_t) HOT_TEMP_VAL_INT) * 0.001 + 1;
f_int1_r = ((int8_t) ROOM_INT1V_VAL) * 0.001 + 1;
// 温度换算
f_temp_h = HOT_TEMP_VAL_INT + HOT_TEMP_VAL_DEC * 0.1;
f_temp_r = ROOM_TEMP_VAL_INT + ROOM_TEMP_VAL_DEC * 0.1;
// 校准ADC值
u16_adc_h = HOT_ADC_VAL * f_int1_h + 0.5;
u16_adc_r = ROOM_ADC_VAL * f_int1_r + 0.5;
}
int8_t Get_Device_Temp(void) {
float f_temp_c; // 粗略温度值
float f_temp_f; // 实际温度值
float f_int1_m; // 基准电压
uint16_t u16_adc_m;
u16_adc_m = Get_ADC_Val(ADC_TEMP_CH) * 3.3; // 基准使用 3.3 V 所以放大3.3倍
f_temp_c = f_temp_r + ((u16_adc_m - u16_adc_r)*(f_temp_h - f_temp_r) / (u16_adc_h - u16_adc_r)); // 温度粗值计算
f_int1_m = f_int1_r + ((f_int1_h - f_int1_r)*(f_temp_c - f_temp_r) / (f_temp_h - f_temp_r)); // 基准电压计算
u16_adc_m = u16_adc_m * f_int1_m + 0.5;
f_temp_f = f_temp_r + ((u16_adc_m - u16_adc_r)*(f_temp_h - f_temp_r) / (u16_adc_h - u16_adc_r));
return (int8_t) f_temp_f;
}
尾
这次是我第一次在CSDN上发表文章。之前一直没有记录的习惯,这也算是我迈出舒适圈的一步吧。水平有限,如有错漏之处,敬请指正。谢谢。
版权声明:本文为CSDN博主「MALPLBYA」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_42341717/article/details/122070550
暂无评论