Аналого-цифровой преобразователь (ADC) микроконтроллера ATtiny45 содержит 4 однополярных канала и 2 дифференциальных канала с усилением на 1 и 20. Все каналы имеют разрешение 10 бит. Так же имеется внутренний ИОН который имеет три опорных напряжения: напряжение питания микроконтроллера, 2.56 В и 1.1 В.
Выбор однополярного или дифференциального входа осуществляется при помощи регистра ADMUX путем записи битов MUX3…MUX0. Так же при помощи этого регистра можно выбрать источник опорного напряжения (биты REFS2 REFS1 REFS0).
Выбор входа
Выбор источника опорного напряжения
REFS2 | REFS1 | REFS0 | Источник опорного напряжения |
X | 0 | 0 | Vcc — источник питания микроконтроллера |
X | 0 | 1 | Внешний источник опорного напряжения, для подачи опорного напряжения используется вход PB0 |
0 | 1 | 1 | Внутренний источник опорного напряжения 1,1 В |
0 | 1 | 1 | N/A |
1 | 1 | 0 | Внутренний источник опорного напряжения 2.56 В |
1 | 1 | 1 | Внутренний источник опорного напряжения 2.56 В с использованием фильтрующего конденсатора подключенного к PB0 |
Регистр управления ADCSRA определяет режим работы АЦП, определяет режим работы предделителя тактовой частоты.
- ADEN — включает АЦП
- ADSC — запуск преобразования
- ADATE — позволяет запускать преобразование по прерыванию от периферийных устройств микроконтроллера
- ADIF — флаг прерывания от АЦП
- ADPS2 — ADPS0 — выбирают режим работы предделителя тактовой частоты:
- 0 0 0 — CLK/2
- 0 0 1 — CLK/2
- 0 1 0 — CLK/4
- 0 1 1 — CLK/8
- 1 0 0 — CLK/16
- 1 0 1 — CLK/32
- 1 1 0 — CLK/64
- 1 1 1 — CLK/128
Результаты преобразования АЦП записываются в два регистра ADCL и ADCH, так как АЦП микроконтроллера 10 бит, то младшие биты записываются в регистр ADCL, а 2 старших в регистр ADCH.
При опросе регистров ADCL и ADCH, обязательным условием служит то что, первым считываться должен регистр ADCL, а потом только ADCH. Так же стоит обратить внимание на бит ADLAR в регистре ADMUX, который меняет расположения чтения битов регистров ADCL и ADCH.
ADLAR = 0
ADLAR = 1
Ниже показано несколько пример использования АЦП микроконтроллера ATtiny45.
Перед прошивкой микроконтроллера рекомендую ознакомится со статьей — http://rcl-radio.ru/?p=129389 (общие сведения о микроконтроллере ATtiny45, прошивка при помощи Arduino IDE).
Для вывода результатов измерения в примерах будет использован модуль TM1637, который представляет собой 4-х разрядный семисегментный дисплей на базе драйвера TM1637.
Одиночный вход, внутреннее опорное — напряжение питания микроконтроллера
#define CLK PB2 // TM1637 #define DIO PB1 // TM1637 // PB4 INPUT void setup(){ cli(); ADMUX |= (1 << MUX1); ADCSRA |= (1<<ADEN)|(1 << ADATE)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); sei(); } void loop(){ long u=0; for(int n=0;n<10;n++){ while((ADCSRA & (1 << ADIF)) == 0); int u_data = (ADCL|ADCH << 8); u += u_data;delay(100); } u=u/10; tm_print(500*u/1023,1,5); } void tm_dec(byte dig){ for(byte i = 0; i < 8; i++) { DDRB |= (1 << CLK);del(); if (dig & 0x01) DDRB &= ~(1 << DIO); else DDRB |= (1 << DIO);del(); DDRB &= ~(1 << CLK);del(); dig = dig >> 1; } DDRB |= (1 << CLK); DDRB &= ~(1 << DIO);del(); DDRB &= ~(1 << CLK);del(); if (((PINB >> DIO) & 1) == 0) DDRB |= (1 << DIO);del(); DDRB |= (1 << CLK);del(); } void tm_stop(){ DDRB |= (1 << DIO);del(); DDRB &= ~(1 << CLK);del(); DDRB &= ~(1 << DIO);del(); } void tm_start(){ DDRB |= (1 << DIO);del(); } void tm_print(int t, bool pd_t, byte br){ tm_start();tm_dec(0b10001000 + br); tm_dec(0x40);tm_stop();tm_start(); int data0 =10; int data1 = t / 100 % 10; int data2 = t / 10 % 10; int data3 = t % 10; for(byte n = 0; n < 4; n++){ int data; switch(n){ case 0: data = data0;break; case 1: data = data1;break; case 2: data = data2;break; case 3: data = data3;break; } switch(data){ // XGFEDCBA case 0: data = 0b00111111;break; // 0 case 1: data = 0b00000110;break; // 1 case 2: data = 0b01011011;break; // 2 case 3: data = 0b01001111;break; // 3 case 4: data = 0b01100110;break; // 4 case 5: data = 0b01101101;break; // 5 case 6: data = 0b01111101;break; // 6 case 7: data = 0b00000111;break; // 7 case 8: data = 0b01111111;break; // 8 case 9: data = 0b01101111;break; // 9 case 10: data = 0b00000000;break; // пусто } if(n == 0){data0 = data;} if(n == 1){data1 = data;} if(n == 2){data2 = data;} if(n == 3){data3 = data;} } if(pd_t==1){data1 = data1+0b10000000;} tm_dec(0xC0);tm_dec(data0);tm_dec(data1);tm_dec(data2);tm_dec(data3);tm_stop(); } void del(){delay(1);}
На вход PB4 подается напряжение от 0 до 5В, для повышения стабильности измерений АЦП производит 10 измерений через каждые 100 мс, а далее на индикатор выводится среднее значение десяти измерений.
Одиночный вход, внутреннее опорное — 2,56 В
#define CLK PB2 // TM1637 #define DIO PB1 // TM1637 // PB4 INPUT void setup(){ cli(); ADMUX |= (1 << MUX1)|(1 << REFS2)|(1 << REFS1); ADCSRA |= (1<<ADEN)|(1 << ADATE)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); sei(); } void loop(){ long u=0; for(int n=0;n<10;n++){ while((ADCSRA & (1 << ADIF)) == 0); int u_data = (ADCL|ADCH << 8); u += u_data;delay(100); } u=u/10; tm_print(256*u/1023,1,5); } void tm_dec(byte dig){ for(byte i = 0; i < 8; i++) { DDRB |= (1 << CLK);del(); if (dig & 0x01) DDRB &= ~(1 << DIO); else DDRB |= (1 << DIO);del(); DDRB &= ~(1 << CLK);del(); dig = dig >> 1; } DDRB |= (1 << CLK); DDRB &= ~(1 << DIO);del(); DDRB &= ~(1 << CLK);del(); if (((PINB >> DIO) & 1) == 0) DDRB |= (1 << DIO);del(); DDRB |= (1 << CLK);del(); } void tm_stop(){ DDRB |= (1 << DIO);del(); DDRB &= ~(1 << CLK);del(); DDRB &= ~(1 << DIO);del(); } void tm_start(){ DDRB |= (1 << DIO);del(); } void tm_print(int t, bool pd_t, byte br){ tm_start();tm_dec(0b10001000 + br); tm_dec(0x40);tm_stop();tm_start(); int data0 =10; int data1 = t / 100 % 10; int data2 = t / 10 % 10; int data3 = t % 10; for(byte n = 0; n < 4; n++){ int data; switch(n){ case 0: data = data0;break; case 1: data = data1;break; case 2: data = data2;break; case 3: data = data3;break; } switch(data){ // XGFEDCBA case 0: data = 0b00111111;break; // 0 case 1: data = 0b00000110;break; // 1 case 2: data = 0b01011011;break; // 2 case 3: data = 0b01001111;break; // 3 case 4: data = 0b01100110;break; // 4 case 5: data = 0b01101101;break; // 5 case 6: data = 0b01111101;break; // 6 case 7: data = 0b00000111;break; // 7 case 8: data = 0b01111111;break; // 8 case 9: data = 0b01101111;break; // 9 case 10: data = 0b00000000;break; // пусто } if(n == 0){data0 = data;} if(n == 1){data1 = data;} if(n == 2){data2 = data;} if(n == 3){data3 = data;} } if(pd_t==1){data1 = data1+0b10000000;} tm_dec(0xC0);tm_dec(data0);tm_dec(data1);tm_dec(data2);tm_dec(data3);tm_stop(); } void del(){delay(1);}
Одиночный вход, внутреннее опорное — 1,10 В
#define CLK PB2 // TM1637 #define DIO PB1 // TM1637 // PB4 INPUT void setup(){ cli(); ADMUX |= (1 << MUX1)|(1 << REFS1); ADCSRA |= (1<<ADEN)|(1 << ADATE)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); sei(); } void loop(){ long u=0; for(int n=0;n<10;n++){ while((ADCSRA & (1 << ADIF)) == 0); int u_data = (ADCL|ADCH << 8); u += u_data;delay(100); } u=u/10; tm_print(1100*u/1023,3,5); } void tm_dec(byte dig){ for(byte i = 0; i < 8; i++) { DDRB |= (1 << CLK);del(); if (dig & 0x01) DDRB &= ~(1 << DIO); else DDRB |= (1 << DIO);del(); DDRB &= ~(1 << CLK);del(); dig = dig >> 1; } DDRB |= (1 << CLK); DDRB &= ~(1 << DIO);del(); DDRB &= ~(1 << CLK);del(); if (((PINB >> DIO) & 1) == 0) DDRB |= (1 << DIO);del(); DDRB |= (1 << CLK);del(); } void tm_stop(){ DDRB |= (1 << DIO);del(); DDRB &= ~(1 << CLK);del(); DDRB &= ~(1 << DIO);del(); } void tm_start(){ DDRB |= (1 << DIO);del(); } void tm_print(int t, byte pd_t, int br){ tm_start();tm_dec(0b10001000 + br);//tm_stop();tm_start(); tm_dec(0x40);tm_stop();tm_start(); int data0 = t / 1000 % 10; int data1 = t / 100 % 10; int data2 = t / 10 % 10; int data3 = t % 10; for(byte n = 0; n < 4; n++){ int data; switch(n){ case 0: data = data0;break; case 1: data = data1;break; case 2: data = data2;break; case 3: data = data3;break; } switch(data){ // XGFEDCBA case 0: data = 0b00111111;break; // 0 case 1: data = 0b00000110;break; // 1 case 2: data = 0b01011011;break; // 2 case 3: data = 0b01001111;break; // 3 case 4: data = 0b01100110;break; // 4 case 5: data = 0b01101101;break; // 5 case 6: data = 0b01111101;break; // 6 case 7: data = 0b00000111;break; // 7 case 8: data = 0b01111111;break; // 8 case 9: data = 0b01101111;break; // 9 case 10: data = 0b00000000;break; // пусто case 11: data = 0b01000000;break; // - } if(n == 0){data0 = data;} if(n == 1){data1 = data;} if(n == 2){data2 = data;} if(n == 3){data3 = data;} } switch(pd_t){ case 1 : data2 = data2+0b10000000;break; case 2 : data1 = data1+0b10000000;break; case 3 : data0 = data0+0b10000000;break; } tm_dec(0xC0);tm_dec(data0);tm_dec(data1);tm_dec(data2);tm_dec(data3);tm_stop(); } void del(){delay(1);}
Внутренний термометр
Помимо измерения напряжения АЦП так же может измерять температуру кристалла микроконтроллера, В при запросе ко внутреннему термометру АЦП выдает числовое значение температуры согласно этой таблице:
Вычислить температуру можно используя следующую формулу:
где k — фиксированный коэффициент наклона, а TOS — смещение датчика температуры. Обычно значение k очень близко к 1,0, и при одноточечной калибровке коэффициент может быть опущен. Там, где требуется более высокая точность, коэффициент наклона следует оценивать на основе измерений двух температур.
#define CLK PB2 // TM1637 #define DIO PB1 // TM1637 void setup(){ cli(); ADMUX |= (1 << MUX3)|(1 << MUX2)|(1 << MUX1)|(1 << MUX0)|(1 << REFS1); ADCSRA |= (1<<ADEN)|(1 << ADATE)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); sei(); } void loop(){ long u=0; for(int n=0;n<10;n++){ while((ADCSRA & (1 << ADIF)) == 0); int u_data = (ADCL|ADCH << 8); u += u_data;delay(100); } u=u/10; tm_print(u-25,1,5); } void tm_dec(byte dig){ for(byte i = 0; i < 8; i++) { DDRB |= (1 << CLK);del(); if (dig & 0x01) DDRB &= ~(1 << DIO); else DDRB |= (1 << DIO);del(); DDRB &= ~(1 << CLK);del(); dig = dig >> 1; } DDRB |= (1 << CLK); DDRB &= ~(1 << DIO);del(); DDRB &= ~(1 << CLK);del(); if (((PINB >> DIO) & 1) == 0) DDRB |= (1 << DIO);del(); DDRB |= (1 << CLK);del(); } void tm_stop(){ DDRB |= (1 << DIO);del(); DDRB &= ~(1 << CLK);del(); DDRB &= ~(1 << DIO);del(); } void tm_start(){ DDRB |= (1 << DIO);del(); } void tm_print(int t, byte pd_t, int br){ tm_start();tm_dec(0b10001000 + br);//tm_stop();tm_start(); tm_dec(0x40);tm_stop();tm_start(); int data0 = 10; int data1 = t / 100 % 10; int data2 = t / 10 % 10; int data3 = t % 10; for(byte n = 0; n < 4; n++){ int data; switch(n){ case 0: data = data0;break; case 1: data = data1;break; case 2: data = data2;break; case 3: data = data3;break; } switch(data){ // XGFEDCBA case 0: data = 0b00111111;break; // 0 case 1: data = 0b00000110;break; // 1 case 2: data = 0b01011011;break; // 2 case 3: data = 0b01001111;break; // 3 case 4: data = 0b01100110;break; // 4 case 5: data = 0b01101101;break; // 5 case 6: data = 0b01111101;break; // 6 case 7: data = 0b00000111;break; // 7 case 8: data = 0b01111111;break; // 8 case 9: data = 0b01101111;break; // 9 case 10: data = 0b00000000;break; // пусто case 11: data = 0b01000000;break; // - } if(n == 0){data0 = data;} if(n == 1){data1 = data;} if(n == 2){data2 = data;} if(n == 3){data3 = data;} } switch(pd_t){ case 1 : data2 = data2+0b10000000;break; case 2 : data1 = data1+0b10000000;break; case 3 : data0 = data0+0b10000000;break; } tm_dec(0xC0);tm_dec(data0);tm_dec(data1);tm_dec(data2);tm_dec(data3);tm_stop(); } void del(){delay(1);}
Дифференциальный вход, внутреннее опорное — 1,10 В, коэффициент усиления 1
При использовании дифференциального входа (в примере использованы входы PB3 и PB4), напряжение любой полярности подается на дифференциальные входы, при этом старший бит результата измерения регистра ADCH используется для определения полярности напряжения поданного на дифференциальные входы напряжения, а сам результат измерения становится 9 бит.
Ниже показан пример использования дифференциального входа АЦП, на входы подается напряжение 0,6 В.
#define CLK PB2 // TM1637 #define DIO PB1 // TM1637 // INPUT PB3 PB4 void setup(){ cli(); ADMUX |= (1 << MUX2)|(1 << MUX1)|(1 << REFS1); ADCSRA |= (1<<ADEN)|(1 << ADATE)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); ADCSRB |= (1 << 7); sei(); } void loop(){ long u=0; unsigned int u_data,pol; int k0 = 35;// калибровка нуля for(int n=0;n<10;n++){ while((ADCSRA & (1 << ADIF)) == 0); u_data = (ADCL|ADCH << 8); u += u_data; delay(100); } u=u/10+k0; if((u>>9)==1){pol=11;u = 1023-u;} else{pol=10;} tm_print(110*u/512,2,pol,5); } void tm_dec(byte dig){ for(byte i = 0; i < 8; i++) { DDRB |= (1 << CLK);del(); if (dig & 0x01) DDRB &= ~(1 << DIO); else DDRB |= (1 << DIO);del(); DDRB &= ~(1 << CLK);del(); dig = dig >> 1; } DDRB |= (1 << CLK); DDRB &= ~(1 << DIO);del(); DDRB &= ~(1 << CLK);del(); if (((PINB >> DIO) & 1) == 0) DDRB |= (1 << DIO);del(); DDRB |= (1 << CLK);del(); } void tm_stop(){ DDRB |= (1 << DIO);del(); DDRB &= ~(1 << CLK);del(); DDRB &= ~(1 << DIO);del(); } void tm_start(){ DDRB |= (1 << DIO);del(); } void tm_print(int t, byte pd_t,int polar, int br){ tm_start();tm_dec(0b10001000 + br);//tm_stop();tm_start(); tm_dec(0x40);tm_stop();tm_start(); int data0 = polar; int data1 = t / 100 % 10; int data2 = t / 10 % 10; int data3 = t % 10; for(byte n = 0; n < 4; n++){ int data; switch(n){ case 0: data = data0;break; case 1: data = data1;break; case 2: data = data2;break; case 3: data = data3;break; } switch(data){ // XGFEDCBA case 0: data = 0b00111111;break; // 0 case 1: data = 0b00000110;break; // 1 case 2: data = 0b01011011;break; // 2 case 3: data = 0b01001111;break; // 3 case 4: data = 0b01100110;break; // 4 case 5: data = 0b01101101;break; // 5 case 6: data = 0b01111101;break; // 6 case 7: data = 0b00000111;break; // 7 case 8: data = 0b01111111;break; // 8 case 9: data = 0b01101111;break; // 9 case 10: data = 0b00000000;break; // пусто case 11: data = 0b01000000;break; // - } if(n == 0){data0 = data;} if(n == 1){data1 = data;} if(n == 2){data2 = data;} if(n == 3){data3 = data;} } switch(pd_t){ case 1 : data2 = data2+0b10000000;break; case 2 : data1 = data1+0b10000000;break; case 3 : data0 = data0+0b10000000;break; } tm_dec(0xC0);tm_dec(data0);tm_dec(data1);tm_dec(data2);tm_dec(data3);tm_stop(); } void del(){delay(1);}
Измерение внутреннего опорного напряжения АЦП
При проведении измерений для повышения точности измерений можно периодически измерять внутренне опорное напряжение и вводить корректировку в результаты измерений при изменении внутреннего опорного напряжения (например из-за изменения температуры окружающей среды).
#define CLK PB2 // TM1637 #define DIO PB1 // TM1637 // PB4 INPUT void setup(){ cli(); ADMUX |= (1 << MUX3)|(1 << MUX2)|(1 << REFS1)|(1 << REFS2); ADCSRA |= (1<<ADEN)|(1 << ADATE)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); sei(); } void loop(){ long u=0; for(int n=0;n<10;n++){ while((ADCSRA & (1 << ADIF)) == 0); int u_data = (ADCL|ADCH << 8); u += u_data;delay(100); } u=u/10; tm_print(2560*u/1023,3,5); } void tm_dec(byte dig){ for(byte i = 0; i < 8; i++) { DDRB |= (1 << CLK);del(); if (dig & 0x01) DDRB &= ~(1 << DIO); else DDRB |= (1 << DIO);del(); DDRB &= ~(1 << CLK);del(); dig = dig >> 1; } DDRB |= (1 << CLK); DDRB &= ~(1 << DIO);del(); DDRB &= ~(1 << CLK);del(); if (((PINB >> DIO) & 1) == 0) DDRB |= (1 << DIO);del(); DDRB |= (1 << CLK);del(); } void tm_stop(){ DDRB |= (1 << DIO);del(); DDRB &= ~(1 << CLK);del(); DDRB &= ~(1 << DIO);del(); } void tm_start(){ DDRB |= (1 << DIO);del(); } void tm_print(int t, byte pd_t, int br){ tm_start();tm_dec(0b10001000 + br);//tm_stop();tm_start(); tm_dec(0x40);tm_stop();tm_start(); int data0 = t/1000; int data1 = t / 100 % 10; int data2 = t / 10 % 10; int data3 = t % 10; for(byte n = 0; n < 4; n++){ int data; switch(n){ case 0: data = data0;break; case 1: data = data1;break; case 2: data = data2;break; case 3: data = data3;break; } switch(data){ // XGFEDCBA case 0: data = 0b00111111;break; // 0 case 1: data = 0b00000110;break; // 1 case 2: data = 0b01011011;break; // 2 case 3: data = 0b01001111;break; // 3 case 4: data = 0b01100110;break; // 4 case 5: data = 0b01101101;break; // 5 case 6: data = 0b01111101;break; // 6 case 7: data = 0b00000111;break; // 7 case 8: data = 0b01111111;break; // 8 case 9: data = 0b01101111;break; // 9 case 10: data = 0b00000000;break; // пусто case 11: data = 0b01000000;break; // - } if(n == 0){data0 = data;} if(n == 1){data1 = data;} if(n == 2){data2 = data;} if(n == 3){data3 = data;} } switch(pd_t){ case 1 : data2 = data2+0b10000000;break; case 2 : data1 = data1+0b10000000;break; case 3 : data0 = data0+0b10000000;break; } tm_dec(0xC0);tm_dec(data0);tm_dec(data1);tm_dec(data2);tm_dec(data3);tm_stop(); } void del(){delay(1);}