Как поместить цикл в case оператора switch

Обсуждаем контроллеры компании Atmel.
uuu000
Открыл глаза
Сообщения: 44
Зарегистрирован: Пн окт 05, 2020 17:20:11

Как поместить цикл в case оператора switch

Сообщение uuu000 »

Необходимо при переключении кнопкой с помощью оператора switch включать мигание светодиода с различной частотой на каком либо выводе.при
использовании конструкции (для примера):
PORTB |= (1<<PB0);
_delay_ms(100);
PORTB&= ~(1<<PB0);
_delay_ms(100);
необходимо зациклить ее в теле case,но невозможно переключить switch в следующий case.Просто невозможно выйти из бесконечного цикла.
Я использовал while(),может быть есть какой либо способ решить эту проблему, я начал изучать программирование МК недавно и до сложных
кодов еще мне очень далеко. Надеюсь на помощь.
Спасибо.
Реклама
Аватара пользователя
Starichok51
Модератор
Сообщения: 19055
Зарегистрирован: Сб авг 14, 2010 15:05:51
Откуда: г. Озерск, Челябинская обл.

Re: Как поместить цикл в case оператора switch

Сообщение Starichok51 »

для реальной помощи положено приводить исходный код и схему.
а также нужно подробно и четко описать, что тебе надо и как это должно работать.
при предоставленном минимуме информации могут помочь только гадалки и экстрасенсы.
Мудрость приходит вместе с импотенцией...
Когда на русском форуме переходят на Вы, в реальной жизни начинают бить морду.
Реклама
parovoZZ
Мудрый кот
Сообщения: 1759
Зарегистрирован: Пт июн 01, 2018 07:28:45

Re: Как поместить цикл в case оператора switch

Сообщение parovoZZ »

Ну какой код? Студент не понятно чем занимался, как вдург препод огорошил!
uuu000
Открыл глаза
Сообщения: 44
Зарегистрирован: Пн окт 05, 2020 17:20:11

Re: Как поместить цикл в case оператора switch

Сообщение uuu000 »

#include <avr/io.h>
#define F_CPU 8000000UL
#include <util/delay.h>
#define time 300
//attiny2313,8Mhz,ДЕЛИТЕЛЬ НА 8 ОТКЛЮЧЕН

void pause (unsigned long a)//Использую вместо стандартной задержки
{ unsigned long counter ;
for(counter=a;counter>0;counter--)
asm("nop");
}
int main(void)
{ uint8_t i=0;
DDRD = 0xFF;
PORTD=0x00;
DDRA = 0x00;
PORTA|=(1<<PA0);
while(1)
{
if (~PINA&(1<<PA0))
{
if (i<3)
{
i++;
}
else
{
i=0;
}
}
switch(i)
{
case 1:
PORTD|=(1<<PD0);
pause (10000L*time);
PORTD&=~(1<<PD0);
pause (10000L*time);
break;

case 2:
PORTD|=(1<<PD0);
pause (5000L*time);
PORTD&=~(1<<PD0);
pause (5000L*time);
break;

case 3:
PORTD|=(1<<PD0);
pause (1000L*time);
PORTD&=~(1<<PD0);
pause (1000L*time);
break;


}
_delay_ms(300);
}
}


