ADC通道扩展和软件处理方法

以下部分若感到枯燥,可以往下滑~!!!去看代码部分

一、系统设计

随着集成电路技术的飞速发展,数字电路的各方面性能得到了极大提升,人们也越来越希望在数字域中进行信号处理,在这个背景下,作为模拟信号与数字信号转换桥梁的模数转换器(Analog-to-Digital Converter,ADC)成为研究的热点与重点。本文主要介绍如何使用时分复用(time-division multiplexing, TDM)技术的方式来实现ADC通道采样以及如何使用移动平均滤波,差值比较器处理ADC采样数据。

MCU和CD4051B系统连接方式如图一所示,该方法使用两片CD4051B和MCU的两个ADC通道扩展到16个ADC通道。MCU的两个ADC通道连接到CD4051B的COM脚,MCU通过控制3个GPIO去选择不同的通道作为ADC的采样信号。

在这里插入图片描述

图一 系统框图

使用模拟切换开关CD4051B作为ADC输入通道的切换器件,CD4051B的内部框图二所示:

在这里插入图片描述

图二 CD4051B模拟开关内部框图

内部使用一个38译码器进行8选1的模拟通道切换,通过C,B,A三个控制引脚可以选择8个输入通道中的一个连接到COM输出引脚。

二、采样时序

MCU通过3个GPIO选择CD4051B不同的通道作为ADC的采样信号。在同一时间段内MCU只能采样两个通道的ADC数据,使用时分复用的方式通过不断切换CD4051B的通道可以实现16通道的ADC数据采样。ADC采样时序图
在这里插入图片描述

图三 ADC采样时序图

从图三ADC采样时序图中可以看出,MCU通过控制CD4051B的C,B和A引脚控制模拟输入的通道。图二有CD4051B的真值表,给出了对应的C,B和A输入时COM对应的输出。MCU的ADC再通过同一个ADC输入通道采样8次ADC的数据,8次数据代表8个输入通道的ADC采样值。使用两个ADC进行同时采样就可以完成16个通道的ADC数据采样。在实际应用中MCU的CD4051B C,B和A引脚每隔2ms进行一次输入通道的切换,16ms完成一次16通道的采样。用示波器实际测试到时序波形如图四所示:
在这里插入图片描述

图四 ADC采样示波器时序

由于Ch4和Ch5悬空的,所以在CD4051B切换到Ch4和Ch5时电压有一个放电的过程。ADC采样的时间需要避开CD4051B的通道切换的不稳定电平对ADC采样结果造成的影响,本文中采样的时序是在CD4051B切换通道延时1ms之后在对ADC输入通道进行采样,确保ADC采样的信号不受CD4051B通道切换电平的影响。

三、移动平均滤波器

本文中使用16个电位器进行各个系统音量,系统音效的音量调整。16个电位器经过 10bit精度的ADC采样后会得到范围在0-1024的采样值,这个采样值除以一个系数后就得到一个0-32或0-64级的音量值。MCU再根据这个音量值去调整各个系统音量和系统音效音量。从ADC采样值到系统的音量值软件处理流程如下:
在这里插入图片描述

图五 音量等级软件处理流程

由于实时采样的信号会收到系统电源,系统底噪和ADC误差等因素干扰,实际采样的到的ADC值会带有一个随机的噪声在里面。这个噪音会对系统音量造成一个音量跳变现象,比如在用户不旋转音量电位器的时候系统音量由于ADC噪音造成音量误跳变。本文使用移动平均滤波器对ADC的采样结果进行平滑滤波,避免噪声对电位器的电压采样造成干扰从而形成系统音量误跳变。
移动平均滤波器的公式如下:
在这里插入图片描述

其中x[ ]为输入信号,y[ ]为输出信号,M是需要进行平均的采样数。从公式中可以看出,移动平均滤波算法有M-1个加法和一个除法运算。M的取值设定为次方,除法运算就可以使用向右移位的方式替代,每右移一位相当与除以2。例如M取值为8=,计算除法运算是只需向右移3位即可。整个移动平均滤波需要的运算为7个加法和1个移位运算,对MCU来算说非常友好,不需要花很多指令周期去做滤波运算。
在旋转电位器的情况下原始采样数据和经过M=8的移动平均滤波的波形图如图六所示:
在这里插入图片描述

图六 经过移动平均滤波后音量等级

上图中蓝色曲线为原始ADC采样数据,橙色曲线为经过移动平均滤波器处理之后的曲线,紫色曲线是音量等级曲线。从波形可以看出移动平均滤波器可以很好的平滑掉原始采样数据的噪声,音量等级保持在16级,解决了因为噪音引起的采样值跳动导致的音量误跳变。

四、差值比较器

