Arduino + ATTiny2313 компаратор

Esteriman

✩✩✩✩✩✩✩
4 Июн 2025
8
0
Приветствую форумчан!
Я новичок, поэтому сильно тапками не кидайтесь.
Подключил сабж, т.е. ATTiny 2313, шью через ардуино уно, всё шьётся замечательно. Написал скетч для дисплея 1602, работает. Есть идея сделать из аттини2313 вольтметр-амперметр для лаб. БП. Пытаюсь понять встроенный компаратор. Но не срабатывает прерывание. Использую инфу отсюда http://www.gaw.ru/html.cgi/txt/app/micros/avr/AVR400.htm
Прикладываю код (модуль для LCD не стал приводить, чтобы не загружать излишне). Подскажите, пожалуйста как правильно включить прерывания от компаратора с учетом того, что это компилятор ардуино. Да, и не предлагайте внешние АЦП и другой камень.

C++:
// ATTiny2313
// LCD 4 bit
// D4 = PB4, D5 = PB5, D6 = PB6, D7 = PB7
// E  = PD5, RS = PD6
//
// компаратор
// 12-я нога — RC-цепочка
// 13-я нога — измеряемый сигнал
// 14-я нога PB2 — выход вкл. заряда конденсатора


const byte opwait = 2; // универсальная задержка в мс
unsigned long m;

void setup () {
    lcdInit();
  lcdCursor(0, 0);
  lcdPrint('T');lcdPrint('=');
  lcdCursor(1, 0);
  lcdPrint('U');lcdPrint('=');
  ACSR = 0x0b;
  TIMSK = 0x02;
  DDRB |= 0x04; // включаем pb2 на выход
  PORTB |= 0;
  delay(50);
  sei();
}

SIGNAL (SIG_COMPARATOR) {
  m = micros - m;
  PORTB |= 0x0; // выкл зарядку конд.
  lcdCursor(0,4);
  lcdPrint('*');
  lcdCursor(1,4);
  lcdPrintNum(m);
  delay(5);
}


void loop () {
  adcStart();
  delay(500);

}

void adcStart () {
  m = micros();
  PORTB |= 0x04; // вкл зарядку конденсатора
}
 

Bruzzer

★★★✩✩✩✩
23 Май 2020
615
186
@Esteriman,
В инете есть примеры для прерываний компаратора.
Но даташит в любом случае полезно прочитать.
Поиск по словам "Arduino ATTiny2313 ISR(ANALOG_COMP_vect)" Дает в том числе ссылку
Принцип работы похож на другие 8 битAVR, поэтому для понимания можно почитать информацию по ним (учитывая разницу между МК).
Поиск по "arduino ISR(ANALOG_COMP_vect)"

Дополнение. По моему, при работе в среде Arduino, не стоит отказываться от digitalRead и прочих функций работы с портами, пока в этом не возникнет необходимость, и должно быть понимание, что именно делает код без arduino функций.

Дополнение. Возможно вам лучше подойдет возможность "The comparator’s output can be set to trigger the Timer/Counter1 Input Capture function."
Примеры в инете тоже должны быть.

Дополнение. Даже если в своем устройстве вы планируете использовать ATTiny2313, отрабатывать алгоритмы НАМНОГО удобнее на UNO, а потом перенести алгоритм на ATTiny2313.
 
Изменено:

Esteriman

✩✩✩✩✩✩✩
4 Июн 2025
8
0
@Bruzzer, благодарю за обстоятельный ответ. Ушел в поисковик.
P. S. Не подумал о том, чтобы отработать проект на Уно, а затем перенести на 2313. Благодарю за отличный совет, попробую.
 

Esteriman

✩✩✩✩✩✩✩
4 Июн 2025
8
0
C++:
// Definition of interrupt names
#include <avr/io.h>
// ISR interrupt service routine
#include <avr/interrupt.h>
const int pAIN0 = 6; // AIN0 at Pin6
const int pAIN1 = 7; // AIN1 at Pin7
const int pCharge = 8; // линия зарядки конденсатора
boolean iflag = false;
uint32_t m = micros();
uint32_t tCharge; // время зарядки конденсатора


//
ISR(ANALOG_COMP_vect) {
  iflag = true;
  digitalWrite(pCharge, LOW); // выключить зарядку С
  noInterrupts();
}

