Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Многопоточность на attiny2313
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > MCS51, AVR, PIC, STM8, 8bit
aivs
Продолжаю изучать программирование под avr.
Написал программу "Часы", добавил три кнопки для выставления времени, кнопки обрабатываются по прерыванию от Таймера каждые 0.1 сек, время тикает от того же таймера, все красиво и четко работает.
Затем я задействовал термодатчик ds18b20 с шиной 1ware и написал программу для показа температуры на дисплее hd44780, датчик опрашивается по прерыванию от таймера каждые 0.1 сек, прекрасно работает.
А затем я решил совместить эти две программы, чтобы часы в одной части дисплея работали, а температура в другой части. Получилось кривенько, при опросе датчика прерывания запрещены, соответственно на нажатия кнопок реакция заторможенная.
Подскажите, как бы реализовать подобие многопоточности?
Может тут подойдут конечные автоматы?
Приветствую список литературы по этому вопросу!
А может для attiny2313 вообще это не возможно сделать без тормозов?
ILYAUL
Цитата
Получилось кривенько, при опросе датчика прерывания запрещены
- так разрешите. И код не помешало бы
Xenia
Цитата(aivs @ Apr 7 2013, 01:15) *
Получилось кривенько, при опросе датчика прерывания запрещены, соответственно на нажатия кнопок реакция заторможенная.
Подскажите, как бы реализовать подобие многопоточности?
А может для attiny2313 вообще это не возможно сделать без тормозов?


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

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

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

Сама я с интерфейсом 1ware дела никогда не имела, но полагаю, что SPI с этой задачей должен справиться. В конце концов, побитно можно выдавать сигнал по таймеру, поднимая и опуская уровень в РАЗНЫЕ (!) такты. Например, часовой таймер вызывает прерывание каждую миллисекунду. Так вот можно сделать так, что через каждую миллисекунду какой-то пин в порте будет изменять свое состояние. И не в коем случае не жмотиться, пытаясь поднять и опустить уровень за один присест с фиксированной задержкой - задержек в прерывании быть не должно, их делают периодом таймера. Однако повторю, что это самый крайний случай, а не рекомендация делать меандр посредством частых прерываний. Наконец, еще есть UART/USI, который может работать в режиме 2-wire, хотя его использвать жалко, т.к. это обычно единственная связь с внешним миром.

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

То же касается разного рода измерений. Не остынет термопара за доли миллисекунды, а потому, когда придет время эту температуру опрашивать, из прерывания этого делать необходимости никакой нету.

P.S. ответила развернуто только потому, что ATtiny2313 - моя любимая тинька, и я сержусь, когда ее незаслуженно обижают. sm.gif
_Артём_
Цитата(Xenia @ Apr 7 2013, 02:39) *
что нельзя долго сидеть в прерывании.

Можно..в разумных пределах. Если вложенные прерывания допустимы.

Цитата(Xenia @ Apr 7 2013, 02:39) *
Сама я с интерфейсом 1ware дела никогда не имела, но полагаю, что SPI с этой задачей должен справиться.

SPI-то справится, но для 1-wire скорость слишком большой может быть(8-16МГц/128 - так быстро не надо)

Цитата(Xenia @ Apr 7 2013, 02:39) *
Например, часовой таймер вызывает прерывание каждую милисекунду. Так вот можно сделать так, что через каждую миллисекунду какой-то пин в порте будет изменять свое состояние.

Не хватает однократного режима таймера - выдать 0 на выход, установить через сколько надо тактов 1 и чтобы однократно. В принципе ШИМ сгодится...

Вход завести на захват по фронту. И где-нибудь разбирать захваченное...

Цитата(Xenia @ Apr 7 2013, 02:39) *
Наконец, еще есть UART/USI, который может работать в режиме 2-wire, хотя его использвать жалко, т.к. это обычно единственная связь с внешним миром.