虽然移动平均滤波能够平滑掉系统噪声,但是当电位器在两个音量等级的临界位置时系统噪音也会引起系统音量在两个相邻音量等级之间跳变。如图七所示:
在这里插入图片描述

图七 经过移动平均滤波后的音量等级

当电位器在音量等级的29和30的临界位置时经过移动平均滤波器还是会引起系统音量在两个相邻音量等级之间跳变。这个时候我们需要在引入一个差值比较器保证电位器在两级音量的临界位置时不会跳变。

在这里插入图片描述

图八 音量等级软件处理流程 差值比较器数学公式如下: ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/6f3302bda40944f2b2f42272d0804105.png#pic_center) 其中x[ ]为当前ADC采样值,y[ ]为上一次ADC采样值,threshold是设定的阈值。根据公式只有ADC的当前采样值减去上一次的采样值的绝对值过阈值时当前的ADC采样值才会被用来计算音量,否则系统会用上一次的采样值去计算音量。Threshold的值要根据系统的实际情况去设定,threshold的值要大于ADC采样的噪声,这样才能有效滤除电位器在两个音量等级的临界位置时音量跳变。在电位器没有转动的情况下增加差值比较器之后ADC采样曲线如图九所示:

在这里插入图片描述

图九经过移动平均滤波和差值比较器后的音量等级

图九中差值比较器的threshold设置为12,移动平均滤波器的曲线随着原始的曲线变化。因为前后两次采样值没有超过threshold的值,所以差值比较器的输出一直稳定在一个值,音量值也稳定在一直音量等级。图十是电位器在旋转情况下的ADC采样值:
在这里插入图片描述

图十电位器旋转情况下经过移动平均滤波和差值比较器的音量等级

从曲线中可以看出经过差值比较器之后音量等级能良好的跟随电位器的转动变化而不受噪音的影响。

五、总结

本文中使用了MCU的3个GPIO和2个ADC的采样通道实现了最多16个电位器电压的采样,对比使用16个GPIO作为ADC通道进行电位器电压采样节省了11个GPIO. 硬件上使用两个CD4051B进行模拟通道的扩展,软件上使用了时分复用的采样方式、移动平均滤波器和差值比较器三个软件算法对ADC采样值处理。经过实际测试,扩展后16个ADC通道能够实现良好的系统功能并具有良好的性能。本文的ADC通道扩展方法可以使用在MCU的ADC采样通道不够或者没有足够的GPIO去作为ADC采样通道的应用中,但是会增加模拟切换开关的硬件成本。软件的资源需求非常少,移动平均滤波器和差值比较器使用到的ROM和RAM在1Kbytes和100bytes以内。

代码部分

主要代码ADC部分:
1、ADC 平均: 十次采样,去掉最大和最小的,八次平均。ADC_and_signal_detect_scan()
2、滤波的threshold 阈值设为12。ADC_diff_filter()
3、每1ms去切换,可以等1ms再去读。拓展切换的通道。项目IO口不够用加上的,如果你的多个ADC都用到io扣上只是切换ADC通道,原理一样下面有实例。

