Нужна помощь по коду для AVR (ATTiny85)

We-BEER

Привет друзья, третий день бьюсь но лыжи все не едут 😵

Есть способ измерения освещенности с помощью обычного светодиода, работает это так: подаем на светодиод обратное напряжение, переключаем ногу катода на вход и считаем за какое время напряжение упадет до логического нуля. Чем темнее, тем дольше будет падать. Для определения ночь сейчас или день точности более чем достаточно. Метод проверено рабочий, но…

код в лоб, считаем интервалы просто накручивая переменную, всё работает

volatile uint8_t lightCount;

uint8_t inline getLight(){

	lightCount = 0;
	while (PINB & (1<<LED_K)) {
		lightCount++;
		if(lightCount == 255) break;
	}
	return lightCount;
}

int main(void) {

	while(1){

		DDRB |= _BV(LED_A) | _BV(LED_K);	// Порты на выход
		PORTB |= _BV(LED_A);			// и моргнуть
		PORTB &= ~_BV(LED_K);
		_delay_ms(10);

		PORTB &= ~_BV(LED_A);			// Подаем обратное напряжение
		PORTB |= _BV(LED_K);

		DDRB &= ~_BV(LED_K);			// Катод на вход
		PORTB &= ~(_BV(LED_K) | _BV(LED_K));	// Отключаем подтяжку

		getLight();

		while (lightCount--) {			// Чем светлее, тем чаще мигает
			_delay_ms(1);			// просто для отладки
		}
	}
}

но это как-то не по фэншую, хотелось бы считать встроенным счетчиком, а пока он считает спать для экономии энергии. Но код c прерыванием ни в какую не работает, где накосячил не пойму

volatile uint8_t flag;

ISR(PCINT0_vect) {
	GIMSK &= ~_BV(PCIE);				// Отключаем прерываения по смене состояния
	flag = 1;					// Взводим флаг
}

int main(void) {

	sei();

	while(1){

		DDRB |= _BV(LED_A) | _BV(LED_K);	// Порты на выход
		PORTB |= _BV(LED_A);			// Моргнуть
		PORTB &= ~_BV(LED_K);
		_delay_ms(10);

		PORTB &= ~_BV(LED_A);			// Подаем обратное напряжение
		PORTB |= _BV(LED_K);

		DDRB &= ~_BV(LED_K);			// Катод на вход
		PORTB &= ~(_BV(LED_K) | _BV(LED_K));	// Отклочаем подтяжку

		GIMSK |= _BV(PCIE);			// Разрешаем прерывание по смене состояния PB0
		PCMSK |= _BV(PCINT0);


		flag = 0;
		while (!flag) {				// Будем спать пока не встанет 0 на PB0
			_delay_ms(1);			// а пока просто ждем
		}
	}
}

Теоретически должно работать так же как первый кусок, но не работает. Счетчик пока убрал, и так в трех соснах заблудилося 😃 Светодиод подключен к голому контроллеру, катод на PB0, анод через резистор 100 Ом на PB2, больше ничего нет. Буду очень признателен за помощь или хотя бы подсказку что не так.

xmailer
volatile uint8_t flag = 1;

ISR(PCINT0_vect) {
	GIMSK &= ~_BV(PCIE);				// Отключаем прерываения по смене состояния
	flag = 1;					// Взводим флаг
}

int main(void)
{
    while (1)
    {
		if(flag){
			flag = 0;
			cli();

			DDRB |= _BV(LED_A) | _BV(LED_K);	// Порты на выход
			PORTB |= _BV(LED_A);			// Моргнуть
			PORTB &= ~_BV(LED_K);
			_delay_ms(10);

			PORTB &= ~_BV(LED_A);			// Подаем обратное напряжение
			PORTB |= _BV(LED_K);

			DDRB &= ~_BV(LED_K);			// Катод на вход
			PORTB &= ~(_BV(LED_K) | _BV(LED_K));	// Отклочаем подтяжку

			GIMSK |= _BV(PCIE);			// Разрешаем прерывание по смене состояния PB0
			PCMSK |= _BV(PCINT0);

			sei();
		}

    }
}
xmailer