Стандартное решение, но на мегах UART-ов не напасёшся обычно.
aivs
В прерывании я опрашиваю три кнопки, и если кнопка нажата то просто инкрементирую переменную, никаких задержек, на каждое 10 прерывание прибавляется секунда.
Код
// Процедура обработки прерывания по Таймеру 1 каждую 0.1 сек. Проверяем нажатия кнопок и прибавляем секунды
ISR (TIMER1_COMPA_vect){
  // При нажатии на кнопку PD5 увеличиваем час на один
  if (bit_is_clear(PIND,5)) {   // Если кнопка PD5 нажата
    _delay_ms(25);              // Пауза на 25 мс
    if (bit_is_clear(PIND,5)) { // Если кнопка PD5 нажат
      while (bit_is_clear(PIND,5)); //Ждем отжатия клавиши
      hours++;
    }
  }

  // При нажатии на кнопку PD4 увеличиваем минуту на один
  if (bit_is_clear(PIND,4)) {   // Если кнопка PD5 нажата
    _delay_ms(25);              // Пауза на 25 мс
    if (bit_is_clear(PIND,4)) { // Если кнопка PD5 нажат
      while (bit_is_clear(PIND,4)); //Ждем отжатия клавиши
      minutes++;
    }
  }

  // При нажатии на кнопку PD3 увеличиваем секунду на один
  if (bit_is_clear(PIND,3)) {   // Если кнопка PD5 нажата
    _delay_ms(25);              // Пауза на 25 мс
    if (bit_is_clear(PIND,3)) { // Если кнопка PD5 нажат
      while (bit_is_clear(PIND,3)); //Ждем отжатия клавиши
      seconds++;
    }
  }

  // 10 прерываний в секунды, на каждое 10 прерывание прибавляем секунду
  second_timer++;
  if (second_timer == 10) {
    seconds++;
    second_timer = 0;
  }
}


Вот главный цикл:
Код
// Основная программа
void main(void) {
  // Деление частоты системного генератора на 1. 8MHz/1 = 8MHz
  CLKPR=0x80;
  CLKPR=0x00;

  // Инициализация порта B. LCD HD44780
  // DDRB  = 0b11111111;
  // PORTB = Порты инициализируются в библиотеке

  
  // Инициализация порта D
  // DDRD.0 = ввод; DDRD.1 = ввод; DDRD.2 = ввод; DDRD.3 = ввод; DDRD.4 = ввод; DDRD.5 = ввод; DDRD.6 = ввод; DDR$
  // PORTD.0 = резистор; PORTD.1 = резистор; PORTD.2 = резистор; PORTD.3 = резистор; PORTD.4 = резистор; PORTD.5 $
    DDRD=  0b00000000;  // 0 = ввод (кнопка); 1 = вывод
    PORTD= 0b11111011;  // Если DDRD.0 == 0(ввод), то в PORTD.0 == 1 (резистор подключен) или PORTD.0 == 0 (резис$
                        // отключаем на PD2 нагрузочный резистор, там сидит термодатчик
  //PORTD= 0b76543210   // Соответствие ножек битам порта

  // Инициализация таймера/счеткика T1
  TCCR1A=0x00;
  TCCR1B=0x0D;  // коэф предделителя 1/1024 и совпадение в канале А
  OCR1A=780;    // Прерывание возникает при достижении таймером числа 780

  // Инициализация прерываний от таймера
  TIMSK=0x40;   // разрешаем прерывание от таймера по совпадению в канале А
  sei();// Разрешаем прерывания

  lcd_init();// инициализация LCD
  _delay_ms(10);
  lcd_clr();// очистить LCD
  // Вечный цикл вывод информации на дисплей
  while(1) {
    cli();                      // Запрещаем прерывания
    ticking_clock();            // Ход часов
    set_time();                 // Выставляем время
    sei();                      // Разрешаем прерывания

    // Получаем температуру ****** Получать температуру нужно так чтобы не тормозить вывод информации при опросе $
    temp = temp_18b20();
    if(temp > 1000) { //если температура <0
      temp = 4096 - temp;
      temp = -temp;
    }
    lcd_gotoxy(1, 1);           // Переходим к 0 ячейки
    lcd_string("Temp=");
    itoa(temp,ch_temp,10);      // Переводим число в символы
    lcd_string(ch_temp);        // Выводим на дисплей
  }
}

