打印

【STM32F0实验】STM32F051 ADC DMA 错位测试

[复制链接]
12177|25
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
airwill|  楼主 | 2012-8-3 22:43 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 airwill 于 2012-8-9 19:06 编辑

STM32F051 ADC DMA 测试
从 STM32F051 的数据手册看到. 在 ADC 部分: ISR 增加了 OVR 位来捕捉 ADC 结果未来得及 DMA 传输导致结果错位的错位情况. 为了验证这方面的改进情况, 本人针对 STM32F0DISCOVERY 写了个测试程序, 并做了一下测试.
测试方法:
为了捕捉 ADC 错位. 我使用了 PA0~7 8路模拟信号中的若干路 + 内部的 TempSensor(ADC_Channel_16) 和 Vrefint(ADC_Channel_17).
并人为将 PA0~7置输出, 并将电平依次设置为01010101.
下面是测试记录.
其中的数据依次是 DMA 的结果, 为方便记录, 只用转换的12位的最高位做记录, 也就是:0 代表 0x00X(低电平的 AD 结果), 6 代表 0x6AX(Vrefint), 7 代表 0x79X(TempSensor), F 代表 =0xFFX(高电平的 AD 结果)
STM32F051 ADC 的扫描方式有两种, 前向和反向. 对两种分别测试.
正向扫描测试结果:   预计结果:
2路:  67            67
3路:  607           067
4路:  60F7          0F67  
5路:  60F07         0F067   
6路:  60F0F7        0F0F67   
7路:  60F0F07       0F0F067   
8路:  60F0F0F7      0F0F0F67               
9路:  60F0F0F07     0F0F0F067      
10路: 60F0F0F0F7    0F0F0F0F67        
反向扫描测试结果:   预计结果:
2路:  76            76
3路:  067           760
4路:  067F          76F0  
5路:  0670F         760F0   
6路:  067F0F        76F0F0   
7路:  0670F0F       760F0F0   
8路:  067F0F0F      76F0F0F0               
9路:  0670F0F0F     760F0F0F0      
10路: 067F0F0F0F    76F0F0F0F0        

结果: 非常令人郁闷!  三路以上 DMA 都是错误的. 也没有触发 OVR 位.
不认为程序有什么错误. 如果有愿意测试的, 我可以提供源代码和工程.
唉, 还是早点睡觉吧
沙发
airwill|  楼主 | 2012-8-4 21:55 | 只看该作者
本帖最后由 airwill 于 2012-8-5 08:16 编辑

败笔!
调试中发现 最后一路 ADC 的采样时间对 cpu  负担占用差别很大.
仔细看手册, 才发现 ADC 的采样时间控制也精简了.
现在所有的通道规则转换的时候使用同样的转换时间了!
既然这样, 为什么不把 内部几个通道的采样允许时间加快呢?
特别是温度传感器的采样时间还是要 2.2uS 以上, 而数据手册则推荐最小值 17.1uS.
要是和其他通道一起使用, 大大影响性能. 真感觉有的败笔.

使用特权

评论回复
板凳
将臣归邪| | 2012-8-4 22:54 | 只看该作者
发的帖子都不一般啊

使用特权

评论回复
地板
airwill|  楼主 | 2012-8-6 21:19 | 只看该作者
本帖最后由 airwill 于 2012-8-6 21:21 编辑

再次测试:
为了准确测试扫描错误情况. 决定将输入改造一下. 将 PA0 输出0, PA7 输出1.
中间各脚模拟输入. 并用电阻依次连接起来(阻值不尽相同).
先设定为 ADC_ScanDirection_Backward, 关闭内部通道, 外通道数从 2到8 进行测试.
测试均从通道0(PA0) 开始. 采样时间 ADC_SampleTime_1_5Cycles
结果如下:
通道数  OVR    AD 结果
2路:     Y     0x000, 0x488
3路:     Y     0x000, 0x50E, 0x526
4路:     Y     0x000, 0x7E0, 0x55D, 0x565     
5路:     Y     0x000, 0x878, 0x838, 0x58D, 0x547     
6路:     Y     0x000, 0x8AF, 0x8A8, 0x84F, 0x58D, 0x54F     
7路:     Y     0x000, 0xB96, 0x9A4, 0x8F0, 0x872, 0x5A0, 0x553     
8路:     Y     0x000, 0xFFF, 0xD3F, 0x9F4, 0x913, 0x87C, 0x5A8, 0x553     

