Stellaris系列微控制器的ADC过采样技术(二)
//
//
定序器将被其中一个通用定时器触发
//
ADCSequenceConfigure(ADC_BASE, 0, ADC_TRIGGER_TIMER, 0);
ADCSoftwareOversampleConfigure(ADC_BASE, 0, 8);
ADCSoftwareOversampleStepConfigure(ADC_BASE, 0, 0, (ADC_CTL_CH1
| ADC_CTL_IE | ADC_CTL_END));
//
//
初始化定时器
0
,每隔
10ms
触发一次
ADC
转换
//
TimerConfigure(TIMER0_BASE, TIMER_CFG_32_BIT_PER);
TimerLoadSet(TIMER0_BASE, TIMER_A, SysCtlClockGet() / 100);
TimerControlTrigger(TIMER0_BASE, TIMER_A, true);
代码段
1.a
的
ADC
配置表示在采样完成时产生一个中断,这样就必须具有中断处理程序(见代码段
1.b
)。驱动库的过采样函数自动将采样的数据进行平均,因此,中断处理函数相对来说也是很基础的。但要记住:要将每次中断中计算的平均值和计算的开销提供给中断处理程序。
代码段
1.b ADC
中断处理程序
void
ADCIntHandler(void)
{
long lStatus;
//
//
清除
ADC
中断
//
ADCIntClear(ADC_BASE, 0);
//
//
获得
ADC
的平均数据
//
lStatus = ADCSoftwareOversampleDataGet(ADC_BASE, 0, g_ulAverage);
//
//
占位符,供
ADC
处理数据
//
}
在将配置步骤和中断处理程序放在适当位置后,启动转换处理。定时器打开(开始计数)之前,
ADC
定序器和中断必须使能(见代码段
1.c
)。
代码段
1.c
使能
ADC
和中断
//
//
使能
ADC
定序器
0
及其中断
(
在
ADC
和
NVIC
中
)
//
ADCSequenceEnable(ADC_BASE, 0);
ADCIntEnable(ADC_BASE, 0);
IntEnable(INT_ADC0);
//
//
使能定时器并启动转换处理
//
TimerEnable(TIMER0_BASE, TIMER_A);
使用多个定序器或一个定时器实现大于
8
倍的过采样
驱动库的过采样函数最大只能进行
8
倍过采样(根据采样定序器的硬件限制),因此需要更大过采样因子的应用必须使用其它的实现。本小节将描述如何使用下面的两种方法:在过采样频率下运行的多个采样定序器和一个定时器来解决这个问题。
例2:使用多个采样定序器的16x过采样
采样定序器的灵活性允许对其进行多种配置。将采样定序器
0-2
累积起来可获得
16
个采样(
8+4+4
),因此使用采样定序器
0-2
可实现
16
倍过采样。为使该级别的过采样能够工作,定序器中的所有阶段必须设置为对相同的模拟输入进行采样,这意味着丢弃了使用一个定序器采样多个输入的功能。
代码段
2.a
使用定序器
0-2
配置一个
10ms
的周期转换。使用一个定时器触发就可启动所有
3
个定序器的采样操作,而无需复杂的触发配置。为获得所需的结果,要对采样定序器的优先级进行配置,这样,采样定序器
2
的优先级最低(即它最后采样),并且在采样定序器
2
的最后一步之后,配置为发出一个
“
转换结束
”
中断。
代码段
2.a ADC
配置-多个采样定序器
//
//
初始化
ADC
,以便使用定序器
0-2
对通道
1
进行
16x
过采样
//
转换操作通过
GPTM
触发
//
ADCSequenceConfigure(ADC_BASE, 0, ADC_TRIGGER_TIMER, 0);
ADCSequenceConfigure(ADC_BASE, 1, ADC_TRIGGER_TIMER, 1);
ADCSequenceConfigure(ADC_BASE, 2, ADC_TRIGGER_TIMER, 2);
//
//
配置定序器
0
的序列步骤(
sequence step
)
//
ADCSequenceStepConfigure(ADC_BASE, 0, 0, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 0, 1, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 0, 2, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 0, 3, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 0, 4, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 0, 5, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 0, 6, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 0, 7, (ADC_CTL_CH1 | ADC_CTL_END));
//
//
配置定序器
1
的序列步骤
//
ADCSequenceStepConfigure(ADC_BASE, 1, 0, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 1, 1, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 1, 2, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 1, 3, (ADC_CTL_CH1 | ADC_CTL_END));
//
//
配置定序器
2
的序列步骤
//
ADCSequenceStepConfigure(ADC_BASE, 2, 0, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 2, 1, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 2, 2, ADC_CTL_CH1);
ADCSequenceStepConfigure(ADC_BASE, 2, 3, (ADC_CTL_CH1 | ADC_CTL_IE
| ADC_CTL_END));
//
//
初始化定时器
0
,每隔
10ms
触发一次
ADC
转换
//
TimerConfigure(TIMER0_BASE, TIMER_CFG_32_BIT_PER);
TimerLoadSet(TIMER0_BASE, TIMER_A, SysCtlClockGet() / 100);
TimerControlTrigger(TIMER0_BASE, TIMER_A, true);
在代码段
2.b
中,中断处理程序必须收集
FIFO
的数据并进行平均计算。因为不需要处理函数开销就可以获得所需的结果,所以不使用
ADCSequenceDataGet
函数,并且使用直接的寄存器读操作来清空定序器的
FIFO
。而使用
ADCSequenceDataGet
时,要求函数定义一个额外的
8
入口采样缓冲区,即使使用直接的寄存器读操作,中断处理程序中执行的总和计算和平均计算仍然会有可计算的开销。
代码段
2.b ADC
中断处理程序
void
ADCIntHandler(void)
{
unsigned long ulIdx;
unsigned long ulSum = 0;
//
//
清除中断
//
ADCIntClear(ADC_BASE, 2);
//
//
获得来自定序器
0
的数据
//
for(ulIdx = 8; ulIdx; ulIdx--)
{
ulSum += HWREG(ADC_BASE + ADC_O_SSFIFO0);
}
//
//
获得来自定序器
1
和
2
的数据
//
for(ulIdx = 4; ulIdx; ulIdx--)
{
ulSum += HWREG(ADC_BASE + ADC_O_SSFIFO1);
ulSum += HWREG(ADC_BASE + ADC_O_SSFIFO2);
}
//
//
将过采样的数据进行平均
//
g_ulAverage = ulSum >> 4;
//
//
占位符,以便
ADC
处理代码
//
}
在启动转换处理之前,将采样定序器和中断使能(见代码段
2.c
)。
代码段
2.c
使能
ADC
和中断
//
//
使能定序器和中断
//
ADCSequenceEnable(ADC_BASE, 0);
ADCSequenceEnable(ADC_BASE, 1);
ADCSequenceEnable(ADC_BASE, 2);
ADCIntEnable(ADC_BASE, 2);
IntEnable(INT_ADC2);
//
//
使能定时器并启动转换处理
//
TimerEnable(TIMER0_BASE, TIMER_A);
例3使用在fOS下运行的定时器进行16x过采样
另一个实现
16x
过采样的方法(无需消耗
ADC
定序器的大部分资源)是使用一个在过采样频率下运行的周期定时器。例如,如果转换处理每
10ms
必须返回到主应用程序并且即将进行
16
倍过采样,则能够将定时器配置为每
625
µ
s
获得一个采样值。让定时器在过采样频率下触发一次转换明显地产生了额外的
ADC
中断,这必须在应用程序中说明。
将
ADC
和定时器配置为执行上述操作的代码见代码段
3.a
。
代码段
3.a ADC
配置-在
fOS
下运行的定时器
//
//
初始化
ADC
,以便在检测到一次触发时在通道
1
、定序器
3
上获得一个采样值。
//
//
ADCSequenceConfigure(ADC_BASE, 3, ADC_TRIGGER_TIMER, 0);
ADCSequenceStepConfigure(ADC_BASE, 3, 0, (ADC_CTL_CH1 | ADC_CTL_IE
| ADC_CTL_END));
//
//
初始化定时器
0
,每
625
µ
s
触发一次
ADC
转换
//
TimerConfigure(TIMER0_BASE, TIMER_CFG_32_BIT_PER);
TimerLoadSet(TIMER0_BASE, TIMER_A, SysCtlClockGet() / 1600);
TimerControlTrigger(TIMER0_BASE, TIMER_A, true);
既然
ADC
在过采样频率下进行采样操作,中断处理程序必须知道已获得的采样数以及总和(见代码段
3.b
)。在累积了
16
次转换后,将这
16
个采样值进行平均,并清除全局采样计数变量和总和变量。
代码段
3.b ADC
中断处理程序
void
ADCIntHandler(void)
{
//
//
清除中断
//
ADCIntClear(ADC_BASE, 3);
//
//
将新的采样值加到全局总和中
//
g_ulSum += HWREG(ADC_BASE + ADC_O_SSFIFO3);
//
// g_ucOversampleCnt
加
1
//
g_ucOversampleCnt++;
//
//
如果累积了
16
个采样值,则将它们平均并将全局变量复位
//
if(g_ucOversampleCnt == 16)
{
g_ulAverage = g_ulSum >> 4;
g_ucOversampleCnt = 0;
g_ulSum = 0;
}
//
//
占位符,以便
ADC
处理代码
//
}
最后,在使能定时器之前,将定序器
3
及其中断使能,并清除全局计数器和总和变量(见代码段
3.c
)。
代码段
3.c
使能
ADC
、中断并清除全局变量
//
//
使能定序器和中断
//
ADCSequenceEnable(ADC_BASE, 3);
ADCIntEnable(ADC_BASE, 3);
IntEnable(INT_ADC3);
//
//
将过采样计数器和总和变量清零
//
g_ucOversampleCnt = 0;
g_ulSum = 0;
//
//
使能定时器并启动转换处理
//
TimerEnable(TIMER0_BASE, TIMER_A);
使用滑动平均进行过采样
当采样频率接近
ADC
的最大采样率时,滑动平均非常有用。滑动平均应用中的主要元件是采样缓冲区,它在每次转换完成时减去
/
加上数据。
例
4
将
ADC
配置为每隔
100
µ
s
进行一次采样,采样缓冲区含有
16
个入口。注意:应用程序不向采样缓冲区预先填充有效的数据,这样,前
16
个采样值必须相应地由软件来处理。
ADC
配置为在定时器触发时采样,并在每次转换之后将处理器中断。
例4使用滑动平均每100µs过采样
代码段
4.a ADC
配置-滑动平均
//
//
初始化
ADC
,以便在检测到触发时在通道
1
、定时器
3
上获得一个采样值。
//
//
ADCSequenceConfigure(ADC_BASE, 3, ADC_TRIGGER_TIMER, 0);
ADCSequenceStepConfigure(ADC_BASE, 3, 0, (ADC_CTL_CH1 | ADC_CTL_IE
| ADC_CTL_END));
//
//
初始化定时器
0
,每
100
µ
s
触发一次
ADC
转换
//
TimerConfigure(TIMER0_BASE, TIMER_CFG_32_BIT_PER);
TimerLoadSet(TIMER0_BASE, TIMER_A, SysCtlClockGet() / 10000);
TimerControlTrigger(TIMER0_BASE, TIMER_A, true);
中断处理程序必须更新采样缓冲区并进行平均计算(见代码段
4.b
)。在每次
ADC
中断时,去掉采样缓冲区中的最后一个元素,缓冲区中剩下的数据移动一个位置。然后,在计算平均值之前将新的转换结果放在采样缓冲区的开始处。中断处理程序中执行的额外计算又一次增加了开销,这一点必须要考虑到。
代码段
4.b ADC
中断处理程序
void
ADCIntHandler(void)
{
//
//
清除中断
//
ADCIntClear(ADC_BASE, 3);
//
//
检查
g_ucOversampleIdx
,确保它的值在范围内
//
if(g_ucOversampleIdx == 16)
{
g_ucOversampleIdx = 0;
}
//
//
从全局总和中减去最早的值
//
g_ulSum -= g_ulSampleBuffer[g_ucOversampleIdx];
//
//
用新的采样值代替最早的值
//
g_ulSampleBuffer[g_ucOversampleIdx] = HWREG(ADC_BASE + ADC_O_SSFIFO3);
//
//
将新的采样值加到总和中
//
g_ulSum += g_ulSampleBuffer[g_ucOversampleIdx];
//
// g_ucOversampleIdx
加
1
//
g_ucOversampleIdx++;
//
//
从采样缓冲区的数据中获得平均值
//
g_ulAverage = g_ulSum >> 4;
//
//
占位符,供
ADC
处理代码
//
}
在启动定时器之前,使能定时器及其中断(见代码段
4.c
)。
代码段
4.c
使能
ADC
和中断
//
//
使能定序器和中断
//
ADCSequenceEnable(ADC_BASE, 3);
ADCIntEnable(ADC_BASE, 3);
IntEnable(INT_ADC3);
//
//
使能定时器并启动转换处理
//
TimerEnable(TIMER0_BASE, TIMER_A);
需考虑的问题
本文档中描述的过采样技术需要额外的代码来执行平均计算,附加中断,和
/
或大部分采样定序器资源,因此它在整个系统性能上有一个显著的影响。在选择最适合应用的技术时,需在增加的中断和庞大的中断处理程序之间进行权衡。
结论
Luminary Micro
的采样定序器结构为过采样技术的实现提供了大量的选项。当与软件平均技术相结合时,该结构能够使系统设计人员有效地在采样频率、系统性能和采样解决方案之间进行权衡。
评论