例1 利用驱动库函数的8x过采样
代码段1.a ADC配置-驱动库函数
//
// 初始化ADC,使用定序器0对通道1进行8x过采样
// 定序器将被其中一个通用定时器触发
//
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的采样定序器结构为过采样技术的实现提供了大量的选项。当与软件平均技术相结合时,该结构能够使系统设计人员有效地在采样频率、系统性能和采样解决方案之间进行权衡。