联盛德W801系列3-如何提高采集多路ADC效率


本文是基于联盛德W801的 《wm_sdk_w80x_20211115.zip》的SDK编写。

1. W801的ADC资料

摘自《WM_W800_寄存器手册V2.1.pdf》

ADC模块的供电电压是2.5V,所以输入不能超过2.5V。

基于 Sigma-Delta ADC 的采集模块,集成 4 路 16 比特 ADC,完成最多 4 路模拟信号的采集,或两路差分信号采集,采样率通过外部输入时钟控制,最高采样率 1KHz,可采集输入电压,也可采集芯片温度,支持输入校准和温度补偿校准。

在这里插入图片描述
W801共有4路供外部使用的ADC,内部的采集通道有1.基准offset;2.内部温度;3.内部电压。我在《WM_W800_寄存器手册V2.1》文档中的目录没有看到关于ADC的内容,正文是有的。通过阅读源码,我对3个内部采集通道说说自己的理解:
1.每次读取一个外部通道前Vn,需要读一次基准offset电压V0,用两种的差值(Vn-V0)来计算外部通道的电压;
2.内部温度,顾名思义,这个通道可以获取芯片内部的温度;
3.内部电压:没有用到,不清楚,资料没有说清楚,根据相关资料推断,应该是电源电压。
下面是缺少第26章目录的图片:
在这里插入图片描述

2.ADC采样频率

官方说明了采样频率为1k,我觉得应该可以高一点吧。于是我测试了一下,直接调用<wm_adc.c>的函数:

2.1函数adc_get_inputVolt

int adc_get_inputVolt(u8 channel)
{
	int average = 0;
	double voltage = 0.0;
	
	adc_get_offset();

	tls_adc_init(0, 0); 
	tls_adc_reference_sel(ADC_REFERENCE_INTERNAL);
	tls_adc_set_pga(1,1);
	tls_adc_set_clk(ADC_CLK_DIV_DATA);	//	---(1)改变参数
	tls_adc_start_with_cpu(channel);

	waitForAdcDone();
	average = tls_read_adc_result();
	signedToUnsignedData(&average); 	
	tls_adc_stop(0);

	if ((channel == 8) || (channel == 9))
	{
		voltage = ((double)average - (double)adc_offset)/4.0;
		voltage = voltage*(126363/1000)/1000000;
	}
	else
	{
		voltage = ((double)average - (double)adc_offset)/4.0;
		voltage = 1.196 + voltage*(126363/1000.0)/1000000;
	}

	average = (int)(voltage*1000);
    return average;
}

测试代码如下:

	startTime = xTaskGetTickCount();
	for(iii=0;iii<100;iii++){	sum += 	adc_get_inputVolt(0);	}
	printf("100 loop time =%d,vol=%d\n",xTaskGetTickCount()-startTime,sum/iii);

测试出来的结果是100次转换时间约140ms。采样频率不到1k。我在想能不能改变哪个参数可以提高速度。我就修改了int adc_get_inputVolt(u8 channel)的第11行的ADC_CLK_DIV_DATA,下面是我尝试的几个值:

//#define		ADC_CLK_DIV_DATA	0x28		//	默认
//#define		ADC_CLK_DIV_DATA	(0x28>>1)	//	0x28 / 2
//#define		ADC_CLK_DIV_DATA	(0x28>>2)	//	0x28 / 4 =10  -- ADC结果错误
#define		ADC_CLK_DIV_DATA	(0x10)			//	这个是最快的,且能保障ADC结果正确的数值

把ADC_CLK_DIV_DATA 改为 0x10 后,100次转换时间约105ms,比较接近1k Hz。

2.2 函数adc_get_offset

函数adc_get_inputVolt每次都要调用adc_get_offset,看看这个函数的源码:

u32 adc_get_offset(void)
{ 
	adc_offset = 0;
    tls_adc_init(0, 0); 
	tls_adc_reference_sel(ADC_REFERENCE_INTERNAL);
	tls_adc_start_with_cpu(CONFIG_ADC_CHL_OFFSET);	
	tls_adc_set_pga(1,1);
	tls_adc_set_clk(ADC_CLK_DIV_DATA);		

    waitForAdcDone();
	adc_offset = tls_read_adc_result(); //获取adc转换结果
	signedToUnsignedData(&adc_offset);
	tls_adc_stop(0);

    return adc_offset;
}

这两个函数最耗时的就是等待ADC转换完毕的函数waitForAdcDone(),见第10行。

2.3函数waitForAdcDone

