STM32H7使用内部参考电压校准ADC和编码器脉冲个数统计
全桥整流2024/12/18原创 秋名山最速传说 IP:广东

塔の町_125226669_p1.jpg

前言:

整了块STM32H750,连板带芯片两个8M FLASH居然只要35元,看着外围器件怕是得亏本吧?先速速拿下,试试STM32的ADC多通道过采样(没使用DMA)以及外部编码器脉冲个数统计。开发使用Cubemx+Keil5。(镇楼图还是挺好看的)


正文:

1、定时器选择编码器脉冲为时钟源

编码器脉冲个数统计我们不使用外部中断的形式(外部中断会反复进入中断占用CPU资源),这里没有使用高级定时器的编码器模式是因为我只统计个数,不在乎方向,同时对精度要求不太高,不想占用高级定时器资源。下图是Cubemx的配置。这里要注意不是所有的定时器都支持外部脉冲作为时钟源,我这里使用的是TIM3。

image.png

然后读取寄存器的值就行。

count=__HAL_TIM_GET_COUNTER(&htim3);    /*读取计数值*/

2、ADC采集

H750 的 ADC 原生支持 16 位分辨率(但在实际采样中,由于噪声等因素,采样结果可能会出现抖动),最高支持过采样1024次,生成一个 26 位的累加值,根据过采样公式(下图一),16 位 ADC 经过 1024 倍过采样后,可以获得21 位的等效分辨率。值得一提的是为什么使用过采样,单纯16位ADC采样得到的值已经挺飘了,我理解过采样的意义是硬件滤波和可以提高ADC等效分辨率。

比如说原本16位的ADC设置过采样1024次(连续采样1024次加起来)可以获得一个26位的数(这个数不代表ADC的精度到了26位),根据下图一的公式(信号处理领域中关于过采样与有效分辨率提升的经典公式,推导过程省略),代入1024这个值可得5,所以16位ADC过采样1024次可以得到21位等效分辨率的ADC。

能实现硬件滤波是因为多次采样取平均。比如说你只需要16位ADC的结果,但过采样1024轮,最后的结果右移10位(相当于除以1024,这种操作的本质是低通滤波)。

我这里读取内部温度以及内部基准电压进而校准外部采集的电压(我这里是采集VCC输入电压)。如下二三图是ADC3的设置(ADC时钟75MHz)。

另外值得一提的是,采样时间也要注意,这个要根据输入阻抗来选择,不能过小,如果采样时间太短,电容无法完全充电,导致误差增加(采样保持误差)。如果过大,会造成资源浪费。我的经验是从大逐渐缩小,直到ADC采样的值出现明显误差然后把时间拉升一到两个等级(如下图四),当然,你也可以翻手册计算出采样时间。

同时增加规则通道(Nember of conversion那里)数量到3,然后选择每个规则通道连接的通道(比如说外部的Pin脚)和采样时间。

image.png


image.png

image.png

image.png

3、部分代码展示

阅读数据手册,拿到芯片在30度和110度的ADC以及出厂时外加3.3V(需要核实)电压的内部基准电压数据存放的位置(这新编辑器的代码看起来这么奇怪的,不同颜色高亮都没有,感觉旧版舒服)。感谢@warmonkey提供的参考代码。

#define FULL_SCALE 65535
extern ADC_HandleTypeDef hadc3;
uint8_t ADC_cycles=0;
double VREFINT_CAL; //VREFINT_CAL 是 VREFINT 校准值
unsigned int TS_DATA; //TS_DATA 是由 ADC 转换得到的实际温度传感器输出值
double adcx; //斜率
double temp; //温度

double VDDA; //器件供电的实际的 VDDA 电压
unsigned int V_CHANNELx; //通道读取的值
double VCC_value; //VCC电压

unsigned int VREFINT_DATA; //VREFINT_DATA 是由 ADC 转换得到的实际 VREFINT 输出值
double vref_value; //内部参考电压的读数

