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

Проблемы С Кодом, Прерывание Вызывается Дважды?


123654789

Рекомендуемые сообщения

Пишу прошивку для управления всяким с пульта от телека, столкнулся с непонятным мне поведением программы (об этом ниже).

Прошивка:

#define F_CPU 4800000L // 4.8 Мгц
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

#define INVERT_PIN(x) PORTB ^= x
/*
   PB2 (7) - реле (ВЫХОД).
   PB1 (PCINT1) (6) - ИК-приёмник (ВХОД).
   PB0 (PCINT0) (5) - сенсорная панель (ВХОД).
   PB3 (2) - светодиод (ВЫХОД).
   PB4 (PCINT4) (3) - кнопка (ВХОД).
*/

volatile char is_sensor_active = 0;
volatile unsigned int rc_code = 0;
volatile unsigned int etalon_rc_code = 0;

// http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__util__delay_1gad22e7a36b80e2f917324dc43a425e9d3.html
// 262.14 / 4.8 = 54.61 мс - это максимальная задержка для данной частоты, до которой можно использовать _delay_ms.
void delay_ms(unsigned int period) {
   while(--period) {
    _delay_ms(1);
   }
}

ISR(PCINT0_vect) {
   is_sensor_active = 1;
}

int main() {
   DDRB = 0b00001100;
   // Срабатывание прерывания при смене логического уровня на ножках.    
   MCUCR = 0b00000001;
   // Прерывание генерятся при изменении уровня на ножках.
   GIMSK = 0b00100000;
   // Выбор генерящих прерывание пинов.
   PCMSK = 0b00000010;
   sei();
   while(1) {
    if( is_sensor_active ) {
	    cli();
	    rc_code = 0;
	    for( int i = 0; i < sizeof(rc_code) * 8; ++i) {
		    rc_code = (rc_code << 1) + ((PINB & 0b00000010) >> 1);
		    _delay_ms(16);		    
	    }
	    if( etalon_rc_code == 0)
		    etalon_rc_code = rc_code;
	    if( etalon_rc_code == rc_code) {
		    INVERT_PIN(0b00000100);		    
	    } else {
		    for( int i = 0; i < 10; ++i) {
			    INVERT_PIN(0b00001000);
			    _delay_ms(30);
		    }
	    }
	    is_sensor_active = 0;
	    delay_ms(240);
	    sei();	    
    }
   }
   return 0;
}

Как я ожидаю она должна работать: как только на ножке 6 сменится логический уровень, с ножки же 6 будет считано четырехбайтовое беззнаковое целое, принято за эталон команды и на ножке 7 установится лог. 1. При повторном зажигании прерывания снова будет считана команда, сравнена с эталонной и, если совпадает, инвертируется ножка 7. В противном случае помигать ножкой 2.

Как оно на самом деле работает: вне зависимости от была ли команда верной или нет, контроллер дергает ножкой 2. Отсюда я делаю вывод, прерывание почему-то зажигается дважды. Но ведь не должно же! Вопрос: что же я упустил, приводящее к такому поведению? При обработке команды я прерывания запрещаю же, потом обратно включаю.

Изменено пользователем 123654789
Ссылка на комментарий
Поделиться на другие сайты

У AVR не нужно сбрасывать флаг прерывания в обработчике, чтобы прерывание не вызывалось по кругу?

И ещё, на всякий случай занулять is_sensor_active лучше после задержки, а не перед, чтобы прерывание от последнего бита не установило в 1 лишний раз.

Изменено пользователем Yurkin2015
Ссылка на комментарий
Поделиться на другие сайты

Проблема в другом: блок

for( int i = 0; i < 10; ++i) {
INVERT_PIN(0b00001000);
_delay_ms(30);
}

вызывается всякий раз, вне зависимости от принятого кода. Притом блок с условием etalon_rc_code == rc_code работает как ожидалось: срабатывает, когда пришел запомненный код и не срабатывает, когда пришел незапомненный (сужу, жамкая разные кнопки на пульте). Согласно коду, проблемная ситуация может возникнуть только если в блок считывания и проверки выполняется более одного раза.

Тем временем, в паспорте читаю (выделение моё):

There are basically two types of interrupts. The first type is triggered by an event that sets the

Interrupt Flag. For these interrupts, the Program Counter is vectored to the actual Interrupt Vec-

tor in order to execute the interrupt handling routine, and hardware clears the corresponding

Interrupt Flag. Interrupt Flags can also be cleared by writing a logic one to the flag bit position(s)

to be cleared. If an interrupt condition occurs while the corresponding interrupt enable bit is

cleared, the Interrupt Flag will be set and remembered until the interrupt is enabled, or the flag is

cleared by software. Similarly, if one or more interrupt conditions occur while the Global Interrupt

Enable bit is cleared, the corresponding Interrupt Flag(s) will be set and remembered until the

Global Interrupt Enable bit is set, and will then be executed by order of priority.

