这代码排版这么奇怪的。
前言:
整了块STM32H750,连板带芯片两个8M FLASH居然只要35元,看着外围器件怕是得亏本吧?先速速拿下,试试STM32的ADC多通道过采样(没使用DMA)以及外部编码器脉冲个数统计。开发使用Cubemx+Keil5。(镇楼图还是挺好看的)
正文:
1、定时器选择编码器脉冲为时钟源
编码器脉冲个数统计我们不使用外部中断的形式(外部中断会反复进入中断占用CPU资源),这里没有使用高级定时器的编码器模式是因为我只统计个数,不在乎方向,同时对精度要求不太高,不想占用高级定时器资源。下图是Cubemx的配置。这里要注意不是所有的定时器都支持外部脉冲作为时钟源,我这里使用的是TIM3。
然后读取寄存器的值就行。
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脚)和采样时间。
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的方法(我的算法似乎还有点瑕疵,需要优化)。
未END
PS:感觉我对ADC的理解还不是很深,欢迎各位发言。
[修改于 2天7时前 - 2024/12/19 17:16:17]
/* 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抠出来的,供参考
200字以内,仅用于支线交流,主线讨论请采用回复功能。