Задержки с использованием _delay_us и _delay_ms содержат функции: set_time() и temp_18b20().
set_time() выводит на экран HD44780 время (много задержек _delay_ms)
temp_18b20() запрашивает и получает температуру по 1-wire& ((много задержек _delay_us)

Как понял мне нужно все задержки _delay_us и _delay_ms сделать через прерывания?
_Pasha
Могу сказать, как делаю я.
1. Беру за основу идею, заключенную в Protothreads
2. Разбиваю задачу на фиксированное число потоков вида
Код
void *thread(void *pc);

3. Все эти потоки свожу в функцию вида
CODE
#define RUN(name) do{\
static void *pc=NULL;\
static char lock=0;\
if(lock==0){lock=1; pc=name(pc); lock = 0;}\
}while(0);
void system(void)
{
RUN(thread1);
RUN(thread2);
// etc.

int main()
{
while(1) system();
return 0;
}
}

4. Теперь с 1-wire
4.1.Лучше всего сделать на основе того же простого треда нормальный драйвер, с перепрограммированием таймера на все необходимые задержки. Это будет полностью прозрачная штучка.
4.2.Еще вариант- все длинные задержки делать на основе опроса сист.таймера
Код
static timer_t time;
if(get_time(time) < DELAY_460ms) system();//замерзли до окончания сброса

а все короткие - с помощью delay_us()
Оно-то у меня работает, но подход неправильный.

По поводу остального - ну нет смысла опрашивать клавиатуру, делать динамическую индикацию и прочая - в прерывании!
---
Xenia: с Новым Годом по тарабарскому календарю sm.gif
mempfis_
Цитата(aivs @ Apr 7 2013, 01:15) *
Затем я задействовал термодатчик ds18b20 с шиной 1ware и написал программу для показа температуры на дисплее hd44780, датчик опрашивается по прерыванию от таймера каждые 0.1 сек, прекрасно работает.
А затем я решил совместить эти две программы, чтобы часы в одной части дисплея работали, а температура в другой части. Получилось кривенько, при опросе датчика прерывания запрещены, соответственно на нажатия кнопок реакция заторможенная.


Есть такая проблема при опросе DS18B20. Датчик требует точного выдерживания временных интервалов, поэтому тут без __delay_cycles(x) с запрещёнными прерываниями никак. Но если внимательно посмотреть на шину 1-wire, то в принципе точно выдерживать временные интервалы необходимо не постоянно, а только в пределах определения наличия устройства на шине и при чтении бита данных. Т.е. можно между чтениями битов на некоторое время разрешать прерывания чтобы сработали прерывания, возникшие в моменты чтения отдельных бит.
А как часто Вы оправшиваете датчик? Некоторые товарищи на форуме советуют опрашивать не чаще чем раз в 30 секунд, ибо будет саморазогрев датчика. Также нет смысла опрашивать его часто при измерении температуры воздуха.