/*scan frequency 1khz, read adc and detect signal every 1ms, only 8ch available, ch 8 and ch 9 is inavailable */
void ADC_and_signal_detect_scan(void)
{
	static uint8_t channel_select=0;
	static uint8_t index=0;
	uint16_t res,res1;
	uint16_t max,min,max1,min1;

	uint8_t i,ch;

	ch = channel_select/2;
	if((channel_select%2)==1)
	{
		ADC1_result_buf[ch][index] = ADC1_read_result();
		ADC2_result_buf[ch][index] = ADC2_read_result();
		sw1_signal_detect_value[ch] = HAL_GPIO_ReadPin(GPIOC, SW1_4051_COM_Pin);
		sw2_signal_detect_value[ch] = HAL_GPIO_ReadPin(SW2_4051_COM_GPIO_Port, SW2_4051_COM_Pin);

		res = 0;
		res1 = 0;

		#if 1
		max =ADC1_result_buf[ch][0];
		min =ADC1_result_buf[ch][0];
		max1= ADC2_result_buf[ch][0];
		min1=ADC2_result_buf[ch][0];
		#endif
		//printf("ADC1_result_buf[%d]:",ch);
		for(i=0;i<SCAN_VALUE;i++)
		{
			#if   1
			if(min>ADC1_result_buf[ch][i])
			{
				min =ADC1_result_buf[ch][i];
			}
			if(max <ADC1_result_buf[ch][i])
			{
				max = ADC1_result_buf[ch][i];
			}
			if(min1>ADC2_result_buf[ch][i])
			{
				min1 =ADC2_result_buf[ch][i];
			}
			if(max1 <ADC2_result_buf[ch][i])
			{
				max1 = ADC2_result_buf[ch][i];
			}
			#endif
			res += ADC1_result_buf[ch][i];
			res1 += ADC2_result_buf[ch][i];


			//printf("%d ",ADC1_result_buf[ch][i]);
		}
		//printf("\r\n");

		ADC1_result[ch]=(res-max-min)>>3;
		ADC2_result[ch]=(res1-max1-min1)>>3;

	}
	else
	{
		switch(ch)
		{
			case 0:
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_C_Pin, GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_B_Pin, GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_A_Pin, GPIO_PIN_RESET);
				break;
		
			case 1:


				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_C_Pin, GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_B_Pin, GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_A_Pin, GPIO_PIN_SET);
				break;
				
			case 2:


				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_C_Pin, GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_B_Pin, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_A_Pin, GPIO_PIN_RESET);
				break;
				
			case 3:


				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_C_Pin, GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_B_Pin, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_A_Pin, GPIO_PIN_SET);

				break;
				
			case 4:


				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_C_Pin, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_B_Pin, GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_A_Pin, GPIO_PIN_RESET);


				break;
				
			case 5:

				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_C_Pin, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_B_Pin, GPIO_PIN_RESET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_A_Pin, GPIO_PIN_SET);

				break;
				
			case 6:
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_C_Pin, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_B_Pin, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_A_Pin, GPIO_PIN_RESET);
				adc1_driver_init(ADC_CHANNEL_2);

				break;
				
			case 7:
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_C_Pin, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_B_Pin, GPIO_PIN_SET);
				HAL_GPIO_WritePin(GPIOC, SW_4051_CTRL_A_Pin, GPIO_PIN_SET);
				adc1_driver_init(ADC_CHANNEL_19);

				break;
		
			default:break;
		}

	}


	channel_select++;
	if(channel_select>=20)
	{
		channel_select = 0;
		index++;
		if(index>=SCAN_VALUE) index=0;
	}

}

/**
 * @brief  ADC different filter. Only update the ADC value when the different value over the ADC_DIFF_FILTER_THRESHOLD
 * @param  old_value:the last ADC value
 * @param  new_value:the current ADC value
 * @retval 0:the updated ADC value 
 */

uint16_t ADC_diff_filter(volatile uint16_t *old_value, volatile uint16_t *new_value)
{
#ifdef ENABLE_ADC_DIFF_FILTER
	uint16_t diff_value,res;
	uint16_t temp_old_value,temp_new_value;


	temp_old_value = *old_value;
	temp_new_value = *new_value;

	if(temp_new_value>=temp_old_value)
		diff_value = temp_new_value - temp_old_value;
	else
		diff_value = temp_old_value -  temp_new_value;

	//printf("ADC filter diff:0x%04X, last:0x%04X, current:0x%04X\n\r", diff_value, temp_old_value, temp_new_value);

	if(diff_value>=ADC_DIFF_FILTER_THRESHOLD)
	{
		res = temp_new_value;
		*old_value = temp_new_value;
	}
	else
	{
		res = temp_old_value;
	}

	//printf("ADC filter res:0x%04X, last:0x%04X, current:0x%04X\n\r", res, temp_old_value, temp_new_value);

	return res;
#else
	return new_value;
#endif
}


uint16_t get_VR_detect_result(_sw_control VR_num)
{
	static volatile uint16_t Last_ADC1_result[10]={0};
	static volatile uint16_t Last_ADC2_result[10]={0};
		
	switch(VR_num)
	{
		case GAIN:
			return ADC_diff_filter(&Last_ADC1_result[0], &ADC1_result[0]);
		break;

		case MASTER:
			return ADC_diff_filter(&Last_ADC2_result[0], &ADC2_result[0]);
		break;

		case BASS:
			return ADC_diff_filter(&Last_ADC1_result[1], &ADC1_result[1]);
		break;

		case REAR_HIGH:
			return ADC_diff_filter(&Last_ADC2_result[1], &ADC2_result[1]);
		break;

		case FRONT_MID:
			return ADC_diff_filter(&Last_ADC1_result[2], &ADC1_result[2]);
		break;

		case REAR_MID:
			return ADC_diff_filter(&Last_ADC2_result[2], &ADC2_result[2]);
		break;

		case LOW:
			return ADC_diff_filter(&Last_ADC2_result[3], &ADC2_result[3]);
		break;

		case FRONT_HIGH:
			return ADC_diff_filter(&Last_ADC1_result[3], &ADC1_result[3]);
		break;

		case GUITAR:
			return ADC_diff_filter(&Last_ADC1_result[4], &ADC1_result[4]);
		break;

		case MUSIC:
			return ADC_diff_filter(&Last_ADC1_result[5], &ADC1_result[5]);		
		break;

		case CH2_VOL:

			return ADC_diff_filter(&Last_ADC2_result[6], &ADC2_result[6]);
		break;

		case CH3_4_VOL:
			return ADC_diff_filter(&Last_ADC2_result[7], &ADC2_result[7]);	
		break;

		case NTC:
			return ADC1_result[6];
		break;
		default:break;
	}

	return 0;
}