void setup() {
  Serial.begin(9600);
  pinMode(pCharge, OUTPUT);
  digitalWrite(pCharge, HIGH); // 1-е включение
  /*
  //pinMode(pAIN0, INPUT);
  //pinMode(pAIN1, INPUT);
  cli();
  ADCSRA &= ~(1<<ADEN); // ADC disabled
  ADCSRB |= ~(1<<ACME); // AMUX enabled
  ACSR = (1<<ACBG) | (1<<ACIE); // ACOMP Interrupt enabled
  DIDR1 = (1<<AIN1D) | (1<< AIN0D);
  sei();*/

  DIDR1 |= (1<<AIN0D) | (1<<AIN1D); // Disable Digital Inputs at AIN0 and AIN1
  ADCSRB &= ~(1<<ACME);    //Clear ACME bits in ADCSRB to use external input at AIN1 -ve input
  ACSR =
  (0 << ACD) |    // Analog Comparator: Enabled
  (0 << ACBG) |   // Clear ACBG to use external input to AIN0 +ve input
  (0 << ACO) |
  (1 << ACI) |    // Clear Pending Interrupt by setting the bit
  (1 << ACIE) |   // Analog Comparator Interrupt Enabled
  (0 << ACIC) |   // Analog Comparator Input Capture Disabled
  (0 << ACIS1) | (0 << ACIS0);   // Analog Comparator Interrupt Mode
  interrupts();
}

long vMeter;
void loop() {
if (iflag) {
    digitalWrite(pCharge, LOW);
    tCharge = micros() - m;
    vMeter = map(tCharge, 92, 1292, 125, 1840);
    //vMeter = constrain( vMeter, 125, 200);
    Serial.print(tCharge);
    Serial.print(' ');
    Serial.println(float(vMeter)/100);
    delay(500);
    iflag = false;
    m = micros();
    digitalWrite(pCharge, HIGH); // вкл зарядку конденсатора
    interrupts();
  }

}
В данный момент пришел к этому. Конденсатор 0,1мкф, резистор 33КОм. Подключал через делитель 1/10 100КОм и 10КОм. Есть некоторая нелинейность и показания слегка «плавают».
Это на плате Уно. Теперь буду переносить.
 

Bruzzer

★★★✩✩✩✩
23 Май 2020
615
186
@Esteriman,
У вас interrupts(); и noInterrupts(); неправильно поставлены. Это сразу бросается в глаза, остальное не смотрел.
noInterrupts(); в ISR не имеет смысла, т.к. они и так запрещены в прерывании.
В loop нет noInterrupts(); и поэтому interrupts(); не имеет смысла. И может быть дополнительно надо сбрасывать аппаратный флаг прерывания, т.к. если он установлен, то сразу вызовется ISR.
В общем изучайте прерывания. Или может быть вам вообще они не нужны. Можно сбрасывать аппаратный флаг прерывания руками и в цикле ждать его установки.

И желательно расчетами и (или) опытом определить требуемую точность измерения времени.
 

Esteriman

✩✩✩✩✩✩✩
4 Июн 2025
8
0
Я полагаю, что noInterrupts сбрасывает флаг разрешения прерываний, чтобы не сработало еще раз пока не пройдут команды в loop после if. Если это не так, то я не знаю как еще.
 

Sergo_ST

★★★★★★✩
15 Мар 2020
1,063
877
@Esteriman, Ничего эта процедура не сбрасывает, только лишь запрещает все прерывания глобально.

Если вам нужно вкл/выкл прерывания от компаратора, то это делается так:
C++:
//Выключить прерывания компаратора
ACSR &= ~(0x01 << ACIE); //запретить прерывания компаратора

//Включить прерывания компаратора
ACSR |= (0x01 << ACI); //сбросить флаг прерывания компаратора
ACSR |= (0x01 << ACIE); //разрешить прерывания от компаратора
 

Esteriman

✩✩✩✩✩✩✩
4 Июн 2025
8
0
С учётом всех поправок код работает на плате Уно. Напряжение замеряет. Есть небольшая нелинейность связанная с законом зарядки конденсатора.
А вот на 2313 не работает. micros странно работает то одни значения то другие. Вплоть до того, что возникают глюки на дисплее, отключаешь micros и глюки пропадают.
Прерывание по компаратору работает, не сразу но получилось.
Думал таймер запустить для отсчёта времени зарядки конденсатора, но... TCCR0 не разпознаётся. Библиотеки для работы с таймерами на 2313 не рассчитаны.
 

Bruzzer

★★★✩✩✩✩
23 Май 2020
615
186
@Esteriman,
Когда на МК мало памяти, то это надо учитывать и проверять ее достаточность и принимать меры к экономии. (Хотя сейчас, когда есть МК с большим объемом памяти, это может быть не целесообразно). Когда приходится считать каждый байт, то даже отказ от прерываний может сократить расход памяти. Но повторюсь, в настоящее время это скорее можно рассматривать как интересную малому числу людей головоломку.
По поводу запуска таймера - имея даташит, сложностей быть не должно. Да и примеров в инете должны быть, в основном для UNO, но там все похоже. Если не занят Таймер1, то может проще на нем. Он 16-ти битный, и может быть его хватит без переполнения.
 