采样时间 ADC_SampleTime_71_5Cycles
结果如下:
通道数  OVR    AD 结果  
2路:     N     0x000, 0x43E
3路:     N     0x000, 0x4CE, 0x43E
4路:     N     0x000, 0x89A, 0xDFD, 0x43E     
5路:     N     0x000, 0x92B, 0x8A8, 0x4D8, 0x442     
6路:     N     0x000, 0x9D9, 0x938, 0x8AA, 0x4D3, 0x43F     
7路:     N     0x000, 0xE0E, 0x9E9, 0x93F, 0x8A9, 0x4D3, 0x443     
8路:     N     0x000, 0xFFF, 0xE05, 0x9E0, 0x938, 0x8AF, 0x4CB, 0x443     

再加入三路内部通道TempSensor(ADC_Channel_16) Vrefint(ADC_Channel_17) 和 ADC_Channel_Vbat(ADC_Channel_18).
结果如下:
通道数  OVR    AD 结果  
8路:     N     0x000, 0x6A3, 0x78D, 0xFFF, 0xE10, 0x9E2, 0x933, 0x8AF, 0x4CE, 0x43F     
2路:     N     0x000, 0x7FF, 0x6A0, 0x78A, 0x443      
0路:     N     0x788, 0x800, 0x6A0      

改设为: ADC_ScanDirection_Upward, 再测试
0路:     N     0x7FD, 0x78A, 0x6A0      
2路:     N     0x7F7, 0x000, 0x43E, 0x78A, 0x6A0      
5路:     N     0x7FD, 0x000, 0x444, 0x4D3, 0x8AA, 0x938, 0x78F, 0x6A3      
8路:     N     0x7F9, 0x000, 0x43E, 0x4C7, 0x8A5, 0x939, 0x9DE, 0xE07, 0xFFC, 0x78F, 0x6A3      

从测试中可以归纳出来, 不论有没有出现 OVR 位, 都是最后一个通道先转换, 然后剩下的通道依次转换.
唯有在 ADC_SampleTime_1_5Cycles 条件下 3,4 个通道测试有些异常. 也可能是我的电阻有点大. 导致这样采样率的结果偏差太大.
基本结论:
1. 规则转换的通道存在反转问题, 即最后一个通道会最先转换.
2. OVR 没有能够反应和捕捉到过冲情况.
STM32F051RB 的ADC 规则转换 DMA 传输, 还是存在错位问题, 而且好象更严重(所有测试都没有发现正常结果, 我反复测试很多次, 希望能够看到一次正常的结果, 但是很失望, 没有遇到).
以上测试是借用调速器通过在 DMA 中断里设定断点, 跟踪获得的.
为了避免因为调速器的因素. 我在 DMA 中断里对转换的数据进行判断. 并用指示灯指示结果. 遗憾的是也都是错位的数据.

使用特权

评论回复
5
IJK| | 2012-8-7 09:30 | 只看该作者
LZ的测试挺有意思。
但结果似乎有些出人意料。印象里STM32F1的ADC转换 使用DMA进行传输时,正常情况下没什么问题,在有干扰时不少人说有问题。LZ的试验,似乎STM32F0 ADC转换 使用DMA进行传输时,正常情况下都有问题。

使用特权

评论回复
6
hptop| | 2012-8-18 13:50 | 只看该作者
st提供的demo程序ADC_DMA就有错误,出来的顺序乱的

使用特权

评论回复
7
lut1lut| | 2012-9-5 15:55 | 只看该作者

本帖最后由 lut1lut 于 2012-9-5 15:58 编辑

从测试中可以归纳出来, 不论有没有出现 OVR 位, 都是最后一个通道先转换, 然后剩下的通道依次转换.

确实如此,这是可以解释的,也是STM32F0的ADC对于初学者来说最容易犯的错误