При симуляции в Proteus зацикливается в первом case , т.е. не переключается в следующий .На макетной плате после первого нажатия кнопки светодиод горит постоянно примерно 5 сек(pause (10000L*time)
и потом гаснет ,дальнейшие манипуляции кнопкой просто повторяют эту картину(нет мигания светодиода).
Что касается студента - я вышел на пенсию и для поддержания здоровья занялся программированием МК, база у меня есть(физфак БГУ) и пока голова работает хочу нагружать ее насколько это возможно.
В марте мне будет 71.
Спасибо за Ваши комментарии, надеюсь на Вашу помощь.
Реклама
Эиком - электронные компоненты и радиодетали
Аватара пользователя
Карбофос
Опытный кот
Сообщения: 760
Зарегистрирован: Сб окт 22, 2016 17:33:32
Откуда: кг

Re: Как поместить цикл в case оператора switch

Сообщение Карбофос »

У вас переменная i принимает значения 0,1,2, а switch(i) 1,2,3.
Реклама
Reflector
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Re: Как поместить цикл в case оператора switch

Сообщение Reflector »

У вас внутри switch одинаковые ветки отличающиеся только задержкой, проще ее хранить в массиве:

Код: Выделить всё

uint16_t arr[] = { 10000, 5000, 1000 };
и потом просто подставлять:

Код: Выделить всё

if (~PINA & (1<<PA0))
{
    if(++i >= 3) i = 0;
}

uint32_t delay = arr[i] * time;
PORTD|=(1<<PD0);     
pause (delay); 
PORTD&=~(1<<PD0);    
pause (delay);
На AVR умножение наверно лучше делать один раз, после инкремента i, или сразу перемноженные значения хранить...
Реклама
uuu000
Открыл глаза
Сообщения: 44
Зарегистрирован: Пн окт 05, 2020 17:20:11

Re: Как поместить цикл в case оператора switch

Сообщение uuu000 »

К сожалению не работает, если только я правильно понял идею с массивами:


#include <avr/io.h>
#define F_CPU 8000000UL
#include <util/delay.h>
#define time 300
//attiny2313,8Mhz,ДЕЛИТЕЛЬ НА 8 ОТКЛЮЧЕН

void pause (unsigned long a)
{ unsigned long counter ;
for(counter=a;counter>0;counter--)
asm("nop");
}
int main(void)
{
uint16_t arr[] = { 10000, 5000, 1000 };


uint8_t i=0;
DDRD = 0xFF;
PORTD=0x00;
DDRA = 0x00;
PORTA|=(1<<PA0);
while(1)
{

if (~PINA & (1<<PA0))
{
if(++i >= 3)
{

uint32_t delay = arr * time;
PORTD|=(1<<PD0);
pause (delay);
PORTD&=~(1<<PD0);
pause (delay);
}
else
i=0;
}
}
_delay_ms(300);

}
Reflector
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Re: Как поместить цикл в case оператора switch

Сообщение Reflector »

Конечно не работает, потому что это какой-то другой код :) Тут, например, будет выход за предела массива:

Код: Выделить всё

