Saturday, 31 December 2016

Простой способ измерения напряжения питания на ATmega328P.

Одно из обязательных требований для устройств работающих автономно (на батарейках к примеру), является контроль источника питания. В различных  датчиках эта информация включается в поток телеметрии и пользователь может оценить требует ли устройство обслуживания  или нет. Очень часто, в целях экономии энергии источника питания, (да и вообще в целях экономии) в простых датчиках не используют преобразователей напряжения , подключая схему непосредственно к источнику (батарейке, аккумулятору). Этому способствует диапазон питающих напряжений у современной элементной базы. Например, для ATmega328P   - 1,8...5,5В; для SI7021(датчик влажности и температуры) - 1,9...3,6В; для передатчика RFM85W-433  - 2,1...5В. Таким образом измерив напряжения питания мы можем непосредственно оценить состояние батарейки, аккумулятора. Как же это сделать?

 Для примера можно использовать готовые платы Arduino - Nano, Uno, Pro Mini и тд любая с ATmega328P на борту.

Идея измерения заключается в использовании входного сигнала АЦП ATmega328P как опорного , а опорное напряжение превращается в объект измерения. Рассмотрим схему из даташита ATmega328P:

ATmega328P ADC
 Как видим имеется 3 источника опорного напряжения AVCC (напряжение питания), INTERNAL 1,1V, AREF(внешнее опорное напряжение)  и 11 коммутируемых входа среди которых есть вход BANDGAP REFERENCE  напряжение на котором равно 1100 мВ (типовое).

Стандартный путь - привести напряжение питания к 1100 мВ внутреннего опорного источника (резистивным делителем) и подключить его к одному из входов ADC0...ADC7.

Пойдем же другим путем. На вход АЦП подадим BANDGAP REFERENCE (1100мВ), опорное напряжение установим AVCC (напряжение питания). Значение АЦП определяется как

                                                       ADC=(Vвх*1024)/Vоп, где

 Vвх - напряжение на входе АЦП, мВ
  Vоп - опорное напряжение, мВ
ADC - результат преобразования АЦП 0-1023


Перепишем это выражение для нашего случая:

                                                        ADC=(Vbg*1024)/Vavcc, где

 Vbg - напряжение BANDGAP REFERENCE, 1100мВ
 Vavcc - напряжение питания, мВ

Тогда напряжение питания определится как:

                                                       Vavcc =(Vbg*1024)/ADC=(1100*1024)/ADC, мВ


Вот и все. И никаких дополнительных элементов. На языке С  (Atmel Studio 7)  и для платы Arduino Nano это может выглядеть так:

#define F_CPU 16000000UL

#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <util/delay.h>


#define UART0_U2X 1

enum{IDLE,ADC_NOISE_REDUCTION,POWER_DOWN,POWER_SAVE,STANDBY=6,EXTERNAL_STANDBY};
#define SET_SLEEP_MODE(A)  SMCR&=(0x01);SMCR|=(A<<1)

void InitADC(void){

 // На вход АЦП подаем BANDGAP REFERENCE (1100мВ), опорное напряжение установим AVCC (напряжение питания).
 ADMUX=(1<<REFS0)|(1<<MUX3)|(1<<MUX2)|(1<<MUX1);

 //задаем тактовую частоту АЦП (62 или 125 кГц зависит от частоты задающего генератора //ATmega328P )
 ADCSRA|=(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);

}

void UartUnoInit(uint32_t baud){ //Parity Mode Disable, 1 Stop Bit, Character Size=8 bit, Baudrate=9600
 uint16_t cbrr=(uint16_t)(F_CPU/(8*(2-UART0_U2X)*baud)-1);

 UBRR0H = (uint8_t)(cbrr>>8);
 UBRR0L = (uint8_t)cbrr;
 
 _delay_ms(100);
 if(UCSR0A&(1<<RXC0)){
  (void)UDR0;
 }
 
 /* Enable receiver and transmitter */
 UCSR0B|= (1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0);
 
 //Double the USART Transmission Speed
 if(UART0_U2X==1){
  UCSR0A|=(1<<U2X0);
 }
}


void UartUnoTransmitByte(uint8_t data)
{
 /* Wait for empty transmit buffer */
 while ( !( UCSR0A & (1<<UDRE0)));
 /* Put data into buffer, sends the data */
 UDR0 = data;
}

void UartUnoTransmitData(uint8_t *data){
 while((*data)!='\0'){
  UartUnoTransmitByte(*data);
  data++;
 }
}

void UartUnoWaitEndTransmitData(void){
    while( !( UCSR0A & (1<<TXC0)));
    UCSR0A|=(1<<TXC0); 
}


void InitWDT(void){
    WDTCSR |= (1<<WDCE) | (1<<WDE);
    WDTCSR=(1<<WDIE)|(1<<WDP3)|(1<<WDP0); 
}

ISR(WDT_vect){            //awake every 8 sec
 uint16_t ubt;
 char string[32]={0};
   
 ADCSRA|=(1<<ADEN);
 _delay_ms(1);
 ADCSRA|=(1<<ADSC);
 while((ADCSRA&(1<<ADSC))!=0);
 ubt=(1100UL*1024)/ADC;
 ADCSRA&=(~(1<<ADEN));
 
 sprintf(string,"AVCC=%5umV\r\n",ubt);
 UartUnoTransmitData((uint8_t *)string);
        UartUnoWaitEndTransmitData(); 
}


int main(void)
{
 InitADC();
 UartUnoInit(115200);
 InitWDT();
 SET_SLEEP_MODE(POWER_DOWN);

 sei();
 sleep_enable();    
 while (1) 
    {
      sleep_cpu();
    }
}

Результат работы прораммы можно увидеть на любом мониторе последовательного порта (например Hercules).

No comments:

Post a Comment