一:APM32F402的ADC知识分享:
系列产品有 2个 ADC,精度为 12 位,每个 ADC 最多有 16 个外部通道和2个内部通道。各通道 AD 转换模式有单次、连续、扫描或间断,ADC 转换结果可以左对齐或右对齐存储在 16 位数据寄存器中。
二:ADC的简单编写思路:
首先开启PA口时钟和ADC1时钟,设置PA1为模拟输入模式,否则AD不能正常读数程序开启之前复位ADC1,同时设置ADC1分频因子和ADC的位数等相关信息。
初始化ADC1参数,配置规则通道参数:
开启软件转换: 等待转换完成,读取ADC值。
简单说明一下具体的思路
1:系统时钟的选择:根据电路板的硬件设计,选择外部时钟,主频设置为120MHZ,然后配置ADC的时钟分频器,确保ADC在工作时频率正确,最后使能使用的ADC1的时钟
2:ADC的基本参数配置:
选择合适的ADC模式,APM32F402支持多种转换模式,单次转换和多次转换的模式,我个人认为还是使用连续多次转换的模式效果更好些。设置好合适的采样周期,ADC的采集到的数据不仅仅和外部传感器输入的实际数据有关,和采样的频率也息息相关,经过我个人的工作经历来说,合适的采样时间才能采集到准确的AD数据,通常采集频率过快,会导致ADC数据不稳定。较高的采样时间会增加数据的稳定性,但是这样会增加程序运行的时间。
三软件代码如下所示:
3.1 ADC的初始化函数
- void ADC_Init(void)
- {
- GPIO_Config_T GPIO_ConfigStruct;
- ADC_Config_T ADC_ConfigStruct;
- /* Enable GPIOA clock */
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA);
- /* ADC channel 0 configuration */
- GPIO_ConfigStructInit(&GPIO_ConfigStruct);
- GPIO_ConfigStruct.mode = GPIO_MODE_ANALOG;
- GPIO_ConfigStruct.pin = GPIO_PIN_0;
- GPIO_Config(GPIOA, &GPIO_ConfigStruct);
- /* ADCCLK = PCLK2/4 */
- RCM_ConfigADCCLK(RCM_PCLK2_DIV_4);
- /* Enable ADC clock */
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_ADC1);
- /* ADC configuration */
- ADC_Reset(ADC1);
- ADC_ConfigStructInit(&ADC_ConfigStruct);
- ADC_ConfigStruct.mode = ADC_MODE_INDEPENDENT;
- ADC_ConfigStruct.scanConvMode = DISABLE;
- ADC_ConfigStruct.continuousConvMode = ENABLE;
- ADC_ConfigStruct.externalTrigConv = ADC_EXT_TRIG_CONV_NONE;
- ADC_ConfigStruct.dataAlign = ADC_DATA_ALIGN_RIGHT;
- /* channel number */
- ADC_ConfigStruct.nbrOfChannel = 1;
- ADC_Config(ADC1, &ADC_ConfigStruct);
- /* ADC channel Convert configuration */
- ADC_ConfigRegularChannel(ADC1, ADC_CHANNEL_0, 1, ADC_SAMPLETIME_13CYCLES5);
- /* Enable complete conversion interupt */
- ADC_EnableInterrupt(ADC1, ADC_INT_EOC);
- /* NVIC configuration */
- NVIC_EnableIRQRequest(ADC1_2_IRQn, 1, 1);
- /* Enable ADC */
- ADC_Enable(ADC1);
- /* Enable ADC1 reset calibration register */
- ADC_ResetCalibration(ADC1);
- /* Check the end of ADC1 reset calibration register */
- while (ADC_ReadResetCalibrationStatus(ADC1));
- /* Start ADC1 calibration */
- ADC_StartCalibration(ADC1);
- /* Check the end of ADC1 calibration */
- while (ADC_ReadCalibrationStartFlag(ADC1));
- /* Start ADC1 Software Conversion */
- ADC_EnableSoftwareStartConv(ADC1);
- }
3.2 编写ADC转换完成的回调函数:
- void ADC_callback(void)
- {
- /* Read ADC Conversion value */
- iAdcData = ADC_ReadConversionValue(ADC1);
- // printf("采集到编码器的原始数据 = : %4d \r\n", iAdcData);
- }
- void ADC1_2_IRQHandler(void)
- {
- if (ADC_ReadIntFlag(ADC1, ADC_INT_EOC) == SET)
- {
- ADC_callback();
- ADC_ClearIntFlag(ADC1, ADC_INT_EOC);
- }
- }
3.3 在时间任务回调函数中,添加输出采集到的ADC数据函数:
- void task_100ms(void)
- {
- //Usart1Txbuffer();
- printf("采集到编码器的原始数据 = : %4d \r\n", iAdcData);
- }
四:测试图片如下:
旋转电位器,可以观察到采集的ADC数据变化过程。
分享一下ADC数据处理方法如下所示:
1: 采用中值算法滤波
主要是程序在执行的时候,连续采集N次(需要注意下这里下,这里的N必须取值奇数),程序需要按大到小或者从小到达的顺序进行排序,然后取中间数值做为有效值。
- int MidValueDeal(int N)
- {
- int value_buf[N]; int i,j,k,temp;
- for( i = 0; i < N; ++i)
- {
- value_buf[i] = HAL_ADC_GetValue(&hadc1);
- }
- for(j = 0 ; j < N-1; ++j)
- {
- for(k = 0; k < N-j-1; ++k)
- { //从小到大排序,冒泡法排序
- if(value_buf[k] > value_buf[k+1])
- {
- temp = value_buf[k];
- value_buf[k] = value_buf[k+1];
- value_buf[k+1] = temp;
- }
- }
- }
- return value_buf[(N-1)/2];
- }
2:算术平均数滤波
连续取值N个数据,对所有的数据进行取平均值;
代码如下:
- int AverValueDeal(int N)
- {
- int sum = 0;
- unsinged short i;
- for(i = 0; i < N; ++i)
- {
- sum += HAL_ADC_GetValue(&hadc1);
- }
- return sum/N;
- }
这里取值时候,需要根据被控对象进行选择,N值取值过大,会导致响应过慢,数据的灵敏度过低,所以采用算术平均值滤波时候,需要我们格外的注意;
3:滑动滤波算法
把连续取N个采样值看成一个队列,队列的长度固定为N。每次采样到一个新数据放入队尾,并扔掉原来队首的一次数据(先进先出原则)。把队列中的N个数据进行算术平均运算,就可获得新的滤波结果;
- #define N 10
- int value_buf[N];
- int sum=0; //数据总和
- int Num=0; //当前队列中数组下标
- int movedataFilter()
- {
- if(Num < N)
- {
- value_buf[Num] = HAL_ADC_GetValue(&hadc1);
- sum += value_buf[Num];
- Num++;
- return sum/Num;
- }
- else
- {
- sum -= sum/N;
- sum += HAL_ADC_GetValue(&hadc1);
- return sum/N;
- }
- }
|