Таким образом, моим следующим шагом будет попробовать очищать флаг PCIF в регистре GIFR перед включением прерывания (после распознавания команды и прочей логики) и посмотреть, что из этого выйдет.

Изменено пользователем 123654789
Ссылка на комментарий
Поделиться на другие сайты

Сравнительное тестирование аккумуляторов EVE Energy и Samsung типоразмера 18650

Инженеры КОМПЭЛ провели сравнительное тестирование аккумуляторов EVE и Samsung популярного для бытовых и индустриальных применений типоразмера 18650. 

Для теста были выбраны аккумуляторы литий-никельмарганцевой системы: по два образца одного наименования каждого производителя – и протестированы на двух значениях тока разряда: 0,5 А и 2,5 А. Испытания проводились в нормальных условиях на электронной нагрузке EBD-USB от ZKEtech, а зарядка осуществлялась от лабораторного источника питания в режиме CC+CV в соответствии с рекомендациями в даташите на определенную модель. Подробнее>>

Реклама: АО КОМПЭЛ, ИНН: 7713005406, ОГРН: 1027700032161

И надо выкинуть все запреты прерывания sei и cli. Они только мешают.

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

После чтения и паузы зануляем is_sensor_active=0 и снова ждём начала следующей посылки. Главное, чтобы пауза была меньше времени между посылками кодов.

Судя по описанию, флаги сбрасываются автоматически при переходе на обработчик прерывания - тогда ничего и делать не надо в обработчике, а только устанавливать is_sensor_active = 1.

Изменено пользователем Yurkin2015
Ссылка на комментарий
Поделиться на другие сайты

Новый аккумулятор EVE серии PLM для GSM-трекеров, работающих в жёстких условиях (до -40°С)

Компания EVE выпустила новый аккумулятор серии PLM, сочетающий в себе высокую безопасность, длительный срок службы, широкий температурный диапазон и высокую токоотдачу даже при отрицательной температуре. 

Эти аккумуляторы поддерживают заряд при температуре от -40/-20°С (сниженным значением тока), безопасны (не воспламеняются и не взрываются) при механическом повреждении (протыкание и сдавливание), устойчивы к вибрации. Они могут применяться как для автотранспорта (трекеры, маячки, сигнализация), так и для промышленных устройств мониторинга, IoT-устройств. Подробнее параметры и результаты тестов новой серии PLM по ссылке.

Реклама: АО КОМПЭЛ, ИНН: 7713005406, ОГРН: 1027700032161

Тут до меня дошло, наконец, что была за проблема в Вашем коде: при поступлении первого бита прерывания запрещались, читался весь код, пауза, затем прерывания разрешались. Но пока данные читались, были ещё прерывания от битов. Они были запрещены, но запомнились. Поэтому сразу же после разрешения прерываний тут же вызывалось отложенное, запомненное прерывание и переменная is_sensor_active устанавливалась 1, хотя никакой посылки и не было. Программа честно читала все нули с ноги, или все единицы, и записывала, как эталон.

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

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

Литиевые батарейки и аккумуляторы от мирового лидера  EVE в Компэл

Компания Компэл, официальный дистрибьютор EVE Energy, бренда №1 по производству химических источников тока (ХИТ) в мире, предлагает продукцию EVE как со склада, так и под заказ. Компания EVE широко известна в странах Европы, Америки и Юго-Восточной Азии уже более 20 лет. Недавно EVE была объявлена поставщиком новых аккумуляторных элементов круглого формата для электрических моделей «нового класса» компании BMW.

Продукция EVE предназначена для самого широкого спектра применений – от бытового до промышленного. Подробнее>>

Реклама: АО КОМПЭЛ, ИНН: 7713005406, ОГРН: 1027700032161

Да, всё именно так: проблемы вызывает поднятый флаг прерывания. Решить можно двумя способами: убрать из кода запрещение/разрешение прерываний (в данном случае запрещать ничего не требуется, поэтому я выбрал этот путь) или вручную сбрасывать флаг прерывания через регистр GIFR.

Исправленный код прилагаю.

