|
Первый проект на АТМЕГА |
|
|
|
Apr 18 2016, 09:19
|
Группа: Участник
Сообщений: 6
Регистрация: 18-04-16
Пользователь №: 91 345

|
Есть задача - в зависимости от скорости,оборотов и температуры двигателя, открывать клапан. Температура - аналоговый сигнал, примерно от 4 до 1 вольт. Обороты - импульсы, причем длинна импульса означает время, потраченное на работу одного цилиндра за полный оборот двигателя. Скорость - 9 импульсов за полный оборот колеса. Условие срабатывания клапана - температура более 50 гр. (~2.5 вольт), обороты более 1200 либо скорость более 60 км/ч. Написал код (до этого с программированием не связывался практически совсем): CODE #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 - Apr 20 2016, 08:11
|
|
|
|
|
 |
Ответов
(1 - 14)
|
Apr 18 2016, 10:17
|
Гуру
     
Группа: Свой
Сообщений: 3 123
Регистрация: 7-04-07
Из: Химки
Пользователь №: 26 847

|
Квалификаторы volatile на переменных rpm_flag и spd_flag излишни sei(); переставить после настройки портов и прерываний Сравнения типа spd_flag == 1 переделать на spd_flag != 0 По коду - При старте переменная temp_flag находится в 0, и если ADC не успеет получить результат до проверки if (temp_flag < 1000) то if сработает и в первой же строке выключит ADC насовсем. Кажется это не совсем желаемое поведение
|
|
|
|
|
Apr 18 2016, 10:22
|

фанат дивана
     
Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684

|
Цитата(ddwrt @ Apr 18 2016, 14:19)  Код ADCSRA &= ~(1<<ADSC)|(1<<ADIE); // Отключаем работу АЦП до следующего холодного старта Тут нужны скобки. Потому что приоритет операции ~ выше, чем операции |. И у вас получается не то, что задумано. Цитата(ddwrt @ Apr 18 2016, 14:19)  Код PORTB &= ~(1<<1)||(1<<2)|(1<<3); // Гасим всю индикацию. Здесь тоже нужны скобки. И в одном месте перепутано | и ||.
--------------------
Если бы я знал, что такое электричество...
|
|
|
|
|
Apr 18 2016, 13:07
|
Группа: Участник
Сообщений: 6
Регистрация: 18-04-16
Пользователь №: 91 345

|
Цитата(XVR @ Apr 18 2016, 11:17)  Сравнения типа spd_flag == 1 переделать на spd_flag != 0 Вот это пока не дошло, почему так лучше. Цитата(XVR @ Apr 18 2016, 11:17)  По коду - При старте переменная temp_flag находится в 0, и если ADC не успеет получить результат до проверки if (temp_flag < 1000) то if сработает и в первой же строке выключит ADC насовсем. Кажется это не совсем желаемое поведение  Согласен, может такое случиться. Спасибо! Цитата(AHTOXA @ Apr 18 2016, 11:22)  Тут нужны скобки. Потому что приоритет операции ~ выше, чем операции |. И у вас получается не то, что задумано. Здесь тоже нужны скобки. И в одном месте перепутано | и ||. А как правильно поставить скобки? так PORTB &= ~((1<<1)|(1<<2)|(1<<3));
|
|
|
|
|
Apr 18 2016, 13:32
|

фанат дивана
     
Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684

|
Цитата(ddwrt @ Apr 18 2016, 18:07)  так PORTB &= ~((1<<1)|(1<<2)|(1<<3)); Да, так. (1<<1) - даёт единичку в первом разряде (00000010), (1<<2) - даёт единичку во втором разряде (00000100), (1<<3) - даёт единичку в третьем разряде (00001000), потом вы их комбинируете при помощи побитовой операции или (|) (00001110), и после этого инвертируете (~): 11110001. Таким образом, делая операцию побитового или (&) порта PORTB и значения 11110001 вы сбрасываете в 0 биты 1, 2 и 3. Как-то так.
--------------------
Если бы я знал, что такое электричество...
|
|
|
|
|
Apr 18 2016, 13:50
|
Группа: Участник
Сообщений: 6
Регистрация: 18-04-16
Пользователь №: 91 345

|
спасибо! теперь понятно.
|
|
|
|
|
Apr 18 2016, 14:13
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
QUOTE (XVR @ Apr 18 2016, 13:17)  Сравнения типа spd_flag == 1 переделать на spd_flag != 0 Масло маслянное. Просто if( spd_flag ) И раздавать каждому биту по байту как то некрасиво. Битовые поля разумнее смотрятся.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
Apr 18 2016, 14:37
|
Гуру
     
Группа: Свой
Сообщений: 3 123
Регистрация: 7-04-07
Из: Химки
Пользователь №: 26 847

|
Цитата Вот это пока не дошло, почему так лучше. Потому что сравнение с 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 Как говорится - почуствуйте разницу
|
|
|
|
|
Apr 20 2016, 04:47
|
Группа: Участник
Сообщений: 6
Регистрация: 18-04-16
Пользователь №: 91 345

|
Цитата(SlavaV @ Apr 19 2016, 14:19)  Зачем задействовать прерывание INT0 не проще на счётчик подать (пусть сам считает) Мне нужно знать время между импульсами, чтобы посмотреть скорость. Если подать на счетчик, он мне просто посчитает импульсы, дальше что с ними делать? Запускать еще один таймер, чтобы узнать за какое время первый таймер насчитал импульсы?
Сообщение отредактировал ddwrt - Apr 20 2016, 04:49
|
|
|
|
|
Apr 20 2016, 06:06
|
Группа: Участник
Сообщений: 6
Регистрация: 18-04-16
Пользователь №: 91 345

|
Цитата(jcxz @ Apr 20 2016, 06:42)  Для измерения интервалов между импульсами гораздо лучше использовать таймер, а не прерывания. В LPC17xx, например, для этого имеется опция CAPTURE в его таймерах - позволяет защёлкивать значение счётчика таймера по внешнему сигналу (с одновременным прерыванием). Я думаю и в других МК есть похожая возможность. А почему лучше через таймер - подумайте сами. Если посмотреть код, то там уже по такому принципу снимается сигнал с тахометра. Если бы был еще один таймер с внешним захватом, думаю скорость также сделал. Поэтому пришлось выкручиваться через прерывание.
Сообщение отредактировал ddwrt - Apr 20 2016, 06:07
|
|
|
|
|
Apr 20 2016, 08:02
|
Группа: Участник
Сообщений: 6
Регистрация: 18-04-16
Пользователь №: 91 345

|
Цитата(jcxz @ Apr 20 2016, 07:15)  Значит неправильно выбрали МК. Выберите другой, в котором больше таймеров. Иначе страдает точность. В задаче точность скорости не нужна, важнее обороты. Просто валялся без дела atmega8, вот его и решил пристроить. Осталась проблема, если по каким-то причинам сигнал тахометра резко пропадает, то rpm зависнет на последнем значении и клапан будет оставаться открытым до следующего сигнала. Это же может произойти, даже если будет одиночный короткий импульс от помехи, во время включения.
Сообщение отредактировал Herz - Apr 20 2016, 08:12
|
|
|
|
|
  |
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|