联盛德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;