ARM7MODA: Исследование встроенного АЦП на AT91SAM7S

ARM7MODA: Исследование встроенного АЦП на AT91SAM7S.

Одно время я искал такую информацию в интернете, но так и не нашел (возможно плохо искал), так что прийдется написать самому. Думаю Вам будет интересно.

Как Вы уже поняли речь пойдет о реалиях внутреннего, тоесть встроенного в AT91SAM7S 10 битного АЦП.

Вспомнил что когдато мне рассказывали старые друзья эмбиддеры: якобы, у этого семейства микроконтроллеров погрешность АЦП в 4-5 бит младшего разряда, и при условии что если не разводить плату специально под работу с АЦП и без специального генератора опорного напряжения.

То есть, достоверных данных остается на 5-6 бит. Как вы понимаете, звучит пугающе. И как минимум нуждается в проверке.

Итак, мы можем это проверить, что я и сделаю при помощи ARM7MODA

Я сделал некоторый тест, "набросал" простенькую программу для работы с АЦП и провел простые измерение, на своей тестовой плате (не предназначеной для тестов такого рода).

На плате нет внешнего опрного источника, нет источника напряжений, а просто стоят подстроечные резисторы в цепи питания, с среднего вывода которых и снимается значение напряжения, как с делителя, и подается на вход АЦП, и также подается на Vref. Для того что бы уменьшить уровень пульсаций переходящих от пульсаций питания, на средний вывод, припаяны конденсаторы 47мкФ, таким образом создан простой фильтр, подавляющий ВЧ состовляющюю пульсаций питания.

Также у этого микроконтроллера я не нашел возможности использовать/подключить Vref к внутреннему питанию, только внешний источник опорного напряжения. Но как я уже писал выше у меня в место источника опорного напряжения  используется RC цепь. И это пожалуй еще более интересно, так как часто именного такого варианта подключения и достаточно, если не нужны сверх точные измерения.

В общем, давайте посмотрим на то что получилось.

 

Нужно заметить что для таких измерений были неточности, а именно:

— внешнее опорное напряжение через переменный резистор, с питания.

— питание от обычного линейного стабилизитора.

— пульсации по питанию 40-50мВ.

— пульсации по сигналу опорного напряжения 10-20мВ.

— пульсации по измеряемому сигналу 10-20мВ.

Итак, теперь настало время для тестов…

 

Чтобы сразу было ясно откуда берутся пульсации по питанияю после линейного стабилизатора 10-20мВ, это пульсации из за присутсвующих на плате модуля светодиодов, и на эти пины с светодиодами выводится информация об измерениях, которые соответсвено и мигают и создают польсации, которые снова замеряются, и снова выводятся, и  т д…  Обратим на это внимание, и вернемся к этому в конце статьи. 

Для тестов, приведу пример который я быстренько набросал, на базе другой программы. В этой тестовой прорамме, преобразованное число выводится на 10 первых выводов, и через светодиоды, их можно наблюдать визуально.

Программа:

/******************************************************************************/
/* BLINKY.C: LED Flasher                                                      */
/******************************************************************************/
/* This file is part of the uVision/ARM development tools.                    */
/* Copyright (c) 2005-2006 Keil Software. All rights reserved.                */
/* This software may only be used under the terms of a valid, current,        */
/* end user licence from KEIL for a compatible version of KEIL software       */
/* development tools. Nothing else gives you the right to use this software.  */
/******************************************************************************/
                  
/* AT91SAMT7S64 definitions  */
#include <AT91SAM7S64.H>  
//#include "AT91SAM7S64.h"

#include "Board.h"

#define  PORT_MASK(nBit) (1 << nBit)
 
#define  TRUE                   1
#define  FALSE                  0

#define  PORT_LED0  11
#define  PORT_LED1  27
#define  PORT_LED2  29
#define  PORT_LED3  31
#define  CHANNEL    7

//#define  ENABLE_PWM
#define  ENABLE_ADC

typedef  unsigned char       BOOL;
typedef  unsigned char       BYTE;
 