/* USER CODE END Header_Uart1_TXRX */
void Uart1_TXRX(void *argument)
{
  /* USER CODE BEGIN Uart1_TXRX */

//	uint32_t ACD_over_cycls=0;
	VREFINT_CAL = *(unsigned short*)(0x1FF1E860);//STM32H750,不同系列可能不一样,具体看手册
	printf("VREFINT_CAL : %f\r\n",VREFINT_CAL);
	HAL_ADC_Start(&hadc3);
  /* Infinite loop */
  for(;;)
  {
		if(__HAL_ADC_GET_FLAG(&hadc3, ADC_FLAG_EOC) && ADC_cycles==0) //第一次转换OK
		//if (ACD_over_cycls >= 1024)			
		{
			//__HAL_ADC_CLEAR_FLAG(&hadc3, ADC_FLAG_EOC); // 每个EOC置1后清除标志位 注意:不需要这一步,因为HAL_ADC_GetValue会自动清除ADC_FLAG_EOC
			ADC_cycles=1;
			TS_DATA = HAL_ADC_GetValue(&hadc3);
			adcx = (110.0-30.0)/(*(unsigned short*)(0x1FF1E840) - *(unsigned short*)(0x1FF1E820));//出厂时110度和30度的ADC值,这里是获得斜率(这个是3.0V下测得还是3.3V下测得的?)
//			printf("adc_30 : %d\r\n",*(unsigned short*)(0x1FF1E840));
//			printf("adc_110 : %d\r\n",*(unsigned short*)(0x1FF1E820));
			temp = adcx*(TS_DATA - *(unsigned short*)(0x1FF1E820))+30;
		}
		
		if(__HAL_ADC_GET_FLAG(&hadc3, ADC_FLAG_EOC) && ADC_cycles==1) //第二次转换OK
		{
			//__HAL_ADC_CLEAR_FLAG(&hadc3, ADC_FLAG_EOC); // 每个EOC置1后清除标志位 注意:不需要这一步,因为HAL_ADC_GetValue会自动清除ADC_FLAG_EOC
			ADC_cycles=2;
			VREFINT_DATA = HAL_ADC_GetValue(&hadc3);
			VDDA = (double)3.3*VREFINT_CAL/VREFINT_DATA;
			vref_value = (double)VDDA/FULL_SCALE*VREFINT_DATA;
		}
		
		if(__HAL_ADC_GET_FLAG(&hadc3, ADC_FLAG_EOC) && ADC_cycles==2) //第二次转换OK
		{
			//__HAL_ADC_CLEAR_FLAG(&hadc3, ADC_FLAG_EOC); // 每个EOC置1后清除标志位 注意:不需要这一步,因为HAL_ADC_GetValue会自动清除ADC_FLAG_EOC
			ADC_cycles=0;
			V_CHANNELx = HAL_ADC_GetValue(&hadc3);
			VCC_value = (double)V_CHANNELx/65535*VDDA;
			HAL_ADC_Stop(&hadc3);
			printf("TS_DATA : %d\r\n",TS_DATA);//读到的值
			printf("MCU Temperature : %f度\r\n",temp);//MCU温度
			printf("VREFINT_DATA: %d\r\n",VREFINT_DATA);//读到的值
			printf("MCU VREF: %f伏\r\n",vref_value);//内部基准的电压
			printf("V_CHANNELx: %d\r\n",V_CHANNELx);//读到的值
			printf("MCU VCC: %f伏\r\n",VCC_value);//VCC电压
			osDelay(500);
			HAL_ADC_Start(&hadc3);
		}
    osDelay(1);
  }
  /* USER CODE END Uart1_TXRX */
}

另外ADC初始化时要校准。

HAL_ADCEx_Calibration_Start(&hadc3, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED);

如下图一二是通过内部参考电压校准ADC的方法(我的算法似乎还有点瑕疵,需要优化)。

image.png


image.png


未END


PS:感觉我对ADC的理解还不是很深,欢迎各位发言。


[修改于 2天6时前 - 2024/12/19 17:16:17]

来自:电子信息 / 电子技术动手实践:实验报导
2
1
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
全桥整流 作者
3天4时前 IP:广东
940478

这代码排版这么奇怪的。


引用
评论(1)
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
warmonkey
2天23时前 IP:广东
940488

/* ADC internal channels related definitions */

/* Internal voltage reference VrefInt */

#define VREFINT_CAL_ADDR                   ((uint16_t*) (0x1FFF7A2AU)) /* Internal voltage reference, address of parameter VREFINT_CAL: VrefInt ADC raw data acquired at temperature 30 DegC (tolerance: +-5 DegC), Vref+ = 3.3 V (tolerance: +-10 mV). */

#define VREFINT_CAL_VREF                   ( 3300UL)                    /* Analog voltage reference (Vref+) value with which temperature sensor has been calibrated in production (tolerance: +-10 mV) (unit: mV). */

/* Temperature sensor */

#define TEMPSENSOR_CAL1_ADDR               ((uint16_t*) (0x1FFF7A2CU)) /* Internal temperature sensor, address of parameter TS_CAL1: On STM32F4, temperature sensor ADC raw data acquired at temperature  30 DegC (tolerance: +-5 DegC), Vref+ = 3.3 V (tolerance: +-10 mV). */

#define TEMPSENSOR_CAL2_ADDR               ((uint16_t*) (0x1FFF7A2EU)) /* Internal temperature sensor, address of parameter TS_CAL2: On STM32F4, temperature sensor ADC raw data acquired at temperature 110 DegC (tolerance: +-5 DegC), Vref+ = 3.3 V (tolerance: +-10 mV). */

#define TEMPSENSOR_CAL1_TEMP               (( int32_t)   30)           /* Internal temperature sensor, temperature at which temperature sensor has been calibrated in production for data into TEMPSENSOR_CAL1_ADDR (tolerance: +-5 DegC) (unit: DegC). */

#define TEMPSENSOR_CAL2_TEMP               (( int32_t)  110)           /* Internal temperature sensor, temperature at which temperature sensor has been calibrated in production for data into TEMPSENSOR_CAL2_ADDR (tolerance: +-5 DegC) (unit: DegC). */

#define TEMPSENSOR_CAL_VREFANALOG          ( 3300UL)                    /* Analog voltage reference (Vref+) voltage with which temperature sensor has been calibrated in production (+-10 mV) (unit: mV). */


float adc_mcutemp(uint16_t adcval)

{

  float dtemp = TEMPSENSOR_CAL2_TEMP - TEMPSENSOR_CAL1_TEMP;

  float dcal = TEMPSENSOR_CAL2_ADDR - TEMPSENSOR_CAL1_ADDR;

  float calv = *TEMPSENSOR_CAL1_ADDR;

  float calt = TEMPSENSOR_CAL1_TEMP;

  return (adcval - calv) * dtemp / dcal + calt;

}

从arduino ststm32抠出来的,供参考


+1
科创币
全桥整流
2024-12-19
谢谢
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
全桥整流
进士 学者 机友 笔友
文章
27
回复
283
学术分
1
2020/01/26注册,6时16分前活动

秋名山最速传说!

主体类型:个人
所属领域:无
认证方式:身份证号
IP归属地:广东
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}