Мне неоднократно приходилось опрашивать такие датчики и отображать температуру на дисплее + сканировать кнопки. Могу кое что посоветовать по поводу организации программы. Вам нет смысла висеть в обработчиках прерываний для сканирования кнопок и динамической индикации. Достаточно одного таймера с периодом = 10мС/(число отображаемых разрядов индикатора). Если у Вас 4 разряда, то период 10/4 = 2,5 мС. Это обеспечит частоту обновления ~100 Гц. В прерывании просто выставляйте флаг что было прерывание, а в основном цикле анализируйте этот флаг, выполняйте процедуру динамической индикации и опрос кнопок. Эти процессы не требуют сверхбыстрой реакции. Если Вы организуете чтение датчика с разрешёнными на время прерываниями между таймслотами 1-wire, то Вы даже не заметите мерцания индикации или пропуска кнопок.
А для часов заведите другой таймер, который будет генерировать прерывания раз в 0,5-1 секунду. За 500 мС процессор всегда найдёт возможность обработать этот флаг и Вы не потеряете точности хода.
В загруженных программах, где много чего обрабатывается, но прерывания разрешены более точно считать время помагает переменная timestamp, которую необх. инрементировать при входе в обработчик прерывания от таймера. Тогда при обработке флага прерывания от таймера Вы более-менее точно будете знать сколько было прерываний от данного таймера. Но Ваша задача не настолько сложна и загружена, чтобы вводить подобное ухищрение.
Xenia
Цитата(aivs @ Apr 7 2013, 11:38) *
Код
  // Вечный цикл вывод информации на дисплей
  while(1) {
    cli();            // Запрещаем прерывания
    ticking_clock();                    // Ход часов
    set_time();        // Выставляем время
    sei();            // Разрешаем прерывания

    // Получаем температуру ****** Получать температуру нужно так чтобы не тормозить вывод информации при опросе $
    temp = temp_18b20();
    if(temp > 1000) { //если температура <0
      temp = 4096 - temp;
      temp = -temp;
    }
    lcd_gotoxy(1, 1);        // Переходим к 0 ячейки
    lcd_string("Temp=");
    itoa(temp,ch_temp,10);    // Переводим число в символы
    lcd_string(ch_temp);        // Выводим на дисплей
  }
}


Возможно, это дисплей время тянет, т.к. выполнение функции lcd_string() может занимать продолжительное время (проверить своё опасение не могу, т.к. ее код мне не ведом).
Самое простое - сэкономить на дисплейном выводе, если температура остается прежней. Какой смысл обновлять на нем число, если оно не изменилось?
Например, так:
Код
if(temp != oldtemp)
{ lcd_gotoxy(1, 1);        // Переходим к 0 ячейки
   lcd_string("Temp=");
   itoa(temp,ch_temp,10);    // Переводим число в символы
   lcd_string(ch_temp);    // Выводим на дисплей

  oldtemp = temp;
}

То же можно сделать и с set_time(), которая выводит время на экран. Полагаю, что время выводится не с миллисекундной точностью, а раз так, то перед его выводом на дисплей тоже можно проверить еще в бинарном виде, стоит ли новое значение вывода (скажем, если время выводится на дисплей без десятых долей секунды).
Или начало строки "Temp=" выводить только однажды (перед вечным циклом), если оно всегда на одном месте печатается, и перескакивать с помощью lcd_gotoxy(1, 6) сразу на цифровую область.
mempfis_
Цитата
Задержки с использованием _delay_us и _delay_ms содержат функции: set_time() и temp_18b20().
set_time() выводит на экран HD44780 время (много задержек _delay_ms)


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


_Pasha
Цитата(Xenia @ Apr 7 2013, 15:23) *
Возможно, это дисплей время тянет

+1
Там в низкоуровневом чтении/записи обычно используется чтение статуса и ожидание, пока контроллер освободится. Вот туда я и помещаю system(), получаем
Код
while (HD44780_Busy()) system();

Культурненько и прозрачненько.
Xenia
Цитата(_Pasha @ Apr 7 2013, 18:19) *
Там в низкоуровневом чтении/записи обычно используется чтение статуса и ожидание, пока контроллер освободится. Вот туда я и помещаю system(), получаем
Код
while (HD44780_Busy()) system();

Культурненько и прозрачненько.


А system() это что такое? sm.gif
_Pasha
Цитата(Xenia @ Apr 7 2013, 17:31) *
А system() это что такое? sm.gif