if(++i >= 3)
{
    uint32_t delay = arr[i] * time;
....
Да, еще на AVR же вроде int 16-ти битный, значит нужно при умножении приведение к uint32_t использовать... Наверно эффективнее сразу перемноженное хранить:

Код: Выделить всё

uint32_t arr[] = { 10000 * time, 5000 * time, 1000 * time };

while(1)
{
    if (~PINA & (1<<PA0))
    {
       if(++i >= 3) i = 0;
    }
 
    PORTD|=(1<<PD0);     
    pause (arr[i]);
    PORTD&=~(1<<PD0);   
    pause (arr[i]);

}
Опять же, это примерный код, я его не проверял потому что с AVR уже давно дела не имею, мелкие правки нужно самому делать. Например, если массив 32-х битный, то time должен быть задефайнен как 32-х битное значение.
Последний раз редактировалось Reflector Вт окт 12, 2021 12:19:07, всего редактировалось 2 раза.
Аватара пользователя
DX168B
Друг Кота
Сообщения: 4468
Зарегистрирован: Вс янв 24, 2010 19:19:52
Откуда: Главный Улей России (Moscow)
Контактная информация:

Re: Как поместить цикл в case оператора switch

Сообщение DX168B »

Как уже сказали, switch у Вас реагирует только на значения i=1, 2 или 3, а переменная i принимает значения 0..2
Соответсвенно, case 3 у Вас никогда не сработает.
Ну и пока кнопка нажата, у Вас переменная i будет постоянно меняться, пока Вы ее не отпустите.
Как я понял, вы хотите мигать светодиодом с переключаемой с помощью кнопки задержкой.
Вам нужно после фиксации факта нажатия кнопки ожидать ее отпускание и уже после отпускания инкрементировать переменную i.
Вам еще нужно изучить такой эффект как "дребезг контактов" и методы борьбы с ним. Ибо в Протеусе дребезга нет,
а в реальной кнопке он есть.

И чтение лучше явно проверять:

Код: Выделить всё

if( (PINA & (1<<PA0)) == 0) // Бит, соответсвующий выводу PA0 равен нулю (прижали подтянутую ногу к минусу)
{
   // Делаем то, что надо
}
 
Мало того, пока протекают задержки, весь цикл заморожен и даже кнопка не опрашивается.
Для решения таких проблем используют неблокирующие задержки на "системном таймере",
программу разбивают на модули и реализуют их в виде конечных автоматов, которые меняют свои состояния
в зависимости от различных событий и делают различные действия в зависимости от своего состояния.
Последний раз редактировалось DX168B Вт окт 12, 2021 12:29:28, всего редактировалось 1 раз.
I am DX168B and this is my favourite forum on internet!
Reflector
Поставщик валерьянки для Кота
Сообщения: 2089
Зарегистрирован: Вс июн 19, 2016 09:32:03

Re: Как поместить цикл в case оператора switch

Сообщение Reflector »

[uquote="DX168B",url="/forum/viewtopic.php?p=4104589#p4104589"]Ну и пока кнопка нажата, у Вас переменная i будет постоянно меняться, пока Вы ее не отпустите.[/uquote]
Там в конце задержка 300ms, если кнопку быстро нажимать, то для проверки сойдет, а дальше если еще и с антидребезгом делать, то это уже продвинутый уровень :)
Аватара пользователя
DX168B
Друг Кота
Сообщения: 4468
Зарегистрирован: Вс янв 24, 2010 19:19:52
Откуда: Главный Улей России (Moscow)
Контактная информация:

Re: Как поместить цикл в case оператора switch

Сообщение DX168B »

К ней еще добавить задержки в switch и там уже гораздо больше, чем 300мс.
I am DX168B and this is my favourite forum on internet!
metan
Вымогатель припоя
Сообщения: 593
Зарегистрирован: Ср янв 06, 2010 10:01:46

Re: Как поместить цикл в case оператора switch

Сообщение metan »

Вариантов решения много.
Задержки лучше бы не использовать, или использовать по минимуму.
Для организации задержек идеально подходят таймеры чипа плюс флаги.
1) заводим таймер на срабатывание прерывания с частотой, например в 10 мс.
2) в обработчике прерывания просто инкрементируем переменную-счетчик (не забыть сделать ее при определении как volatile)
3) в начале main обнуляем переменную-счетчик и задаем переменную - максимум (например, через настройку индекса массива на его начало, как вам уже предлагали)
4) главный цикл:
4.1) читаем состояние кнопки (кнопок)
4.2) каскад проверок:
Если переменная - счетчик достигла значения переменной - максимум, обнуляем счетчик и инвертируем выход светодиода. Например, если время свечения / паузы светодиода должно быть полсекунды, переменная - максимум должна быть равна 50.
Если была нажата кнопка, переопределям переменную - максимум (например, через приращение индекса массива, как вам уже предлагали)

Вроде всё. В протеусе должно взлететь, я думаю. В реальной схеме нужно будет добавить обработку дребезга контактов кнопки.
uuu000
Открыл глаза
Сообщения: 44
Зарегистрирован: Пн окт 05, 2020 17:20:11

Re: Как поместить цикл в case оператора switch

Сообщение uuu000 »

Cпасибо.Сейчас не могу проверить,
доберусь до компьютера -отпишусь.
uuu000
Открыл глаза
Сообщения: 44
Зарегистрирован: Пн окт 05, 2020 17:20:11

Re: Как поместить цикл в case оператора switch

Сообщение uuu000 »