не нашел как повторно редактировать свое сообщение, прерывание


ISR(PCINT0_vect) {
	// Отключаем прерываения по смене состояния на INT0
	GIMSK &= ~_BV(PCIE);
	PCMSK &= ~_BV(PCINT0);
	flag = 1;	// Взводим флаг
}
We-BEER

Спасибо что откликнулись, но чет не работает или я не понимаю как это должно работать… Мой первый код мигает светодиодом, чем светлее тем чаще он мигает, я это вижу невооруженным глазом просто поднося к нему фонарик. Ваш код просто светит (полагаю на самом деле очень быстро мигает), и я не пойму куда поставить задержку чтобы в железе это выглядело как первый вариант. И еще, в будущем я хочу чтобы контроллер спал пока ждет перехода уровня, а у вас он бодро крутит пустой цикл получается? То есть потом, когда все наладится, после if(flag){} можно будет уходить в сон с пробуждением по переполнению таймера или по смене состояния на входе?

Вот к каким изысканиям я пришел, но это все еще не работает как задумано. Мне надо чтобы частота мигания светодиода зависела от текущего уровня освещенности.

#define LED_A PB2
#define LED_K PB0
#define F_CPU 1000000L

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

volatile uint8_t lightValue, lightCycle;

ISR(PCINT0_vect) {
	GIMSK &= ~_BV(PCIE);			// Отключаем прерываения по смене состояния
	lightCycle = 1;				// Взводим флаг
	lightValue = TCNT1;
}

ISR(TIMER1_OVF_vect) {
	lightValue = 255;			// Устанавливаем максимальное значение
	lightCycle = 1;				// Устанавливаем флаг окончания измерения
}

uint8_t inline getLight(){

	DDRB |= _BV(LED_A) | _BV(LED_K);	// Порты на выход
	PORTB |= _BV(LED_A);			// и моргнуть
	PORTB &= ~_BV(LED_K);
	_delay_ms(10);

	PORTB &= ~_BV(LED_A);			// Подаем обратное напряжение
	PORTB |= _BV(LED_K);
	_delay_ms(10);

	DDRB &= ~_BV(LED_K);			// Катод на вход
	PORTB &= ~(_BV(LED_K) | _BV(LED_A));	// Отклочаем подтяжку

	PRR &= ~_BV(PRTIM1);			// Включаем питание счетчика
	TCNT1 = 0;
	TCCR1 = 0;
	TCCR1 |= _BV(CS13) | _BV(CS12) | _BV(CS11); // Запускаем счетчик на частоте clk/8192

	TIMSK |= _BV(TOIE1);			// Разрешаем прерывания по переполнению счетчика
	GIMSK |= _BV(PCIE);			// Включаем прерывание по смене состояния PB0
	PCMSK |= _BV(PCINT0);

	lightCycle = 0;
	while(!lightCycle){
		// ---------------------Тут будем спать
	}

	TIMSK &= ~_BV(TOIE1);			// Запрещаем прерывания по переполнению счетчика
	GIMSK &= ~_BV(PCIE);			// Запрещаем прерывания по смене состояния пина
	PRR |= _BV(PRTIM1);			// Отключаем счетчик

	return lightValue;
}

int main(void) {

	sei();

	while(1){

		getLight();			// Получаем освещенность

		while (lightValue--) {		// Чем темнее, тем больше пауза
			_delay_ms(1);		// просто для наглядности
		}

	}
}

Только начал разбираться во всех этих дебрях, вероятно где-то сильно туплю 😵

emax

на первый взгляд, похоже с частотой счетчика не попали. В первом коде крутится практически пустой цикл измерения, пусть это тактов 10-20, а в последнем коде устанавливаете частоту /512.

We-BEER

Во втором коде делитель /8192, переполнение счетчика наступает каждые чуть более 2 секунд, с такой частотой светодиод и моргает независимо от освещенности. Не срабатывает прерывание по смене состояния пина PCINT0, и я не пойму почему. На этом же железе первый код прекрасно отрабатывает появление на этом же пине “0”, что собственно и есть смена состояния, просто отслеживается не через прерывание а прямым чтением регистра.