Это несколькими постами выше.
_Артём_
Цитата(mempfis_ @ Apr 7 2013, 14:12) *
Есть такая проблема при опросе DS18B20. Датчик требует точного выдерживания временных интервалов,

Проблемы скорее нет, чем есть.
Разве самому можно её создать, подключив 1-wire к неподходящим для этого выводам МК.
Точности тоже никакой не требуется.

Цитата(mempfis_ @ Apr 7 2013, 14:12) *
поэтому тут без __delay_cycles(x) с запрещёнными прерываниями никак.

Делать запрет прерываний на 500 мкс? Странно это.

Цитата(mempfis_ @ Apr 7 2013, 14:12) *
Но если внимательно посмотреть на шину 1-wire, то в принципе точно выдерживать временные интервалы необходимо не постоянно, а только в пределах определения наличия устройства на шине и при чтении бита данных.

Все циклы обмена начинаются мастером. Остаётся только настроить захват таймера по фронту(или считывать по PIN_CHANGE, но это хуже).
mempfis_
Цитата
Проблемы скорее нет, чем есть.
Разве самому можно её создать, подключив 1-wire к неподходящим для этого выводам МК.


Не всегда программист выбирает к какому выводу подключать датчики. И не всегда "правильные" выводы свободны.
Зато можно написать одну процедуру чтения 1-wire устройства, которая будет работать на любом пине любого порта AVR.
Для AVR у меня именно так и сделано.


Цитата
Точности тоже никакой не требуется. Делать запрет прерываний на 500 мкс? Странно это.


В чём странность запрещать прерывания? Возникновение прерывания во время вызова процедуры __delay_cycles() увеличивает задержку на время входа в прерывание, его обработки и выхода из прерывания. Если прерывание тяжёлое, то и задержка может растянуться. Если посмотреть на таблицу, то практически везде требуется точность не хуже 10 uS.
_basile
DATASHEET 2313: "The External Interrupts are triggered by the INT0 pin, INT1 pin or any of the PCINT7..0"
Ну, и зачем кнопки по таймеру опрашивать?
Как тут уже заметили, температура - штука достаточно инерционная, поэтому ее вполне можно считывать раз в 10 секунд.
Ну, и, естественно, разрешать прерывания после каждого прочитанного БИТА.
Что касается дисплея, то я статус вообще не опрашиваю, а просто выставляю гарантированную задержку.

Ха! Дак у топикстартера что в прерывании по таймеру творится:

