这代码排版这么奇怪的。
前言:
整了块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的理解还不是很深,欢迎各位发言。
[修改于 3时40分前 - 2024/12/19 17:16:17]
时段 | 个数 |
---|---|
{{f.startingTime}}点 - {{f.endTime}}点 | {{f.fileCount}} |
200字以内,仅用于支线交流,主线讨论请采用回复功能。