因为STM32F0的ADC在使能之前需要先校准,而这个校准数据也是放在ADC_DR中,并且也可以触发DMA。这就是根本原因。如果企图 Ch1 --> Ch2 --> Ch3,那么实际上DMA实际传送的是 calibration data --> CH1 --> CH2 --> CH3 --> CH1 --> CH2 --> CH3 --> CH1 --> CH2 --> ...

所以你看到就好像是最后一个通道先转换了,然后才剩下通道依次转换。

要解决很容易,在启动DMA之前,先把校准数据读走就可以了。

使用特权

评论回复
8
lut1lut| | 2012-9-5 15:59 | 只看该作者
7# hptop

那个Demo确实也没有先处理校准数据,是的转换出来,看似顺序乱了。

使用特权

评论回复
9
airwill|  楼主 | 2012-9-13 06:22 | 只看该作者
上本人的测试代码   
/* Includes */
#include "stm32f0xx.h"
#include "stm32f0xx_rcc.h"
#include "stm32f0xx_adc.h"
#include "stm32f0xx_dma.h"
#include "stm32f0xx_misc.h"
#include "stm32f0xx_gpio.h"

#define EXCHANNELS     8                // 设定 AD 转换的外部通道数
#define INCHANNELS     3                // 设定 AD 转换的内部通道数
#define ADCBACKWARD    0                // 反向扫描设定
#define ADCDMA1_INT_PRIORITY  1
#define GPIO_CLK RCC_AHBPeriph_GPIOC | RCC_AHBPeriph_GPIOB | RCC_AHBPeriph_GPIOA
#define LEDPORT  GPIOC
#define PINLED1  GPIO_Pin_8
#define PINLED2  GPIO_Pin_9

#define ADC1_DR_Address                0x40012440
unsigned short ghADCRes[16];
volatile unsigned int fault, adisr, adcfg;

#define SetFault(x)  fault|=1<<x
RCC_ClocksTypeDef RCC_Clocks;

void DMA1_Channel1_IRQHandler(void) {
        DMA1->IFCR = DMA1_FLAG_TC1;                 // DMA_ClearFlag(DMA1_FLAG_TC1);
    adisr = ADC1->ISR;
        adcfg = ADC1->CFGR1;
        /*if (ADCHANNELS >2) {
                int x=0;
                for (; x<ADCHANNELS -2; x++) {
                        if (x&1) {
                                if (ghADCRes[x] < 0xF00)  SetFault(x);
                        } else if (ghADCRes[x] > 255)  SetFault(x);
                }
        }*/
}
void ADC1_COMP_IRQHandler(void) {
        LEDPORT->BSRR = PINLED2;
}

void ADC1_CH_DMA_Config(void){
        GPIO_InitTypeDef  GPIO_InitStructure;
        NVIC_InitTypeDef    NVIC_InitStructure;
        ADC_InitTypeDef     ADC_InitStructure;
        DMA_InitTypeDef     DMA_InitStructure;

        ADC_DeInit(ADC1);          // ADC1 DeInit  
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);        // ADC1 Periph clock enable      
        RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE);         // DMA1 clock enable

        // Configure the GPIO_LED pin
        GPIO_InitStructure.GPIO_Pin = 0x81;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        //GPIOA->BSRR = (GPIO_Pin_6<<16) | GPIO_Pin_7;
        GPIO_InitStructure.GPIO_Pin = 0x7E;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
        GPIO_Init(GPIOA, &GPIO_InitStructure);
        GPIOA->ODR = 0x80;

        NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPriority = ADCDMA1_INT_PRIORITY;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);        
        NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
        NVIC_Init(&NVIC_InitStructure);        

        DMA_DeInit(DMA1_Channel1);  // DMA1 Channel1 Config  
        DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address;
        DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)ghADCRes;
        DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
        DMA_InitStructure.DMA_BufferSize = EXCHANNELS + INCHANNELS;
        DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
        DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
        DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
        DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
        DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; //DMA_Priority_High;
        DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
        DMA_Init(DMA1_Channel1, &DMA_InitStructure);

        DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);                // 允许接收中断
        DMA_Cmd(DMA1_Channel1, ENABLE);  // DMA1 Channel1 enable  
        ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular);  // ADC DMA 循环模式   
        ADC_DMACmd(ADC1, ENABLE);    // Enable ADC_DMA  

        ADC_StructInit(&ADC_InitStructure);
        // Configure the ADC1 in continous mode withe a resolutuion equal to 12 bits  
        ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
        ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
        ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
        ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
