MicroChip SAM L21 内部温度传感器

MicroChip SAM L21 内部温度传感器的应用

前言

今年的芯片慌已经迫使我换了三四个厂家的MCU,每次使用新的MCU总是会碰到各种奇怪的坑或者难点,故写个简单的笔记记录一下。

温度采样的ADC配置

ADC配置

温度采样的ADC配置大体上和普通IO口的配置是一样的,但是需要注意以下几个点:

  1. 温度采样的采样时间需要大于40us,如果没有特殊要求,请尽量的大于40us。在使用过程中,发现过采样时间为60us时,温度采样有概率失败的问题。后我进一步提高了采样时间到88us,问题不再复现;
  2. 检测ADC配置中TSEN寄存器是否开启;
  3. 在使能ADC之前,关闭VREFOE,开启TEMPSENSE,然后再使能ADC,最后再读取温度。
  4. 下图为我使用的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
公式1,是一个标准的AD值转换成电压的转换公式,将这个公式带入到最上方的温度计算公式,就可以得到公式2。
公式2
合并同类项,得到公式3
公式3
通过公式3的计算,可以得到一个粗略的温度值,这时,还没有考虑温度对参考电压的影响。公式4
PS:公式4中INT1VVR应为INT1VR
通过温度的线性关系,可以得到公式四,再通过公式4以及粗略温度tempc,可以计算出精确的基准电压,也就是公式5。
公式5
最后,将精确的基准电压INT1Vm,替换掉公式3中的INT1Vc。即可得到公式6,从而也计算出了精准的温度。
公式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上发表文章。之前一直没有记录的习惯,这也算是我迈出舒适圈的一步吧。水平有限,如有错漏之处,敬请指正。谢谢。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值