Esteriman

✩✩✩✩✩✩✩
4 Июн 2025
8
0
@Bruzzer, и таймер 0 и таймер 1 не доступны. Пробовал бесконечный цикл, но вот не пойму то ли это глюки аппаратные то ли... Прерывание то срабатавыет нормально, то один раз сработает и всё.
А памяти хватает 1200 байт весь код, ещё есть места.
И да это действительно такой квест для меня. Проще было бы сделать с платой Нано, а еще лучше использовать серию АЦП INO. Только они дорогие очень.

C++:
// ATTiny2313
// LCD 4 bit
// D4 = PB4 16, D5 = PB5 17, D6 = PB6 18, D7 = PB7 19
// E  = PD5 9, RS = PD6 11
//
// компаратор
// 12-я нога AIN0 — RC-цепочка
// 13-я нога AIN1 — измеряемый сигнал
// 14-я нога PB2 — выход вкл. заряда конденсатора
#include <avr/io.h>
#include <avr/interrupt.h>

const byte conCh = 2; // линия зарядки конденсатора
const byte opwait = 1; // универсальная задержка в мс *10
uint32_t t=0;

void setup () {
    lcdInit();
  lcdCursor(0, 0);
  lcdPrint('U');lcdPrint('=');
 
  ACSR |= 0x03; // вкл компаратор
  DIDR |= 0x3; // выключить цифровой буфер на пинах компаратора
  DDRB |= (1 << conCh); // включаем pb2 на выход
  PORTB &= ~(1 << conCh);// выключить линию зарядки
}

void loop () {
  adcStart();
  do { }
  while ((ACSR & (1 << 5))==0);
  t = micros() - t;
  PORTB &= ~(1 << conCh); // выкл зарядку конд.
  lcdCursor(0, 4);
  lcdPrint('*');
  lcdPrintNum(t);
  for (int i=0; i<1000;i++) { }; // задержка для разряда емкости
}

void adcStart() {
  t = micros(); // сбросить счетчик времени заряда
  PORTB |= (1 << conCh); // вкл зарядку конденсатора
}

// модуль для 1602
// импульс Enable
void lcdE () {
  PORTD |= 0b00100000;
  delay(opwait);
  PORTD &= 0b11011111;
}

// rs = 0 — команды
// rs = 1 — данные
void lcdSend (byte rs, byte d) {
    PORTD &= 0b10111111; // сбрасываем RS
    PORTD |= (rs << 6); // если rs = 1 включаем RS
    PORTB = (d & 0xf0) ^ (PORTB & 0x0f); // сначала старшие биты
    lcdE();   
    PORTB = (d << 4) ^ (PORTB & 0x0f); // затем младшие биты
    lcdE();
}

void lcdInit () {
    DDRB = 0b11110000;
    DDRD |= 0b01100000;
    delay(10);
    lcdSend(0, 0b00110011); // инициализация
    delay(1); // значение по даташиту — 4.1 мс
    lcdSend(0, 0b00110011);
    delay(opwait);
    lcdSend(0, 0b00000010); // 4 bit interface
    lcdSend(0, 0b00001100); //
    lcdClear();
}

void lcdClear () { lcdSend(0, 0b00000001); }

void lcdPrintNum (unsigned long n) {
  uint32_t i = 1, k;
  do {
    i *= 10;
    k = n / i; }
  while (k > 0);
  k = n;
  i /= 10;
  do {
    lcdPrint(k/i+0x30);
    k = n % i;
    i /= 10; }
  while ( i >= 1);
}

void lcdPrint (byte c) {
    lcdSend(1, c);
}

// nstr: 0..1 — номер строки
// offset: 0..15 — позиция курсора
void lcdCursor (byte nstr, byte ofset) {
    lcdSend(0, (0b10000000 | (nstr << 6)) + ofset);
}
Вот этот код работает на 2313. Резистор зарядки использовал 1МОм, конденсатор 0,1МкФ. Частота МК - 8МГц, внутренний. Входной делитель 1/10.
На входе 1,25В — значение 144
--- 18,38В - 2712
Оставил все прерывания и просто проверяю бит ACO в бесконечном цикле.
Есть глюки на 2313 в Ардуино при использовании временных функций, после всех экспериментов, по.добрался работающий код.
P.S. На функции map прога зависает.
 

Bruzzer