#define F_CPU 4800000L // 4.8 Мгц
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#define INVERT_PIN(x) PORTB ^= x
/*
   PB2 (7) - реле (ВЫХОД).
   PB1 (PCINT1) (6) - ИК-приёмник (ВХОД).
   PB0 (PCINT0) (5) - сенсорная панель (ВХОД).
   PB3 (2) - светодиод (ВЫХОД).
   PB4 (PCINT4) (3) - кнопка (ВХОД).
*/
volatile char is_sensor_active = 0;
volatile unsigned int rc_code = 0;
volatile unsigned int etalon_rc_code = 0;
// http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__util__delay_1gad22e7a36b80e2f917324dc43a425e9d3.html
// 262.14 / 4.8 = 54.61 мс - это максимальная задержка для данной частоты, до которой можно использовать _delay_ms.
void delay_ms(unsigned int period) {
   while(--period) {
    _delay_ms(1);
   }
}
ISR(PCINT0_vect) {
   is_sensor_active = 1;
}
int main() {
   DDRB = 0b00001100;
   // Срабатывание прерывания при смене логического уровня на ножках.   
   MCUCR = 0b00000001;
   // Прерывание генерятся при изменении уровня на ножках.
   GIMSK = 0b00100000;
   // Выбор генерящих прерывание пинов.
   PCMSK = 0b00000010;
   sei();
   while(1) {
    if( is_sensor_active ) {
	    rc_code = 0;
	    for( int i = 0; i < sizeof(rc_code) * 8; ++i) {
		    rc_code = (rc_code << 1) + ((PINB & 0b00000010) >> 1);
		    _delay_ms(16);		   
	    }
	    if( etalon_rc_code == 0)
		    etalon_rc_code = rc_code;
	    if( etalon_rc_code == rc_code) {
		    INVERT_PIN(0b00000100);		   
	    } else {
		    for( int i = 0; i < 10; ++i) {
			    INVERT_PIN(0b00001000);
			    _delay_ms(30);
		    }
	    }
	    is_sensor_active = 0;
	    delay_ms(240);	   
    }
   }
   return 0;
}

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

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

Это не хорошо. За время приема 48 битов период передатчика и период опроса неизбежно разъедутся по времени. Хорошо, если момент опроса уедет внутрь бита. Но если эти 16мс в реале немного меньше, чем период передатчика, то за время приёма кода опрос сдвинется и выйдет за пределы бита. Таким образом предыдущий бит почитается дважды, будет ошибка приёма.

Для исправления ситуации после обнаружения is-sensor-active = 1 надо добавить задержку на половину длительности бита, 8мс, а уже потом начинать цикл опроса ножки. Тогда момент опроса будет посредине бита, и небольшие расхождения периодов передатчика и приёмника уже не волнуют.

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

На счет прерываний лучше поступить немного по-другому. При обработке флага запретить только PCINT, а лучше - только PCINT.1. Если будете использовать другие прерываний, от таймеров, от АЦП, мало ли от чего еще, они не будут заблокированы. А после приема данных, сбрасываете флаг (мало ли как он мог взвестись), и разрешаете именно нужное прерывание.

А на аппаратных модулях не получается сделать? Таймер, UART, SPI, USI теоретически могли бы пригодиться.

Ну и традиционно, совет по форматированию:

DDRB = 0b00001100;

Неплохая запись, но можно использовать DDRB = (1<<2 | 1<<3);

MCUCR = 0b00000001;

Как и с большинством регистров настроек, лучше записать сдвигом. MCUCR = (1<<ISC00); что сразу даст понять, что в данном случае заполнять его не нужно вовсе, он не влияет на PCINT, а прерывания INT0, INT1,... не используются

GIMSK = 0b00100000;

Аналогично, лучше записать GIMSK = (1<<PCIE); чтобы не пришлось лезть в даташит смотреть, за что же отвечает 5-й бит.

PCMSK = 0b00000010;

Как и с DDRB можно записать PCMSK = (1<<1); но, в принципе, обе записи неплохи.

Ругался на отсутствие форматирования исходного кода (включая отсутствие осмысленных комментариев и наличие неубранного после конфигуратора мусора) не менее 15 раз.

Часть моих наработок.

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

Советы применил, спасибо. Обнаружил еще одну неточность: выше я пишу

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

Это неверно. int в AVR занимает 2 байта, не 4. Источник: https://gcc.gnu.org/wiki/avr-gcc

Для четырехбайтового целого следовало использовать long.

А на аппаратных модулях не получается сделать?

Не понимаю вопроса, аппаратные модули это что в данном контексте? Конструкторы a-la Arduino? Я же так в контроллеры не научусь никогда.

Изменено пользователем 123654789
Ссылка на комментарий
Поделиться на другие сайты

Я же написал - таймер, UART, USI. Я не знаю, что у вас за контроллер и какие модули там доступны. Arduino или cvavr я точно не имел в виду.

Ругался на отсутствие форматирования исходного кода (включая отсутствие осмысленных комментариев и наличие неубранного после конфигуратора мусора) не менее 15 раз.

Часть моих наработок.

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

В нем есть обычный таймер, собака и, в порядке извращения, АЦП. Если таймер нигде не используется, можно воспользоваться им, будет немного точнее, чем задержками. Достаточно ли точности у watchdog и АЦП не знаю (сомнительно), как и целесообразность подобных извращений.

Ругался на отсутствие форматирования исходного кода (включая отсутствие осмысленных комментариев и наличие неубранного после конфигуратора мусора) не менее 15 раз.

Часть моих наработок.

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

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

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

Гость
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Ответить в этой теме...

×   Вставлено с форматированием.   Восстановить форматирование

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

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

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

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

Загрузка...
  • Последние посетители   0 пользователей онлайн

    • Ни одного зарегистрированного пользователя не просматривает данную страницу
×
×
  • Создать...