#if ADCBACKWARD >0
        ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Backward;
#else
        ADC_InitStructure.ADC_ScanDirection = ADC_ScanDirection_Upward;
#endif
        ADC_Init(ADC1, &ADC_InitStructure);
        if (EXCHANNELS >0) {
                int x = EXCHANNELS -1;
                do         ADC_ChannelConfig(ADC1, 1 << x, ADC_SampleTime_71_5Cycles);  
                while (--x >= 0);
        }
        // ADC1 温度传感器采样时间 239.5 周期
        if (INCHANNELS >0) {
                int x = INCHANNELS -1;
                ADC_TempSensorCmd(ENABLE);
                ADC_VrefintCmd(ENABLE);
                ADC_VbatCmd(ENABLE);
                do ADC_ChannelConfig(ADC1, ADC_Channel_16 <<x, ADC_SampleTime_71_5Cycles);  
                while (--x >= 0);
        }
        ADC_GetCalibrationFactor(ADC1);  // ADC 校准  
        ADC_ITConfig(ADC1, ADC_IT_OVR, ENABLE);                // 允许接收中断
        ADC_Cmd(ADC1, ENABLE);       // 开启 ADC1
        while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADEN));

        ADC_StartOfConversion(ADC1);  // ADC1 软件启动
}

void LEDInit(void){
        GPIO_InitTypeDef  GPIO_InitStructure;
  // Enable the GPIO_LED Clock *
        RCC_AHBPeriphClockCmd(GPIO_CLK, ENABLE);

        // Configure the GPIO_LED pin
        GPIO_InitStructure.GPIO_Pin = PINLED1 | PINLED2;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
        GPIO_Init(LEDPORT, &GPIO_InitStructure);
        LEDPORT->BRR = PINLED1 | PINLED2;
}

int main(void) {
        RCC_GetClocksFreq(&RCC_Clocks);
        LEDInit();
        ADC1_CH_DMA_Config();
        while (1) {
                if (fault) LEDPORT->BSRR = PINLED1;
        }
}

使用特权

评论回复
10
airwill|  楼主 | 2012-9-13 06:55 | 只看该作者
对于 7 楼提到的问题. 确实可以解释这个问题.
嗯, 先看数据手册:
12.4.1  校准 (ADCAL)
ADC 本身具有校准功能,在校准期间,ADC 计算一个用于 ADC 校准的7位校准因子 (ADC断电后丢失)。在ADC 校准期间和校准未完成前,应用不能使用 ADC 模块。
在 A/D 转换前应执行校准操作,校准用于消除各芯片 AD 转换的偏移误差。
校准是由软件设置 ADCAL=1 来实现初始化。其只能在 ADC 禁用 (ADEN=0) 时完成初始化。在校准期间,ADCAL 位必须保持为1。当校准完成后,ADCAL 被硬件清0。校准完成后,可从 ADC_DR 寄存器的 6:0 位读出校准因子。
当 ADC 禁用时 (ADEN=0) 校准因子保持原值。然而,若 ADC 长时间禁用,建议在启用 ADC 之前重新做一次 ADC 校准操作。
校准因子在 ADC 每次断电后丢失 ( 例如当器件从待机模式或由 VBAT 供电模式)。

12.6.5  用DMA管理转换数据
因为所有通道的转换结果数据存放到一个单一的数据寄存器中,故当转换通道超过 1 个时用DMA 方式会更有效。这样可以避免丢失存在 ADC_DR 寄存器中的转换结果。
当 DMA 模式开启时 (ADC_CFGR1 寄存器中的 DMAEN =1), 每次转换结束时都会产生一个DMA 请求。这样就允许把在 ADC_DR 寄存器中的转换数据传送到软件指定的目标地址中。