xmailer

Попробуйте, следующий код, в симуляторе atmel studio отрабатывает, но конечно pb0 я в ручную сбрасываю в 0, чтобы прерывание сработало. Если не решите вопрос - на выходных соберу прототип, пока со временем вилы.

volatile uint8_t flag = 1;
uint8_t light = 0;

ISR(PCINT0_vect) {
    GIMSK &= ~_BV(PCIE);                // Отключаем прерываения по смене состояния
    PCMSK &= ~_BV(PCINT0);
    flag = 1;                    // Взводим флаг
}

int main(void)
{
    while (1)
    {
        if(flag){
            // задержка для визуализации результата замера
            if(light){
                while(light--){
                    _delay_ms(1);
                }
            }
            cli();
            DDRB |= _BV(LED_A) | _BV(LED_K);    // Порты на выход
            PORTB |= _BV(LED_A);            // Моргнуть
            PORTB &= ~_BV(LED_K);
            _delay_ms(10);

            PORTB &= ~_BV(LED_A);            // Подаем обратное напряжение
            PORTB |= _BV(LED_K);

            DDRB &= ~_BV(LED_K);            // Катод на вход
            PORTB &= ~(_BV(LED_K) | _BV(LED_K));    // Отклочаем подтяжку

            GIMSK |= _BV(PCIE);            // Разрешаем прерывание по смене состояния PB0
            PCMSK |= _BV(PCINT0);

            light = 0;
            flag = 0;
            sei();
        }
        if(light<255)
        {
            light++;
        }
    }
}
We-BEER

Да, так работает так же как мой первый пример, при увеличении освещенности моргает чаще, в темноте реже. А как сделать чтобы light считался встроенным счетчиком, как в моей втором варианте? Если усыпить проц до срабатывания прерывания, он же не будет гонять цикл и считать.

xmailer
We-BEER:

А как сделать чтобы light считался встроенным счетчиком, как в моей втором варианте? Если усыпить проц до срабатывания прерывания, он же не будет гонять цикл и считать.

  1. если как у Вас, то я бы исправил

ISR(PCINT0_vect) {
	...
	lightValue = TCNT1L;		// Значение, до которого досчитал таймер low byte
	//lightValue = TCNT1;		// если сделать lightValue типом uint16_t, то можно так
}
  1. есть один момент: цикл main->while(1) должен крутиться всегда и работать с последним значением освещенности, расчитанным параллельно в таймерах, нельзя вот так останавливать работу до получения значения while(!lightCycle){}. Если конечно это не особые требования функционала.
We-BEER
  1. У ATTiny85 нет TCNT1L/TCNT1H, у нее оба счетчика 8-битные.
  2. Я представлял себе алгоритм работы так: всегда крутим main->while(1), в котором вызываем getLight(), вся работа со светодиодом будет там, и там же проц будет засыпать внутри цикла while(!lightCycle){}. то есть после подачи обратного напряжения и настройки прерываний проц уснет, появление “0” на входе или переполнение таймера его будит и взводит флаг, после обработки прерываний идет возврат в цикл где условие уже не выполняется и происходит возврат в основную программу и мы продолжаем что-то там делать… ну как-то так
ISR(PCINT0_vect){...lightCycle=1;...} // появление "0" после "1"

ISR(TIMER1_OVF_vect){...lightCycle=1;...} // переполнение счетчика

int getLight(){
  ...                                    // настройка портов и прерываний

  flag = 0;
  while(!lightCycle){sleep}       // тут спим

  return;
}

int main(void){
  ...
  getLight();
  ...
}
emax

странный конечно алгоритм, если проц спит, то он уже точно while проверять не может. А если его все таки разбудило прерывание, то зачем проверять флаг lightCycle ?

We-BEER
emax:

странный конечно алгоритм, если проц спит, то он уже точно while проверять не может.