// ------------------------------------------------------------
// Init() initializes the Flash, the watchdog timer, the Power
// Management Controller (PMC), and the PIO port.
// See the steps outlined in the section titled "Programming
// Sequence" of the Atmel AT91SAM7S data sheet (Section 26.7 )
// to program the PMC.
// ------------------------------------------------------------
void Init(void)
{
    AT91PS_PMC pPMC;
    AT91PS_PIO pPIO;
    
    // --- set flash memory parameters ---
    // MCK will be set to 47.92 MHz (approximately 48MHz) below
    // so set FMCN=48.  Add minimal wait states since we are above 40 MHz.
     AT91C_BASE_MC->MC_FMR = ( (AT91C_MC_FMCN) & (48 << 16) ) |
                                AT91C_MC_FWS_1FWS ;
    
    // --- disable the watchdog timer ---
     AT91C_BASE_WDTC->WDTC_WDMR = AT91C_WDTC_WDDIS;
    
    // --- program the Power Management Controller (PMC) ---
    // steps are as given in Section 26.7 "Programming Sequence"
    
     pPMC = AT91C_BASE_PMC;
    
    // 1. start the "main oscillator" (Section 25.3.3)
    // slow clock frequency = 32.768 KHz
    // slow clock period = 30.5176 microseconds
    // Start up time = 8*7*30.5176 = 1708.984375 microseconds or 1.709 milliseconds
     pPMC->PMC_MOR = ( AT91C_CKGR_OSCOUNT & ( 0x07 << 8 ) ) |
                       AT91C_CKGR_MOSCEN;    
    // 2. check the main oscillator frequency (optional)
    // skipped
    
    // wait the master clock to settle
     while( !(pPMC->PMC_SR & AT91C_PMC_MOSCS) );
    
    // 3. set the PLL and the divider
    // main clock or (crystal) frequency = 18.432MHz)
    // use: USBDIV=1, DIV=5, MUL=25, and OUT=0, which yields,
    // (18.432/5)*(25+1)=95,8464MHz
    // for a LOCK time of 1000 microseconds (1 milliseconds),
    // set PLLCOUNT to 1000/30.5176=33
     pPMC->PMC_PLLR = ( (AT91C_CKGR_DIV & 5) |
                        (AT91C_CKGR_PLLCOUNT & (33 << 8)) |
                        (AT91C_CKGR_MUL & (25 << 16)) |
                        (AT91C_CKGR_USBDIV & (1<< 28)) );
    
    // wait for the PLL to stabalize
     while ( !(pPMC->PMC_SR & AT91C_PMC_LOCK) );
    // wait for the "master clock ready" flag
     while ( !(pPMC->PMC_SR & AT91C_PMC_MCKRDY) );
    
    // 4. select the master clock source and the prescalar
    // source    = the PLL clock
    // prescalar = 2
     pPMC->PMC_MCKR = AT91C_PMC_CSS_PLL_CLK |
    //                  AT91C_PMC_PRES_CLK_2;
                      AT91C_PMC_PRES_CLK;
     while( !(pPMC->PMC_SR & AT91C_PMC_MCKRDY) );
    
    // 5. select programmable clocks
    // (programmable clocks are not used in this application)
    
#ifdef ENABLE_ADC
    {
        unsigned int u;
         // 6. enable the peripheral clock used by PIOA and ADC
         pPMC->PMC_PCER|=(1<<AT91C_ID_PIOA)|(1<<AT91C_ID_ADC);
       
        /* // initializ the port defined as PORT_LED as an output port.
        //configure the PIO register used by the LED
        // pPIO=AT91C_BASE_PIOA;
         u = PORT_MASK(PORT_LED3) |
             PORT_MASK(PORT_LED2) |
             PORT_MASK(PORT_LED1) |
             PORT_MASK(PORT_LED0);
         pPIO->PIO_PER  = u;   // enable LED ports
         pPIO->PIO_OER  = u;   // configure LED ports as an outputs
         pPIO->PIO_SODR = u;   // setting the output ports turn off the LEDs */
    }
#endif /*ENABLE_ADC*/

    {
    AT91PS_SYS   regs = AT91C_BASE_SYS;
    unsigned int data;
    /* Init I/O Lines
    ----------------------------------------------
        D    N    Discription of connect to pins
    ----------------------------------------------
        0    48    led0    lcd_d0
        1    47    led1    lcd_d1
        2    44    led2    lcd_d2
        3    43            lcd_d3
        4    36            lcd_d4
        5    35            lcd_d5
        6    34            lcd_d6
        7    32            lcd_d7
        8    31    
        9    30    DRXD
        10    29    DTXD
        11    28
        12    27
        13    22
        14    21
        15    20            lcd_E
        16    19    k0
        17    9    k1
        18    10    k2
        19    13    k3
        20    16            lcd_RS
        21    11            
        22    14
        23    15
        24    23
        25    25
        26    26
        27    37
        28    38
        29    41
        30    42
        31    52
    ----------------------------------------------
    0x15ABCDEF  
        0001 0101 1010 1011 1100 1101 1110 1111
    HEX    1    5    A    B    C    D    E    F
    ----------------------------------------------*/

    /* PIO enable */
    // PIO Enable/Disable Register
    data=0x0a00003ff;
    regs->PIOA_PER  = data;    regs->PIOA_PDR  =~data; // pck
    
    /* Output Enable */
    // Output Enable/Disable Register
    data=0x000003ff;
    regs->PIOA_OER  = data;    regs->PIOA_ODR  =~data;
    
    /* Glitch */
    // Input Filter Enable/Disable Register
    data=0x000F0000;
    regs->PIOA_IFER = data;    regs->PIOA_IFDR =~data;
    
    // Set/Clear Output Data Register
    data=0x00000000;
    regs->PIOA_SODR = data;    regs->PIOA_CODR = data;
    
    // Interrupt Enable/Disable Register
    data=0x00000000;
    regs->PIOA_IER  = data;    regs->PIOA_IDR  =~data;
    
    // Multi-driver Enable/Disable Register
    data=0x00000000;
    regs->PIOA_MDER = data;    regs->PIOA_MDDR =~data;
    
    /* Pull-up  */
    // Pull-up Enable/Disable Register
    data=0x000003ff;   
    regs->PIOA_PPUER= data;    regs->PIOA_PPUDR=~data; // pck
    //data=0x00000000;    
    //regs->PIOA_ASR  =data;    regs->PIOA_BSR  =~data;
    
    /* Out stat wr en*/
    // Output Write Enable/Disable Register
    data=0x000003ff;    
    regs->PIOA_OWER = data;    regs->PIOA_OWDR =~data; // pck
    /*---------------------------------------------------------------*/
    }
}
// ------------------------------------------------------------
BOOL GetPort(BYTE bPort)
{
    AT91PS_PIO pPio;
    
    pPio = AT91C_BASE_PIOA;
    return( ( pPio->PIO_PDSR & PORT_MASK(bPort) )?TRUE:FALSE );  // return BOOL
}
// ------------------------------------------------------------
void SetPort(BYTE bPort, BOOL bState)
{
    AT91PS_PIO pPio;
    
    pPio = AT91C_BASE_PIOA;
    if (bState) pPio->PIO_SODR = PORT_MASK(bPort);
       else pPio->PIO_CODR = PORT_MASK(bPort);
}
// ------------------------------------------------------------
void ADCOutData( unsigned int uiPort )
{
    AT91PS_PIO pPio;

    pPio   = AT91C_BASE_PIOA;
    uiPort = uiPort & 0x3ff;

    pPio->PIO_ODSR &= 0xfffffc00;
    pPio->PIO_ODSR |= uiPort;
}
// ------------------------------------------------------------
int main(void)
{

#ifdef ENABLE_ADC

#endif /*ENABLE_ADC*/
    AT91PS_ADC   pADC;
    volatile int n, nADC;

    // perform initialization tasks
     Init();
    
    // initialize the ADC
    // track-an-hold : 600 nS   --> SHTIM   =  2
    // start up time :  20 uS   --> STARTUP = 12
    // 10-bit adc clk:   5 MHz  --> PRESCAL =  3
     pADC=AT91C_BASE_ADC;
     pADC->ADC_MR   = 0x020C0300;             // SHTIM:STARTUP:PRESCAL:00
     pADC->ADC_CHER = (1<<CHANNEL);           // enable adc channel
     pADC->ADC_CR   = AT91C_ADC_START /*2*/;  // start ADC
    // endless loop blinks the LED
     nADC=0;
    
     while(TRUE)
     {
       for(n=0; n<0x10000; n++);        // delay

       if((pADC->ADC_SR)&(1<<CHANNEL))  // check if adc channel is ready
       {
         nADC = pADC->ADC_LCDR;           // read conversion value
         pADC->ADC_CR = AT91C_ADC_START /*2*/;                // start ADC
       }
       //LedMeter((nADC>>6)&0xFF);
       ADCOutData (~nADC>>2);
     }
}
 