Всем спасибо .Все получилось. Для прерываний использовал Т0 в режиме переполнения и срабатывания 8мс.Правда обошелся без массивов.
Аватара пользователя
DX168B
Друг Кота
Сообщения: 4468
Зарегистрирован: Вс янв 24, 2010 19:19:52
Откуда: Главный Улей России (Moscow)
Контактная информация:

Re: Как поместить цикл в case оператора switch

Сообщение DX168B »

Рады, что смогли чем-то помочь. :beer:

Вообще, можете взять за правило.
Пишите один раз код, реализующий задержки на таймере и каждый раз,
когда создаете проект, копируете туда этот код и налаживаете его работу.
Задержки на таймере позволяют организовать асинхронные задержки, а таких задержек можно организовать
великое множество. Саму же программу реализовать можно в виде конечного автомата, который будет состоять
из множества состояний для различных действий. Главный цикл постоянно проходится по автомату и выполняет действия,
согласно текущему состоянию и переключается в другие состояния по каким-то событиям, значениям в переменных или флажках.
Благодаря такому подходу легко можно реализовать нечто, вроде "мигать светодиодом в течении 10 секунд. Если за это время нажали кнопку, зажечь светодиод, а иначе потушить."
Есть по этим вещам множество статей в блоге easyelectronics.

Вот пример того, как выглядит один из конечных автоматов в одном из моих проектов:
СпойлерТут язык C++, но это не суть.
Эта функция постоянно вызывается из главного цикла.
Она реализует опрос клавиатуры и антидребезг.
Состояния автомата определяются перечислительным типом enum KbProcState (это такой удобный способ дать числам названия)
и хранятся переменной state
Тут же используются и задержки с таймером.

Все разбито на определенный порядок действий.
Состояния физических кнопок читаются постоянно, функцией CheckPressedKey()
А дальше автомат:

1 состояние (оно же и начальное):
Проверяется, нажато ли вообще что-то.
Если нет, то остаемся в этом же состоянии.
Если нажалось что-то, значит запускаем таймер антидребезга и меняем состояние автомата (переход во второе состояние)

2:
Проверка, не поменялся ли номер нажатой кнопки при очередном опросе.
Если в течении задержки антидребезга номер не менялся, значит кнопка в стабильном состоянии,
происходит переключение в следующее состояние.
Если же номер изменялся (контакт еще нестабилен), то автомат переключается в начальное состояние и все начинается заново.

Последующие состояния уже детектируют короткое или длинное нажатие и выставляют событие (коротко/длинно нажата такая-то кнопка).
Это событие уже используется другими конечными автоматами, реализованными в программе.

Код: Выделить всё

///////////////////////////////////////////////////////////////////////
// Keyboard process
void CKeyboard::Process()
{
    // Detect pressed key
    KbKeys pressedKey = CheckPressedKey();
    
    // Keyboard process state machine
    switch (state)
    {
    // Initial state
    case KbProcState::INITIAL:
        if(pressedKey != KbKeys::KEY_NONE)
        {
            prevKey = pressedKey;
            kbTim.SetTimer(KEYBOARD_DEBOUNCE_DELAY); // begin debounce timer
            state = KbProcState::KEY_PRESS_DEBOUNCE;
        }
        break;

    // Press debounsce
    case KbProcState::KEY_PRESS_DEBOUNCE:
        if(pressedKey != prevKey)
        {
            state = KbProcState::INITIAL;
        }
        else
        {
            if(kbTim.CheckTimer() == true) // check debounce timer
            {
                kbTim.SetTimer(KEYBOARD_LONGPRESS_DELAY);
#ifndef KEYBOARD_EVENT_KEY_AND_CLICK_SYNC
                currentKey = pressedKey;
#endif
                currentClick = ClickType::NO_CLICK;
                state = KbProcState::KEY_PRESS;
            }
        }

        break;

    // Key pressed state
    case KbProcState::KEY_PRESS:
        if(pressedKey == KbKeys::KEY_NONE)
        {
            if(kbTim.CheckTimer() == false)
            {
                currentClick = ClickType::SHORT_CLICK;
#ifdef KEYBOARD_EVENT_KEY_AND_CLICK_SYNC
                currentKey = prevKey;
#endif
            }

            state = KbProcState::INITIAL;
        }

        if(kbTim.CheckTimer() == true)
        {
            currentClick = ClickType::LONG_CLICK;
#ifdef KEYBOARD_EVENT_KEY_AND_CLICK_SYNC
            currentKey = pressedKey;
#endif
            state = KbProcState::KEY_UNPRESS;
        }
        
        break;

    // Wait for unpress key
    case KbProcState::KEY_UNPRESS:
        if(pressedKey == KbKeys::KEY_NONE)
        {
#ifndef KEYBOARD_EVENT_KEY_AND_CLICK_SYNC
            currentKey = pressedKey;
#endif
            state = KbProcState::INITIAL;
        }

        break;

    // Other states
    default:
        currentKey = KbKeys::KEY_NONE;
        currentClick = ClickType::NO_CLICK;
        state = KbProcState::INITIAL;
        break;
    }
}
I am DX168B and this is my favourite forum on internet!
uuu000
Открыл глаза
Сообщения: 44
Зарегистрирован: Пн окт 05, 2020 17:20:11

