Для примера можно использовать готовые платы Arduino - Nano, Uno, Pro Mini и тд любая с ATmega328P на борту.
Идея измерения заключается в использовании входного сигнала АЦП ATmega328P как опорного , а опорное напряжение превращается в объект измерения. Рассмотрим схему из даташита ATmega328P:
Как видим имеется 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).