// -------------------------------------------------------------
// end of module main
// -------------------------------------------------------------

Итак в этой программе, получаются "пригающие" значения при выводе через смещение в 2 бит, через функцию

ADCOutData (~nADC>>2);

Дребезг погрешности в 2 младших битах,  из 8 бит, тоесть достоверных 6 бит, 4 бита-погрешности.

Как Вы помните, разрядность у встроенного АЦП 10 бит. Теперь интересно разобраться, почему же такое поведение у АЦП? Разумеется не забывает что тест "туманный".

Ранее уже было сказано что пульсации по источнику опорного сигнала и измеряемого 10-20мВ.

3,3В/10бит = 3,3/1024 = 3,222мВ/бит.

10мВ/(3,222мВ/бит)=3бит

20мВ/(3,222мВ/бит)=6бит

То есть, измерянные показания значений, это измеряное напряжение пульсаций поступающих на вход АЦП.

Да, определенно 1-2 бита могут быть собственными погрешостями, об этом даже сказано в даташите.

Если посмотреть в даташит, или его русский перевод http://www.gaw.ru/html.cgi/txt/doc/micros/arm/arh_sam7s/37.htm , то можно увидеть более детально:

37.7 Характеристики АЦП

  •  . . .

Таблица 37.19. Передаточные характеристики

Параметр Мин. Тип. Макс. Ед.изм.
Разрешающая способность   10   бит
Интегральная нелинейность     ±3 мл.разр.
Дифференциальная нелинейность     ±2 мл.разр.
Погрешность смещения     ±2 мл.разр.
Погрешность усиления     ±2

мл.разр.

  • . . .

Итак, мы четко видим такое поведение может иметь место:

Интегральная нелинейность  + Дифференциальная нелинейность + Погрешность смещения + Погрешность усиления

А также нужно учитывать реальное измерение шума, в АЦП из-за пульсаций питания и наводок на вход АЦП. Как вы помните в начале я сказал что данные что мы меряем выводятся на порт где сидят светодиоды, из за мигания которых появляются наводки по питанию 10-20мВ.

 

Выводы:

— не используете порты с светодиодами, это улучшит показания измерений.

— используйте микросхему источник опорного напряжения, для Vref.

 

Добавить комментарий