★★★✩✩✩✩
23 Май 2020
615
186
@Esteriman,
for (int i=0; i<1000;i++) { }; // задержка для разряда емкости
Этой задержки в программе не будет. Оптимизатор выкинет этот код, как ничего не делающий.
Есть функции задержки не использующие таймеры, например
void delayMicroseconds(unsigned int us)

Можете показать код с map на котором прога зависает?
Вроде в приведенном выше коде свободной памяти RAM много.
 

Esteriman

✩✩✩✩✩✩✩
4 Июн 2025
8
0
@Bruzzer, да задержка, действительно не работает. Переделал. А map уже не зависает. Не знаю, что это было, но я поставил дополнительную ёмкость по питанию на макетку; подсветка LCD много берёт 160 мА. И вот новый рабочий код.

C++:
// ATTiny2313
// LCD 4 bit
// D4 = PB4 16, D5 = PB5 17, D6 = PB6 18, D7 = PB7 19
// E  = PD5 9, RS = PD6 11
//
// компаратор
// 12-я нога AIN0 — RC-цепочка
// 13-я нога AIN1 — измеряемый сигнал
// 14-я нога PB2 — выход вкл. заряда конденсатора
#include <avr/io.h>
#include <avr/interrupt.h>

const byte conCh = 2; // линия зарядки конденсатора
const byte opwait = 1; // универсальная задержка в мс *10
uint32_t t=0;

void setup () {
    lcdInit();
  lcdCursor(0, 0);
  lcdPrint('U');lcdPrint('=');

  ACSR |= 0x03; // вкл компаратор
  DIDR |= 0x3; // выключить цифровой буфер на пинах компаратора
  DDRB |= (1 << conCh); // включаем pb2 на выход
  PORTB &= ~(1 << conCh);// выключить линию зарядки
}

void loop () {
  adcStart();
  do { }
  while ((ACSR & (1 << 5))==0);
  t = micros() - t;
  PORTB &= ~(1 << conCh); // выкл зарядку конд.
  t = map(t,168,2944,125,1838);
  lcdViewNums(t);
  for (long i=0; i<100;i++) { delayMicroseconds(100); } // задержка для разряда емкости
}

void adcStart() {
  t = micros(); // сбросить счетчик времени заряда
  PORTB |= (1 << conCh); // вкл зарядку конденсатора
}

// модуль для 1602
// импульс Enable
void lcdE () {
  PORTD |= 0b00100000;
  delay(opwait);
  PORTD &= 0b11011111;
}

// rs = 0 — команды
// rs = 1 — данные
void lcdSend (byte rs, byte d) {
    PORTD &= 0b10111111; // сбрасываем RS
    PORTD |= (rs << 6); // если rs = 1 включаем RS
    PORTB = (d & 0xf0) ^ (PORTB & 0x0f); // сначала старшие биты
    lcdE();  
    PORTB = (d << 4) ^ (PORTB & 0x0f); // затем младшие биты
    lcdE();
}

void lcdInit () {
    DDRB = 0b11110000;
    DDRD |= 0b01100000;
    delay(10);
    lcdSend(0, 0b00110011); // инициализация
    delay(1); // значение по даташиту — 4.1 мс
    lcdSend(0, 0b00110011);
    delay(opwait);
    lcdSend(0, 0b00000010); // 4 bit interface
    lcdSend(0, 0b00001100); //
    lcdClear();
}

void lcdClear () { lcdSend(0, 0b00000001); }

void lcdViewNums(uint32_t n) {
  lcdCursor(0, 4);
  if (n/1000<0) lcdPrint(' ');
  lcdPrintNum(t/100);
  lcdPrint('.');
  lcdPrintNum(t%100);
  lcdPrint(' ');
}

void lcdPrintNum (unsigned long n) {
  uint32_t i = 1, k;
  do {
    i *= 10;
    k = n / i; }
  while (k > 0);
  k = n;
  i /= 10;
  do {
    lcdPrint(k/i+0x30);
    k = n % i;
    i /= 10; }
  while ( i >= 1);
}

void lcdPrint (byte c) {
    lcdSend(1, c);
}

// nstr: 0..1 — номер строки
// offset: 0..15 — позиция курсора
void lcdCursor (byte nstr, byte ofset) {
    lcdSend(0, (0b10000000 | (nstr << 6)) + ofset);
}
Осталось две проблемы: 1) Нелинейность 2) Нестабильность показаний на 0,1-0,2в

Попробовал аппроксимировать по 17 точкам. Получилось неплохо.
Нестабильность, всё-таки, не такая большая — 0,05-0,1в. С 0,2в я погорячился. После аппроксимации цифры прыгают меньше.
Думаю теперь как прикрутить измерение тока.