加载中
加载中
表情图片
评为精选
鼓励
加载中...
分享
加载中...
文件下载
加载中...
修改排序
加载中...
STM32H7使用内部参考电压校准ADC和编码器脉冲个数统计
全桥整流2024/12/18原创 秋名山最速传说 IP:广东

塔の町_125226669_p1.jpg

前言:

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


正文:

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

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

image.png

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

Other
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提供的参考代码。

C++
#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初始化时要校准。

Other
HAL_ADCEx_Calibration_Start(&hadc3, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED);

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

image.png


image.png


未END


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


[修改于 4个月28天前 - 2024/12/19 17:16:17]

来自:电子信息 / 电子技术动手实践:实验报导
2
2
新版本公告
~~空空如也
全桥整流 作者
4个月29天前 IP:广东
940478

这代码排版这么奇怪的。


引用
评论(1)
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
warmonkey
4个月29天前 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字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

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

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

秋名山最速传说! 邮箱:331924204@XXXXXX

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

空空如也

笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的