Re: Как поместить цикл в case оператора switch

Сообщение uuu000 »

Подскажите, если не трудно литературу по конечным автоматам .Может быть есть ссылки в интернете по этой теме.
Спасибо.
parovoZZ
Мудрый кот
Сообщения: 1759
Зарегистрирован: Пт июн 01, 2018 07:28:45

Re: Как поместить цикл в case оператора switch

Сообщение parovoZZ »

Цифровые устройства Пухальского и Новосельцевой.
Demiurg
Это не хвост, это антенна
Сообщения: 1480
Зарегистрирован: Ср июн 25, 2008 15:19:44
Контактная информация:

Re: Как поместить цикл в case оператора switch

Сообщение Demiurg »

Цикл статей Татарчевского. Здесь тоже статья, в ней ссылка на архив.
Моя реализация программных таймеров.
uuu000
Открыл глаза
Сообщения: 44
Зарегистрирован: Пн окт 05, 2020 17:20:11

Re: Как поместить цикл в case оператора switch

Сообщение uuu000 »

DX168B , metan!
Попробовал эту методику, с прерываниями и конечным автоматом. Все получилось.
Спасибо!
uuu000
Открыл глаза
Сообщения: 44
Зарегистрирован: Пн окт 05, 2020 17:20:11

Re: Как поместить цикл в case оператора switch

Сообщение uuu000 »

Возникла проблема. Хочу написать код для переключения кнопками(например три кнопки)
трех выходов. С помощью методики "конечных автоматов" попробовал сделать программу.
Но получился трехканальный переключатель без фиксации ,т.е. включение и выключение
любого канала происходит независимо от состояния других каналов.
Мне нужно превратить этот переключатель в переключатель с фиксацией. Т.е.
при нажатии на любую кнопку остальные выходы обнуляются(неважно что на них было -
0v,+5v или blink для светодиода) и на выходе канала с этой включенной кнопкой появляется
нужный сигнал +5v или blink.Причем выключить этот канал можно включением другого("переключение
с фиксацией") или просто повторным нажатием на кнопку включенного канала.
Проблема в том, что я не смог добиться отключения активного канала включением любого другого.
Перепробовал различные приемы выключения других выходов при включении нужного мне канала.
Помещал код типа PORTB&=~(1<<PB3); в switch для blink_click,пробовал в button_ KA после строки
поднятия флага fclk: if(fclk =1){PORTB&=~(1<<PB3)}.Номера портов здесь условные.
Я понимаю, что мне не хватает опыта для использования таких тонкостей.
Поэтому и прошу помощи.
Спасибо.
В архиве код и протеус
https://drive.google.com/file/d/1kOjc23 ... sp=sharing
Вложения
temp2.rar
(103.71 КБ) 95 скачиваний
Ответить

Вернуться в «AVR»