手册上提到了校准因子的事情, 也没有说这个因子是干啥用的, 也没有强制要求读走这个数据, 另外下面这段关于 DMA  的说法. 是说 DMA 要等 ADC 结束后才发出请求, 照这么说, 起动 DMA 时(肯定在发出 DMA 请求以后) 这个校准因子早被后来的数据冲掉了.
找个时间再作一次测试.

使用特权

评论回复
11
lollipooop| | 2012-9-13 08:40 | 只看该作者
我用下来没发现任何m0的adc dma错位问题,楼主再好好检查下自己的程序吧

使用特权

评论回复
12
yinyangdianzi| | 2012-9-13 12:45 | 只看该作者
MARKy一下,,不错的帖子

使用特权

评论回复
13
airwill|  楼主 | 2012-9-13 20:20 | 只看该作者
本帖最后由 airwill 于 2012-9-13 20:23 编辑

公布再次测试结果.
我在 ADC_StartOfConversion(ADC1);  // ADC1 软件启动
之前插入了
        ghADCRes[15] = ADC1->DR; // 读取校准值
再次执行, 问题依旧, 就不详细描述了.
读到的校准值是 0x44, 这个数值是不是挺大的?

使用特权

评论回复
14
lut1lut| | 2012-10-24 14:07 | 只看该作者
7位校准值存放在ADC_DR,可供用户读取。虽然ADC校准和转换的数据都放在ADC_DR中,但是校准结束的标志是ADCAL被硬件复位;而转换结束的标志是EOC被硬件置位。

如何flush掉ADC_DR中的校准数据,比如DMA不想传第一次的校准因子?

我用中断方式处理了一下,在ADRDY引起的中断里读取ADC DR(ADRDY中断表示使能ADC,并已经稳定了。离校准结束已经有一段时间了);然后在EOC中断里继续读取ADC DR来把数据存储到SRAM中。就发现分开了。

后来在DMA方式下,设置DMA CNT=1,即使不软件触发ADC转换,还是会有一次DMA的传输,而且就是把calibration value传到SRAM中了。【可见】ADC DR中的calibration factor也会触发DMA请求。试验表明即使没有enable ADC都可以完成本次DMA传输。

使用特权

评论回复
15
wxlhonker| | 2012-11-26 13:42 | 只看该作者
费了半天事终于和同事一块把这个问题搞定了:
ADC配置部分,将  
ADC_DMACmd(ADC1, ENABLE);  
放到
ADC_StartOfConversion(ADC1);
这句的后面,这样出来的结果就是一一对应的了!

使用特权

评论回复
评分
参与人数 1威望 +1 收起 理由
stevenzhongan + 1 很给力!
16
zxm19820916| | 2013-2-25 15:03 | 只看该作者
不是吧,为什么呢?

使用特权

评论回复
17
712abc| | 2013-2-25 15:45 | 只看该作者
wxlhonker 发表于 2012-11-26 13:42
费了半天事终于和同事一块把这个问题搞定了:
ADC配置部分,将  
ADC_DMACmd(ADC1, ENABLE);  

我也一直被这问题郁闷着

15楼的确定问题解决了?只是第一次调正好顺序而已吧

试试把AD停下来,再启动采集,数据还是会随机错乱的,

DMA一旦被打开了之后,就不能停

使用特权

评论回复
18
wxlhonker| | 2013-2-26 12:25 | 只看该作者
我这现在多通道就是这么用的,没再发现问题啊

使用特权

评论回复
19
zxm19820916| | 2013-4-22 19:29 | 只看该作者
不行啊,各位的方法都试了,还是会错位。

使用特权

评论回复
20
airwill|  楼主 | 2013-4-24 07:21 | 只看该作者
今天我又简单测试了一下:
将这一行代码
        ADC_DMACmd(ADC1, ENABLE);    // Enable ADC_DMA  
放到了 ADC_GetCalibrationFactor 值的读取之后.
错位的确消失
没时间了, 容后再详细测试.

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:欢迎进入 TI 模拟技术论坛!

556

主题

17724

帖子

884

粉丝