Перейти к содержанию
    

Первый проект на АТМЕГА

Есть задача - в зависимости от скорости,оборотов и температуры двигателя, открывать клапан.

Температура - аналоговый сигнал, примерно от 4 до 1 вольт.

Обороты - импульсы, причем длинна импульса означает время, потраченное на работу одного цилиндра за полный оборот двигателя.

Скорость - 9 импульсов за полный оборот колеса.

Условие срабатывания клапана - температура более 50 гр. (~2.5 вольт), обороты более 1200 либо скорость более 60 км/ч.

 

Написал код (до этого с программированием не связывался практически совсем):

 

#include <avr/io.h>
#include <avr/interrupt.h>
volatile unsigned int spd_impulse = 0;
volatile unsigned int spd = 0;
volatile unsigned int rpm = 65535;
volatile unsigned int temp_flag = 0;
ISR(INT0_vect) {
spd_impulse++; // Считаем импульсы с датчика скорости
}
ISR(TIMER2_COMP_vect) {
spd = spd_impulse; // Передаем переменной spd колличество импульсов за 0.2 сек
spd_impulse = 0; // обнуляем счетчик
}
ISR(TIMER1_CAPT_vect) { 
if((TCCR1B & (1<<ICES1)) != 0 ) {
	TCNT1 = 0;
	ICR1 = 0;
	TCCR1B &= ~(1<<ICES1); // Прерывание по нисходящему фронту
}
else {
	TCCR1B |= (1<<ICES1); // Прерывание по восходящему фронту
	rpm = (ICR1); // Передаем переменной rpm количество тиков счетчика, поместившихся в длину импульса от тахометра
}
}
ISR(ADC_vect){
temp_flag = ADC; // Присваиваем переменной значение ЦАП
ADCSRA |=(1<<ADSC); // Запускаем ЦАП
}
int main(void)
{
sei();

// настройка прерываний по импульсам с датчика скорости
GICR |= (1 << INT0);
MCUCR |= (1 << ISC01)|(1 << ISC00); // Прерывание формируется по переднему фронту 

// Настройка таймера для подсчета количества импульсов скорости каждые 0.2 сек
OCR2 = 0xC4; // Отчитываем 0.2 секунды.
TIMSK |= (1<<OCIE2); // Прерывание по совпадению
TCCR2 |= 0x07; // делитель на 1024
TCCR2 |= (1<<WGM21); // Сброс при совпадении

// Настройка прерываний и таймера тахометр
TIMSK |= (1<<TICIE1); // разрешить прерывание на захват
TCCR1B |= (1<<ICES1); // Прерывание по восходящему фронту
TCCR1B |= (1<<CS10)|(1<<CS11); // тактирование счетчика 64
// Датчик температуры
ADCSRA |=(1<<ADEN)|(1<<ADIE)|(1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2);
ADMUX |=(1<<REFS0)|(1<<REFS1)|(0<<MUX0)|(0<<MUX1)|(0<<MUX2)|(0<<MUX3);
ADCSRA |=(1<<ADSC); // Запускаем ЦАП

DDRC &= ~(1<<0); // Порт датчика температуры
DDRD |= (1<<0);  // Порт включения клапана
DDRB |= (1<<1); // Индикация температура
DDRB |= (1<<2); // Индикация обороты
DDRB |= (1<<3); // Индикация скорость

volatile unsigned char rpm_flag = 0;
volatile unsigned char spd_flag = 0;
while(1) {
	// Задаем люфт условий открывания клапана
	if (rpm < 130) {
		rpm_flag = 1;
	}
	else if (rpm > 135) {
		rpm_flag = 0;
	}
	if (spd > 11) {
		spd_flag = 1;
	}
	else if (spd < 10) {
		spd_flag = 0;
	}
	// Проверяем условия
	if (temp_flag < 1000) { // Ждем пока температура достигнет рабочей
		ADCSRA &= ~(1<<ADSC)|(1<<ADIE); // Отключаем работу АЦП до следующего холодного старта
		PORTB |= (1<<1); // Сигнализируем, что температура в норме

		if (spd_flag == 1) { // Проверка флага скорости
			PORTD |= (1<<0); // Открываем клапан
			PORTB |= (1<<3); // Сигнализируем открытие клапана по скорости
		}
		else if (rpm_flag == 1 ) { // // Проверка флага оборотов
			PORTD |= (1<<0); // Открываем клапан
			PORTB |= (1<<2); // Сигнализируем открытие клапана по оборотам
		}
		else {
			PORTD &= ~(1<<0); // Отключаем клапан
		}
	}
	PORTB &= ~(1<<1)||(1<<2)|(1<<3); // Гасим всю индикацию.
}
}

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

Забыл добавить - делаю устройство на Atmega8

Изменено пользователем Herz

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Квалификаторы volatile на переменных rpm_flag и spd_flag излишни

sei(); переставить после настройки портов и прерываний

Сравнения типа spd_flag == 1 переделать на spd_flag != 0

 

По коду -