_delay_ms(25); // Пауза на 25 мс Ну, и ктож задержки делает в обработчике прерываний ??????
if (bit_is_clear(PIND,5)) { // Если кнопка PD5 нажат
while (bit_is_clear(PIND,5)); //Ждем отжатия клавиши Еще бы не тормозило !
_Pasha
Цитата(_basile @ Apr 8 2013, 10:52) *
Как тут уже заметили, температура - штука достаточно инерционная, поэтому ее вполне можно считывать раз в 10 секунд.

Вы не с того конца мысль выразили. sm.gif Чаще считывать температуру не надо, потому что в этом случае идет саморазогрев ds18b20.
_basile
Цитата(_Pasha @ Apr 8 2013, 11:57) *
Вы не с того конца мысль выразили. sm.gif Чаще считывать температуру не надо, потому что в этом случае идет саморазогрев ds18b20.

Палка - о двух концах. И что, сильный саморазогрев ? А если бы аналоговый датчик был, то зафигачиваем, на сколько АЦП тянет ?
Мой подход - с точки зрения логики. И относится не только к датчикам температуры. Зачем "грузить" процессор, если в этом нет необходимости?
_Pasha
Цитата(_basile @ Apr 8 2013, 11:08) *
И что, сильный саморазогрев ? А если бы аналоговый датчик был, то зафигачиваем, на сколько АЦП тянет ?
Мой подход - с точки зрения логики. И относится не только к датчикам температуры. Зачем "грузить" процессор, если в этом нет необходимости?

Разогрев до 3 градусов - аж бегом. И Zth корпуса не позволяет культурно отвести тепло.
Насчет инерционности: объясните это устройству, измеряющему и регулирующему температуру воздушного потока. Или контроллеру модуля Пельтье в режимах вблизи точки росы. sm.gif

Цитата(_basile @ Apr 8 2013, 10:52) *
Ха! Дак у топикстартера что в прерывании по таймеру творится:

sm.gif ага.
aivs
Спасибо, уберу из прерывания:
Код
_delay_ms(25); // Пауза на 25 мс Ну, и ктож задержки делает в обработчике прерываний ??????
if (bit_is_clear(PIND,5)) { // Если кнопка PD5 нажат
while (bit_is_clear(PIND,5)); //Ждем отжатия клавиши Еще бы не тормозило !

На дисплей информацию буду выводить по мере ее поступления, а не в цикле.
Датчик температуры по реже буду опрашивать.
_Артём_
Цитата(mempfis_ @ Apr 8 2013, 10:31) *
Не всегда программист выбирает к какому выводу подключать датчики.

Ну да, на RESET не подключили - и на том спасибо.
Но всё-таки выводы надо бы соответственно задаче выбирать.

Цитата(mempfis_ @ Apr 8 2013, 10:31) *
И не всегда "правильные" выводы свободны.

Бывает...тогда другой МК лучше выбирать, более подходящий.

Цитата(mempfis_ @ Apr 8 2013, 10:31) *
Зато можно написать одну процедуру чтения 1-wire устройства, которая будет работать на любом пине любого порта AVR.

Решение универсальное и отсюда его недостатки.

Цитата(mempfis_ @ Apr 8 2013, 10:31) *
В чём странность запрещать прерывания? Возникновение прерывания во время вызова процедуры __delay_cycles() увеличивает задержку на время входа в прерывание, его обработки и выхода из прерывания.

Странность в том же, что в случае вызова задержек в прерывании. Нехорошо это.

aivs
Цитата(_basile @ Apr 8 2013, 11:52) *
DATASHEET 2313: "The External Interrupts are triggered by the INT0 pin, INT1 pin or any of the PCINT7..0"
Ну, и зачем кнопки по таймеру опрашивать?


А у меня три кнопки, как быть если не по таймер опрашивать?
ARV
всем начинающим всегда рекомендую мудрить поменьше, а поступать попроще: делайте в прерываниях только то, что либо требует мгновенной реакции на внешнее событие, либо должно происходить незаметно для всех остальных дел. для первого подходят прерывания от внешних сигналов типа INT0, встроенных SPI и т.п. интерфейсов, а для второго - таймеры (иногда иначе). при этом всегда обработчик прерывания должен стремиться быть максимально коротким по времени выполнения.

кнопки по определению не попадают в эти категории, т.к. работа с ними всегда ведется в диапазоне человеческих реакций: человек физически неспособен заметить разницу между мгновенной реакцией на нажатие кнопки и реакцией, последовавшей через 150 миллисекунд (чертовски много по меркам МК), т.к. только специально тренированный человек имеет мускульную реакцию, соизмеримую с этим интервалом времени, все прочие тормозят сильнее.

все это я к тому, что работу кнопок в 99% случаев следует делать самым тупым способом - методом опроса пинов в главном цикле. при числе кнопок не более 8, особенно если они подключены к пинам одного порта, лучше этого способа вряд ли можно придумать.

итак, опрос кнопок - методом поллинга в главном цикле, т.е. без прерываний.

надеюсь, проведя аналогичные рассуждения можно прийти к выводу, что индикация на ЖКИ и работа с DS18B20 так же не попадают в категорию, для которой применение прерываний является НЕОБХОДИМЫМ. следовательно, не мудрите понапрасну, в главном цикле делайте все максимально просто:
1. вывели на индикатор подготовленные данные
2. опросили кнопки
3. обменялись с датчиком температуры
4. в зависимости от нажатой кнопки подготовили новые данные
5. перешли к п.1

и будет вам счастье!
Xenia
Я тоже сразу заметила, что в этой программе пока держишь кнопку нажатой, то висишь в прерывании, а система стоит. Тоже сперва хотела этим возмутиться, но одумалась sm.gif. В принципе процесс установки времени не обязан сопровождаться одновременной работой устройства. Например, никто не возмущается тем, что в процессе установки точного времени (перевода часов) на микроволновке в это время нельзя жарить курицу-гриль. sm.gif Вот и наручные часы не обязаны соблюдать точность хода, пока им переводят стрелки. А COM-порт соблюдать правильную передачу, в то время, тогда ему переустанавливают боды.

Поэтому, наверное, не стоит заставлять топикстатера возиться с этими кнопками, переводя их правильный режим. Поскольку он имеет полное право не рассматривать установочную процедуру, как часть рабочего цикла.
_Pasha
Цитата(Xenia @ Apr 9 2013, 01:36) *
Поэтому, наверное, не стоит заставлять топикстатера возиться с этими кнопками, переводя их правильный режим. Поскольку он имеет полное право не рассматривать установочную процедуру, как часть рабочего цикла.

Интересно все-таки, какой индивид первым придумал, что пофиксенная бага есть фича ...
aivs
Цитата(Xenia @ Apr 9 2013, 02:36) *
Поэтому, наверное, не стоит заставлять топикстатера возиться с этими кнопками, переводя их правильный режим. Поскольку он имеет полное право не рассматривать установочную процедуру, как часть рабочего цикла.

Нет! заставляйте меня делать правильно! Я только учусь и хочу все делать правильно, сложностей не боюсь.
И еще во время установки времени, я бы хотел, чтобы время шло, в данный момент время останавливается пока кнопка нажата, это и понятно, но я уберу из прерывания обработку нажатий, в перывании буду только менять статус переменной для кнопки: нажата/не нажата
_Pasha
Цитата(aivs @ Apr 9 2013, 11:17) *
Нет!

Упражнение было для одного знакомого: 4 канала температуры + выход последовательного порта.
Работает уж 3 года. Но код ужасен: я не ставил комментарии и всё это, можно сказать, левой ногой и в спешке. И на меге16. А по построению - все, что говорил выше. И даже плавучка есть, и все успевает и все прозрачно.
Нажмите для просмотра прикрепленного файла

Конечно, со временем стало ясно, что можно и культурнее написать и меньше текста итд итп...
PS еще тема есть такая http://electronix.ru/forum/index.php?showtopic=10934 прикреплённая, там много чего интересного.
mempfis_
Цитата(aivs @ Apr 9 2013, 11:17) *
Нет! заставляйте меня делать правильно! Я только учусь и хочу все делать правильно, сложностей не боюсь.
И еще во время установки времени, я бы хотел, чтобы время шло, в данный момент время останавливается пока кнопка нажата, это и понятно, но я уберу из прерывания обработку нажатий, в перывании буду только менять статус переменной для кнопки: нажата/не нажата


Простейший вариант организации обработки кнопки. Раньше я пользовался подобным методом (один из вариантов, их у меня много было).

Код
typedef struct {
unsigned char status; //статус структуры, не кнопки!!!
unsigned char timer; //таймер
} stButton;
stButton button[1]; //это одна кнопка, а может быть несколько если записать button[3];

#define button1_msk 0x1; //маска кнопки 1
unsigned char button_states;
volatile unsigned char timer_flag;
unsigned char one_wire_timer = 0; //это позволит быстро произвести первый опрос датчика
unsigned char display_flag = 1; //это позволит первый раз отобразить данные на индикаторе

while(1)
{

//код ниже будет выполняться только если установлен флаг прерывания от таймера
//этот флаг можно устанавливать скажем раз в 10 мС
//грубо говоря это системный таймер
if(timer_flag)
{
timer_flag = 0;

button_states = getDin(); //получения текущего состояния кнопок

//декремент таймеров кнопок (может быть несколько)
if(button[0].timer > 0) button[0].timer --;

//обработка кнопок (может быть не одна кнопка)
switch(button[0].status)
{
case 0: //ожидание нажатия кнопки
           if(button_states & button1_msk)
           {
              //ожидаемая кнопка нажата
              //какие-либо действия для данной кнопки - например инкремент часов
             incHoures(); //эта процедура должна устанавливать флаг что данные на индикаторе обновились

             button[0].status = 1; //переход на ожидание отпускания
             button[0].timer = 50; //таймаут автоинкремента (50*(период системного таймера) == 500мС)
           }
break;
case 1: //ожидание нажатия кнопки
           if( !(button_states & button1_msk) )
           {
              //ожидаемая кнопка отпущена
             button[0].status = 0; //переход на ожидание нажатия
             button[0].timer = 30; //таймаут антидребезга  (30*(период системного таймера) == 300 мС)
           }

          if(button.timer == 0)
         {
             //вышел таймаут автоинкремента
            //какие-либо действия для данной кнопки - например инкремент часов
             incHoures(); //эта процедура должна устанавливать флаг что данные на индикаторе обновились
            
                       button[0].timer = 50; (50*(период системного таймера) == 500мС)
         }
break;

default: button[0].status = 0; //переход на ожидание нажатия
            button[0].timer = 0;
break;
}

//проверка флага опроса по 1wire
if(one_wire_timer > 0) one_wire_timer--;
else
{
one_wire_timer = 1000; (1000*(период системного таймера) == 10000 мС)
//тут опрос датчика, после опроса необх удостоверится что температура поменялась и установить флаг что данные на индикаторе обновились
}

//проверка флага необходимости отображения изменений на индикаторе
//может быть установлен по нажатию кнопок, после опроса 1-wire или при изменении времени

if(display_flag)
{
display_flag = 0;
//отображам изменения на индикаторе
}

}

__enable_interrupt();
__sleep();

}


Вам необходимо организовать прерывания от таймера с периодом скажем 10 мС и устанавливать в них флаг timer_flag.
При желании можете завести отдельный таймер с секундными прерываниями для часов или использовать системный таймер.
Как определить нужен ли Вам отдельный таймер для часов - если длительность опроса 1-wire будет заведомо меньше периода системного таймера, то можете использовать системный таймер. Иначе для надёжности введите таймер для часов или просто увеличьте системный период.
В результате у Вас одно (максимум 2 простых прерывания) и определяемая вами реакция на кнопки.
ARV
Цитата(mempfis_ @ Apr 9 2013, 13:43) *
Простейший вариант организации обработки кнопки.



в общем на самом деле простейший вариант, для начинающего самое то smile3009.gif

P.S. судя по вашему коду, МК у вас спит все время между прерываниями таймера - нафига тогда флаги какие-то?!


mempfis_
Цитата(ARV @ Apr 9 2013, 13:11) *
P.S. судя по вашему коду, МК у вас спит все время между прерываниями таймера - нафига тогда флаги какие-то?!


В этой программе может быть флаги не нужны, и то при условии что будет только один источник прерываний от системного таймера. Если прерываний будет несколько (другие таймеры, уарты и т.д.) то без этого флага никак т.к. мк просыпается от любого прерывания. Лучше сразу выработать правильный подход. Можно вообще не использовать таймер и для отлад ввести delay_ms(); вместо __enable_interrupt(); __sleep();

Для топикстартера - чтобы работала инструкция __sleep(); необходимо установить бит SE регистра MCUCSR. Если этого не сделать, то процессор будет себя вести так, будто совсем не спит. Если бы не было флага от таймера, то сами догадайтесь что могло бы произойти с исполняемой программой sm.gif
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.