#关于STM32F051C8T6项目总结(三)

关于STM32F051C8T6某项目总述

1. 总述目录

  1. 模块总述

    1. TM1640
    2. R25_F3950
    3. VS1838B
  2. 硬件总述

    1. 差分运放
    2. 电流稳定
    3. 温控开关
  3. R25_F3950

    1. 功能说明

      ​ R25F3950是我对指标定温度为25℃,B值为3950阻值为10K的NTC热敏电阻的称呼。热敏电阻是阻值由材料表面温度变化而变化的电阻元器件,当在某特定电路测量其两边的电压可根据两边的电压计算出阻值的大小。目的是通过温度的变化而引起一系列电压变化,从而产生某些效果。在项目中是为了获得环境温度。

    2. 思路解析

      ​ 在STM32F051C8T6芯片中可通过ADC输入获得引脚电压从而得到电阻两端的电压,来测出其温度值。也就是说,NTC的使用即是:

      ​ NTC热敏电阻+特定电路+ADC输入测量+阻值电压计算=当前环境温度

      ​ 首先是NTC热敏电阻参数:

      ​ 标定温度:25℃

      ​ 标定温度下阻值:10k

      ​ B值:3950

      在这里插入图片描述

      注:以上图片来自知乎

      ​ 然后是特定电路:

      ​ 在以上电路中,RT1的环境温度的变化会引发其本身阻值的变化,所带来的影响是RT1两端电压的变化。电容C1是滤波的作用,用来稳定电压变化的,防止幅度变化过大。R1的作用是分压用来保护电路的,那么电阻的两端的电压就是ADC采样的电压。

      ​ 接着是ADC输入测量:

      ​ ADC含义是模拟量转换数字量。模拟量在坐标轴上显示就是一个可以连续变化的曲线,数字量在坐标轴上显示是散点图,在电路中的数字量只有0和1。当使用ADC时即使用芯片的ADC模块对应引脚获取其连续变化的电压值,将其连续变化的电压值在某时刻取出(采样)一份得到一个电压,而当取的值足够多(分辨率足够大)时,其ADC值再经过DAC就能转换回相同的模拟量。当然所谓相同只是理想状态,可以把它理解成微分和积分的过程。所以我们就需要配置一下采样分辨率,关于采样分辨率就是可以理解成函数曲线的微分单位量的大小。

      ​ 关于阻值电压计算有两种方案,一种是查表法,每个NTC热敏电阻都有一个阻值和温度的对应表,去查那个表再根据电路计算出某温度对应电压,用一个数组储存各个温度的电压这样就能得出对应温度。另外一种是公式法:具体公式:可以自行查阅热敏电阻B值计算公式,反推出温度公式即可。关于何时用查表法何时用公式法,我是这样思考的:

      1. 如果你的MCU对小数的运算效率较高,且你需要对温度测量达到小数后两位,并且测量温度的上下限(例如:-20.00℃到50.00℃)很大,那么可以采用公式法。
      2. 如果你的MCU对小数的运算效率不够,且你只需要整数部分的温度,且上下限很小(20℃到50℃),那么可以采用公式法。这样做效率会高很多,直接随机存取数。

      ​ 本项目采用的是查表法。

    3. 代码解析

      ​ 首先是对ADC的配置。

      
      void GPIO_Configuration(void)
      {
          GPIO_InitTypeDef GPIO_CFG;
          RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE); // 启用GPIOA的时钟
      
      
          GPIO_CFG.GPIO_Pin  = R25F3950INS1_PIN;
          GPIO_CFG.GPIO_Mode = GPIO_Mode_AN;
          GPIO_CFG.GPIO_PuPd = GPIO_PuPd_NOPULL;
          GPIO_Init(GPIOA, &GPIO_CFG);
      
          GPIO_CFG.GPIO_Pin  = R25F3950INS2_PIN;
          GPIO_CFG.GPIO_Mode = GPIO_Mode_AN;
          GPIO_CFG.GPIO_PuPd = GPIO_PuPd_NOPULL;
          GPIO_Init(GPIOA, &GPIO_CFG);
      }
      
      void ADC_Configuration(void)
      {
          ADC_InitTypeDef ADC_CFG;
          RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // 使能ADC1的时钟
      
          // R25F3950Ins1 ADC_channel1 Init
          ADC_StructInit(&ADC_CFG);
          ADC_DeInit(ADC1);
      
          ADC_CFG.ADC_ContinuousConvMode   = DISABLE;                       // 连续模式禁止 要由定时器触发
          ADC_CFG.ADC_Resolution           = ADC_Resolution_12b;            // ADC分辨率为12位
          ADC_CFG.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; // 不触发
          ADC_CFG.ADC_ExternalTrigConv     = ADC_ExternalTrigConv_T1_TRGO;
          ADC_CFG.ADC_DataAlign            = ADC_DataAlign_Right;      // 数据右对齐
          ADC_CFG.ADC_ScanDirection        = ADC_ScanDirection_Upward; // 扫描方向为CH0-18
      
          ADC_Init(ADC1, &ADC_CFG); // 初始化ADC
      
          ADC_GetCalibrationFactor(ADC1);
          ADC_VrefintCmd(ENABLE);
      
          ADC_Cmd(ADC1, ENABLE); // 启动ADC
      }
      
      

      其次是开始ADC采集任务。

      //在主循环里循环调用
      void R25_Trans_Task(void)
      {
          uint16_t Value = 0;
          if (ADC_Index == INTRA_TEMP) {
      
              ADC_ChannelConfig(ADC1, ADC_Channel_2, ADC_SampleTime_239_5Cycles);
              ADC1->CHSELR = ADC_Channel_2;
              while (ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY) == RESET)
                  ;
      
              ADC_StartOfConversion(ADC1);
      
              while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET)
                  ;
      
              ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
              Value = ADC_GetConversionValue(ADC1);
              Temp_Cal(Value, INTRA_TEMP);
              ADC_StopOfConversion(ADC1);
              ADC_Index = BELLY_TEMP;
          } else {
              ADC_ChannelConfig(ADC1, ADC_Channel_0, ADC_SampleTime_239_5Cycles);
      
              ADC1->CHSELR = ADC_Channel_0;
              while (ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY) == RESET)
                  ;
      
              ADC_StartOfConversion(ADC1);
      
              while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET)
                  ;
              ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
              Value = ADC_GetConversionValue(ADC1);
              Temp_Cal(Value, BELLY_TEMP);
              ADC_StopOfConversion(ADC1);
              ADC_Index = INTRA_TEMP;
          }
      
          if (StateValue.INTRA_TEMP_STATE.isNeedUpdate == true && StateValue.INTRA_TEMP_STATE.isExcute == false) {
              TM1640_Display(StateValue.INTRA_TEMP_STATE.Show_Addr_Decade, Number_Array[(StateValue.INTRA_TEMP_STATE.value / 10)]);
      
              TM1640_Display(StateValue.INTRA_TEMP_STATE.Show_Addr_Unit, Number_Array[(StateValue.INTRA_TEMP_STATE.value % 10)]);
              StateValue.INTRA_TEMP_STATE.isNeedUpdate = false;
              StateValue.INTRA_TEMP_STATE.isExcute     = true;
          }
      
          if (StateValue.BELLY_TEMP_STATE.isNeedUpdate == true && StateValue.BELLY_TEMP_STATE.isExcute == false) {
              TM1640_Display(StateValue.BELLY_TEMP_STATE.Show_Addr_Decade, Number_Array[(StateValue.BELLY_TEMP_STATE.value / 10)]);
      
              TM1640_Display(StateValue.BELLY_TEMP_STATE.Show_Addr_Unit, Number_Array[(StateValue.BELLY_TEMP_STATE.value % 10)]);
              StateValue.BELLY_TEMP_STATE.isNeedUpdate = false;
              StateValue.BELLY_TEMP_STATE.isExcute     = true;
          }
          return;
      }
      

      ​ 这里有一个坑,ST芯片的ADC要求在使用ADC_GetConversionValue(ADC1);函数进行得到值之前要保证各个寄存器的值以及各个寄存器标志位都是正确和预期的值,才会进行ADC转换,否则ADC没有值。首先,ADC_ChannelConfig(ADC1, ADC_Channel_2, ADC_SampleTime_239_5Cycles);函数目的是ADC要转换的同时是通道2,采样时间是239.5个周期,即ADC1将从通道2输入信号,并使用239.5个ADC时钟周期进行采样。ADC1->CHSELR = ADC_Channel_2;也是设置采用通道,因为有的库函数版本有BUG导致通道设置失效。
      ADC_GetFlagStatus(ADC1, ADC_FLAG_ADRDY) == RESET表示芯片模数转换器已经准备好转换了。ADC_StartOfConversion(ADC1);开始对模拟量转换。ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET表示MCU模数转换已经完成。 ADC_ClearFlag(ADC1, ADC_FLAG_EOC);清除转换完成标志位以开始下一轮的转换。ADC_GetConversionValue(ADC1)得到最终采样结果。

      ​ 接下来是温度计算:

      // 温度转换
      // 参数:ADC转换值
      
      uint32_t Temp_Cal(uint16_t adc, uint8_t ins)
      {
          uint8_t i;
      		static uint8_t belly_count = 0;
      		static uint8_t intra_count = 0;
            uint32_t temp = 0;
      		static uint32_t belly_temps[50]={0};
      		static uint32_t intra_temps[50]={0};
      
          if (adc > adc_table[0])
      		{
      		if (ins == INTRA_TEMP) {
      			tPostEvent(&SystemEvent.ErrorEvent,SystemError.INTRA_SENSOR);
      			TM1640_Intra_Error_ON(); 	
      		}
      		else if (ins == BELLY_TEMP) 
      		{
      			tPostEvent(&SystemEvent.ErrorEvent,SystemError.BELLY_SENSOR);
      			TM1640_Belly_Error_ON();   		
      		}
      			return 0;
      		}
          else if (adc < adc_table[190])
              return 32767;
          else {
              for (i = 0; i < sizeof(adc_table)/sizeof(uint16_t)-1; i++) {
                  if (adc >= adc_table[i]) {
      							if(temp_table[i]>0){
                      temp = (uint32_t)temp_table[i];
      								if(ApplicationState.Application_WorkTime != 0){
      									if (ins == INTRA_TEMP) {
      										intra_count++;
      										intra_temps[intra_count] = temp;
      										if(intra_count>=50){		   														
      										temp = equal_t(temp,intra_temps);
      										intra_count = 0;
      	//									USART_Std_Printf("temp:%d\n",temp);     												
      										StateValue.INTRA_TEMP_STATE.isNeedUpdate = true;
      										StateValue.INTRA_TEMP_STATE.isExcute     = false;
      										StateValue.INTRA_TEMP_STATE.value        = temp - 6;
      										// 每次读取后要把该标志位重新设置为RESET
      										}								
      									} 
      									else if (ins == BELLY_TEMP) 
      										{
      										belly_count++;
      										belly_temps[belly_count] = temp;
      										if(belly_count>=50){
      											temp = equal_t(temp,belly_temps);
      	//										USART_Std_Printf("temp:%d\n",temp);
      											belly_count = 0;      												
      											StateValue.BELLY_TEMP_STATE.isNeedUpdate = true;
      											StateValue.BELLY_TEMP_STATE.isExcute     = false;
      											StateValue.BELLY_TEMP_STATE.value        = temp;
      											}	
      										}
      								}else {   								
      									if (ins == INTRA_TEMP) {    													
      										StateValue.INTRA_TEMP_STATE.isNeedUpdate = true;
      										StateValue.INTRA_TEMP_STATE.isExcute     = false;
      										StateValue.INTRA_TEMP_STATE.value        = temp - 6;																											
      										// 每次读取后要把该标志位重新设置为RESET
      										} 
      									else if (ins == BELLY_TEMP) 
      										{    												
      											StateValue.BELLY_TEMP_STATE.isNeedUpdate = true;
      											StateValue.BELLY_TEMP_STATE.isExcute     = false;
      											StateValue.BELLY_TEMP_STATE.value        = temp;
      		//									USART_Std_Printf("temp:%d\n",temp);
      										}
      									}					
      								}							
                      break;
      							}
                  }
              }
      		return 0;
      }
      

      ​ 注意:所有ADC采样得到的值都需要滤波,不只是硬件滤波,更要软件滤波。可以自行搜索ADC滤波算法,进行编写。

      关于采样分辨率的最大值和最小值:如果是12位则ADC采样最大值是4095,即2^12-1。下面是计算公式:

    在这里插入图片描述

    ​ VDDA官方给的是3V.

    ​ 以上是对R25_F3950的使用总结.

    自行转载即可,转载请注明出处
  • 22
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值