多个adc channel去切换的做法,一样的效果。

/*scan frequency 1khz, read adc and detect signal every 1ms, only 8ch available, ch 8 and ch 9 is inavailable */
void ADC_and_signal_detect_scan(void)
{
    static uint8_t channel_select=0;
    static uint8_t index=0;
    uint16_t res,res1;
    uint16_t max,min,max1,min1;

    uint8_t i,ch;

    ch = channel_select/2;
    if((channel_select%2)==1)
    {
        ADC1_result_buf[ch][index] = ADC1_read_result();
        ADC3_result_buf[ch][index] = ADC3_read_result();

        res = 0;
        res1 = 0;

        #if 1
        max =ADC1_result_buf[ch][0];
        min =ADC1_result_buf[ch][0];
        max1= ADC3_result_buf[ch][0];
        min1=ADC3_result_buf[ch][0];
        #endif
        //printf("ADC1_result_buf[%d]:",ch);
        for(i=0;i<SCAN_VALUE;i++)
        {
            #if   1
            if(min>ADC1_result_buf[ch][i])
            {
                min =ADC1_result_buf[ch][i];
            }
            if(max <ADC1_result_buf[ch][i])
            {
                max = ADC1_result_buf[ch][i];
            }
            if(min1>ADC3_result_buf[ch][i])
            {
                min1 =ADC3_result_buf[ch][i];
            }
            if(max1 <ADC3_result_buf[ch][i])
            {
                max1 = ADC3_result_buf[ch][i];
            }
            #endif
            res += ADC1_result_buf[ch][i];
            res1 += ADC3_result_buf[ch][i];


            //printf("%d ",ADC1_result_buf[ch][i]);
        }
        //printf("\r\n");

        ADC1_result[ch]=(res-max-min)>>3;
        ADC3_result[ch]=(res1-max1-min1)>>3;

    }
    else
    {
        switch(ch)
        {
            case 0:
                adc1_driver_init(ADC_CHANNEL_15);//VR1
                adc3_driver_init(ADC_CHANNEL_13);//VR4
                sw1_signal_detect_value[ch] = HAL_GPIO_ReadPin(AUX_IN_DET_GPIO_Port, AUX_IN_DET_Pin);
                //sai_data_IN_NO0();
                break;

            case 1:
                adc1_driver_init(ADC_CHANNEL_18);//VR2
                adc3_driver_init(ADC_CHANNEL_14);//VR7
                sw1_signal_detect_value[ch] = HAL_GPIO_ReadPin(HP_OUT_L_DET_GPIO_Port, HP_OUT_L_DET_Pin);

                break;

            case 2:
                adc1_driver_init(ADC_CHANNEL_14);//VR3
                adc3_driver_init(ADC_CHANNEL_15);//VR8
                sw1_signal_detect_value[ch] = HAL_GPIO_ReadPin(LINEOUT1_DET_GPIO_Port, LINEOUT1_DET_Pin);

                break;

            case 3:
                adc1_driver_init(ADC_CHANNEL_7);//VR5
                sw1_signal_detect_value[ch] = HAL_GPIO_ReadPin(LINEOUT2_DET_GPIO_Port, LINEOUT1_DET_Pin);
                //sai_data_OUT_NO0();
                break;

            case 4:
                adc1_driver_init(ADC_CHANNEL_3);//VR6
                sw1_signal_detect_value[ch] = HAL_GPIO_ReadPin(VBUS_DET_GPIO_Port, VBUS_DET_Pin);

                break;

            case 5:
                adc1_driver_init(ADC_CHANNEL_5);//VR9
                sw1_signal_detect_value[ch] = HAL_GPIO_ReadPin(GTIN_DET1_GPIO_Port, GTIN_DET1_Pin);

                break;

            case 6:
                adc1_driver_init(ADC_CHANNEL_2);//VR10

                break;

            case 7:
                adc1_driver_init(ADC_CHANNEL_10);//NTC
                break;

            default:break;
        }

    }


    channel_select++;
    if(channel_select>=20)
    {
        channel_select = 0;
        index++;
        if(index>=SCAN_VALUE) index=0;
    }

}

再次总结:
ADC获取提高准确性的方法:平均、滤波、threshold等
还有就是可以对参考电压进行校准,一般可能使用内部参考电压VREF去实现校准。这个就不细说了。

  • 28
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yank_k

点个关注加分享,一起探讨学习!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值