Теряются значения переменных основной программы при выходе из прерывания.
- Войдите на сайт для отправки комментариев
Arduino Mega 2560.
Суть проблемы:
Master с интервалом в 1 минуту запрашивает поочередно температуры измеренные 4-мя датчиками.
По приходу первого байта запроса срабатывает обработчик прерывания ISR(USART0_RX_vect).
При некорректном принятии запрос игнорируется, при корректном -
в обработчике формируется массив символов запроса, определяется условный адрес запрашиваемого датчика, формируется ответ Masterу, отправляется, и здесь выход из прерывания. Идет 4 запроса-ответа подряд, естественно еще минимум три раза вызывается прерывание.
Первый ответ содержит действительную температуру датчика, но последующие три ответа
(2, 3 и 4 датчики) содержат ноль по температуре.
Причина: обнуляются переменные температур 2, 3 и 4 при первом выходе из прерывания по USART.
Переменные температур обьявлены как volatile.
Где ошибка? Способ решения?
#include <avr/interrupt.h> //библиотека прерываний #include <OneWire.h> #include <DallasTemperature.h> ... //переменные задействованные в void loop() и в обработчике //прерывания ISR(USART0_RX_vect) определены как volatile float volatile float val_T1; температура 1-го датчика volatile float val_T2; температура 1-го датчика volatile float val_T3; температура 1-го датчика volatile float val_T4; температура 1-го датчика volatile unsigned char Symb; volatile unsigned char s[16]; ... // функция инициализации прерывания по приходу байта ч/з USART void USART_Init(int baudrate ) { UBRR0H = baudrate>>8; UBRR0L = baudrate; UCSR0B = (1<<RXCIE0)|(1<<TXCIE0)|(1<<RXEN0)|(1<<TXEN0); UCSR0C = (1<<UCSZ01)|(1<<UCSZ00); sei(); } ... void setup() { USART_Init(103); ... } ... void USART_Transmit( unsigned char data )//Функция отправки данных {...} unsigned char USART_Receive( void )//Функция приема данных {...} ... void loop() { * * * * * ... sensors_1.requestTemperatures(); val_K1T1 = sensors_1.getTempC(add_K1T1); val_K1T2 = sensors_1.getTempC(add_K1T2); val_K1T3 = sensors_1.getTempC(add_K1T3); sensors_2.requestTemperatures(); val_K1T4 = sensors_2.getTempC(add_K1T4); } ... //Обрабатываем прерывание по поступлению байта ISR(USART0_RX_vect) { // UDR0; //прининятый байт s[14] = 0; if (UDR0 == 35) { for (byte ii=2; ii<=14; ii++) {s[ii] = USART_Receive();} if (s[14] == 13) { ArrReq[0] = s[2]; ArrReq[1] = s[3]; ... // определяется условный адрес опрашиваемого датчика Sens_addres = 128*ArrOut[1] + 64*ArrOut[2] + ... + ArrOut[8]; ... // переменной Temp присваивается значение температуры измеренное датчиком if (Sens_addres == 120) {Temp = val_T1;} 1-й датчик if (Sens_addres == 121) {Temp = val_T2;} 2-й датчик if (Sens_addres == 122) {Temp = val_T3;} 3-й датчик if (Sens_addres == 123) {Temp = val_T4;} 4-й датчик ... // формируется строка символов для ответа ... // отправка строки символов, (ответ на запрос Master'a): delay(2); // требуется по протоколу обмена с Master'ом for (byte i=1; i<=27; i++) {USART_Transmit(ArrPac[i]);} } } }
Тут у вас не весь листинг. То что вы описали возможно из-за нехватки памяти. Компилятор не контролирует пересечение адресов памяти с адресами стека или уход указателя за пределы массива и пр. и др. Кроме того есть такие "интересные" директивы как ststic и public. Директивы можно просто поробывать. А вот первые две возможные причины вычислить трудно. Но скорее всего проблема в тек кусках кода которые вы скрыли.
В чем проблема, не знаю, куча текста скрыта. Но это не настолько важно, сколько вызов в обработчике прерывания по получению данных вызывается приём остальных данных в цикле. А в конце ещё и передача данных. Обработчик прерываний должен быть коротким, не вызывать функций задержки и т.п. А обработку того, что принял обработчик прерываний нужно делать в loop.
brokly, kisoft спасибо за разьяснение и направление.
Пробую static. Бегло не прошло: компилятор не ругается о невидимости переменной в обработчике прерывания но и в ответе пока ноли по температуре. Изучу видимости, продолжу.
Обрабатывать полученные байты в loop не могу т.к. там процессы опросов датчиков медленные и непредсказуемые по времени, а ответ на запрос нужно дать в течении 2-5 мс после прихода последнего 14-го байта запроса. Как выйти из обработчика в другое место той же loop (где и описать формирование и отправку ответа) не знаю. Вопрос: можно ли выйти из обработчика прервания по приему байта USART в другое место программы, не туда где прерывание было вызвано? (интернет по этому поводу читал, но готовых решений ни одного не видел, только теория с подменой адреса возврата в стеке. высшая математика!)
Задумался, можно ли организовать "свое" хранение данных в дальних нетрогаемых программой регистрах оперативной памяти (не флеш!), по надобности обновлять там значения температур вычисленные в loop и оттуда же считывать обработчиком прерывания. Будет однозначно. Уйти от вижу-невижу-обноляю в работе с прерываниями, от ограничений типа короткого кода. Или это лишнее, есть "правильные" пути?
Весь код:
Если Статик для глобальной переменной, то здесь бесполезно.
С loop понятно, запросы с 1-Wire это должно быть море задержек и скорей всего прерывания запрещаются, а если нет, то труба 1-Wire, поплывут времянки.
Выход из прерывания не в точку вызова - проще застрелиться, ни разу не выгодно, потому что не факт, что вызов был из loop. Можно, но костыли и головняк.
Если на приём нужно 14 байт, то правильней организовать счётчик принятых байтов и на 14-ом реагировать, будет лучше точно. В любом случае как сейчас костыльно, лотерея, как луна по вернётся, скорее работать не будет.
Вывод ответа можно организовать также по прерываниями, т.е. сформировать буфер вывода и пнуть вывод.
Вообще, скрестить 1-Wire программный с асинхронными прерываниями, думаю не реально. Если хотя бы 1-Wire был аппаратным, типа DS2480b, тогда было бы чуть проще.
В целом, сейчас задачу оценить сложно из-за отсутствия ТЗ и ограниченности атмеги (отчасти).
А ещё, отлаживать всё и сразу это иметь кучу проблем. Но в любом случае, даже если получится по частям, то при соединении работать не будет.
Много букв, но такова се ля ви.
Изучаю работу оперативной памяти и возможность ручной работы с ней. Получится обойти потерю переменных - отпишусь.
Вообще передать все температуры без потерь удается если не выходить из прерывания, а передавать все 4 температуры циклом: вначале обработки стоит метка, а в конце обработки goto на метку. Но тогда не могу выйти из прерывания, бесконечный цикл. Пробовал аппаратно после пропадания активности на проводах Rx Tx в перезагруз по выводу RESET, но там нужно не просто сбросить уровень в LOW но и отпустить иначе не загрузится. Хоть реле ставь, оно с пружиной. Но это уже другая история. Если поставить счетчик отработанных циклов и условие: после четвертого цикла на выход из прерывания, вот тут температуры 2, 3, 4 и теряются!
Код обработчика прерывания который работал с циклами выложу завтра, затер, писать по новой. Может там что укажет на ошибку.
Ок, Вы по какой то причине считаете, что у Вас что то теряется. И не понимаете о чем я говорю. Тогда не буду тратить наше время. Удачи.
Думаю, что если это было бы возможно, то Вы бы удивились, что прерывания у Вас вызываются не 2 не 3 и не 4 раза. И не в те моменты, что Вы предполагаете.
Решено.
В одном прерывании обрабатываются все 4 запроса и даются ответы Masterу. Прибор работает.
Факт потери значений переменными проверен но механизм не исследован.
Тема закрыта.
Благодарности brokly и kisoft.
В догонку
стоит избегать применения таких типов как fliat и double. Они генерят код интенсивно использующий ram, flash и вычислительные мощности МК. Лучше применять целые. Если требуются цифры после запятой (например десятая доля градуса) лучше паковать в целое умножив на 10. Т.е. Например 26.4 градуса хранить как 264. При всех манипуляциях это учитывать, это позволяет полностью избавится от типов float и double - все операции с ними делаются програмно, у МК нет никаких средств для работы с такими типами. Отсюда неразумное разбрасывание ресурсами
Совсем короткая программа. Крутится цикл опрашивается датчик и выводятся на LCD текущее и максимальное значение. Время от времени нужно нажать на кнопку, что бы сбросить максимальное значение. Попытался повесить кнопку на прерывание. В перывании единственная команда - button = true. Оказалось, что при выходе из прерывания случайным образом button превращается в ноль. Объявлена как volatile. Проверял - внутри всегда присваивается true (1). Единственно что обнаружил - при прерывании 0 реже, при прерывании 1 существенно чаще.
Это всё было на Nano V3. На Leonardo тот же код работает без проблем.
nik182 LCD библиотеки не смотрел но наверное именно они содежат обработчики прерываний, которые также срабатывают в момент нажатия Вами кнопки и иногда не дают времени "зафиксировать" переменную. На другой плате просто случайно выбран пин который позволяет избежать этой накладки.
На практике просто цепляйте нажатие кнопки не на прерывание, а на опрос пина в последовательном коде. Если есть места где есть delay(очень долго) это обычно в библиотеках DallasTemperature после команды датчикам "подготовить температуры", то :
При этом нужно подкорректировать библиотеку DallasTemperature.cpp - убрать (закомментировать) delay()
Как то так ...
(коды взяты из проекта работающего регулятора климата для выращивания грибов)
У меня примерно так всё и работает. Но хотелось красиво. По прерыванию.
В основной программе по условию из прерывания обрабатывались две float переменные. Одна обнулялась, другой присваивалось стандартное значение. Перенёс их в прерывание. Обнулённая всегда воэвращается обнулённой. А вот той, которой присваивается значение, случайным образом возвращается обнулённой. Этого я на понимаю. Serial.print из прерывания дает нормальное значение, просле выхода из прерывания переменная иногда обнулена. Меня напрягает "иногда". Непонятен механизм обнуления и нет возможности 100% воспроизведения этого эффекта. На Leonardo ноги те же. Работает 100% правильно.
Прошу прощения, случайно пометил как спам последнее сообщение. С телефона иногда бывают казусы. Ещё раз, сорри, возможно админы вернут. Спасибо.
По делу, к сожалению ничего не могу сказать. Разве что в LCD куча delayMicroseconds используется, как раз сейчас копаю это все. Если получится, будет вывод на лсиди без задержек, но там ещё пока не все как надо.
Все переменные обрабатываемые в прерываниях должны быть инициализированы с приставкой volatile
http://arduino.ru/Reference/Volatile
http://robocraft.ru/blog/arduino/45.html
http://alenacpp.blogspot.com/2006/04/volatile.html
Цитаты относительно использования volatile:
Т.о. переменная получается как бы «расшарена». Т.е. значение переменной могут изменять разные части программы — обработчики прерываний, подпрограммы, функции.