он засыпает внутри while, и когда проснется тоже будет внутри, сходит в обработчик прерывания которое его разбудило, там он поднимет флаг, вернется из обработчика обратно в while, теперь условие уже не выполняется и он перепрыгнув while вернется в основную программу.

в принципе можно while заменить на if, сути это не изменит, после настройки флаг сбрасывается и проц идет спать, проснуться он может только от прерывания так что после пробуждения проверять флаг уже не надо.

у меня нет вопросов ко сну, этот момент вообще сейчас заменен пустым циклом. из этого цикла можно выйти только по двум условиям - либо счетчик отсчитал 2 секунды, либо на пине PB0 случился переход уровня. так вот переход уровня не случается, и я не понимаю почему.

xmailer

у Вас рабочий код, на железе пока не могу проверить, но в симуляторе

GIMSK &= ~_BV(PCIE); не достаточно

PCMSK &= ~_BV(PCINT0); обязательно, в противном случае постоянно проваливался в прерывание ISR(PCINT0_vect)

второй момент - while(!lightCycle){} - это не сон, а ожидание, проц будет продолжать жрать питание. Термин “сон” - это перевод камня в состояние, при котором у него часть функции будут отключены и режим потребления питания будет минимален. Про сон погуглите watchdog и как этот таймер можно использовать, я не пользовался им поэтому не могу ничего утверждать.

We-BEER
xmailer:

второй момент - while(!lightCycle){} - это не сон, а ожидание

да, я об этом знаю 😃 пустой цикл я поставил на время отладки, чтобы исключить лишние глюки пока не разберусь с PCINT0.

xmailer:

GIMSK &= ~_BV(PCIE); не достаточно

PCMSK &= ~_BV(PCINT0); обязательно, в противном случае постоянно проваливался в прерывание ISR(PCINT0_vect)

Заработало!!! Господи, спасибо тебе что есть такие люди! Мой второй код заработал, только разогнал таймер до /128, а то моргало очень быстро, мало успевал накрутить. Огромное вам спасибо, я чуть с ума не сошел))

Вот рабочий код:

#define LED_A PB2
#define LED_K PB0
#define F_CPU 1000000L

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

volatile uint8_t lightValue, lightCycle;

ISR(PCINT0_vect) {
	GIMSK &= ~_BV(PCIE);			// Запрещаем прерываения по смене состояния
	lightCycle = 1;				// Взводим флаг
	lightValue = TCNT1;			// Забираем значение счетчика
}

ISR(TIMER1_OVF_vect) {
	TIMSK &= ~_BV(TOIE1);			// Запрещаем прерывания по переполнению счетчика
	lightCycle = 1;				// Устанавливаем флаг окончания измерения
	lightValue = 255;			// Устанавливаем максимальное значение
}

uint8_t inline getLight(){

	DDRB |= _BV(LED_A) | _BV(LED_K);	// Порты на выход
	PORTB |= _BV(LED_A);			// и моргнуть
	PORTB &= ~_BV(LED_K);
	_delay_ms(10);

	PORTB &= ~_BV(LED_A);			// Подаем обратное напряжение
	PORTB |= _BV(LED_K);

	DDRB &= ~_BV(LED_K);			// Катод на вход
	PORTB &= ~_BV(LED_K);			// Отклочаем подтяжку

	PRR &= ~_BV(PRTIM1);			// Включаем питание счетчика
	TCNT1 = 0;
	TCCR1 = 0;
	TCCR1 |= _BV(CS13);			// Запускаем счетчик на частоте clk/128

	TIMSK |= _BV(TOIE1);			// Разрешаем прерывания по переполнению счетчика
	GIMSK |= _BV(PCIE);			// Разрешаем прерывания по смене состояния PCINT0
	PCMSK |= _BV(PCINT0);			// на пине PB0

	lightCycle = 0;
	while(!lightCycle){
		// Тут будем спать
	}

	TIMSK &= ~_BV(TOIE1);			// Запрещаем прерывания по переполнению счетчика
	GIMSK &= ~_BV(PCIE);			// Запрещаем прерывания по смене состояния пина
	PCMSK &= ~_BV(PCINT0);			// <<<<< Иначе не работает
	PRR |= _BV(PRTIM1);			// Отключаем счетчик

	return lightValue;
}