При старте переменная temp_flag находится в 0, и если ADC не успеет получить результат до проверки if (temp_flag < 1000) то if сработает и в первой же строке выключит ADC насовсем. Кажется это не совсем желаемое поведение :)

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

            ADCSRA &= ~(1<<ADSC)|(1<<ADIE); // Отключаем работу АЦП до следующего холодного старта

Тут нужны скобки. Потому что приоритет операции ~ выше, чем операции |. И у вас получается не то, что задумано.

        PORTB &= ~(1<<1)||(1<<2)|(1<<3); // Гасим всю индикацию.

Здесь тоже нужны скобки. И в одном месте перепутано | и ||.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Сравнения типа spd_flag == 1 переделать на spd_flag != 0

Вот это пока не дошло, почему так лучше.

По коду -

При старте переменная temp_flag находится в 0, и если ADC не успеет получить результат до проверки if (temp_flag < 1000) то if сработает и в первой же строке выключит ADC насовсем. Кажется это не совсем желаемое поведение :)

Согласен, может такое случиться. Спасибо!

Тут нужны скобки. Потому что приоритет операции ~ выше, чем операции |. И у вас получается не то, что задумано.

Здесь тоже нужны скобки. И в одном месте перепутано | и ||.

А как правильно поставить скобки?

так PORTB &= ~((1<<1)|(1<<2)|(1<<3));

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

так PORTB &= ~((1<<1)|(1<<2)|(1<<3));

Да, так.

(1<<1) - даёт единичку в первом разряде (00000010),

(1<<2) - даёт единичку во втором разряде (00000100),

(1<<3) - даёт единичку в третьем разряде (00001000),

потом вы их комбинируете при помощи побитовой операции или (|) (00001110),

и после этого инвертируете (~): 11110001.

Таким образом, делая операцию побитового или (&) порта PORTB и значения 11110001 вы сбрасываете в 0 биты 1, 2 и 3.

Как-то так.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Сравнения типа spd_flag == 1 переделать на spd_flag != 0

Масло маслянное. Просто

if( spd_flag )

 

И раздавать каждому биту по байту как то некрасиво. Битовые поля разумнее смотрятся.

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вот это пока не дошло, почему так лучше.
Потому что сравнение с 1 будет выполняться именно как сравнение с константой 1, в то время как в С для логического значения 0 считается как false , а все остальное (не только 1) как true. У компилятора больше возможностей для более оптимальной генерации кода, если вы его не будете искуственно ограничивать в определении, что есть false а что есть true.

 

Пример (несколько надуманный, но все же):

Код с 1 и 0:

void call_some(void);

void test(unsigned char v)
{
  if (v==1) call_some();
  if (v==0) call_some();
}

результат -

test(unsigned char):
        cpi r24,lo8(1)
        breq .L5
        cpse r24,__zero_reg__
        ret
        rcall call_some()
        ret
.L5:
        rcall call_some()
        ret

Код без таких ограничений:

void call_some(void);

void test(unsigned char v)
{
  if (v) call_some();
  if (!v) call_some();
}

Результат:

test(unsigned char):
        rcall call_some()
        ret

Как говорится - почуствуйте разницу :)

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Зачем задействовать прерывание INT0 не проще на счётчик подать (пусть сам считает)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Зачем задействовать прерывание INT0 не проще на счётчик подать (пусть сам считает)

Мне нужно знать время между импульсами, чтобы посмотреть скорость. Если подать на счетчик, он мне просто посчитает импульсы, дальше что с ними делать? Запускать еще один таймер, чтобы узнать за какое время первый таймер насчитал импульсы?

Изменено пользователем ddwrt

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Мне нужно знать время между импульсами, чтобы посмотреть скорость. Если подать на счетчик, он мне просто посчитает импульсы, дальше что с ними делать? Запускать еще один таймер, чтобы узнать за какое время первый таймер насчитал импульсы?

Для измерения интервалов между импульсами гораздо лучше использовать таймер, а не прерывания. В LPC17xx, например, для этого имеется опция CAPTURE в его таймерах - позволяет защёлкивать значение счётчика таймера по внешнему сигналу (с одновременным прерыванием). Я думаю и в других МК есть похожая возможность.

А почему лучше через таймер - подумайте сами.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Для измерения интервалов между импульсами гораздо лучше использовать таймер, а не прерывания. В LPC17xx, например, для этого имеется опция CAPTURE в его таймерах - позволяет защёлкивать значение счётчика таймера по внешнему сигналу (с одновременным прерыванием). Я думаю и в других МК есть похожая возможность.

А почему лучше через таймер - подумайте сами.

 

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

Изменено пользователем ddwrt

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

Значит неправильно выбрали МК. Выберите другой, в котором больше таймеров. Иначе страдает точность.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Значит неправильно выбрали МК. Выберите другой, в котором больше таймеров. Иначе страдает точность.

В задаче точность скорости не нужна, важнее обороты. Просто валялся без дела atmega8, вот его и решил пристроить.

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

Изменено пользователем Herz

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

ddwrt, не надо "растягивать" сообщения, разбавляя их пустыми строками. Читабельность от этого не улучшается.

Кроме того, нет слова "проэкт" в русском языке. Исправил заголовок.

Модератор.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...