函数源码:

 void waitForAdcDone(void)
{
    u32 counter = 0;
	u32 timeout = 10000;
	u32 reg = 0;

	/*wait for transfer success*/
	tls_irq_disable(ADC_IRQn);
	while(timeout--)
	{
		reg = tls_reg_read32(HR_SD_ADC_INT_STATUS);
		if (reg & ADC_INT_MASK)
		{
			counter++;
		    tls_reg_write32(HR_SD_ADC_INT_STATUS, reg|ADC_INT_MASK);			
			if (counter == 4)
			{
				break;
			}
		}
		else if(reg & CMP_INT_MASK)
        {
        	counter++;
			tls_reg_write32(HR_SD_ADC_INT_STATUS, reg|CMP_INT_MASK);
			if (counter == 4)
			{
				break;
			}
        }
	}
	tls_irq_enable(ADC_IRQn);
}

程序老是在这里等待,效率很低哦。在RTOS中,不应该有长时间的等待。

3.在freeRTOS中,提高w801多路ADC采集效率的探索

为了提高多通道ADC效率效率,我想到了2点:
1.获取基准offset通道的AD值,不用每次都获取;
2.我需要3路外部通道,加上基准通道,共4路,先启动第1路,延时20ms(释放当前任务),–>读取第1路AD值,同时启动第2路,延时20ms–>读取第2路AD值,同时启动第3路,延时20ms -->…–>读取第4路AD值,同时启动第1路,延时20ms …循环读取。

下面的是参考代码(函数my_adc_get_inputVolt 每20ms调用一次):

#define		ADC_N_BUF_LEN		8
#define		ADC_CHANNEL_MAX		4
enum{
	CHL_offset	=0,	//	0 -  基准 通道	
	CHL_heatBedTemp,    	//	1 -  加热板NTC
	CHL_pcbTemp,        	//	2 -  PCB  NTC
	CHL_heatCurrent, 		//	3 -  加热板电流
};
//	ADC的N个通道,每个通道存储8组数据,每组数据保存最近的8个 采样值
unsigned short	ADC_buf[ADC_CHANNEL_MAX][ADC_N_BUF_LEN];
unsigned int    ADC_QueueIndex;
unsigned int   	ADC_ChanelIndex;
unsigned int       	ADC_CHL_IndexBuf[ADC_CHANNEL_MAX]=
{
	CONFIG_ADC_CHL_OFFSET,   //	-- 参考电压
	0,		//	PA01 -- ADC0   	-- 加热板温度
	3,		//	PA02 -- ADC3	-- PCB温度
	2		//	PA03 -- ADC2	-- 加热板电流检测
};
void	my_adc_init(void)
{
	ADC_QueueIndex = 0;
	ADC_ChanelIndex = 0;
	//	启动  ADC_ChanelIndex 对应的channel
	tls_adc_init(0, 0); 
	tls_adc_reference_sel(ADC_REFERENCE_INTERNAL);
	tls_adc_set_pga(1,1);
	tls_adc_set_clk(ADC_CLK_DIV_DATA);	

	tls_adc_start_with_cpu(ADC_CHL_IndexBuf[ADC_ChanelIndex]);
}
int my_adc_get_inputVolt(void)
{	
	int average = 0;
	double voltage = 0.0;
	//	1.读取上一次启动的通道
	average = tls_read_adc_result();
	signedToUnsignedData(&average); 	
	tls_adc_stop(0);
	if(ADC_ChanelIndex == 0){
		adc_offset =average;
	}else{
		double adc_delta = 0.0;
		adc_delta = ((double)average - (double)adc_offset);
		voltage = 1196*2 + adc_delta*0.03159075*2;
		
		//printf("os:%d,a:%d \r\n",adc_offset,average);
		//printf("delta:%f,v:%f \r\n",adc_delta,voltage);
		average = (int)(voltage);
		ADC_buf[ADC_ChanelIndex][ADC_QueueIndex] = average;
	}
	//	2.启动下一个通道
	ADC_ChanelIndex ++;
	if(ADC_ChanelIndex >= ADC_CHANNEL_MAX){
		ADC_ChanelIndex = 0;
		ADC_QueueIndex ++;
		ADC_QueueIndex &= 0x07;		//		ADC_QueueIndex == 【0-7】
	}
	//	启动  ADC_ChanelIndex 对应的channel
	tls_adc_init(0, 0); 
	tls_adc_reference_sel(ADC_REFERENCE_INTERNAL);
	tls_adc_set_pga(1,1);
	tls_adc_set_clk(ADC_CLK_DIV_DATA);	
	tls_adc_start_with_cpu(ADC_CHL_IndexBuf[ADC_ChanelIndex]);
	
    return average;

上面代码的第42,42行:

		adc_delta = ((double)average - (double)adc_offset);
		voltage = 1196*2 + adc_delta*0.03159075*2;

原来是下面的代码,我修改了一下,把电压值由2.5V换算到5V。因为NTC电压表(由阻值表转换过来,为双字节整型),范围是0-2500毫伏,分辨率不够高,所以换算到0-5000毫伏。

		voltage = ((double)average - (double)adc_offset)/4.0;
		voltage = 1.196 + voltage*(126363/1000.0)/1000000;
posted @ 2022-07-13 11:43  汉塘阿德  阅读(81)  评论(0编辑  收藏  举报  来源