int main(void) {

	sei();

	while(1){

		getLight();

		while (lightValue--) {		// Чем темнее, тем дольше пауза
			_delay_ms(1);
		}

	}
}
We-BEER

В общем-то не долго я радовался, опять застрял, на этот раз с ADC… В общем мне надо контролировать напряжение питания контроллера, и сигнализировать при падении ниже заданного уровня (скажем 3.3V). Самым простым видиться измерение внутреннего источника 1.1V относительно текущего напряжения Vcc. Получилась такая функция, которая отлично работает:


uint16_t vBat;

uint16_t getVbat() {

  ADMUX = 1 << MUX3 | 1 << MUX2;        // Измеряем внутренний ИОН 1.1V относительно Vcc
  _delay_ms(10);                        // Ждем стабилизации напряжения

  ADCSRA |= _BV(ADSC);                  // Запускаме измерение
  while ((ADCSRA & (1 << ADSC)) == 1);  // Ждем окончания

  uint16_t result = ADCL;               // Забираем младший байт
  result |= ADCH << 8;                  // забираем старший байт

  vBat = 1099366L / result;             // Вычисляем Vcc в mV, 4.880V / 5.000V =  0.976
                                        // (1.1V*1024*1000) * 0.976 = 1099366L
  return vBat;

}

int main(void)
{

  ADCSRA |= _BV(ADEN);                  // Включаем АЦП
  ADCSRA |= _BV(ADPS1) | _BV(ADPS0);    // Частота АЦП clk/8 = 125кГц

  while (1) {
    ...
    getVbat();
    if(vBat < 3300) beep(2);
    ...
  }
}

Кроме постоянного контроля напряжения питания, мне надо иногда опрашивать датчик - измерять напряжение на PB3. Получилась вторая функция, которая тоже отлично работает:

uint16_t sens;
uint16_t getSens() {

  DDRB |= _BV(SENS_PW);                 // Питание сенсора на выход
  PORTB |= _BV(SENS_PW);                // Включаем
  DDRB &= ~_BV(SENS);                   // Сигнал сенсора на вход

  ADMUX = 1 << MUX0 | 1 << MUX1;    // Выбираем вход ADC3 (PB3)
  _delay_ms(10);

  ADCSRA |= _BV(ADSC);                  // Запускаме измерение
  while ((ADCSRA & (1 << ADSC)) == 1);  // Ждем окончания измерения
  PORTB &= ~_BV(SENS_PW);               // Выключаем питание сенсора

  uint16_t result = ADCL;             // Забираем младший байт
  result |= ADCH << 8;                // забираем старший байт

  sens = vBat / 1024.0 * result;       // Получаем текущее напряжение в mV

  return sens;
}

но если я пытаюсь использовать их совместно, ничего не получается, они начинают друг другу мешать, и такой код работает неправильно

int main(void)
{

  ADCSRA |= _BV(ADEN);                  // Включаем АЦП
  ADCSRA |= _BV(ADPS1) | _BV(ADPS0);    // Частота АЦП clk/8 = 125кГц

  while (1) {
    ...
    getVbat();
    if(vBat < 3300) beep(2);
    ...
    getSens();
    while(sens--) _delay_ms(1);
    blink(1);
  }
}

убираю вызов любой из функций (подставив константу в соответствующую переменную), и оставшаяся все делает правильно, вместе блин ни в какую… гуглил, но толком не нагуглил, предлагают использовать прерывания, в обработчике переключать каналы… нафига не понял, и зачем оно мне. Надо чтобы функции работали независимо друг от друга, просто вызвал и получил результат. Контроль напряжения будет идти постоянно по вачдогу, а опрос датчика только при необходимости. Наверняка забыл в какой-то регистр что-то записать, но весь моск сломал не вижу, олень оленем… 😵

We-BEER

Отбой, разобрались…
неправильно ждал, вместо
while ((ADCSRA & (1 << ADSC)) == 1);
надо
while (ADCSRA & (1 << ADSC));